mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 09:12:57 +03:00
Merge pull request #537 from zed-industries/disconnected-status
Render overlay after remote project becomes read-only
This commit is contained in:
commit
9b8c782609
@ -127,6 +127,12 @@ pub enum Status {
|
||||
ReconnectionError { next_reconnection: Instant },
|
||||
}
|
||||
|
||||
impl Status {
|
||||
pub fn is_connected(&self) -> bool {
|
||||
matches!(self, Self::Connected { .. })
|
||||
}
|
||||
}
|
||||
|
||||
struct ClientState {
|
||||
credentials: Option<Credentials>,
|
||||
status: (watch::Sender<Status>, watch::Receiver<Status>),
|
||||
|
@ -979,14 +979,6 @@ impl MutableAppContext {
|
||||
.and_then(|window| window.root_view.clone().downcast::<T>())
|
||||
}
|
||||
|
||||
pub fn root_view_id(&self, window_id: usize) -> Option<usize> {
|
||||
self.cx.root_view_id(window_id)
|
||||
}
|
||||
|
||||
pub fn focused_view_id(&self, window_id: usize) -> Option<usize> {
|
||||
self.cx.focused_view_id(window_id)
|
||||
}
|
||||
|
||||
pub fn render_view(
|
||||
&mut self,
|
||||
window_id: usize,
|
||||
@ -1376,7 +1368,7 @@ impl MutableAppContext {
|
||||
window_id,
|
||||
Window {
|
||||
root_view: root_view.clone().into(),
|
||||
focused_view_id: root_view.id(),
|
||||
focused_view_id: Some(root_view.id()),
|
||||
invalidation: None,
|
||||
},
|
||||
);
|
||||
@ -1544,7 +1536,7 @@ impl MutableAppContext {
|
||||
.get_or_insert_with(Default::default)
|
||||
.removed
|
||||
.push(view_id);
|
||||
if window.focused_view_id == view_id {
|
||||
if window.focused_view_id == Some(view_id) {
|
||||
Some(window.root_view.id())
|
||||
} else {
|
||||
None
|
||||
@ -1552,7 +1544,7 @@ impl MutableAppContext {
|
||||
});
|
||||
|
||||
if let Some(view_id) = change_focus_to {
|
||||
self.focus(window_id, view_id);
|
||||
self.focus(window_id, Some(view_id));
|
||||
}
|
||||
|
||||
self.pending_effects
|
||||
@ -1755,7 +1747,7 @@ impl MutableAppContext {
|
||||
}
|
||||
}
|
||||
|
||||
fn focus(&mut self, window_id: usize, focused_id: usize) {
|
||||
fn focus(&mut self, window_id: usize, focused_id: Option<usize>) {
|
||||
if self
|
||||
.cx
|
||||
.windows
|
||||
@ -1767,7 +1759,7 @@ impl MutableAppContext {
|
||||
}
|
||||
|
||||
self.update(|this| {
|
||||
let blurred_id = this.cx.windows.get_mut(&window_id).map(|window| {
|
||||
let blurred_id = this.cx.windows.get_mut(&window_id).and_then(|window| {
|
||||
let blurred_id = window.focused_view_id;
|
||||
window.focused_view_id = focused_id;
|
||||
blurred_id
|
||||
@ -1780,9 +1772,11 @@ impl MutableAppContext {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut focused_view) = this.cx.views.remove(&(window_id, focused_id)) {
|
||||
focused_view.on_focus(this, window_id, focused_id);
|
||||
this.cx.views.insert((window_id, focused_id), focused_view);
|
||||
if let Some(focused_id) = focused_id {
|
||||
if let Some(mut focused_view) = this.cx.views.remove(&(window_id, focused_id)) {
|
||||
focused_view.on_focus(this, window_id, focused_id);
|
||||
this.cx.views.insert((window_id, focused_id), focused_view);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1958,7 +1952,7 @@ impl AppContext {
|
||||
pub fn focused_view_id(&self, window_id: usize) -> Option<usize> {
|
||||
self.windows
|
||||
.get(&window_id)
|
||||
.map(|window| window.focused_view_id)
|
||||
.and_then(|window| window.focused_view_id)
|
||||
}
|
||||
|
||||
pub fn background(&self) -> &Arc<executor::Background> {
|
||||
@ -2052,7 +2046,7 @@ impl ReadView for AppContext {
|
||||
|
||||
struct Window {
|
||||
root_view: AnyViewHandle,
|
||||
focused_view_id: usize,
|
||||
focused_view_id: Option<usize>,
|
||||
invalidation: Option<WindowInvalidation>,
|
||||
}
|
||||
|
||||
@ -2080,7 +2074,7 @@ pub enum Effect {
|
||||
},
|
||||
Focus {
|
||||
window_id: usize,
|
||||
view_id: usize,
|
||||
view_id: Option<usize>,
|
||||
},
|
||||
ResizeWindow {
|
||||
window_id: usize,
|
||||
@ -2514,14 +2508,21 @@ impl<'a, T: View> ViewContext<'a, T> {
|
||||
let handle = handle.into();
|
||||
self.app.pending_effects.push_back(Effect::Focus {
|
||||
window_id: handle.window_id,
|
||||
view_id: handle.view_id,
|
||||
view_id: Some(handle.view_id),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn focus_self(&mut self) {
|
||||
self.app.pending_effects.push_back(Effect::Focus {
|
||||
window_id: self.window_id,
|
||||
view_id: self.view_id,
|
||||
view_id: Some(self.view_id),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn blur(&mut self) {
|
||||
self.app.pending_effects.push_back(Effect::Focus {
|
||||
window_id: self.window_id,
|
||||
view_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ use crate::{
|
||||
|
||||
pub struct EventHandler {
|
||||
child: ElementBox,
|
||||
capture: Option<Box<dyn FnMut(&Event, RectF, &mut EventContext) -> bool>>,
|
||||
mouse_down: Option<Box<dyn FnMut(&mut EventContext) -> bool>>,
|
||||
}
|
||||
|
||||
@ -15,6 +16,7 @@ impl EventHandler {
|
||||
pub fn new(child: ElementBox) -> Self {
|
||||
Self {
|
||||
child,
|
||||
capture: None,
|
||||
mouse_down: None,
|
||||
}
|
||||
}
|
||||
@ -26,6 +28,14 @@ impl EventHandler {
|
||||
self.mouse_down = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn capture<F>(mut self, callback: F) -> Self
|
||||
where
|
||||
F: 'static + FnMut(&Event, RectF, &mut EventContext) -> bool,
|
||||
{
|
||||
self.capture = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for EventHandler {
|
||||
@ -59,6 +69,12 @@ impl Element for EventHandler {
|
||||
_: &mut Self::PaintState,
|
||||
cx: &mut EventContext,
|
||||
) -> bool {
|
||||
if let Some(capture) = self.capture.as_mut() {
|
||||
if capture(event, bounds, cx) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if self.child.dispatch_event(event, cx) {
|
||||
true
|
||||
} else {
|
||||
|
@ -51,13 +51,15 @@ impl Presenter {
|
||||
}
|
||||
|
||||
pub fn dispatch_path(&self, app: &AppContext) -> Vec<usize> {
|
||||
let mut view_id = app.focused_view_id(self.window_id).unwrap();
|
||||
let mut path = vec![view_id];
|
||||
while let Some(parent_id) = self.parents.get(&view_id).copied() {
|
||||
path.push(parent_id);
|
||||
view_id = parent_id;
|
||||
let mut path = Vec::new();
|
||||
if let Some(mut view_id) = app.focused_view_id(self.window_id) {
|
||||
path.push(view_id);
|
||||
while let Some(parent_id) = self.parents.get(&view_id).copied() {
|
||||
path.push(parent_id);
|
||||
view_id = parent_id;
|
||||
}
|
||||
path.reverse();
|
||||
}
|
||||
path.reverse();
|
||||
path
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,7 @@ enum ProjectClientState {
|
||||
sharing_has_stopped: bool,
|
||||
remote_id: u64,
|
||||
replica_id: ReplicaId,
|
||||
_detect_unshare_task: Task<Option<()>>,
|
||||
},
|
||||
}
|
||||
|
||||
@ -244,7 +245,7 @@ impl Project {
|
||||
let mut status = rpc.status();
|
||||
while let Some(status) = status.next().await {
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
let remote_id = if let client::Status::Connected { .. } = status {
|
||||
let remote_id = if status.is_connected() {
|
||||
let response = rpc.request(proto::RegisterProject {}).await?;
|
||||
Some(response.project_id)
|
||||
} else {
|
||||
@ -333,7 +334,7 @@ impl Project {
|
||||
}
|
||||
|
||||
let (opened_buffer_tx, opened_buffer_rx) = watch::channel();
|
||||
let this = cx.add_model(|cx| {
|
||||
let this = cx.add_model(|cx: &mut ModelContext<Self>| {
|
||||
let mut this = Self {
|
||||
worktrees: Vec::new(),
|
||||
loading_buffers: Default::default(),
|
||||
@ -346,11 +347,26 @@ impl Project {
|
||||
user_store: user_store.clone(),
|
||||
fs,
|
||||
subscriptions: vec![client.add_model_for_remote_entity(remote_id, cx)],
|
||||
client,
|
||||
client: client.clone(),
|
||||
client_state: ProjectClientState::Remote {
|
||||
sharing_has_stopped: false,
|
||||
remote_id,
|
||||
replica_id,
|
||||
_detect_unshare_task: cx.spawn_weak(move |this, mut cx| {
|
||||
async move {
|
||||
let mut status = client.status();
|
||||
let is_connected =
|
||||
status.next().await.map_or(false, |s| s.is_connected());
|
||||
// Even if we're initially connected, any future change of the status means we momentarily disconnected.
|
||||
if !is_connected || status.next().await.is_some() {
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| this.project_unshared(cx))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
.log_err()
|
||||
}),
|
||||
},
|
||||
language_servers_with_diagnostics_running: 0,
|
||||
language_servers: Default::default(),
|
||||
@ -666,6 +682,18 @@ impl Project {
|
||||
})
|
||||
}
|
||||
|
||||
fn project_unshared(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let ProjectClientState::Remote {
|
||||
sharing_has_stopped,
|
||||
..
|
||||
} = &mut self.client_state
|
||||
{
|
||||
*sharing_has_stopped = true;
|
||||
self.collaborators.clear();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_read_only(&self) -> bool {
|
||||
match &self.client_state {
|
||||
ProjectClientState::Local { .. } => false,
|
||||
@ -2672,20 +2700,7 @@ impl Project {
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let ProjectClientState::Remote {
|
||||
sharing_has_stopped,
|
||||
..
|
||||
} = &mut this.client_state
|
||||
{
|
||||
*sharing_has_stopped = true;
|
||||
this.collaborators.clear();
|
||||
cx.notify();
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
|
||||
this.update(&mut cx, |this, cx| this.project_unshared(cx));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ pub struct Workspace {
|
||||
pub right_sidebar: Sidebar,
|
||||
pub status_bar: StatusBar,
|
||||
pub toolbar: Toolbar,
|
||||
pub disconnected_overlay: ContainedText,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
|
@ -576,7 +576,13 @@ pub struct Workspace {
|
||||
|
||||
impl Workspace {
|
||||
pub fn new(params: &WorkspaceParams, cx: &mut ViewContext<Self>) -> Self {
|
||||
cx.observe(¶ms.project, |_, _, cx| cx.notify()).detach();
|
||||
cx.observe(¶ms.project, |_, project, cx| {
|
||||
if project.read(cx).is_read_only() {
|
||||
cx.blur();
|
||||
}
|
||||
cx.notify()
|
||||
})
|
||||
.detach();
|
||||
|
||||
let pane = cx.add_view(|_| Pane::new(params.settings.clone()));
|
||||
let pane_id = pane.id();
|
||||
@ -1297,6 +1303,28 @@ impl Workspace {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_disconnected_overlay(&self, cx: &AppContext) -> Option<ElementBox> {
|
||||
if self.project.read(cx).is_read_only() {
|
||||
let theme = &self.settings.borrow().theme;
|
||||
Some(
|
||||
EventHandler::new(
|
||||
Label::new(
|
||||
"Your connection to the remote project has been lost.".to_string(),
|
||||
theme.workspace.disconnected_overlay.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(theme.workspace.disconnected_overlay.container)
|
||||
.boxed(),
|
||||
)
|
||||
.capture(|_, _, _| true)
|
||||
.boxed(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for Workspace {
|
||||
@ -1311,39 +1339,51 @@ impl View for Workspace {
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let settings = self.settings.borrow();
|
||||
let theme = &settings.theme;
|
||||
Flex::column()
|
||||
.with_child(self.render_titlebar(&theme, cx))
|
||||
Stack::new()
|
||||
.with_child(
|
||||
Stack::new()
|
||||
.with_child({
|
||||
let mut content = Flex::row();
|
||||
content.add_child(self.left_sidebar.render(&settings, cx));
|
||||
if let Some(element) = self.left_sidebar.render_active_item(&settings, cx) {
|
||||
content.add_child(Flexible::new(0.8, false, element).boxed());
|
||||
}
|
||||
content.add_child(
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Flexible::new(1., true, self.center.render(&settings.theme))
|
||||
Flex::column()
|
||||
.with_child(self.render_titlebar(&theme, cx))
|
||||
.with_child(
|
||||
Stack::new()
|
||||
.with_child({
|
||||
let mut content = Flex::row();
|
||||
content.add_child(self.left_sidebar.render(&settings, cx));
|
||||
if let Some(element) =
|
||||
self.left_sidebar.render_active_item(&settings, cx)
|
||||
{
|
||||
content.add_child(Flexible::new(0.8, false, element).boxed());
|
||||
}
|
||||
content.add_child(
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Flexible::new(
|
||||
1.,
|
||||
true,
|
||||
self.center.render(&settings.theme),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(ChildView::new(&self.status_bar).boxed())
|
||||
.flexible(1., true)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(ChildView::new(&self.status_bar).boxed())
|
||||
.flexible(1., true)
|
||||
.boxed(),
|
||||
);
|
||||
if let Some(element) = self.right_sidebar.render_active_item(&settings, cx)
|
||||
{
|
||||
content.add_child(Flexible::new(0.8, false, element).boxed());
|
||||
}
|
||||
content.add_child(self.right_sidebar.render(&settings, cx));
|
||||
content.boxed()
|
||||
})
|
||||
.with_children(self.modal.as_ref().map(|m| ChildView::new(m).boxed()))
|
||||
.flexible(1.0, true)
|
||||
);
|
||||
if let Some(element) =
|
||||
self.right_sidebar.render_active_item(&settings, cx)
|
||||
{
|
||||
content.add_child(Flexible::new(0.8, false, element).boxed());
|
||||
}
|
||||
content.add_child(self.right_sidebar.render(&settings, cx));
|
||||
content.boxed()
|
||||
})
|
||||
.with_children(self.modal.as_ref().map(|m| ChildView::new(m).boxed()))
|
||||
.flexible(1.0, true)
|
||||
.boxed(),
|
||||
)
|
||||
.contained()
|
||||
.with_background_color(settings.theme.workspace.background)
|
||||
.boxed(),
|
||||
)
|
||||
.contained()
|
||||
.with_background_color(settings.theme.workspace.background)
|
||||
.with_children(self.render_disconnected_overlay(cx))
|
||||
.named("workspace")
|
||||
}
|
||||
|
||||
|
@ -60,3 +60,8 @@ emphasis = "#4ec9b0"
|
||||
link_uri = { color = "#6a9955", underline = true }
|
||||
link_text = { color = "#cb8f77", italic = true }
|
||||
list_marker = "#4e94ce"
|
||||
|
||||
[workspace.disconnected_overlay]
|
||||
extends = "$text.base"
|
||||
color = "#ffffff"
|
||||
background = "#000000aa"
|
||||
|
@ -60,3 +60,8 @@ emphasis = "#4ec9b0"
|
||||
link_uri = { color = "#6a9955", underline = true }
|
||||
link_text = { color = "#cb8f77", italic = true }
|
||||
list_marker = "#4e94ce"
|
||||
|
||||
[workspace.disconnected_overlay]
|
||||
extends = "$text.base"
|
||||
color = "#ffffff"
|
||||
background = "#000000aa"
|
||||
|
@ -60,3 +60,8 @@ emphasis = "#267f29"
|
||||
link_uri = { color = "#6a9955", underline = true }
|
||||
link_text = { color = "#a82121", italic = true }
|
||||
list_marker = "#4e94ce"
|
||||
|
||||
[workspace.disconnected_overlay]
|
||||
extends = "$text.base"
|
||||
color = "#ffffff"
|
||||
background = "#000000cc"
|
||||
|
Loading…
Reference in New Issue
Block a user