Merge pull request #537 from zed-industries/disconnected-status

Render overlay after remote project becomes read-only
This commit is contained in:
Nathan Sobo 2022-03-04 10:39:43 -07:00 committed by GitHub
commit 9b8c782609
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 170 additions and 74 deletions

View File

@ -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>),

View File

@ -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,
});
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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(())
}

View File

@ -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)]

View File

@ -576,7 +576,13 @@ pub struct Workspace {
impl Workspace {
pub fn new(params: &WorkspaceParams, cx: &mut ViewContext<Self>) -> Self {
cx.observe(&params.project, |_, _, cx| cx.notify()).detach();
cx.observe(&params.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")
}

View File

@ -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"

View File

@ -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"

View File

@ -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"