@@ -62,6 +62,18 @@ impl Interactivity {
}));
}
+ pub fn capture_any_mouse_down(
+ &mut self,
+ listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
+ ) {
+ self.mouse_down_listeners
+ .push(Box::new(move |event, bounds, phase, cx| {
+ if phase == DispatchPhase::Capture && bounds.visibly_contains(&event.position, cx) {
+ (listener)(event, cx)
+ }
+ }));
+ }
+
pub fn on_any_mouse_down(
&mut self,
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
@@ -90,6 +102,18 @@ impl Interactivity {
}));
}
+ pub fn capture_any_mouse_up(
+ &mut self,
+ listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
+ ) {
+ self.mouse_up_listeners
+ .push(Box::new(move |event, bounds, phase, cx| {
+ if phase == DispatchPhase::Capture && bounds.visibly_contains(&event.position, cx) {
+ (listener)(event, cx)
+ }
+ }));
+ }
+
pub fn on_any_mouse_up(
&mut self,
listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
@@ -384,6 +408,14 @@ pub trait InteractiveElement: Sized {
self
}
+ fn capture_any_mouse_down(
+ mut self,
+ listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
+ ) -> Self {
+ self.interactivity().capture_any_mouse_down(listener);
+ self
+ }
+
fn on_any_mouse_down(
mut self,
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
@@ -401,6 +433,14 @@ pub trait InteractiveElement: Sized {
self
}
+ fn capture_any_mouse_up(
+ mut self,
+ listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
+ ) -> Self {
+ self.interactivity().capture_any_mouse_up(listener);
+ self
+ }
+
fn on_mouse_down_out(
mut self,
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
@@ -273,6 +273,7 @@ pub struct Window {
pub(crate) drawing: bool,
activation_observers: SubscriberSet<(), AnyObserver>,
pub(crate) focus: Option<FocusId>,
+ focus_enabled: bool,
#[cfg(any(test, feature = "test-support"))]
pub(crate) focus_invalidated: bool,
@@ -420,6 +421,7 @@ impl Window {
drawing: false,
activation_observers: SubscriberSet::new(),
focus: None,
+ focus_enabled: true,
#[cfg(any(test, feature = "test-support"))]
focus_invalidated: false,
@@ -496,7 +498,7 @@ impl<'a> WindowContext<'a> {
/// Move focus to the element associated with the given `FocusHandle`.
pub fn focus(&mut self, handle: &FocusHandle) {
- if self.window.focus == Some(handle.id) {
+ if !self.window.focus_enabled || self.window.focus == Some(handle.id) {
return;
}
@@ -516,10 +518,19 @@ impl<'a> WindowContext<'a> {
/// Remove focus from all elements within this context's window.
pub fn blur(&mut self) {
+ if !self.window.focus_enabled {
+ return;
+ }
+
self.window.focus = None;
self.notify();
}
+ pub fn disable_focus(&mut self) {
+ self.blur();
+ self.window.focus_enabled = false;
+ }
+
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
let focus_handle = self.focused();
@@ -564,9 +564,9 @@ mod element {
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
use gpui::{
- px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement,
- MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style,
- WindowContext,
+ px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, InteractiveBounds,
+ IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
+ Size, Style, WindowContext,
};
use parking_lot::Mutex;
use smallvec::SmallVec;
@@ -717,7 +717,11 @@ mod element {
};
cx.with_z_index(3, |cx| {
- if handle_bounds.contains(&cx.mouse_position()) {
+ let interactive_handle_bounds = InteractiveBounds {
+ bounds: handle_bounds,
+ stacking_order: cx.stacking_order().clone(),
+ };
+ if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
cx.set_cursor_style(match axis {
Axis::Vertical => CursorStyle::ResizeUpDown,
Axis::Horizontal => CursorStyle::ResizeLeftRight,
@@ -25,12 +25,13 @@ use futures::{
Future, FutureExt, StreamExt,
};
use gpui::{
- actions, canvas, div, impl_actions, point, size, Action, AnyModel, AnyView, AnyWeakView,
- AnyWindowHandle, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div,
- DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, GlobalPixels,
- InteractiveElement, KeyContext, ManagedView, Model, ModelContext, ParentElement,
- PathPromptOptions, Pixels, Point, PromptLevel, Render, Size, Styled, Subscription, Task, View,
- ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
+ actions, canvas, div, impl_actions, point, size, Action, AnyElement, AnyModel, AnyView,
+ AnyWeakView, AnyWindowHandle, AppContext, AsyncAppContext, AsyncWindowContext, BorrowWindow,
+ Bounds, Context, Div, DragMoveEvent, Element, Entity, EntityId, EventEmitter, FocusHandle,
+ FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId,
+ ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel,
+ Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
+ WindowBounds, WindowContext, WindowHandle, WindowOptions,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
use itertools::Itertools;
@@ -64,6 +65,7 @@ use std::{
use theme::{ActiveTheme, ThemeSettings};
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub use ui;
+use ui::Label;
use util::ResultExt;
use uuid::Uuid;
pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
@@ -502,7 +504,7 @@ impl Workspace {
project::Event::DisconnectedFromHost => {
this.update_window_edited(cx);
- cx.blur();
+ cx.disable_focus();
}
project::Event::Closed => {
@@ -2519,32 +2521,6 @@ impl Workspace {
}
}
- // fn render_disconnected_overlay(
- // &self,
- // cx: &mut ViewContext<Workspace>,
- // ) -> Option<AnyElement<Workspace>> {
- // if self.project.read(cx).is_read_only() {
- // enum DisconnectedOverlay {}
- // Some(
- // MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
- // let theme = &theme::current(cx);
- // Label::new(
- // "Your connection to the remote project has been lost.",
- // theme.workspace.disconnected_overlay.text.clone(),
- // )
- // .aligned()
- // .contained()
- // .with_style(theme.workspace.disconnected_overlay.container)
- // })
- // .with_cursor_style(CursorStyle::Arrow)
- // .capture_all()
- // .into_any_named("disconnected overlay"),
- // )
- // } else {
- // None
- // }
- // }
-
fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
if self.notifications.is_empty() {
None
@@ -3661,6 +3637,11 @@ impl Render for Workspace {
})),
)
.child(self.status_bar.clone())
+ .children(if self.project.read(cx).is_read_only() {
+ Some(DisconnectedOverlay)
+ } else {
+ None
+ })
}
}
@@ -4284,6 +4265,56 @@ fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
Some(size((width as f64).into(), (height as f64).into()))
}
+struct DisconnectedOverlay;
+
+impl Element for DisconnectedOverlay {
+ type State = AnyElement;
+
+ fn layout(
+ &mut self,
+ _: Option<Self::State>,
+ cx: &mut WindowContext,
+ ) -> (LayoutId, Self::State) {
+ let mut background = cx.theme().colors().elevated_surface_background;
+ background.fade_out(0.2);
+ let mut overlay = div()
+ .bg(background)
+ .absolute()
+ .left_0()
+ .top_0()
+ .size_full()
+ .flex()
+ .items_center()
+ .justify_center()
+ .capture_any_mouse_down(|_, cx| cx.stop_propagation())
+ .capture_any_mouse_up(|_, cx| cx.stop_propagation())
+ .child(Label::new(
+ "Your connection to the remote project has been lost.",
+ ))
+ .into_any();
+ (overlay.layout(cx), overlay)
+ }
+
+ fn paint(&mut self, bounds: Bounds<Pixels>, overlay: &mut Self::State, cx: &mut WindowContext) {
+ cx.with_z_index(u8::MAX, |cx| {
+ cx.add_opaque_layer(bounds);
+ overlay.paint(cx);
+ })
+ }
+}
+
+impl IntoElement for DisconnectedOverlay {
+ type Element = Self;
+
+ fn element_id(&self) -> Option<ui::prelude::ElementId> {
+ None
+ }
+
+ fn into_element(self) -> Self::Element {
+ self
+ }
+}
+
#[cfg(test)]
mod tests {
use std::{cell::RefCell, rc::Rc};