Merge branch 'main' into zed2-breadcrumbs
This commit is contained in:
@@ -7,8 +7,8 @@ use gpui::{
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use theme2::ActiveTheme;
|
||||
use ui::{h_stack, menu_handle, ContextMenu, IconButton, InteractionState, Tooltip};
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, menu_handle, ContextMenu, IconButton, Tooltip};
|
||||
|
||||
pub enum PanelEvent {
|
||||
ChangePosition,
|
||||
@@ -686,20 +686,19 @@ impl Render for PanelButtons {
|
||||
let name = entry.panel.persistent_name();
|
||||
let panel = entry.panel.clone();
|
||||
|
||||
let mut button: IconButton = if i == active_index && is_open {
|
||||
let is_active_button = i == active_index && is_open;
|
||||
|
||||
let (action, tooltip) = if is_active_button {
|
||||
let action = dock.toggle_action();
|
||||
|
||||
let tooltip: SharedString =
|
||||
format!("Close {} dock", dock.position.to_label()).into();
|
||||
IconButton::new(name, icon)
|
||||
.state(InteractionState::Active)
|
||||
.action(action.boxed_clone())
|
||||
.tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx))
|
||||
|
||||
(action, tooltip)
|
||||
} else {
|
||||
let action = entry.panel.toggle_action(cx);
|
||||
|
||||
IconButton::new(name, icon)
|
||||
.action(action.boxed_clone())
|
||||
.tooltip(move |cx| Tooltip::for_action(name, &*action, cx))
|
||||
(action, name.into())
|
||||
};
|
||||
|
||||
Some(
|
||||
@@ -717,7 +716,7 @@ impl Render for PanelButtons {
|
||||
&& panel.position_is_valid(position, cx)
|
||||
{
|
||||
let panel = panel.clone();
|
||||
menu = menu.entry(position.to_label(), move |_, cx| {
|
||||
menu = menu.entry(position.to_label(), move |cx| {
|
||||
panel.set_position(position, cx);
|
||||
})
|
||||
}
|
||||
@@ -727,7 +726,14 @@ impl Render for PanelButtons {
|
||||
})
|
||||
.anchor(menu_anchor)
|
||||
.attach(menu_attach)
|
||||
.child(|is_open| button.selected(is_open)),
|
||||
.child(move |_is_open| {
|
||||
IconButton::new(name, icon)
|
||||
.selected(is_active_button)
|
||||
.action(action.boxed_clone())
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(tooltip.clone(), &*action, cx)
|
||||
})
|
||||
}),
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
@@ -95,10 +95,6 @@ impl Render for ModalLayer {
|
||||
.track_focus(&active_modal.focus_handle)
|
||||
.child(
|
||||
h_stack()
|
||||
// needed to prevent mouse events leaking to the
|
||||
// UI below. // todo! for gpui3.
|
||||
.on_any_mouse_down(|_, cx| cx.stop_propagation())
|
||||
.on_any_mouse_up(|_, cx| cx.stop_propagation())
|
||||
.on_mouse_down_out(cx.listener(|this, _, cx| {
|
||||
this.hide_modal(cx);
|
||||
}))
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::{Toast, Workspace};
|
||||
use collections::HashMap;
|
||||
use gpui::{AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext};
|
||||
use gpui::{
|
||||
AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render,
|
||||
View, ViewContext, VisualContext,
|
||||
};
|
||||
use std::{any::TypeId, ops::DerefMut};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
@@ -9,13 +12,9 @@ pub fn init(cx: &mut AppContext) {
|
||||
// simple_message_notification::init(cx);
|
||||
}
|
||||
|
||||
pub enum NotificationEvent {
|
||||
Dismiss,
|
||||
}
|
||||
pub trait Notification: EventEmitter<DismissEvent> + Render {}
|
||||
|
||||
pub trait Notification: EventEmitter<NotificationEvent> + Render {}
|
||||
|
||||
impl<V: EventEmitter<NotificationEvent> + Render> Notification for V {}
|
||||
impl<V: EventEmitter<DismissEvent> + Render> Notification for V {}
|
||||
|
||||
pub trait NotificationHandle: Send {
|
||||
fn id(&self) -> EntityId;
|
||||
@@ -107,10 +106,8 @@ impl Workspace {
|
||||
let notification = build_notification(cx);
|
||||
cx.subscribe(
|
||||
¬ification,
|
||||
move |this, handle, event: &NotificationEvent, cx| match event {
|
||||
NotificationEvent::Dismiss => {
|
||||
this.dismiss_notification_internal(type_id, id, cx);
|
||||
}
|
||||
move |this, handle, event: &DismissEvent, cx| {
|
||||
this.dismiss_notification_internal(type_id, id, cx);
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
@@ -120,6 +117,17 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_error<E>(&mut self, err: &E, cx: &mut ViewContext<Self>)
|
||||
where
|
||||
E: std::fmt::Debug,
|
||||
{
|
||||
self.show_notification(0, cx, |cx| {
|
||||
cx.build_view(|_cx| {
|
||||
simple_message_notification::MessageNotification::new(format!("Error: {err:?}"))
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn dismiss_notification<V: Notification>(&mut self, id: usize, cx: &mut ViewContext<Self>) {
|
||||
let type_id = TypeId::of::<V>();
|
||||
|
||||
@@ -166,13 +174,15 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub mod simple_message_notification {
|
||||
use super::NotificationEvent;
|
||||
use gpui::{AnyElement, AppContext, Div, EventEmitter, Render, TextStyle, ViewContext};
|
||||
use gpui::{
|
||||
div, AnyElement, AppContext, DismissEvent, Div, EventEmitter, InteractiveElement,
|
||||
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, TextStyle,
|
||||
ViewContext,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
// todo!()
|
||||
// actions!(message_notifications, [CancelMessageNotification]);
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
|
||||
|
||||
#[derive(Clone, Default, Deserialize, PartialEq)]
|
||||
pub struct OsOpen(pub Cow<'static, str>);
|
||||
@@ -197,22 +207,22 @@ pub mod simple_message_notification {
|
||||
// }
|
||||
|
||||
enum NotificationMessage {
|
||||
Text(Cow<'static, str>),
|
||||
Text(SharedString),
|
||||
Element(fn(TextStyle, &AppContext) -> AnyElement),
|
||||
}
|
||||
|
||||
pub struct MessageNotification {
|
||||
message: NotificationMessage,
|
||||
on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>) + Send + Sync>>,
|
||||
click_message: Option<Cow<'static, str>>,
|
||||
click_message: Option<SharedString>,
|
||||
}
|
||||
|
||||
impl EventEmitter<NotificationMessage> for MessageNotification {}
|
||||
impl EventEmitter<DismissEvent> for MessageNotification {}
|
||||
|
||||
impl MessageNotification {
|
||||
pub fn new<S>(message: S) -> MessageNotification
|
||||
where
|
||||
S: Into<Cow<'static, str>>,
|
||||
S: Into<SharedString>,
|
||||
{
|
||||
Self {
|
||||
message: NotificationMessage::Text(message.into()),
|
||||
@@ -221,19 +231,20 @@ pub mod simple_message_notification {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_element(
|
||||
message: fn(TextStyle, &AppContext) -> AnyElement,
|
||||
) -> MessageNotification {
|
||||
Self {
|
||||
message: NotificationMessage::Element(message),
|
||||
on_click: None,
|
||||
click_message: None,
|
||||
}
|
||||
}
|
||||
// not needed I think (only for the "new panel" toast, which is outdated now)
|
||||
// pub fn new_element(
|
||||
// message: fn(TextStyle, &AppContext) -> AnyElement,
|
||||
// ) -> MessageNotification {
|
||||
// Self {
|
||||
// message: NotificationMessage::Element(message),
|
||||
// on_click: None,
|
||||
// click_message: None,
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn with_click_message<S>(mut self, message: S) -> Self
|
||||
where
|
||||
S: Into<Cow<'static, str>>,
|
||||
S: Into<SharedString>,
|
||||
{
|
||||
self.click_message = Some(message.into());
|
||||
self
|
||||
@@ -247,17 +258,45 @@ pub mod simple_message_notification {
|
||||
self
|
||||
}
|
||||
|
||||
// todo!()
|
||||
// pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext<Self>) {
|
||||
// cx.emit(MessageNotificationEvent::Dismiss);
|
||||
// }
|
||||
pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for MessageNotification {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
todo!()
|
||||
v_stack()
|
||||
.elevation_3(cx)
|
||||
.p_4()
|
||||
.child(
|
||||
h_stack()
|
||||
.justify_between()
|
||||
.child(div().max_w_80().child(match &self.message {
|
||||
NotificationMessage::Text(text) => Label::new(text.clone()),
|
||||
NotificationMessage::Element(element) => {
|
||||
todo!()
|
||||
}
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.id("cancel")
|
||||
.child(IconElement::new(Icon::Close))
|
||||
.cursor_pointer()
|
||||
.on_click(cx.listener(|this, event, cx| this.dismiss(cx))),
|
||||
),
|
||||
)
|
||||
.children(self.click_message.iter().map(|message| {
|
||||
Button::new(message.clone(), message.clone()).on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
if let Some(on_click) = this.on_click.as_ref() {
|
||||
(on_click)(cx)
|
||||
};
|
||||
this.dismiss(cx)
|
||||
},
|
||||
))
|
||||
}))
|
||||
}
|
||||
}
|
||||
// todo!()
|
||||
@@ -359,8 +398,6 @@ pub mod simple_message_notification {
|
||||
// .into_any()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl EventEmitter<NotificationEvent> for MessageNotification {}
|
||||
}
|
||||
|
||||
pub trait NotifyResultExt {
|
||||
@@ -371,6 +408,8 @@ pub trait NotifyResultExt {
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<Self::Ok>;
|
||||
|
||||
fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<Self::Ok>;
|
||||
}
|
||||
|
||||
impl<T, E> NotifyResultExt for Result<T, E>
|
||||
@@ -384,14 +423,23 @@ where
|
||||
Ok(value) => Some(value),
|
||||
Err(err) => {
|
||||
log::error!("TODO {err:?}");
|
||||
// todo!()
|
||||
// workspace.show_notification(0, cx, |cx| {
|
||||
// cx.add_view(|_cx| {
|
||||
// simple_message_notification::MessageNotification::new(format!(
|
||||
// "Error: {err:?}",
|
||||
// ))
|
||||
// })
|
||||
// });
|
||||
workspace.show_error(&err, cx);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<T> {
|
||||
match self {
|
||||
Ok(value) => Some(value),
|
||||
Err(err) => {
|
||||
log::error!("TODO {err:?}");
|
||||
cx.update(|view, cx| {
|
||||
if let Ok(workspace) = view.downcast::<Workspace>() {
|
||||
workspace.update(cx, |workspace, cx| workspace.show_error(&err, cx))
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1534,18 +1534,14 @@ impl Pane {
|
||||
.gap_px()
|
||||
.child(
|
||||
div().border().border_color(gpui::red()).child(
|
||||
IconButton::new("navigate_backward", Icon::ArrowLeft).state(
|
||||
InteractionState::Enabled
|
||||
.if_enabled(self.can_navigate_backward()),
|
||||
),
|
||||
IconButton::new("navigate_backward", Icon::ArrowLeft)
|
||||
.disabled(!self.can_navigate_backward()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().border().border_color(gpui::red()).child(
|
||||
IconButton::new("navigate_forward", Icon::ArrowRight).state(
|
||||
InteractionState::Enabled
|
||||
.if_enabled(self.can_navigate_forward()),
|
||||
),
|
||||
IconButton::new("navigate_forward", Icon::ArrowRight)
|
||||
.disabled(!self.can_navigate_forward()),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
use crate::{
|
||||
item::{Item, ItemEvent},
|
||||
ItemNavHistory, WorkspaceId,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use call::participant::{Frame, RemoteVideoTrack};
|
||||
use client::{proto::PeerId, User};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
elements::*,
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
platform::MouseButton,
|
||||
AppContext, Entity, Task, View, ViewContext,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
pub enum Event {
|
||||
Close,
|
||||
}
|
||||
|
||||
pub struct SharedScreen {
|
||||
track: Weak<RemoteVideoTrack>,
|
||||
frame: Option<Frame>,
|
||||
pub peer_id: PeerId,
|
||||
user: Arc<User>,
|
||||
nav_history: Option<ItemNavHistory>,
|
||||
_maintain_frame: Task<Result<()>>,
|
||||
}
|
||||
|
||||
impl SharedScreen {
|
||||
pub fn new(
|
||||
track: &Arc<RemoteVideoTrack>,
|
||||
peer_id: PeerId,
|
||||
user: Arc<User>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let mut frames = track.frames();
|
||||
Self {
|
||||
track: Arc::downgrade(track),
|
||||
frame: None,
|
||||
peer_id,
|
||||
user,
|
||||
nav_history: Default::default(),
|
||||
_maintain_frame: cx.spawn(|this, mut cx| async move {
|
||||
while let Some(frame) = frames.next().await {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.frame = Some(frame);
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
this.update(&mut cx, |_, cx| cx.emit(Event::Close))?;
|
||||
Ok(())
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for SharedScreen {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl View for SharedScreen {
|
||||
fn ui_name() -> &'static str {
|
||||
"SharedScreen"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
enum Focus {}
|
||||
|
||||
let frame = self.frame.clone();
|
||||
MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
|
||||
Canvas::new(move |bounds, _, _, cx| {
|
||||
if let Some(frame) = frame.clone() {
|
||||
let size = constrain_size_preserving_aspect_ratio(
|
||||
bounds.size(),
|
||||
vec2f(frame.width() as f32, frame.height() as f32),
|
||||
);
|
||||
let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
|
||||
cx.scene().push_surface(gpui::platform::mac::Surface {
|
||||
bounds: RectF::new(origin, size),
|
||||
image_buffer: frame.image(),
|
||||
});
|
||||
}
|
||||
})
|
||||
.contained()
|
||||
.with_style(theme::current(cx).shared_screen)
|
||||
})
|
||||
.on_down(MouseButton::Left, |_, _, cx| cx.focus_parent())
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for SharedScreen {
|
||||
fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
|
||||
Some(format!("{}'s screen", self.user.github_login).into())
|
||||
}
|
||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(nav_history) = self.nav_history.as_mut() {
|
||||
nav_history.push::<()>(None, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn tab_content<V: 'static>(
|
||||
&self,
|
||||
_: Option<usize>,
|
||||
style: &theme::Tab,
|
||||
_: &AppContext,
|
||||
) -> gpui::AnyElement<V> {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Svg::new("icons/desktop.svg")
|
||||
.with_color(style.label.text.color)
|
||||
.constrained()
|
||||
.with_width(style.type_icon_width)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_margin_right(style.spacing),
|
||||
)
|
||||
.with_child(
|
||||
Label::new(
|
||||
format!("{}'s screen", self.user.github_login),
|
||||
style.label.clone(),
|
||||
)
|
||||
.aligned(),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
|
||||
self.nav_history = Some(history);
|
||||
}
|
||||
|
||||
fn clone_on_split(
|
||||
&self,
|
||||
_workspace_id: WorkspaceId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Self> {
|
||||
let track = self.track.upgrade()?;
|
||||
Some(Self::new(&track, self.peer_id, self.user.clone(), cx))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
|
||||
match event {
|
||||
Event::Close => smallvec::smallvec!(ItemEvent::CloseItem),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use gpui::{
|
||||
div, AnyView, Div, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
use theme2::ActiveTheme;
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, Button, Icon, IconButton};
|
||||
use util::ResultExt;
|
||||
|
||||
@@ -47,19 +47,7 @@ impl Render for StatusBar {
|
||||
.w_full()
|
||||
.h_8()
|
||||
.bg(cx.theme().colors().status_bar_background)
|
||||
// Nate: I know this isn't how we render status bar tools
|
||||
// We can move these to the correct place once we port their tools
|
||||
.child(
|
||||
h_stack().gap_1().child(self.render_left_tools(cx)).child(
|
||||
h_stack().gap_4().child(
|
||||
// TODO: Language Server status
|
||||
div()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child("Checking..."),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(h_stack().gap_1().child(self.render_left_tools(cx)))
|
||||
.child(
|
||||
h_stack()
|
||||
.gap_4()
|
||||
@@ -71,14 +59,14 @@ impl Render for StatusBar {
|
||||
div()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(Button::new("15:22")),
|
||||
.child(Button::new("status_line_column_numbers", "15:22")),
|
||||
)
|
||||
.child(
|
||||
// TODO: Language picker
|
||||
div()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.child(Button::new("Rust")),
|
||||
.child(Button::new("status_buffer_language", "Rust")),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
|
||||
@@ -3,8 +3,8 @@ use gpui::{
|
||||
div, AnyView, Div, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View,
|
||||
ViewContext, WindowContext,
|
||||
};
|
||||
use theme2::ActiveTheme;
|
||||
use ui::{h_stack, v_stack, Button, Color, Icon, IconButton, Label};
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, v_stack, ButtonLike, Color, Icon, IconButton, Label};
|
||||
|
||||
pub enum ToolbarItemEvent {
|
||||
ChangeLocation(ToolbarItemLocation),
|
||||
@@ -90,13 +90,14 @@ impl Render for Toolbar {
|
||||
.justify_between()
|
||||
.child(
|
||||
// Toolbar left side
|
||||
h_stack()
|
||||
.border()
|
||||
.border_color(gpui::red())
|
||||
.p_1()
|
||||
.child(Button::new("crates"))
|
||||
.child(Label::new("/").color(Color::Muted))
|
||||
.child(Button::new("workspace2")),
|
||||
h_stack().border().border_color(gpui::red()).p_1().child(
|
||||
ButtonLike::new("breadcrumb")
|
||||
.child(Label::new("crates/workspace2/src/toolbar.rs"))
|
||||
.child(Label::new("›").color(Color::Muted))
|
||||
.child(Label::new("impl Render for Toolbar"))
|
||||
.child(Label::new("›").color(Color::Muted))
|
||||
.child(Label::new("fn render")),
|
||||
),
|
||||
)
|
||||
// Toolbar right side
|
||||
.child(
|
||||
|
||||
+202
-128
@@ -9,7 +9,6 @@ pub mod pane_group;
|
||||
mod persistence;
|
||||
pub mod searchable;
|
||||
// todo!()
|
||||
// pub mod shared_screen;
|
||||
mod modal_layer;
|
||||
mod status_bar;
|
||||
mod toolbar;
|
||||
@@ -19,7 +18,7 @@ use anyhow::{anyhow, Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
use client2::{
|
||||
proto::{self, PeerId},
|
||||
Client, TypedEnvelope, UserStore,
|
||||
Client, TypedEnvelope, User, UserStore,
|
||||
};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
|
||||
@@ -327,7 +326,12 @@ pub struct TestCallHandler;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl CallHandler for TestCallHandler {
|
||||
fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)> {
|
||||
fn peer_state(
|
||||
&mut self,
|
||||
id: PeerId,
|
||||
project: &Model<Project>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<(bool, bool)> {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -344,14 +348,42 @@ impl CallHandler for TestCallHandler {
|
||||
None
|
||||
}
|
||||
|
||||
fn hang_up(&self, cx: AsyncWindowContext) -> Result<Task<Result<()>>> {
|
||||
anyhow::bail!("TestCallHandler should not be hanging up")
|
||||
fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
Task::ready(Err(anyhow!("TestCallHandler should not be hanging up")))
|
||||
}
|
||||
|
||||
fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn invite(
|
||||
&mut self,
|
||||
called_user_id: u64,
|
||||
initial_project: Option<Model<Project>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_muted(&self, cx: &AppContext) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
fn toggle_mute(&self, cx: &mut AppContext) {}
|
||||
|
||||
fn toggle_screen_share(&self, cx: &mut AppContext) {}
|
||||
|
||||
fn toggle_deafen(&self, cx: &mut AppContext) {}
|
||||
|
||||
fn is_deafened(&self, cx: &AppContext) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test(cx: &mut AppContext) -> Arc<Self> {
|
||||
@@ -382,7 +414,7 @@ impl AppState {
|
||||
workspace_store,
|
||||
node_runtime: FakeNodeRuntime::new(),
|
||||
build_window_options: |_, _, _| Default::default(),
|
||||
call_factory: |_, _| Box::new(TestCallHandler),
|
||||
call_factory: |_| Box::new(TestCallHandler),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -441,7 +473,12 @@ pub enum Event {
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait CallHandler {
|
||||
fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)>;
|
||||
fn peer_state(
|
||||
&mut self,
|
||||
id: PeerId,
|
||||
project: &Model<Project>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<(bool, bool)>;
|
||||
fn shared_screen_for_peer(
|
||||
&self,
|
||||
peer_id: PeerId,
|
||||
@@ -452,8 +489,20 @@ pub trait CallHandler {
|
||||
fn is_in_room(&self, cx: &mut ViewContext<Workspace>) -> bool {
|
||||
self.room_id(cx).is_some()
|
||||
}
|
||||
fn hang_up(&self, cx: AsyncWindowContext) -> Result<Task<Result<()>>>;
|
||||
fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>>;
|
||||
fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>>;
|
||||
fn invite(
|
||||
&mut self,
|
||||
called_user_id: u64,
|
||||
initial_project: Option<Model<Project>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<()>>;
|
||||
fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>>;
|
||||
fn is_muted(&self, cx: &AppContext) -> Option<bool>;
|
||||
fn is_deafened(&self, cx: &AppContext) -> Option<bool>;
|
||||
fn toggle_mute(&self, cx: &mut AppContext);
|
||||
fn toggle_deafen(&self, cx: &mut AppContext);
|
||||
fn toggle_screen_share(&self, cx: &mut AppContext);
|
||||
}
|
||||
|
||||
pub struct Workspace {
|
||||
@@ -507,7 +556,7 @@ struct FollowerState {
|
||||
|
||||
enum WorkspaceBounds {}
|
||||
|
||||
type CallFactory = fn(WeakView<Workspace>, &mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
|
||||
type CallFactory = fn(&mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
|
||||
impl Workspace {
|
||||
pub fn new(
|
||||
workspace_id: WorkspaceId,
|
||||
@@ -683,7 +732,21 @@ impl Workspace {
|
||||
}),
|
||||
];
|
||||
|
||||
cx.defer(|this, cx| this.update_window_title(cx));
|
||||
cx.defer(|this, cx| {
|
||||
this.update_window_title(cx);
|
||||
// todo! @nate - these are useful for testing notifications
|
||||
// this.show_error(
|
||||
// &anyhow::anyhow!("what happens if this message is very very very very very long"),
|
||||
// cx,
|
||||
// );
|
||||
|
||||
// this.show_notification(1, cx, |cx| {
|
||||
// cx.build_view(|_cx| {
|
||||
// simple_message_notification::MessageNotification::new(format!("Error:"))
|
||||
// .with_click_message("click here because!")
|
||||
// })
|
||||
// });
|
||||
});
|
||||
Workspace {
|
||||
window_self: window_handle,
|
||||
weak_self: weak_handle.clone(),
|
||||
@@ -707,7 +770,7 @@ impl Workspace {
|
||||
last_leaders_by_pane: Default::default(),
|
||||
window_edited: false,
|
||||
|
||||
call_handler: (app_state.call_factory)(weak_handle.clone(), cx),
|
||||
call_handler: (app_state.call_factory)(cx),
|
||||
database_id: workspace_id,
|
||||
app_state,
|
||||
_observe_current_user,
|
||||
@@ -1183,7 +1246,7 @@ impl Workspace {
|
||||
if answer.await.log_err() == Some(1) {
|
||||
return anyhow::Ok(false);
|
||||
} else {
|
||||
this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx.to_async()))??
|
||||
this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx))?
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
@@ -1755,22 +1818,22 @@ impl Workspace {
|
||||
pane
|
||||
}
|
||||
|
||||
// pub fn add_item_to_center(
|
||||
// &mut self,
|
||||
// item: Box<dyn ItemHandle>,
|
||||
// cx: &mut ViewContext<Self>,
|
||||
// ) -> bool {
|
||||
// if let Some(center_pane) = self.last_active_center_pane.clone() {
|
||||
// if let Some(center_pane) = center_pane.upgrade(cx) {
|
||||
// center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
|
||||
// true
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
// }
|
||||
pub fn add_item_to_center(
|
||||
&mut self,
|
||||
item: Box<dyn ItemHandle>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> bool {
|
||||
if let Some(center_pane) = self.last_active_center_pane.clone() {
|
||||
if let Some(center_pane) = center_pane.upgrade() {
|
||||
center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
|
||||
self.active_pane
|
||||
@@ -1967,13 +2030,13 @@ impl Workspace {
|
||||
item
|
||||
}
|
||||
|
||||
// pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
|
||||
// if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
|
||||
// self.active_pane.update(cx, |pane, cx| {
|
||||
// pane.add_item(Box::new(shared_screen), false, true, None, cx)
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
|
||||
if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
|
||||
self.active_pane.update(cx, |pane, cx| {
|
||||
pane.add_item(shared_screen, false, true, None, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
|
||||
let result = self.panes.iter().find_map(|pane| {
|
||||
@@ -2289,6 +2352,11 @@ impl Workspace {
|
||||
&self.active_pane
|
||||
}
|
||||
|
||||
pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
|
||||
let weak_pane = self.panes_by_item.get(&handle.item_id())?;
|
||||
weak_pane.upgrade()
|
||||
}
|
||||
|
||||
fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
|
||||
self.follower_states.retain(|_, state| {
|
||||
if state.leader_id == peer_id {
|
||||
@@ -2566,32 +2634,30 @@ impl Workspace {
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn render_notifications(
|
||||
// &self,
|
||||
// theme: &theme::Workspace,
|
||||
// cx: &AppContext,
|
||||
// ) -> Option<AnyElement<Workspace>> {
|
||||
// if self.notifications.is_empty() {
|
||||
// None
|
||||
// } else {
|
||||
// Some(
|
||||
// Flex::column()
|
||||
// .with_children(self.notifications.iter().map(|(_, _, notification)| {
|
||||
// ChildView::new(notification.as_any(), cx)
|
||||
// .contained()
|
||||
// .with_style(theme.notification)
|
||||
// }))
|
||||
// .constrained()
|
||||
// .with_width(theme.notifications.width)
|
||||
// .contained()
|
||||
// .with_style(theme.notifications.container)
|
||||
// .aligned()
|
||||
// .bottom()
|
||||
// .right()
|
||||
// .into_any(),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
fn render_notifications(&self, cx: &ViewContext<Self>) -> Option<Div> {
|
||||
if self.notifications.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
div()
|
||||
.absolute()
|
||||
.z_index(100)
|
||||
.right_3()
|
||||
.bottom_3()
|
||||
.w_96()
|
||||
.h_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.justify_end()
|
||||
.gap_2()
|
||||
.children(
|
||||
self.notifications
|
||||
.iter()
|
||||
.map(|(_, _, notification)| notification.to_any()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// // RPC handlers
|
||||
|
||||
@@ -2828,7 +2894,7 @@ impl Workspace {
|
||||
cx.notify();
|
||||
|
||||
let (leader_in_this_project, leader_in_this_app) =
|
||||
self.call_handler.peer_state(leader_id, cx)?;
|
||||
self.call_handler.peer_state(leader_id, &self.project, cx)?;
|
||||
let mut items_to_activate = Vec::new();
|
||||
for (pane, state) in &self.follower_states {
|
||||
if state.leader_id != leader_id {
|
||||
@@ -2848,10 +2914,10 @@ impl Workspace {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// todo!()
|
||||
// if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
|
||||
// items_to_activate.push((pane.clone(), Box::new(shared_screen)));
|
||||
// }
|
||||
|
||||
if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
|
||||
items_to_activate.push((pane.clone(), shared_screen));
|
||||
}
|
||||
}
|
||||
|
||||
for (pane, item) in items_to_activate {
|
||||
@@ -2872,27 +2938,27 @@ impl Workspace {
|
||||
None
|
||||
}
|
||||
|
||||
// todo!()
|
||||
// fn shared_screen_for_peer(
|
||||
// &self,
|
||||
// peer_id: PeerId,
|
||||
// pane: &View<Pane>,
|
||||
// cx: &mut ViewContext<Self>,
|
||||
// ) -> Option<View<SharedScreen>> {
|
||||
// let call = self.active_call()?;
|
||||
// let room = call.read(cx).room()?.read(cx);
|
||||
// let participant = room.remote_participant_for_peer_id(peer_id)?;
|
||||
// let track = participant.video_tracks.values().next()?.clone();
|
||||
// let user = participant.user.clone();
|
||||
fn shared_screen_for_peer(
|
||||
&self,
|
||||
peer_id: PeerId,
|
||||
pane: &View<Pane>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Box<dyn ItemHandle>> {
|
||||
self.call_handler.shared_screen_for_peer(peer_id, pane, cx)
|
||||
// let call = self.active_call()?;
|
||||
// let room = call.read(cx).room()?.read(cx);
|
||||
// let participant = room.remote_participant_for_peer_id(peer_id)?;
|
||||
// let track = participant.video_tracks.values().next()?.clone();
|
||||
// let user = participant.user.clone();
|
||||
|
||||
// for item in pane.read(cx).items_of_type::<SharedScreen>() {
|
||||
// if item.read(cx).peer_id == peer_id {
|
||||
// return Some(item);
|
||||
// }
|
||||
// }
|
||||
// for item in pane.read(cx).items_of_type::<SharedScreen>() {
|
||||
// if item.read(cx).peer_id == peer_id {
|
||||
// return Some(item);
|
||||
// }
|
||||
// }
|
||||
|
||||
// Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
|
||||
// }
|
||||
// Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
|
||||
}
|
||||
|
||||
pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_window_active() {
|
||||
@@ -3329,7 +3395,7 @@ impl Workspace {
|
||||
fs: project.read(cx).fs().clone(),
|
||||
build_window_options: |_, _, _| Default::default(),
|
||||
node_runtime: FakeNodeRuntime::new(),
|
||||
call_factory: |_, _| Box::new(TestCallHandler),
|
||||
call_factory: |_| Box::new(TestCallHandler),
|
||||
});
|
||||
let workspace = Self::new(0, project, app_state, cx);
|
||||
workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
|
||||
@@ -3408,6 +3474,10 @@ impl Workspace {
|
||||
self.modal_layer
|
||||
.update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
|
||||
}
|
||||
|
||||
pub fn call_state(&mut self) -> &mut dyn CallHandler {
|
||||
&mut *self.call_handler
|
||||
}
|
||||
}
|
||||
|
||||
fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
|
||||
@@ -3653,7 +3723,6 @@ impl Render for Workspace {
|
||||
.bg(cx.theme().colors().background)
|
||||
.children(self.titlebar_item.clone())
|
||||
.child(
|
||||
// todo! should this be a component a view?
|
||||
div()
|
||||
.id("workspace")
|
||||
.relative()
|
||||
@@ -3703,7 +3772,8 @@ impl Render for Workspace {
|
||||
.overflow_hidden()
|
||||
.child(self.right_dock.clone()),
|
||||
),
|
||||
),
|
||||
)
|
||||
.children(self.render_notifications(cx)),
|
||||
)
|
||||
.child(self.status_bar.clone())
|
||||
}
|
||||
@@ -4421,50 +4491,54 @@ pub fn create_and_open_local_file(
|
||||
// })
|
||||
// }
|
||||
|
||||
// pub fn restart(_: &Restart, cx: &mut AppContext) {
|
||||
// let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
|
||||
// cx.spawn(|mut cx| async move {
|
||||
// let mut workspace_windows = cx
|
||||
// .windows()
|
||||
// .into_iter()
|
||||
// .filter_map(|window| window.downcast::<Workspace>())
|
||||
// .collect::<Vec<_>>();
|
||||
pub fn restart(_: &Restart, cx: &mut AppContext) {
|
||||
let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
|
||||
let mut workspace_windows = cx
|
||||
.windows()
|
||||
.into_iter()
|
||||
.filter_map(|window| window.downcast::<Workspace>())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// // If multiple windows have unsaved changes, and need a save prompt,
|
||||
// // prompt in the active window before switching to a different window.
|
||||
// workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
|
||||
// If multiple windows have unsaved changes, and need a save prompt,
|
||||
// prompt in the active window before switching to a different window.
|
||||
workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
|
||||
|
||||
// if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
|
||||
// let answer = window.prompt(
|
||||
// PromptLevel::Info,
|
||||
// "Are you sure you want to restart?",
|
||||
// &["Restart", "Cancel"],
|
||||
// &mut cx,
|
||||
// );
|
||||
let mut prompt = None;
|
||||
if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
|
||||
prompt = window
|
||||
.update(cx, |_, cx| {
|
||||
cx.prompt(
|
||||
PromptLevel::Info,
|
||||
"Are you sure you want to restart?",
|
||||
&["Restart", "Cancel"],
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
// if let Some(mut answer) = answer {
|
||||
// let answer = answer.next().await;
|
||||
// if answer != Some(0) {
|
||||
// return Ok(());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
cx.spawn(|mut cx| async move {
|
||||
if let Some(mut prompt) = prompt {
|
||||
let answer = prompt.await?;
|
||||
if answer != 0 {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// // If the user cancels any save prompt, then keep the app open.
|
||||
// for window in workspace_windows {
|
||||
// if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
|
||||
// workspace.prepare_to_close(true, cx)
|
||||
// }) {
|
||||
// if !should_close.await? {
|
||||
// return Ok(());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// cx.platform().restart();
|
||||
// anyhow::Ok(())
|
||||
// })
|
||||
// .detach_and_log_err(cx);
|
||||
// }
|
||||
// If the user cancels any save prompt, then keep the app open.
|
||||
for window in workspace_windows {
|
||||
if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
|
||||
workspace.prepare_to_close(true, cx)
|
||||
}) {
|
||||
if !should_close.await? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cx.update(|cx| cx.restart())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
|
||||
let mut parts = value.split(',');
|
||||
|
||||
Reference in New Issue
Block a user