Allow modals to override their dismissal

Co-Authored-By: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
Joseph T. Lyons 2023-12-08 15:26:06 -05:00
parent 1c850f495c
commit 113c7287df
13 changed files with 118 additions and 46 deletions

View file

@ -13,6 +13,7 @@ use picker::{Picker, PickerDelegate};
use std::sync::Arc; use std::sync::Arc;
use ui::prelude::*; use ui::prelude::*;
use util::TryFutureExt; use util::TryFutureExt;
use workspace::ModalView;
actions!( actions!(
SelectNextControl, SelectNextControl,
@ -140,6 +141,7 @@ impl ChannelModal {
} }
impl EventEmitter<DismissEvent> for ChannelModal {} impl EventEmitter<DismissEvent> for ChannelModal {}
impl ModalView for ChannelModal {}
impl FocusableView for ChannelModal { impl FocusableView for ChannelModal {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {

View file

@ -9,6 +9,7 @@ use std::sync::Arc;
use theme::ActiveTheme as _; use theme::ActiveTheme as _;
use ui::prelude::*; use ui::prelude::*;
use util::{ResultExt as _, TryFutureExt}; use util::{ResultExt as _, TryFutureExt};
use workspace::ModalView;
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
//Picker::<ContactFinderDelegate>::init(cx); //Picker::<ContactFinderDelegate>::init(cx);
@ -95,6 +96,7 @@ pub struct ContactFinderDelegate {
} }
impl EventEmitter<DismissEvent> for ContactFinder {} impl EventEmitter<DismissEvent> for ContactFinder {}
impl ModalView for ContactFinder {}
impl FocusableView for ContactFinder { impl FocusableView for ContactFinder {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &AppContext) -> FocusHandle {

View file

@ -16,7 +16,7 @@ use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt, ResultExt,
}; };
use workspace::Workspace; use workspace::{ModalView, Workspace};
use zed_actions::OpenZedURL; use zed_actions::OpenZedURL;
actions!(Toggle); actions!(Toggle);
@ -26,6 +26,8 @@ pub fn init(cx: &mut AppContext) {
cx.observe_new_views(CommandPalette::register).detach(); cx.observe_new_views(CommandPalette::register).detach();
} }
impl ModalView for CommandPalette {}
pub struct CommandPalette { pub struct CommandPalette {
picker: View<Picker<CommandPaletteDelegate>>, picker: View<Picker<CommandPaletteDelegate>>,
} }

View file

@ -4,7 +4,7 @@ use anyhow::bail;
use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use editor::{Editor, EditorEvent}; use editor::{Editor, EditorEvent};
use futures::AsyncReadExt; use futures::{AsyncReadExt, Future};
use gpui::{ use gpui::{
div, rems, serde_json, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, div, rems, serde_json, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
Model, PromptLevel, Render, Task, View, ViewContext, Model, PromptLevel, Render, Task, View, ViewContext,
@ -16,7 +16,7 @@ use regex::Regex;
use serde_derive::Serialize; use serde_derive::Serialize;
use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip}; use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip};
use util::ResultExt; use util::ResultExt;
use workspace::Workspace; use workspace::{ModalView, Workspace};
use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedCommunityRepo}; use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedCommunityRepo};
@ -52,6 +52,13 @@ impl FocusableView for FeedbackModal {
} }
impl EventEmitter<DismissEvent> for FeedbackModal {} impl EventEmitter<DismissEvent> for FeedbackModal {}
impl ModalView for FeedbackModal {
fn dismiss(&mut self, cx: &mut ViewContext<Self>) -> Task<bool> {
let prompt = Self::prompt_dismiss(cx);
cx.spawn(|_, _| prompt)
}
}
impl FeedbackModal { impl FeedbackModal {
pub fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) { pub fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
let _handle = cx.view().downgrade(); let _handle = cx.view().downgrade();
@ -105,7 +112,7 @@ impl FeedbackModal {
let feedback_editor = cx.build_view(|cx| { let feedback_editor = cx.build_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx); let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx);
editor.set_placeholder_text( editor.set_placeholder_text(
"You can use markdown to add links or organize feedback.", "You can use markdown to organize your feedback wiht add code and links, or organize feedback.",
cx, cx,
); );
// editor.set_show_gutter(false, cx); // editor.set_show_gutter(false, cx);
@ -242,7 +249,27 @@ impl FeedbackModal {
// Close immediately if no text in field // Close immediately if no text in field
// Ask to close if text in the field // Ask to close if text in the field
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) { fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent); Self::dismiss_event(cx)
}
fn dismiss_event(cx: &mut ViewContext<Self>) {
let dismiss = Self::prompt_dismiss(cx);
cx.spawn(|this, mut cx| async move {
if dismiss.await {
this.update(&mut cx, |_, cx| cx.emit(DismissEvent)).ok();
}
})
.detach()
}
fn prompt_dismiss(cx: &mut ViewContext<Self>) -> impl Future<Output = bool> {
let answer = cx.prompt(PromptLevel::Info, "Discard feedback?", &["Yes", "No"]);
async {
let answer = answer.await.ok();
answer == Some(0)
}
} }
} }
@ -267,20 +294,7 @@ impl Render for FeedbackModal {
} else { } else {
"Submit" "Submit"
}; };
let dismiss = cx.listener(|_, _, cx| {
cx.emit(DismissEvent);
});
// TODO: get the "are you sure you want to dismiss?" prompt here working
let dismiss_prompt = cx.listener(|_, _, _| {
// let answer = cx.prompt(PromptLevel::Info, "Exit feedback?", &["Yes", "No"]);
// cx.spawn(|_, _| async move {
// let answer = answer.await.ok();
// if answer == Some(0) {
// cx.emit(DismissEvent);
// }
// })
// .detach();
});
let open_community_repo = let open_community_repo =
cx.listener(|_, _, cx| cx.dispatch_action(Box::new(OpenZedCommunityRepo))); cx.listener(|_, _, cx| cx.dispatch_action(Box::new(OpenZedCommunityRepo)));
@ -368,9 +382,13 @@ impl Render for FeedbackModal {
// TODO: Will require somehow overriding the modal dismal default behavior // TODO: Will require somehow overriding the modal dismal default behavior
.map(|this| { .map(|this| {
if has_feedback { if has_feedback {
this.on_click(dismiss_prompt) this.on_click(cx.listener(|_, _, cx| {
Self::dismiss_event(cx)
}))
} else { } else {
this.on_click(dismiss) this.on_click(cx.listener(|_, _, cx| {
cx.emit(DismissEvent);
}))
} }
}), }),
) )

