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

39
Cargo.lock generated
View File

@@ -1014,7 +1014,7 @@ dependencies = [
[[package]] [[package]]
name = "collections" name = "collections"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" source = "git+https://github.com/zed-industries/zed#a6b1514246c2efeefde5ed0f1fb18aac5c7cc8b2"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"rustc-hash 2.1.0", "rustc-hash 2.1.0",
@@ -1213,9 +1213,9 @@ dependencies = [
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.16" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -1318,7 +1318,7 @@ dependencies = [
[[package]] [[package]]
name = "derive_refineable" name = "derive_refineable"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" source = "git+https://github.com/zed-industries/zed#a6b1514246c2efeefde5ed0f1fb18aac5c7cc8b2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2013,7 +2013,7 @@ dependencies = [
[[package]] [[package]]
name = "gpui" name = "gpui"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" source = "git+https://github.com/zed-industries/zed#a6b1514246c2efeefde5ed0f1fb18aac5c7cc8b2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"as-raw-xcb-connection", "as-raw-xcb-connection",
@@ -2098,7 +2098,7 @@ dependencies = [
[[package]] [[package]]
name = "gpui_macros" name = "gpui_macros"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" source = "git+https://github.com/zed-industries/zed#a6b1514246c2efeefde5ed0f1fb18aac5c7cc8b2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2303,7 +2303,7 @@ dependencies = [
[[package]] [[package]]
name = "http_client" name = "http_client"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" source = "git+https://github.com/zed-industries/zed#a6b1514246c2efeefde5ed0f1fb18aac5c7cc8b2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@@ -2913,7 +2913,7 @@ dependencies = [
[[package]] [[package]]
name = "media" name = "media"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" source = "git+https://github.com/zed-industries/zed#a6b1514246c2efeefde5ed0f1fb18aac5c7cc8b2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bindgen", "bindgen",
@@ -3051,8 +3051,7 @@ checksum = "e664971378a3987224f7a0e10059782035e89899ae403718ee07de85bec42afe"
[[package]] [[package]]
name = "negentropy" name = "negentropy"
version = "0.4.3" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/rust-nostr/negentropy?rev=311013ce05dd3f670d9d9c444c09195837837271#311013ce05dd3f670d9d9c444c09195837837271"
checksum = "43a88da9dd148bbcdce323dd6ac47d369b4769d4a3b78c6c52389b9269f77932"
[[package]] [[package]]
name = "new_debug_unreachable" name = "new_debug_unreachable"
@@ -3092,7 +3091,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]] [[package]]
name = "nostr" name = "nostr"
version = "0.38.0" version = "0.38.0"
source = "git+https://github.com/rust-nostr/nostr#1470b8b00437e586fb86035484f942d6202db83a" source = "git+https://github.com/rust-nostr/nostr#d8dcb0697b1ebce28cce6f89747dc6ba73a45b56"
dependencies = [ dependencies = [
"aes", "aes",
"base64", "base64",
@@ -3120,7 +3119,7 @@ dependencies = [
[[package]] [[package]]
name = "nostr-database" name = "nostr-database"
version = "0.38.0" version = "0.38.0"
source = "git+https://github.com/rust-nostr/nostr#1470b8b00437e586fb86035484f942d6202db83a" source = "git+https://github.com/rust-nostr/nostr#d8dcb0697b1ebce28cce6f89747dc6ba73a45b56"
dependencies = [ dependencies = [
"flatbuffers", "flatbuffers",
"nostr", "nostr",
@@ -3130,7 +3129,7 @@ dependencies = [
[[package]] [[package]]
name = "nostr-lmdb" name = "nostr-lmdb"
version = "0.38.0" version = "0.38.0"
source = "git+https://github.com/rust-nostr/nostr#1470b8b00437e586fb86035484f942d6202db83a" source = "git+https://github.com/rust-nostr/nostr#d8dcb0697b1ebce28cce6f89747dc6ba73a45b56"
dependencies = [ dependencies = [
"async-utility", "async-utility",
"heed", "heed",
@@ -3141,7 +3140,7 @@ dependencies = [
[[package]] [[package]]
name = "nostr-relay-pool" name = "nostr-relay-pool"
version = "0.38.0" version = "0.38.0"
source = "git+https://github.com/rust-nostr/nostr#1470b8b00437e586fb86035484f942d6202db83a" source = "git+https://github.com/rust-nostr/nostr#d8dcb0697b1ebce28cce6f89747dc6ba73a45b56"
dependencies = [ dependencies = [
"async-utility", "async-utility",
"async-wsocket", "async-wsocket",
@@ -3157,7 +3156,7 @@ dependencies = [
[[package]] [[package]]
name = "nostr-sdk" name = "nostr-sdk"
version = "0.38.0" version = "0.38.0"
source = "git+https://github.com/rust-nostr/nostr#1470b8b00437e586fb86035484f942d6202db83a" source = "git+https://github.com/rust-nostr/nostr#d8dcb0697b1ebce28cce6f89747dc6ba73a45b56"
dependencies = [ dependencies = [
"async-utility", "async-utility",
"nostr", "nostr",
@@ -4164,7 +4163,7 @@ dependencies = [
[[package]] [[package]]
name = "refineable" name = "refineable"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" source = "git+https://github.com/zed-industries/zed#a6b1514246c2efeefde5ed0f1fb18aac5c7cc8b2"
dependencies = [ dependencies = [
"derive_refineable", "derive_refineable",
] ]
@@ -4305,7 +4304,7 @@ dependencies = [
[[package]] [[package]]
name = "reqwest_client" name = "reqwest_client"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" source = "git+https://github.com/zed-industries/zed#a6b1514246c2efeefde5ed0f1fb18aac5c7cc8b2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@@ -4655,7 +4654,7 @@ checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe"
[[package]] [[package]]
name = "semantic_version" name = "semantic_version"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" source = "git+https://github.com/zed-industries/zed#a6b1514246c2efeefde5ed0f1fb18aac5c7cc8b2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"serde", "serde",
@@ -4980,7 +4979,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "sum_tree" name = "sum_tree"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" source = "git+https://github.com/zed-industries/zed#a6b1514246c2efeefde5ed0f1fb18aac5c7cc8b2"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"log", "log",
@@ -5802,7 +5801,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "util" name = "util"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" source = "git+https://github.com/zed-industries/zed#a6b1514246c2efeefde5ed0f1fb18aac5c7cc8b2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-fs", "async-fs",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
use crate::views::app::{AddPanel, PanelKind}; use crate::views::app::{AddPanel, PanelKind};
use common::utils::message_ago; use common::utils::message_ago;
use gpui::{ use gpui::{
div, img, percentage, prelude::FluentBuilder, px, InteractiveElement, IntoElement, div, img, percentage, prelude::FluentBuilder, px, Context, InteractiveElement, IntoElement,
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, ViewContext, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window,
}; };
use registry::chat::ChatRegistry; use registry::chat::ChatRegistry;
use ui::{ use ui::{
@@ -18,7 +18,7 @@ pub struct Inbox {
} }
impl Inbox { impl Inbox {
pub fn new(_cx: &mut ViewContext<'_, Self>) -> Self { pub fn new(_window: &mut Window, _cx: &mut Context<'_, Self>) -> Self {
Self { Self {
label: "Inbox".into(), label: "Inbox".into(),
is_collapsed: false, 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(); let weak_model = cx.global::<ChatRegistry>().inbox();
if let Some(model) = weak_model.upgrade() { if let Some(model) = weak_model.upgrade() {
@@ -91,8 +91,8 @@ impl Inbox {
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)) .text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
.child(ago), .child(ago),
) )
.on_click(cx.listener(move |this, _, cx| { .on_click(cx.listener(move |this, _, window, cx| {
this.action(id, cx); this.action(id, window, cx);
})) }))
})) }))
} }
@@ -102,11 +102,13 @@ impl Inbox {
} }
} }
fn action(&self, id: u64, cx: &mut ViewContext<Self>) { fn action(&self, id: u64, _window: &mut Window, cx: &mut Context<Self>) {
cx.dispatch_action(Box::new(AddPanel { let action = AddPanel {
panel: PanelKind::Room(id), panel: PanelKind::Room(id),
position: DockPlacement::Center, position: DockPlacement::Center,
})) };
cx.dispatch_action(&action)
} }
} }
@@ -122,7 +124,7 @@ impl Collapsible for Inbox {
} }
impl Render 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() v_flex()
.px_2() .px_2()
.gap_1() .gap_1()
@@ -145,11 +147,13 @@ impl Render for Inbox {
) )
.child(self.label.clone()) .child(self.label.clone())
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))) .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; view.is_collapsed = !view.is_collapsed;
cx.notify(); 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 crate::views::sidebar::inbox::Inbox;
use compose::Compose; use compose::Compose;
use gpui::{ use gpui::{
div, px, AnyElement, AppContext, BorrowAppContext, Entity, EntityId, EventEmitter, FocusHandle, div, px, AnyElement, App, AppContext, BorrowAppContext, Context, Entity, EntityId,
FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render,
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WindowContext, SharedString, StatefulInteractiveElement, Styled, Window,
}; };
use registry::chat::ChatRegistry; use registry::chat::ChatRegistry;
use ui::{ use ui::{
button::{Button, ButtonRounded, ButtonVariants}, button::{Button, ButtonRounded, ButtonVariants},
dock_area::{ dock_area::panel::{Panel, PanelEvent},
panel::{Panel, PanelEvent},
state::PanelState,
},
popup_menu::PopupMenu, popup_menu::PopupMenu,
scroll::ScrollbarAxis, scroll::ScrollbarAxis,
theme::{scale::ColorScaleStep, ActiveTheme}, theme::{scale::ColorScaleStep, ActiveTheme},
@@ -28,33 +25,33 @@ pub struct Sidebar {
zoomable: bool, zoomable: bool,
focus_handle: FocusHandle, focus_handle: FocusHandle,
// Dock // Dock
inbox: View<Inbox>, inbox: Entity<Inbox>,
view_id: EntityId, view_id: EntityId,
} }
impl Sidebar { impl Sidebar {
pub fn new(cx: &mut WindowContext) -> View<Self> { pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new_view(Self::view) cx.new(|cx| Self::view(window, cx))
} }
fn view(cx: &mut ViewContext<Self>) -> Self { fn view(window: &mut Window, cx: &mut Context<Self>) -> Self {
let inbox = cx.new_view(Inbox::new); let inbox = cx.new(|cx| Inbox::new(window, cx));
Self { Self {
name: "Sidebar".into(), name: "Sidebar".into(),
closeable: true, closeable: true,
zoomable: true, zoomable: true,
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
view_id: cx.view().entity_id(), view_id: cx.model().entity_id(),
inbox, inbox,
} }
} }
fn show_compose(&mut self, cx: &mut ViewContext<Self>) { fn show_compose(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let compose = cx.new_view(Compose::new); let compose = cx.new(|cx| Compose::new(window, cx));
cx.open_modal(move |modal, cx| { window.open_modal(cx, move |modal, window, cx| {
let label = compose.read(cx).label(cx); let label = compose.read(cx).label(window, cx);
modal modal
.title("Direct Messages") .title("Direct Messages")
@@ -72,13 +69,13 @@ impl Sidebar {
.bold() .bold()
.rounded(ButtonRounded::Large) .rounded(ButtonRounded::Large)
.w_full() .w_full()
.on_click(cx.listener_for(&compose, |this, _, cx| { .on_click(window.listener_for(&compose, |this, _, window, cx| {
if let Some(room) = this.room(cx) { if let Some(room) = this.room(window, cx) {
cx.update_global::<ChatRegistry, _>(|this, cx| { cx.update_global::<ChatRegistry, _>(|this, cx| {
this.new_room(room, cx); this.new_room(room, cx);
}); });
cx.close_modal(); window.close_modal(cx);
} }
})), })),
), ),
@@ -92,41 +89,37 @@ impl Panel for Sidebar {
"Sidebar".into() "Sidebar".into()
} }
fn title(&self, _cx: &WindowContext) -> AnyElement { fn title(&self, _cx: &App) -> AnyElement {
self.name.clone().into_any_element() self.name.clone().into_any_element()
} }
fn closeable(&self, _cx: &WindowContext) -> bool { fn closeable(&self, _cx: &App) -> bool {
self.closeable self.closeable
} }
fn zoomable(&self, _cx: &WindowContext) -> bool { fn zoomable(&self, _cx: &App) -> bool {
self.zoomable 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) 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![] vec![]
} }
fn dump(&self, _cx: &AppContext) -> PanelState {
PanelState::new(self)
}
} }
impl EventEmitter<PanelEvent> for Sidebar {} impl EventEmitter<PanelEvent> for Sidebar {}
impl FocusableView for Sidebar { impl Focusable for Sidebar {
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle { fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
impl Render for Sidebar { 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() v_flex()
.scrollable(self.view_id, ScrollbarAxis::Vertical) .scrollable(self.view_id, ScrollbarAxis::Vertical)
.py_3() .py_3()
@@ -159,7 +152,7 @@ impl Render for Sidebar {
) )
.child("New Message") .child("New Message")
.hover(|this| this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))) .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()) .child(self.inbox.clone())

View File

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

View File

@@ -1,5 +1,5 @@
use common::constants::{ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID}; use common::constants::{ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID};
use gpui::{AppContext, Context, Global, Model, WeakModel, WindowContext}; use gpui::{App, AppContext, Entity, Global, WeakEntity, Window};
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use state::get_client; use state::get_client;
use std::time::Duration; use std::time::Duration;
@@ -7,15 +7,15 @@ use std::time::Duration;
use crate::contact::Contact; use crate::contact::Contact;
pub struct AppRegistry { pub struct AppRegistry {
user: Model<Option<Contact>>, user: Entity<Option<Contact>>,
pub is_loading: bool, pub is_loading: bool,
} }
impl Global for AppRegistry {} impl Global for AppRegistry {}
impl AppRegistry { impl AppRegistry {
pub fn set_global(cx: &mut AppContext) { pub fn set_global(cx: &mut App) {
let user: Model<Option<Contact>> = cx.new_model(|_| None); let user: Entity<Option<Contact>> = cx.new(|_| None);
let is_loading = true; let is_loading = true;
cx.observe(&user, |this, cx| { cx.observe(&user, |this, cx| {
@@ -76,15 +76,15 @@ impl AppRegistry {
cx.set_global(Self { user, is_loading }); cx.set_global(Self { user, is_loading });
} }
pub fn user(&self) -> WeakModel<Option<Contact>> { pub fn user(&self) -> WeakEntity<Option<Contact>> {
self.user.downgrade() self.user.downgrade()
} }
pub fn current_user(&self, cx: &WindowContext) -> Option<Contact> { pub fn current_user(&self, _window: &Window, cx: &App) -> Option<Contact> {
self.user.read(cx).clone() self.user.read(cx).clone()
} }
pub fn set_user(&mut self, contact: Contact, cx: &mut AppContext) { pub fn set_user(&mut self, contact: Contact, cx: &mut App) {
self.user.update(cx, |this, cx| { self.user.update(cx, |this, cx| {
*this = Some(contact); *this = Some(contact);
cx.notify(); cx.notify();
@@ -93,7 +93,7 @@ impl AppRegistry {
self.is_loading = false; self.is_loading = false;
} }
pub fn logout(&mut self, cx: &mut AppContext) { pub fn logout(&mut self, cx: &mut App) {
self.user.update(cx, |this, cx| { self.user.update(cx, |this, cx| {
*this = None; *this = None;
cx.notify(); cx.notify();

View File

@@ -1,14 +1,14 @@
use crate::room::Room; use crate::room::Room;
use anyhow::Error; use anyhow::Error;
use common::utils::{compare, room_hash}; use common::utils::{compare, room_hash};
use gpui::{AppContext, AsyncAppContext, Context, Global, Model, ModelContext, Task, WeakModel}; use gpui::{App, AppContext, AsyncApp, Context, Entity, Global, Task, WeakEntity};
use itertools::Itertools; use itertools::Itertools;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use state::get_client; use state::get_client;
use std::cmp::Reverse; use std::cmp::Reverse;
pub struct Inbox { pub struct Inbox {
pub rooms: Vec<Model<Room>>, pub rooms: Vec<Entity<Room>>,
pub is_loading: bool, pub is_loading: bool,
} }
@@ -20,11 +20,11 @@ impl Inbox {
} }
} }
pub fn current_rooms(&self, cx: &ModelContext<Self>) -> Vec<u64> { pub fn current_rooms(&self, cx: &Context<Self>) -> Vec<u64> {
self.rooms.iter().map(|room| room.read(cx).id).collect() self.rooms.iter().map(|room| room.read(cx).id).collect()
} }
pub fn load(&mut self, cx: AsyncAppContext) -> Task<Result<Vec<Event>, Error>> { pub fn load(&mut self, cx: AsyncApp) -> Task<Result<Vec<Event>, Error>> {
cx.background_executor().spawn(async move { cx.background_executor().spawn(async move {
let client = get_client(); let client = get_client();
let signer = client.signer().await?; let signer = client.signer().await?;
@@ -59,16 +59,16 @@ impl Default for Inbox {
} }
pub struct ChatRegistry { pub struct ChatRegistry {
inbox: Model<Inbox>, inbox: Entity<Inbox>,
} }
impl Global for ChatRegistry {} impl Global for ChatRegistry {}
impl ChatRegistry { impl ChatRegistry {
pub fn set_global(cx: &mut AppContext) { pub fn set_global(cx: &mut App) {
let inbox = cx.new_model(|_| Inbox::default()); let inbox = cx.new(|_| Inbox::default());
cx.observe_new_models::<Room>(|this, cx| { cx.observe_new::<Room>(|this, _window, cx| {
// Get all pubkeys to load metadata // Get all pubkeys to load metadata
let pubkeys = this.get_all_keys(); let pubkeys = this.get_all_keys();
@@ -92,7 +92,7 @@ impl ChatRegistry {
if let Ok(profiles) = query { if let Ok(profiles) = query {
if let Some(model) = weak_model.upgrade() { if let Some(model) = weak_model.upgrade() {
_ = async_cx.update_model(&model, |model, cx| { _ = async_cx.update_entity(&model, |model, cx| {
for profile in profiles.into_iter() { for profile in profiles.into_iter() {
model.set_metadata(profile.0, profile.1); model.set_metadata(profile.0, profile.1);
} }
@@ -108,22 +108,22 @@ impl ChatRegistry {
cx.set_global(Self { inbox }); cx.set_global(Self { inbox });
} }
pub fn load(&mut self, cx: &mut AppContext) { pub fn load(&mut self, cx: &mut App) {
self.inbox.update(cx, |this, cx| { self.inbox.update(cx, |this, cx| {
let task = this.load(cx.to_async()); let task = this.load(cx.to_async());
cx.spawn(|this, mut async_cx| async move { cx.spawn(|this, mut async_cx| async move {
if let Some(inbox) = this.upgrade() { if let Some(inbox) = this.upgrade() {
if let Ok(events) = task.await { if let Ok(events) = task.await {
_ = async_cx.update_model(&inbox, |this, cx| { _ = async_cx.update_entity(&inbox, |this, cx| {
let current_rooms = this.current_rooms(cx); let current_rooms = this.current_rooms(cx);
let items: Vec<Model<Room>> = events let items: Vec<Entity<Room>> = events
.into_iter() .into_iter()
.filter_map(|ev| { .filter_map(|ev| {
let id = room_hash(&ev.tags); let id = room_hash(&ev.tags);
// Filter all seen events // Filter all seen events
if !current_rooms.iter().any(|h| h == &id) { if !current_rooms.iter().any(|h| h == &id) {
Some(cx.new_model(|_| Room::parse(&ev))) Some(cx.new(|_| Room::parse(&ev)))
} else { } else {
None None
} }
@@ -142,11 +142,11 @@ impl ChatRegistry {
}); });
} }
pub fn inbox(&self) -> WeakModel<Inbox> { pub fn inbox(&self) -> WeakEntity<Inbox> {
self.inbox.downgrade() self.inbox.downgrade()
} }
pub fn room(&self, id: &u64, cx: &AppContext) -> Option<WeakModel<Room>> { pub fn room(&self, id: &u64, cx: &App) -> Option<WeakEntity<Room>> {
self.inbox self.inbox
.read(cx) .read(cx)
.rooms .rooms
@@ -155,8 +155,8 @@ impl ChatRegistry {
.map(|model| model.downgrade()) .map(|model| model.downgrade())
} }
pub fn new_room(&mut self, room: Room, cx: &mut AppContext) { pub fn new_room(&mut self, room: Room, cx: &mut App) {
let room = cx.new_model(|_| room); let room = cx.new(|_| room);
self.inbox.update(cx, |this, cx| { self.inbox.update(cx, |this, cx| {
if !this.rooms.iter().any(|r| r.read(cx) == room.read(cx)) { if !this.rooms.iter().any(|r| r.read(cx) == room.read(cx)) {
@@ -166,7 +166,7 @@ impl ChatRegistry {
}) })
} }
pub fn receive(&mut self, event: Event, cx: &mut AppContext) { pub fn receive(&mut self, event: Event, cx: &mut App) {
let mut pubkeys: Vec<_> = event.tags.public_keys().copied().collect(); let mut pubkeys: Vec<_> = event.tags.public_keys().copied().collect();
pubkeys.push(event.pubkey); pubkeys.push(event.pubkey);
@@ -180,7 +180,7 @@ impl ChatRegistry {
cx.notify(); cx.notify();
}) })
} else { } else {
let room = cx.new_model(|_| Room::parse(&event)); let room = cx.new(|_| Room::parse(&event));
self.inbox.update(cx, |this, cx| { self.inbox.update(cx, |this, cx| {
this.rooms.insert(0, room); this.rooms.insert(0, room);

View File

@@ -5,9 +5,9 @@ use crate::{
Disableable, Icon, Selectable, Sizable, Size, StyledExt, Disableable, Icon, Selectable, Sizable, Size, StyledExt,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Corners, Div, Edges, div, prelude::FluentBuilder as _, px, relative, AnyElement, App, ClickEvent, Corners, Div,
ElementId, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, Edges, ElementId, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels,
RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, WindowContext, RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, Window,
}; };
pub enum ButtonRounded { pub enum ButtonRounded {
@@ -64,7 +64,7 @@ pub trait ButtonVariants: Sized {
} }
impl ButtonCustomVariant { impl ButtonCustomVariant {
pub fn new(cx: &WindowContext) -> Self { pub fn new(_window: &Window, cx: &App) -> Self {
Self { Self {
color: cx.theme().accent.step(cx, ColorScaleStep::NINE), color: cx.theme().accent.step(cx, ColorScaleStep::NINE),
foreground: cx.theme().accent.step(cx, ColorScaleStep::ONE), foreground: cx.theme().accent.step(cx, ColorScaleStep::ONE),
@@ -136,7 +136,7 @@ impl ButtonVariant {
} }
} }
type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>; type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>;
/// A Button element. /// A Button element.
#[derive(IntoElement)] #[derive(IntoElement)]
@@ -253,7 +253,10 @@ impl Button {
self self
} }
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self { pub fn on_click(
mut self,
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.on_click = Some(Box::new(handler)); self.on_click = Some(Box::new(handler));
self self
} }
@@ -320,9 +323,9 @@ impl InteractiveElement for Button {
} }
impl RenderOnce for Button { impl RenderOnce for Button {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let style: ButtonVariant = self.variant; let style: ButtonVariant = self.variant;
let normal_style = style.normal(cx); let normal_style = style.normal(window, cx);
let icon_size = match self.size { let icon_size = match self.size {
Size::Size(v) => Size::Size(v * 0.75), Size::Size(v) => Size::Size(v * 0.75),
_ => self.size, _ => self.size,
@@ -384,7 +387,7 @@ impl RenderOnce for Button {
.when(self.border_edges.bottom, |this| this.border_b_1()) .when(self.border_edges.bottom, |this| this.border_b_1())
.text_color(normal_style.fg) .text_color(normal_style.fg)
.when(self.selected, |this| { .when(self.selected, |this| {
let selected_style = style.selected(cx); let selected_style = style.selected(window, cx);
this.bg(selected_style.bg) this.bg(selected_style.bg)
.border_color(selected_style.border) .border_color(selected_style.border)
.text_color(selected_style.fg) .text_color(selected_style.fg)
@@ -394,11 +397,11 @@ impl RenderOnce for Button {
.bg(normal_style.bg) .bg(normal_style.bg)
.when(normal_style.underline, |this| this.text_decoration_1()) .when(normal_style.underline, |this| this.text_decoration_1())
.hover(|this| { .hover(|this| {
let hover_style = style.hovered(cx); let hover_style = style.hovered(window, cx);
this.bg(hover_style.bg).border_color(hover_style.border) this.bg(hover_style.bg).border_color(hover_style.border)
}) })
.active(|this| { .active(|this| {
let active_style = style.active(cx); let active_style = style.active(window, cx);
this.bg(active_style.bg) this.bg(active_style.bg)
.border_color(active_style.border) .border_color(active_style.border)
.text_color(active_style.fg) .text_color(active_style.fg)
@@ -408,19 +411,19 @@ impl RenderOnce for Button {
self.on_click.filter(|_| !self.disabled && !self.loading), self.on_click.filter(|_| !self.disabled && !self.loading),
|this, on_click| { |this, on_click| {
let stop_propagation = self.stop_propagation; let stop_propagation = self.stop_propagation;
this.on_mouse_down(MouseButton::Left, move |_, cx| { this.on_mouse_down(MouseButton::Left, move |_, window, cx| {
cx.prevent_default(); window.prevent_default();
if stop_propagation { if stop_propagation {
cx.stop_propagation(); cx.stop_propagation();
} }
}) })
.on_click(move |event, cx| { .on_click(move |event, window, cx| {
(on_click)(event, cx); (on_click)(event, window, cx);
}) })
}, },
) )
.when(self.disabled, |this| { .when(self.disabled, |this| {
let disabled_style = style.disabled(cx); let disabled_style = style.disabled(window, cx);
this.cursor_not_allowed() this.cursor_not_allowed()
.bg(disabled_style.bg) .bg(disabled_style.bg)
.text_color(disabled_style.fg) .text_color(disabled_style.fg)
@@ -464,7 +467,7 @@ impl RenderOnce for Button {
}) })
.when(self.loading, |this| this.bg(normal_style.bg.opacity(0.8))) .when(self.loading, |this| this.bg(normal_style.bg.opacity(0.8)))
.when_some(self.tooltip.clone(), |this, tooltip| { .when_some(self.tooltip.clone(), |this, tooltip| {
this.tooltip(move |cx| Tooltip::new(tooltip.clone(), cx).into()) this.tooltip(move |window, cx| Tooltip::new(tooltip.clone(), window, cx).into())
}) })
} }
} }
@@ -478,7 +481,7 @@ struct ButtonVariantStyle {
} }
impl ButtonVariant { impl ButtonVariant {
fn bg_color(&self, cx: &WindowContext) -> Hsla { fn bg_color(&self, _window: &Window, cx: &App) -> Hsla {
match self { match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::NINE), ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::NINE),
ButtonVariant::Custom(colors) => colors.color, ButtonVariant::Custom(colors) => colors.color,
@@ -486,7 +489,7 @@ impl ButtonVariant {
} }
} }
fn text_color(&self, cx: &WindowContext) -> Hsla { fn text_color(&self, _window: &Window, cx: &App) -> Hsla {
match self { match self {
ButtonVariant::Primary => match cx.theme().accent.name().to_string().as_str() { ButtonVariant::Primary => match cx.theme().accent.name().to_string().as_str() {
"Sky" => cx.theme().base.darken(cx), "Sky" => cx.theme().base.darken(cx),
@@ -503,7 +506,7 @@ impl ButtonVariant {
} }
} }
fn border_color(&self, cx: &WindowContext) -> Hsla { fn border_color(&self, _window: &Window, cx: &App) -> Hsla {
match self { match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::NINE), ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::NINE),
ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => { ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => {
@@ -513,11 +516,11 @@ impl ButtonVariant {
} }
} }
fn underline(&self, _: &WindowContext) -> bool { fn underline(&self, _window: &Window, _cx: &App) -> bool {
matches!(self, ButtonVariant::Link) matches!(self, ButtonVariant::Link)
} }
fn shadow(&self, _: &WindowContext) -> bool { fn shadow(&self, _window: &Window, _cx: &App) -> bool {
match self { match self {
ButtonVariant::Primary => true, ButtonVariant::Primary => true,
ButtonVariant::Custom(c) => c.shadow, ButtonVariant::Custom(c) => c.shadow,
@@ -525,12 +528,12 @@ impl ButtonVariant {
} }
} }
fn normal(&self, cx: &WindowContext) -> ButtonVariantStyle { fn normal(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = self.bg_color(cx); let bg = self.bg_color(window, cx);
let border = self.border_color(cx); let border = self.border_color(window, cx);
let fg = self.text_color(cx); let fg = self.text_color(window, cx);
let underline = self.underline(cx); let underline = self.underline(window, cx);
let shadow = self.shadow(cx); let shadow = self.shadow(window, cx);
ButtonVariantStyle { ButtonVariantStyle {
bg, bg,
@@ -541,7 +544,7 @@ impl ButtonVariant {
} }
} }
fn hovered(&self, cx: &WindowContext) -> ButtonVariantStyle { fn hovered(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self { let bg = match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN), ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::FOUR), ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::FOUR),
@@ -549,14 +552,14 @@ impl ButtonVariant {
ButtonVariant::Text => cx.theme().transparent, ButtonVariant::Text => cx.theme().transparent,
ButtonVariant::Custom(colors) => colors.hover, ButtonVariant::Custom(colors) => colors.hover,
}; };
let border = self.border_color(cx); let border = self.border_color(window, cx);
let fg = match self { let fg = match self {
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::TWELVE), ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::TWELVE),
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::TEN), ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::TEN),
_ => self.text_color(cx), _ => self.text_color(window, cx),
}; };
let underline = self.underline(cx); let underline = self.underline(window, cx);
let shadow = self.shadow(cx); let shadow = self.shadow(window, cx);
ButtonVariantStyle { ButtonVariantStyle {
bg, bg,
@@ -567,7 +570,7 @@ impl ButtonVariant {
} }
} }
fn active(&self, cx: &WindowContext) -> ButtonVariantStyle { fn active(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self { let bg = match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN), ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE), ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE),
@@ -579,12 +582,12 @@ impl ButtonVariant {
let fg = match self { let fg = match self {
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::NINE), ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::NINE),
ButtonVariant::Text => cx.theme().base.step(cx, ColorScaleStep::ELEVEN), ButtonVariant::Text => cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
_ => self.text_color(cx), _ => self.text_color(window, cx),
}; };
let border = self.border_color(cx); let border = self.border_color(window, cx);
let underline = self.underline(cx); let underline = self.underline(window, cx);
let shadow = self.shadow(cx); let shadow = self.shadow(window, cx);
ButtonVariantStyle { ButtonVariantStyle {
bg, bg,
@@ -595,7 +598,7 @@ impl ButtonVariant {
} }
} }
fn selected(&self, cx: &WindowContext) -> ButtonVariantStyle { fn selected(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self { let bg = match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN), ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE), ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE),
@@ -607,12 +610,12 @@ impl ButtonVariant {
let fg = match self { let fg = match self {
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::TEN), ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Text => cx.theme().accent.step(cx, ColorScaleStep::TEN), ButtonVariant::Text => cx.theme().accent.step(cx, ColorScaleStep::TEN),
_ => self.text_color(cx), _ => self.text_color(window, cx),
}; };
let border = self.border_color(cx); let border = self.border_color(window, cx);
let underline = self.underline(cx); let underline = self.underline(window, cx);
let shadow = self.shadow(cx); let shadow = self.shadow(window, cx);
ButtonVariantStyle { ButtonVariantStyle {
bg, bg,
@@ -623,7 +626,7 @@ impl ButtonVariant {
} }
} }
fn disabled(&self, cx: &WindowContext) -> ButtonVariantStyle { fn disabled(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self { let bg = match self {
ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => { ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => {
cx.theme().transparent cx.theme().transparent
@@ -633,7 +636,7 @@ impl ButtonVariant {
let fg = cx.theme().base.step(cx, ColorScaleStep::ELEVEN); let fg = cx.theme().base.step(cx, ColorScaleStep::ELEVEN);
let border = bg; let border = bg;
let underline = self.underline(cx); let underline = self.underline(window, cx);
let shadow = false; let shadow = false;
ButtonVariantStyle { ButtonVariantStyle {

View File

@@ -1,6 +1,6 @@
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, Corners, Div, Edges, ElementId, InteractiveElement, div, prelude::FluentBuilder as _, App, Corners, Div, Edges, ElementId, InteractiveElement,
IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement as _, Styled, WindowContext, IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement as _, Styled, Window,
}; };
use std::{cell::Cell, rc::Rc}; use std::{cell::Cell, rc::Rc};
@@ -9,7 +9,7 @@ use crate::{
Disableable, Sizable, Size, Disableable, Sizable, Size,
}; };
type OnClick = Option<Box<dyn Fn(&Vec<usize>, &mut WindowContext) + 'static>>; type OnClick = Option<Box<dyn Fn(&Vec<usize>, &mut Window, &mut App) + 'static>>;
/// A ButtonGroup element, to wrap multiple buttons in a group. /// A ButtonGroup element, to wrap multiple buttons in a group.
#[derive(IntoElement)] #[derive(IntoElement)]
@@ -72,7 +72,10 @@ impl ButtonGroup {
/// Sets the on_click handler for the ButtonGroup. /// Sets the on_click handler for the ButtonGroup.
/// ///
/// The handler first argument is a vector of the selected button indices. /// The handler first argument is a vector of the selected button indices.
pub fn on_click(mut self, handler: impl Fn(&Vec<usize>, &mut WindowContext) + 'static) -> Self { pub fn on_click(
mut self,
handler: impl Fn(&Vec<usize>, &mut Window, &mut App) + 'static,
) -> Self {
self.on_click = Some(Box::new(handler)); self.on_click = Some(Box::new(handler));
self self
} }
@@ -99,7 +102,7 @@ impl ButtonVariants for ButtonGroup {
} }
impl RenderOnce for ButtonGroup { impl RenderOnce for ButtonGroup {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let children_len = self.children.len(); let children_len = self.children.len();
let mut selected_ixs: Vec<usize> = Vec::new(); let mut selected_ixs: Vec<usize> = Vec::new();
let state = Rc::new(Cell::new(None)); let state = Rc::new(Cell::new(None));
@@ -167,7 +170,7 @@ impl RenderOnce for ButtonGroup {
.stop_propagation(false) .stop_propagation(false)
.when_some(self.size, |this, size| this.with_size(size)) .when_some(self.size, |this, size| this.with_size(size))
.when_some(self.variant, |this, variant| this.with_variant(variant)) .when_some(self.variant, |this, variant| this.with_variant(variant))
.on_click(move |_, _| { .on_click(move |_, _, _| {
state.set(Some(child_index)); state.set(Some(child_index));
}) })
}), }),
@@ -175,7 +178,7 @@ impl RenderOnce for ButtonGroup {
.when_some( .when_some(
self.on_click.filter(|_| !self.disabled), self.on_click.filter(|_| !self.disabled),
move |this, on_click| { move |this, on_click| {
this.on_click(move |_, cx| { this.on_click(move |_, window, cx| {
let mut selected_ixs = selected_ixs.clone(); let mut selected_ixs = selected_ixs.clone();
if let Some(ix) = state.get() { if let Some(ix) = state.get() {
if self.multiple { if self.multiple {
@@ -190,7 +193,7 @@ impl RenderOnce for ButtonGroup {
} }
} }
on_click(&selected_ixs, cx); on_click(&selected_ixs, window, cx);
}) })
}, },
) )

View File

@@ -4,12 +4,12 @@ use crate::{
v_flex, Disableable, IconName, Selectable, v_flex, Disableable, IconName, Selectable,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, relative, svg, ElementId, InteractiveElement, IntoElement, div, prelude::FluentBuilder as _, relative, svg, App, ElementId, InteractiveElement,
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, Styled as _, IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _,
WindowContext, Styled as _, Window,
}; };
type OnClick = Option<Box<dyn Fn(&bool, &mut WindowContext) + 'static>>; type OnClick = Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>;
/// A Checkbox element. /// A Checkbox element.
#[derive(IntoElement)] #[derive(IntoElement)]
@@ -42,7 +42,7 @@ impl Checkbox {
self self
} }
pub fn on_click(mut self, handler: impl Fn(&bool, &mut WindowContext) + 'static) -> Self { pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Box::new(handler)); self.on_click = Some(Box::new(handler));
self self
} }
@@ -66,7 +66,7 @@ impl Selectable for Checkbox {
} }
impl RenderOnce for Checkbox { impl RenderOnce for Checkbox {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let (color, icon_color) = if self.disabled { let (color, icon_color) = if self.disabled {
( (
cx.theme().base.step(cx, ColorScaleStep::THREE), cx.theme().base.step(cx, ColorScaleStep::THREE),
@@ -131,9 +131,9 @@ impl RenderOnce for Checkbox {
.when_some( .when_some(
self.on_click.filter(|_| !self.disabled), self.on_click.filter(|_| !self.disabled),
|this, on_click| { |this, on_click| {
this.on_click(move |_, cx| { this.on_click(move |_, window, cx| {
let checked = !self.checked; let checked = !self.checked;
on_click(&checked, cx); on_click(&checked, window, cx);
}) })
}, },
) )

View File

@@ -1,23 +1,18 @@
use std::{cell::RefCell, rc::Rc, time::Duration};
use gpui::{
prelude::FluentBuilder, AnyElement, ClipboardItem, Element, ElementId, GlobalElementId,
IntoElement, LayoutId, ParentElement, SharedString, Styled, WindowContext,
};
use crate::{ use crate::{
button::{Button, ButtonVariants as _}, button::{Button, ButtonVariants as _},
h_flex, IconName, Sizable as _, h_flex, IconName, Sizable as _,
}; };
use gpui::{
type ContentBuilder = Option<Box<dyn Fn(&mut WindowContext) -> AnyElement>>; prelude::FluentBuilder, AnyElement, App, ClipboardItem, Element, ElementId, GlobalElementId,
type CopiedCallback = Option<Rc<dyn Fn(SharedString, &mut WindowContext)>>; IntoElement, LayoutId, ParentElement, SharedString, Styled, Window,
};
use std::{cell::RefCell, rc::Rc, time::Duration};
pub struct Clipboard { pub struct Clipboard {
id: ElementId, id: ElementId,
value: SharedString, value: SharedString,
content_builder: ContentBuilder, content_builder: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement>>,
copied_callback: CopiedCallback, copied_callback: Option<Rc<dyn Fn(SharedString, &mut Window, &mut App)>>,
} }
impl Clipboard { impl Clipboard {
@@ -35,18 +30,20 @@ impl Clipboard {
self self
} }
pub fn content<E, F>(mut self, element_builder: F) -> Self pub fn content<E, F>(mut self, builder: F) -> Self
where where
E: IntoElement, E: IntoElement,
F: Fn(&mut WindowContext) -> E + 'static, F: Fn(&mut Window, &mut App) -> E + 'static,
{ {
self.content_builder = Some(Box::new(move |cx| element_builder(cx).into_any_element())); self.content_builder = Some(Box::new(move |window, cx| {
builder(window, cx).into_any_element()
}));
self self
} }
pub fn on_copied<F>(mut self, handler: F) -> Self pub fn on_copied<F>(mut self, handler: F) -> Self
where where
F: Fn(SharedString, &mut WindowContext) + 'static, F: Fn(SharedString, &mut Window, &mut App) + 'static,
{ {
self.copied_callback = Some(Rc::new(handler)); self.copied_callback = Some(Rc::new(handler));
self self
@@ -78,15 +75,16 @@ impl Element for Clipboard {
fn request_layout( fn request_layout(
&mut self, &mut self,
global_id: Option<&GlobalElementId>, global_id: Option<&GlobalElementId>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) { ) -> (LayoutId, Self::RequestLayoutState) {
cx.with_element_state::<ClipboardState, _>(global_id.unwrap(), |state, cx| { window.with_element_state::<ClipboardState, _>(global_id.unwrap(), |state, window| {
let state = state.unwrap_or_default(); let state = state.unwrap_or_default();
let content_element = self let content_element = self
.content_builder .content_builder
.as_ref() .as_ref()
.map(|builder| builder(cx).into_any_element()); .map(|builder| builder(window, cx).into_any_element());
let value = self.value.clone(); let value = self.value.clone();
let clipboard_id = self.id.clone(); let clipboard_id = self.id.clone();
let copied_callback = self.copied_callback.as_ref().map(|c| c.clone()); let copied_callback = self.copied_callback.as_ref().map(|c| c.clone());
@@ -107,7 +105,7 @@ impl Element for Clipboard {
.ghost() .ghost()
.xsmall() .xsmall()
.when(!copide_value, |this| { .when(!copide_value, |this| {
this.on_click(move |_, cx| { this.on_click(move |_, window, cx| {
cx.stop_propagation(); cx.stop_propagation();
cx.write_to_clipboard(ClipboardItem::new_string(value.to_string())); cx.write_to_clipboard(ClipboardItem::new_string(value.to_string()));
*copied.borrow_mut() = true; *copied.borrow_mut() = true;
@@ -121,14 +119,14 @@ impl Element for Clipboard {
.detach(); .detach();
if let Some(callback) = &copied_callback { if let Some(callback) = &copied_callback {
callback(value.clone(), cx); callback(value.clone(), window, cx);
} }
}) })
}), }),
) )
.into_any_element(); .into_any_element();
((element.request_layout(cx), element), state) ((element.request_layout(window, cx), element), state)
}) })
} }
@@ -137,9 +135,10 @@ impl Element for Clipboard {
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
_: gpui::Bounds<gpui::Pixels>, _: gpui::Bounds<gpui::Pixels>,
element: &mut Self::RequestLayoutState, element: &mut Self::RequestLayoutState,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
element.prepaint(cx); element.prepaint(window, cx);
} }
fn paint( fn paint(
@@ -148,8 +147,9 @@ impl Element for Clipboard {
_: gpui::Bounds<gpui::Pixels>, _: gpui::Bounds<gpui::Pixels>,
element: &mut Self::RequestLayoutState, element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState, _: &mut Self::PrepaintState,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
element.paint(cx) element.paint(window, cx)
} }
} }

View File

@@ -1,30 +1,29 @@
use crate::popup_menu::PopupMenu; use crate::popup_menu::PopupMenu;
use gpui::{ use gpui::{
anchored, deferred, div, prelude::FluentBuilder, px, relative, AnyElement, Corner, anchored, deferred, div, prelude::FluentBuilder, px, relative, AnyElement, App, Context,
DismissEvent, DispatchPhase, Element, ElementId, Focusable, GlobalElementId, Corner, DismissEvent, DispatchPhase, Element, ElementId, Entity, Focusable, FocusableWrapper,
InteractiveElement, IntoElement, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, GlobalElementId, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, ParentElement,
Position, Stateful, Style, View, ViewContext, WindowContext, Pixels, Point, Position, Size, Stateful, Style, Window,
}; };
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
pub trait ContextMenuExt: ParentElement + Sized { pub trait ContextMenuExt: ParentElement + Sized {
fn context_menu( fn context_menu(
self, self,
f: impl Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static, f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
) -> Self { ) -> Self {
self.child(ContextMenu::new("context-menu").menu(f)) self.child(ContextMenu::new("context-menu").menu(f))
} }
} }
impl<E> ContextMenuExt for Stateful<E> where E: ParentElement {} impl<E> ContextMenuExt for Stateful<E> where E: ParentElement {}
impl<E> ContextMenuExt for Focusable<E> where E: ParentElement {} impl<E> ContextMenuExt for FocusableWrapper<E> where E: ParentElement {}
type Menu<M> = Option<Box<dyn Fn(PopupMenu, &mut ViewContext<M>) -> PopupMenu + 'static>>;
/// A context menu that can be shown on right-click. /// A context menu that can be shown on right-click.
pub struct ContextMenu { pub struct ContextMenu {
id: ElementId, id: ElementId,
menu: Menu<PopupMenu>, menu:
Option<Box<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static>>,
anchor: Corner, anchor: Corner,
} }
@@ -40,7 +39,7 @@ impl ContextMenu {
#[must_use] #[must_use]
pub fn menu<F>(mut self, builder: F) -> Self pub fn menu<F>(mut self, builder: F) -> Self
where where
F: Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static, F: Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
{ {
self.menu = Some(Box::new(builder)); self.menu = Some(Box::new(builder));
self self
@@ -49,14 +48,18 @@ impl ContextMenu {
fn with_element_state<R>( fn with_element_state<R>(
&mut self, &mut self,
id: &GlobalElementId, id: &GlobalElementId,
cx: &mut WindowContext, window: &mut Window,
f: impl FnOnce(&mut Self, &mut ContextMenuState, &mut WindowContext) -> R, cx: &mut App,
f: impl FnOnce(&mut Self, &mut ContextMenuState, &mut Window, &mut App) -> R,
) -> R { ) -> R {
cx.with_optional_element_state::<ContextMenuState, _>(Some(id), |element_state, cx| { window.with_optional_element_state::<ContextMenuState, _>(
Some(id),
|element_state, window| {
let mut element_state = element_state.unwrap().unwrap_or_default(); let mut element_state = element_state.unwrap().unwrap_or_default();
let result = f(self, &mut element_state, cx); let result = f(self, &mut element_state, window, cx);
(result, Some(element_state)) (result, Some(element_state))
}) },
)
} }
} }
@@ -69,7 +72,7 @@ impl IntoElement for ContextMenu {
} }
pub struct ContextMenuState { pub struct ContextMenuState {
menu_view: Rc<RefCell<Option<View<PopupMenu>>>>, menu_view: Rc<RefCell<Option<Entity<PopupMenu>>>>,
menu_element: Option<AnyElement>, menu_element: Option<AnyElement>,
open: Rc<RefCell<bool>>, open: Rc<RefCell<bool>>,
position: Rc<RefCell<Point<Pixels>>>, position: Rc<RefCell<Point<Pixels>>>,
@@ -97,23 +100,26 @@ impl Element for ContextMenu {
fn request_layout( fn request_layout(
&mut self, &mut self,
id: Option<&gpui::GlobalElementId>, id: Option<&gpui::GlobalElementId>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) { ) -> (gpui::LayoutId, Self::RequestLayoutState) {
// Set the layout style relative to the table view to get same size. let anchor = self.anchor;
let style = Style { let style = Style {
position: Position::Absolute, position: Position::Absolute,
flex_grow: 1.0, flex_grow: 1.0,
flex_shrink: 1.0, flex_shrink: 1.0,
size: gpui::Size { size: Size {
width: relative(1.).into(), width: relative(1.).into(),
height: relative(1.).into(), height: relative(1.).into(),
}, },
..Default::default() ..Default::default()
}; };
let anchor = self.anchor; self.with_element_state(
id.unwrap(),
self.with_element_state(id.unwrap(), cx, |_, state: &mut ContextMenuState, cx| { window,
cx,
|_, state: &mut ContextMenuState, window, cx| {
let position = state.position.clone(); let position = state.position.clone();
let position = position.borrow(); let position = position.borrow();
let open = state.open.clone(); let open = state.open.clone();
@@ -133,7 +139,7 @@ impl Element for ContextMenu {
.anchor(anchor) .anchor(anchor)
.when_some(menu_view, |this, menu| { .when_some(menu_view, |this, menu| {
// Focus the menu, so that can be handle the action. // Focus the menu, so that can be handle the action.
menu.focus_handle(cx).focus(cx); menu.focus_handle(cx).focus(window);
this.child(div().occlude().child(menu.clone())) this.child(div().occlude().child(menu.clone()))
}), }),
@@ -141,7 +147,7 @@ impl Element for ContextMenu {
.with_priority(1) .with_priority(1)
.into_any(); .into_any();
let menu_layout_id = menu_element.request_layout(cx); let menu_layout_id = menu_element.request_layout(window, cx);
(Some(menu_element), Some(menu_layout_id)) (Some(menu_element), Some(menu_layout_id))
} else { } else {
(None, None) (None, None)
@@ -155,7 +161,7 @@ impl Element for ContextMenu {
layout_ids.push(menu_layout_id); layout_ids.push(menu_layout_id);
} }
let layout_id = cx.request_layout(style, layout_ids); let layout_id = window.request_layout(style, layout_ids, cx);
( (
layout_id, layout_id,
@@ -165,7 +171,8 @@ impl Element for ContextMenu {
..Default::default() ..Default::default()
}, },
) )
}) },
)
} }
fn prepaint( fn prepaint(
@@ -173,10 +180,11 @@ impl Element for ContextMenu {
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
_: gpui::Bounds<gpui::Pixels>, _: gpui::Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState, request_layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self::PrepaintState { ) -> Self::PrepaintState {
if let Some(menu_element) = &mut request_layout.menu_element { if let Some(menu_element) = &mut request_layout.menu_element {
menu_element.prepaint(cx); menu_element.prepaint(window, cx);
} }
} }
@@ -186,10 +194,11 @@ impl Element for ContextMenu {
bounds: gpui::Bounds<gpui::Pixels>, bounds: gpui::Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState, request_layout: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState, _: &mut Self::PrepaintState,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
if let Some(menu_element) = &mut request_layout.menu_element { if let Some(menu_element) = &mut request_layout.menu_element {
menu_element.paint(cx); menu_element.paint(window, cx);
} }
let Some(builder) = self.menu.take() else { let Some(builder) = self.menu.take() else {
@@ -198,14 +207,15 @@ impl Element for ContextMenu {
self.with_element_state( self.with_element_state(
id.unwrap(), id.unwrap(),
window,
cx, cx,
|_view, state: &mut ContextMenuState, cx| { |_view, state: &mut ContextMenuState, window, _| {
let position = state.position.clone(); let position = state.position.clone();
let open = state.open.clone(); let open = state.open.clone();
let menu_view = state.menu_view.clone(); let menu_view = state.menu_view.clone();
// When right mouse click, to build content menu, and show it at the mouse position. // When right mouse click, to build content menu, and show it at the mouse position.
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
if phase == DispatchPhase::Bubble if phase == DispatchPhase::Bubble
&& event.button == MouseButton::Right && event.button == MouseButton::Right
&& bounds.contains(&event.position) && bounds.contains(&event.position)
@@ -213,19 +223,22 @@ impl Element for ContextMenu {
*position.borrow_mut() = event.position; *position.borrow_mut() = event.position;
*open.borrow_mut() = true; *open.borrow_mut() = true;
let menu = let menu = PopupMenu::build(window, cx, |menu, window, cx| {
PopupMenu::build(cx, |menu, cx| (builder)(menu, cx)).into_element(); (builder)(menu, window, cx)
})
.into_element();
let open = open.clone(); let open = open.clone();
cx.subscribe(&menu, move |_, _: &DismissEvent, cx| { window
.subscribe(&menu, cx, move |_, _: &DismissEvent, window, _| {
*open.borrow_mut() = false; *open.borrow_mut() = false;
cx.refresh(); window.refresh();
}) })
.detach(); .detach();
*menu_view.borrow_mut() = Some(menu); *menu_view.borrow_mut() = Some(menu);
cx.refresh(); window.refresh();
} }
}); });
}, },

View File

@@ -50,7 +50,7 @@ impl Styled for Divider {
} }
impl RenderOnce for Divider { impl RenderOnce for Divider {
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement { fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
self.base self.base
.flex() .flex()
.flex_shrink_0() .flex_shrink_0()

View File

@@ -6,10 +6,9 @@ use crate::{
AxisExt as _, StyledExt, AxisExt as _, StyledExt,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, Axis, Element, Entity, InteractiveElement as _, div, prelude::FluentBuilder as _, px, App, AppContext, Axis, Context, Element, Entity,
IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, InteractiveElement as _, IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels,
StatefulInteractiveElement, Style, Styled as _, View, ViewContext, VisualContext as _, Point, Render, StatefulInteractiveElement, Style, Styled as _, WeakEntity, Window,
WeakView, WindowContext,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
@@ -56,7 +55,7 @@ impl DockPlacement {
/// This is unlike Panel, it can't be move or add any other panel. /// This is unlike Panel, it can't be move or add any other panel.
pub struct Dock { pub struct Dock {
pub(super) placement: DockPlacement, pub(super) placement: DockPlacement,
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
pub(crate) panel: DockItem, pub(crate) panel: DockItem,
/// The size is means the width or height of the Dock, if the placement is left or right, the size is width, otherwise the size is height. /// The size is means the width or height of the Dock, if the placement is left or right, the size is width, otherwise the size is height.
pub(super) size: Pixels, pub(super) size: Pixels,
@@ -71,12 +70,13 @@ pub struct Dock {
impl Dock { impl Dock {
pub(crate) fn new( pub(crate) fn new(
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
placement: DockPlacement, placement: DockPlacement,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let panel = cx.new_view(|cx| { let panel = cx.new(|cx| {
let mut tab = TabPanel::new(None, dock_area.clone(), cx); let mut tab = TabPanel::new(None, dock_area.clone(), window, cx);
tab.closeable = false; tab.closeable = false;
tab tab
}); });
@@ -87,7 +87,7 @@ impl Dock {
view: panel.clone(), view: panel.clone(),
}; };
Self::subscribe_panel_events(dock_area.clone(), &panel, cx); Self::subscribe_panel_events(dock_area.clone(), &panel, window, cx);
Self { Self {
placement, placement,
@@ -100,22 +100,39 @@ impl Dock {
} }
} }
pub fn left(dock_area: WeakView<DockArea>, cx: &mut ViewContext<Self>) -> Self { pub fn left(
Self::new(dock_area, DockPlacement::Left, cx) dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
Self::new(dock_area, DockPlacement::Left, window, cx)
} }
pub fn bottom(dock_area: WeakView<DockArea>, cx: &mut ViewContext<Self>) -> Self { pub fn bottom(
Self::new(dock_area, DockPlacement::Bottom, cx) dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
Self::new(dock_area, DockPlacement::Bottom, window, cx)
} }
pub fn right(dock_area: WeakView<DockArea>, cx: &mut ViewContext<Self>) -> Self { pub fn right(
Self::new(dock_area, DockPlacement::Right, cx) dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
Self::new(dock_area, DockPlacement::Right, window, cx)
} }
/// Update the Dock to be collapsible or not. /// Update the Dock to be collapsible or not.
/// ///
/// And if the Dock is not collapsible, it will be open. /// And if the Dock is not collapsible, it will be open.
pub fn set_collapsible(&mut self, collapsible: bool, cx: &mut ViewContext<Self>) { pub fn set_collapsible(
&mut self,
collapsible: bool,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.collapsible = collapsible; self.collapsible = collapsible;
if !collapsible { if !collapsible {
self.open = true self.open = true
@@ -124,25 +141,26 @@ impl Dock {
} }
pub(super) fn from_state( pub(super) fn from_state(
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
placement: DockPlacement, placement: DockPlacement,
size: Pixels, size: Pixels,
panel: DockItem, panel: DockItem,
open: bool, open: bool,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self { ) -> Self {
Self::subscribe_panel_events(dock_area.clone(), &panel, cx); Self::subscribe_panel_events(dock_area.clone(), &panel, window, cx);
if !open { if !open {
match panel.clone() { match panel.clone() {
DockItem::Tabs { view, .. } => { DockItem::Tabs { view, .. } => {
view.update(cx, |panel, cx| { view.update(cx, |panel, cx| {
panel.set_collapsed(true, cx); panel.set_collapsed(true, window, cx);
}); });
} }
DockItem::Split { items, .. } => { DockItem::Split { items, .. } => {
for item in items { for item in items {
item.set_collapsed(true, cx); item.set_collapsed(true, window, cx);
} }
} }
_ => {} _ => {}
@@ -161,30 +179,31 @@ impl Dock {
} }
fn subscribe_panel_events( fn subscribe_panel_events(
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
panel: &DockItem, panel: &DockItem,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
match panel { match panel {
DockItem::Tabs { view, .. } => { DockItem::Tabs { view, .. } => {
cx.defer({ window.defer(cx, {
let view = view.clone(); let view = view.clone();
move |cx| { move |window, cx| {
_ = dock_area.update(cx, |this, cx| { _ = dock_area.update(cx, |this, cx| {
this.subscribe_panel(&view, cx); this.subscribe_panel(&view, window, cx);
}); });
} }
}); });
} }
DockItem::Split { items, view, .. } => { DockItem::Split { items, view, .. } => {
for item in items { for item in items {
Self::subscribe_panel_events(dock_area.clone(), item, cx); Self::subscribe_panel_events(dock_area.clone(), item, window, cx);
} }
cx.defer({ window.defer(cx, {
let view = view.clone(); let view = view.clone();
move |cx| { move |window, cx| {
_ = dock_area.update(cx, |this, cx| { _ = dock_area.update(cx, |this, cx| {
this.subscribe_panel(&view, cx); this.subscribe_panel(&view, window, cx);
}); });
} }
}); });
@@ -195,7 +214,7 @@ impl Dock {
} }
} }
pub fn set_panel(&mut self, panel: DockItem, cx: &mut ViewContext<Self>) { pub fn set_panel(&mut self, panel: DockItem, _window: &mut Window, cx: &mut Context<Self>) {
self.panel = panel; self.panel = panel;
cx.notify(); cx.notify();
} }
@@ -204,8 +223,8 @@ impl Dock {
self.open self.open
} }
pub fn toggle_open(&mut self, cx: &mut ViewContext<Self>) { pub fn toggle_open(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.set_open(!self.open, cx); self.set_open(!self.open, window, cx);
} }
/// Returns the size of the Dock, the size is means the width or height of /// Returns the size of the Dock, the size is means the width or height of
@@ -216,31 +235,40 @@ impl Dock {
} }
/// Set the size of the Dock. /// Set the size of the Dock.
pub fn set_size(&mut self, size: Pixels, cx: &mut ViewContext<Self>) { pub fn set_size(&mut self, size: Pixels, _window: &mut Window, cx: &mut Context<Self>) {
self.size = size.max(PANEL_MIN_SIZE); self.size = size.max(PANEL_MIN_SIZE);
cx.notify(); cx.notify();
} }
/// Set the open state of the Dock. /// Set the open state of the Dock.
pub fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) { pub fn set_open(&mut self, open: bool, window: &mut Window, cx: &mut Context<Self>) {
self.open = open; self.open = open;
let item = self.panel.clone(); let item = self.panel.clone();
cx.defer(move |_, cx| { cx.defer_in(window, move |_, window, cx| {
item.set_collapsed(!open, cx); item.set_collapsed(!open, window, cx);
}); });
cx.notify(); cx.notify();
} }
/// Add item to the Dock. /// Add item to the Dock.
pub fn add_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) { pub fn add_panel(
self.panel.add_panel(panel, &self.dock_area, cx); &mut self,
panel: Arc<dyn PanelView>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.panel.add_panel(panel, &self.dock_area, window, cx);
cx.notify(); cx.notify();
} }
fn render_resize_handle(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_resize_handle(
&mut self,
_window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let axis = self.placement.axis(); let axis = self.placement.axis();
let neg_offset = -HANDLE_PADDING; let neg_offset = -HANDLE_PADDING;
let view = cx.view().clone(); let view = cx.model().clone();
div() div()
.id("resize-handle") .id("resize-handle")
@@ -279,15 +307,20 @@ impl Dock {
.when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE)) .when(axis.is_horizontal(), |this| this.h_full().w(HANDLE_SIZE))
.when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)), .when(axis.is_vertical(), |this| this.w_full().h(HANDLE_SIZE)),
) )
.on_drag(ResizePanel {}, move |info, _, cx| { .on_drag(ResizePanel {}, move |info, _, _, cx| {
cx.stop_propagation(); cx.stop_propagation();
view.update(cx, |view, _| { view.update(cx, |view, _| {
view.is_resizing = true; view.is_resizing = true;
}); });
cx.new_view(|_| info.clone()) cx.new(|_| info.clone())
}) })
} }
fn resize(&mut self, mouse_position: Point<Pixels>, cx: &mut ViewContext<Self>) { fn resize(
&mut self,
mouse_position: Point<Pixels>,
_window: &mut Window,
cx: &mut Context<Self>,
) {
if !self.is_resizing { if !self.is_resizing {
return; return;
} }
@@ -303,7 +336,7 @@ impl Dock {
// Get the size of the left dock if it's open and not the current dock // Get the size of the left dock if it's open and not the current dock
if let Some(left_dock) = &dock_area.left_dock { if let Some(left_dock) = &dock_area.left_dock {
if left_dock.entity_id() != cx.view().entity_id() { if left_dock.entity_id() != cx.model().entity_id() {
let left_dock_read = left_dock.read(cx); let left_dock_read = left_dock.read(cx);
if left_dock_read.is_open() { if left_dock_read.is_open() {
left_dock_size = left_dock_read.size; left_dock_size = left_dock_read.size;
@@ -313,7 +346,7 @@ impl Dock {
// Get the size of the right dock if it's open and not the current dock // Get the size of the right dock if it's open and not the current dock
if let Some(right_dock) = &dock_area.right_dock { if let Some(right_dock) = &dock_area.right_dock {
if right_dock.entity_id() != cx.view().entity_id() { if right_dock.entity_id() != cx.model().entity_id() {
let right_dock_read = right_dock.read(cx); let right_dock_read = right_dock.read(cx);
if right_dock_read.is_open() { if right_dock_read.is_open() {
right_dock_size = right_dock_read.size; right_dock_size = right_dock_read.size;
@@ -346,13 +379,13 @@ impl Dock {
cx.notify(); cx.notify();
} }
fn done_resizing(&mut self, _: &mut ViewContext<Self>) { fn done_resizing(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
self.is_resizing = false; self.is_resizing = false;
} }
} }
impl Render for Dock { impl Render for Dock {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl gpui::IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {
if !self.open && !self.placement.is_bottom() { if !self.open && !self.placement.is_bottom() {
return div(); return div();
} }
@@ -374,15 +407,15 @@ impl Render for Dock {
DockItem::Tabs { view, .. } => this.child(view.clone()), DockItem::Tabs { view, .. } => this.child(view.clone()),
DockItem::Panel { view, .. } => this.child(view.clone().view()), DockItem::Panel { view, .. } => this.child(view.clone().view()),
}) })
.child(self.render_resize_handle(cx)) .child(self.render_resize_handle(window, cx))
.child(DockElement { .child(DockElement {
view: cx.view().clone(), view: cx.model().clone(),
}) })
} }
} }
struct DockElement { struct DockElement {
view: View<Dock>, view: Entity<Dock>,
} }
impl IntoElement for DockElement { impl IntoElement for DockElement {
@@ -404,9 +437,10 @@ impl Element for DockElement {
fn request_layout( fn request_layout(
&mut self, &mut self,
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
cx: &mut WindowContext, window: &mut gpui::Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) { ) -> (gpui::LayoutId, Self::RequestLayoutState) {
(cx.request_layout(Style::default(), None), ()) (window.request_layout(Style::default(), None, cx), ())
} }
fn prepaint( fn prepaint(
@@ -414,7 +448,8 @@ impl Element for DockElement {
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
_: gpui::Bounds<Pixels>, _: gpui::Bounds<Pixels>,
_: &mut Self::RequestLayoutState, _: &mut Self::RequestLayoutState,
_: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Self::PrepaintState { ) -> Self::PrepaintState {
} }
@@ -424,23 +459,24 @@ impl Element for DockElement {
_: gpui::Bounds<Pixels>, _: gpui::Bounds<Pixels>,
_: &mut Self::RequestLayoutState, _: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState, _: &mut Self::PrepaintState,
cx: &mut WindowContext, window: &mut gpui::Window,
_: &mut App,
) { ) {
cx.on_mouse_event({ window.on_mouse_event({
let view = self.view.clone(); let view = self.view.clone();
move |e: &MouseMoveEvent, phase, cx| { move |e: &MouseMoveEvent, phase, window, cx| {
if phase.bubble() { if phase.bubble() {
view.update(cx, |view, cx| view.resize(e.position, cx)) view.update(cx, |view, cx| view.resize(e.position, window, cx))
} }
} }
}); });
// When any mouse up, stop dragging // When any mouse up, stop dragging
cx.on_mouse_event({ window.on_mouse_event({
let view = self.view.clone(); let view = self.view.clone();
move |_: &MouseUpEvent, phase, cx| { move |_: &MouseUpEvent, phase, window, cx| {
if phase.bubble() { if phase.bubble() {
view.update(cx, |view, cx| view.done_resizing(cx)); view.update(cx, |view, cx| view.done_resizing(window, cx));
} }
} }
}) })

View File

@@ -1,61 +0,0 @@
use super::PanelEvent;
use crate::{
dock_area::panel::Panel,
dock_area::state::PanelState,
theme::{scale::ColorScaleStep, ActiveTheme},
};
use gpui::{
AppContext, EventEmitter, FocusHandle, FocusableView, ParentElement as _, Render, SharedString,
Styled as _, WindowContext,
};
pub(crate) struct InvalidPanel {
name: SharedString,
focus_handle: FocusHandle,
old_state: PanelState,
}
impl InvalidPanel {
pub(crate) fn new(name: &str, state: PanelState, cx: &mut WindowContext) -> Self {
Self {
focus_handle: cx.focus_handle(),
name: SharedString::from(name.to_owned()),
old_state: state,
}
}
}
impl Panel for InvalidPanel {
fn panel_id(&self) -> SharedString {
"InvalidPanel".into()
}
fn dump(&self, _cx: &AppContext) -> PanelState {
self.old_state.clone()
}
}
impl EventEmitter<PanelEvent> for InvalidPanel {}
impl FocusableView for InvalidPanel {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for InvalidPanel {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
gpui::div()
.size_full()
.my_6()
.flex()
.flex_col()
.items_center()
.justify_center()
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
.child(format!(
"The `{}` panel type is not registered in PanelRegistry.",
self.name.clone()
))
}
}

View File

@@ -2,30 +2,20 @@ use crate::dock_area::{
dock::{Dock, DockPlacement}, dock::{Dock, DockPlacement},
panel::{Panel, PanelEvent, PanelStyle, PanelView}, panel::{Panel, PanelEvent, PanelStyle, PanelView},
stack_panel::StackPanel, stack_panel::StackPanel,
state::{DockAreaState, DockState},
tab_panel::TabPanel, tab_panel::TabPanel,
}; };
use anyhow::Result;
use gpui::{ use gpui::{
actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, AppContext, Axis, Bounds, actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, App, AppContext, Axis,
Edges, Entity as _, EntityId, EventEmitter, InteractiveElement as _, IntoElement, Bounds, Context, Edges, Entity, EntityId, EventEmitter, InteractiveElement as _, IntoElement,
ParentElement as _, Pixels, Render, SharedString, Styled, Subscription, View, ViewContext, ParentElement as _, Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window,
VisualContext, WeakView, WindowContext,
}; };
use panel::PanelRegistry;
use std::sync::Arc; use std::sync::Arc;
pub mod dock; pub mod dock;
pub mod invalid_panel;
pub mod panel; pub mod panel;
pub mod stack_panel; pub mod stack_panel;
pub mod state;
pub mod tab_panel; pub mod tab_panel;
pub fn init(cx: &mut AppContext) {
cx.set_global(PanelRegistry::new());
}
actions!(dock, [ToggleZoom, ClosePanel]); actions!(dock, [ToggleZoom, ClosePanel]);
pub enum DockEvent { pub enum DockEvent {
@@ -50,11 +40,11 @@ pub struct DockArea {
toggle_button_panels: Edges<Option<EntityId>>, toggle_button_panels: Edges<Option<EntityId>>,
/// The left dock of the dock_area. /// The left dock of the dock_area.
left_dock: Option<View<Dock>>, left_dock: Option<Entity<Dock>>,
/// The bottom dock of the dock_area. /// The bottom dock of the dock_area.
bottom_dock: Option<View<Dock>>, bottom_dock: Option<Entity<Dock>>,
/// The right dock of the dock_area. /// The right dock of the dock_area.
right_dock: Option<View<Dock>>, right_dock: Option<Entity<Dock>>,
/// The top zoom view of the dock_area, if any. /// The top zoom view of the dock_area, if any.
zoom_view: Option<AnyView>, zoom_view: Option<AnyView>,
@@ -75,13 +65,13 @@ pub enum DockItem {
axis: Axis, axis: Axis,
items: Vec<DockItem>, items: Vec<DockItem>,
sizes: Vec<Option<Pixels>>, sizes: Vec<Option<Pixels>>,
view: View<StackPanel>, view: Entity<StackPanel>,
}, },
/// Tab layout /// Tab layout
Tabs { Tabs {
items: Vec<Arc<dyn PanelView>>, items: Vec<Arc<dyn PanelView>>,
active_ix: usize, active_ix: usize,
view: View<TabPanel>, view: Entity<TabPanel>,
}, },
/// Panel layout /// Panel layout
Panel { view: Arc<dyn PanelView> }, Panel { view: Arc<dyn PanelView> },
@@ -92,11 +82,13 @@ impl DockItem {
pub fn split( pub fn split(
axis: Axis, axis: Axis,
items: Vec<DockItem>, items: Vec<DockItem>,
dock_area: &WeakView<DockArea>, dock_area: &WeakEntity<DockArea>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self { ) -> Self {
let sizes = vec![None; items.len()]; let sizes = vec![None; items.len()];
Self::split_with_sizes(axis, items, sizes, dock_area, cx)
Self::split_with_sizes(axis, items, sizes, dock_area, window, cx)
} }
/// Create DockItem with split layout, each item of panel have specified size. /// Create DockItem with split layout, each item of panel have specified size.
@@ -107,34 +99,35 @@ impl DockItem {
axis: Axis, axis: Axis,
items: Vec<DockItem>, items: Vec<DockItem>,
sizes: Vec<Option<Pixels>>, sizes: Vec<Option<Pixels>>,
dock_area: &WeakView<DockArea>, dock_area: &WeakEntity<DockArea>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self { ) -> Self {
let mut items = items; let mut items = items;
let stack_panel = cx.new_view(|cx| { let stack_panel = cx.new(|cx| {
let mut stack_panel = StackPanel::new(axis, cx); let mut stack_panel = StackPanel::new(axis, window, cx);
for (i, item) in items.iter_mut().enumerate() { for (i, item) in items.iter_mut().enumerate() {
let view = item.view(); let view = item.view();
let size = sizes.get(i).copied().flatten(); let size = sizes.get(i).copied().flatten();
stack_panel.add_panel(view.clone(), size, dock_area.clone(), cx) stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx)
} }
for (i, item) in items.iter().enumerate() { for (i, item) in items.iter().enumerate() {
let view = item.view(); let view = item.view();
let size = sizes.get(i).copied().flatten(); let size = sizes.get(i).copied().flatten();
stack_panel.add_panel(view.clone(), size, dock_area.clone(), cx) stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx)
} }
stack_panel stack_panel
}); });
cx.defer({ window.defer(cx, {
let stack_panel = stack_panel.clone(); let stack_panel = stack_panel.clone();
let dock_area = dock_area.clone(); let dock_area = dock_area.clone();
move |cx| { move |window, cx| {
_ = dock_area.update(cx, |this, cx| { _ = dock_area.update(cx, |this, cx| {
this.subscribe_panel(&stack_panel, cx); this.subscribe_panel(&stack_panel, window, cx);
}); });
} }
}); });
@@ -158,35 +151,38 @@ impl DockItem {
pub fn tabs( pub fn tabs(
items: Vec<Arc<dyn PanelView>>, items: Vec<Arc<dyn PanelView>>,
active_ix: Option<usize>, active_ix: Option<usize>,
dock_area: &WeakView<DockArea>, dock_area: &WeakEntity<DockArea>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self { ) -> Self {
let mut new_items: Vec<Arc<dyn PanelView>> = vec![]; let mut new_items: Vec<Arc<dyn PanelView>> = vec![];
for item in items.into_iter() { for item in items.into_iter() {
new_items.push(item) new_items.push(item)
} }
Self::new_tabs(new_items, active_ix, dock_area, cx) Self::new_tabs(new_items, active_ix, dock_area, window, cx)
} }
pub fn tab<P: Panel>( pub fn tab<P: Panel>(
item: View<P>, item: Entity<P>,
dock_area: &WeakView<DockArea>, dock_area: &WeakEntity<DockArea>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self { ) -> Self {
Self::new_tabs(vec![Arc::new(item.clone())], None, dock_area, cx) Self::new_tabs(vec![Arc::new(item.clone())], None, dock_area, window, cx)
} }
fn new_tabs( fn new_tabs(
items: Vec<Arc<dyn PanelView>>, items: Vec<Arc<dyn PanelView>>,
active_ix: Option<usize>, active_ix: Option<usize>,
dock_area: &WeakView<DockArea>, dock_area: &WeakEntity<DockArea>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self { ) -> Self {
let active_ix = active_ix.unwrap_or(0); let active_ix = active_ix.unwrap_or(0);
let tab_panel = cx.new_view(|cx| { let tab_panel = cx.new(|cx| {
let mut tab_panel = TabPanel::new(None, dock_area.clone(), cx); let mut tab_panel = TabPanel::new(None, dock_area.clone(), window, cx);
for item in items.iter() { for item in items.iter() {
tab_panel.add_panel(item.clone(), cx) tab_panel.add_panel(item.clone(), window, cx)
} }
tab_panel.active_ix = active_ix; tab_panel.active_ix = active_ix;
tab_panel tab_panel
@@ -223,14 +219,15 @@ impl DockItem {
pub fn add_panel( pub fn add_panel(
&mut self, &mut self,
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
dock_area: &WeakView<DockArea>, dock_area: &WeakEntity<DockArea>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
match self { match self {
Self::Tabs { view, items, .. } => { Self::Tabs { view, items, .. } => {
items.push(panel.clone()); items.push(panel.clone());
view.update(cx, |tab_panel, cx| { view.update(cx, |tab_panel, cx| {
tab_panel.add_panel(panel, cx); tab_panel.add_panel(panel, window, cx);
}); });
} }
Self::Split { view, items, .. } => { Self::Split { view, items, .. } => {
@@ -238,34 +235,34 @@ impl DockItem {
for item in items.iter_mut() { for item in items.iter_mut() {
if let DockItem::Tabs { view, .. } = item { if let DockItem::Tabs { view, .. } = item {
view.update(cx, |tab_panel, cx| { view.update(cx, |tab_panel, cx| {
tab_panel.add_panel(panel.clone(), cx); tab_panel.add_panel(panel.clone(), window, cx);
}); });
return; return;
} }
} }
// Unable to find tabs, create new tabs // Unable to find tabs, create new tabs
let new_item = Self::tabs(vec![panel.clone()], None, dock_area, cx); let new_item = Self::tabs(vec![panel.clone()], None, dock_area, window, cx);
items.push(new_item.clone()); items.push(new_item.clone());
view.update(cx, |stack_panel, cx| { view.update(cx, |stack_panel, cx| {
stack_panel.add_panel(new_item.view(), None, dock_area.clone(), cx); stack_panel.add_panel(new_item.view(), None, dock_area.clone(), window, cx);
}); });
} }
Self::Panel { .. } => {} Self::Panel { .. } => {}
} }
} }
pub fn set_collapsed(&self, collapsed: bool, cx: &mut WindowContext) { pub fn set_collapsed(&self, collapsed: bool, window: &mut Window, cx: &mut App) {
match self { match self {
DockItem::Tabs { view, .. } => { DockItem::Tabs { view, .. } => {
view.update(cx, |tab_panel, cx| { view.update(cx, |tab_panel, cx| {
tab_panel.set_collapsed(collapsed, cx); tab_panel.set_collapsed(collapsed, window, cx);
}); });
} }
DockItem::Split { items, .. } => { DockItem::Split { items, .. } => {
// For each child item, set collapsed state // For each child item, set collapsed state
for item in items { for item in items {
item.set_collapsed(collapsed, cx); item.set_collapsed(collapsed, window, cx);
} }
} }
DockItem::Panel { .. } => {} DockItem::Panel { .. } => {}
@@ -273,7 +270,7 @@ impl DockItem {
} }
/// Recursively traverses to find the left-most and top-most TabPanel. /// Recursively traverses to find the left-most and top-most TabPanel.
pub(crate) fn left_top_tab_panel(&self, cx: &AppContext) -> Option<View<TabPanel>> { pub(crate) fn left_top_tab_panel(&self, cx: &App) -> Option<Entity<TabPanel>> {
match self { match self {
DockItem::Tabs { view, .. } => Some(view.clone()), DockItem::Tabs { view, .. } => Some(view.clone()),
DockItem::Split { view, .. } => view.read(cx).left_top_tab_panel(true, cx), DockItem::Split { view, .. } => view.read(cx).left_top_tab_panel(true, cx),
@@ -282,7 +279,7 @@ impl DockItem {
} }
/// Recursively traverses to find the right-most and top-most TabPanel. /// Recursively traverses to find the right-most and top-most TabPanel.
pub(crate) fn right_top_tab_panel(&self, cx: &AppContext) -> Option<View<TabPanel>> { pub(crate) fn right_top_tab_panel(&self, cx: &App) -> Option<Entity<TabPanel>> {
match self { match self {
DockItem::Tabs { view, .. } => Some(view.clone()), DockItem::Tabs { view, .. } => Some(view.clone()),
DockItem::Split { view, .. } => view.read(cx).right_top_tab_panel(true, cx), DockItem::Split { view, .. } => view.read(cx).right_top_tab_panel(true, cx),
@@ -295,9 +292,10 @@ impl DockArea {
pub fn new( pub fn new(
id: impl Into<SharedString>, id: impl Into<SharedString>,
version: Option<usize>, version: Option<usize>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let stack_panel = cx.new_view(|cx| StackPanel::new(Axis::Horizontal, cx)); let stack_panel = cx.new(|cx| StackPanel::new(Axis::Horizontal, window, cx));
let dock_item = DockItem::Split { let dock_item = DockItem::Split {
axis: Axis::Horizontal, axis: Axis::Horizontal,
@@ -321,7 +319,7 @@ impl DockArea {
_subscriptions: vec![], _subscriptions: vec![],
}; };
this.subscribe_panel(&stack_panel, cx); this.subscribe_panel(&stack_panel, window, cx);
this this
} }
@@ -333,7 +331,7 @@ impl DockArea {
} }
/// Set version of the dock area. /// Set version of the dock area.
pub fn set_version(&mut self, version: usize, cx: &mut ViewContext<Self>) { pub fn set_version(&mut self, version: usize, _window: &mut Window, cx: &mut Context<Self>) {
self.version = Some(version); self.version = Some(version);
cx.notify(); cx.notify();
} }
@@ -341,10 +339,10 @@ impl DockArea {
/// The DockItem as the center of the dock area. /// The DockItem as the center of the dock area.
/// ///
/// This is used to render at the Center of the DockArea. /// This is used to render at the Center of the DockArea.
pub fn set_center(&mut self, item: DockItem, cx: &mut ViewContext<Self>) { pub fn set_center(&mut self, item: DockItem, window: &mut Window, cx: &mut Context<Self>) {
self.subscribe_item(&item, cx); self.subscribe_item(&item, window, cx);
self.items = item; self.items = item;
self.update_toggle_button_tab_panels(cx); self.update_toggle_button_tab_panels(window, cx);
cx.notify(); cx.notify();
} }
@@ -353,20 +351,21 @@ impl DockArea {
panel: DockItem, panel: DockItem,
size: Option<Pixels>, size: Option<Pixels>,
open: bool, open: bool,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
self.subscribe_item(&panel, cx); self.subscribe_item(&panel, window, cx);
let weak_self = cx.view().downgrade(); let weak_self = cx.model().downgrade();
self.left_dock = Some(cx.new_view(|cx| { self.left_dock = Some(cx.new(|cx| {
let mut dock = Dock::left(weak_self.clone(), cx); let mut dock = Dock::left(weak_self.clone(), window, cx);
if let Some(size) = size { if let Some(size) = size {
dock.set_size(size, cx); dock.set_size(size, window, cx);
} }
dock.set_panel(panel, cx); dock.set_panel(panel, window, cx);
dock.set_open(open, cx); dock.set_open(open, window, cx);
dock dock
})); }));
self.update_toggle_button_tab_panels(cx); self.update_toggle_button_tab_panels(window, cx);
} }
pub fn set_bottom_dock( pub fn set_bottom_dock(
@@ -374,20 +373,21 @@ impl DockArea {
panel: DockItem, panel: DockItem,
size: Option<Pixels>, size: Option<Pixels>,
open: bool, open: bool,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
self.subscribe_item(&panel, cx); self.subscribe_item(&panel, window, cx);
let weak_self = cx.view().downgrade(); let weak_self = cx.model().downgrade();
self.bottom_dock = Some(cx.new_view(|cx| { self.bottom_dock = Some(cx.new(|cx| {
let mut dock = Dock::bottom(weak_self.clone(), cx); let mut dock = Dock::bottom(weak_self.clone(), window, cx);
if let Some(size) = size { if let Some(size) = size {
dock.set_size(size, cx); dock.set_size(size, window, cx);
} }
dock.set_panel(panel, cx); dock.set_panel(panel, window, cx);
dock.set_open(open, cx); dock.set_open(open, window, cx);
dock dock
})); }));
self.update_toggle_button_tab_panels(cx); self.update_toggle_button_tab_panels(window, cx);
} }
pub fn set_right_dock( pub fn set_right_dock(
@@ -395,24 +395,25 @@ impl DockArea {
panel: DockItem, panel: DockItem,
size: Option<Pixels>, size: Option<Pixels>,
open: bool, open: bool,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
self.subscribe_item(&panel, cx); self.subscribe_item(&panel, window, cx);
let weak_self = cx.view().downgrade(); let weak_self = cx.model().downgrade();
self.right_dock = Some(cx.new_view(|cx| { self.right_dock = Some(cx.new(|cx| {
let mut dock = Dock::right(weak_self.clone(), cx); let mut dock = Dock::right(weak_self.clone(), window, cx);
if let Some(size) = size { if let Some(size) = size {
dock.set_size(size, cx); dock.set_size(size, window, cx);
} }
dock.set_panel(panel, cx); dock.set_panel(panel, window, cx);
dock.set_open(open, cx); dock.set_open(open, window, cx);
dock dock
})); }));
self.update_toggle_button_tab_panels(cx); self.update_toggle_button_tab_panels(window, cx);
} }
/// Set locked state of the dock area, if locked, the dock area cannot be split or move, but allows to resize panels. /// Set locked state of the dock area, if locked, the dock area cannot be split or move, but allows to resize panels.
pub fn set_locked(&mut self, locked: bool, _: &mut WindowContext) { pub fn set_locked(&mut self, locked: bool, _window: &mut Window, _cx: &mut App) {
self.is_locked = locked; self.is_locked = locked;
} }
@@ -432,7 +433,7 @@ impl DockArea {
} }
/// Determine if the dock at the given placement is open. /// Determine if the dock at the given placement is open.
pub fn is_dock_open(&self, placement: DockPlacement, cx: &AppContext) -> bool { pub fn is_dock_open(&self, placement: DockPlacement, cx: &App) -> bool {
match placement { match placement {
DockPlacement::Left => self DockPlacement::Left => self
.left_dock .left_dock
@@ -459,29 +460,30 @@ impl DockArea {
pub fn set_dock_collapsible( pub fn set_dock_collapsible(
&mut self, &mut self,
collapsible_edges: Edges<bool>, collapsible_edges: Edges<bool>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
if let Some(left_dock) = self.left_dock.as_ref() { if let Some(left_dock) = self.left_dock.as_ref() {
left_dock.update(cx, |dock, cx| { left_dock.update(cx, |dock, cx| {
dock.set_collapsible(collapsible_edges.left, cx); dock.set_collapsible(collapsible_edges.left, window, cx);
}); });
} }
if let Some(bottom_dock) = self.bottom_dock.as_ref() { if let Some(bottom_dock) = self.bottom_dock.as_ref() {
bottom_dock.update(cx, |dock, cx| { bottom_dock.update(cx, |dock, cx| {
dock.set_collapsible(collapsible_edges.bottom, cx); dock.set_collapsible(collapsible_edges.bottom, window, cx);
}); });
} }
if let Some(right_dock) = self.right_dock.as_ref() { if let Some(right_dock) = self.right_dock.as_ref() {
right_dock.update(cx, |dock, cx| { right_dock.update(cx, |dock, cx| {
dock.set_collapsible(collapsible_edges.right, cx); dock.set_collapsible(collapsible_edges.right, window, cx);
}); });
} }
} }
/// Determine if the dock at the given placement is collapsible. /// Determine if the dock at the given placement is collapsible.
pub fn is_dock_collapsible(&self, placement: DockPlacement, cx: &AppContext) -> bool { pub fn is_dock_collapsible(&self, placement: DockPlacement, cx: &App) -> bool {
match placement { match placement {
DockPlacement::Left => self DockPlacement::Left => self
.left_dock .left_dock
@@ -502,7 +504,12 @@ impl DockArea {
} }
} }
pub fn toggle_dock(&self, placement: DockPlacement, cx: &mut ViewContext<Self>) { pub fn toggle_dock(
&self,
placement: DockPlacement,
window: &mut Window,
cx: &mut Context<Self>,
) {
let dock = match placement { let dock = match placement {
DockPlacement::Left => &self.left_dock, DockPlacement::Left => &self.left_dock,
DockPlacement::Bottom => &self.bottom_dock, DockPlacement::Bottom => &self.bottom_dock,
@@ -512,7 +519,7 @@ impl DockArea {
if let Some(dock) = dock { if let Some(dock) = dock {
dock.update(cx, |view, cx| { dock.update(cx, |view, cx| {
view.toggle_open(cx); view.toggle_open(window, cx);
}) })
} }
} }
@@ -524,128 +531,80 @@ impl DockArea {
&mut self, &mut self,
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
placement: DockPlacement, placement: DockPlacement,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
let weak_self = cx.view().downgrade(); let weak_self = cx.model().downgrade();
match placement { match placement {
DockPlacement::Left => { DockPlacement::Left => {
if let Some(dock) = self.left_dock.as_ref() { if let Some(dock) = self.left_dock.as_ref() {
dock.update(cx, |dock, cx| dock.add_panel(panel, cx)) dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
} else { } else {
self.set_left_dock( self.set_left_dock(
DockItem::tabs(vec![panel], None, &weak_self, cx), DockItem::tabs(vec![panel], None, &weak_self, window, cx),
None, None,
true, true,
window,
cx, cx,
); );
} }
} }
DockPlacement::Bottom => { DockPlacement::Bottom => {
if let Some(dock) = self.bottom_dock.as_ref() { if let Some(dock) = self.bottom_dock.as_ref() {
dock.update(cx, |dock, cx| dock.add_panel(panel, cx)) dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
} else { } else {
self.set_bottom_dock( self.set_bottom_dock(
DockItem::tabs(vec![panel], None, &weak_self, cx), DockItem::tabs(vec![panel], None, &weak_self, window, cx),
None, None,
true, true,
window,
cx, cx,
); );
} }
} }
DockPlacement::Right => { DockPlacement::Right => {
if let Some(dock) = self.right_dock.as_ref() { if let Some(dock) = self.right_dock.as_ref() {
dock.update(cx, |dock, cx| dock.add_panel(panel, cx)) dock.update(cx, |dock, cx| dock.add_panel(panel, window, cx))
} else { } else {
self.set_right_dock( self.set_right_dock(
DockItem::tabs(vec![panel], None, &weak_self, cx), DockItem::tabs(vec![panel], None, &weak_self, window, cx),
None, None,
true, true,
window,
cx, cx,
); );
} }
} }
DockPlacement::Center => { DockPlacement::Center => {
self.items.add_panel(panel, &cx.view().downgrade(), cx); self.items
.add_panel(panel, &cx.model().downgrade(), window, cx);
} }
} }
} }
/// Load the state of the DockArea from the DockAreaState.
///
/// See also [DockeArea::dump].
pub fn load(&mut self, state: DockAreaState, cx: &mut ViewContext<Self>) -> Result<()> {
self.version = state.version;
let weak_self = cx.view().downgrade();
if let Some(left_dock_state) = state.left_dock {
self.left_dock = Some(left_dock_state.to_dock(weak_self.clone(), cx));
}
if let Some(right_dock_state) = state.right_dock {
self.right_dock = Some(right_dock_state.to_dock(weak_self.clone(), cx));
}
if let Some(bottom_dock_state) = state.bottom_dock {
self.bottom_dock = Some(bottom_dock_state.to_dock(weak_self.clone(), cx));
}
self.items = state.center.to_item(weak_self, cx);
self.update_toggle_button_tab_panels(cx);
Ok(())
}
/// Dump the dock panels layout to PanelState.
///
/// See also [DockArea::load].
pub fn dump(&self, cx: &AppContext) -> DockAreaState {
let root = self.items.view();
let center = root.dump(cx);
let left_dock = self
.left_dock
.as_ref()
.map(|dock| DockState::new(dock.clone(), cx));
let right_dock = self
.right_dock
.as_ref()
.map(|dock| DockState::new(dock.clone(), cx));
let bottom_dock = self
.bottom_dock
.as_ref()
.map(|dock| DockState::new(dock.clone(), cx));
DockAreaState {
version: self.version,
center,
left_dock,
right_dock,
bottom_dock,
}
}
/// Subscribe event on the panels /// Subscribe event on the panels
fn subscribe_item(&mut self, item: &DockItem, cx: &mut ViewContext<Self>) { fn subscribe_item(&mut self, item: &DockItem, window: &mut Window, cx: &mut Context<Self>) {
match item { match item {
DockItem::Split { items, view, .. } => { DockItem::Split { items, view, .. } => {
for item in items { for item in items {
self.subscribe_item(item, cx); self.subscribe_item(item, window, cx);
} }
self._subscriptions self._subscriptions.push(cx.subscribe_in(
.push(cx.subscribe(view, move |_, _, event, cx| { view,
window,
move |_, _, event, window, cx| {
if let PanelEvent::LayoutChanged = event { if let PanelEvent::LayoutChanged = event {
let dock_area = cx.view().clone(); cx.spawn_in(window, |view, mut window| async move {
cx.spawn(|_, mut cx| async move { _ = view.update_in(&mut window, |view, window, cx| {
let _ = cx.update(|cx| { view.update_toggle_button_tab_panels(window, cx)
dock_area.update(cx, |view, cx| {
view.update_toggle_button_tab_panels(cx)
});
}); });
}) })
.detach(); .detach();
cx.emit(DockEvent::LayoutChanged); cx.emit(DockEvent::LayoutChanged);
} }
})); },
));
} }
DockItem::Tabs { .. } => { DockItem::Tabs { .. } => {
// We subscribe to the tab panel event in StackPanel's insert_panel // We subscribe to the tab panel event in StackPanel's insert_panel
@@ -659,43 +618,43 @@ impl DockArea {
/// Subscribe zoom event on the panel /// Subscribe zoom event on the panel
pub(crate) fn subscribe_panel<P: Panel>( pub(crate) fn subscribe_panel<P: Panel>(
&mut self, &mut self,
view: &View<P>, view: &Entity<P>,
cx: &mut ViewContext<DockArea>, window: &mut Window,
cx: &mut Context<DockArea>,
) { ) {
let subscription = cx.subscribe(view, move |_, panel, event, cx| match event { let subscription =
cx.subscribe_in(
view,
window,
move |_, panel, event, window, cx| match event {
PanelEvent::ZoomIn => { PanelEvent::ZoomIn => {
let dock_area = cx.view().clone();
let panel = panel.clone(); let panel = panel.clone();
cx.spawn(|_, mut cx| async move { cx.spawn_in(window, |view, mut window| async move {
let _ = cx.update(|cx| { _ = view.update_in(&mut window, |view, window, cx| {
dock_area.update(cx, |dock, cx| { view.set_zoomed_in(panel, window, cx);
dock.set_zoomed_in(panel, cx);
cx.notify(); cx.notify();
}); });
});
}) })
.detach(); .detach();
} }
PanelEvent::ZoomOut => { PanelEvent::ZoomOut => cx
let dock_area = cx.view().clone(); .spawn_in(window, |view, mut window| async move {
cx.spawn(|_, mut cx| async move { _ = view.update_in(&mut window, |view, window, cx| {
let _ = cx.update(|cx| { view.set_zoomed_out(window, cx);
dock_area.update(cx, |view, cx| view.set_zoomed_out(cx));
}); });
}) })
.detach() .detach(),
}
PanelEvent::LayoutChanged => { PanelEvent::LayoutChanged => {
let dock_area = cx.view().clone(); cx.spawn_in(window, |view, mut window| async move {
cx.spawn(|_, mut cx| async move { _ = view.update_in(&mut window, |view, window, cx| {
let _ = cx.update(|cx| { view.update_toggle_button_tab_panels(window, cx)
dock_area.update(cx, |view, cx| view.update_toggle_button_tab_panels(cx));
}); });
}) })
.detach(); .detach();
cx.emit(DockEvent::LayoutChanged); cx.emit(DockEvent::LayoutChanged);
} }
}); },
);
self._subscriptions.push(subscription); self._subscriptions.push(subscription);
} }
@@ -705,17 +664,22 @@ impl DockArea {
self.id.clone() self.id.clone()
} }
pub fn set_zoomed_in<P: Panel>(&mut self, panel: View<P>, cx: &mut ViewContext<Self>) { pub fn set_zoomed_in<P: Panel>(
&mut self,
panel: Entity<P>,
_: &mut Window,
cx: &mut Context<Self>,
) {
self.zoom_view = Some(panel.into()); self.zoom_view = Some(panel.into());
cx.notify(); cx.notify();
} }
pub fn set_zoomed_out(&mut self, cx: &mut ViewContext<Self>) { pub fn set_zoomed_out(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.zoom_view = None; self.zoom_view = None;
cx.notify(); cx.notify();
} }
fn render_items(&self, _cx: &mut ViewContext<Self>) -> AnyElement { fn render_items(&self, _window: &mut Window, _cx: &mut Context<Self>) -> AnyElement {
match &self.items { match &self.items {
DockItem::Split { view, .. } => view.clone().into_any_element(), DockItem::Split { view, .. } => view.clone().into_any_element(),
DockItem::Tabs { view, .. } => view.clone().into_any_element(), DockItem::Tabs { view, .. } => view.clone().into_any_element(),
@@ -723,7 +687,11 @@ impl DockArea {
} }
} }
pub fn update_toggle_button_tab_panels(&mut self, cx: &mut ViewContext<Self>) { pub fn update_toggle_button_tab_panels(
&mut self,
_window: &mut Window,
cx: &mut Context<Self>,
) {
// Left toggle button // Left toggle button
self.toggle_button_panels.left = self self.toggle_button_panels.left = self
.items .items
@@ -748,8 +716,8 @@ impl DockArea {
impl EventEmitter<DockEvent> for DockArea {} impl EventEmitter<DockEvent> for DockArea {}
impl Render for DockArea { impl Render for DockArea {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.view().clone(); let view = cx.model().clone();
div() div()
.id("dock-area") .id("dock-area")
@@ -758,8 +726,8 @@ impl Render for DockArea {
.overflow_hidden() .overflow_hidden()
.child( .child(
canvas( canvas(
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds), move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|_, _, _| {}, |_, _, _, _| {},
) )
.absolute() .absolute()
.size_full(), .size_full(),
@@ -790,7 +758,7 @@ impl Render for DockArea {
div() div()
.flex_1() .flex_1()
.overflow_hidden() .overflow_hidden()
.child(self.render_items(cx)), .child(self.render_items(window, cx)),
) )
// Bottom Dock // Bottom Dock
.when_some(self.bottom_dock.clone(), |this, dock| { .when_some(self.bottom_dock.clone(), |this, dock| {

View File

@@ -1,14 +1,8 @@
use super::DockArea; use crate::{button::Button, popup_menu::PopupMenu};
use crate::{
button::Button,
dock_area::state::{PanelInfo, PanelState},
popup_menu::PopupMenu,
};
use gpui::{ use gpui::{
AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Global, Hsla, AnyElement, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, Hsla, IntoElement,
IntoElement, SharedString, View, WeakView, WindowContext, Render, SharedString, Window,
}; };
use std::{collections::HashMap, sync::Arc};
pub enum PanelEvent { pub enum PanelEvent {
ZoomIn, ZoomIn,
@@ -30,7 +24,7 @@ pub struct TitleStyle {
pub foreground: Hsla, pub foreground: Hsla,
} }
pub trait Panel: EventEmitter<PanelEvent> + FocusableView { pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
/// The name of the panel used to serialize, deserialize and identify the panel. /// The name of the panel used to serialize, deserialize and identify the panel.
/// ///
/// This is used to identify the panel when deserializing the panel. /// This is used to identify the panel when deserializing the panel.
@@ -38,94 +32,84 @@ pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
fn panel_id(&self) -> SharedString; fn panel_id(&self) -> SharedString;
/// The optional facepile of the panel /// The optional facepile of the panel
fn panel_facepile(&self, _cx: &WindowContext) -> Option<Vec<String>> { fn panel_facepile(&self, _cx: &App) -> Option<Vec<String>> {
None None
} }
/// The title of the panel /// The title of the panel
fn title(&self, _cx: &WindowContext) -> AnyElement { fn title(&self, _cx: &App) -> AnyElement {
SharedString::from("Untitled").into_any_element() SharedString::from("Unamed").into_any_element()
} }
/// Whether the panel can be closed, default is `true`. /// Whether the panel can be closed, default is `true`.
fn closeable(&self, _cx: &WindowContext) -> bool { fn closeable(&self, _cx: &App) -> bool {
true true
} }
/// Return true if the panel is zoomable, default is `false`. /// Return true if the panel is zoomable, default is `false`.
fn zoomable(&self, _cx: &WindowContext) -> bool { fn zoomable(&self, _cx: &App) -> bool {
true true
} }
/// The addition popup menu of the panel, default is `None`. /// The addition popup menu of the panel, default is `None`.
fn popup_menu(&self, this: PopupMenu, _cx: &WindowContext) -> PopupMenu { fn popup_menu(&self, this: PopupMenu, _cx: &App) -> PopupMenu {
this this
} }
/// The addition toolbar buttons of the panel used to show in the right of the title bar, default is `None`. /// The addition toolbar buttons of the panel used to show in the right of the title bar, default is `None`.
fn toolbar_buttons(&self, _cx: &WindowContext) -> Vec<Button> { fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
vec![] vec![]
} }
/// Dump the panel, used to serialize the panel.
fn dump(&self, _cx: &AppContext) -> PanelState {
PanelState::new(self)
}
} }
pub trait PanelView: 'static + Send + Sync { pub trait PanelView: 'static + Send + Sync {
fn panel_id(&self, cx: &WindowContext) -> SharedString; fn panel_id(&self, cx: &App) -> SharedString;
fn panel_facepile(&self, cx: &WindowContext) -> Option<Vec<String>>; fn panel_facepile(&self, cx: &App) -> Option<Vec<String>>;
fn title(&self, _cx: &WindowContext) -> AnyElement; fn title(&self, cx: &App) -> AnyElement;
fn closeable(&self, cx: &WindowContext) -> bool; fn closeable(&self, cx: &App) -> bool;
fn zoomable(&self, cx: &WindowContext) -> bool; fn zoomable(&self, cx: &App) -> bool;
fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu; fn popup_menu(&self, menu: PopupMenu, cx: &App) -> PopupMenu;
fn toolbar_buttons(&self, cx: &WindowContext) -> Vec<Button>; fn toolbar_buttons(&self, window: &Window, cx: &App) -> Vec<Button>;
fn view(&self) -> AnyView; fn view(&self) -> AnyView;
fn focus_handle(&self, cx: &AppContext) -> FocusHandle; fn focus_handle(&self, cx: &App) -> FocusHandle;
fn dump(&self, cx: &AppContext) -> PanelState;
} }
impl<T: Panel> PanelView for View<T> { impl<T: Panel> PanelView for Entity<T> {
fn panel_id(&self, cx: &WindowContext) -> SharedString { fn panel_id(&self, cx: &App) -> SharedString {
self.read(cx).panel_id() self.read(cx).panel_id()
} }
fn panel_facepile(&self, cx: &WindowContext) -> Option<Vec<String>> { fn panel_facepile(&self, cx: &App) -> Option<Vec<String>> {
self.read(cx).panel_facepile(cx) self.read(cx).panel_facepile(cx)
} }
fn title(&self, cx: &WindowContext) -> AnyElement { fn title(&self, cx: &App) -> AnyElement {
self.read(cx).title(cx) self.read(cx).title(cx)
} }
fn closeable(&self, cx: &WindowContext) -> bool { fn closeable(&self, cx: &App) -> bool {
self.read(cx).closeable(cx) self.read(cx).closeable(cx)
} }
fn zoomable(&self, cx: &WindowContext) -> bool { fn zoomable(&self, cx: &App) -> bool {
self.read(cx).zoomable(cx) self.read(cx).zoomable(cx)
} }
fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu { fn popup_menu(&self, menu: PopupMenu, cx: &App) -> PopupMenu {
self.read(cx).popup_menu(menu, cx) self.read(cx).popup_menu(menu, cx)
} }
fn toolbar_buttons(&self, cx: &WindowContext) -> Vec<Button> { fn toolbar_buttons(&self, window: &Window, cx: &App) -> Vec<Button> {
self.read(cx).toolbar_buttons(cx) self.read(cx).toolbar_buttons(window, cx)
} }
fn view(&self) -> AnyView { fn view(&self) -> AnyView {
self.clone().into() self.clone().into()
} }
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
self.read(cx).focus_handle(cx) self.read(cx).focus_handle(cx)
} }
fn dump(&self, cx: &AppContext) -> PanelState {
self.read(cx).dump(cx)
}
} }
impl From<&dyn PanelView> for AnyView { impl From<&dyn PanelView> for AnyView {
@@ -134,7 +118,7 @@ impl From<&dyn PanelView> for AnyView {
} }
} }
impl<T: Panel> From<&dyn PanelView> for View<T> { impl<T: Panel> From<&dyn PanelView> for Entity<T> {
fn from(value: &dyn PanelView) -> Self { fn from(value: &dyn PanelView) -> Self {
value.view().downcast::<T>().unwrap() value.view().downcast::<T>().unwrap()
} }
@@ -145,50 +129,3 @@ impl PartialEq for dyn PanelView {
self.view() == other.view() self.view() == other.view()
} }
} }
type Items = HashMap<
String,
Arc<
dyn Fn(
WeakView<DockArea>,
&PanelState,
&PanelInfo,
&mut WindowContext,
) -> Box<dyn PanelView>,
>,
>;
pub struct PanelRegistry {
pub(super) items: Items,
}
impl PanelRegistry {
pub fn new() -> Self {
Self {
items: HashMap::new(),
}
}
}
impl Default for PanelRegistry {
fn default() -> Self {
Self::new()
}
}
impl Global for PanelRegistry {}
/// Register the Panel init by panel_name to global registry.
pub fn register_panel<F>(cx: &mut AppContext, panel_id: &str, deserialize: F)
where
F: Fn(WeakView<DockArea>, &PanelState, &PanelInfo, &mut WindowContext) -> Box<dyn PanelView>
+ 'static,
{
if cx.try_global::<PanelRegistry>().is_none() {
cx.set_global(PanelRegistry::new());
}
cx.global_mut::<PanelRegistry>()
.items
.insert(panel_id.to_string(), Arc::new(deserialize));
}

View File

@@ -2,7 +2,6 @@ use super::{DockArea, PanelEvent};
use crate::{ use crate::{
dock_area::{ dock_area::{
panel::{Panel, PanelView}, panel::{Panel, PanelView},
state::{PanelInfo, PanelState},
tab_panel::TabPanel, tab_panel::TabPanel,
}, },
h_flex, h_flex,
@@ -13,19 +12,19 @@ use crate::{
AxisExt as _, Placement, AxisExt as _, Placement,
}; };
use gpui::{ use gpui::{
prelude::FluentBuilder, AppContext, Axis, DismissEvent, EventEmitter, FocusHandle, prelude::FluentBuilder, App, AppContext, Axis, Context, DismissEvent, Entity, EventEmitter,
FocusableView, IntoElement, ParentElement, Pixels, Render, SharedString, Styled, Subscription, FocusHandle, Focusable, IntoElement, ParentElement, Pixels, Render, SharedString, Styled,
View, ViewContext, VisualContext as _, WeakView, Subscription, WeakEntity, Window,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use std::sync::Arc; use std::sync::Arc;
pub struct StackPanel { pub struct StackPanel {
pub(super) parent: Option<WeakView<StackPanel>>, pub(super) parent: Option<WeakEntity<StackPanel>>,
pub(super) axis: Axis, pub(super) axis: Axis,
focus_handle: FocusHandle, focus_handle: FocusHandle,
pub(crate) panels: SmallVec<[Arc<dyn PanelView>; 2]>, pub(crate) panels: SmallVec<[Arc<dyn PanelView>; 2]>,
panel_group: View<ResizablePanelGroup>, panel_group: Entity<ResizablePanelGroup>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@@ -34,29 +33,18 @@ impl Panel for StackPanel {
"StackPanel".into() "StackPanel".into()
} }
fn title(&self, _cx: &gpui::WindowContext) -> gpui::AnyElement { fn title(&self, _cx: &App) -> gpui::AnyElement {
"StackPanel".into_any_element() "StackPanel".into_any_element()
} }
fn dump(&self, cx: &AppContext) -> PanelState {
let sizes = self.panel_group.read(cx).sizes();
let mut state = PanelState::new(self);
for panel in &self.panels {
state.add_child(panel.dump(cx));
state.info = PanelInfo::stack(sizes.clone(), self.axis);
}
state
}
} }
impl StackPanel { impl StackPanel {
pub fn new(axis: Axis, cx: &mut ViewContext<Self>) -> Self { pub fn new(axis: Axis, window: &mut Window, cx: &mut Context<Self>) -> Self {
let panel_group = cx.new_view(|cx| { let panel_group = cx.new(|cx| {
if axis == Axis::Horizontal { if axis == Axis::Horizontal {
h_resizable(cx) h_resizable(window, cx)
} else { } else {
v_resizable(cx) v_resizable(window, cx)
} }
}); });
@@ -82,7 +70,7 @@ impl StackPanel {
} }
/// Return true if self or parent only have last panel. /// Return true if self or parent only have last panel.
pub(super) fn is_last_panel(&self, cx: &AppContext) -> bool { pub(super) fn is_last_panel(&self, cx: &App) -> bool {
if self.panels.len() > 1 { if self.panels.len() > 1 {
return false; return false;
} }
@@ -110,10 +98,11 @@ impl StackPanel {
&mut self, &mut self,
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
size: Option<Pixels>, size: Option<Pixels>,
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
self.insert_panel(panel, self.panels.len(), size, dock_area, cx); self.insert_panel(panel, self.panels.len(), size, dock_area, window, cx);
} }
pub fn add_panel_at( pub fn add_panel_at(
@@ -121,10 +110,19 @@ impl StackPanel {
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
placement: Placement, placement: Placement,
size: Option<Pixels>, size: Option<Pixels>,
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
self.insert_panel_at(panel, self.panels_len(), placement, size, dock_area, cx); self.insert_panel_at(
panel,
self.panels_len(),
placement,
size,
dock_area,
window,
cx,
);
} }
pub fn insert_panel_at( pub fn insert_panel_at(
@@ -133,15 +131,16 @@ impl StackPanel {
ix: usize, ix: usize,
placement: Placement, placement: Placement,
size: Option<Pixels>, size: Option<Pixels>,
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
match placement { match placement {
Placement::Top | Placement::Left => { Placement::Top | Placement::Left => {
self.insert_panel_before(panel, ix, size, dock_area, cx) self.insert_panel_before(panel, ix, size, dock_area, window, cx)
} }
Placement::Right | Placement::Bottom => { Placement::Right | Placement::Bottom => {
self.insert_panel_after(panel, ix, size, dock_area, cx) self.insert_panel_after(panel, ix, size, dock_area, window, cx)
} }
} }
} }
@@ -152,10 +151,11 @@ impl StackPanel {
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
ix: usize, ix: usize,
size: Option<Pixels>, size: Option<Pixels>,
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
self.insert_panel(panel, ix, size, dock_area, cx); self.insert_panel(panel, ix, size, dock_area, window, cx);
} }
/// Insert a panel after the index. /// Insert a panel after the index.
@@ -164,10 +164,11 @@ impl StackPanel {
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
ix: usize, ix: usize,
size: Option<Pixels>, size: Option<Pixels>,
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
self.insert_panel(panel, ix + 1, size, dock_area, cx); self.insert_panel(panel, ix + 1, size, dock_area, window, cx);
} }
fn new_resizable_panel(panel: Arc<dyn PanelView>, size: Option<Pixels>) -> ResizablePanel { fn new_resizable_panel(panel: Arc<dyn PanelView>, size: Option<Pixels>) -> ResizablePanel {
@@ -181,19 +182,21 @@ impl StackPanel {
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
ix: usize, ix: usize,
size: Option<Pixels>, size: Option<Pixels>,
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
// If the panel is already in the stack, return. // If the panel is already in the stack, return.
if self.index_of_panel(panel.clone()).is_some() { if self.index_of_panel(panel.clone()).is_some() {
return; return;
} }
let view = cx.view().clone(); let view = cx.model().clone();
cx.window_context().defer({
window.defer(cx, {
let panel = panel.clone(); let panel = panel.clone();
move |cx| { move |window, cx| {
// If the panel is a TabPanel, set its parent to this. // If the panel is a TabPanel, set its parent to this.
if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() { if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
tab_panel.update(cx, |tab_panel, _| tab_panel.set_parent(view.downgrade())); tab_panel.update(cx, |tab_panel, _| tab_panel.set_parent(view.downgrade()));
@@ -206,9 +209,9 @@ impl StackPanel {
// Subscribe to the panel's layout change event. // Subscribe to the panel's layout change event.
_ = dock_area.update(cx, |this, cx| { _ = dock_area.update(cx, |this, cx| {
if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() { if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
this.subscribe_panel(&tab_panel, cx); this.subscribe_panel(&tab_panel, window, cx);
} else if let Ok(stack_panel) = panel.view().downcast::<Self>() { } else if let Ok(stack_panel) = panel.view().downcast::<Self>() {
this.subscribe_panel(&stack_panel, cx); this.subscribe_panel(&stack_panel, window, cx);
} }
}); });
} }
@@ -222,7 +225,12 @@ impl StackPanel {
self.panels.insert(ix, panel.clone()); self.panels.insert(ix, panel.clone());
self.panel_group.update(cx, |view, cx| { self.panel_group.update(cx, |view, cx| {
view.insert_child(Self::new_resizable_panel(panel.clone(), size), ix, cx) view.insert_child(
Self::new_resizable_panel(panel.clone(), size),
ix,
window,
cx,
)
}); });
cx.emit(PanelEvent::LayoutChanged); cx.emit(PanelEvent::LayoutChanged);
@@ -230,15 +238,20 @@ impl StackPanel {
} }
/// Remove panel from the stack. /// Remove panel from the stack.
pub fn remove_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) { pub fn remove_panel(
&mut self,
panel: Arc<dyn PanelView>,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(ix) = self.index_of_panel(panel.clone()) { if let Some(ix) = self.index_of_panel(panel.clone()) {
self.panels.remove(ix); self.panels.remove(ix);
self.panel_group.update(cx, |view, cx| { self.panel_group.update(cx, |view, cx| {
view.remove_child(ix, cx); view.remove_child(ix, window, cx);
}); });
cx.emit(PanelEvent::LayoutChanged); cx.emit(PanelEvent::LayoutChanged);
self.remove_self_if_empty(cx); self.remove_self_if_empty(window, cx);
} else { } else {
println!("Panel not found in stack panel."); println!("Panel not found in stack panel.");
} }
@@ -248,8 +261,9 @@ impl StackPanel {
pub(super) fn replace_panel( pub(super) fn replace_panel(
&mut self, &mut self,
old_panel: Arc<dyn PanelView>, old_panel: Arc<dyn PanelView>,
new_panel: View<StackPanel>, new_panel: Entity<StackPanel>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
if let Some(ix) = self.index_of_panel(old_panel.clone()) { if let Some(ix) = self.index_of_panel(old_panel.clone()) {
self.panels[ix] = Arc::new(new_panel.clone()); self.panels[ix] = Arc::new(new_panel.clone());
@@ -257,6 +271,7 @@ impl StackPanel {
view.replace_child( view.replace_child(
Self::new_resizable_panel(Arc::new(new_panel.clone()), None), Self::new_resizable_panel(Arc::new(new_panel.clone()), None),
ix, ix,
window,
cx, cx,
); );
}); });
@@ -265,7 +280,7 @@ impl StackPanel {
} }
/// If children is empty, remove self from parent view. /// If children is empty, remove self from parent view.
pub(crate) fn remove_self_if_empty(&mut self, cx: &mut ViewContext<Self>) { pub(crate) fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.is_root() { if self.is_root() {
return; return;
} }
@@ -274,10 +289,10 @@ impl StackPanel {
return; return;
} }
let view = cx.view().clone(); let view = cx.model().clone();
if let Some(parent) = self.parent.as_ref() { if let Some(parent) = self.parent.as_ref() {
_ = parent.update(cx, |parent, cx| { _ = parent.update(cx, |parent, cx| {
parent.remove_panel(Arc::new(view.clone()), cx); parent.remove_panel(Arc::new(view.clone()), window, cx);
}); });
} }
@@ -289,8 +304,8 @@ impl StackPanel {
pub(super) fn left_top_tab_panel( pub(super) fn left_top_tab_panel(
&self, &self,
check_parent: bool, check_parent: bool,
cx: &AppContext, cx: &App,
) -> Option<View<TabPanel>> { ) -> Option<Entity<TabPanel>> {
if check_parent { if check_parent {
if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) { if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
if let Some(panel) = parent.read(cx).left_top_tab_panel(true, cx) { if let Some(panel) = parent.read(cx).left_top_tab_panel(true, cx) {
@@ -317,8 +332,8 @@ impl StackPanel {
pub(super) fn right_top_tab_panel( pub(super) fn right_top_tab_panel(
&self, &self,
check_parent: bool, check_parent: bool,
cx: &AppContext, cx: &App,
) -> Option<View<TabPanel>> { ) -> Option<Entity<TabPanel>> {
if check_parent { if check_parent {
if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) { if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
if let Some(panel) = parent.read(cx).right_top_tab_panel(true, cx) { if let Some(panel) = parent.read(cx).right_top_tab_panel(true, cx) {
@@ -347,23 +362,23 @@ impl StackPanel {
} }
/// Remove all panels from the stack. /// Remove all panels from the stack.
pub(super) fn remove_all_panels(&mut self, cx: &mut ViewContext<Self>) { pub(super) fn remove_all_panels(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.panels.clear(); self.panels.clear();
self.panel_group self.panel_group
.update(cx, |view, cx| view.remove_all_children(cx)); .update(cx, |view, cx| view.remove_all_children(window, cx));
} }
/// Change the axis of the stack panel. /// Change the axis of the stack panel.
pub(super) fn set_axis(&mut self, axis: Axis, cx: &mut ViewContext<Self>) { pub(super) fn set_axis(&mut self, axis: Axis, window: &mut Window, cx: &mut Context<Self>) {
self.axis = axis; self.axis = axis;
self.panel_group self.panel_group
.update(cx, |view, cx| view.set_axis(axis, cx)); .update(cx, |view, cx| view.set_axis(axis, window, cx));
cx.notify(); cx.notify();
} }
} }
impl FocusableView for StackPanel { impl Focusable for StackPanel {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
@@ -373,7 +388,7 @@ impl EventEmitter<PanelEvent> for StackPanel {}
impl EventEmitter<DismissEvent> for StackPanel {} impl EventEmitter<DismissEvent> for StackPanel {}
impl Render for StackPanel { impl Render for StackPanel {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
h_flex() h_flex()
.size_full() .size_full()
.overflow_hidden() .overflow_hidden()

View File

@@ -1,229 +0,0 @@
use super::{invalid_panel::InvalidPanel, Dock, DockArea, DockItem, PanelRegistry};
use crate::dock_area::{dock::DockPlacement, panel::Panel};
use gpui::{
point, px, size, AppContext, Axis, Bounds, Pixels, View, VisualContext as _, WeakView,
WindowContext,
};
use itertools::Itertools as _;
use serde::{Deserialize, Serialize};
/// Used to serialize and deserialize the DockArea
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct DockAreaState {
/// The version is used to mark this persisted state is compatible with the current version
/// For example, sometimes we many totally changed the structure of the Panel,
/// then we can compare the version to decide whether we can use the state or ignore.
#[serde(default)]
pub version: Option<usize>,
pub center: PanelState,
#[serde(skip_serializing_if = "Option::is_none")]
pub left_dock: Option<DockState>,
#[serde(skip_serializing_if = "Option::is_none")]
pub right_dock: Option<DockState>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bottom_dock: Option<DockState>,
}
/// Used to serialize and deserialize the Dock
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DockState {
panel: PanelState,
placement: DockPlacement,
size: Pixels,
open: bool,
}
impl DockState {
pub fn new(dock: View<Dock>, cx: &AppContext) -> Self {
let dock = dock.read(cx);
Self {
placement: dock.placement,
size: dock.size,
open: dock.open,
panel: dock.panel.view().dump(cx),
}
}
/// Convert the DockState to Dock
pub fn to_dock(&self, dock_area: WeakView<DockArea>, cx: &mut WindowContext) -> View<Dock> {
let item = self.panel.to_item(dock_area.clone(), cx);
cx.new_view(|cx| {
Dock::from_state(
dock_area.clone(),
self.placement,
self.size,
item,
self.open,
cx,
)
})
}
}
/// Used to serialize and deserialize the DockerItem
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PanelState {
pub panel_name: String,
pub children: Vec<PanelState>,
pub info: PanelInfo,
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub struct TileMeta {
pub bounds: Bounds<Pixels>,
pub z_index: usize,
}
impl Default for TileMeta {
fn default() -> Self {
Self {
bounds: Bounds {
origin: point(px(10.), px(10.)),
size: size(px(200.), px(200.)),
},
z_index: 0,
}
}
}
impl From<Bounds<Pixels>> for TileMeta {
fn from(bounds: Bounds<Pixels>) -> Self {
Self { bounds, z_index: 0 }
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PanelInfo {
#[serde(rename = "stack")]
Stack {
sizes: Vec<Pixels>,
axis: usize, // 0 for horizontal, 1 for vertical
},
#[serde(rename = "tabs")]
Tabs { active_index: usize },
#[serde(rename = "panel")]
Panel(serde_json::Value),
}
impl PanelInfo {
pub fn stack(sizes: Vec<Pixels>, axis: Axis) -> Self {
Self::Stack {
sizes,
axis: if axis == Axis::Horizontal { 0 } else { 1 },
}
}
pub fn tabs(active_index: usize) -> Self {
Self::Tabs { active_index }
}
pub fn panel(info: serde_json::Value) -> Self {
Self::Panel(info)
}
pub fn axis(&self) -> Option<Axis> {
match self {
Self::Stack { axis, .. } => Some(if *axis == 0 {
Axis::Horizontal
} else {
Axis::Vertical
}),
_ => None,
}
}
pub fn sizes(&self) -> Option<&Vec<Pixels>> {
match self {
Self::Stack { sizes, .. } => Some(sizes),
_ => None,
}
}
pub fn active_index(&self) -> Option<usize> {
match self {
Self::Tabs { active_index } => Some(*active_index),
_ => None,
}
}
}
impl Default for PanelState {
fn default() -> Self {
Self {
panel_name: "".to_string(),
children: Vec::new(),
info: PanelInfo::Panel(serde_json::Value::Null),
}
}
}
impl PanelState {
pub fn new<P: Panel>(panel: &P) -> Self {
Self {
panel_name: panel.panel_id().to_string(),
..Default::default()
}
}
pub fn add_child(&mut self, panel: PanelState) {
self.children.push(panel);
}
pub fn to_item(&self, dock_area: WeakView<DockArea>, cx: &mut WindowContext) -> DockItem {
let info = self.info.clone();
let items: Vec<DockItem> = self
.children
.iter()
.map(|child| child.to_item(dock_area.clone(), cx))
.collect();
match info {
PanelInfo::Stack { sizes, axis } => {
let axis = if axis == 0 {
Axis::Horizontal
} else {
Axis::Vertical
};
let sizes = sizes.iter().map(|s| Some(*s)).collect_vec();
DockItem::split_with_sizes(axis, items, sizes, &dock_area, cx)
}
PanelInfo::Tabs { active_index } => {
if items.len() == 1 {
return items[0].clone();
}
let items = items
.iter()
.flat_map(|item| match item {
DockItem::Tabs { items, .. } => items.clone(),
_ => {
// ignore invalid panels in tabs
vec![]
}
})
.collect_vec();
DockItem::tabs(items, Some(active_index), &dock_area, cx)
}
PanelInfo::Panel(_) => {
let view = if let Some(f) = cx
.global::<PanelRegistry>()
.items
.get(&self.panel_name)
.cloned()
{
f(dock_area.clone(), self, &info, cx)
} else {
// Show an invalid panel if the panel is not registered.
Box::new(
cx.new_view(|cx| InvalidPanel::new(&self.panel_name, self.clone(), cx)),
)
};
DockItem::tabs(vec![view.into()], None, &dock_area, cx)
}
}
}
}

View File

@@ -4,11 +4,7 @@ use super::{
}; };
use crate::{ use crate::{
button::{Button, ButtonVariants as _}, button::{Button, ButtonVariants as _},
dock_area::{ dock_area::{dock::DockPlacement, panel::Panel},
dock::DockPlacement,
panel::Panel,
state::{PanelInfo, PanelState},
},
h_flex, h_flex,
popup_menu::{PopupMenu, PopupMenuExt}, popup_menu::{PopupMenu, PopupMenuExt},
tab::{tab_bar::TabBar, Tab}, tab::{tab_bar::TabBar, Tab},
@@ -16,11 +12,10 @@ use crate::{
v_flex, AxisExt, IconName, Placement, Selectable, Sizable, v_flex, AxisExt, IconName, Placement, Selectable, Sizable,
}; };
use gpui::{ use gpui::{
div, img, prelude::FluentBuilder, px, rems, AppContext, Corner, DefiniteLength, DismissEvent, div, img, prelude::FluentBuilder, px, rems, App, AppContext, Context, Corner, DefiniteLength,
DragMoveEvent, Empty, Entity, EventEmitter, FocusHandle, FocusableView, DismissEvent, DragMoveEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement as _, IntoElement, ObjectFit, ParentElement, Pixels, Render, ScrollHandle, InteractiveElement as _, IntoElement, ObjectFit, ParentElement, Pixels, Render, ScrollHandle,
SharedString, StatefulInteractiveElement, Styled, StyledImage, View, ViewContext, SharedString, StatefulInteractiveElement, Styled, StyledImage, WeakEntity, Window,
VisualContext as _, WeakView, WindowContext,
}; };
use std::sync::Arc; use std::sync::Arc;
@@ -35,17 +30,17 @@ struct TabState {
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct DragPanel { pub(crate) struct DragPanel {
pub(crate) panel: Arc<dyn PanelView>, pub(crate) panel: Arc<dyn PanelView>,
pub(crate) tab_panel: View<TabPanel>, pub(crate) tab_panel: Entity<TabPanel>,
} }
impl DragPanel { impl DragPanel {
pub(crate) fn new(panel: Arc<dyn PanelView>, tab_panel: View<TabPanel>) -> Self { pub(crate) fn new(panel: Arc<dyn PanelView>, tab_panel: Entity<TabPanel>) -> Self {
Self { panel, tab_panel } Self { panel, tab_panel }
} }
} }
impl Render for DragPanel { impl Render for DragPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div() div()
.id("drag-panel") .id("drag-panel")
.cursor_grab() .cursor_grab()
@@ -68,9 +63,9 @@ impl Render for DragPanel {
pub struct TabPanel { pub struct TabPanel {
focus_handle: FocusHandle, focus_handle: FocusHandle,
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
/// The stock_panel can be None, if is None, that means the panels can't be split or move /// The stock_panel can be None, if is None, that means the panels can't be split or move
stack_panel: Option<WeakView<StackPanel>>, stack_panel: Option<WeakEntity<StackPanel>>,
pub(crate) panels: Vec<Arc<dyn PanelView>>, pub(crate) panels: Vec<Arc<dyn PanelView>>,
pub(crate) active_ix: usize, pub(crate) active_ix: usize,
/// If this is true, the Panel closeable will follow the active panel's closeable, /// If this is true, the Panel closeable will follow the active panel's closeable,
@@ -88,13 +83,13 @@ impl Panel for TabPanel {
"TabPanel".into() "TabPanel".into()
} }
fn title(&self, cx: &WindowContext) -> gpui::AnyElement { fn title(&self, cx: &App) -> gpui::AnyElement {
self.active_panel() self.active_panel()
.map(|panel| panel.title(cx)) .map(|panel| panel.title(cx))
.unwrap_or("Empty Tab".into_any_element()) .unwrap_or("Empty Tab".into_any_element())
} }
fn closeable(&self, cx: &WindowContext) -> bool { fn closeable(&self, cx: &App) -> bool {
if !self.closeable { if !self.closeable {
return false; return false;
} }
@@ -104,13 +99,13 @@ impl Panel for TabPanel {
.unwrap_or(false) .unwrap_or(false)
} }
fn zoomable(&self, cx: &WindowContext) -> bool { fn zoomable(&self, cx: &App) -> bool {
self.active_panel() self.active_panel()
.map(|panel| panel.zoomable(cx)) .map(|panel| panel.zoomable(cx))
.unwrap_or(false) .unwrap_or(false)
} }
fn popup_menu(&self, menu: PopupMenu, cx: &WindowContext) -> PopupMenu { fn popup_menu(&self, menu: PopupMenu, cx: &App) -> PopupMenu {
if let Some(panel) = self.active_panel() { if let Some(panel) = self.active_panel() {
panel.popup_menu(menu, cx) panel.popup_menu(menu, cx)
} else { } else {
@@ -118,29 +113,21 @@ impl Panel for TabPanel {
} }
} }
fn toolbar_buttons(&self, cx: &WindowContext) -> Vec<Button> { fn toolbar_buttons(&self, window: &Window, cx: &App) -> Vec<Button> {
if let Some(panel) = self.active_panel() { if let Some(panel) = self.active_panel() {
panel.toolbar_buttons(cx) panel.toolbar_buttons(window, cx)
} else { } else {
vec![] vec![]
} }
} }
fn dump(&self, cx: &AppContext) -> PanelState {
let mut state = PanelState::new(self);
for panel in self.panels.iter() {
state.add_child(panel.dump(cx));
state.info = PanelInfo::tabs(self.active_ix);
}
state
}
} }
impl TabPanel { impl TabPanel {
pub fn new( pub fn new(
stack_panel: Option<WeakView<StackPanel>>, stack_panel: Option<WeakEntity<StackPanel>>,
dock_area: WeakView<DockArea>, dock_area: WeakEntity<DockArea>,
cx: &mut ViewContext<Self>, _window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
Self { Self {
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
@@ -156,7 +143,7 @@ impl TabPanel {
} }
} }
pub(super) fn set_parent(&mut self, view: WeakView<StackPanel>) { pub(super) fn set_parent(&mut self, view: WeakEntity<StackPanel>) {
self.stack_panel = Some(view); self.stack_panel = Some(view);
} }
@@ -165,24 +152,30 @@ impl TabPanel {
self.panels.get(self.active_ix).cloned() self.panels.get(self.active_ix).cloned()
} }
fn set_active_ix(&mut self, ix: usize, cx: &mut ViewContext<Self>) { fn set_active_ix(&mut self, ix: usize, window: &mut Window, cx: &mut Context<Self>) {
self.active_ix = ix; self.active_ix = ix;
self.tab_bar_scroll_handle.scroll_to_item(ix); self.tab_bar_scroll_handle.scroll_to_item(ix);
self.focus_active_panel(cx); self.focus_active_panel(window, cx);
cx.emit(PanelEvent::LayoutChanged); cx.emit(PanelEvent::LayoutChanged);
cx.notify(); cx.notify();
} }
/// Add a panel to the end of the tabs /// Add a panel to the end of the tabs
pub fn add_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) { pub fn add_panel(
self.add_panel_with_active(panel, true, cx); &mut self,
panel: Arc<dyn PanelView>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.add_panel_with_active(panel, true, window, cx);
} }
fn add_panel_with_active( fn add_panel_with_active(
&mut self, &mut self,
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
active: bool, active: bool,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
if self if self
.panels .panels
@@ -196,7 +189,7 @@ impl TabPanel {
.iter() .iter()
.position(|p| p.panel_id(cx) == panel.panel_id(cx)) .position(|p| p.panel_id(cx) == panel.panel_id(cx))
{ {
self.set_active_ix(ix, cx); self.set_active_ix(ix, window, cx);
} }
} }
@@ -207,7 +200,7 @@ impl TabPanel {
// Set the active panel to the new panel // Set the active panel to the new panel
if active { if active {
self.set_active_ix(self.panels.len() - 1, cx); self.set_active_ix(self.panels.len() - 1, window, cx);
} }
cx.emit(PanelEvent::LayoutChanged); cx.emit(PanelEvent::LayoutChanged);
@@ -220,13 +213,14 @@ impl TabPanel {
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
placement: Placement, placement: Placement,
size: Option<Pixels>, size: Option<Pixels>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
cx.spawn(|view, mut cx| async move { cx.spawn_in(window, |view, mut cx| async move {
cx.update(|cx| { cx.update(|window, cx| {
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
view.will_split_placement = Some(placement); view.will_split_placement = Some(placement);
view.split_panel(panel, placement, size, cx) view.split_panel(panel, placement, size, window, cx)
}) })
.ok() .ok()
}) })
@@ -241,7 +235,8 @@ impl TabPanel {
&mut self, &mut self,
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
ix: usize, ix: usize,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
if self if self
.panels .panels
@@ -252,47 +247,63 @@ impl TabPanel {
} }
self.panels.insert(ix, panel); self.panels.insert(ix, panel);
self.set_active_ix(ix, cx); self.set_active_ix(ix, window, cx);
cx.emit(PanelEvent::LayoutChanged); cx.emit(PanelEvent::LayoutChanged);
cx.notify(); cx.notify();
} }
/// Remove a panel from the tab panel /// Remove a panel from the tab panel
pub fn remove_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) { pub fn remove_panel(
self.detach_panel(panel, cx); &mut self,
self.remove_self_if_empty(cx); panel: Arc<dyn PanelView>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.detach_panel(panel, window, cx);
self.remove_self_if_empty(window, cx);
cx.emit(PanelEvent::ZoomOut); cx.emit(PanelEvent::ZoomOut);
cx.emit(PanelEvent::LayoutChanged); cx.emit(PanelEvent::LayoutChanged);
} }
fn detach_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) { fn detach_panel(
&mut self,
panel: Arc<dyn PanelView>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let panel_view = panel.view(); let panel_view = panel.view();
self.panels.retain(|p| p.view() != panel_view); self.panels.retain(|p| p.view() != panel_view);
if self.active_ix >= self.panels.len() { if self.active_ix >= self.panels.len() {
self.set_active_ix(self.panels.len().saturating_sub(1), cx) self.set_active_ix(self.panels.len().saturating_sub(1), window, cx)
} }
} }
/// Check to remove self from the parent StackPanel, if there is no panel left /// Check to remove self from the parent StackPanel, if there is no panel left
fn remove_self_if_empty(&self, cx: &mut ViewContext<Self>) { fn remove_self_if_empty(&self, window: &mut Window, cx: &mut Context<Self>) {
if !self.panels.is_empty() { if !self.panels.is_empty() {
return; return;
} }
let tab_view = cx.view().clone(); let tab_view = cx.model().clone();
if let Some(stack_panel) = self.stack_panel.as_ref() { if let Some(stack_panel) = self.stack_panel.as_ref() {
_ = stack_panel.update(cx, |view, cx| { _ = stack_panel.update(cx, |view, cx| {
view.remove_panel(Arc::new(tab_view), cx); view.remove_panel(Arc::new(tab_view), window, cx);
}); });
} }
} }
pub(super) fn set_collapsed(&mut self, collapsed: bool, cx: &mut ViewContext<Self>) { pub(super) fn set_collapsed(
&mut self,
collapsed: bool,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.is_collapsed = collapsed; self.is_collapsed = collapsed;
cx.notify(); cx.notify();
} }
fn is_locked(&self, cx: &AppContext) -> bool { fn is_locked(&self, cx: &App) -> bool {
let Some(dock_area) = self.dock_area.upgrade() else { let Some(dock_area) = self.dock_area.upgrade() else {
return true; return true;
}; };
@@ -309,7 +320,7 @@ impl TabPanel {
} }
/// Return true if self or parent only have last panel. /// Return true if self or parent only have last panel.
fn is_last_panel(&self, cx: &AppContext) -> bool { fn is_last_panel(&self, cx: &App) -> bool {
if let Some(parent) = &self.stack_panel { if let Some(parent) = &self.stack_panel {
if let Some(stack_panel) = parent.upgrade() { if let Some(stack_panel) = parent.upgrade() {
if !stack_panel.read(cx).is_last_panel(cx) { if !stack_panel.read(cx).is_last_panel(cx) {
@@ -324,21 +335,26 @@ impl TabPanel {
/// Return true if the tab panel is draggable. /// Return true if the tab panel is draggable.
/// ///
/// E.g. if the parent and self only have one panel, it is not draggable. /// E.g. if the parent and self only have one panel, it is not draggable.
fn draggable(&self, cx: &AppContext) -> bool { fn draggable(&self, cx: &App) -> bool {
!self.is_locked(cx) && !self.is_last_panel(cx) !self.is_locked(cx) && !self.is_last_panel(cx)
} }
/// Return true if the tab panel is droppable. /// Return true if the tab panel is droppable.
/// ///
/// E.g. if the tab panel is locked, it is not droppable. /// E.g. if the tab panel is locked, it is not droppable.
fn droppable(&self, cx: &AppContext) -> bool { fn droppable(&self, cx: &App) -> bool {
!self.is_locked(cx) !self.is_locked(cx)
} }
fn render_toolbar(&self, state: TabState, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_toolbar(
&self,
state: TabState,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let is_zoomed = self.is_zoomed && state.zoomable; let is_zoomed = self.is_zoomed && state.zoomable;
let view = cx.view().clone(); let view = cx.model().clone();
let build_popup_menu = move |this, cx: &WindowContext| view.read(cx).popup_menu(this, cx); let build_popup_menu = move |this, cx: &App| view.read(cx).popup_menu(this, cx);
// TODO: Do not show MenuButton if there is no menu items // TODO: Do not show MenuButton if there is no menu items
@@ -347,7 +363,7 @@ impl TabPanel {
.occlude() .occlude()
.items_center() .items_center()
.children( .children(
self.toolbar_buttons(cx) self.toolbar_buttons(window, cx)
.into_iter() .into_iter()
.map(|btn| btn.xsmall().ghost()), .map(|btn| btn.xsmall().ghost()),
) )
@@ -358,9 +374,9 @@ impl TabPanel {
.xsmall() .xsmall()
.ghost() .ghost()
.tooltip("Zoom Out") .tooltip("Zoom Out")
.on_click( .on_click(cx.listener(|view, _, window, cx| {
cx.listener(|view, _, cx| view.on_action_toggle_zoom(&ToggleZoom, cx)), view.on_action_toggle_zoom(&ToggleZoom, window, cx)
), })),
) )
}) })
.child( .child(
@@ -368,7 +384,7 @@ impl TabPanel {
.icon(IconName::Ellipsis) .icon(IconName::Ellipsis)
.xsmall() .xsmall()
.ghost() .ghost()
.popup_menu(move |this, cx| { .popup_menu(move |this, _window, cx| {
build_popup_menu(this, cx) build_popup_menu(this, cx)
.when(state.zoomable, |this| { .when(state.zoomable, |this| {
let name = if is_zoomed { "Zoom Out" } else { "Zoom In" }; let name = if is_zoomed { "Zoom Out" } else { "Zoom In" };
@@ -385,7 +401,8 @@ impl TabPanel {
fn render_dock_toggle_button( fn render_dock_toggle_button(
&self, &self,
placement: DockPlacement, placement: DockPlacement,
cx: &mut ViewContext<Self>, _window: &mut Window,
cx: &mut Context<Self>,
) -> Option<impl IntoElement> { ) -> Option<impl IntoElement> {
if self.is_zoomed { if self.is_zoomed {
return None; return None;
@@ -396,7 +413,7 @@ impl TabPanel {
return None; return None;
} }
let view_entity_id = cx.view().entity_id(); let view_entity_id = cx.model().entity_id();
let toggle_button_panels = dock_area.toggle_button_panels; let toggle_button_panels = dock_area.toggle_button_panels;
// Check if current TabPanel's entity_id matches the one stored in DockArea for this placement // Check if current TabPanel's entity_id matches the one stored in DockArea for this placement
@@ -454,26 +471,31 @@ impl TabPanel {
}) })
.on_click(cx.listener({ .on_click(cx.listener({
let dock_area = self.dock_area.clone(); let dock_area = self.dock_area.clone();
move |_, _, cx| { move |_, _, window, cx| {
_ = dock_area.update(cx, |dock_area, cx| { _ = dock_area.update(cx, |dock_area, cx| {
dock_area.toggle_dock(placement, cx); dock_area.toggle_dock(placement, window, cx);
}); });
} }
})), })),
) )
} }
fn render_title_bar(&self, state: TabState, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_title_bar(
let view = cx.view().clone(); &self,
state: TabState,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let view = cx.model().clone();
let Some(dock_area) = self.dock_area.upgrade() else { let Some(dock_area) = self.dock_area.upgrade() else {
return div().into_any_element(); return div().into_any_element();
}; };
let panel_style = dock_area.read(cx).panel_style; let panel_style = dock_area.read(cx).panel_style;
let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, cx); let left_dock_button = self.render_dock_toggle_button(DockPlacement::Left, window, cx);
let bottom_dock_button = self.render_dock_toggle_button(DockPlacement::Bottom, cx); let bottom_dock_button = self.render_dock_toggle_button(DockPlacement::Bottom, window, cx);
let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, cx); let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, window, cx);
if self.panels.len() == 1 && panel_style == PanelStyle::Default { if self.panels.len() == 1 && panel_style == PanelStyle::Default {
let panel = self.panels.first().unwrap(); let panel = self.panels.first().unwrap();
@@ -542,9 +564,9 @@ impl TabPanel {
panel: panel.clone(), panel: panel.clone(),
tab_panel: view, tab_panel: view,
}, },
|drag, _, cx| { |drag, _, _, cx| {
cx.stop_propagation(); cx.stop_propagation();
cx.new_view(|_| drag.clone()) cx.new(|_| drag.clone())
}, },
) )
}), }),
@@ -554,7 +576,7 @@ impl TabPanel {
.flex_shrink_0() .flex_shrink_0()
.ml_1() .ml_1()
.gap_1() .gap_1()
.child(self.render_toolbar(state, cx)) .child(self.render_toolbar(state, window, cx))
.children(right_dock_button), .children(right_dock_button),
) )
.into_any_element(); .into_any_element();
@@ -594,29 +616,29 @@ impl TabPanel {
.selected(active) .selected(active)
.disabled(disabled) .disabled(disabled)
.when(!disabled, |this| { .when(!disabled, |this| {
this.on_click(cx.listener(move |view, _, cx| { this.on_click(cx.listener(move |view, _, window, cx| {
view.set_active_ix(ix, cx); view.set_active_ix(ix, window, cx);
})) }))
.when(state.draggable, |this| { .when(state.draggable, |this| {
this.on_drag( this.on_drag(
DragPanel::new(panel.clone(), view.clone()), DragPanel::new(panel.clone(), view.clone()),
|drag, _, cx| { |drag, _, _, cx| {
cx.stop_propagation(); cx.stop_propagation();
cx.new_view(|_| drag.clone()) cx.new(|_| drag.clone())
}, },
) )
}) })
.when(state.droppable, |this| { .when(state.droppable, |this| {
this.drag_over::<DragPanel>(|this, _, cx| { this.drag_over::<DragPanel>(|this, _, _, cx| {
this.rounded_l_none() this.rounded_l_none()
.border_l_2() .border_l_2()
.border_r_0() .border_r_0()
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE)) .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
}) })
.on_drop(cx.listener( .on_drop(cx.listener(
move |this, drag: &DragPanel, cx| { move |this, drag: &DragPanel, window, cx| {
this.will_split_placement = None; this.will_split_placement = None;
this.on_drop(drag, Some(ix), true, cx) this.on_drop(drag, Some(ix), true, window, cx)
}, },
)) ))
}) })
@@ -630,11 +652,11 @@ impl TabPanel {
.flex_grow() .flex_grow()
.min_w_16() .min_w_16()
.when(state.droppable, |this| { .when(state.droppable, |this| {
this.drag_over::<DragPanel>(|this, _, cx| { this.drag_over::<DragPanel>(|this, _, _, cx| {
this.bg(cx.theme().base.step(cx, ColorScaleStep::TWO)) this.bg(cx.theme().base.step(cx, ColorScaleStep::TWO))
}) })
.on_drop(cx.listener( .on_drop(cx.listener(
move |this, drag: &DragPanel, cx| { move |this, drag: &DragPanel, window, cx| {
this.will_split_placement = None; this.will_split_placement = None;
let ix = if drag.tab_panel == view { let ix = if drag.tab_panel == view {
@@ -643,7 +665,7 @@ impl TabPanel {
None None
}; };
this.on_drop(drag, ix, false, cx) this.on_drop(drag, ix, false, window, cx)
}, },
)) ))
}), }),
@@ -656,13 +678,18 @@ impl TabPanel {
.h_full() .h_full()
.px_2() .px_2()
.gap_1() .gap_1()
.child(self.render_toolbar(state, cx)) .child(self.render_toolbar(state, window, cx))
.when_some(right_dock_button, |this, btn| this.child(btn)), .when_some(right_dock_button, |this, btn| this.child(btn)),
) )
.into_any_element() .into_any_element()
} }
fn render_active_panel(&self, state: TabState, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_active_panel(
&self,
state: TabState,
_window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
if self.is_collapsed { if self.is_collapsed {
return Empty {}.into_any_element(); return Empty {}.into_any_element();
} }
@@ -725,8 +752,8 @@ impl TabPanel {
None => this.top_0().left_0().size_full(), None => this.top_0().left_0().size_full(),
}) })
.group_drag_over::<DragPanel>("", |this| this.visible()) .group_drag_over::<DragPanel>("", |this| this.visible())
.on_drop(cx.listener(|this, drag: &DragPanel, cx| { .on_drop(cx.listener(|this, drag: &DragPanel, window, cx| {
this.on_drop(drag, None, true, cx) this.on_drop(drag, None, true, window, cx)
})), })),
) )
}) })
@@ -736,7 +763,12 @@ impl TabPanel {
} }
/// Calculate the split direction based on the current mouse position /// Calculate the split direction based on the current mouse position
fn on_panel_drag_move(&mut self, drag: &DragMoveEvent<DragPanel>, cx: &mut ViewContext<Self>) { fn on_panel_drag_move(
&mut self,
drag: &DragMoveEvent<DragPanel>,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let bounds = drag.bounds; let bounds = drag.bounds;
let position = drag.event.position; let position = drag.event.position;
@@ -764,10 +796,11 @@ impl TabPanel {
drag: &DragPanel, drag: &DragPanel,
ix: Option<usize>, ix: Option<usize>,
active: bool, active: bool,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
let panel = drag.panel.clone(); let panel = drag.panel.clone();
let is_same_tab = drag.tab_panel == *cx.view(); let is_same_tab = drag.tab_panel == cx.model();
// If target is same tab, and it is only one panel, do nothing. // If target is same tab, and it is only one panel, do nothing.
if is_same_tab && ix.is_none() { if is_same_tab && ix.is_none() {
@@ -784,24 +817,24 @@ impl TabPanel {
// We must to split it to remove_panel, unless it will be crash by error: // We must to split it to remove_panel, unless it will be crash by error:
// Cannot update ui::dock::tab_panel::TabPanel while it is already being updated // Cannot update ui::dock::tab_panel::TabPanel while it is already being updated
if is_same_tab { if is_same_tab {
self.detach_panel(panel.clone(), cx); self.detach_panel(panel.clone(), window, cx);
} else { } else {
drag.tab_panel.update(cx, |view, cx| { drag.tab_panel.update(cx, |view, cx| {
view.detach_panel(panel.clone(), cx); view.detach_panel(panel.clone(), window, cx);
view.remove_self_if_empty(cx); view.remove_self_if_empty(window, cx);
}); });
} }
// Insert into new tabs // Insert into new tabs
if let Some(placement) = self.will_split_placement { if let Some(placement) = self.will_split_placement {
self.split_panel(panel, placement, None, cx); self.split_panel(panel, placement, None, window, cx);
} else if let Some(ix) = ix { } else if let Some(ix) = ix {
self.insert_panel_at(panel, ix, cx) self.insert_panel_at(panel, ix, window, cx)
} else { } else {
self.add_panel_with_active(panel, active, cx) self.add_panel_with_active(panel, active, window, cx)
} }
self.remove_self_if_empty(cx); self.remove_self_if_empty(window, cx);
cx.emit(PanelEvent::LayoutChanged); cx.emit(PanelEvent::LayoutChanged);
} }
@@ -811,13 +844,14 @@ impl TabPanel {
panel: Arc<dyn PanelView>, panel: Arc<dyn PanelView>,
placement: Placement, placement: Placement,
size: Option<Pixels>, size: Option<Pixels>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
let dock_area = self.dock_area.clone(); let dock_area = self.dock_area.clone();
// wrap the panel in a TabPanel // wrap the panel in a TabPanel
let new_tab_panel = cx.new_view(|cx| Self::new(None, dock_area.clone(), cx)); let new_tab_panel = cx.new(|cx| Self::new(None, dock_area.clone(), window, cx));
new_tab_panel.update(cx, |view, cx| { new_tab_panel.update(cx, |view, cx| {
view.add_panel(panel, cx); view.add_panel(panel, window, cx);
}); });
let stack_panel = match self.stack_panel.as_ref().and_then(|panel| panel.upgrade()) { let stack_panel = match self.stack_panel.as_ref().and_then(|panel| panel.upgrade()) {
@@ -829,7 +863,7 @@ impl TabPanel {
let ix = stack_panel let ix = stack_panel
.read(cx) .read(cx)
.index_of_panel(Arc::new(cx.view().clone())) .index_of_panel(Arc::new(cx.model().clone()))
.unwrap_or_default(); .unwrap_or_default();
if parent_axis.is_vertical() && placement.is_vertical() { if parent_axis.is_vertical() && placement.is_vertical() {
@@ -840,6 +874,7 @@ impl TabPanel {
placement, placement,
size, size,
dock_area.clone(), dock_area.clone(),
window,
cx, cx,
); );
}); });
@@ -851,26 +886,27 @@ impl TabPanel {
placement, placement,
size, size,
dock_area.clone(), dock_area.clone(),
window,
cx, cx,
); );
}); });
} else { } else {
// 1. Create new StackPanel with new axis // 1. Create new StackPanel with new axis
// 2. Move cx.view() from parent StackPanel to the new StackPanel // 2. Move cx.model() from parent StackPanel to the new StackPanel
// 3. Add the new TabPanel to the new StackPanel at the correct index // 3. Add the new TabPanel to the new StackPanel at the correct index
// 4. Add new StackPanel to the parent StackPanel at the correct index // 4. Add new StackPanel to the parent StackPanel at the correct index
let tab_panel = cx.view().clone(); let tab_panel = cx.model().clone();
// Try to use the old stack panel, not just create a new one, to avoid too many nested stack panels // Try to use the old stack panel, not just create a new one, to avoid too many nested stack panels
let new_stack_panel = if stack_panel.read(cx).panels_len() <= 1 { let new_stack_panel = if stack_panel.read(cx).panels_len() <= 1 {
stack_panel.update(cx, |view, cx| { stack_panel.update(cx, |view, cx| {
view.remove_all_panels(cx); view.remove_all_panels(window, cx);
view.set_axis(placement.axis(), cx); view.set_axis(placement.axis(), window, cx);
}); });
stack_panel.clone() stack_panel.clone()
} else { } else {
cx.new_view(|cx| { cx.new(|cx| {
let mut panel = StackPanel::new(placement.axis(), cx); let mut panel = StackPanel::new(placement.axis(), window, cx);
panel.parent = Some(stack_panel.downgrade()); panel.parent = Some(stack_panel.downgrade());
panel panel
}) })
@@ -878,23 +914,42 @@ impl TabPanel {
new_stack_panel.update(cx, |view, cx| match placement { new_stack_panel.update(cx, |view, cx| match placement {
Placement::Left | Placement::Top => { Placement::Left | Placement::Top => {
view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), cx); view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), window, cx);
view.add_panel(Arc::new(tab_panel.clone()), None, dock_area.clone(), cx); view.add_panel(
Arc::new(tab_panel.clone()),
None,
dock_area.clone(),
window,
cx,
);
} }
Placement::Right | Placement::Bottom => { Placement::Right | Placement::Bottom => {
view.add_panel(Arc::new(tab_panel.clone()), None, dock_area.clone(), cx); view.add_panel(
view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), cx); Arc::new(tab_panel.clone()),
None,
dock_area.clone(),
window,
cx,
);
view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), window, cx);
} }
}); });
if stack_panel != new_stack_panel { if stack_panel != new_stack_panel {
stack_panel.update(cx, |view, cx| { stack_panel.update(cx, |view, cx| {
view.replace_panel(Arc::new(tab_panel.clone()), new_stack_panel.clone(), cx); view.replace_panel(
Arc::new(tab_panel.clone()),
new_stack_panel.clone(),
window,
cx,
);
}); });
} }
cx.spawn(|_, mut cx| async move { cx.spawn_in(window, |_, mut cx| async move {
cx.update(|cx| tab_panel.update(cx, |view, cx| view.remove_self_if_empty(cx))) cx.update(|window, cx| {
tab_panel.update(cx, |view, cx| view.remove_self_if_empty(window, cx))
})
}) })
.detach() .detach()
} }
@@ -902,13 +957,18 @@ impl TabPanel {
cx.emit(PanelEvent::LayoutChanged); cx.emit(PanelEvent::LayoutChanged);
} }
fn focus_active_panel(&self, cx: &mut ViewContext<Self>) { fn focus_active_panel(&self, window: &mut Window, cx: &mut Context<Self>) {
if let Some(active_panel) = self.active_panel() { if let Some(active_panel) = self.active_panel() {
active_panel.focus_handle(cx).focus(cx); active_panel.focus_handle(cx).focus(window);
} }
} }
fn on_action_toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) { fn on_action_toggle_zoom(
&mut self,
_action: &ToggleZoom,
_window: &mut Window,
cx: &mut Context<Self>,
) {
if !self.zoomable(cx) { if !self.zoomable(cx) {
return; return;
} }
@@ -921,15 +981,20 @@ impl TabPanel {
self.is_zoomed = !self.is_zoomed; self.is_zoomed = !self.is_zoomed;
} }
fn on_action_close_panel(&mut self, _: &ClosePanel, cx: &mut ViewContext<Self>) { fn on_action_close_panel(
&mut self,
_: &ClosePanel,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(panel) = self.active_panel() { if let Some(panel) = self.active_panel() {
self.remove_panel(panel, cx); self.remove_panel(panel, window, cx);
} }
} }
} }
impl FocusableView for TabPanel { impl Focusable for TabPanel {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
if let Some(active_panel) = self.active_panel() { if let Some(active_panel) = self.active_panel() {
active_panel.focus_handle(cx) active_panel.focus_handle(cx)
} else { } else {
@@ -943,7 +1008,7 @@ impl EventEmitter<DismissEvent> for TabPanel {}
impl EventEmitter<PanelEvent> for TabPanel {} impl EventEmitter<PanelEvent> for TabPanel {}
impl Render for TabPanel { impl Render for TabPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl gpui::IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {
let focus_handle = self.focus_handle(cx); let focus_handle = self.focus_handle(cx);
let mut state = TabState { let mut state = TabState {
closeable: self.closeable(cx), closeable: self.closeable(cx),
@@ -962,7 +1027,7 @@ impl Render for TabPanel {
.on_action(cx.listener(Self::on_action_close_panel)) .on_action(cx.listener(Self::on_action_close_panel))
.size_full() .size_full()
.overflow_hidden() .overflow_hidden()
.child(self.render_title_bar(state, cx)) .child(self.render_title_bar(state, window, cx))
.child(self.render_active_panel(state, cx)) .child(self.render_active_panel(state, window, cx))
} }
} }

View File

@@ -1,22 +1,21 @@
use crate::{ use crate::{
h_flex, h_flex,
input::ClearButton,
list::{self, List, ListDelegate, ListItem}, list::{self, List, ListDelegate, ListItem},
theme::{scale::ColorScaleStep, ActiveTheme}, theme::{scale::ColorScaleStep, ActiveTheme},
v_flex, Disableable, Icon, IconName, Sizable, Size, StyleSized, StyledExt, v_flex, Icon, IconName, Sizable, Size, StyleSized, StyledExt,
}; };
use gpui::{ use gpui::{
actions, anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, actions, anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, App,
AppContext, Bounds, ClickEvent, DismissEvent, ElementId, EventEmitter, FocusHandle, AppContext, Bounds, ClickEvent, Context, DismissEvent, ElementId, Entity, EventEmitter,
FocusableView, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement, Pixels, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement,
Render, SharedString, StatefulInteractiveElement, Styled, Task, View, ViewContext, Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Task, WeakEntity, Window,
VisualContext, WeakView, WindowContext,
}; };
actions!(dropdown, [Up, Down, Enter, Escape]); actions!(dropdown, [Up, Down, Enter, Escape]);
const CONTEXT: &str = "Dropdown"; const CONTEXT: &str = "Dropdown";
pub fn init(cx: &mut AppContext) {
pub fn init(cx: &mut App) {
cx.bind_keys([ cx.bind_keys([
KeyBinding::new("up", Up, Some(CONTEXT)), KeyBinding::new("up", Up, Some(CONTEXT)),
KeyBinding::new("down", Down, Some(CONTEXT)), KeyBinding::new("down", Down, Some(CONTEXT)),
@@ -79,7 +78,12 @@ pub trait DropdownDelegate: Sized {
false false
} }
fn perform_search(&mut self, _query: &str, _cx: &mut ViewContext<Dropdown<Self>>) -> Task<()> { fn perform_search(
&mut self,
_query: &str,
_window: &mut Window,
_cx: &mut Context<Dropdown<Self>>,
) -> Task<()> {
Task::ready(()) Task::ready(())
} }
} }
@@ -106,7 +110,7 @@ impl<T: DropdownItem> DropdownDelegate for Vec<T> {
struct DropdownListDelegate<D: DropdownDelegate + 'static> { struct DropdownListDelegate<D: DropdownDelegate + 'static> {
delegate: D, delegate: D,
dropdown: WeakView<Dropdown<D>>, dropdown: WeakEntity<Dropdown<D>>,
selected_index: Option<usize>, selected_index: Option<usize>,
} }
@@ -116,15 +120,20 @@ where
{ {
type Item = ListItem; type Item = ListItem;
fn items_count(&self, _: &AppContext) -> usize { fn items_count(&self, _: &App) -> usize {
self.delegate.len() self.delegate.len()
} }
fn confirmed_index(&self, _: &AppContext) -> Option<usize> { fn confirmed_index(&self, _: &App) -> Option<usize> {
self.selected_index self.selected_index
} }
fn render_item(&self, ix: usize, cx: &mut gpui::ViewContext<List<Self>>) -> Option<Self::Item> { fn render_item(
&self,
ix: usize,
_window: &mut gpui::Window,
cx: &mut gpui::Context<List<Self>>,
) -> Option<Self::Item> {
let selected = self.selected_index == Some(ix); let selected = self.selected_index == Some(ix);
let size = self let size = self
.dropdown .dropdown
@@ -145,17 +154,17 @@ where
} }
} }
fn cancel(&mut self, cx: &mut ViewContext<List<Self>>) { fn cancel(&mut self, window: &mut Window, cx: &mut Context<List<Self>>) {
let dropdown = self.dropdown.clone(); let dropdown = self.dropdown.clone();
cx.defer(move |_, cx| { cx.defer_in(window, move |_, window, cx| {
_ = dropdown.update(cx, |this, cx| { _ = dropdown.update(cx, |this, cx| {
this.open = false; this.open = false;
this.focus(cx); this.focus(window, cx);
}); });
}); });
} }
fn confirm(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>) { fn confirm(&mut self, ix: Option<usize>, window: &mut Window, cx: &mut Context<List<Self>>) {
self.selected_index = ix; self.selected_index = ix;
let selected_value = self let selected_value = self
@@ -164,33 +173,43 @@ where
.map(|item| item.value().clone()); .map(|item| item.value().clone());
let dropdown = self.dropdown.clone(); let dropdown = self.dropdown.clone();
cx.defer(move |_, cx| { cx.defer_in(window, move |_, window, cx| {
_ = dropdown.update(cx, |this, cx| { _ = dropdown.update(cx, |this, cx| {
cx.emit(DropdownEvent::Confirm(selected_value.clone())); cx.emit(DropdownEvent::Confirm(selected_value.clone()));
this.selected_value = selected_value; this.selected_value = selected_value;
this.open = false; this.open = false;
this.focus(cx); this.focus(window, cx);
}); });
}); });
} }
fn perform_search(&mut self, query: &str, cx: &mut ViewContext<List<Self>>) -> Task<()> { fn perform_search(
&mut self,
query: &str,
window: &mut Window,
cx: &mut Context<List<Self>>,
) -> Task<()> {
self.dropdown.upgrade().map_or(Task::ready(()), |dropdown| { self.dropdown.upgrade().map_or(Task::ready(()), |dropdown| {
dropdown.update(cx, |_, cx| self.delegate.perform_search(query, cx)) dropdown.update(cx, |_, cx| self.delegate.perform_search(query, window, cx))
}) })
} }
fn set_selected_index(&mut self, ix: Option<usize>, _: &mut ViewContext<List<Self>>) { fn set_selected_index(
&mut self,
ix: Option<usize>,
_: &mut Window,
_: &mut Context<List<Self>>,
) {
self.selected_index = ix; self.selected_index = ix;
} }
fn render_empty(&self, cx: &mut ViewContext<List<Self>>) -> impl IntoElement { fn render_empty(&self, window: &mut Window, cx: &mut Context<List<Self>>) -> impl IntoElement {
if let Some(empty) = self if let Some(empty) = self
.dropdown .dropdown
.upgrade() .upgrade()
.and_then(|dropdown| dropdown.read(cx).empty.as_ref()) .and_then(|dropdown| dropdown.read(cx).empty.as_ref())
{ {
empty(cx).into_any_element() empty(window, cx).into_any_element()
} else { } else {
h_flex() h_flex()
.justify_center() .justify_center()
@@ -206,21 +225,18 @@ pub enum DropdownEvent<D: DropdownDelegate + 'static> {
Confirm(Option<<D::Item as DropdownItem>::Value>), Confirm(Option<<D::Item as DropdownItem>::Value>),
} }
type Empty = Option<Box<dyn Fn(&WindowContext) -> AnyElement + 'static>>;
/// A Dropdown element. /// A Dropdown element.
pub struct Dropdown<D: DropdownDelegate + 'static> { pub struct Dropdown<D: DropdownDelegate + 'static> {
id: ElementId, id: ElementId,
focus_handle: FocusHandle, focus_handle: FocusHandle,
list: View<List<DropdownListDelegate<D>>>, list: Entity<List<DropdownListDelegate<D>>>,
size: Size, size: Size,
icon: Option<IconName>, icon: Option<IconName>,
open: bool, open: bool,
cleanable: bool,
placeholder: Option<SharedString>, placeholder: Option<SharedString>,
title_prefix: Option<SharedString>, title_prefix: Option<SharedString>,
selected_value: Option<<D::Item as DropdownItem>::Value>, selected_value: Option<<D::Item as DropdownItem>::Value>,
empty: Empty, empty: Option<Box<dyn Fn(&Window, &App) -> AnyElement + 'static>>,
width: Length, width: Length,
menu_width: Length, menu_width: Length,
/// Store the bounds of the input /// Store the bounds of the input
@@ -272,7 +288,12 @@ impl<T: DropdownItem + Clone> DropdownDelegate for SearchableVec<T> {
true true
} }
fn perform_search(&mut self, query: &str, _cx: &mut ViewContext<Dropdown<Self>>) -> Task<()> { fn perform_search(
&mut self,
query: &str,
_window: &mut Window,
_cx: &mut Context<Dropdown<Self>>,
) -> Task<()> {
self.matched_items = self self.matched_items = self
.items .items
.iter() .iter()
@@ -301,27 +322,30 @@ where
id: impl Into<ElementId>, id: impl Into<ElementId>,
delegate: D, delegate: D,
selected_index: Option<usize>, selected_index: Option<usize>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
let delegate = DropdownListDelegate { let delegate = DropdownListDelegate {
delegate, delegate,
dropdown: cx.view().downgrade(), dropdown: cx.model().downgrade(),
selected_index, selected_index,
}; };
let searchable = delegate.delegate.can_search(); let searchable = delegate.delegate.can_search();
let list = cx.new_view(|cx| { let list = cx.new(|cx| {
let mut list = List::new(delegate, cx).max_h(rems(20.)); let mut list = List::new(delegate, window, cx).max_h(rems(20.));
if !searchable { if !searchable {
list = list.no_query(); list = list.no_query();
} }
list list
}); });
cx.on_blur(&list.focus_handle(cx), Self::on_blur).detach(); cx.on_blur(&list.focus_handle(cx), window, Self::on_blur)
cx.on_blur(&focus_handle, Self::on_blur).detach(); .detach();
cx.on_blur(&focus_handle, window, Self::on_blur).detach();
let mut this = Self { let mut this = Self {
id: id.into(), id: id.into(),
@@ -332,7 +356,6 @@ where
icon: None, icon: None,
selected_value: None, selected_value: None,
open: false, open: false,
cleanable: false,
title_prefix: None, title_prefix: None,
empty: None, empty: None,
width: Length::Auto, width: Length::Auto,
@@ -340,7 +363,7 @@ where
bounds: Bounds::default(), bounds: Bounds::default(),
disabled: false, disabled: false,
}; };
this.set_selected_index(selected_index, cx); this.set_selected_index(selected_index, window, cx);
this this
} }
@@ -378,12 +401,6 @@ where
self self
} }
/// Set true to show the clear button when the input field is not empty.
pub fn cleanable(mut self) -> Self {
self.cleanable = true;
self
}
/// Set the disable state for the dropdown. /// Set the disable state for the dropdown.
pub fn disabled(mut self, disabled: bool) -> Self { pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled; self.disabled = disabled;
@@ -397,42 +414,44 @@ where
pub fn empty<E, F>(mut self, f: F) -> Self pub fn empty<E, F>(mut self, f: F) -> Self
where where
E: IntoElement, E: IntoElement,
F: Fn(&WindowContext) -> E + 'static, F: Fn(&Window, &App) -> E + 'static,
{ {
self.empty = Some(Box::new(move |cx| f(cx).into_any_element())); self.empty = Some(Box::new(move |window, cx| f(window, cx).into_any_element()));
self self
} }
pub fn set_selected_index( pub fn set_selected_index(
&mut self, &mut self,
selected_index: Option<usize>, selected_index: Option<usize>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
self.list.update(cx, |list, cx| { self.list.update(cx, |list, cx| {
list.set_selected_index(selected_index, cx); list.set_selected_index(selected_index, window, cx);
}); });
self.update_selected_value(cx); self.update_selected_value(window, cx);
} }
pub fn set_selected_value( pub fn set_selected_value(
&mut self, &mut self,
selected_value: &<D::Item as DropdownItem>::Value, selected_value: &<D::Item as DropdownItem>::Value,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) where ) where
<<D as DropdownDelegate>::Item as DropdownItem>::Value: PartialEq, <<D as DropdownDelegate>::Item as DropdownItem>::Value: PartialEq,
{ {
let delegate = self.list.read(cx).delegate(); let delegate = self.list.read(cx).delegate();
let selected_index = delegate.delegate.position(selected_value); let selected_index = delegate.delegate.position(selected_value);
self.set_selected_index(selected_index, cx); self.set_selected_index(selected_index, window, cx);
} }
pub fn selected_index(&self, cx: &WindowContext) -> Option<usize> { pub fn selected_index(&self, _window: &Window, cx: &App) -> Option<usize> {
self.list.read(cx).selected_index() self.list.read(cx).selected_index()
} }
fn update_selected_value(&mut self, cx: &WindowContext) { fn update_selected_value(&mut self, window: &Window, cx: &App) {
self.selected_value = self self.selected_value = self
.selected_index(cx) .selected_index(window, cx)
.and_then(|ix| self.list.read(cx).delegate().delegate.get(ix)) .and_then(|ix| self.list.read(cx).delegate().delegate.get(ix))
.map(|item| item.value().clone()); .map(|item| item.value().clone());
} }
@@ -441,13 +460,13 @@ where
self.selected_value.as_ref() self.selected_value.as_ref()
} }
pub fn focus(&self, cx: &mut WindowContext) { pub fn focus(&self, window: &mut Window, _: &mut App) {
self.focus_handle.focus(cx); self.focus_handle.focus(window);
} }
fn on_blur(&mut self, cx: &mut ViewContext<Self>) { fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
// When the dropdown and dropdown menu are both not focused, close the dropdown menu. // When the dropdown and dropdown menu are both not focused, close the dropdown menu.
if self.list.focus_handle(cx).is_focused(cx) || self.focus_handle.is_focused(cx) { if self.list.focus_handle(cx).is_focused(window) || self.focus_handle.is_focused(window) {
return; return;
} }
@@ -455,24 +474,24 @@ where
cx.notify(); cx.notify();
} }
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) { fn up(&mut self, _: &Up, window: &mut Window, cx: &mut Context<Self>) {
if !self.open { if !self.open {
return; return;
} }
self.list.focus_handle(cx).focus(cx); self.list.focus_handle(cx).focus(window);
cx.dispatch_action(Box::new(list::SelectPrev)); cx.dispatch_action(&list::SelectPrev);
} }
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) { fn down(&mut self, _: &Down, window: &mut Window, cx: &mut Context<Self>) {
if !self.open { if !self.open {
self.open = true; self.open = true;
} }
self.list.focus_handle(cx).focus(cx); self.list.focus_handle(cx).focus(window);
cx.dispatch_action(Box::new(list::SelectNext)); cx.dispatch_action(&list::SelectNext);
} }
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) { fn enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context<Self>) {
// Propagate the event to the parent view, for example to the Modal to support ENTER to confirm. // Propagate the event to the parent view, for example to the Modal to support ENTER to confirm.
cx.propagate(); cx.propagate();
@@ -480,22 +499,22 @@ where
self.open = true; self.open = true;
cx.notify(); cx.notify();
} else { } else {
self.list.focus_handle(cx).focus(cx); self.list.focus_handle(cx).focus(window);
cx.dispatch_action(Box::new(list::Confirm)); cx.dispatch_action(&list::Confirm);
} }
} }
fn toggle_menu(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) { fn toggle_menu(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
cx.stop_propagation(); cx.stop_propagation();
self.open = !self.open; self.open = !self.open;
if self.open { if self.open {
self.list.focus_handle(cx).focus(cx); self.list.focus_handle(cx).focus(window);
} }
cx.notify(); cx.notify();
} }
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) { fn escape(&mut self, _: &Escape, _window: &mut Window, cx: &mut Context<Self>) {
// Propagate the event to the parent view, for example to the Modal to support ESC to close. // Propagate the event to the parent view, for example to the Modal to support ESC to close.
cx.propagate(); cx.propagate();
@@ -503,13 +522,13 @@ where
cx.notify(); cx.notify();
} }
fn clean(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) { fn clean(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
self.set_selected_index(None, cx); self.set_selected_index(None, window, cx);
cx.emit(DropdownEvent::Confirm(None)); cx.emit(DropdownEvent::Confirm(None));
} }
fn display_title(&self, cx: &WindowContext) -> impl IntoElement { fn display_title(&self, window: &Window, cx: &App) -> impl IntoElement {
let title = if let Some(selected_index) = &self.selected_index(cx) { let title = if let Some(selected_index) = &self.selected_index(window, cx) {
let title = self let title = self
.list .list
.read(cx) .read(cx)
@@ -551,11 +570,11 @@ where
impl<D> EventEmitter<DropdownEvent<D>> for Dropdown<D> where D: DropdownDelegate + 'static {} impl<D> EventEmitter<DropdownEvent<D>> for Dropdown<D> where D: DropdownDelegate + 'static {}
impl<D> EventEmitter<DismissEvent> for Dropdown<D> where D: DropdownDelegate + 'static {} impl<D> EventEmitter<DismissEvent> for Dropdown<D> where D: DropdownDelegate + 'static {}
impl<D> FocusableView for Dropdown<D> impl<D> Focusable for Dropdown<D>
where where
D: DropdownDelegate + 'static, D: DropdownDelegate + 'static,
{ {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
if self.open { if self.open {
self.list.focus_handle(cx) self.list.focus_handle(cx)
} else { } else {
@@ -568,10 +587,9 @@ impl<D> Render for Dropdown<D>
where where
D: DropdownDelegate + 'static, D: DropdownDelegate + 'static,
{ {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_focused = self.focus_handle.is_focused(cx); let is_focused = self.focus_handle.is_focused(window);
let show_clean = self.cleanable && self.selected_index(cx).is_some(); let view = cx.model().clone();
let view = cx.view().clone();
let bounds = self.bounds; let bounds = self.bounds;
let allow_open = !(self.open || self.disabled); let allow_open = !(self.open || self.disabled);
let outline_visible = self.open || is_focused && !self.disabled; let outline_visible = self.open || is_focused && !self.disabled;
@@ -579,7 +597,7 @@ where
// If the size has change, set size to self.list, to change the QueryInput size. // If the size has change, set size to self.list, to change the QueryInput size.
if self.list.read(cx).size != self.size { if self.list.read(cx).size != self.size {
self.list self.list
.update(cx, |this, cx| this.set_size(self.size, cx)) .update(cx, |this, cx| this.set_size(self.size, window, cx))
} }
div() div()
@@ -618,7 +636,7 @@ where
Length::Definite(l) => this.flex_none().w(l), Length::Definite(l) => this.flex_none().w(l),
Length::Auto => this.w_full(), Length::Auto => this.w_full(),
}) })
.when(outline_visible, |this| this.outline(cx)) .when(outline_visible, |this| this.outline(window, cx))
.input_size(self.size) .input_size(self.size)
.when(allow_open, |this| { .when(allow_open, |this| {
this.on_click(cx.listener(Self::toggle_menu)) this.on_click(cx.listener(Self::toggle_menu))
@@ -633,18 +651,9 @@ where
div() div()
.w_full() .w_full()
.overflow_hidden() .overflow_hidden()
.child(self.display_title(cx)), .child(self.display_title(window, cx)),
) )
.when(show_clean, |this| { .map(|this| {
this.child(ClearButton::new(cx).map(|this| {
if self.disabled {
this.disabled(true)
} else {
this.on_click(cx.listener(Self::clean))
}
}))
})
.when(!show_clean, |this| {
let icon = match self.icon.clone() { let icon = match self.icon.clone() {
Some(icon) => icon, Some(icon) => icon,
None => { None => {
@@ -671,8 +680,8 @@ where
) )
.child( .child(
canvas( canvas(
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds), move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|_, _, _| {}, |_, _, _, _| {},
) )
.absolute() .absolute()
.size_full(), .size_full(),
@@ -699,13 +708,13 @@ where
) )
.rounded(px(cx.theme().radius)) .rounded(px(cx.theme().radius))
.shadow_md() .shadow_md()
.on_mouse_down_out(|_, cx| { .on_mouse_down_out(|_, _, cx| {
cx.dispatch_action(Box::new(Escape)); cx.dispatch_action(&Escape);
}) })
.child(self.list.clone()), .child(self.list.clone()),
) )
.on_mouse_down_out(cx.listener(|this, _, cx| { .on_mouse_down_out(cx.listener(|this, _, window, cx| {
this.escape(&Escape, cx); this.escape(&Escape, window, cx);
})), })),
), ),
) )

View File

@@ -1,22 +1,22 @@
use gpui::{ClickEvent, Focusable, InteractiveElement, Stateful, WindowContext}; use gpui::{App, ClickEvent, FocusableWrapper, InteractiveElement, Stateful, Window};
pub trait InteractiveElementExt: InteractiveElement { pub trait InteractiveElementExt: InteractiveElement {
/// Set the listener for a double click event. /// Set the listener for a double click event.
fn on_double_click( fn on_double_click(
mut self, mut self,
listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self ) -> Self
where where
Self: Sized, Self: Sized,
{ {
self.interactivity().on_click(move |event, context| { self.interactivity().on_click(move |event, window, cx| {
if event.up.click_count == 2 { if event.up.click_count == 2 {
listener(event, context); listener(event, window, cx);
} }
}); });
self self
} }
} }
impl<E: InteractiveElement> InteractiveElementExt for Focusable<E> {} impl<E: InteractiveElement> InteractiveElementExt for FocusableWrapper<E> {}
impl<E: InteractiveElement> InteractiveElementExt for Stateful<E> {} impl<E: InteractiveElement> InteractiveElementExt for Stateful<E> {}

View File

@@ -1,4 +1,4 @@
use gpui::{FocusHandle, ViewContext}; use gpui::{Context, FocusHandle, Window};
/// A trait for views that can cycle focus between its children. /// A trait for views that can cycle focus between its children.
/// ///
@@ -8,18 +8,18 @@ use gpui::{FocusHandle, ViewContext};
/// should be cycled, and the cycle will follow the order of the list. /// should be cycled, and the cycle will follow the order of the list.
pub trait FocusableCycle { pub trait FocusableCycle {
/// Returns a list of focus handles that should be cycled. /// Returns a list of focus handles that should be cycled.
fn cycle_focus_handles(&self, cx: &mut ViewContext<Self>) -> Vec<FocusHandle> fn cycle_focus_handles(&self, window: &mut Window, cx: &mut Context<Self>) -> Vec<FocusHandle>
where where
Self: Sized; Self: Sized;
/// Cycles focus between the focus handles returned by `cycle_focus_handles`. /// Cycles focus between the focus handles returned by `cycle_focus_handles`.
/// If `is_next` is `true`, it will cycle to the next focus handle, otherwise it will cycle to prev. /// If `is_next` is `true`, it will cycle to the next focus handle, otherwise it will cycle to prev.
fn cycle_focus(&self, is_next: bool, cx: &mut ViewContext<Self>) fn cycle_focus(&self, is_next: bool, window: &mut Window, cx: &mut Context<Self>)
where where
Self: Sized, Self: Sized,
{ {
let focused_handle = cx.focused(); let focused_handle = window.focused(cx);
let handles = self.cycle_focus_handles(cx); let handles = self.cycle_focus_handles(window, cx);
let handles = if is_next { let handles = if is_next {
handles handles
} else { } else {
@@ -33,7 +33,7 @@ pub trait FocusableCycle {
.nth(1) .nth(1)
.unwrap_or(fallback_handle); .unwrap_or(fallback_handle);
target_focus_handle.focus(cx); target_focus_handle.focus(window);
cx.stop_propagation(); cx.stop_propagation();
} }
} }

View File

@@ -3,8 +3,9 @@ use crate::{
Sizable, Size, Sizable, Size,
}; };
use gpui::{ use gpui::{
prelude::FluentBuilder as _, svg, AnyElement, Hsla, IntoElement, Radians, Render, RenderOnce, prelude::FluentBuilder as _, svg, AnyElement, App, AppContext, Entity, Hsla, IntoElement,
SharedString, StyleRefinement, Styled, Svg, Transformation, View, VisualContext, WindowContext, Radians, Render, RenderOnce, SharedString, StyleRefinement, Styled, Svg, Transformation,
Window,
}; };
#[derive(IntoElement, Clone)] #[derive(IntoElement, Clone)]
@@ -174,9 +175,9 @@ impl IconName {
.into() .into()
} }
/// Return the icon as a View<Icon> /// Return the icon as a Entity<Icon>
pub fn view(self, cx: &mut WindowContext) -> View<Icon> { pub fn view(self, window: &mut Window, cx: &mut App) -> Entity<Icon> {
Icon::build(self).view(cx) Icon::build(self).view(window, cx)
} }
} }
@@ -193,7 +194,7 @@ impl From<IconName> for AnyElement {
} }
impl RenderOnce for IconName { impl RenderOnce for IconName {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
Icon::build(self) Icon::build(self)
} }
} }
@@ -251,8 +252,8 @@ impl Icon {
} }
/// Create a new view for the icon /// Create a new view for the icon
pub fn view(self, cx: &mut WindowContext) -> View<Icon> { pub fn view(self, _window: &mut Window, cx: &mut App) -> Entity<Icon> {
cx.new_view(|_| self) cx.new(|_| self)
} }
pub fn transform(mut self, transformation: gpui::Transformation) -> Self { pub fn transform(mut self, transformation: gpui::Transformation) -> Self {
@@ -292,8 +293,8 @@ impl Sizable for Icon {
} }
impl RenderOnce for Icon { impl RenderOnce for Icon {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
let text_color = self.text_color.unwrap_or_else(|| cx.text_style().color); let text_color = self.text_color.unwrap_or_else(|| window.text_style().color);
self.base self.base
.text_color(text_color) .text_color(text_color)
@@ -315,7 +316,11 @@ impl From<Icon> for AnyElement {
} }
impl Render for Icon { impl Render for Icon {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement { fn render(
&mut self,
_window: &mut gpui::Window,
cx: &mut gpui::Context<Self>,
) -> impl IntoElement {
let text_color = self let text_color = self
.text_color .text_color
.unwrap_or_else(|| cx.theme().base.step(cx, ColorScaleStep::ELEVEN)); .unwrap_or_else(|| cx.theme().base.step(cx, ColorScaleStep::ELEVEN));

View File

@@ -1,7 +1,7 @@
use crate::{Icon, IconName, Sizable, Size}; use crate::{Icon, IconName, Sizable, Size};
use gpui::{ use gpui::{
div, ease_in_out, percentage, prelude::FluentBuilder as _, Animation, AnimationExt as _, Hsla, div, ease_in_out, percentage, prelude::FluentBuilder as _, Animation, AnimationExt as _, App,
IntoElement, ParentElement, RenderOnce, Styled as _, Transformation, WindowContext, Hsla, IntoElement, ParentElement, RenderOnce, Styled as _, Transformation, Window,
}; };
use std::time::Duration; use std::time::Duration;
@@ -48,7 +48,7 @@ impl Sizable for Indicator {
} }
impl RenderOnce for Indicator { impl RenderOnce for Indicator {
fn render(self, _: &mut WindowContext) -> impl IntoElement { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
div() div()
.child( .child(
self.icon self.icon

View File

@@ -1,4 +1,4 @@
use gpui::{ModelContext, Timer}; use gpui::{Context, Timer};
use std::time::Duration; use std::time::Duration;
static INTERVAL: Duration = Duration::from_millis(500); static INTERVAL: Duration = Duration::from_millis(500);
@@ -26,11 +26,11 @@ impl BlinkCursor {
} }
/// Start the blinking /// Start the blinking
pub fn start(&mut self, cx: &mut ModelContext<Self>) { pub fn start(&mut self, cx: &mut Context<Self>) {
self.blink(self.epoch, cx); self.blink(self.epoch, cx);
} }
pub fn stop(&mut self, cx: &mut ModelContext<Self>) { pub fn stop(&mut self, cx: &mut Context<Self>) {
self.epoch = 0; self.epoch = 0;
cx.notify(); cx.notify();
} }
@@ -40,7 +40,7 @@ impl BlinkCursor {
self.epoch self.epoch
} }
fn blink(&mut self, epoch: usize, cx: &mut ModelContext<Self>) { fn blink(&mut self, epoch: usize, cx: &mut Context<Self>) {
if self.paused || epoch != self.epoch { if self.paused || epoch != self.epoch {
return; return;
} }
@@ -65,13 +65,12 @@ impl BlinkCursor {
} }
/// Pause the blinking, and delay 500ms to resume the blinking. /// Pause the blinking, and delay 500ms to resume the blinking.
pub fn pause(&mut self, cx: &mut ModelContext<Self>) { pub fn pause(&mut self, cx: &mut Context<Self>) {
self.paused = true; self.paused = true;
cx.notify(); cx.notify();
// delay 500ms to start the blinking // delay 500ms to start the blinking
let epoch = self.next_epoch(); let epoch = self.next_epoch();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
Timer::after(PAUSE_DELAY).await; Timer::after(PAUSE_DELAY).await;

View File

@@ -1,21 +0,0 @@
use crate::{
button::{Button, ButtonVariants as _},
theme::{scale::ColorScaleStep, ActiveTheme as _},
Icon, IconName, Sizable as _,
};
use gpui::{Styled, WindowContext};
pub(crate) struct ClearButton {}
impl ClearButton {
#[allow(clippy::new_ret_no_self)]
pub fn new(cx: &mut WindowContext) -> Button {
Button::new("clean")
.icon(
Icon::new(IconName::CircleX)
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)),
)
.ghost()
.xsmall()
}
}

View File

@@ -1,32 +1,32 @@
use super::TextInput; use super::TextInput;
use crate::theme::{scale::ColorScaleStep, ActiveTheme as _}; use crate::theme::{scale::ColorScaleStep, ActiveTheme};
use gpui::{ use gpui::{
fill, point, px, relative, size, Bounds, Corners, Element, ElementId, ElementInputHandler, fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler,
GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, PaintQuad, Path, Pixels, Entity, GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, PaintQuad, Path,
Point, Style, TextRun, UnderlineStyle, View, WindowContext, WrappedLine, Pixels, Point, Style, TextRun, UnderlineStyle, Window, WrappedLine,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
const RIGHT_MARGIN: Pixels = px(5.); const RIGHT_MARGIN: Pixels = px(5.);
const CURSOR_INSET: Pixels = px(0.5); const BOTTOM_MARGIN: Pixels = px(20.);
pub(super) struct TextElement { pub(super) struct TextElement {
input: View<TextInput>, input: Entity<TextInput>,
} }
impl TextElement { impl TextElement {
pub(super) fn new(input: View<TextInput>) -> Self { pub(super) fn new(input: Entity<TextInput>) -> Self {
Self { input } Self { input }
} }
fn paint_mouse_listeners(&mut self, cx: &mut WindowContext) { fn paint_mouse_listeners(&mut self, window: &mut Window, _: &mut App) {
cx.on_mouse_event({ window.on_mouse_event({
let input = self.input.clone(); let input = self.input.clone();
move |event: &MouseMoveEvent, _, cx| { move |event: &MouseMoveEvent, _, window, cx| {
if event.pressed_button == Some(MouseButton::Left) { if event.pressed_button == Some(MouseButton::Left) {
input.update(cx, |input, cx| { input.update(cx, |input, cx| {
input.on_drag_move(event, cx); input.on_drag_move(event, window, cx);
}); });
} }
} }
@@ -38,7 +38,8 @@ impl TextElement {
lines: &[WrappedLine], lines: &[WrappedLine],
line_height: Pixels, line_height: Pixels,
bounds: &mut Bounds<Pixels>, bounds: &mut Bounds<Pixels>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> (Option<PaintQuad>, Point<Pixels>) { ) -> (Option<PaintQuad>, Point<Pixels>) {
let input = self.input.read(cx); let input = self.input.read(cx);
let selected_range = &input.selected_range; let selected_range = &input.selected_range;
@@ -101,9 +102,10 @@ impl TextElement {
} else { } else {
scroll_offset.x scroll_offset.x
}; };
scroll_offset.y = if scroll_offset.y + cursor_pos.y > (bounds.size.height) { scroll_offset.y =
if scroll_offset.y + cursor_pos.y > (bounds.size.height - BOTTOM_MARGIN) {
// cursor is out of bottom // cursor is out of bottom
bounds.size.height - cursor_pos.y bounds.size.height - BOTTOM_MARGIN - cursor_pos.y
} else if scroll_offset.y + cursor_pos.y < px(0.) { } else if scroll_offset.y + cursor_pos.y < px(0.) {
// cursor is out of top // cursor is out of top
scroll_offset.y - cursor_pos.y scroll_offset.y - cursor_pos.y
@@ -134,15 +136,17 @@ impl TextElement {
bounds.origin += scroll_offset; bounds.origin += scroll_offset;
if input.show_cursor(cx) { if input.show_cursor(window, cx) {
// cursor blink // cursor blink
let cursor_height =
window.text_style().font_size.to_pixels(window.rem_size()) + px(2.);
cursor = Some(fill( cursor = Some(fill(
Bounds::new( Bounds::new(
point( point(
bounds.left() + cursor_pos.x, bounds.left() + cursor_pos.x,
bounds.top() + cursor_pos.y + CURSOR_INSET, bounds.top() + cursor_pos.y + ((line_height - cursor_height) / 2.),
), ),
size(px(1.5), line_height), size(px(1.), cursor_height),
), ),
cx.theme().accent.step(cx, ColorScaleStep::NINE), cx.theme().accent.step(cx, ColorScaleStep::NINE),
)) ))
@@ -157,7 +161,8 @@ impl TextElement {
lines: &[WrappedLine], lines: &[WrappedLine],
line_height: Pixels, line_height: Pixels,
bounds: &mut Bounds<Pixels>, bounds: &mut Bounds<Pixels>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Option<Path<Pixels>> { ) -> Option<Path<Pixels>> {
let input = self.input.read(cx); let input = self.input.read(cx);
let selected_range = &input.selected_range; let selected_range = &input.selected_range;
@@ -327,18 +332,19 @@ impl Element for TextElement {
fn request_layout( fn request_layout(
&mut self, &mut self,
_id: Option<&GlobalElementId>, _id: Option<&GlobalElementId>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) { ) -> (LayoutId, Self::RequestLayoutState) {
let input = self.input.read(cx); let input = self.input.read(cx);
let mut style = Style::default(); let mut style = Style::default();
style.size.width = relative(1.).into(); style.size.width = relative(1.).into();
if self.input.read(cx).is_multi_line() { if self.input.read(cx).is_multi_line() {
style.size.height = relative(1.).into(); style.size.height = relative(1.).into();
style.min_size.height = (input.rows.max(1) as f32 * cx.line_height()).into(); style.min_size.height = (input.rows.max(1) as f32 * window.line_height()).into();
} else { } else {
style.size.height = cx.line_height().into(); style.size.height = window.line_height().into();
}; };
(cx.request_layout(style, []), ()) (window.request_layout(style, [], cx), ())
} }
fn prepaint( fn prepaint(
@@ -346,14 +352,15 @@ impl Element for TextElement {
_id: Option<&GlobalElementId>, _id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState, _request_layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self::PrepaintState { ) -> Self::PrepaintState {
let multi_line = self.input.read(cx).is_multi_line(); let multi_line = self.input.read(cx).is_multi_line();
let line_height = cx.line_height(); let line_height = window.line_height();
let input = self.input.read(cx); let input = self.input.read(cx);
let text = input.text.clone(); let text = input.text.clone();
let placeholder = input.placeholder.clone(); let placeholder = input.placeholder.clone();
let style = cx.text_style(); let style = window.text_style();
let mut bounds = bounds; let mut bounds = bounds;
let (display_text, text_color) = if text.is_empty() { let (display_text, text_color) = if text.is_empty() {
@@ -406,14 +413,15 @@ impl Element for TextElement {
vec![run] vec![run]
}; };
let font_size = style.font_size.to_pixels(cx.rem_size()); let font_size = style.font_size.to_pixels(window.rem_size());
let wrap_width = if multi_line { let wrap_width = if multi_line {
Some(bounds.size.width - RIGHT_MARGIN) Some(bounds.size.width - RIGHT_MARGIN)
} else { } else {
None None
}; };
let lines = cx let lines = window
.text_system() .text_system()
.shape_text(display_text, font_size, &runs, wrap_width) .shape_text(display_text, font_size, &runs, wrap_width)
.unwrap(); .unwrap();
@@ -450,9 +458,9 @@ impl Element for TextElement {
// Calculate the scroll offset to keep the cursor in view // Calculate the scroll offset to keep the cursor in view
let (cursor, cursor_scroll_offset) = let (cursor, cursor_scroll_offset) =
self.layout_cursor(&lines, line_height, &mut bounds, cx); self.layout_cursor(&lines, line_height, &mut bounds, window, cx);
let selection_path = self.layout_selections(&lines, line_height, &mut bounds, cx); let selection_path = self.layout_selections(&lines, line_height, &mut bounds, window, cx);
PrepaintState { PrepaintState {
bounds, bounds,
@@ -469,37 +477,39 @@ impl Element for TextElement {
input_bounds: Bounds<Pixels>, input_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState, _request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState, prepaint: &mut Self::PrepaintState,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
let focus_handle = self.input.read(cx).focus_handle.clone(); let focus_handle = self.input.read(cx).focus_handle.clone();
let focused = focus_handle.is_focused(cx); let focused = focus_handle.is_focused(window);
let bounds = prepaint.bounds; let bounds = prepaint.bounds;
let selected_range = self.input.read(cx).selected_range.clone(); let selected_range = self.input.read(cx).selected_range.clone();
cx.handle_input( window.handle_input(
&focus_handle, &focus_handle,
ElementInputHandler::new(bounds, self.input.clone()), ElementInputHandler::new(bounds, self.input.clone()),
cx,
); );
// Paint selections // Paint selections
if let Some(path) = prepaint.selection_path.take() { if let Some(path) = prepaint.selection_path.take() {
cx.paint_path(path, cx.theme().accent.step(cx, ColorScaleStep::FIVE)); window.paint_path(path, cx.theme().accent.step(cx, ColorScaleStep::FIVE));
} }
// Paint multi line text // Paint multi line text
let line_height = cx.line_height(); let line_height = window.line_height();
let origin = bounds.origin; let origin = bounds.origin;
let mut offset_y = px(0.); let mut offset_y = px(0.);
for line in prepaint.lines.iter() { for line in prepaint.lines.iter() {
let p = point(origin.x, origin.y + offset_y); let p = point(origin.x, origin.y + offset_y);
_ = line.paint(p, line_height, cx); _ = line.paint(p, line_height, window, cx);
offset_y += line.size(line_height).height; offset_y += line.size(line_height).height;
} }
if focused { if focused {
if let Some(cursor) = prepaint.cursor.take() { if let Some(cursor) = prepaint.cursor.take() {
cx.paint_quad(cursor); window.paint_quad(cursor);
} }
} }
@@ -530,6 +540,6 @@ impl Element for TextElement {
input.scroll_size = scroll_size; input.scroll_size = scroll_size;
}); });
self.paint_mouse_listeners(cx); self.paint_mouse_listeners(window, cx);
} }
} }

View File

@@ -3,28 +3,7 @@
//! Based on the `Input` example from the `gpui` crate. //! Based on the `Input` example from the `gpui` crate.
//! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs //! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs
use smallvec::SmallVec; use super::{blink_cursor::BlinkCursor, change::Change, element::TextElement};
use std::cell::Cell;
use std::ops::Range;
use std::rc::Rc;
use unicode_segmentation::*;
use gpui::prelude::FluentBuilder as _;
use gpui::{
actions, div, point, px, AnyElement, AppContext, Bounds, ClickEvent, ClipboardItem,
Context as _, Entity, EventEmitter, FocusHandle, FocusableView, Half, InteractiveElement as _,
IntoElement, KeyBinding, KeyDownEvent, Model, MouseButton, MouseDownEvent, MouseMoveEvent,
MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render, ScrollHandle, ScrollWheelEvent,
SharedString, Styled as _, UTF16Selection, ViewContext, ViewInputHandler, WindowContext,
WrappedLine,
};
// TODO:
// - Press Up,Down to move cursor up, down line if multi-line
// - Move cursor to skip line eof empty chars.
use super::{blink_cursor::BlinkCursor, change::Change, element::TextElement, ClearButton};
use crate::{ use crate::{
history::History, history::History,
indicator::Indicator, indicator::Indicator,
@@ -32,6 +11,16 @@ use crate::{
theme::{scale::ColorScaleStep, ActiveTheme}, theme::{scale::ColorScaleStep, ActiveTheme},
Sizable, Size, StyleSized, StyledExt, Sizable, Size, StyleSized, StyledExt,
}; };
use gpui::{
actions, div, point, prelude::FluentBuilder as _, px, AnyElement, App, AppContext, Bounds,
ClickEvent, ClipboardItem, Context, Entity, EntityInputHandler, EventEmitter, FocusHandle,
Focusable, Half, InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render,
ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, UTF16Selection, Window, WrappedLine,
};
use smallvec::SmallVec;
use std::{cell::Cell, ops::Range, rc::Rc};
use unicode_segmentation::*;
actions!( actions!(
input, input,
@@ -80,7 +69,7 @@ pub enum InputEvent {
const CONTEXT: &str = "Input"; const CONTEXT: &str = "Input";
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut App) {
cx.bind_keys([ cx.bind_keys([
KeyBinding::new("backspace", Backspace, Some(CONTEXT)), KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
KeyBinding::new("delete", Delete, Some(CONTEXT)), KeyBinding::new("delete", Delete, Some(CONTEXT)),
@@ -154,8 +143,8 @@ pub fn init(cx: &mut AppContext) {
]); ]);
} }
type TextInputPrefix<T> = Option<Box<dyn Fn(&mut ViewContext<T>) -> AnyElement + 'static>>; type TextInputPrefix<T> = Option<Box<dyn Fn(&mut Window, &mut Context<T>) -> AnyElement + 'static>>;
type TextInputSuffix<T> = Option<Box<dyn Fn(&mut ViewContext<T>) -> AnyElement + 'static>>; type TextInputSuffix<T> = Option<Box<dyn Fn(&mut Window, &mut Context<T>) -> AnyElement + 'static>>;
type Validate = Option<Box<dyn Fn(&str) -> bool + 'static>>; type Validate = Option<Box<dyn Fn(&str) -> bool + 'static>>;
pub struct TextInput { pub struct TextInput {
@@ -163,7 +152,7 @@ pub struct TextInput {
pub(super) text: SharedString, pub(super) text: SharedString,
multi_line: bool, multi_line: bool,
pub(super) history: History<Change>, pub(super) history: History<Change>,
pub(super) blink_cursor: Model<BlinkCursor>, pub(super) blink_cursor: Entity<BlinkCursor>,
pub(super) prefix: TextInputPrefix<Self>, pub(super) prefix: TextInputPrefix<Self>,
pub(super) suffix: TextInputSuffix<Self>, pub(super) suffix: TextInputSuffix<Self>,
pub(super) loading: bool, pub(super) loading: bool,
@@ -203,9 +192,9 @@ pub struct TextInput {
impl EventEmitter<InputEvent> for TextInput {} impl EventEmitter<InputEvent> for TextInput {}
impl TextInput { impl TextInput {
pub fn new(cx: &mut ViewContext<Self>) -> Self { pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
let blink_cursor = cx.new_model(|_| BlinkCursor::new()); let blink_cursor = cx.new(|_| BlinkCursor::new());
let history = History::new().group_interval(std::time::Duration::from_secs(1)); let history = History::new().group_interval(std::time::Duration::from_secs(1));
let input = Self { let input = Self {
focus_handle: focus_handle.clone(), focus_handle: focus_handle.clone(),
@@ -246,11 +235,12 @@ impl TextInput {
// Observe the blink cursor to repaint the view when it changes. // Observe the blink cursor to repaint the view when it changes.
cx.observe(&input.blink_cursor, |_, _, cx| cx.notify()) cx.observe(&input.blink_cursor, |_, _, cx| cx.notify())
.detach(); .detach();
// Blink the cursor when the window is active, pause when it's not. // Blink the cursor when the window is active, pause when it's not.
cx.observe_window_activation(|input, cx| { cx.observe_window_activation(window, |input, window, cx| {
if cx.is_window_active() { if window.is_window_active() {
let focus_handle = input.focus_handle.clone(); let focus_handle = input.focus_handle.clone();
if focus_handle.is_focused(cx) { if focus_handle.is_focused(window) {
input.blink_cursor.update(cx, |blink_cursor, cx| { input.blink_cursor.update(cx, |blink_cursor, cx| {
blink_cursor.start(cx); blink_cursor.start(cx);
}); });
@@ -259,8 +249,8 @@ impl TextInput {
}) })
.detach(); .detach();
cx.on_focus(&focus_handle, Self::on_focus).detach(); cx.on_focus(&focus_handle, window, Self::on_focus).detach();
cx.on_blur(&focus_handle, Self::on_blur).detach(); cx.on_blur(&focus_handle, window, Self::on_blur).detach();
input input
} }
@@ -272,7 +262,7 @@ impl TextInput {
} }
/// Called after moving the cursor. Updates preferred_x_offset if we know where the cursor now is. /// Called after moving the cursor. Updates preferred_x_offset if we know where the cursor now is.
fn update_preferred_x_offset(&mut self, _cx: &mut ViewContext<Self>) { fn update_preferred_x_offset(&mut self, _cx: &mut Context<Self>) {
if let (Some(lines), Some(bounds)) = (&self.last_layout, &self.last_bounds) { if let (Some(lines), Some(bounds)) = (&self.last_layout, &self.last_bounds) {
let offset = self.cursor_offset(); let offset = self.cursor_offset();
let line_height = self.last_line_height; let line_height = self.last_line_height;
@@ -314,7 +304,7 @@ impl TextInput {
/// Move the cursor vertically by one line (up or down) while preserving the column if possible. /// Move the cursor vertically by one line (up or down) while preserving the column if possible.
/// direction: -1 for up, +1 for down /// direction: -1 for up, +1 for down
fn move_vertical(&mut self, direction: i32, cx: &mut ViewContext<Self>) { fn move_vertical(&mut self, direction: i32, window: &mut Window, cx: &mut Context<Self>) {
if self.is_single_line() { if self.is_single_line() {
return; return;
} }
@@ -344,7 +334,7 @@ impl TextInput {
// Handle moving above the first line // Handle moving above the first line
if direction == -1 && new_line_index == 0 && new_sub_line < 0 { if direction == -1 && new_line_index == 0 && new_sub_line < 0 {
// Move cursor to the beginning of the text // Move cursor to the beginning of the text
self.move_to(0, cx); self.move_to(0, window, cx);
return; return;
} }
@@ -421,9 +411,14 @@ impl TextInput {
/// Set the text of the input field. /// Set the text of the input field.
/// ///
/// And the selection_range will be reset to 0..0. /// And the selection_range will be reset to 0..0.
pub fn set_text(&mut self, text: impl Into<SharedString>, cx: &mut ViewContext<Self>) { pub fn set_text(
&mut self,
text: impl Into<SharedString>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.history.ignore = true; self.history.ignore = true;
self.replace_text(text, cx); self.replace_text(text, window, cx);
self.history.ignore = false; self.history.ignore = false;
// Ensure cursor to start when set text // Ensure cursor to start when set text
self.selected_range = 0..0; self.selected_range = 0..0;
@@ -431,46 +426,55 @@ impl TextInput {
cx.notify(); cx.notify();
} }
fn replace_text(&mut self, text: impl Into<SharedString>, cx: &mut ViewContext<Self>) { fn replace_text(
&mut self,
text: impl Into<SharedString>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let text: SharedString = text.into(); let text: SharedString = text.into();
let range = 0..self.text.chars().map(|c| c.len_utf16()).sum(); let range = 0..self.text.chars().map(|c| c.len_utf16()).sum();
self.replace_text_in_range(Some(range), &text, cx); self.replace_text_in_range(Some(range), &text, window, cx);
} }
/// Set the disabled state of the input field. /// Set the disabled state of the input field.
pub fn set_disabled(&mut self, disabled: bool, cx: &mut ViewContext<Self>) { pub fn set_disabled(&mut self, disabled: bool, window: &mut Window, cx: &mut Context<Self>) {
self.disabled = disabled; self.disabled = disabled;
cx.notify(); cx.notify();
} }
/// Set the masked state of the input field. /// Set the masked state of the input field.
pub fn set_masked(&mut self, masked: bool, cx: &mut ViewContext<Self>) { pub fn set_masked(&mut self, masked: bool, window: &mut Window, cx: &mut Context<Self>) {
self.masked = masked; self.masked = masked;
cx.notify(); cx.notify();
} }
/// Set the prefix element of the input field. /// Set the prefix element of the input field.
pub fn set_prefix<F, E>(&mut self, builder: F, cx: &mut ViewContext<Self>) pub fn set_prefix<F, E>(&mut self, builder: F, _: &mut Window, cx: &mut Context<Self>)
where where
F: Fn(&ViewContext<Self>) -> E + 'static, F: Fn(&Window, &Context<Self>) -> E + 'static,
E: IntoElement, E: IntoElement,
{ {
self.prefix = Some(Box::new(move |cx| builder(cx).into_any_element())); self.prefix = Some(Box::new(move |window, cx| {
builder(window, cx).into_any_element()
}));
cx.notify(); cx.notify();
} }
/// Set the suffix element of the input field. /// Set the suffix element of the input field.
pub fn set_suffix<F, E>(&mut self, builder: F, cx: &mut ViewContext<Self>) pub fn set_suffix<F, E>(&mut self, builder: F, _: &mut Window, cx: &mut Context<Self>)
where where
F: Fn(&ViewContext<Self>) -> E + 'static, F: Fn(&Window, &Context<Self>) -> E + 'static,
E: IntoElement, E: IntoElement,
{ {
self.suffix = Some(Box::new(move |cx| builder(cx).into_any_element())); self.suffix = Some(Box::new(move |window, cx| {
builder(window, cx).into_any_element()
}));
cx.notify(); cx.notify();
} }
/// Set the Input size /// Set the Input size
pub fn set_size(&mut self, size: Size, cx: &mut ViewContext<Self>) { pub fn set_size(&mut self, size: Size, window: &mut Window, cx: &mut Context<Self>) {
self.size = size; self.size = size;
cx.notify(); cx.notify();
} }
@@ -490,20 +494,24 @@ impl TextInput {
/// Set the prefix element of the input field, for example a search Icon. /// Set the prefix element of the input field, for example a search Icon.
pub fn prefix<F, E>(mut self, builder: F) -> Self pub fn prefix<F, E>(mut self, builder: F) -> Self
where where
F: Fn(&mut ViewContext<Self>) -> E + 'static, F: Fn(&mut Window, &mut Context<Self>) -> E + 'static,
E: IntoElement, E: IntoElement,
{ {
self.prefix = Some(Box::new(move |cx| builder(cx).into_any_element())); self.prefix = Some(Box::new(move |window, cx| {
builder(window, cx).into_any_element()
}));
self self
} }
/// Set the suffix element of the input field, for example a clear button. /// Set the suffix element of the input field, for example a clear button.
pub fn suffix<F, E>(mut self, builder: F) -> Self pub fn suffix<F, E>(mut self, builder: F) -> Self
where where
F: Fn(&mut ViewContext<Self>) -> E + 'static, F: Fn(&mut Window, &mut Context<Self>) -> E + 'static,
E: IntoElement, E: IntoElement,
{ {
self.suffix = Some(Box::new(move |cx| builder(cx).into_any_element())); self.suffix = Some(Box::new(move |window, cx| {
builder(window, cx).into_any_element()
}));
self self
} }
@@ -542,7 +550,7 @@ impl TextInput {
} }
/// Set true to show indicator at the input right. /// Set true to show indicator at the input right.
pub fn set_loading(&mut self, loading: bool, cx: &mut ViewContext<Self>) { pub fn set_loading(&mut self, loading: bool, window: &mut Window, cx: &mut Context<Self>) {
self.loading = loading; self.loading = loading;
cx.notify(); cx.notify();
} }
@@ -557,130 +565,141 @@ impl TextInput {
} }
/// Focus the input field. /// Focus the input field.
pub fn focus(&self, cx: &mut ViewContext<Self>) { pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
self.focus_handle.focus(cx); self.focus_handle.focus(window);
} }
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) { fn left(&mut self, _: &Left, window: &mut Window, cx: &mut Context<Self>) {
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
if self.selected_range.is_empty() { if self.selected_range.is_empty() {
self.move_to(self.previous_boundary(self.cursor_offset()), cx); self.move_to(self.previous_boundary(self.cursor_offset()), window, cx);
} else { } else {
self.move_to(self.selected_range.start, cx) self.move_to(self.selected_range.start, window, cx)
} }
} }
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) { fn right(&mut self, _: &Right, window: &mut Window, cx: &mut Context<Self>) {
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
if self.selected_range.is_empty() { if self.selected_range.is_empty() {
self.move_to(self.next_boundary(self.selected_range.end), cx); self.move_to(self.next_boundary(self.selected_range.end), window, cx);
} else { } else {
self.move_to(self.selected_range.end, cx) self.move_to(self.selected_range.end, window, cx)
} }
} }
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) { fn up(&mut self, _: &Up, window: &mut Window, cx: &mut Context<Self>) {
if self.is_single_line() { if self.is_single_line() {
return; return;
} }
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
self.move_vertical(-1, cx); self.move_vertical(-1, window, cx);
} }
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) { fn down(&mut self, _: &Down, window: &mut Window, cx: &mut Context<Self>) {
if self.is_single_line() { if self.is_single_line() {
return; return;
} }
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
self.move_vertical(1, cx); self.move_vertical(1, window, cx);
} }
fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) { fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
self.select_to(self.previous_boundary(self.cursor_offset()), cx); self.select_to(self.previous_boundary(self.cursor_offset()), window, cx);
} }
fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) { fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
self.select_to(self.next_boundary(self.cursor_offset()), cx); self.select_to(self.next_boundary(self.cursor_offset()), window, cx);
} }
fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) { fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
if self.is_single_line() { if self.is_single_line() {
return; return;
} }
let offset = self.start_of_line(cx).saturating_sub(1); let offset = self.start_of_line(window, cx).saturating_sub(1);
self.select_to(offset, cx); self.select_to(offset, window, cx);
} }
fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) { fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
if self.is_single_line() { if self.is_single_line() {
return; return;
} }
let offset = (self.end_of_line(cx) + 1).min(self.text.len()); let offset = (self.end_of_line(window, cx) + 1).min(self.text.len());
self.select_to(offset, cx); self.select_to(self.next_boundary(offset), window, cx);
} }
fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) { fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
self.move_to(0, cx); self.move_to(0, window, cx);
self.select_to(self.text.len(), cx) self.select_to(self.text.len(), window, cx)
} }
fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) { fn home(&mut self, _: &Home, window: &mut Window, cx: &mut Context<Self>) {
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
let offset = self.start_of_line(cx); let offset = self.start_of_line(window, cx);
self.move_to(offset, cx); self.move_to(offset, window, cx);
} }
fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) { fn end(&mut self, _: &End, window: &mut Window, cx: &mut Context<Self>) {
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
let offset = self.end_of_line(cx); let offset = self.end_of_line(window, cx);
self.move_to(offset, cx); self.move_to(offset, window, cx);
} }
fn move_to_start(&mut self, _: &MoveToStart, cx: &mut ViewContext<Self>) { fn move_to_start(&mut self, _: &MoveToStart, window: &mut Window, cx: &mut Context<Self>) {
self.move_to(0, cx); self.move_to(0, window, cx);
} }
fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) { fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
let end = self.text.len(); let end = self.text.len();
self.move_to(end, cx); self.move_to(end, window, cx);
} }
fn select_to_start(&mut self, _: &SelectToStart, cx: &mut ViewContext<Self>) { fn select_to_start(&mut self, _: &SelectToStart, window: &mut Window, cx: &mut Context<Self>) {
self.select_to(0, cx); self.select_to(0, window, cx);
} }
fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) { fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
let end = self.text.len(); let end = self.text.len();
self.select_to(end, cx); self.select_to(end, window, cx);
} }
fn select_to_start_of_line(&mut self, _: &SelectToStartOfLine, cx: &mut ViewContext<Self>) { fn select_to_start_of_line(
let offset = self.start_of_line(cx); &mut self,
self.select_to(offset, cx); _: &SelectToStartOfLine,
window: &mut Window,
cx: &mut Context<Self>,
) {
let offset = self.start_of_line(window, cx);
self.select_to(self.previous_boundary(offset), window, cx);
} }
fn select_to_end_of_line(&mut self, _: &SelectToEndOfLine, cx: &mut ViewContext<Self>) { fn select_to_end_of_line(
let offset = self.end_of_line(cx); &mut self,
self.select_to(offset, cx); _: &SelectToEndOfLine,
window: &mut Window,
cx: &mut Context<Self>,
) {
let offset = self.end_of_line(window, cx);
self.select_to(self.next_boundary(offset), window, cx);
} }
/// Get start of line /// Get start of line
fn start_of_line(&mut self, cx: &mut ViewContext<Self>) -> usize { fn start_of_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> usize {
if self.is_single_line() { if self.is_single_line() {
return 0; return 0;
} }
let offset = self.previous_boundary(self.cursor_offset()); let offset = self.previous_boundary(self.cursor_offset());
let line = self
self.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, cx) .text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx)
.unwrap_or_default() .unwrap_or_default()
.rfind('\n') .rfind('\n')
.map(|i| i + 1) .map(|i| i + 1)
.unwrap_or(0) .unwrap_or(0);
line
} }
/// Get end of line /// Get end of line
fn end_of_line(&mut self, cx: &mut ViewContext<Self>) -> usize { fn end_of_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> usize {
if self.is_single_line() { if self.is_single_line() {
return self.text.len(); return self.text.len();
} }
@@ -688,7 +707,12 @@ impl TextInput {
let offset = self.next_boundary(self.cursor_offset()); let offset = self.next_boundary(self.cursor_offset());
// ignore if offset is "\n" // ignore if offset is "\n"
if self if self
.text_for_range(self.range_to_utf16(&(offset - 1..offset)), &mut None, cx) .text_for_range(
self.range_to_utf16(&(offset - 1..offset)),
&mut None,
window,
cx,
)
.unwrap_or_default() .unwrap_or_default()
.eq("\n") .eq("\n")
{ {
@@ -698,6 +722,7 @@ impl TextInput {
self.text_for_range( self.text_for_range(
self.range_to_utf16(&(offset..self.text.len())), self.range_to_utf16(&(offset..self.text.len())),
&mut None, &mut None,
window,
cx, cx,
) )
.unwrap_or_default() .unwrap_or_default()
@@ -706,88 +731,106 @@ impl TextInput {
.unwrap_or(self.text.len()) .unwrap_or(self.text.len())
} }
fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) { fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
if self.selected_range.is_empty() { if self.selected_range.is_empty() {
self.select_to(self.previous_boundary(self.cursor_offset()), cx) self.select_to(self.previous_boundary(self.cursor_offset()), window, cx)
} }
self.replace_text_in_range(None, "", cx); self.replace_text_in_range(None, "", window, cx);
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
} }
fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) { fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
if self.selected_range.is_empty() { if self.selected_range.is_empty() {
self.select_to(self.next_boundary(self.cursor_offset()), cx) self.select_to(self.next_boundary(self.cursor_offset()), window, cx)
} }
self.replace_text_in_range(None, "", cx); self.replace_text_in_range(None, "", window, cx);
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
} }
fn delete_to_beginning_of_line( fn delete_to_beginning_of_line(
&mut self, &mut self,
_: &DeleteToBeginningOfLine, _: &DeleteToBeginningOfLine,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
let offset = self.start_of_line(cx); let offset = self.start_of_line(window, cx);
self.replace_text_in_range( self.replace_text_in_range(
Some(self.range_to_utf16(&(offset..self.cursor_offset()))), Some(self.range_to_utf16(&(offset..self.cursor_offset()))),
"", "",
window,
cx, cx,
); );
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
} }
fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) { fn delete_to_end_of_line(
let offset = self.end_of_line(cx); &mut self,
_: &DeleteToEndOfLine,
window: &mut Window,
cx: &mut Context<Self>,
) {
let offset = self.end_of_line(window, cx);
self.replace_text_in_range( self.replace_text_in_range(
Some(self.range_to_utf16(&(self.cursor_offset()..offset))), Some(self.range_to_utf16(&(self.cursor_offset()..offset))),
"", "",
window,
cx, cx,
); );
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
} }
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) { fn enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context<Self>) {
if self.is_multi_line() { if self.is_multi_line() {
let is_eof = self.selected_range.end == self.text.len(); let is_eof = self.selected_range.end == self.text.len();
self.replace_text_in_range(None, "\n", cx); self.replace_text_in_range(None, "\n", window, cx);
// Move cursor to the start of the next line // Move cursor to the start of the next line
let mut new_offset = self.next_boundary(self.cursor_offset()) - 1; let mut new_offset = self.next_boundary(self.cursor_offset()) - 1;
if is_eof { if is_eof {
new_offset += 1; new_offset += 1;
} }
self.move_to(new_offset, cx); self.move_to(new_offset, window, cx);
} }
cx.emit(InputEvent::PressEnter); cx.emit(InputEvent::PressEnter);
} }
fn clean(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) { fn clean(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
self.replace_text("", cx); self.replace_text("", window, cx);
} }
fn on_mouse_down(&mut self, event: &MouseDownEvent, cx: &mut ViewContext<Self>) { fn on_mouse_down(
&mut self,
event: &MouseDownEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.is_selecting = true; self.is_selecting = true;
let offset = self.index_for_mouse_position(event.position, cx); let offset = self.index_for_mouse_position(event.position, window, cx);
// Double click to select word // Double click to select word
if event.button == MouseButton::Left && event.click_count == 2 { if event.button == MouseButton::Left && event.click_count == 2 {
self.select_word(offset, cx); self.select_word(offset, window, cx);
return; return;
} }
if event.modifiers.shift { if event.modifiers.shift {
self.select_to(offset, cx); self.select_to(offset, window, cx);
} else { } else {
self.move_to(offset, cx) self.move_to(offset, window, cx)
} }
} }
fn on_mouse_up(&mut self, _: &MouseUpEvent, _: &mut ViewContext<Self>) { fn on_mouse_up(&mut self, _: &MouseUpEvent, _window: &mut Window, _cx: &mut Context<Self>) {
self.is_selecting = false; self.is_selecting = false;
self.selected_word_range = None; self.selected_word_range = None;
} }
fn on_scroll_wheel(&mut self, event: &ScrollWheelEvent, _: &mut ViewContext<Self>) { fn on_scroll_wheel(
&mut self,
event: &ScrollWheelEvent,
_window: &mut Window,
_cx: &mut Context<Self>,
) {
let delta = event.delta.pixel_delta(self.last_line_height); let delta = event.delta.pixel_delta(self.last_line_height);
let safe_y_range = let safe_y_range =
(-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.); (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
@@ -801,11 +844,16 @@ impl TextInput {
self.scroll_handle.set_offset(offset); self.scroll_handle.set_offset(offset);
} }
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) { fn show_character_palette(
cx.show_character_palette(); &mut self,
_: &ShowCharacterPalette,
window: &mut Window,
_: &mut Context<Self>,
) {
window.show_character_palette();
} }
fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) { fn copy(&mut self, _: &Copy, _window: &mut Window, cx: &mut Context<Self>) {
if self.selected_range.is_empty() { if self.selected_range.is_empty() {
return; return;
} }
@@ -814,7 +862,7 @@ impl TextInput {
cx.write_to_clipboard(ClipboardItem::new_string(selected_text)); cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
} }
fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) { fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
if self.selected_range.is_empty() { if self.selected_range.is_empty() {
return; return;
} }
@@ -822,27 +870,33 @@ impl TextInput {
let range = self.range_from_utf16(&self.selected_range); let range = self.range_from_utf16(&self.selected_range);
let selected_text = self.text[range].to_string(); let selected_text = self.text[range].to_string();
cx.write_to_clipboard(ClipboardItem::new_string(selected_text)); cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
self.replace_text_in_range(None, "", cx); self.replace_text_in_range(None, "", window, cx);
} }
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) { fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
if let Some(clipboard) = cx.read_from_clipboard() { if let Some(clipboard) = cx.read_from_clipboard() {
let mut new_text = clipboard.text().unwrap_or_default(); let mut new_text = clipboard.text().unwrap_or_default();
if !self.multi_line { if !self.multi_line {
new_text = new_text.replace('\n', ""); new_text = new_text.replace('\n', "");
} }
self.replace_text_in_range(None, &new_text, cx); self.replace_text_in_range(None, &new_text, window, cx);
} }
} }
fn push_history(&mut self, range: &Range<usize>, new_text: &str, cx: &mut ViewContext<Self>) { fn push_history(
&mut self,
range: &Range<usize>,
new_text: &str,
window: &mut Window,
cx: &mut Context<Self>,
) {
if self.history.ignore { if self.history.ignore {
return; return;
} }
let old_text = self let old_text = self
.text_for_range(self.range_to_utf16(range), &mut None, cx) .text_for_range(self.range_to_utf16(range), &mut None, window, cx)
.unwrap_or("".to_string()); .unwrap_or("".to_string());
let new_range = range.start..range.start + new_text.len(); let new_range = range.start..range.start + new_text.len();
@@ -855,29 +909,34 @@ impl TextInput {
)); ));
} }
fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) { fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
self.history.ignore = true; self.history.ignore = true;
if let Some(changes) = self.history.undo() { if let Some(changes) = self.history.undo() {
for change in changes { for change in changes {
let range_utf16 = self.range_to_utf16(&change.new_range); let range_utf16 = self.range_to_utf16(&change.new_range);
self.replace_text_in_range(Some(range_utf16), &change.old_text, cx); self.replace_text_in_range(Some(range_utf16), &change.old_text, window, cx);
} }
} }
self.history.ignore = false; self.history.ignore = false;
} }
fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) { fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
self.history.ignore = true; self.history.ignore = true;
if let Some(changes) = self.history.redo() { if let Some(changes) = self.history.redo() {
for change in changes { for change in changes {
let range_utf16 = self.range_to_utf16(&change.old_range); let range_utf16 = self.range_to_utf16(&change.old_range);
self.replace_text_in_range(Some(range_utf16), &change.new_text, cx); self.replace_text_in_range(Some(range_utf16), &change.new_text, window, cx);
} }
} }
self.history.ignore = false; self.history.ignore = false;
} }
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) { /// Move the cursor to the given offset.
///
/// The offset is the UTF-8 offset.
///
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
fn move_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
self.selected_range = offset..offset; self.selected_range = offset..offset;
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
self.update_preferred_x_offset(cx); self.update_preferred_x_offset(cx);
@@ -892,7 +951,12 @@ impl TextInput {
} }
} }
fn index_for_mouse_position(&self, position: Point<Pixels>, _: &WindowContext) -> usize { fn index_for_mouse_position(
&self,
position: Point<Pixels>,
_window: &Window,
_cx: &App,
) -> usize {
// If the text is empty, always return 0 // If the text is empty, always return 0
if self.text.is_empty() { if self.text.is_empty() {
return 0; return 0;
@@ -987,7 +1051,12 @@ impl TextInput {
} }
} }
fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) { /// Select the text from the current cursor position to the given offset.
///
/// The offset is the UTF-8 offset.
///
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
fn select_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
if self.selection_reversed { if self.selection_reversed {
self.selected_range.start = offset self.selected_range.start = offset
} else { } else {
@@ -1015,7 +1084,7 @@ impl TextInput {
} }
/// Select the word at the given offset. /// Select the word at the given offset.
fn select_word(&mut self, offset: usize, cx: &mut ViewContext<Self>) { fn select_word(&mut self, offset: usize, window: &mut Window, cx: &mut Context<Self>) {
fn is_word(c: char) -> bool { fn is_word(c: char) -> bool {
c.is_alphanumeric() || matches!(c, '_') c.is_alphanumeric() || matches!(c, '_')
} }
@@ -1023,10 +1092,10 @@ impl TextInput {
let mut start = self.offset_to_utf16(offset); let mut start = self.offset_to_utf16(offset);
let mut end = start; let mut end = start;
let prev_text = self let prev_text = self
.text_for_range(0..start, &mut None, cx) .text_for_range(0..start, &mut None, window, cx)
.unwrap_or_default(); .unwrap_or_default();
let next_text = self let next_text = self
.text_for_range(end..self.text.len(), &mut None, cx) .text_for_range(end..self.text.len(), &mut None, window, cx)
.unwrap_or_default(); .unwrap_or_default();
let prev_chars = prev_text.chars().rev().peekable(); let prev_chars = prev_text.chars().rev().peekable();
@@ -1053,7 +1122,7 @@ impl TextInput {
cx.notify() cx.notify()
} }
fn unselect(&mut self, cx: &mut ViewContext<Self>) { fn unselect(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.selected_range = self.cursor_offset()..self.cursor_offset(); self.selected_range = self.cursor_offset()..self.cursor_offset();
cx.notify() cx.notify()
} }
@@ -1112,36 +1181,46 @@ impl TextInput {
} }
/// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible. /// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
pub(crate) fn show_cursor(&self, cx: &WindowContext) -> bool { pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
self.focus_handle.is_focused(cx) && self.blink_cursor.read(cx).visible() self.focus_handle.is_focused(window) && self.blink_cursor.read(cx).visible()
} }
fn on_focus(&mut self, cx: &mut ViewContext<Self>) { fn on_focus(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.blink_cursor.update(cx, |cursor, cx| { self.blink_cursor.update(cx, |cursor, cx| {
cursor.start(cx); cursor.start(cx);
}); });
cx.emit(InputEvent::Focus); cx.emit(InputEvent::Focus);
} }
fn on_blur(&mut self, cx: &mut ViewContext<Self>) { fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.unselect(cx); self.unselect(window, cx);
self.blink_cursor.update(cx, |cursor, cx| { self.blink_cursor.update(cx, |cursor, cx| {
cursor.stop(cx); cursor.stop(cx);
}); });
cx.emit(InputEvent::Blur); cx.emit(InputEvent::Blur);
} }
fn pause_blink_cursor(&mut self, cx: &mut ViewContext<Self>) { fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
self.blink_cursor.update(cx, |cursor, cx| { self.blink_cursor.update(cx, |cursor, cx| {
cursor.pause(cx); cursor.pause(cx);
}); });
} }
fn on_key_down_for_blink_cursor(&mut self, _: &KeyDownEvent, cx: &mut ViewContext<Self>) { fn on_key_down_for_blink_cursor(
&mut self,
_: &KeyDownEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.pause_blink_cursor(cx) self.pause_blink_cursor(cx)
} }
pub(super) fn on_drag_move(&mut self, event: &MouseMoveEvent, cx: &mut ViewContext<Self>) { pub(super) fn on_drag_move(
&mut self,
event: &MouseMoveEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
if self.text.is_empty() { if self.text.is_empty() {
return; return;
} }
@@ -1150,7 +1229,7 @@ impl TextInput {
return; return;
} }
if !self.focus_handle.is_focused(cx) { if !self.focus_handle.is_focused(window) {
return; return;
} }
@@ -1158,8 +1237,8 @@ impl TextInput {
return; return;
} }
let offset = self.index_for_mouse_position(event.position, cx); let offset = self.index_for_mouse_position(event.position, window, cx);
self.select_to(offset, cx); self.select_to(offset, window, cx);
} }
fn is_valid_input(&self, new_text: &str) -> bool { fn is_valid_input(&self, new_text: &str) -> bool {
@@ -1187,12 +1266,13 @@ impl Sizable for TextInput {
} }
} }
impl ViewInputHandler for TextInput { impl EntityInputHandler for TextInput {
fn text_for_range( fn text_for_range(
&mut self, &mut self,
range_utf16: Range<usize>, range_utf16: Range<usize>,
adjusted_range: &mut Option<Range<usize>>, adjusted_range: &mut Option<Range<usize>>,
_cx: &mut ViewContext<Self>, _window: &mut Window,
cx: &mut Context<Self>,
) -> Option<String> { ) -> Option<String> {
let range = self.range_from_utf16(&range_utf16); let range = self.range_from_utf16(&range_utf16);
adjusted_range.replace(self.range_to_utf16(&range)); adjusted_range.replace(self.range_to_utf16(&range));
@@ -1202,7 +1282,8 @@ impl ViewInputHandler for TextInput {
fn selected_text_range( fn selected_text_range(
&mut self, &mut self,
_ignore_disabled_input: bool, _ignore_disabled_input: bool,
_cx: &mut ViewContext<Self>, _window: &mut Window,
cx: &mut Context<Self>,
) -> Option<UTF16Selection> { ) -> Option<UTF16Selection> {
Some(UTF16Selection { Some(UTF16Selection {
range: self.range_to_utf16(&self.selected_range), range: self.range_to_utf16(&self.selected_range),
@@ -1210,13 +1291,17 @@ impl ViewInputHandler for TextInput {
}) })
} }
fn marked_text_range(&self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> { fn marked_text_range(
&self,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Range<usize>> {
self.marked_range self.marked_range
.as_ref() .as_ref()
.map(|range| self.range_to_utf16(range)) .map(|range| self.range_to_utf16(range))
} }
fn unmark_text(&mut self, _cx: &mut ViewContext<Self>) { fn unmark_text(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.marked_range = None; self.marked_range = None;
} }
@@ -1224,7 +1309,8 @@ impl ViewInputHandler for TextInput {
&mut self, &mut self,
range_utf16: Option<Range<usize>>, range_utf16: Option<Range<usize>>,
new_text: &str, new_text: &str,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
if self.disabled { if self.disabled {
return; return;
@@ -1242,7 +1328,7 @@ impl ViewInputHandler for TextInput {
return; return;
} }
self.push_history(&range, new_text, cx); self.push_history(&range, new_text, window, cx);
self.text = pending_text; self.text = pending_text;
self.selected_range = range.start + new_text.len()..range.start + new_text.len(); self.selected_range = range.start + new_text.len()..range.start + new_text.len();
self.marked_range.take(); self.marked_range.take();
@@ -1256,7 +1342,8 @@ impl ViewInputHandler for TextInput {
range_utf16: Option<Range<usize>>, range_utf16: Option<Range<usize>>,
new_text: &str, new_text: &str,
new_selected_range_utf16: Option<Range<usize>>, new_selected_range_utf16: Option<Range<usize>>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
if self.disabled { if self.disabled {
return; return;
@@ -1273,7 +1360,7 @@ impl ViewInputHandler for TextInput {
return; return;
} }
self.push_history(&range, new_text, cx); self.push_history(&range, new_text, window, cx);
self.text = pending_text; self.text = pending_text;
self.marked_range = Some(range.start..range.start + new_text.len()); self.marked_range = Some(range.start..range.start + new_text.len());
self.selected_range = new_selected_range_utf16 self.selected_range = new_selected_range_utf16
@@ -1291,7 +1378,8 @@ impl ViewInputHandler for TextInput {
&mut self, &mut self,
range_utf16: Range<usize>, range_utf16: Range<usize>,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
_: &mut ViewContext<Self>, _window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<Bounds<Pixels>> { ) -> Option<Bounds<Pixels>> {
let line_height = self.last_line_height; let line_height = self.last_line_height;
let lines = self.last_layout.as_ref()?; let lines = self.last_layout.as_ref()?;
@@ -1325,19 +1413,19 @@ impl ViewInputHandler for TextInput {
} }
} }
impl FocusableView for TextInput { impl Focusable for TextInput {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
impl Render for TextInput { impl Render for TextInput {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
const LINE_HEIGHT: Rems = Rems(1.25); const LINE_HEIGHT: Rems = Rems(1.25);
let focused = self.focus_handle.is_focused(cx); let focused = self.focus_handle.is_focused(window);
let prefix = self.prefix.as_ref().map(|build| build(cx)); let prefix = self.prefix.as_ref().map(|build| build(window, cx));
let suffix = self.suffix.as_ref().map(|build| build(cx)); let suffix = self.suffix.as_ref().map(|build| build(window, cx));
div() div()
.flex() .flex()
@@ -1392,7 +1480,7 @@ impl Render for TextInput {
this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)) this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
.rounded(px(cx.theme().radius)) .rounded(px(cx.theme().radius))
.when(cx.theme().shadow, |this| this.shadow_sm()) .when(cx.theme().shadow, |this| this.shadow_sm())
.when(focused, |this| this.outline(cx)) .when(focused, |this| this.outline(window, cx))
.when(prefix.is_none(), |this| this.input_pl(self.size)) .when(prefix.is_none(), |this| this.input_pl(self.size))
.when(suffix.is_none(), |this| this.input_pr(self.size)) .when(suffix.is_none(), |this| this.input_pr(self.size))
}) })
@@ -1404,18 +1492,14 @@ impl Render for TextInput {
.id("TextElement") .id("TextElement")
.flex_grow() .flex_grow()
.overflow_x_hidden() .overflow_x_hidden()
.child(TextElement::new(cx.view().clone())), .child(TextElement::new(cx.model().clone())),
) )
.when(self.loading, |this| { .when(self.loading, |this| {
this.child(Indicator::new().color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))) this.child(Indicator::new().color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)))
}) })
.when(
self.cleanable && !self.loading && !self.text.is_empty() && self.is_single_line(),
|this| this.child(ClearButton::new(cx).on_click(cx.listener(Self::clean))),
)
.children(suffix) .children(suffix)
.when(self.is_multi_line(), |this| { .when(self.is_multi_line(), |this| {
let entity_id = cx.view().entity_id(); let entity_id = cx.model().entity_id();
if self.last_layout.is_some() { if self.last_layout.is_some() {
let scroll_size = self.scroll_size; let scroll_size = self.scroll_size;

View File

@@ -1,9 +1,7 @@
mod blink_cursor; mod blink_cursor;
mod change; mod change;
mod clear_button;
mod element; mod element;
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
mod input; mod input;
pub(crate) use clear_button::*;
pub use input::*; pub use input::*;

View File

@@ -47,9 +47,8 @@ mod window_border;
/// ///
/// This must be called before using any of the UI components. /// This must be called before using any of the UI components.
/// You can initialize the UI module at your application's entry point. /// You can initialize the UI module at your application's entry point.
pub fn init(cx: &mut gpui::AppContext) { pub fn init(cx: &mut gpui::App) {
theme::init(cx); theme::init(cx);
dock_area::init(cx);
dropdown::init(cx); dropdown::init(cx);
input::init(cx); input::init(cx);
list::init(cx); list::init(cx);

View File

@@ -5,18 +5,19 @@ use crate::{
v_flex, Icon, IconName, Size, v_flex, Icon, IconName, Size,
}; };
use gpui::{ use gpui::{
actions, div, prelude::FluentBuilder, px, uniform_list, AnyElement, AppContext, Entity, actions, div, prelude::FluentBuilder, px, uniform_list, AnyElement, App, AppContext, Context,
FocusHandle, FocusableView, InteractiveElement, IntoElement, KeyBinding, Length, Entity, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Length,
ListSizingBehavior, MouseButton, ParentElement, Render, ScrollStrategy, SharedString, Styled, ListSizingBehavior, MouseButton, ParentElement, Render, ScrollStrategy, SharedString, Styled,
Task, UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext, Task, UniformListScrollHandle, Window,
}; };
use smol::Timer; use smol::Timer;
use std::{cell::Cell, rc::Rc, time::Duration}; use std::{cell::Cell, rc::Rc, time::Duration};
actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]); actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut App) {
let context: Option<&str> = Some("List"); let context: Option<&str> = Some("List");
cx.bind_keys([ cx.bind_keys([
KeyBinding::new("escape", Cancel, context), KeyBinding::new("escape", Cancel, context),
KeyBinding::new("enter", Confirm, context), KeyBinding::new("enter", Confirm, context),
@@ -32,20 +33,30 @@ pub trait ListDelegate: Sized + 'static {
/// When Query Input change, this method will be called. /// When Query Input change, this method will be called.
/// You can perform search here. /// You can perform search here.
fn perform_search(&mut self, query: &str, cx: &mut ViewContext<List<Self>>) -> Task<()> { fn perform_search(
&mut self,
query: &str,
window: &mut Window,
cx: &mut Context<List<Self>>,
) -> Task<()> {
Task::ready(()) Task::ready(())
} }
/// Return the number of items in the list. /// Return the number of items in the list.
fn items_count(&self, cx: &AppContext) -> usize; fn items_count(&self, cx: &App) -> usize;
/// Render the item at the given index. /// Render the item at the given index.
/// ///
/// Return None will skip the item. /// Return None will skip the item.
fn render_item(&self, ix: usize, cx: &mut ViewContext<List<Self>>) -> Option<Self::Item>; fn render_item(
&self,
ix: usize,
window: &mut Window,
cx: &mut Context<List<Self>>,
) -> Option<Self::Item>;
/// Return a Element to show when list is empty. /// Return a Element to show when list is empty.
fn render_empty(&self, cx: &mut ViewContext<List<Self>>) -> impl IntoElement { fn render_empty(&self, window: &mut Window, cx: &mut Context<List<Self>>) -> impl IntoElement {
div() div()
} }
@@ -56,30 +67,39 @@ pub trait ListDelegate: Sized + 'static {
/// For example: The last search results, or the last selected item. /// For example: The last search results, or the last selected item.
/// ///
/// Default is None, that means no initial state. /// Default is None, that means no initial state.
fn render_initial(&self, cx: &mut ViewContext<List<Self>>) -> Option<AnyElement> { fn render_initial(
&self,
window: &mut Window,
cx: &mut Context<List<Self>>,
) -> Option<AnyElement> {
None None
} }
/// Return the confirmed index of the selected item. /// Return the confirmed index of the selected item.
fn confirmed_index(&self, cx: &AppContext) -> Option<usize> { fn confirmed_index(&self, cx: &App) -> Option<usize> {
None None
} }
/// Set the selected index, just store the ix, don't confirm. /// Set the selected index, just store the ix, don't confirm.
fn set_selected_index(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>); fn set_selected_index(
&mut self,
ix: Option<usize>,
window: &mut Window,
cx: &mut Context<List<Self>>,
);
/// Set the confirm and give the selected index, this is means user have clicked the item or pressed Enter. /// Set the confirm and give the selected index, this is means user have clicked the item or pressed Enter.
fn confirm(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>) {} fn confirm(&mut self, ix: Option<usize>, window: &mut Window, cx: &mut Context<List<Self>>) {}
/// Cancel the selection, e.g.: Pressed ESC. /// Cancel the selection, e.g.: Pressed ESC.
fn cancel(&mut self, cx: &mut ViewContext<List<Self>>) {} fn cancel(&mut self, window: &mut Window, cx: &mut Context<List<Self>>) {}
} }
pub struct List<D: ListDelegate> { pub struct List<D: ListDelegate> {
focus_handle: FocusHandle, focus_handle: FocusHandle,
delegate: D, delegate: D,
max_height: Option<Length>, max_height: Option<Length>,
query_input: Option<View<TextInput>>, query_input: Option<Entity<TextInput>>,
last_query: Option<String>, last_query: Option<String>,
loading: bool, loading: bool,
@@ -97,11 +117,11 @@ impl<D> List<D>
where where
D: ListDelegate, D: ListDelegate,
{ {
pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self { pub fn new(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self {
let query_input = cx.new_view(|cx| { let query_input = cx.new(|cx| {
TextInput::new(cx) TextInput::new(window, cx)
.appearance(false) .appearance(false)
.prefix(|cx| { .prefix(|_window, cx| {
Icon::new(IconName::Search) Icon::new(IconName::Search)
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)) .text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
}) })
@@ -109,7 +129,7 @@ where
.cleanable() .cleanable()
}); });
cx.subscribe(&query_input, Self::on_query_input_event) cx.subscribe_in(&query_input, window, Self::on_query_input_event)
.detach(); .detach();
Self { Self {
@@ -130,10 +150,10 @@ where
} }
/// Set the size /// Set the size
pub fn set_size(&mut self, size: Size, cx: &mut ViewContext<Self>) { pub fn set_size(&mut self, size: Size, window: &mut Window, cx: &mut Context<Self>) {
if let Some(input) = &self.query_input { if let Some(input) = &self.query_input {
input.update(cx, |input, cx| { input.update(cx, |input, cx| {
input.set_size(size, cx); input.set_size(size, window, cx);
}) })
} }
self.size = size; self.size = size;
@@ -154,8 +174,13 @@ where
self self
} }
pub fn set_query_input(&mut self, query_input: View<TextInput>, cx: &mut ViewContext<Self>) { pub fn set_query_input(
cx.subscribe(&query_input, Self::on_query_input_event) &mut self,
query_input: Entity<TextInput>,
window: &mut Window,
cx: &mut Context<Self>,
) {
cx.subscribe_in(&query_input, window, Self::on_query_input_event)
.detach(); .detach();
self.query_input = Some(query_input); self.query_input = Some(query_input);
} }
@@ -168,13 +193,18 @@ where
&mut self.delegate &mut self.delegate
} }
pub fn focus(&mut self, cx: &mut WindowContext) { pub fn focus(&mut self, window: &mut Window, cx: &mut App) {
self.focus_handle(cx).focus(cx); self.focus_handle(cx).focus(window);
} }
pub fn set_selected_index(&mut self, ix: Option<usize>, cx: &mut ViewContext<Self>) { pub fn set_selected_index(
&mut self,
ix: Option<usize>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.selected_index = ix; self.selected_index = ix;
self.delegate.set_selected_index(ix, cx); self.delegate.set_selected_index(ix, window, cx);
} }
pub fn selected_index(&self) -> Option<usize> { pub fn selected_index(&self) -> Option<usize> {
@@ -182,31 +212,35 @@ where
} }
/// Set the query_input text /// Set the query_input text
pub fn set_query(&mut self, query: &str, cx: &mut ViewContext<Self>) { pub fn set_query(&mut self, query: &str, window: &mut Window, cx: &mut Context<Self>) {
if let Some(query_input) = &self.query_input { if let Some(query_input) = &self.query_input {
let query = query.to_owned(); let query = query.to_owned();
query_input.update(cx, |input, cx| input.set_text(query, cx)) query_input.update(cx, |input, cx| input.set_text(query, window, cx))
} }
} }
/// Get the query_input text /// Get the query_input text
pub fn query(&self, cx: &mut ViewContext<Self>) -> Option<SharedString> { pub fn query(&self, _window: &mut Window, cx: &mut Context<Self>) -> Option<SharedString> {
self.query_input.as_ref().map(|input| input.read(cx).text()) self.query_input.as_ref().map(|input| input.read(cx).text())
} }
fn render_scrollbar(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> { fn render_scrollbar(
&self,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Option<impl IntoElement> {
if !self.enable_scrollbar { if !self.enable_scrollbar {
return None; return None;
} }
Some(Scrollbar::uniform_scroll( Some(Scrollbar::uniform_scroll(
cx.view().entity_id(), cx.model().entity_id(),
self.scrollbar_state.clone(), self.scrollbar_state.clone(),
self.vertical_scroll_handle.clone(), self.vertical_scroll_handle.clone(),
)) ))
} }
fn scroll_to_selected_item(&mut self, _cx: &mut ViewContext<Self>) { fn scroll_to_selected_item(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
if let Some(ix) = self.selected_index { if let Some(ix) = self.selected_index {
self.vertical_scroll_handle self.vertical_scroll_handle
.scroll_to_item(ix, ScrollStrategy::Top); .scroll_to_item(ix, ScrollStrategy::Top);
@@ -215,9 +249,10 @@ where
fn on_query_input_event( fn on_query_input_event(
&mut self, &mut self,
_: View<TextInput>, _: &Entity<TextInput>,
event: &InputEvent, event: &InputEvent,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
match event { match event {
InputEvent::Change(text) => { InputEvent::Change(text) => {
@@ -226,13 +261,13 @@ where
return; return;
} }
self.set_loading(true, cx); self.set_loading(true, window, cx);
let search = self.delegate.perform_search(&text, cx); let search = self.delegate.perform_search(&text, window, cx);
self._search_task = cx.spawn(|this, mut cx| async move { self._search_task = cx.spawn_in(window, |this, mut window| async move {
search.await; search.await;
let _ = this.update(&mut cx, |this, _| { _ = this.update_in(&mut window, |this, _, _| {
this.vertical_scroll_handle this.vertical_scroll_handle
.scroll_to_item(0, ScrollStrategy::Top); .scroll_to_item(0, ScrollStrategy::Top);
this.last_query = Some(text); this.last_query = Some(text);
@@ -240,40 +275,45 @@ where
// Always wait 100ms to avoid flicker // Always wait 100ms to avoid flicker
Timer::after(Duration::from_millis(100)).await; Timer::after(Duration::from_millis(100)).await;
let _ = this.update(&mut cx, |this, cx| { _ = this.update_in(&mut window, |this, window, cx| {
this.set_loading(false, cx); this.set_loading(false, window, cx);
}); });
}); });
} }
InputEvent::PressEnter => self.on_action_confirm(&Confirm, cx), InputEvent::PressEnter => self.on_action_confirm(&Confirm, window, cx),
_ => {} _ => {}
} }
} }
fn set_loading(&mut self, loading: bool, cx: &mut ViewContext<Self>) { fn set_loading(&mut self, loading: bool, window: &mut Window, cx: &mut Context<Self>) {
self.loading = loading; self.loading = loading;
if let Some(input) = &self.query_input { if let Some(input) = &self.query_input {
input.update(cx, |input, cx| input.set_loading(loading, cx)) input.update(cx, |input, cx| input.set_loading(loading, window, cx))
} }
cx.notify(); cx.notify();
} }
fn on_action_cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) { fn on_action_cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
self.set_selected_index(None, cx); self.set_selected_index(None, window, cx);
self.delegate.cancel(cx); self.delegate.cancel(window, cx);
cx.notify(); cx.notify();
} }
fn on_action_confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) { fn on_action_confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
if self.delegate.items_count(cx) == 0 { if self.delegate.items_count(cx) == 0 {
return; return;
} }
self.delegate.confirm(self.selected_index, cx); self.delegate.confirm(self.selected_index, window, cx);
cx.notify(); cx.notify();
} }
fn on_action_select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) { fn on_action_select_prev(
&mut self,
_: &SelectPrev,
window: &mut Window,
cx: &mut Context<Self>,
) {
if self.delegate.items_count(cx) == 0 { if self.delegate.items_count(cx) == 0 {
return; return;
} }
@@ -285,12 +325,18 @@ where
self.selected_index = Some(self.delegate.items_count(cx) - 1); self.selected_index = Some(self.delegate.items_count(cx) - 1);
} }
self.delegate.set_selected_index(self.selected_index, cx); self.delegate
self.scroll_to_selected_item(cx); .set_selected_index(self.selected_index, window, cx);
self.scroll_to_selected_item(window, cx);
cx.notify(); cx.notify();
} }
fn on_action_select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) { fn on_action_select_next(
&mut self,
_: &SelectNext,
window: &mut Window,
cx: &mut Context<Self>,
) {
if self.delegate.items_count(cx) == 0 { if self.delegate.items_count(cx) == 0 {
return; return;
} }
@@ -305,17 +351,23 @@ where
self.selected_index = Some(0); self.selected_index = Some(0);
} }
self.delegate.set_selected_index(self.selected_index, cx); self.delegate
self.scroll_to_selected_item(cx); .set_selected_index(self.selected_index, window, cx);
self.scroll_to_selected_item(window, cx);
cx.notify(); cx.notify();
} }
fn render_list_item(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_list_item(
&mut self,
ix: usize,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
div() div()
.id("list-item") .id("list-item")
.w_full() .w_full()
.relative() .relative()
.children(self.delegate.render_item(ix, cx)) .children(self.delegate.render_item(ix, window, cx))
.when_some(self.selected_index, |this, selected_index| { .when_some(self.selected_index, |this, selected_index| {
this.when(ix == selected_index, |this| { this.when(ix == selected_index, |this| {
this.child( this.child(
@@ -345,15 +397,15 @@ where
}) })
.on_mouse_down( .on_mouse_down(
MouseButton::Left, MouseButton::Left,
cx.listener(move |this, _, cx| { cx.listener(move |this, _, window, cx| {
this.right_clicked_index = None; this.right_clicked_index = None;
this.selected_index = Some(ix); this.selected_index = Some(ix);
this.on_action_confirm(&Confirm, cx); this.on_action_confirm(&Confirm, window, cx);
}), }),
) )
.on_mouse_down( .on_mouse_down(
MouseButton::Right, MouseButton::Right,
cx.listener(move |this, _, cx| { cx.listener(move |this, _, _window, cx| {
this.right_clicked_index = Some(ix); this.right_clicked_index = Some(ix);
cx.notify(); cx.notify();
}), }),
@@ -361,11 +413,11 @@ where
} }
} }
impl<D> FocusableView for List<D> impl<D> Focusable for List<D>
where where
D: ListDelegate, D: ListDelegate,
{ {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
if let Some(query_input) = &self.query_input { if let Some(query_input) = &self.query_input {
query_input.focus_handle(cx) query_input.focus_handle(cx)
} else { } else {
@@ -378,8 +430,8 @@ impl<D> Render for List<D>
where where
D: ListDelegate, D: ListDelegate,
{ {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.view().clone(); let view = cx.model().clone();
let vertical_scroll_handle = self.vertical_scroll_handle.clone(); let vertical_scroll_handle = self.vertical_scroll_handle.clone();
let items_count = self.delegate.items_count(cx); let items_count = self.delegate.items_count(cx);
let sizing_behavior = if self.max_height.is_some() { let sizing_behavior = if self.max_height.is_some() {
@@ -390,7 +442,7 @@ where
let initial_view = if let Some(input) = &self.query_input { let initial_view = if let Some(input) = &self.query_input {
if input.read(cx).text().is_empty() { if input.read(cx).text().is_empty() {
self.delegate().render_initial(cx) self.delegate().render_initial(window, cx)
} else { } else {
None None
} }
@@ -432,14 +484,14 @@ where
.when_some(self.max_height, |this, h| this.max_h(h)) .when_some(self.max_height, |this, h| this.max_h(h))
.overflow_hidden() .overflow_hidden()
.when(items_count == 0, |this| { .when(items_count == 0, |this| {
this.child(self.delegate().render_empty(cx)) this.child(self.delegate().render_empty(window, cx))
}) })
.when(items_count > 0, |this| { .when(items_count > 0, |this| {
this.child( this.child(
uniform_list(view, "uniform-list", items_count, { uniform_list(view, "uniform-list", items_count, {
move |list, visible_range, cx| { move |list, visible_range, window, cx| {
visible_range visible_range
.map(|ix| list.render_list_item(ix, cx)) .map(|ix| list.render_list_item(ix, window, cx))
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
}) })
@@ -449,13 +501,13 @@ where
.into_any_element(), .into_any_element(),
) )
}) })
.children(self.render_scrollbar(cx)), .children(self.render_scrollbar(window, cx)),
) )
} }
}) })
// Click out to cancel right clicked row // Click out to cancel right clicked row
.when(self.right_clicked_index.is_some(), |this| { .when(self.right_clicked_index.is_some(), |this| {
this.on_mouse_down_out(cx.listener(|this, _, cx| { this.on_mouse_down_out(cx.listener(|this, _, _window, cx| {
this.right_clicked_index = None; this.right_clicked_index = None;
cx.notify(); cx.notify();
})) }))

View File

@@ -4,15 +4,15 @@ use crate::{
Disableable, Icon, IconName, Selectable, Sizable as _, Disableable, Icon, IconName, Selectable, Sizable as _,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, AnyElement, ClickEvent, Div, ElementId, InteractiveElement, div, prelude::FluentBuilder as _, AnyElement, App, ClickEvent, Div, ElementId,
IntoElement, MouseButton, MouseMoveEvent, ParentElement, RenderOnce, Stateful, InteractiveElement, IntoElement, MouseButton, MouseMoveEvent, ParentElement, RenderOnce,
StatefulInteractiveElement as _, Styled, WindowContext, Stateful, StatefulInteractiveElement as _, Styled, Window,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>; type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>;
type OnMouseEnter = Option<Box<dyn Fn(&MouseMoveEvent, &mut WindowContext) + 'static>>; type OnMouseEnter = Option<Box<dyn Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static>>;
type Suffix = Option<Box<dyn Fn(&mut WindowContext) -> AnyElement + 'static>>; type Suffix = Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>;
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct ListItem { pub struct ListItem {
@@ -71,21 +71,26 @@ impl ListItem {
/// Set the suffix element of the input field, for example a clear button. /// Set the suffix element of the input field, for example a clear button.
pub fn suffix<F, E>(mut self, builder: F) -> Self pub fn suffix<F, E>(mut self, builder: F) -> Self
where where
F: Fn(&mut WindowContext) -> E + 'static, F: Fn(&mut Window, &mut App) -> E + 'static,
E: IntoElement, E: IntoElement,
{ {
self.suffix = Some(Box::new(move |cx| builder(cx).into_any_element())); self.suffix = Some(Box::new(move |window, cx| {
builder(window, cx).into_any_element()
}));
self self
} }
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self { pub fn on_click(
mut self,
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.on_click = Some(Box::new(handler)); self.on_click = Some(Box::new(handler));
self self
} }
pub fn on_mouse_enter( pub fn on_mouse_enter(
mut self, mut self,
handler: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static, handler: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
) -> Self { ) -> Self {
self.on_mouse_enter = Some(Box::new(handler)); self.on_mouse_enter = Some(Box::new(handler));
self self
@@ -123,7 +128,7 @@ impl ParentElement for ListItem {
} }
impl RenderOnce for ListItem { impl RenderOnce for ListItem {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let is_active = self.selected || self.confirmed; let is_active = self.selected || self.confirmed;
self.base self.base
@@ -134,7 +139,7 @@ impl RenderOnce for ListItem {
.when_some(self.on_click, |this, on_click| { .when_some(self.on_click, |this, on_click| {
if !self.disabled { if !self.disabled {
this.cursor_pointer() this.cursor_pointer()
.on_mouse_down(MouseButton::Left, move |_, cx| { .on_mouse_down(MouseButton::Left, move |_, _window, cx| {
cx.stop_propagation(); cx.stop_propagation();
}) })
.on_click(on_click) .on_click(on_click)
@@ -151,7 +156,7 @@ impl RenderOnce for ListItem {
// Mouse enter // Mouse enter
.when_some(self.on_mouse_enter, |this, on_mouse_enter| { .when_some(self.on_mouse_enter, |this, on_mouse_enter| {
if !self.disabled { if !self.disabled {
this.on_mouse_move(move |ev, cx| (on_mouse_enter)(ev, cx)) this.on_mouse_move(move |ev, window, cx| (on_mouse_enter)(ev, window, cx))
} else { } else {
this this
} }
@@ -176,6 +181,6 @@ impl RenderOnce for ListItem {
)) ))
}), }),
) )
.when_some(self.suffix, |this, suffix| this.child(suffix(cx))) .when_some(self.suffix, |this, suffix| this.child(suffix(window, cx)))
} }
} }

View File

@@ -6,9 +6,9 @@ use crate::{
}; };
use gpui::{ use gpui::{
actions, anchored, div, point, prelude::FluentBuilder, px, relative, Animation, actions, anchored, div, point, prelude::FluentBuilder, px, relative, Animation,
AnimationExt as _, AnyElement, AppContext, Bounds, ClickEvent, Div, FocusHandle, AnimationExt as _, AnyElement, App, Bounds, ClickEvent, Div, FocusHandle, InteractiveElement,
InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString,
RenderOnce, SharedString, Styled, WindowContext, Styled, Window,
}; };
use std::{rc::Rc, time::Duration}; use std::{rc::Rc, time::Duration};
@@ -16,12 +16,10 @@ actions!(modal, [Escape]);
const CONTEXT: &str = "Modal"; const CONTEXT: &str = "Modal";
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut App) {
cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))]) cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))])
} }
type OnClose = Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Modal { pub struct Modal {
base: Div, base: Div,
@@ -31,7 +29,7 @@ pub struct Modal {
width: Pixels, width: Pixels,
max_width: Option<Pixels>, max_width: Option<Pixels>,
margin_top: Option<Pixels>, margin_top: Option<Pixels>,
on_close: OnClose, on_close: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
show_close: bool, show_close: bool,
keyboard: bool, keyboard: bool,
/// This will be change when open the modal, the focus handle is create when open the modal. /// This will be change when open the modal, the focus handle is create when open the modal.
@@ -41,7 +39,7 @@ pub struct Modal {
} }
impl Modal { impl Modal {
pub fn new(cx: &mut WindowContext) -> Self { pub fn new(window: &mut Window, cx: &mut App) -> Self {
let base = v_flex() let base = v_flex()
.bg(cx.theme().background) .bg(cx.theme().background)
.border_1() .border_1()
@@ -62,7 +60,7 @@ impl Modal {
overlay: true, overlay: true,
keyboard: true, keyboard: true,
layer_ix: 0, layer_ix: 0,
on_close: Rc::new(|_, _| {}), on_close: Rc::new(|_, _, _| {}),
show_close: true, show_close: true,
} }
} }
@@ -82,7 +80,7 @@ impl Modal {
/// Sets the callback for when the modal is closed. /// Sets the callback for when the modal is closed.
pub fn on_close( pub fn on_close(
mut self, mut self,
on_close: impl Fn(&ClickEvent, &mut WindowContext) + 'static, on_close: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self { ) -> Self {
self.on_close = Rc::new(on_close); self.on_close = Rc::new(on_close);
self self
@@ -142,11 +140,11 @@ impl Styled for Modal {
} }
impl RenderOnce for Modal { impl RenderOnce for Modal {
fn render(self, cx: &mut WindowContext) -> impl gpui::IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl gpui::IntoElement {
let layer_ix = self.layer_ix; let layer_ix = self.layer_ix;
let on_close = self.on_close.clone(); let on_close = self.on_close.clone();
let window_paddings = crate::window_border::window_paddings(cx); let window_paddings = crate::window_border::window_paddings(window, cx);
let view_size = cx.viewport_size() let view_size = window.viewport_size()
- gpui::size( - gpui::size(
window_paddings.left + window_paddings.right, window_paddings.left + window_paddings.right,
window_paddings.top + window_paddings.bottom, window_paddings.top + window_paddings.bottom,
@@ -172,9 +170,9 @@ impl RenderOnce for Modal {
}) })
.on_mouse_down(MouseButton::Left, { .on_mouse_down(MouseButton::Left, {
let on_close = self.on_close.clone(); let on_close = self.on_close.clone();
move |_, cx| { move |_, window, cx| {
on_close(&ClickEvent::default(), cx); on_close(&ClickEvent::default(), window, cx);
cx.close_modal(); window.close_modal(cx);
} }
}) })
.child( .child(
@@ -185,13 +183,13 @@ impl RenderOnce for Modal {
.when(self.keyboard, |this| { .when(self.keyboard, |this| {
this.on_action({ this.on_action({
let on_close = self.on_close.clone(); let on_close = self.on_close.clone();
move |_: &Escape, cx| { move |_: &Escape, window, cx| {
// FIXME: // FIXME:
// //
// Here some Modal have no focus_handle, so it will not work will Escape key. // Here some Modal have no focus_handle, so it will not work will Escape key.
// But by now, we `cx.close_modal()` going to close the last active model, so the Escape is unexpected to work. // But by now, we `cx.close_modal()` going to close the last active model, so the Escape is unexpected to work.
on_close(&ClickEvent::default(), cx); on_close(&ClickEvent::default(), window, cx);
cx.close_modal(); window.close_modal(cx);
} }
}) })
}) })
@@ -227,9 +225,9 @@ impl RenderOnce for Modal {
.ghost() .ghost()
.icon(IconName::Close) .icon(IconName::Close)
.on_click( .on_click(
move |_, cx| { move |_, window, cx| {
on_close(&ClickEvent::default(), cx); on_close(&ClickEvent::default(), window, cx);
cx.close_modal(); window.close_modal(cx);
}, },
), ),
) )

View File

@@ -10,9 +10,9 @@ use crate::{
v_flex, Icon, IconName, Sizable as _, StyledExt, v_flex, Icon, IconName, Sizable as _, StyledExt,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder, px, Animation, AnimationExt, ClickEvent, DismissEvent, ElementId, div, prelude::FluentBuilder, px, Animation, AnimationExt, App, AppContext, ClickEvent, Context,
EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, Render, SharedString, DismissEvent, ElementId, Entity, EventEmitter, InteractiveElement as _, IntoElement,
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WindowContext, ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, Window,
}; };
use smol::Timer; use smol::Timer;
use std::{any::TypeId, collections::VecDeque, sync::Arc, time::Duration}; use std::{any::TypeId, collections::VecDeque, sync::Arc, time::Duration};
@@ -42,7 +42,7 @@ impl From<(TypeId, ElementId)> for NotificationId {
} }
} }
type OnClick = Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext)>>; type OnClick = Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>;
/// A notification element. /// A notification element.
pub struct Notification { pub struct Notification {
@@ -175,18 +175,18 @@ impl Notification {
/// Set the click callback of the notification. /// Set the click callback of the notification.
pub fn on_click( pub fn on_click(
mut self, mut self,
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static, on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self { ) -> Self {
self.on_click = Some(Arc::new(on_click)); self.on_click = Some(Arc::new(on_click));
self self
} }
fn dismiss(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) { fn dismiss(&mut self, _: &ClickEvent, _window: &mut Window, cx: &mut Context<Self>) {
self.closing = true; self.closing = true;
cx.notify(); cx.notify();
// Dismiss the notification after 0.15s to show the animation. // Dismiss the notification after 0.15s to show the animation.
cx.spawn(|view, mut cx| async move { cx.spawn(|view, cx| async move {
Timer::after(Duration::from_secs_f32(0.15)).await; Timer::after(Duration::from_secs_f32(0.15)).await;
cx.update(|cx| { cx.update(|cx| {
if let Some(view) = view.upgrade() { if let Some(view) = view.upgrade() {
@@ -206,7 +206,7 @@ impl EventEmitter<DismissEvent> for Notification {}
impl FluentBuilder for Notification {} impl FluentBuilder for Notification {}
impl Render for Notification { impl Render for Notification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let closing = self.closing; let closing = self.closing;
let icon = match self.icon.clone() { let icon = match self.icon.clone() {
Some(icon) => icon, Some(icon) => icon,
@@ -250,9 +250,9 @@ impl Render for Notification {
) )
.when_some(self.on_click.clone(), |this, on_click| { .when_some(self.on_click.clone(), |this, on_click| {
this.cursor_pointer() this.cursor_pointer()
.on_click(cx.listener(move |view, event, cx| { .on_click(cx.listener(move |view, event, window, cx| {
view.dismiss(event, cx); view.dismiss(event, window, cx);
on_click(event, cx); on_click(event, window, cx);
})) }))
}) })
.when(!self.autohide, |this| { .when(!self.autohide, |this| {
@@ -292,19 +292,24 @@ impl Render for Notification {
/// A list of notifications. /// A list of notifications.
pub struct NotificationList { pub struct NotificationList {
/// Notifications that will be auto hidden. /// Notifications that will be auto hidden.
pub(crate) notifications: VecDeque<View<Notification>>, pub(crate) notifications: VecDeque<Entity<Notification>>,
expanded: bool, expanded: bool,
} }
impl NotificationList { impl NotificationList {
pub fn new(_cx: &mut ViewContext<Self>) -> Self { pub fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
Self { Self {
notifications: VecDeque::new(), notifications: VecDeque::new(),
expanded: false, expanded: false,
} }
} }
pub fn push(&mut self, notification: impl Into<Notification>, cx: &mut ViewContext<Self>) { pub fn push(
&mut self,
notification: impl Into<Notification>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let notification = notification.into(); let notification = notification.into();
let id = notification.id.clone(); let id = notification.id.clone();
let autohide = notification.autohide; let autohide = notification.autohide;
@@ -312,7 +317,8 @@ impl NotificationList {
// Remove the notification by id, for keep unique. // Remove the notification by id, for keep unique.
self.notifications.retain(|note| note.read(cx).id != id); self.notifications.retain(|note| note.read(cx).id != id);
let notification = cx.new_view(|_| notification); let notification = cx.new(|_| notification);
cx.subscribe(&notification, move |view, _, _: &DismissEvent, cx| { cx.subscribe(&notification, move |view, _, _: &DismissEvent, cx| {
view.notifications.retain(|note| id != note.read(cx).id); view.notifications.retain(|note| id != note.read(cx).id);
}) })
@@ -320,13 +326,13 @@ impl NotificationList {
self.notifications.push_back(notification.clone()); self.notifications.push_back(notification.clone());
if autohide { if autohide {
// Sleep for 5 seconds to autohide the notification // Sleep for 3 seconds to autohide the notification
cx.spawn(|_, mut cx| async move { cx.spawn_in(window, |_, mut cx| async move {
Timer::after(Duration::from_secs(5)).await; Timer::after(Duration::from_secs(3)).await;
if let Err(err) = notification if let Err(err) = notification.update_in(&mut cx, |note, window, cx| {
.update(&mut cx, |note, cx| note.dismiss(&ClickEvent::default(), cx)) note.dismiss(&ClickEvent::default(), window, cx)
{ }) {
println!("failed to auto hide notification: {:?}", err); println!("failed to auto hide notification: {:?}", err);
} }
}) })
@@ -335,19 +341,23 @@ impl NotificationList {
cx.notify(); cx.notify();
} }
pub fn clear(&mut self, cx: &mut ViewContext<Self>) { pub fn clear(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.notifications.clear(); self.notifications.clear();
cx.notify(); cx.notify();
} }
pub fn notifications(&self) -> Vec<View<Notification>> { pub fn notifications(&self) -> Vec<Entity<Notification>> {
self.notifications.iter().cloned().collect() self.notifications.iter().cloned().collect()
} }
} }
impl Render for NotificationList { impl Render for NotificationList {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement { fn render(
let size = cx.viewport_size(); &mut self,
window: &mut gpui::Window,
cx: &mut gpui::Context<Self>,
) -> impl IntoElement {
let size = window.viewport_size();
let items = self.notifications.iter().rev().take(10).rev().cloned(); let items = self.notifications.iter().rev().take(10).rev().cloned();
div() div()
@@ -364,9 +374,9 @@ impl Render for NotificationList {
.relative() .relative()
.right_0() .right_0()
.h(size.height - px(8.)) .h(size.height - px(8.))
.on_hover(cx.listener(|view, hovered, cx| { .on_hover(cx.listener(|view, hovered, _window, cx| {
view.expanded = *hovered; view.expanded = *hovered;
cx.notify() cx.notify();
})) }))
.gap_3() .gap_3()
.children(items), .children(items),

View File

@@ -1,10 +1,10 @@
use crate::{Selectable, StyledExt as _}; use crate::{Selectable, StyledExt as _};
use gpui::{ use gpui::{
actions, anchored, deferred, div, prelude::FluentBuilder as _, px, AnyElement, AppContext, actions, anchored, deferred, div, prelude::FluentBuilder as _, px, AnyElement, App, Bounds,
Bounds, Corner, DismissEvent, DispatchPhase, Element, ElementId, EventEmitter, FocusHandle, Context, Corner, DismissEvent, DispatchPhase, Element, ElementId, Entity, EventEmitter,
FocusableView, GlobalElementId, Hitbox, InteractiveElement as _, IntoElement, KeyBinding, FocusHandle, Focusable, GlobalElementId, Hitbox, InteractiveElement as _, IntoElement,
LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render, KeyBinding, LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point,
Style, StyleRefinement, Styled, View, ViewContext, VisualContext, WindowContext, Render, Style, StyleRefinement, Styled, Window,
}; };
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
@@ -12,22 +12,20 @@ const CONTEXT: &str = "Popover";
actions!(popover, [Escape]); actions!(popover, [Escape]);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut App) {
cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))]) cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))])
} }
type Content<T> = Rc<dyn Fn(&mut ViewContext<T>) -> AnyElement>;
pub struct PopoverContent { pub struct PopoverContent {
focus_handle: FocusHandle, focus_handle: FocusHandle,
content: Content<Self>, content: Rc<dyn Fn(&mut Window, &mut Context<Self>) -> AnyElement>,
max_width: Option<Pixels>, max_width: Option<Pixels>,
} }
impl PopoverContent { impl PopoverContent {
pub fn new<B>(cx: &mut WindowContext, content: B) -> Self pub fn new<B>(_window: &mut Window, cx: &mut App, content: B) -> Self
where where
B: Fn(&mut ViewContext<Self>) -> AnyElement + 'static, B: Fn(&mut Window, &mut Context<Self>) -> AnyElement + 'static,
{ {
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
@@ -46,32 +44,29 @@ impl PopoverContent {
impl EventEmitter<DismissEvent> for PopoverContent {} impl EventEmitter<DismissEvent> for PopoverContent {}
impl FocusableView for PopoverContent { impl Focusable for PopoverContent {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
impl Render for PopoverContent { impl Render for PopoverContent {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div() div()
.track_focus(&self.focus_handle) .track_focus(&self.focus_handle)
.key_context(CONTEXT) .key_context(CONTEXT)
.on_action(cx.listener(|_, _: &Escape, cx| cx.emit(DismissEvent))) .on_action(cx.listener(|_, _: &Escape, _, cx| cx.emit(DismissEvent)))
.p_2() .p_2()
.when_some(self.max_width, |this, v| this.max_w(v)) .when_some(self.max_width, |this, v| this.max_w(v))
.child(self.content.clone()(cx)) .child(self.content.clone()(window, cx))
} }
} }
type Trigger = Option<Box<dyn FnOnce(bool, &WindowContext) -> AnyElement + 'static>>;
type ViewContent<M> = Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>;
pub struct Popover<M: ManagedView> { pub struct Popover<M: ManagedView> {
id: ElementId, id: ElementId,
anchor: Corner, anchor: Corner,
trigger: Trigger, trigger: Option<Box<dyn FnOnce(bool, &Window, &App) -> AnyElement + 'static>>,
content: ViewContent<M>, content: Option<Rc<dyn Fn(&mut Window, &mut App) -> Entity<M> + 'static>>,
/// Style for trigger element. /// Style for trigger element.
/// This is used for hotfix the trigger element style to support w_full. /// This is used for hotfix the trigger element style to support w_full.
trigger_style: Option<StyleRefinement>, trigger_style: Option<StyleRefinement>,
@@ -111,7 +106,7 @@ where
where where
T: Selectable + IntoElement + 'static, T: Selectable + IntoElement + 'static,
{ {
self.trigger = Some(Box::new(|is_open, _| { self.trigger = Some(Box::new(|is_open, _, _| {
trigger.selected(is_open).into_any_element() trigger.selected(is_open).into_any_element()
})); }));
self self
@@ -121,13 +116,12 @@ where
self.trigger_style = Some(style); self.trigger_style = Some(style);
self self
} }
/// Set the content of the popover. /// Set the content of the popover.
/// ///
/// The `content` is a closure that returns an `AnyElement`. /// The `content` is a closure that returns an `AnyElement`.
pub fn content<C>(mut self, content: C) -> Self pub fn content<C>(mut self, content: C) -> Self
where where
C: Fn(&mut WindowContext) -> View<M> + 'static, C: Fn(&mut Window, &mut App) -> Entity<M> + 'static,
{ {
self.content = Some(Rc::new(content)); self.content = Some(Rc::new(content));
self self
@@ -144,12 +138,12 @@ where
self self
} }
fn render_trigger(&mut self, is_open: bool, cx: &mut WindowContext) -> AnyElement { fn render_trigger(&mut self, is_open: bool, window: &mut Window, cx: &mut App) -> AnyElement {
let Some(trigger) = self.trigger.take() else { let Some(trigger) = self.trigger.take() else {
return div().into_any_element(); return div().into_any_element();
}; };
(trigger)(is_open, cx) (trigger)(is_open, window, cx)
} }
fn resolved_corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> { fn resolved_corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
@@ -164,14 +158,15 @@ where
fn with_element_state<R>( fn with_element_state<R>(
&mut self, &mut self,
id: &GlobalElementId, id: &GlobalElementId,
cx: &mut WindowContext, window: &mut Window,
f: impl FnOnce(&mut Self, &mut PopoverElementState<M>, &mut WindowContext) -> R, cx: &mut App,
f: impl FnOnce(&mut Self, &mut PopoverElementState<M>, &mut Window, &mut App) -> R,
) -> R { ) -> R {
cx.with_optional_element_state::<PopoverElementState<M>, _>( window.with_optional_element_state::<PopoverElementState<M>, _>(
Some(id), Some(id),
|element_state, cx| { |element_state, window| {
let mut element_state = element_state.unwrap().unwrap_or_default(); let mut element_state = element_state.unwrap().unwrap_or_default();
let result = f(self, &mut element_state, cx); let result = f(self, &mut element_state, window, cx);
(result, Some(element_state)) (result, Some(element_state))
}, },
) )
@@ -194,7 +189,7 @@ pub struct PopoverElementState<M> {
popover_layout_id: Option<LayoutId>, popover_layout_id: Option<LayoutId>,
popover_element: Option<AnyElement>, popover_element: Option<AnyElement>,
trigger_element: Option<AnyElement>, trigger_element: Option<AnyElement>,
content_view: Rc<RefCell<Option<View<M>>>>, content_view: Rc<RefCell<Option<Entity<M>>>>,
/// Trigger bounds for positioning the popover. /// Trigger bounds for positioning the popover.
trigger_bounds: Option<Bounds<Pixels>>, trigger_bounds: Option<Bounds<Pixels>>,
} }
@@ -229,7 +224,8 @@ impl<M: ManagedView> Element for Popover<M> {
fn request_layout( fn request_layout(
&mut self, &mut self,
id: Option<&gpui::GlobalElementId>, id: Option<&gpui::GlobalElementId>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) { ) -> (gpui::LayoutId, Self::RequestLayoutState) {
let mut style = Style::default(); let mut style = Style::default();
@@ -246,7 +242,11 @@ impl<M: ManagedView> Element for Popover<M> {
} }
} }
self.with_element_state(id.unwrap(), cx, |view, element_state, cx| { self.with_element_state(
id.unwrap(),
window,
cx,
|view, element_state, window, cx| {
let mut popover_layout_id = None; let mut popover_layout_id = None;
let mut popover_element = None; let mut popover_element = None;
let mut is_open = false; let mut is_open = false;
@@ -273,15 +273,17 @@ impl<M: ManagedView> Element for Popover<M> {
.when(!no_style, |this| this.popover_style(cx)) .when(!no_style, |this| this.popover_style(cx))
.map(|this| match anchor { .map(|this| match anchor {
Corner::TopLeft | Corner::TopRight => this.top_1p5(), Corner::TopLeft | Corner::TopRight => this.top_1p5(),
Corner::BottomLeft | Corner::BottomRight => this.bottom_1p5(), Corner::BottomLeft | Corner::BottomRight => {
this.bottom_1p5()
}
}) })
.child(content_view.clone()) .child(content_view.clone())
.when(!no_style, |this| { .when(!no_style, |this| {
this.on_mouse_down_out(move |_, cx| { this.on_mouse_down_out(move |_, window, _| {
// Update the element_state.content_view to `None`, // Update the element_state.content_view to `None`,
// so that the `paint`` method will not paint it. // so that the `paint`` method will not paint it.
*content_view_mut.borrow_mut() = None; *content_view_mut.borrow_mut() = None;
cx.refresh(); window.refresh();
}) })
}), }),
), ),
@@ -290,16 +292,17 @@ impl<M: ManagedView> Element for Popover<M> {
.into_any() .into_any()
}; };
popover_layout_id = Some(element.request_layout(cx)); popover_layout_id = Some(element.request_layout(window, cx));
popover_element = Some(element); popover_element = Some(element);
} }
let mut trigger_element = view.render_trigger(is_open, cx); let mut trigger_element = view.render_trigger(is_open, window, cx);
let trigger_layout_id = trigger_element.request_layout(cx); let trigger_layout_id = trigger_element.request_layout(window, cx);
let layout_id = cx.request_layout( let layout_id = window.request_layout(
style, style,
Some(trigger_layout_id).into_iter().chain(popover_layout_id), Some(trigger_layout_id).into_iter().chain(popover_layout_id),
cx,
); );
( (
@@ -312,7 +315,8 @@ impl<M: ManagedView> Element for Popover<M> {
..Default::default() ..Default::default()
}, },
) )
}) },
)
} }
fn prepaint( fn prepaint(
@@ -320,25 +324,26 @@ impl<M: ManagedView> Element for Popover<M> {
_id: Option<&gpui::GlobalElementId>, _id: Option<&gpui::GlobalElementId>,
_bounds: gpui::Bounds<gpui::Pixels>, _bounds: gpui::Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState, request_layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self::PrepaintState { ) -> Self::PrepaintState {
if let Some(element) = &mut request_layout.trigger_element { if let Some(element) = &mut request_layout.trigger_element {
element.prepaint(cx); element.prepaint(window, cx);
} }
if let Some(element) = &mut request_layout.popover_element { if let Some(element) = &mut request_layout.popover_element {
element.prepaint(cx); element.prepaint(window, cx);
} }
let trigger_bounds = request_layout let trigger_bounds = request_layout
.trigger_layout_id .trigger_layout_id
.map(|id| cx.layout_bounds(id)); .map(|id| window.layout_bounds(id));
// Prepare the popover, for get the bounds of it for open window size. // Prepare the popover, for get the bounds of it for open window size.
let _ = request_layout let _ = request_layout
.popover_layout_id .popover_layout_id
.map(|id| cx.layout_bounds(id)); .map(|id| window.layout_bounds(id));
let hitbox = cx.insert_hitbox(trigger_bounds.unwrap_or_default(), false); let hitbox = window.insert_hitbox(trigger_bounds.unwrap_or_default(), false);
PrepaintState { PrepaintState {
trigger_bounds, trigger_bounds,
@@ -352,17 +357,22 @@ impl<M: ManagedView> Element for Popover<M> {
_bounds: Bounds<Pixels>, _bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState, request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState, prepaint: &mut Self::PrepaintState,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
self.with_element_state(id.unwrap(), cx, |this, element_state, cx| { self.with_element_state(
id.unwrap(),
window,
cx,
|this, element_state, window, cx| {
element_state.trigger_bounds = prepaint.trigger_bounds; element_state.trigger_bounds = prepaint.trigger_bounds;
if let Some(mut element) = request_layout.trigger_element.take() { if let Some(mut element) = request_layout.trigger_element.take() {
element.paint(cx); element.paint(window, cx);
} }
if let Some(mut element) = request_layout.popover_element.take() { if let Some(mut element) = request_layout.popover_element.take() {
element.paint(cx); element.paint(window, cx);
return; return;
} }
@@ -373,36 +383,44 @@ impl<M: ManagedView> Element for Popover<M> {
let old_content_view = element_state.content_view.clone(); let old_content_view = element_state.content_view.clone();
let hitbox_id = prepaint.hitbox.id; let hitbox_id = prepaint.hitbox.id;
let mouse_button = this.mouse_button; let mouse_button = this.mouse_button;
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
if phase == DispatchPhase::Bubble if phase == DispatchPhase::Bubble
&& event.button == mouse_button && event.button == mouse_button
&& hitbox_id.is_hovered(cx) && hitbox_id.is_hovered(window)
{ {
cx.stop_propagation(); cx.stop_propagation();
cx.prevent_default(); window.prevent_default();
let new_content_view = (content_build)(cx); let new_content_view = (content_build)(window, cx);
let old_content_view1 = old_content_view.clone(); let old_content_view1 = old_content_view.clone();
let previous_focus_handle = cx.focused(); let previous_focus_handle = window.focused(cx);
cx.subscribe(&new_content_view, move |modal, _: &DismissEvent, cx| { window
if modal.focus_handle(cx).contains_focused(cx) { .subscribe(
if let Some(previous_focus_handle) = previous_focus_handle.as_ref() { &new_content_view,
cx.focus(previous_focus_handle); cx,
move |modal, _: &DismissEvent, window, cx| {
if modal.focus_handle(cx).contains_focused(window, cx) {
if let Some(previous_focus_handle) =
previous_focus_handle.as_ref()
{
window.focus(previous_focus_handle);
} }
} }
*old_content_view1.borrow_mut() = None; *old_content_view1.borrow_mut() = None;
cx.refresh(); window.refresh();
}) },
)
.detach(); .detach();
cx.focus_view(&new_content_view); window.focus(&new_content_view.focus_handle(cx));
*old_content_view.borrow_mut() = Some(new_content_view); *old_content_view.borrow_mut() = Some(new_content_view);
cx.refresh(); window.refresh();
} }
}); });
}); },
);
} }
} }

View File

@@ -8,11 +8,10 @@ use crate::{
v_flex, Icon, IconName, Selectable, Sizable as _, StyledExt, v_flex, Icon, IconName, Selectable, Sizable as _, StyledExt,
}; };
use gpui::{ use gpui::{
actions, anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement, actions, anchored, canvas, div, prelude::FluentBuilder, px, rems, Action, AnyElement, App,
AppContext, Bounds, Corner, DismissEvent, Edges, EventEmitter, FocusHandle, FocusableView, AppContext, Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle,
InteractiveElement, IntoElement, KeyBinding, Keystroke, ParentElement, Pixels, Render, Focusable, InteractiveElement, IntoElement, KeyBinding, Keystroke, ParentElement, Pixels,
ScrollHandle, SharedString, StatefulInteractiveElement, Styled, View, ViewContext, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window,
VisualContext as _, WeakView, WindowContext,
}; };
use std::{cell::Cell, ops::Deref, rc::Rc}; use std::{cell::Cell, ops::Deref, rc::Rc};
@@ -20,8 +19,9 @@ actions!(menu, [Confirm, Dismiss, SelectNext, SelectPrev]);
const ITEM_HEIGHT: Pixels = px(26.); const ITEM_HEIGHT: Pixels = px(26.);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut App) {
let context = Some("PopupMenu"); let context = Some("PopupMenu");
cx.bind_keys([ cx.bind_keys([
KeyBinding::new("enter", Confirm, context), KeyBinding::new("enter", Confirm, context),
KeyBinding::new("escape", Dismiss, context), KeyBinding::new("escape", Dismiss, context),
@@ -34,7 +34,7 @@ pub trait PopupMenuExt: Styled + Selectable + IntoElement + 'static {
/// Create a popup menu with the given items, anchored to the TopLeft corner /// Create a popup menu with the given items, anchored to the TopLeft corner
fn popup_menu( fn popup_menu(
self, self,
f: impl Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static, f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
) -> Popover<PopupMenu> { ) -> Popover<PopupMenu> {
self.popup_menu_with_anchor(Corner::TopLeft, f) self.popup_menu_with_anchor(Corner::TopLeft, f)
} }
@@ -43,7 +43,7 @@ pub trait PopupMenuExt: Styled + Selectable + IntoElement + 'static {
fn popup_menu_with_anchor( fn popup_menu_with_anchor(
mut self, mut self,
anchor: impl Into<Corner>, anchor: impl Into<Corner>,
f: impl Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static, f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
) -> Popover<PopupMenu> { ) -> Popover<PopupMenu> {
let style = self.style().clone(); let style = self.style().clone();
let element_id = self.element_id(); let element_id = self.element_id();
@@ -53,7 +53,9 @@ pub trait PopupMenuExt: Styled + Selectable + IntoElement + 'static {
.trigger(self) .trigger(self)
.trigger_style(style) .trigger_style(style)
.anchor(anchor.into()) .anchor(anchor.into())
.content(move |cx| PopupMenu::build(cx, |menu, cx| f(menu, cx))) .content(move |window, cx| {
PopupMenu::build(window, cx, |menu, window, cx| f(menu, window, cx))
})
} }
} }
@@ -65,16 +67,16 @@ enum PopupMenuItem {
icon: Option<Icon>, icon: Option<Icon>,
label: SharedString, label: SharedString,
action: Option<Box<dyn Action>>, action: Option<Box<dyn Action>>,
handler: Rc<dyn Fn(&mut WindowContext)>, handler: Rc<dyn Fn(&mut Window, &mut App)>,
}, },
ElementItem { ElementItem {
render: Box<dyn Fn(&mut WindowContext) -> AnyElement + 'static>, render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>,
handler: Rc<dyn Fn(&mut WindowContext)>, handler: Rc<dyn Fn(&mut Window, &mut App)>,
}, },
Submenu { Submenu {
icon: Option<Icon>, icon: Option<Icon>,
label: SharedString, label: SharedString,
menu: View<PopupMenu>, menu: Entity<PopupMenu>,
}, },
} }
@@ -94,7 +96,7 @@ impl PopupMenuItem {
pub struct PopupMenu { pub struct PopupMenu {
/// The parent menu of this menu, if this is a submenu /// The parent menu of this menu, if this is a submenu
parent_menu: Option<WeakView<Self>>, parent_menu: Option<WeakEntity<Self>>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
menu_items: Vec<PopupMenuItem>, menu_items: Vec<PopupMenuItem>,
has_icon: bool, has_icon: bool,
@@ -114,13 +116,15 @@ pub struct PopupMenu {
impl PopupMenu { impl PopupMenu {
pub fn build( pub fn build(
cx: &mut WindowContext, window: &mut Window,
f: impl FnOnce(Self, &mut ViewContext<PopupMenu>) -> Self, cx: &mut App,
) -> View<Self> { f: impl FnOnce(Self, &mut Window, &mut Context<PopupMenu>) -> Self,
cx.new_view(|cx| { ) -> Entity<Self> {
cx.new(|cx| {
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
let _on_blur_subscription = cx.on_blur(&focus_handle, |this: &mut PopupMenu, cx| { let _on_blur_subscription =
this.dismiss(&Dismiss, cx) cx.on_blur(&focus_handle, window, |this: &mut PopupMenu, window, cx| {
this.dismiss(&Dismiss, window, cx)
}); });
let menu = Self { let menu = Self {
@@ -139,8 +143,8 @@ impl PopupMenu {
scroll_state: Rc::new(Cell::new(ScrollbarState::default())), scroll_state: Rc::new(Cell::new(ScrollbarState::default())),
_subscriptions: [_on_blur_subscription], _subscriptions: [_on_blur_subscription],
}; };
cx.refresh(); window.refresh();
f(menu, cx) f(menu, window, cx)
}) })
} }
@@ -183,7 +187,7 @@ impl PopupMenu {
icon: None, icon: None,
label: label.into(), label: label.into(),
action: None, action: None,
handler: Rc::new(move |cx| cx.open_url(&href)), handler: Rc::new(move |_window, cx| cx.open_url(&href)),
}); });
self self
} }
@@ -200,7 +204,7 @@ impl PopupMenu {
icon: Some(icon.into()), icon: Some(icon.into()),
label: label.into(), label: label.into(),
action: None, action: None,
handler: Rc::new(move |cx| cx.open_url(&href)), handler: Rc::new(move |_window, cx| cx.open_url(&href)),
}); });
self self
} }
@@ -235,21 +239,21 @@ impl PopupMenu {
/// Add Menu Item with custom element render. /// Add Menu Item with custom element render.
pub fn menu_with_element<F, E>(mut self, builder: F, action: Box<dyn Action>) -> Self pub fn menu_with_element<F, E>(mut self, builder: F, action: Box<dyn Action>) -> Self
where where
F: Fn(&mut WindowContext) -> E + 'static, F: Fn(&mut Window, &mut App) -> E + 'static,
E: IntoElement, E: IntoElement,
{ {
self.menu_items.push(PopupMenuItem::ElementItem { self.menu_items.push(PopupMenuItem::ElementItem {
render: Box::new(move |cx| builder(cx).into_any_element()), render: Box::new(move |window, cx| builder(window, cx).into_any_element()),
handler: self.wrap_handler(action), handler: self.wrap_handler(action),
}); });
self self
} }
fn wrap_handler(&self, action: Box<dyn Action>) -> Rc<dyn Fn(&mut WindowContext)> { fn wrap_handler(&self, action: Box<dyn Action>) -> Rc<dyn Fn(&mut Window, &mut App)> {
let action_focus_handle = self.action_focus_handle.clone(); let action_focus_handle = self.action_focus_handle.clone();
Rc::new(move |cx| { Rc::new(move |window, cx| {
cx.activate_window(); window.activate_window();
// Focus back to the user expected focus handle // Focus back to the user expected focus handle
// Then the actions listened on that focus handle can be received // Then the actions listened on that focus handle can be received
@@ -264,10 +268,10 @@ impl PopupMenu {
// If the actions are listened on the `PanelContent`, // If the actions are listened on the `PanelContent`,
// it can't receive the actions from the `PopupMenu`, unless we focus on `PanelContent`. // it can't receive the actions from the `PopupMenu`, unless we focus on `PanelContent`.
if let Some(handle) = action_focus_handle.as_ref() { if let Some(handle) = action_focus_handle.as_ref() {
cx.focus(handle); window.focus(handle);
} }
cx.dispatch_action(action.boxed_clone()); cx.dispatch_action(action.as_ref());
}) })
} }
@@ -307,10 +311,11 @@ impl PopupMenu {
pub fn submenu( pub fn submenu(
self, self,
label: impl Into<SharedString>, label: impl Into<SharedString>,
cx: &mut ViewContext<Self>, window: &mut Window,
f: impl Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static, cx: &mut Context<Self>,
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
) -> Self { ) -> Self {
self.submenu_with_icon(None, label, cx, f) self.submenu_with_icon(None, label, window, cx, f)
} }
/// Add a Submenu item with icon /// Add a Submenu item with icon
@@ -318,11 +323,12 @@ impl PopupMenu {
mut self, mut self,
icon: Option<Icon>, icon: Option<Icon>,
label: impl Into<SharedString>, label: impl Into<SharedString>,
cx: &mut ViewContext<Self>, window: &mut Window,
f: impl Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static, cx: &mut Context<Self>,
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
) -> Self { ) -> Self {
let submenu = PopupMenu::build(cx, f); let submenu = PopupMenu::build(window, cx, f);
let parent_menu = cx.view().downgrade(); let parent_menu = cx.model().downgrade();
submenu.update(cx, |view, _| { submenu.update(cx, |view, _| {
view.parent_menu = Some(parent_menu); view.parent_menu = Some(parent_menu);
}); });
@@ -335,7 +341,7 @@ impl PopupMenu {
self self
} }
pub(crate) fn active_submenu(&self) -> Option<View<PopupMenu>> { pub(crate) fn active_submenu(&self) -> Option<Entity<PopupMenu>> {
if let Some(ix) = self.hovered_menu_ix { if let Some(ix) = self.hovered_menu_ix {
if let Some(item) = self.menu_items.get(ix) { if let Some(item) = self.menu_items.get(ix) {
return match item { return match item {
@@ -359,31 +365,31 @@ impl PopupMenu {
.filter(|(_, item)| item.is_clickable()) .filter(|(_, item)| item.is_clickable())
} }
fn on_click(&mut self, ix: usize, cx: &mut ViewContext<Self>) { fn on_click(&mut self, ix: usize, window: &mut Window, cx: &mut Context<Self>) {
cx.stop_propagation(); cx.stop_propagation();
cx.prevent_default(); window.prevent_default();
self.selected_index = Some(ix); self.selected_index = Some(ix);
self.confirm(&Confirm, cx); self.confirm(&Confirm, window, cx);
} }
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) { fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
if let Some(index) = self.selected_index { if let Some(index) = self.selected_index {
let item = self.menu_items.get(index); let item = self.menu_items.get(index);
match item { match item {
Some(PopupMenuItem::Item { handler, .. }) => { Some(PopupMenuItem::Item { handler, .. }) => {
handler(cx); handler(window, cx);
self.dismiss(&Dismiss, cx) self.dismiss(&Dismiss, window, cx)
} }
Some(PopupMenuItem::ElementItem { handler, .. }) => { Some(PopupMenuItem::ElementItem { handler, .. }) => {
handler(cx); handler(window, cx);
self.dismiss(&Dismiss, cx) self.dismiss(&Dismiss, window, cx)
} }
_ => {} _ => {}
} }
} }
} }
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) { fn select_next(&mut self, _: &SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
let count = self.clickable_menu_items().count(); let count = self.clickable_menu_items().count();
if count > 0 { if count > 0 {
let last_ix = count.saturating_sub(1); let last_ix = count.saturating_sub(1);
@@ -397,7 +403,7 @@ impl PopupMenu {
} }
} }
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) { fn select_prev(&mut self, _: &SelectPrev, _window: &mut Window, cx: &mut Context<Self>) {
let count = self.clickable_menu_items().count(); let count = self.clickable_menu_items().count();
if count > 0 { if count > 0 {
let last_ix = count.saturating_sub(1); let last_ix = count.saturating_sub(1);
@@ -417,27 +423,31 @@ impl PopupMenu {
} }
} }
fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) { // TODO: fix this
#[allow(clippy::only_used_in_recursion)]
fn dismiss(&mut self, _: &Dismiss, window: &mut Window, cx: &mut Context<Self>) {
if self.active_submenu().is_some() { if self.active_submenu().is_some() {
return; return;
} }
cx.emit(DismissEvent); cx.emit(DismissEvent);
// Dismiss parent menu, when this menu is dismissed // Dismiss parent menu, when this menu is dismissed
if let Some(parent_menu) = self.parent_menu.clone().and_then(|menu| menu.upgrade()) { if let Some(parent_menu) = self.parent_menu.clone().and_then(|menu| menu.upgrade()) {
parent_menu.update(cx, |view, cx| { parent_menu.update(cx, |view, cx| {
view.hovered_menu_ix = None; view.hovered_menu_ix = None;
view.dismiss(&Dismiss, cx); view.dismiss(&Dismiss, window, cx);
}) })
} }
} }
fn render_keybinding( fn render_keybinding(
action: Option<Box<dyn Action>>, action: Option<Box<dyn Action>>,
cx: &ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Option<impl IntoElement> { ) -> Option<impl IntoElement> {
if let Some(action) = action { if let Some(action) = action {
if let Some(keybinding) = cx.bindings_for_action(action.deref()).first() { if let Some(keybinding) = window.bindings_for_action(action.deref()).first() {
let el = div() let el = div()
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)) .text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
.children( .children(
@@ -457,7 +467,8 @@ impl PopupMenu {
fn render_icon( fn render_icon(
has_icon: bool, has_icon: bool,
icon: Option<Icon>, icon: Option<Icon>,
_: &ViewContext<Self>, _window: &Window,
_cx: &Context<Self>,
) -> Option<impl IntoElement> { ) -> Option<impl IntoElement> {
let icon_placeholder = if has_icon { Some(Icon::empty()) } else { None }; let icon_placeholder = if has_icon { Some(Icon::empty()) } else { None };
@@ -487,21 +498,21 @@ impl FluentBuilder for PopupMenu {}
impl EventEmitter<DismissEvent> for PopupMenu {} impl EventEmitter<DismissEvent> for PopupMenu {}
impl FocusableView for PopupMenu { impl Focusable for PopupMenu {
fn focus_handle(&self, _: &AppContext) -> FocusHandle { fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
impl Render for PopupMenu { impl Render for PopupMenu {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.view().clone(); let view = cx.model().clone();
let has_icon = self.menu_items.iter().any(|item| item.has_icon()); let has_icon = self.menu_items.iter().any(|item| item.has_icon());
let items_count = self.menu_items.len(); let items_count = self.menu_items.len();
let max_width = self.max_width; let max_width = self.max_width;
let bounds = self.bounds; let bounds = self.bounds;
let window_haft_height = cx.window_bounds().get_bounds().size.height * 0.5; let window_haft_height = window.window_bounds().get_bounds().size.height * 0.5;
let max_height = window_haft_height.min(px(450.)); let max_height = window_haft_height.min(px(450.));
v_flex() v_flex()
@@ -512,7 +523,9 @@ impl Render for PopupMenu {
.on_action(cx.listener(Self::select_prev)) .on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::dismiss)) .on_action(cx.listener(Self::dismiss))
.on_mouse_down_out(cx.listener(|this, _, cx| this.dismiss(&Dismiss, cx))) .on_mouse_down_out(
cx.listener(|this, _, window, cx| this.dismiss(&Dismiss, window, cx)),
)
.popover_style(cx) .popover_style(cx)
.relative() .relative()
.p_1() .p_1()
@@ -532,8 +545,8 @@ impl Render for PopupMenu {
.min_w(rems(8.)) .min_w(rems(8.))
.child({ .child({
canvas( canvas(
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds), move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|_, _, _| {}, |_, _, _, _| {},
) )
.absolute() .absolute()
.size_full() .size_full()
@@ -554,10 +567,12 @@ impl Render for PopupMenu {
.px_2() .px_2()
.rounded_md() .rounded_md()
.text_xs() .text_xs()
.on_mouse_enter(cx.listener(move |this, _, cx| { .on_mouse_enter(cx.listener(
move |this, _, _window, cx| {
this.hovered_menu_ix = Some(ix); this.hovered_menu_ix = Some(ix);
cx.notify(); cx.notify();
})); },
));
match item { match item {
PopupMenuItem::Separator => { PopupMenuItem::Separator => {
@@ -574,18 +589,20 @@ impl Render for PopupMenu {
) )
} }
PopupMenuItem::ElementItem { render, .. } => this PopupMenuItem::ElementItem { render, .. } => this
.on_click(cx.listener(move |this, _, cx| { .on_click(cx.listener(
this.on_click(ix, cx) move |this, _, window, cx| {
})) this.on_click(ix, window, cx)
},
))
.child( .child(
h_flex() h_flex()
.min_h(ITEM_HEIGHT) .min_h(ITEM_HEIGHT)
.items_center() .items_center()
.gap_x_1p5() .gap_x_1()
.children(Self::render_icon( .children(Self::render_icon(
has_icon, None, cx, has_icon, None, window, cx,
)) ))
.child((render)(cx)), .child((render)(window, cx)),
), ),
PopupMenuItem::Item { PopupMenuItem::Item {
icon, icon,
@@ -596,11 +613,14 @@ impl Render for PopupMenu {
let action = action let action = action
.as_ref() .as_ref()
.map(|action| action.boxed_clone()); .map(|action| action.boxed_clone());
let key = Self::render_keybinding(action, cx); let key =
Self::render_keybinding(action, window, cx);
this.on_click(cx.listener(move |this, _, cx| { this.on_click(cx.listener(
this.on_click(ix, cx) move |this, _, window, cx| {
})) this.on_click(ix, window, cx)
},
))
.child( .child(
h_flex() h_flex()
.h(ITEM_HEIGHT) .h(ITEM_HEIGHT)
@@ -609,6 +629,7 @@ impl Render for PopupMenu {
.children(Self::render_icon( .children(Self::render_icon(
has_icon, has_icon,
icon.clone(), icon.clone(),
window,
cx, cx,
)) ))
.child( .child(
@@ -637,6 +658,7 @@ impl Render for PopupMenu {
.children(Self::render_icon( .children(Self::render_icon(
has_icon, has_icon,
icon.clone(), icon.clone(),
window,
cx, cx,
)) ))
.child( .child(
@@ -655,7 +677,7 @@ impl Render for PopupMenu {
self.hovered_menu_ix, self.hovered_menu_ix,
|this, hovered_ix| { |this, hovered_ix| {
let (anchor, left) = let (anchor, left) =
if cx.bounds().size.width if window.bounds().size.width
- bounds.origin.x - bounds.origin.x
< max_width < max_width
{ {
@@ -670,7 +692,7 @@ impl Render for PopupMenu {
let top = if bounds.origin.y let top = if bounds.origin.y
+ bounds.size.height + bounds.size.height
> cx.bounds().size.height > window.bounds().size.height
{ {
px(32.) px(32.)
} else { } else {

View File

@@ -1,6 +1,5 @@
pub use gpui::prelude::*; pub use gpui::prelude::*;
pub use gpui::{ pub use gpui::{
div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId, div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,
InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, ViewContext, InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, Window,
WindowContext,
}; };

View File

@@ -1,7 +1,7 @@
use crate::theme::{scale::ColorScaleStep, ActiveTheme}; use crate::theme::{scale::ColorScaleStep, ActiveTheme};
use gpui::{ use gpui::{
div, prelude::FluentBuilder, px, relative, IntoElement, ParentElement, RenderOnce, Styled, div, prelude::FluentBuilder, px, relative, App, IntoElement, ParentElement, RenderOnce, Styled,
WindowContext, Window,
}; };
/// A Progress bar element. /// A Progress bar element.
@@ -32,7 +32,7 @@ impl Default for Progress {
} }
impl RenderOnce for Progress { impl RenderOnce for Progress {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let rounded = px(self.height / 2.); let rounded = px(self.height / 2.);
let relative_w = relative(match self.value { let relative_w = relative(match self.value {
v if v < 0. => 0., v if v < 0. => 0.,

View File

@@ -4,11 +4,11 @@ use crate::{
IconName, IconName,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder, relative, svg, ElementId, InteractiveElement, IntoElement, div, prelude::FluentBuilder, relative, svg, App, ElementId, InteractiveElement, IntoElement,
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, WindowContext, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
}; };
type OnClick = Option<Box<dyn Fn(&bool, &mut WindowContext) + 'static>>; type OnClick = Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>;
/// A Radio element. /// A Radio element.
/// ///
@@ -48,14 +48,14 @@ impl Radio {
self self
} }
pub fn on_click(mut self, handler: impl Fn(&bool, &mut WindowContext) + 'static) -> Self { pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Box::new(handler)); self.on_click = Some(Box::new(handler));
self self
} }
} }
impl RenderOnce for Radio { impl RenderOnce for Radio {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let color = if self.disabled { let color = if self.disabled {
cx.theme().accent.step(cx, ColorScaleStep::FIVE) cx.theme().accent.step(cx, ColorScaleStep::FIVE)
} else { } else {
@@ -102,8 +102,8 @@ impl RenderOnce for Radio {
.when_some( .when_some(
self.on_click.filter(|_| !self.disabled), self.on_click.filter(|_| !self.disabled),
|this, on_click| { |this, on_click| {
this.on_click(move |_event, cx| { this.on_click(move |_event, window, cx| {
on_click(&!self.checked, cx); on_click(&!self.checked, window, cx);
}) })
}, },
) )

View File

@@ -1,16 +1,22 @@
use gpui::{Axis, ViewContext}; use gpui::{Axis, Context, Window};
mod panel; mod panel;
mod resize_handle; mod resize_handle;
pub use panel::*; pub use panel::*;
pub(crate) use resize_handle::*; pub(crate) use resize_handle::*;
pub fn h_resizable(cx: &mut ViewContext<ResizablePanelGroup>) -> ResizablePanelGroup { pub fn h_resizable(
ResizablePanelGroup::new(cx).axis(Axis::Horizontal) window: &mut Window,
cx: &mut Context<ResizablePanelGroup>,
) -> ResizablePanelGroup {
ResizablePanelGroup::new(window, cx).axis(Axis::Horizontal)
} }
pub fn v_resizable(cx: &mut ViewContext<ResizablePanelGroup>) -> ResizablePanelGroup { pub fn v_resizable(
ResizablePanelGroup::new(cx).axis(Axis::Vertical) window: &mut Window,
cx: &mut Context<ResizablePanelGroup>,
) -> ResizablePanelGroup {
ResizablePanelGroup::new(window, cx).axis(Axis::Vertical)
} }
pub fn resizable_panel() -> ResizablePanel { pub fn resizable_panel() -> ResizablePanel {

View File

@@ -1,10 +1,10 @@
use super::resize_handle; use super::resize_handle;
use crate::{h_flex, v_flex, AxisExt}; use crate::{h_flex, v_flex, AxisExt};
use gpui::{ use gpui::{
canvas, div, prelude::FluentBuilder, px, relative, Along, AnyElement, AnyView, Axis, Bounds, canvas, div, prelude::FluentBuilder, px, relative, Along, AnyElement, AnyView, App, AppContext,
Element, Entity, EntityId, EventEmitter, IntoElement, IsZero, MouseMoveEvent, MouseUpEvent, Axis, Bounds, Context, Element, Entity, EntityId, EventEmitter, IntoElement, IsZero,
ParentElement, Pixels, Render, StatefulInteractiveElement as _, Style, Styled, View, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Render, StatefulInteractiveElement as _,
ViewContext, VisualContext as _, WeakView, WindowContext, Style, Styled, WeakEntity, Window,
}; };
use std::rc::Rc; use std::rc::Rc;
@@ -19,7 +19,7 @@ pub struct DragPanel(pub (EntityId, usize, Axis));
#[derive(Clone)] #[derive(Clone)]
pub struct ResizablePanelGroup { pub struct ResizablePanelGroup {
panels: Vec<View<ResizablePanel>>, panels: Vec<Entity<ResizablePanel>>,
sizes: Vec<Pixels>, sizes: Vec<Pixels>,
axis: Axis, axis: Axis,
size: Option<Pixels>, size: Option<Pixels>,
@@ -28,7 +28,7 @@ pub struct ResizablePanelGroup {
} }
impl ResizablePanelGroup { impl ResizablePanelGroup {
pub(super) fn new(_cx: &mut ViewContext<Self>) -> Self { pub(super) fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
Self { Self {
axis: Axis::Horizontal, axis: Axis::Horizontal,
sizes: Vec::new(), sizes: Vec::new(),
@@ -39,7 +39,7 @@ impl ResizablePanelGroup {
} }
} }
pub fn load(&mut self, sizes: Vec<Pixels>, panels: Vec<View<ResizablePanel>>) { pub fn load(&mut self, sizes: Vec<Pixels>, panels: Vec<Entity<ResizablePanel>>) {
self.sizes = sizes; self.sizes = sizes;
self.panels = panels; self.panels = panels;
} }
@@ -50,25 +50,35 @@ impl ResizablePanelGroup {
self self
} }
pub(crate) fn set_axis(&mut self, axis: Axis, cx: &mut ViewContext<Self>) { pub(crate) fn set_axis(&mut self, axis: Axis, _window: &mut Window, cx: &mut Context<Self>) {
self.axis = axis; self.axis = axis;
cx.notify(); cx.notify();
} }
/// Add a resizable panel to the group. /// Add a resizable panel to the group.
pub fn child(mut self, panel: ResizablePanel, cx: &mut ViewContext<Self>) -> Self { pub fn child(
self.add_child(panel, cx); mut self,
panel: ResizablePanel,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
self.add_child(panel, window, cx);
self self
} }
/// Add a ResizablePanelGroup as a child to the group. /// Add a ResizablePanelGroup as a child to the group.
pub fn group(self, group: ResizablePanelGroup, cx: &mut ViewContext<Self>) -> Self { pub fn group(
self,
group: ResizablePanelGroup,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let group: ResizablePanelGroup = group; let group: ResizablePanelGroup = group;
let size = group.size; let size = group.size;
let panel = ResizablePanel::new() let panel = ResizablePanel::new()
.content_view(cx.new_view(|_| group).into()) .content_view(cx.new(|_| group).into())
.when_some(size, |this, size| this.size(size)); .when_some(size, |this, size| this.size(size));
self.child(panel, cx) self.child(panel, window, cx)
} }
/// Set size of the resizable panel group /// Set size of the resizable panel group
@@ -90,22 +100,34 @@ impl ResizablePanelGroup {
self.sizes.iter().fold(px(0.0), |acc, &size| acc + size) self.sizes.iter().fold(px(0.0), |acc, &size| acc + size)
} }
pub fn add_child(&mut self, panel: ResizablePanel, cx: &mut ViewContext<Self>) { pub fn add_child(
&mut self,
panel: ResizablePanel,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let mut panel = panel; let mut panel = panel;
panel.axis = self.axis; panel.axis = self.axis;
panel.group = Some(cx.view().downgrade()); panel.group = Some(cx.model().downgrade());
self.sizes.push(panel.initial_size.unwrap_or_default()); self.sizes.push(panel.initial_size.unwrap_or_default());
self.panels.push(cx.new_view(|_| panel)); self.panels.push(cx.new(|_| panel));
} }
pub fn insert_child(&mut self, panel: ResizablePanel, ix: usize, cx: &mut ViewContext<Self>) { pub fn insert_child(
&mut self,
panel: ResizablePanel,
ix: usize,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let mut panel = panel; let mut panel = panel;
panel.axis = self.axis; panel.axis = self.axis;
panel.group = Some(cx.view().downgrade()); panel.group = Some(cx.model().downgrade());
self.sizes self.sizes
.insert(ix, panel.initial_size.unwrap_or_default()); .insert(ix, panel.initial_size.unwrap_or_default());
self.panels.insert(ix, cx.new_view(|_| panel)); self.panels.insert(ix, cx.new(|_| panel));
cx.notify() cx.notify()
} }
@@ -114,7 +136,8 @@ impl ResizablePanelGroup {
&mut self, &mut self,
panel: ResizablePanel, panel: ResizablePanel,
ix: usize, ix: usize,
cx: &mut ViewContext<Self>, _window: &mut Window,
cx: &mut Context<Self>,
) { ) {
let mut panel = panel; let mut panel = panel;
@@ -125,45 +148,50 @@ impl ResizablePanelGroup {
panel.initial_size = old_panel_initial_size; panel.initial_size = old_panel_initial_size;
panel.size_ratio = old_panel_size_ratio; panel.size_ratio = old_panel_size_ratio;
panel.axis = self.axis; panel.axis = self.axis;
panel.group = Some(cx.view().downgrade()); panel.group = Some(cx.model().downgrade());
self.sizes[ix] = panel.initial_size.unwrap_or_default(); self.sizes[ix] = panel.initial_size.unwrap_or_default();
self.panels[ix] = cx.new_view(|_| panel); self.panels[ix] = cx.new(|_| panel);
cx.notify() cx.notify()
} }
pub fn remove_child(&mut self, ix: usize, cx: &mut ViewContext<Self>) { pub fn remove_child(&mut self, ix: usize, _window: &mut Window, cx: &mut Context<Self>) {
self.sizes.remove(ix); self.sizes.remove(ix);
self.panels.remove(ix); self.panels.remove(ix);
cx.notify() cx.notify()
} }
pub(crate) fn remove_all_children(&mut self, cx: &mut ViewContext<Self>) { pub(crate) fn remove_all_children(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.sizes.clear(); self.sizes.clear();
self.panels.clear(); self.panels.clear();
cx.notify() cx.notify()
} }
fn render_resize_handle(&self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_resize_handle(
let view = cx.view().clone(); &self,
ix: usize,
_window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let view = cx.model().clone();
resize_handle(("resizable-handle", ix), self.axis).on_drag( resize_handle(("resizable-handle", ix), self.axis).on_drag(
DragPanel((cx.entity_id(), ix, self.axis)), DragPanel((cx.entity_id(), ix, self.axis)),
move |drag_panel, _, cx| { move |drag_panel, _, _window, cx| {
cx.stop_propagation(); cx.stop_propagation();
// Set current resizing panel ix // Set current resizing panel ix
view.update(cx, |view, _| { view.update(cx, |view, _| {
view.resizing_panel_ix = Some(ix); view.resizing_panel_ix = Some(ix);
}); });
cx.new_view(|_| drag_panel.clone()) cx.new(|_| drag_panel.clone())
}, },
) )
} }
fn done_resizing(&mut self, cx: &mut ViewContext<Self>) { fn done_resizing(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
cx.emit(ResizablePanelEvent::Resized); cx.emit(ResizablePanelEvent::Resized);
self.resizing_panel_ix = None; self.resizing_panel_ix = None;
} }
fn sync_real_panel_sizes(&mut self, cx: &WindowContext) { fn sync_real_panel_sizes(&mut self, _window: &Window, cx: &App) {
for (i, panel) in self.panels.iter().enumerate() { for (i, panel) in self.panels.iter().enumerate() {
self.sizes[i] = panel.read(cx).bounds.size.along(self.axis) self.sizes[i] = panel.read(cx).bounds.size.along(self.axis)
} }
@@ -171,7 +199,13 @@ impl ResizablePanelGroup {
/// The `ix`` is the index of the panel to resize, /// The `ix`` is the index of the panel to resize,
/// and the `size` is the new size for the panel. /// and the `size` is the new size for the panel.
fn resize_panels(&mut self, ix: usize, size: Pixels, cx: &mut ViewContext<Self>) { fn resize_panels(
&mut self,
ix: usize,
size: Pixels,
window: &mut Window,
cx: &mut Context<Self>,
) {
let mut ix = ix; let mut ix = ix;
// Only resize the left panels. // Only resize the left panels.
if ix >= self.panels.len() - 1 { if ix >= self.panels.len() - 1 {
@@ -180,7 +214,7 @@ impl ResizablePanelGroup {
let size = size.floor(); let size = size.floor();
let container_size = self.bounds.size.along(self.axis); let container_size = self.bounds.size.along(self.axis);
self.sync_real_panel_sizes(cx); self.sync_real_panel_sizes(window, cx);
let mut changed = size - self.sizes[ix]; let mut changed = size - self.sizes[ix];
let is_expand = changed > px(0.); let is_expand = changed > px(0.);
@@ -238,8 +272,8 @@ impl ResizablePanelGroup {
impl EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {} impl EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {}
impl Render for ResizablePanelGroup { impl Render for ResizablePanelGroup {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.view().clone(); let view = cx.model().clone();
let container = if self.axis.is_horizontal() { let container = if self.axis.is_horizontal() {
h_flex() h_flex()
} else { } else {
@@ -250,7 +284,7 @@ impl Render for ResizablePanelGroup {
.size_full() .size_full()
.children(self.panels.iter().enumerate().map(|(ix, panel)| { .children(self.panels.iter().enumerate().map(|(ix, panel)| {
if ix > 0 { if ix > 0 {
let handle = self.render_resize_handle(ix - 1, cx); let handle = self.render_resize_handle(ix - 1, window, cx);
panel.update(cx, |view, _| { panel.update(cx, |view, _| {
view.resize_handle = Some(handle.into_any_element()) view.resize_handle = Some(handle.into_any_element())
}); });
@@ -260,23 +294,23 @@ impl Render for ResizablePanelGroup {
})) }))
.child({ .child({
canvas( canvas(
move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds), move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|_, _, _| {}, |_, _, _, _| {},
) )
.absolute() .absolute()
.size_full() .size_full()
}) })
.child(ResizePanelGroupElement { .child(ResizePanelGroupElement {
view: cx.view().clone(), view: cx.model().clone(),
axis: self.axis, axis: self.axis,
}) })
} }
} }
type ContentBuilder = Option<Rc<dyn Fn(&mut WindowContext) -> AnyElement>>; type ContentBuilder = Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>>;
pub struct ResizablePanel { pub struct ResizablePanel {
group: Option<WeakView<ResizablePanelGroup>>, group: Option<WeakEntity<ResizablePanelGroup>>,
/// Initial size is the size that the panel has when it is created. /// Initial size is the size that the panel has when it is created.
initial_size: Option<Pixels>, initial_size: Option<Pixels>,
/// size is the size that the panel has when it is resized or adjusted by flex layout. /// size is the size that the panel has when it is resized or adjusted by flex layout.
@@ -308,7 +342,7 @@ impl ResizablePanel {
pub fn content<F>(mut self, content: F) -> Self pub fn content<F>(mut self, content: F) -> Self
where where
F: Fn(&mut WindowContext) -> AnyElement + 'static, F: Fn(&mut Window, &mut App) -> AnyElement + 'static,
{ {
self.content_builder = Some(Rc::new(content)); self.content_builder = Some(Rc::new(content));
self self
@@ -326,12 +360,17 @@ impl ResizablePanel {
} }
/// Save the real panel size, and update group sizes /// Save the real panel size, and update group sizes
fn update_size(&mut self, bounds: Bounds<Pixels>, cx: &mut ViewContext<Self>) { fn update_size(
&mut self,
bounds: Bounds<Pixels>,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let new_size = bounds.size.along(self.axis); let new_size = bounds.size.along(self.axis);
self.bounds = bounds; self.bounds = bounds;
self.size = Some(new_size); self.size = Some(new_size);
let panel_view = cx.view().clone(); let panel_view = cx.model().clone();
if let Some(group) = self.group.as_ref() { if let Some(group) = self.group.as_ref() {
_ = group.update(cx, |view, _| { _ = group.update(cx, |view, _| {
if let Some(ix) = view if let Some(ix) = view
@@ -350,8 +389,8 @@ impl ResizablePanel {
impl FluentBuilder for ResizablePanel {} impl FluentBuilder for ResizablePanel {}
impl Render for ResizablePanel { impl Render for ResizablePanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.view().clone(); let view = cx.model().clone();
let total_size = self let total_size = self
.group .group
.as_ref() .as_ref()
@@ -388,13 +427,17 @@ impl Render for ResizablePanel {
}) })
.child({ .child({
canvas( canvas(
move |bounds, cx| view.update(cx, |r, cx| r.update_size(bounds, cx)), move |bounds, window, cx| {
|_, _, _| {}, view.update(cx, |r, cx| r.update_size(bounds, window, cx))
},
|_, _, _, _| {},
) )
.absolute() .absolute()
.size_full() .size_full()
}) })
.when_some(self.content_builder.clone(), |this, c| this.child(c(cx))) .when_some(self.content_builder.clone(), |this, c| {
this.child(c(window, cx))
})
.when_some(self.content_view.clone(), |this, c| this.child(c)) .when_some(self.content_view.clone(), |this, c| this.child(c))
.when_some(self.resize_handle.take(), |this, c| this.child(c)) .when_some(self.resize_handle.take(), |this, c| this.child(c))
} }
@@ -402,7 +445,7 @@ impl Render for ResizablePanel {
struct ResizePanelGroupElement { struct ResizePanelGroupElement {
axis: Axis, axis: Axis,
view: View<ResizablePanelGroup>, view: Entity<ResizablePanelGroup>,
} }
impl IntoElement for ResizePanelGroupElement { impl IntoElement for ResizePanelGroupElement {
@@ -424,9 +467,10 @@ impl Element for ResizePanelGroupElement {
fn request_layout( fn request_layout(
&mut self, &mut self,
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) { ) -> (gpui::LayoutId, Self::RequestLayoutState) {
(cx.request_layout(Style::default(), None), ()) (window.request_layout(Style::default(), None, cx), ())
} }
fn prepaint( fn prepaint(
@@ -434,7 +478,8 @@ impl Element for ResizePanelGroupElement {
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
_: Bounds<Pixels>, _: Bounds<Pixels>,
_: &mut Self::RequestLayoutState, _: &mut Self::RequestLayoutState,
_: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Self::PrepaintState { ) -> Self::PrepaintState {
} }
@@ -444,13 +489,14 @@ impl Element for ResizePanelGroupElement {
_: Bounds<Pixels>, _: Bounds<Pixels>,
_: &mut Self::RequestLayoutState, _: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState, _: &mut Self::PrepaintState,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
cx.on_mouse_event({ window.on_mouse_event({
let view = self.view.clone(); let view = self.view.clone();
let axis = self.axis; let axis = self.axis;
let current_ix = view.read(cx).resizing_panel_ix; let current_ix = view.read(cx).resizing_panel_ix;
move |e: &MouseMoveEvent, phase, cx| { move |e: &MouseMoveEvent, phase, window, cx| {
if phase.bubble() { if phase.bubble() {
if let Some(ix) = current_ix { if let Some(ix) = current_ix {
view.update(cx, |view, cx| { view.update(cx, |view, cx| {
@@ -461,11 +507,19 @@ impl Element for ResizePanelGroupElement {
.read(cx); .read(cx);
match axis { match axis {
Axis::Horizontal => { Axis::Horizontal => view.resize_panels(
view.resize_panels(ix, e.position.x - panel.bounds.left(), cx) ix,
} e.position.x - panel.bounds.left(),
window,
cx,
),
Axis::Vertical => { Axis::Vertical => {
view.resize_panels(ix, e.position.y - panel.bounds.top(), cx); view.resize_panels(
ix,
e.position.y - panel.bounds.top(),
window,
cx,
);
} }
} }
}) })
@@ -475,11 +529,11 @@ impl Element for ResizePanelGroupElement {
}); });
// When any mouse up, stop dragging // When any mouse up, stop dragging
cx.on_mouse_event({ window.on_mouse_event({
let view = self.view.clone(); let view = self.view.clone();
move |_: &MouseUpEvent, phase, cx| { move |_: &MouseUpEvent, phase, window, cx| {
if phase.bubble() { if phase.bubble() {
view.update(cx, |view, cx| view.done_resizing(cx)); view.update(cx, |view, cx| view.done_resizing(window, cx));
} }
} }
}) })

View File

@@ -3,9 +3,9 @@ use crate::{
AxisExt as _, AxisExt as _,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, Axis, Div, ElementId, InteractiveElement, IntoElement, div, prelude::FluentBuilder as _, px, App, Axis, Div, ElementId, InteractiveElement,
ParentElement as _, Pixels, RenderOnce, Stateful, StatefulInteractiveElement, Styled as _, IntoElement, ParentElement as _, Pixels, RenderOnce, Stateful, StatefulInteractiveElement,
WindowContext, Styled as _, Window,
}; };
pub(crate) const HANDLE_PADDING: Pixels = px(8.); pub(crate) const HANDLE_PADDING: Pixels = px(8.);
@@ -40,7 +40,7 @@ impl InteractiveElement for ResizeHandle {
impl StatefulInteractiveElement for ResizeHandle {} impl StatefulInteractiveElement for ResizeHandle {}
impl RenderOnce for ResizeHandle { impl RenderOnce for ResizeHandle {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
self.base self.base
.occlude() .occlude()
.absolute() .absolute()

View File

@@ -1,55 +1,52 @@
use crate::{ use crate::{
modal::Modal, modal::Modal,
notification::{Notification, NotificationList}, notification::{Notification, NotificationList},
theme::{scale::ColorScaleStep, ActiveTheme, Theme}, theme::{scale::ColorScaleStep, ActiveTheme},
window_border, window_border,
}; };
use gpui::{ use gpui::{
div, AnyView, FocusHandle, InteractiveElement, IntoElement, ParentElement as _, Render, Styled, div, AnyView, App, AppContext, Context, DefiniteLength, Entity, FocusHandle,
View, ViewContext, VisualContext as _, WindowContext, InteractiveElement, IntoElement, ParentElement as _, Render, Styled, Window,
};
use std::{
ops::{Deref, DerefMut},
rc::Rc,
}; };
use std::rc::Rc;
/// Extension trait for [`WindowContext`] and [`ViewContext`] to add drawer functionality. /// Extension trait for [`WindowContext`] and [`ViewContext`] to add drawer functionality.
pub trait ContextModal: Sized { pub trait ContextModal: Sized {
/// Opens a Modal. /// Opens a Modal.
fn open_modal<F>(&mut self, build: F) fn open_modal<F>(&mut self, cx: &mut App, build: F)
where where
F: Fn(Modal, &mut WindowContext) -> Modal + 'static; F: Fn(Modal, &mut Window, &mut App) -> Modal + 'static;
/// Return true, if there is an active Modal. /// Return true, if there is an active Modal.
fn has_active_modal(&self) -> bool; fn has_active_modal(&mut self, cx: &mut App) -> bool;
/// Closes the last active Modal. /// Closes the last active Modal.
fn close_modal(&mut self); fn close_modal(&mut self, cx: &mut App);
/// Closes all active Modals. /// Closes all active Modals.
fn close_all_modals(&mut self); fn close_all_modals(&mut self, cx: &mut App);
/// Returns number of notifications.
fn notifications(&mut self, cx: &mut App) -> Rc<Vec<Entity<Notification>>>;
/// Pushes a notification to the notification list. /// Pushes a notification to the notification list.
fn push_notification(&mut self, note: impl Into<Notification>); fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App);
fn clear_notifications(&mut self); fn clear_notifications(&mut self, cx: &mut App);
/// Returns number of notifications.
fn notifications(&self) -> Rc<Vec<View<Notification>>>;
} }
impl ContextModal for WindowContext<'_> { impl ContextModal for Window {
fn open_modal<F>(&mut self, build: F) fn open_modal<F>(&mut self, cx: &mut App, build: F)
where where
F: Fn(Modal, &mut WindowContext) -> Modal + 'static, F: Fn(Modal, &mut Window, &mut App) -> Modal + 'static,
{ {
Root::update(self, move |root, cx| { Root::update(self, cx, move |root, window, cx| {
// Only save focus handle if there are no active modals. // Only save focus handle if there are no active modals.
// This is used to restore focus when all modals are closed. // This is used to restore focus when all modals are closed.
if root.active_modals.is_empty() { if root.active_modals.is_empty() {
root.previous_focus_handle = cx.focused(); root.previous_focus_handle = window.focused(cx);
} }
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
focus_handle.focus(cx); focus_handle.focus(window);
root.active_modals.push(ActiveModal { root.active_modals.push(ActiveModal {
focus_handle, focus_handle,
@@ -59,86 +56,112 @@ impl ContextModal for WindowContext<'_> {
}) })
} }
fn has_active_modal(&self) -> bool { fn has_active_modal(&mut self, cx: &mut App) -> bool {
!Root::read(self).active_modals.is_empty() !Root::read(self, cx).active_modals.is_empty()
} }
fn close_modal(&mut self) { fn close_modal(&mut self, cx: &mut App) {
Root::update(self, move |root, cx| { Root::update(self, cx, move |root, window, cx| {
root.active_modals.pop(); root.active_modals.pop();
if let Some(top_modal) = root.active_modals.last() { if let Some(top_modal) = root.active_modals.last() {
// Focus the next modal. // Focus the next modal.
top_modal.focus_handle.focus(cx); top_modal.focus_handle.focus(window);
} else { } else {
// Restore focus if there are no more modals. // Restore focus if there are no more modals.
root.focus_back(cx); root.focus_back(window, cx);
} }
cx.notify(); cx.notify();
}) })
} }
fn close_all_modals(&mut self) { fn close_all_modals(&mut self, cx: &mut App) {
Root::update(self, |root, cx| { Root::update(self, cx, |root, window, cx| {
root.active_modals.clear(); root.active_modals.clear();
root.focus_back(cx); root.focus_back(window, cx);
cx.notify(); cx.notify();
}) })
} }
fn push_notification(&mut self, note: impl Into<Notification>) { fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App) {
let note = note.into(); let note = note.into();
Root::update(self, move |root, cx| { Root::update(self, cx, move |root, window, cx| {
root.notification.update(cx, |view, cx| view.push(note, cx)); root.notification
.update(cx, |view, cx| view.push(note, window, cx));
cx.notify(); cx.notify();
}) })
} }
fn clear_notifications(&mut self) { fn clear_notifications(&mut self, cx: &mut App) {
Root::update(self, move |root, cx| { Root::update(self, cx, move |root, window, cx| {
root.notification.update(cx, |view, cx| view.clear(cx)); root.notification
.update(cx, |view, cx| view.clear(window, cx));
cx.notify(); cx.notify();
}) })
} }
fn notifications(&self) -> Rc<Vec<View<Notification>>> { fn notifications(&mut self, cx: &mut App) -> Rc<Vec<Entity<Notification>>> {
Rc::new(Root::read(self).notification.read(self).notifications()) let entity = Root::read(self, cx).notification.clone();
Rc::new(entity.read(cx).notifications())
} }
} }
impl<V> ContextModal for ViewContext<'_, V> {
fn has_active_modal(&self) -> bool {
self.deref().has_active_modal()
}
fn open_modal<F>(&mut self, build: F) // impl<V> ContextModal for Context<'_, V> {
where // fn open_drawer<F>(&mut self, cx: &mut App, build: F)
F: Fn(Modal, &mut WindowContext) -> Modal + 'static, // where
{ // F: Fn(Drawer, &mut Window, &mut App) -> Drawer + 'static,
self.deref_mut().open_modal(build) // {
} // self.deref_mut().open_drawer(cx, build)
// }
/// Close the last active modal. // fn open_drawer_at<F>(&mut self, cx: &mut App, placement: Placement, build: F)
fn close_modal(&mut self) { // where
self.deref_mut().close_modal() // F: Fn(Drawer, &mut Window, &mut App) -> Drawer + 'static,
} // {
// self.deref_mut().open_drawer_at(cx, placement, build)
// }
/// Close all modals. // fn has_active_modal(&self, cx: &mut App) -> bool {
fn close_all_modals(&mut self) { // self.deref().has_active_modal(cx)
self.deref_mut().close_all_modals() // }
}
fn push_notification(&mut self, note: impl Into<Notification>) { // fn close_drawer(&mut self, cx: &mut App) {
self.deref_mut().push_notification(note) // self.deref_mut().close_drawer(cx)
} // }
fn clear_notifications(&mut self) { // fn open_modal<F>(&mut self, cx: &mut App, build: F)
self.deref_mut().clear_notifications() // where
} // F: Fn(Modal, &mut Window, &mut App) -> Modal + 'static,
// {
// self.deref_mut().open_modal(cx, build)
// }
fn notifications(&self) -> Rc<Vec<View<Notification>>> { // fn has_active_drawer(&self, cx: &mut App) -> bool {
self.deref().notifications() // self.deref().has_active_drawer(cx)
} // }
}
// /// Close the last active modal.
// fn close_modal(&mut self, cx: &mut App) {
// self.deref_mut().close_modal(cx)
// }
// /// Close all modals.
// fn close_all_modals(&mut self, cx: &mut App) {
// self.deref_mut().close_all_modals(cx)
// }
// fn push_notification(&mut self, cx: &mut App, note: impl Into<Notification>) {
// self.deref_mut().push_notification(cx, note)
// }
// fn clear_notifications(&mut self, cx: &mut App) {
// self.deref_mut().clear_notifications(cx)
// }
// fn notifications(&self, cx: &mut App) -> Rc<Vec<Entity<Notification>>> {
// self.deref().notifications(cx)
// }
// }
/// Root is a view for the App window for as the top level view (Must be the first view in the window). /// Root is a view for the App window for as the top level view (Must be the first view in the window).
/// ///
@@ -148,80 +171,62 @@ pub struct Root {
/// When the Modal, Drawer closes, we will focus back to the previous view. /// When the Modal, Drawer closes, we will focus back to the previous view.
previous_focus_handle: Option<FocusHandle>, previous_focus_handle: Option<FocusHandle>,
active_modals: Vec<ActiveModal>, active_modals: Vec<ActiveModal>,
pub notification: View<NotificationList>, pub notification: Entity<NotificationList>,
view: AnyView, view: AnyView,
} }
type ModelBuilder = Rc<dyn Fn(Modal, &mut WindowContext) -> Modal + 'static>;
#[derive(Clone)] #[derive(Clone)]
struct ActiveModal { struct ActiveModal {
focus_handle: FocusHandle, focus_handle: FocusHandle,
builder: ModelBuilder, builder: Rc<dyn Fn(Modal, &mut Window, &mut App) -> Modal + 'static>,
} }
impl Root { impl Root {
pub fn new(view: AnyView, cx: &mut ViewContext<Self>) -> Self { pub fn new(view: AnyView, window: &mut Window, cx: &mut Context<Self>) -> Self {
cx.observe_window_appearance(|_, cx| {
Theme::sync_system_appearance(cx);
})
.detach();
Self { Self {
previous_focus_handle: None, previous_focus_handle: None,
active_modals: Vec::new(), active_modals: Vec::new(),
notification: cx.new_view(NotificationList::new), notification: cx.new(|cx| NotificationList::new(window, cx)),
view, view,
} }
} }
pub fn update<F>(cx: &mut WindowContext, f: F) pub fn update<F>(window: &mut Window, cx: &mut App, f: F)
where where
F: FnOnce(&mut Self, &mut ViewContext<Self>) + 'static, F: FnOnce(&mut Self, &mut Window, &mut Context<Self>) + 'static,
{ {
let root = cx if let Some(Some(root)) = window.root_model::<Root>() {
.window_handle() root.update(cx, |root, cx| f(root, window, cx));
.downcast::<Root>() }
.and_then(|w| w.root_view(cx).ok())
.expect("The window root view should be of type `ui::Root`.");
root.update(cx, |root, cx| f(root, cx))
} }
pub fn read<'a>(cx: &'a WindowContext) -> &'a Self { pub fn read<'a>(window: &'a mut Window, cx: &'a mut App) -> &'a Self {
let root = cx window
.window_handle() .root_model::<Root>()
.downcast::<Root>() .expect("The window root view should be of type `ui::Root`.")
.and_then(|w| w.root_view(cx).ok()) .unwrap()
.expect("The window root view should be of type `ui::Root`."); .read(cx)
root.read(cx)
} }
fn focus_back(&mut self, cx: &mut WindowContext) { fn focus_back(&mut self, window: &mut Window, _: &mut App) {
if let Some(handle) = self.previous_focus_handle.clone() { if let Some(handle) = self.previous_focus_handle.clone() {
cx.focus(&handle); window.focus(&handle);
} }
} }
// Render Notification layer. // Render Notification layer.
pub fn render_notification_layer(cx: &mut WindowContext) -> Option<impl IntoElement> { pub fn render_notification_layer(
let root = cx window: &mut Window,
.window_handle() cx: &mut App,
.downcast::<Root>() ) -> Option<impl IntoElement> {
.and_then(|w| w.root_view(cx).ok()) let root = window.root_model::<Root>()??;
.expect("The window root view should be of type `ui::Root`.");
Some(div().child(root.read(cx).notification.clone())) Some(div().child(root.read(cx).notification.clone()))
} }
/// Render the Modal layer. /// Render the Modal layer.
pub fn render_modal_layer(cx: &mut WindowContext) -> Option<impl IntoElement> { pub fn render_modal_layer(window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
let root = cx let root = window.root_model::<Root>()??;
.window_handle()
.downcast::<Root>()
.and_then(|w| w.root_view(cx).ok())
.expect("The window root view should be of type `ui::Root`.");
let active_modals = root.read(cx).active_modals.clone(); let active_modals = root.read(cx).active_modals.clone();
let mut has_overlay = false; let mut has_overlay = false;
@@ -232,9 +237,9 @@ impl Root {
Some( Some(
div().children(active_modals.iter().enumerate().map(|(i, active_modal)| { div().children(active_modals.iter().enumerate().map(|(i, active_modal)| {
let mut modal = Modal::new(cx); let mut modal = Modal::new(window, cx);
modal = (active_modal.builder)(modal, cx); modal = (active_modal.builder)(modal, window, cx);
modal.layer_ix = i; modal.layer_ix = i;
// Give the modal the focus handle, because `modal` is a temporary value, is not possible to // Give the modal the focus handle, because `modal` is a temporary value, is not possible to
// keep the focus handle in the modal. // keep the focus handle in the modal.
@@ -262,9 +267,9 @@ impl Root {
} }
impl Render for Root { impl Render for Root {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let base_font_size = cx.theme().font_size; let base_font_size = cx.theme().font_size;
cx.set_rem_size(base_font_size); window.set_rem_size(base_font_size);
window_border().child( window_border().child(
div() div()

View File

@@ -1,7 +1,7 @@
use gpui::{ use gpui::{
canvas, div, relative, AnyElement, Div, Element, ElementId, EntityId, GlobalElementId, canvas, div, relative, AnyElement, App, Div, Element, ElementId, EntityId, GlobalElementId,
InteractiveElement, IntoElement, ParentElement, Pixels, Position, ScrollHandle, SharedString, InteractiveElement, IntoElement, ParentElement, Pixels, Position, ScrollHandle, SharedString,
Size, Stateful, StatefulInteractiveElement, Style, StyleRefinement, Styled, WindowContext, Size, Stateful, StatefulInteractiveElement, Style, StyleRefinement, Styled, Window,
}; };
use std::{cell::Cell, rc::Rc}; use std::{cell::Cell, rc::Rc};
@@ -58,14 +58,18 @@ where
fn with_element_state<R>( fn with_element_state<R>(
&mut self, &mut self,
id: &GlobalElementId, id: &GlobalElementId,
cx: &mut WindowContext, window: &mut Window,
f: impl FnOnce(&mut Self, &mut ScrollViewState, &mut WindowContext) -> R, cx: &mut App,
f: impl FnOnce(&mut Self, &mut ScrollViewState, &mut Window, &mut App) -> R,
) -> R { ) -> R {
cx.with_optional_element_state::<ScrollViewState, _>(Some(id), |element_state, cx| { window.with_optional_element_state::<ScrollViewState, _>(
Some(id),
|element_state, window| {
let mut element_state = element_state.unwrap().unwrap_or_default(); let mut element_state = element_state.unwrap().unwrap_or_default();
let result = f(self, &mut element_state, cx); let result = f(self, &mut element_state, window, cx);
(result, Some(element_state)) (result, Some(element_state))
}) },
)
} }
} }
@@ -149,14 +153,16 @@ where
fn request_layout( fn request_layout(
&mut self, &mut self,
id: Option<&gpui::GlobalElementId>, id: Option<&gpui::GlobalElementId>,
cx: &mut gpui::WindowContext, window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) { ) -> (gpui::LayoutId, Self::RequestLayoutState) {
let style = Style { let style = Style {
flex_grow: 1.0,
position: Position::Relative, position: Position::Relative,
flex_grow: 1.0,
flex_shrink: 1.0,
size: Size { size: Size {
width: relative(1.0).into(), width: relative(1.).into(),
height: relative(1.0).into(), height: relative(1.).into(),
}, },
..Default::default() ..Default::default()
}; };
@@ -167,7 +173,7 @@ where
let scroll_id = self.id.clone(); let scroll_id = self.id.clone();
let content = self.element.take().map(|c| c.into_any_element()); let content = self.element.take().map(|c| c.into_any_element());
self.with_element_state(id.unwrap(), cx, |_, element_state, cx| { self.with_element_state(id.unwrap(), window, cx, |_, element_state, window, cx| {
let handle = element_state.handle.clone(); let handle = element_state.handle.clone();
let state = element_state.state.clone(); let state = element_state.state.clone();
let scroll_size = element_state.scroll_size.clone(); let scroll_size = element_state.scroll_size.clone();
@@ -185,7 +191,7 @@ where
.size_full() .size_full()
.child(div().children(content).child({ .child(div().children(content).child({
let scroll_size = element_state.scroll_size.clone(); let scroll_size = element_state.scroll_size.clone();
canvas(move |b, _| scroll_size.set(b.size), |_, _, _| {}) canvas(move |b, _, _| scroll_size.set(b.size), |_, _, _, _| {})
.absolute() .absolute()
.size_full() .size_full()
})), })),
@@ -203,9 +209,9 @@ where
), ),
) )
.into_any_element(); .into_any_element();
let element_id = element.request_layout(window, cx);
let element_id = element.request_layout(cx); let layout_id = window.request_layout(style, vec![element_id], cx);
let layout_id = cx.request_layout(style, vec![element_id]);
(layout_id, element) (layout_id, element)
}) })
@@ -216,9 +222,10 @@ where
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
_: gpui::Bounds<Pixels>, _: gpui::Bounds<Pixels>,
element: &mut Self::RequestLayoutState, element: &mut Self::RequestLayoutState,
cx: &mut gpui::WindowContext, window: &mut Window,
cx: &mut App,
) -> Self::PrepaintState { ) -> Self::PrepaintState {
element.prepaint(cx); element.prepaint(window, cx);
// do nothing // do nothing
ScrollViewState::default() ScrollViewState::default()
} }
@@ -229,8 +236,9 @@ where
_: gpui::Bounds<Pixels>, _: gpui::Bounds<Pixels>,
element: &mut Self::RequestLayoutState, element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState, _: &mut Self::PrepaintState,
cx: &mut gpui::WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
element.paint(cx) element.paint(window, cx)
} }
} }

View File

@@ -1,7 +1,7 @@
use gpui::{ use gpui::{
px, relative, Axis, Bounds, ContentMask, Corners, Edges, Element, ElementId, EntityId, px, relative, App, Axis, Bounds, ContentMask, Corners, Edges, Element, ElementId, EntityId,
GlobalElementId, Hitbox, Hsla, IntoElement, IsZero as _, LayoutId, PaintQuad, Pixels, Point, GlobalElementId, Hitbox, Hsla, IntoElement, IsZero as _, LayoutId, PaintQuad, Pixels, Point,
Position, ScrollHandle, ScrollWheelEvent, Size, Style, WindowContext, Position, ScrollHandle, ScrollWheelEvent, Size, Style, Window,
}; };
use crate::AxisExt; use crate::AxisExt;
@@ -56,9 +56,9 @@ impl Element for ScrollableMask {
fn request_layout( fn request_layout(
&mut self, &mut self,
_: Option<&GlobalElementId>, _: Option<&GlobalElementId>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) { ) -> (LayoutId, Self::RequestLayoutState) {
// Set the layout style relative to the table view to get same size.
let style = Style { let style = Style {
position: Position::Absolute, position: Position::Absolute,
flex_grow: 1.0, flex_grow: 1.0,
@@ -70,7 +70,7 @@ impl Element for ScrollableMask {
..Default::default() ..Default::default()
}; };
(cx.request_layout(style, None), ()) (window.request_layout(style, None, cx), ())
} }
fn prepaint( fn prepaint(
@@ -78,7 +78,8 @@ impl Element for ScrollableMask {
_: Option<&GlobalElementId>, _: Option<&GlobalElementId>,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState, _: &mut Self::RequestLayoutState,
cx: &mut WindowContext, window: &mut Window,
_: &mut App,
) -> Self::PrepaintState { ) -> Self::PrepaintState {
// Move y to bounds height to cover the parent view. // Move y to bounds height to cover the parent view.
let cover_bounds = Bounds { let cover_bounds = Bounds {
@@ -89,7 +90,7 @@ impl Element for ScrollableMask {
size: bounds.size, size: bounds.size,
}; };
cx.insert_hitbox(cover_bounds, false) window.insert_hitbox(cover_bounds, false)
} }
fn paint( fn paint(
@@ -98,14 +99,15 @@ impl Element for ScrollableMask {
_: Bounds<Pixels>, _: Bounds<Pixels>,
_: &mut Self::RequestLayoutState, _: &mut Self::RequestLayoutState,
hitbox: &mut Self::PrepaintState, hitbox: &mut Self::PrepaintState,
cx: &mut WindowContext, window: &mut Window,
_: &mut App,
) { ) {
let line_height = cx.line_height(); let line_height = window.line_height();
let bounds = hitbox.bounds; let bounds = hitbox.bounds;
cx.with_content_mask(Some(ContentMask { bounds }), |cx| { window.with_content_mask(Some(ContentMask { bounds }), |window| {
if let Some(color) = self.debug { if let Some(color) = self.debug {
cx.paint_quad(PaintQuad { window.paint_quad(PaintQuad {
bounds, bounds,
border_widths: Edges::all(px(1.0)), border_widths: Edges::all(px(1.0)),
border_color: color, border_color: color,
@@ -114,16 +116,19 @@ impl Element for ScrollableMask {
}); });
} }
cx.on_mouse_event({ window.on_mouse_event({
let view_id = self.view_id; let view_id = self.view_id;
let is_horizontal = self.axis.is_horizontal(); let is_horizontal = self.axis.is_horizontal();
let scroll_handle = self.scroll_handle.clone(); let scroll_handle = self.scroll_handle.clone();
let hitbox = hitbox.clone(); let hitbox = hitbox.clone();
let mouse_position = cx.mouse_position(); let mouse_position = window.mouse_position();
let last_offset = scroll_handle.offset(); let last_offset = scroll_handle.offset();
move |event: &ScrollWheelEvent, phase, cx| { move |event: &ScrollWheelEvent, phase, window, cx| {
if bounds.contains(&mouse_position) && phase.bubble() && hitbox.is_hovered(cx) { if bounds.contains(&mouse_position)
&& phase.bubble()
&& hitbox.is_hovered(window)
{
let mut offset = scroll_handle.offset(); let mut offset = scroll_handle.offset();
let mut delta = event.delta.pixel_delta(line_height); let mut delta = event.delta.pixel_delta(line_height);
@@ -146,7 +151,7 @@ impl Element for ScrollableMask {
if last_offset != offset { if last_offset != offset {
scroll_handle.set_offset(offset); scroll_handle.set_offset(offset);
cx.notify(Some(view_id)); cx.notify(view_id);
cx.stop_propagation(); cx.stop_propagation();
} }
} }

View File

@@ -290,7 +290,7 @@ impl Scrollbar {
self self
} }
fn style_for_active(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) { fn style_for_active(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
( (
cx.theme().scrollbar_thumb_hover, cx.theme().scrollbar_thumb_hover,
cx.theme().scrollbar, cx.theme().scrollbar,
@@ -300,7 +300,7 @@ impl Scrollbar {
) )
} }
fn style_for_hovered_thumb(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) { fn style_for_hovered_thumb(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
( (
cx.theme().scrollbar_thumb_hover, cx.theme().scrollbar_thumb_hover,
cx.theme().scrollbar, cx.theme().scrollbar,
@@ -310,7 +310,7 @@ impl Scrollbar {
) )
} }
fn style_for_hovered_bar(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) { fn style_for_hovered_bar(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
let (inset, radius) = if cx.theme().scrollbar_show.is_hover() { let (inset, radius) = if cx.theme().scrollbar_show.is_hover() {
(THUMB_INSET, THUMB_RADIUS - px(1.)) (THUMB_INSET, THUMB_RADIUS - px(1.))
} else { } else {
@@ -326,7 +326,7 @@ impl Scrollbar {
) )
} }
fn style_for_idle(_: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) { fn style_for_idle(_: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
( (
gpui::transparent_black(), gpui::transparent_black(),
gpui::transparent_black(), gpui::transparent_black(),
@@ -379,7 +379,8 @@ impl Element for Scrollbar {
fn request_layout( fn request_layout(
&mut self, &mut self,
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
cx: &mut gpui::WindowContext, window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) { ) -> (gpui::LayoutId, Self::RequestLayoutState) {
let style = Style { let style = Style {
position: Position::Absolute, position: Position::Absolute,
@@ -392,7 +393,7 @@ impl Element for Scrollbar {
..Default::default() ..Default::default()
}; };
(cx.request_layout(style, None), ()) (window.request_layout(style, None, cx), ())
} }
fn prepaint( fn prepaint(
@@ -400,10 +401,11 @@ impl Element for Scrollbar {
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState, _: &mut Self::RequestLayoutState,
cx: &mut gpui::WindowContext, window: &mut Window,
cx: &mut App,
) -> Self::PrepaintState { ) -> Self::PrepaintState {
let hitbox = cx.with_content_mask(Some(ContentMask { bounds }), |cx| { let hitbox = window.with_content_mask(Some(ContentMask { bounds }), |window| {
cx.insert_hitbox(bounds, false) window.insert_hitbox(bounds, false)
}); });
let mut states = vec![]; let mut states = vec![];
@@ -507,7 +509,7 @@ impl Element for Scrollbar {
idle_state.0 = cx.theme().scrollbar_thumb.opacity(opacity); idle_state.0 = cx.theme().scrollbar_thumb.opacity(opacity);
}; };
cx.request_animation_frame(); window.request_animation_frame();
} }
} }
} }
@@ -550,8 +552,8 @@ impl Element for Scrollbar {
) )
}; };
let bar_hitbox = cx.with_content_mask(Some(ContentMask { bounds }), |cx| { let bar_hitbox = window.with_content_mask(Some(ContentMask { bounds }), |window| {
cx.insert_hitbox(bounds, false) window.insert_hitbox(bounds, false)
}); });
states.push(AxisPrepaintState { states.push(AxisPrepaintState {
@@ -580,7 +582,8 @@ impl Element for Scrollbar {
_: Bounds<Pixels>, _: Bounds<Pixels>,
_: &mut Self::RequestLayoutState, _: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState, prepaint: &mut Self::PrepaintState,
cx: &mut gpui::WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
let hitbox_bounds = prepaint.hitbox.bounds; let hitbox_bounds = prepaint.hitbox.bounds;
let is_visible = self.state.get().is_scrollbar_visible(); let is_visible = self.state.get().is_scrollbar_visible();
@@ -597,9 +600,9 @@ impl Element for Scrollbar {
let margin_end = state.margin_end; let margin_end = state.margin_end;
let is_vertical = axis.is_vertical(); let is_vertical = axis.is_vertical();
cx.set_cursor_style(CursorStyle::default(), &state.bar_hitbox); window.set_cursor_style(CursorStyle::default(), &state.bar_hitbox);
cx.paint_layer(hitbox_bounds, |cx| { window.paint_layer(hitbox_bounds, |cx| {
cx.paint_quad(fill(state.bounds, state.bg)); cx.paint_quad(fill(state.bounds, state.bg));
cx.paint_quad(PaintQuad { cx.paint_quad(PaintQuad {
@@ -627,12 +630,12 @@ impl Element for Scrollbar {
cx.paint_quad(fill(state.thumb_fill_bounds, state.thumb_bg).corner_radii(radius)); cx.paint_quad(fill(state.thumb_fill_bounds, state.thumb_bg).corner_radii(radius));
}); });
cx.on_mouse_event({ window.on_mouse_event({
let state = self.state.clone(); let state = self.state.clone();
let view_id = self.view_id; let view_id = self.view_id;
let scroll_handle = self.scroll_handle.clone(); let scroll_handle = self.scroll_handle.clone();
move |event: &ScrollWheelEvent, phase, cx| { move |event: &ScrollWheelEvent, phase, _, cx| {
if phase.bubble() if phase.bubble()
&& hitbox_bounds.contains(&event.position) && hitbox_bounds.contains(&event.position)
&& scroll_handle.offset() != state.get().last_scroll_offset && scroll_handle.offset() != state.get().last_scroll_offset
@@ -642,7 +645,7 @@ impl Element for Scrollbar {
.get() .get()
.with_last_scroll(scroll_handle.offset(), Some(Instant::now())), .with_last_scroll(scroll_handle.offset(), Some(Instant::now())),
); );
cx.notify(Some(view_id)); cx.notify(view_id);
} }
} }
}); });
@@ -650,12 +653,12 @@ impl Element for Scrollbar {
let safe_range = (-scroll_area_size + container_size)..px(0.); let safe_range = (-scroll_area_size + container_size)..px(0.);
if is_hover_to_show || is_visible { if is_hover_to_show || is_visible {
cx.on_mouse_event({ window.on_mouse_event({
let state = self.state.clone(); let state = self.state.clone();
let view_id = self.view_id; let view_id = self.view_id;
let scroll_handle = self.scroll_handle.clone(); let scroll_handle = self.scroll_handle.clone();
move |event: &MouseDownEvent, phase, cx| { move |event: &MouseDownEvent, phase, _, cx| {
if phase.bubble() && bounds.contains(&event.position) { if phase.bubble() && bounds.contains(&event.position) {
cx.stop_propagation(); cx.stop_propagation();
@@ -665,7 +668,7 @@ impl Element for Scrollbar {
state.set(state.get().with_drag_pos(axis, pos)); state.set(state.get().with_drag_pos(axis, pos));
cx.notify(Some(view_id)); cx.notify(view_id);
} else { } else {
// click on the scrollbar, jump to the position // click on the scrollbar, jump to the position
// Set the thumb bar center to the click position // Set the thumb bar center to the click position
@@ -698,34 +701,34 @@ impl Element for Scrollbar {
}); });
} }
cx.on_mouse_event({ window.on_mouse_event({
let scroll_handle = self.scroll_handle.clone(); let scroll_handle = self.scroll_handle.clone();
let state = self.state.clone(); let state = self.state.clone();
let view_id = self.view_id; let view_id = self.view_id;
move |event: &MouseMoveEvent, _, cx| { move |event: &MouseMoveEvent, _, _, cx| {
// Update hovered state for scrollbar // Update hovered state for scrollbar
if bounds.contains(&event.position) { if bounds.contains(&event.position) {
if state.get().hovered_axis != Some(axis) { if state.get().hovered_axis != Some(axis) {
state.set(state.get().with_hovered(Some(axis))); state.set(state.get().with_hovered(Some(axis)));
cx.notify(Some(view_id)); cx.notify(view_id);
} }
} else if state.get().hovered_axis == Some(axis) } else if state.get().hovered_axis == Some(axis)
&& state.get().hovered_axis.is_some() && state.get().hovered_axis.is_some()
{ {
state.set(state.get().with_hovered(None)); state.set(state.get().with_hovered(None));
cx.notify(Some(view_id)); cx.notify(view_id);
} }
// Update hovered state for scrollbar thumb // Update hovered state for scrollbar thumb
if thumb_bounds.contains(&event.position) { if thumb_bounds.contains(&event.position) {
if state.get().hovered_on_thumb != Some(axis) { if state.get().hovered_on_thumb != Some(axis) {
state.set(state.get().with_hovered_on_thumb(Some(axis))); state.set(state.get().with_hovered_on_thumb(Some(axis)));
cx.notify(Some(view_id)); cx.notify(view_id);
} }
} else if state.get().hovered_on_thumb == Some(axis) { } else if state.get().hovered_on_thumb == Some(axis) {
state.set(state.get().with_hovered_on_thumb(None)); state.set(state.get().with_hovered_on_thumb(None));
cx.notify(Some(view_id)); cx.notify(view_id);
} }
// Move thumb position on dragging // Move thumb position on dragging
@@ -757,20 +760,24 @@ impl Element for Scrollbar {
) )
}; };
if (scroll_handle.offset().y - offset.y).abs() > px(1.)
|| (scroll_handle.offset().x - offset.x).abs() > px(1.)
{
scroll_handle.set_offset(offset); scroll_handle.set_offset(offset);
cx.notify(Some(view_id)); cx.notify(view_id);
}
} }
} }
}); });
cx.on_mouse_event({ window.on_mouse_event({
let view_id = self.view_id; let view_id = self.view_id;
let state = self.state.clone(); let state = self.state.clone();
move |_event: &MouseUpEvent, phase, cx| { move |_event: &MouseUpEvent, phase, _, cx| {
if phase.bubble() { if phase.bubble() {
state.set(state.get().with_unset_drag_pos()); state.set(state.get().with_unset_drag_pos());
cx.notify(Some(view_id)); cx.notify(view_id);
} }
} }
}); });

View File

@@ -31,7 +31,7 @@ impl Styled for Skeleton {
} }
impl RenderOnce for Skeleton { impl RenderOnce for Skeleton {
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement { fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
div().child( div().child(
self.base self.base
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)) .bg(cx.theme().base.step(cx, ColorScaleStep::THREE))

View File

@@ -2,7 +2,7 @@ use crate::{
scroll::{Scrollable, ScrollbarAxis}, scroll::{Scrollable, ScrollbarAxis},
theme::{scale::ColorScaleStep, ActiveTheme}, theme::{scale::ColorScaleStep, ActiveTheme},
}; };
use gpui::{div, px, Axis, Div, Element, ElementId, EntityId, Pixels, Styled, WindowContext}; use gpui::{div, px, App, Axis, Div, Element, ElementId, EntityId, Pixels, Styled, Window};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
@@ -38,7 +38,7 @@ pub trait StyledExt: Styled + Sized {
} }
/// Render a border with a width of 1px, color ring color /// Render a border with a width of 1px, color ring color
fn outline(self, cx: &WindowContext) -> Self { fn outline(self, _window: &Window, cx: &App) -> Self {
self.border_color(cx.theme().accent.step(cx, ColorScaleStep::NINE)) self.border_color(cx.theme().accent.step(cx, ColorScaleStep::NINE))
} }
@@ -63,7 +63,7 @@ pub trait StyledExt: Styled + Sized {
font_weight!(font_black, BLACK); font_weight!(font_black, BLACK);
/// Set as Popover style /// Set as Popover style
fn popover_style(self, cx: &mut WindowContext) -> Self { fn popover_style(self, cx: &mut App) -> Self {
self.bg(cx.theme().background) self.bg(cx.theme().background)
.border_1() .border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::SIX)) .border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))

View File

@@ -4,13 +4,13 @@ use crate::{
Disableable, Side, Sizable, Size, Disableable, Side, Sizable, Size,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, AnyElement, Element, div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, AnyElement, App, Element,
ElementId, GlobalElementId, InteractiveElement, IntoElement, LayoutId, ParentElement as _, ElementId, GlobalElementId, InteractiveElement, IntoElement, LayoutId, ParentElement as _,
SharedString, Styled as _, WindowContext, SharedString, Styled as _, Window,
}; };
use std::{cell::RefCell, rc::Rc, time::Duration}; use std::{cell::RefCell, rc::Rc, time::Duration};
type OnClick = Option<Rc<dyn Fn(&bool, &mut WindowContext)>>; type OnClick = Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>;
pub struct Switch { pub struct Switch {
id: ElementId, id: ElementId,
@@ -48,7 +48,7 @@ impl Switch {
pub fn on_click<F>(mut self, handler: F) -> Self pub fn on_click<F>(mut self, handler: F) -> Self
where where
F: Fn(&bool, &mut WindowContext) + 'static, F: Fn(&bool, &mut Window, &mut App) + 'static,
{ {
self.on_click = Some(Rc::new(handler)); self.on_click = Some(Rc::new(handler));
self self
@@ -99,9 +99,10 @@ impl Element for Switch {
fn request_layout( fn request_layout(
&mut self, &mut self,
global_id: Option<&GlobalElementId>, global_id: Option<&GlobalElementId>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) { ) -> (LayoutId, Self::RequestLayoutState) {
cx.with_element_state::<SwitchState, _>(global_id.unwrap(), move |state, cx| { window.with_element_state::<SwitchState, _>(global_id.unwrap(), move |state, window| {
let state = state.unwrap_or_default(); let state = state.unwrap_or_default();
let theme = cx.theme(); let theme = cx.theme();
@@ -131,7 +132,10 @@ impl Element for Switch {
}; };
let inset = px(2.); let inset = px(2.);
let mut element = h_flex() let mut element = div()
.flex()
.child(
h_flex()
.id(self.id.clone()) .id(self.id.clone())
.items_center() .items_center()
.gap_2() .gap_2()
@@ -151,11 +155,8 @@ impl Element for Switch {
.when(!self.disabled, |this| this.cursor_pointer()) .when(!self.disabled, |this| this.cursor_pointer())
.child( .child(
// Switch Toggle // Switch Toggle
div() div().rounded_full().bg(toggle_bg).size(bar_width).map(
.rounded_full() |this| {
.bg(toggle_bg)
.size(bar_width)
.map(|this| {
let prev_checked = state.prev_checked.clone(); let prev_checked = state.prev_checked.clone();
if !self.disabled if !self.disabled
&& prev_checked && prev_checked
@@ -176,7 +177,8 @@ impl Element for Switch {
), ),
Animation::new(dur), Animation::new(dur),
move |this, delta| { move |this, delta| {
let max_x = bg_width - bar_width - inset * 2; let max_x =
bg_width - bar_width - inset * 2;
let x = if checked { let x = if checked {
max_x * delta max_x * delta
} else { } else {
@@ -191,7 +193,8 @@ impl Element for Switch {
let x = if checked { max_x } else { px(0.) }; let x = if checked { max_x } else { px(0.) };
this.left(x).into_any_element() this.left(x).into_any_element()
} }
}), },
),
), ),
) )
.when_some(self.label.clone(), |this, label| { .when_some(self.label.clone(), |this, label| {
@@ -207,16 +210,17 @@ impl Element for Switch {
.filter(|_| !self.disabled), .filter(|_| !self.disabled),
|this, on_click| { |this, on_click| {
let prev_checked = state.prev_checked.clone(); let prev_checked = state.prev_checked.clone();
this.on_mouse_down(gpui::MouseButton::Left, move |_, cx| { this.on_mouse_down(gpui::MouseButton::Left, move |_, window, cx| {
cx.stop_propagation(); cx.stop_propagation();
*prev_checked.borrow_mut() = Some(checked); *prev_checked.borrow_mut() = Some(checked);
on_click(&!checked, cx); on_click(&!checked, window, cx);
}) })
}, },
),
) )
.into_any_element(); .into_any_element();
((element.request_layout(cx), element), state) ((element.request_layout(window, cx), element), state)
}) })
} }
@@ -225,9 +229,10 @@ impl Element for Switch {
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
_: gpui::Bounds<gpui::Pixels>, _: gpui::Bounds<gpui::Pixels>,
element: &mut Self::RequestLayoutState, element: &mut Self::RequestLayoutState,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
element.prepaint(cx); element.prepaint(window, cx);
} }
fn paint( fn paint(
@@ -236,8 +241,9 @@ impl Element for Switch {
_: gpui::Bounds<gpui::Pixels>, _: gpui::Bounds<gpui::Pixels>,
element: &mut Self::RequestLayoutState, element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState, _: &mut Self::PrepaintState,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
element.paint(cx) element.paint(window, cx)
} }
} }

View File

@@ -83,7 +83,7 @@ impl Styled for Tab {
} }
impl RenderOnce for Tab { impl RenderOnce for Tab {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let (text_color, bg_color, hover_bg_color) = match (self.selected, self.disabled) { let (text_color, bg_color, hover_bg_color) = match (self.selected, self.disabled) {
(true, false) => ( (true, false) => (
cx.theme().base.step(cx, ColorScaleStep::TWELVE), cx.theme().base.step(cx, ColorScaleStep::TWELVE),

View File

@@ -1,8 +1,8 @@
use crate::h_flex; use crate::h_flex;
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, AnyElement, Div, ElementId, InteractiveElement, div, prelude::FluentBuilder as _, px, AnyElement, App, Div, ElementId, InteractiveElement,
IntoElement, ParentElement, RenderOnce, ScrollHandle, StatefulInteractiveElement as _, Styled, IntoElement, ParentElement, RenderOnce, ScrollHandle, StatefulInteractiveElement as _, Styled,
WindowContext, Window,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
@@ -60,7 +60,7 @@ impl Styled for TabBar {
} }
impl RenderOnce for TabBar { impl RenderOnce for TabBar {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
self.base self.base
.id(self.id) .id(self.id)
.group("tab-bar") .group("tab-bar")

View File

@@ -1,15 +1,27 @@
use crate::scroll::ScrollbarShow; use crate::scroll::ScrollbarShow;
use colors::{default_color_scales, hsl}; use colors::{default_color_scales, hsl};
use gpui::{ use gpui::{App, Global, Hsla, SharedString, Window, WindowAppearance};
AppContext, Global, Hsla, ModelContext, SharedString, ViewContext, WindowAppearance,
WindowContext,
};
use scale::ColorScaleSet; use scale::ColorScaleSet;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
pub mod colors; pub mod colors;
pub mod scale; pub mod scale;
pub fn init(cx: &mut App) {
Theme::sync_system_appearance(None, cx)
}
pub trait ActiveTheme {
fn theme(&self) -> &Theme;
}
impl ActiveTheme for App {
#[inline]
fn theme(&self) -> &Theme {
Theme::global(self)
}
}
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct SystemColors { pub struct SystemColors {
pub background: Hsla, pub background: Hsla,
@@ -47,38 +59,6 @@ impl SystemColors {
} }
} }
pub trait ActiveTheme {
fn theme(&self) -> &Theme;
}
impl ActiveTheme for AppContext {
fn theme(&self) -> &Theme {
Theme::global(self)
}
}
impl<V> ActiveTheme for ViewContext<'_, V> {
fn theme(&self) -> &Theme {
self.deref().theme()
}
}
impl<V> ActiveTheme for ModelContext<'_, V> {
fn theme(&self) -> &Theme {
self.deref().theme()
}
}
impl ActiveTheme for WindowContext<'_> {
fn theme(&self) -> &Theme {
self.deref().theme()
}
}
pub fn init(cx: &mut AppContext) {
Theme::sync_system_appearance(cx)
}
#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq)] #[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq)]
pub enum Appearance { pub enum Appearance {
#[default] #[default]
@@ -126,32 +106,34 @@ impl Global for Theme {}
impl Theme { impl Theme {
/// Returns the global theme reference /// Returns the global theme reference
pub fn global(cx: &AppContext) -> &Theme { pub fn global(cx: &App) -> &Theme {
cx.global::<Theme>() cx.global::<Theme>()
} }
/// Returns the global theme mutable reference /// Returns the global theme mutable reference
pub fn global_mut(cx: &mut AppContext) -> &mut Theme { pub fn global_mut(cx: &mut App) -> &mut Theme {
cx.global_mut::<Theme>() cx.global_mut::<Theme>()
} }
/// Sync the theme with the system appearance /// Sync the theme with the system appearance
pub fn sync_system_appearance(cx: &mut AppContext) { pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) {
match cx.window_appearance() { match cx.window_appearance() {
WindowAppearance::Dark | WindowAppearance::VibrantDark => { WindowAppearance::Dark | WindowAppearance::VibrantDark => {
Self::change(Appearance::Dark, cx) Self::change(Appearance::Dark, window, cx)
} }
WindowAppearance::Light | WindowAppearance::VibrantLight => { WindowAppearance::Light | WindowAppearance::VibrantLight => {
Self::change(Appearance::Light, cx) Self::change(Appearance::Light, window, cx)
} }
} }
} }
pub fn change(mode: Appearance, cx: &mut AppContext) { pub fn change(mode: Appearance, window: Option<&mut Window>, cx: &mut App) {
let theme = Theme::new(mode); let theme = Theme::new(mode);
cx.set_global(theme); cx.set_global(theme);
cx.refresh();
if let Some(window) = window {
window.refresh();
}
} }
} }

View File

@@ -1,5 +1,5 @@
use crate::theme::{ActiveTheme, Appearance}; use crate::theme::{ActiveTheme, Appearance};
use gpui::{AppContext, Hsla, SharedString}; use gpui::{App, Hsla, SharedString};
/// A collection of colors that are used to style the UI. /// A collection of colors that are used to style the UI.
/// ///
@@ -279,21 +279,21 @@ impl ColorScaleSet {
&self.dark_alpha &self.dark_alpha
} }
pub fn step(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { pub fn step(&self, cx: &App, step: ColorScaleStep) -> Hsla {
match cx.theme().appearance { match cx.theme().appearance {
Appearance::Light => self.light().step(step), Appearance::Light => self.light().step(step),
Appearance::Dark => self.dark().step(step), Appearance::Dark => self.dark().step(step),
} }
} }
pub fn step_alpha(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { pub fn step_alpha(&self, cx: &App, step: ColorScaleStep) -> Hsla {
match cx.theme().appearance { match cx.theme().appearance {
Appearance::Light => self.light_alpha.step(step), Appearance::Light => self.light_alpha.step(step),
Appearance::Dark => self.dark_alpha.step(step), Appearance::Dark => self.dark_alpha.step(step),
} }
} }
pub fn darken(&self, cx: &AppContext) -> Hsla { pub fn darken(&self, cx: &App) -> Hsla {
match cx.theme().appearance { match cx.theme().appearance {
Appearance::Light => self.light.step_12(), Appearance::Light => self.light.step_12(),
Appearance::Dark => self.dark.step_1(), Appearance::Dark => self.dark.step_1(),

View File

@@ -4,9 +4,9 @@ use crate::{
Icon, IconName, InteractiveElementExt as _, Sizable as _, Icon, IconName, InteractiveElementExt as _, Sizable as _,
}; };
use gpui::{ use gpui::{
black, div, prelude::FluentBuilder as _, px, relative, white, AnyElement, ClickEvent, Div, black, div, prelude::FluentBuilder as _, px, relative, white, AnyElement, App, ClickEvent, Div,
Element, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, Element, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels,
RenderOnce, Rgba, Stateful, StatefulInteractiveElement as _, Style, Styled, WindowContext, RenderOnce, Rgba, Stateful, StatefulInteractiveElement as _, Style, Styled, Window,
}; };
use std::rc::Rc; use std::rc::Rc;
@@ -17,7 +17,7 @@ const TITLE_BAR_LEFT_PADDING: Pixels = px(80.);
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
const TITLE_BAR_LEFT_PADDING: Pixels = px(12.); const TITLE_BAR_LEFT_PADDING: Pixels = px(12.);
type OnCloseWindow = Option<Rc<Box<dyn Fn(&ClickEvent, &mut WindowContext)>>>; type OnCloseWindow = Option<Rc<Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>>>;
/// TitleBar used to customize the appearance of the title bar. /// TitleBar used to customize the appearance of the title bar.
/// ///
@@ -38,11 +38,11 @@ impl TitleBar {
} }
} }
/// Add custom for close window event, default is None, then click X button will call `cx.remove_window()`. /// Add custom for close window event, default is None, then click X button will call `window.remove_window()`.
/// Linux only, this will do nothing on other platforms. /// Linux only, this will do nothing on other platforms.
pub fn on_close_window( pub fn on_close_window(
mut self, mut self,
f: impl Fn(&ClickEvent, &mut WindowContext) + 'static, f: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self { ) -> Self {
if cfg!(target_os = "linux") { if cfg!(target_os = "linux") {
self.on_close_window = Some(Rc::new(Box::new(f))); self.on_close_window = Some(Rc::new(Box::new(f)));
@@ -108,7 +108,7 @@ impl Control {
matches!(self, Self::Close { .. }) matches!(self, Self::Close { .. })
} }
fn fg(&self, cx: &WindowContext) -> Hsla { fn fg(&self, _window: &Window, cx: &App) -> Hsla {
if cx.theme().appearance.is_dark() { if cx.theme().appearance.is_dark() {
white() white()
} else { } else {
@@ -116,7 +116,7 @@ impl Control {
} }
} }
fn hover_fg(&self, cx: &WindowContext) -> Hsla { fn hover_fg(&self, _window: &Window, cx: &App) -> Hsla {
if self.is_close() || cx.theme().appearance.is_dark() { if self.is_close() || cx.theme().appearance.is_dark() {
white() white()
} else { } else {
@@ -124,7 +124,7 @@ impl Control {
} }
} }
fn hover_bg(&self, cx: &WindowContext) -> Rgba { fn hover_bg(&self, _window: &Window, cx: &App) -> Rgba {
if self.is_close() { if self.is_close() {
Rgba { Rgba {
r: 232.0 / 255.0, r: 232.0 / 255.0,
@@ -151,10 +151,10 @@ impl Control {
} }
impl RenderOnce for Control { impl RenderOnce for Control {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let fg = self.fg(cx); let fg = self.fg(window, cx);
let hover_fg = self.hover_fg(cx); let hover_fg = self.hover_fg(window, cx);
let hover_bg = self.hover_bg(cx); let hover_bg = self.hover_bg(window, cx);
let icon = self.clone(); let icon = self.clone();
let is_linux = cfg!(target_os = "linux"); let is_linux = cfg!(target_os = "linux");
let on_close_window = match &icon { let on_close_window = match &icon {
@@ -173,19 +173,19 @@ impl RenderOnce for Control {
.items_center() .items_center()
.text_color(fg) .text_color(fg)
.when(is_linux, |this| { .when(is_linux, |this| {
this.on_mouse_down(MouseButton::Left, move |_, cx| { this.on_mouse_down(MouseButton::Left, move |_, window, cx| {
cx.prevent_default(); window.prevent_default();
cx.stop_propagation(); cx.stop_propagation();
}) })
.on_click(move |_, cx| match icon { .on_click(move |_, window, cx| match icon {
Self::Minimize => cx.minimize_window(), Self::Minimize => window.minimize_window(),
Self::Restore => cx.zoom_window(), Self::Restore => window.zoom_window(),
Self::Maximize => cx.zoom_window(), Self::Maximize => window.zoom_window(),
Self::Close { .. } => { Self::Close { .. } => {
if let Some(f) = on_close_window.clone() { if let Some(f) = on_close_window.clone() {
f(&ClickEvent::default(), cx); f(&ClickEvent::default(), window, cx);
} else { } else {
cx.remove_window(); window.remove_window();
} }
} }
}) })
@@ -202,7 +202,7 @@ struct WindowControls {
} }
impl RenderOnce for WindowControls { impl RenderOnce for WindowControls {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
if cfg!(target_os = "macos") { if cfg!(target_os = "macos") {
return div().id("window-controls"); return div().id("window-controls");
} }
@@ -218,7 +218,7 @@ impl RenderOnce for WindowControls {
.content_stretch() .content_stretch()
.h_full() .h_full()
.child(Control::minimize()) .child(Control::minimize())
.child(if cx.is_maximized() { .child(if window.is_maximized() {
Control::restore() Control::restore()
} else { } else {
Control::maximize() Control::maximize()
@@ -241,7 +241,7 @@ impl ParentElement for TitleBar {
} }
impl RenderOnce for TitleBar { impl RenderOnce for TitleBar {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let is_linux = cfg!(target_os = "linux"); let is_linux = cfg!(target_os = "linux");
div().flex_shrink_0().child( div().flex_shrink_0().child(
@@ -252,8 +252,8 @@ impl RenderOnce for TitleBar {
.justify_between() .justify_between()
.h(HEIGHT) .h(HEIGHT)
.bg(cx.theme().base.step(cx, ColorScaleStep::ONE)) .bg(cx.theme().base.step(cx, ColorScaleStep::ONE))
.when(cx.is_fullscreen(), |this| this.pl(px(12.))) .when(window.is_fullscreen(), |this| this.pl(px(12.)))
.on_double_click(|_, cx| cx.zoom_window()) .on_double_click(|_, window, _cx| window.zoom_window())
.child( .child(
h_flex() h_flex()
.h_full() .h_full()
@@ -302,7 +302,8 @@ impl Element for TitleBarElement {
fn request_layout( fn request_layout(
&mut self, &mut self,
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) { ) -> (gpui::LayoutId, Self::RequestLayoutState) {
let style = Style { let style = Style {
flex_grow: 1.0, flex_grow: 1.0,
@@ -314,7 +315,7 @@ impl Element for TitleBarElement {
..Default::default() ..Default::default()
}; };
let id = cx.request_layout(style, []); let id = window.request_layout(style, [], cx);
(id, ()) (id, ())
} }
@@ -324,7 +325,8 @@ impl Element for TitleBarElement {
_: Option<&gpui::GlobalElementId>, _: Option<&gpui::GlobalElementId>,
_: gpui::Bounds<Pixels>, _: gpui::Bounds<Pixels>,
_: &mut Self::RequestLayoutState, _: &mut Self::RequestLayoutState,
_: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Self::PrepaintState { ) -> Self::PrepaintState {
} }
@@ -334,20 +336,25 @@ impl Element for TitleBarElement {
bounds: gpui::Bounds<Pixels>, bounds: gpui::Bounds<Pixels>,
_: &mut Self::RequestLayoutState, _: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState, _: &mut Self::PrepaintState,
cx: &mut WindowContext, window: &mut Window,
_cx: &mut App,
) { ) {
use gpui::{MouseButton, MouseMoveEvent, MouseUpEvent}; use gpui::{MouseButton, MouseMoveEvent, MouseUpEvent};
cx.on_mouse_event(move |ev: &MouseMoveEvent, _, cx: &mut WindowContext| { window.on_mouse_event(
move |ev: &MouseMoveEvent, _, window: &mut Window, _cx: &mut App| {
if bounds.contains(&ev.position) && ev.pressed_button == Some(MouseButton::Left) { if bounds.contains(&ev.position) && ev.pressed_button == Some(MouseButton::Left) {
cx.start_window_move(); window.start_window_move();
} }
}); },
);
cx.on_mouse_event(move |ev: &MouseUpEvent, _, cx: &mut WindowContext| { window.on_mouse_event(
move |ev: &MouseUpEvent, _, window: &mut Window, _cx: &mut App| {
if ev.button == MouseButton::Left { if ev.button == MouseButton::Left {
cx.show_window_menu(ev.position); window.show_window_menu(ev.position);
} }
}); },
);
} }
} }

View File

@@ -1,7 +1,7 @@
use crate::theme::{scale::ColorScaleStep, ActiveTheme}; use crate::theme::{scale::ColorScaleStep, ActiveTheme};
use gpui::{ use gpui::{
div, px, IntoElement, ParentElement, Render, SharedString, Styled, View, ViewContext, div, px, App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString,
VisualContext, WindowContext, Styled, Window,
}; };
pub struct Tooltip { pub struct Tooltip {
@@ -9,13 +9,13 @@ pub struct Tooltip {
} }
impl Tooltip { impl Tooltip {
pub fn new(text: impl Into<SharedString>, cx: &mut WindowContext) -> View<Self> { pub fn new(text: impl Into<SharedString>, _window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new_view(|_| Self { text: text.into() }) cx.new(|_| Self { text: text.into() })
} }
} }
impl Render for Tooltip { impl Render for Tooltip {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div().child( div().child(
// Wrap in a child, to ensure the left margin is applied to the tooltip // Wrap in a child, to ensure the left margin is applied to the tooltip
div() div()

View File

@@ -1,8 +1,8 @@
use crate::theme::ActiveTheme; use crate::theme::ActiveTheme;
use gpui::{ use gpui::{
canvas, div, point, prelude::FluentBuilder as _, px, AnyElement, Bounds, CursorStyle, canvas, div, point, prelude::FluentBuilder as _, px, AnyElement, App, Bounds, CursorStyle,
Decorations, Edges, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Decorations, Edges, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement,
Pixels, Point, RenderOnce, ResizeEdge, Size, Styled as _, WindowContext, Pixels, Point, RenderOnce, ResizeEdge, Size, Styled as _, Window,
}; };
pub(crate) const BORDER_SIZE: Pixels = Pixels(1.0); pub(crate) const BORDER_SIZE: Pixels = Pixels(1.0);
@@ -24,8 +24,8 @@ pub struct WindowBorder {
} }
/// Get the window paddings. /// Get the window paddings.
pub fn window_paddings(cx: &WindowContext) -> Edges<Pixels> { pub fn window_paddings(window: &Window, _cx: &App) -> Edges<Pixels> {
match cx.window_decorations() { match window.window_decorations() {
Decorations::Server => Edges::all(px(0.0)), Decorations::Server => Edges::all(px(0.0)),
Decorations::Client { tiling } => { Decorations::Client { tiling } => {
let mut paddings = Edges::all(SHADOW_SIZE); let mut paddings = Edges::all(SHADOW_SIZE);
@@ -61,9 +61,9 @@ impl ParentElement for WindowBorder {
} }
impl RenderOnce for WindowBorder { impl RenderOnce for WindowBorder {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let decorations = cx.window_decorations(); let decorations = window.window_decorations();
cx.set_client_inset(SHADOW_SIZE); window.set_client_inset(SHADOW_SIZE);
div() div()
.id("window-backdrop") .id("window-backdrop")
@@ -74,22 +74,22 @@ impl RenderOnce for WindowBorder {
.bg(gpui::transparent_black()) .bg(gpui::transparent_black())
.child( .child(
canvas( canvas(
|_bounds, cx| { |_bounds, window, _cx| {
cx.insert_hitbox( window.insert_hitbox(
Bounds::new( Bounds::new(
point(px(0.0), px(0.0)), point(px(0.0), px(0.0)),
cx.window_bounds().get_bounds().size, window.window_bounds().get_bounds().size,
), ),
false, false,
) )
}, },
move |_bounds, hitbox, cx| { move |_bounds, hitbox, window, _cx| {
let mouse = cx.mouse_position(); let mouse = window.mouse_position();
let size = cx.window_bounds().get_bounds().size; let size = window.window_bounds().get_bounds().size;
let Some(edge) = resize_edge(mouse, SHADOW_SIZE, size) else { let Some(edge) = resize_edge(mouse, SHADOW_SIZE, size) else {
return; return;
}; };
cx.set_cursor_style( window.set_cursor_style(
match edge { match edge {
ResizeEdge::Top | ResizeEdge::Bottom => { ResizeEdge::Top | ResizeEdge::Bottom => {
CursorStyle::ResizeUpDown CursorStyle::ResizeUpDown
@@ -121,13 +121,13 @@ impl RenderOnce for WindowBorder {
.when(!tiling.bottom, |div| div.pb(SHADOW_SIZE)) .when(!tiling.bottom, |div| div.pb(SHADOW_SIZE))
.when(!tiling.left, |div| div.pl(SHADOW_SIZE)) .when(!tiling.left, |div| div.pl(SHADOW_SIZE))
.when(!tiling.right, |div| div.pr(SHADOW_SIZE)) .when(!tiling.right, |div| div.pr(SHADOW_SIZE))
.on_mouse_move(|_e, cx| cx.refresh()) .on_mouse_move(|_e, window, _cx| window.refresh())
.on_mouse_down(MouseButton::Left, move |_, cx| { .on_mouse_down(MouseButton::Left, move |_, window, _cx| {
let size = cx.window_bounds().get_bounds().size; let size = window.window_bounds().get_bounds().size;
let pos = cx.mouse_position(); let pos = window.mouse_position();
if let Some(edge) = resize_edge(pos, SHADOW_SIZE, size) { if let Some(edge) = resize_edge(pos, SHADOW_SIZE, size) {
cx.start_window_resize(edge) window.start_window_resize(edge)
}; };
}), }),
}) })
@@ -162,7 +162,7 @@ impl RenderOnce for WindowBorder {
}]) }])
}), }),
}) })
.on_mouse_move(|_e, cx| { .on_mouse_move(|_e, _window, cx| {
cx.stop_propagation(); cx.stop_propagation();
}) })
.bg(gpui::transparent_black()) .bg(gpui::transparent_black())