View file

@ -17,10 +17,12 @@ use std::{
use text::Point; use text::Point;
use ui::{prelude::*, HighlightedLabel, ListItem}; use ui::{prelude::*, HighlightedLabel, ListItem};
use util::{paths::PathLikeWithPosition, post_inc, ResultExt}; use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
use workspace::Workspace; use workspace::{ModalView, Workspace};
actions!(Toggle); actions!(Toggle);
impl ModalView for FileFinder {}
pub struct FileFinder { pub struct FileFinder {
picker: View<Picker<FileFinderDelegate>>, picker: View<Picker<FileFinderDelegate>>,
} }

View file

@ -8,6 +8,7 @@ use text::{Bias, Point};
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::{h_stack, prelude::*, v_stack, Label}; use ui::{h_stack, prelude::*, v_stack, Label};
use util::paths::FILE_ROW_COLUMN_DELIMITER; use util::paths::FILE_ROW_COLUMN_DELIMITER;
use workspace::ModalView;
actions!(Toggle); actions!(Toggle);
@ -23,6 +24,8 @@ pub struct GoToLine {
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
impl ModalView for GoToLine {}
impl FocusableView for GoToLine { impl FocusableView for GoToLine {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.line_editor.focus_handle(cx) self.line_editor.focus_handle(cx)

View file

@ -14,7 +14,7 @@ use project::Project;
use std::sync::Arc; use std::sync::Arc;
use ui::{prelude::*, HighlightedLabel, ListItem}; use ui::{prelude::*, HighlightedLabel, ListItem};
use util::ResultExt; use util::ResultExt;
use workspace::Workspace; use workspace::{ModalView, Workspace};
actions!(Toggle); actions!(Toggle);
@ -81,6 +81,7 @@ impl FocusableView for LanguageSelector {
} }
impl EventEmitter<DismissEvent> for LanguageSelector {} impl EventEmitter<DismissEvent> for LanguageSelector {}
impl ModalView for LanguageSelector {}
pub struct LanguageSelectorDelegate { pub struct LanguageSelectorDelegate {
language_selector: WeakView<LanguageSelector>, language_selector: WeakView<LanguageSelector>,

View file

@ -20,7 +20,7 @@ use std::{
use theme::{color_alpha, ActiveTheme, ThemeSettings}; use theme::{color_alpha, ActiveTheme, ThemeSettings};
use ui::{prelude::*, ListItem}; use ui::{prelude::*, ListItem};
use util::ResultExt; use util::ResultExt;
use workspace::Workspace; use workspace::{ModalView, Workspace};
actions!(Toggle); actions!(Toggle);
@ -57,6 +57,7 @@ impl FocusableView for OutlineView {
} }
impl EventEmitter<DismissEvent> for OutlineView {} impl EventEmitter<DismissEvent> for OutlineView {}
impl ModalView for OutlineView {}
impl Render for OutlineView { impl Render for OutlineView {
type Element = Div; type Element = Div;

View file

@ -13,8 +13,8 @@ use std::sync::Arc;
use ui::{prelude::*, ListItem}; use ui::{prelude::*, ListItem};
use util::paths::PathExt; use util::paths::PathExt;
use workspace::{ use workspace::{
notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation, notifications::simple_message_notification::MessageNotification, ModalView, Workspace,
WORKSPACE_DB, WorkspaceLocation, WORKSPACE_DB,
}; };
pub use projects::OpenRecent; pub use projects::OpenRecent;
@ -27,6 +27,8 @@ pub struct RecentProjects {
picker: View<Picker<RecentProjectsDelegate>>, picker: View<Picker<RecentProjectsDelegate>>,
} }
impl ModalView for RecentProjects {}
impl RecentProjects { impl RecentProjects {
fn new(delegate: RecentProjectsDelegate, cx: &mut ViewContext<Self>) -> Self { fn new(delegate: RecentProjectsDelegate, cx: &mut ViewContext<Self>) -> Self {
Self { Self {

View file

@ -11,7 +11,7 @@ use std::sync::Arc;
use theme::{Theme, ThemeRegistry, ThemeSettings}; use theme::{Theme, ThemeRegistry, ThemeSettings};
use ui::{prelude::*, v_stack, ListItem}; use ui::{prelude::*, v_stack, ListItem};
use util::ResultExt; use util::ResultExt;
use workspace::{ui::HighlightedLabel, Workspace}; use workspace::{ui::HighlightedLabel, ModalView, Workspace};
actions!(Toggle, Reload); actions!(Toggle, Reload);
@ -52,6 +52,8 @@ pub fn reload(cx: &mut AppContext) {
} }
} }
impl ModalView for ThemeSelector {}
pub struct ThemeSelector { pub struct ThemeSelector {
picker: View<Picker<ThemeSelectorDelegate>>, picker: View<Picker<ThemeSelectorDelegate>>,
} }

View file

@ -10,7 +10,7 @@ use settings::{update_settings_file, Settings};
use std::sync::Arc; use std::sync::Arc;
use ui::{prelude::*, ListItem}; use ui::{prelude::*, ListItem};
use util::ResultExt; use util::ResultExt;
use workspace::{ui::HighlightedLabel, Workspace}; use workspace::{ui::HighlightedLabel, ModalView, Workspace};
actions!(ToggleBaseKeymapSelector); actions!(ToggleBaseKeymapSelector);
@ -47,6 +47,7 @@ impl FocusableView for BaseKeymapSelector {
} }
impl EventEmitter<DismissEvent> for BaseKeymapSelector {} impl EventEmitter<DismissEvent> for BaseKeymapSelector {}
impl ModalView for BaseKeymapSelector {}
impl BaseKeymapSelector { impl BaseKeymapSelector {
pub fn new( pub fn new(

View file

@ -1,11 +1,33 @@
use futures::FutureExt;
use gpui::{ use gpui::{
div, prelude::*, px, AnyView, Div, FocusHandle, ManagedView, Render, Subscription, View, div, prelude::*, px, AnyView, Div, FocusHandle, ManagedView, Render, Subscription, Task, View,
ViewContext, ViewContext, WindowContext,
}; };
use ui::{h_stack, v_stack}; use ui::{h_stack, v_stack};
pub trait ModalView: ManagedView {
fn dismiss(&mut self, cx: &mut ViewContext<Self>) -> Task<bool> {
Task::ready(true)
}
}
trait ModalViewHandle {
fn should_dismiss(&mut self, cx: &mut WindowContext) -> Task<bool>;
fn view(&self) -> AnyView;
}
impl<V: ModalView> ModalViewHandle for View<V> {
fn should_dismiss(&mut self, cx: &mut WindowContext) -> Task<bool> {
self.update(cx, |this, cx| this.dismiss(cx))
}
fn view(&self) -> AnyView {
self.clone().into()
}
}
pub struct ActiveModal { pub struct ActiveModal {
modal: AnyView, modal: Box<dyn ModalViewHandle>,
subscription: Subscription, subscription: Subscription,
previous_focus_handle: Option<FocusHandle>, previous_focus_handle: Option<FocusHandle>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
@ -22,11 +44,11 @@ impl ModalLayer {
pub fn toggle_modal<V, B>(&mut self, cx: &mut ViewContext<Self>, build_view: B) pub fn toggle_modal<V, B>(&mut self, cx: &mut ViewContext<Self>, build_view: B)
where where
V: ManagedView, V: ModalView,
B: FnOnce(&mut ViewContext<V>) -> V, B: FnOnce(&mut ViewContext<V>) -> V,
{ {
if let Some(active_modal) = &self.active_modal { if let Some(active_modal) = &self.active_modal {
let is_close = active_modal.modal.clone().downcast::<V>().is_ok(); let is_close = active_modal.modal.view().downcast::<V>().is_ok();
self.hide_modal(cx); self.hide_modal(cx);
if is_close { if is_close {
return; return;
@ -38,10 +60,10 @@ impl ModalLayer {
pub fn show_modal<V>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Self>) pub fn show_modal<V>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Self>)
where where
V: ManagedView, V: ModalView,
{ {
self.active_modal = Some(ActiveModal { self.active_modal = Some(ActiveModal {
modal: new_modal.clone().into(), modal: Box::new(new_modal.clone()),
subscription: cx.subscribe(&new_modal, |this, modal, e, cx| this.hide_modal(cx)), subscription: cx.subscribe(&new_modal, |this, modal, e, cx| this.hide_modal(cx)),
previous_focus_handle: cx.focused(), previous_focus_handle: cx.focused(),
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
@ -51,23 +73,37 @@ impl ModalLayer {
} }
pub fn hide_modal(&mut self, cx: &mut ViewContext<Self>) { pub fn hide_modal(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_modal) = self.active_modal.take() { let Some(active_modal) = self.active_modal.as_mut() else {
return;
};
let dismiss = active_modal.modal.should_dismiss(cx);
cx.spawn(|this, mut cx| async move {
if dismiss.await {
this.update(&mut cx, |this, cx| {
if let Some(active_modal) = this.active_modal.take() {
if let Some(previous_focus) = active_modal.previous_focus_handle { if let Some(previous_focus) = active_modal.previous_focus_handle {
if active_modal.focus_handle.contains_focused(cx) { if active_modal.focus_handle.contains_focused(cx) {
previous_focus.focus(cx); previous_focus.focus(cx);
} }
} }
}
cx.notify(); cx.notify();
} }
})
.ok();
}
})
.shared()
.detach();
}
pub fn active_modal<V>(&self) -> Option<View<V>> pub fn active_modal<V>(&self) -> Option<View<V>>
where where
V: 'static, V: 'static,
{ {
let active_modal = self.active_modal.as_ref()?; let active_modal = self.active_modal.as_ref()?;
active_modal.modal.clone().downcast::<V>().ok() active_modal.modal.view().downcast::<V>().ok()
} }
} }
@ -98,7 +134,7 @@ impl Render for ModalLayer {
.on_mouse_down_out(cx.listener(|this, _, cx| { .on_mouse_down_out(cx.listener(|this, _, cx| {
this.hide_modal(cx); this.hide_modal(cx);
})) }))
.child(active_modal.modal.clone()), .child(active_modal.modal.view()),
), ),
) )
} }

View file

@ -3414,7 +3414,7 @@ impl Workspace {
self.modal_layer.read(cx).active_modal() self.modal_layer.read(cx).active_modal()
} }
pub fn toggle_modal<V: ManagedView, B>(&mut self, cx: &mut ViewContext<Self>, build: B) pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
where where
B: FnOnce(&mut ViewContext<V>) -> V, B: FnOnce(&mut ViewContext<V>) -> V,
{ {