@@ -15,7 +15,7 @@ use collections::HashSet;
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, PromptLevel,
|
||||
Subscription, Task, View, ViewContext, VisualContext, WeakModel, WeakView, WindowHandle,
|
||||
Subscription, Task, View, ViewContext, VisualContext, WeakModel, WindowHandle,
|
||||
};
|
||||
pub use participant::ParticipantLocation;
|
||||
use postage::watch;
|
||||
@@ -557,24 +557,17 @@ pub fn report_call_event_for_channel(
|
||||
|
||||
pub struct Call {
|
||||
active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
|
||||
parent_workspace: WeakView<Workspace>,
|
||||
}
|
||||
|
||||
impl Call {
|
||||
pub fn new(
|
||||
parent_workspace: WeakView<Workspace>,
|
||||
cx: &mut ViewContext<'_, Workspace>,
|
||||
) -> Box<dyn CallHandler> {
|
||||
pub fn new(cx: &mut ViewContext<'_, Workspace>) -> Box<dyn CallHandler> {
|
||||
let mut active_call = None;
|
||||
if cx.has_global::<Model<ActiveCall>>() {
|
||||
let call = cx.global::<Model<ActiveCall>>().clone();
|
||||
let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)];
|
||||
active_call = Some((call, subscriptions));
|
||||
}
|
||||
Box::new(Self {
|
||||
active_call,
|
||||
parent_workspace,
|
||||
})
|
||||
Box::new(Self { active_call })
|
||||
}
|
||||
fn on_active_call_event(
|
||||
workspace: &mut Workspace,
|
||||
@@ -597,6 +590,7 @@ impl CallHandler for Call {
|
||||
fn peer_state(
|
||||
&mut self,
|
||||
leader_id: PeerId,
|
||||
project: &Model<Project>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<(bool, bool)> {
|
||||
let (call, _) = self.active_call.as_ref()?;
|
||||
@@ -608,12 +602,7 @@ impl CallHandler for Call {
|
||||
match participant.location {
|
||||
ParticipantLocation::SharedProject { project_id } => {
|
||||
leader_in_this_app = true;
|
||||
leader_in_this_project = Some(project_id)
|
||||
== self
|
||||
.parent_workspace
|
||||
.update(cx, |this, cx| this.project().read(cx).remote_id())
|
||||
.log_err()
|
||||
.flatten();
|
||||
leader_in_this_project = Some(project_id) == project.read(cx).remote_id();
|
||||
}
|
||||
ParticipantLocation::UnsharedProject => {
|
||||
leader_in_this_app = true;
|
||||
|
||||
@@ -3,10 +3,12 @@ use anyhow::Result;
|
||||
use client::{proto::PeerId, User};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
div, AppContext, Div, Element, EventEmitter, FocusHandle, FocusableView, ParentElement, Render,
|
||||
SharedString, Task, View, ViewContext, VisualContext, WindowContext,
|
||||
div, img, AppContext, Div, Element, EventEmitter, FocusHandle, Focusable, FocusableView,
|
||||
InteractiveElement, ParentElement, Render, SharedString, Styled, Task, View, ViewContext,
|
||||
VisualContext, WindowContext,
|
||||
};
|
||||
use std::sync::{Arc, Weak};
|
||||
use ui::{h_stack, Icon, IconElement};
|
||||
use workspace::{item::Item, ItemNavHistory, WorkspaceId};
|
||||
|
||||
pub enum Event {
|
||||
@@ -16,8 +18,6 @@ pub enum Event {
|
||||
pub struct SharedScreen {
|
||||
track: Weak<RemoteVideoTrack>,
|
||||
frame: Option<Frame>,
|
||||
// temporary addition just to render something interactive.
|
||||
current_frame_id: usize,
|
||||
pub peer_id: PeerId,
|
||||
user: Arc<User>,
|
||||
nav_history: Option<ItemNavHistory>,
|
||||
@@ -51,7 +51,6 @@ impl SharedScreen {
|
||||
Ok(())
|
||||
}),
|
||||
focus: cx.focus_handle(),
|
||||
current_frame_id: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,50 +64,16 @@ impl FocusableView for SharedScreen {
|
||||
}
|
||||
}
|
||||
impl Render for SharedScreen {
|
||||
type Element = Div;
|
||||
type Element = Focusable<Div>;
|
||||
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
|
||||
let frame = self.frame.clone();
|
||||
let frame_id = self.current_frame_id;
|
||||
self.current_frame_id = self.current_frame_id.wrapping_add(1);
|
||||
div().children(frame.map(|_| {
|
||||
ui::Label::new(frame_id.to_string()).color(ui::Color::Error)
|
||||
// img().data(Arc::new(ImageData::new(image::ImageBuffer::new(
|
||||
// frame.width() as u32,
|
||||
// frame.height() as u32,
|
||||
// ))))
|
||||
}))
|
||||
div().track_focus(&self.focus).size_full().children(
|
||||
self.frame
|
||||
.as_ref()
|
||||
.map(|frame| img(frame.image()).size_full()),
|
||||
)
|
||||
}
|
||||
}
|
||||
// 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<SharedString> {
|
||||
@@ -121,25 +86,14 @@ impl Item for SharedScreen {
|
||||
}
|
||||
|
||||
fn tab_content(&self, _: Option<usize>, _: &WindowContext<'_>) -> gpui::AnyElement {
|
||||
div().child("Shared screen").into_any()
|
||||
// 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()
|
||||
h_stack()
|
||||
.gap_1()
|
||||
.child(IconElement::new(Icon::Screen))
|
||||
.child(SharedString::from(format!(
|
||||
"{}'s screen",
|
||||
self.user.github_login
|
||||
)))
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
|
||||
|
||||
@@ -221,7 +221,7 @@ impl TestServer {
|
||||
fs: fs.clone(),
|
||||
build_window_options: |_, _, _| Default::default(),
|
||||
node_runtime: FakeNodeRuntime::new(),
|
||||
call_factory: |_, _| Box::new(workspace::TestCallHandler),
|
||||
call_factory: |_| Box::new(workspace::TestCallHandler),
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
|
||||
@@ -185,7 +185,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
div()
|
||||
.flex_1()
|
||||
.justify_between()
|
||||
.children(user.avatar.clone().map(|avatar| img().data(avatar)))
|
||||
.children(user.avatar.clone().map(|avatar| img(avatar)))
|
||||
.child(Label::new(user.github_login.clone()))
|
||||
.children(icon_path.map(|icon_path| svg().path(icon_path))),
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ pub fn marked_display_snapshot(
|
||||
let (unmarked_text, markers) = marked_text_offsets(text);
|
||||
|
||||
let font = cx.text_style().font();
|
||||
let font_size: Pixels = 14.into();
|
||||
let font_size: Pixels = 14usize.into();
|
||||
|
||||
let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
|
||||
let display_map = cx.build_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));
|
||||
|
||||
@@ -65,6 +65,8 @@ fn generate_shader_bindings() -> PathBuf {
|
||||
"MonochromeSprite".into(),
|
||||
"PolychromeSprite".into(),
|
||||
"PathSprite".into(),
|
||||
"SurfaceInputIndex".into(),
|
||||
"SurfaceBounds".into(),
|
||||
]);
|
||||
config.no_includes = true;
|
||||
config.enumeration.prefix_with_name = true;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity,
|
||||
IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
|
||||
point, size, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
|
||||
InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size,
|
||||
StyleRefinement, Styled, WindowContext,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use media::core_video::CVImageBuffer;
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -12,6 +14,7 @@ pub enum ImageSource {
|
||||
/// Image content will be loaded from provided URI at render time.
|
||||
Uri(SharedString),
|
||||
Data(Arc<ImageData>),
|
||||
Surface(CVImageBuffer),
|
||||
}
|
||||
|
||||
impl From<SharedString> for ImageSource {
|
||||
@@ -20,40 +23,45 @@ impl From<SharedString> for ImageSource {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for ImageSource {
|
||||
fn from(uri: &'static str) -> Self {
|
||||
Self::Uri(uri.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ImageSource {
|
||||
fn from(uri: String) -> Self {
|
||||
Self::Uri(uri.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<ImageData>> for ImageSource {
|
||||
fn from(value: Arc<ImageData>) -> Self {
|
||||
Self::Data(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CVImageBuffer> for ImageSource {
|
||||
fn from(value: CVImageBuffer) -> Self {
|
||||
Self::Surface(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Img {
|
||||
interactivity: Interactivity,
|
||||
source: Option<ImageSource>,
|
||||
source: ImageSource,
|
||||
grayscale: bool,
|
||||
}
|
||||
|
||||
pub fn img() -> Img {
|
||||
pub fn img(source: impl Into<ImageSource>) -> Img {
|
||||
Img {
|
||||
interactivity: Interactivity::default(),
|
||||
source: None,
|
||||
source: source.into(),
|
||||
grayscale: false,
|
||||
}
|
||||
}
|
||||
|
||||
impl Img {
|
||||
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
|
||||
self.source = Some(ImageSource::from(uri.into()));
|
||||
self
|
||||
}
|
||||
pub fn data(mut self, data: Arc<ImageData>) -> Self {
|
||||
self.source = Some(ImageSource::from(data));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn source(mut self, source: impl Into<ImageSource>) -> Self {
|
||||
self.source = Some(source.into());
|
||||
self
|
||||
}
|
||||
pub fn grayscale(mut self, grayscale: bool) -> Self {
|
||||
self.grayscale = grayscale;
|
||||
self
|
||||
@@ -68,9 +76,8 @@ impl Element for Img {
|
||||
element_state: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
self.interactivity.layout(element_state, cx, |style, cx| {
|
||||
cx.request_layout(&style, None)
|
||||
})
|
||||
self.interactivity
|
||||
.layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
@@ -85,10 +92,9 @@ impl Element for Img {
|
||||
element_state,
|
||||
cx,
|
||||
|style, _scroll_offset, cx| {
|
||||
let corner_radii = style.corner_radii;
|
||||
|
||||
if let Some(source) = self.source {
|
||||
let image = match source {
|
||||
let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
|
||||
cx.with_z_index(1, |cx| {
|
||||
match self.source {
|
||||
ImageSource::Uri(uri) => {
|
||||
let image_future = cx.image_cache.get(uri.clone());
|
||||
if let Some(data) = image_future
|
||||
@@ -96,7 +102,9 @@ impl Element for Img {
|
||||
.now_or_never()
|
||||
.and_then(|result| result.ok())
|
||||
{
|
||||
data
|
||||
let new_bounds = preserve_aspect_ratio(bounds, data.size());
|
||||
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
|
||||
.log_err();
|
||||
} else {
|
||||
cx.spawn(|mut cx| async move {
|
||||
if image_future.await.ok().is_some() {
|
||||
@@ -104,17 +112,23 @@ impl Element for Img {
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
return;
|
||||
}
|
||||
}
|
||||
ImageSource::Data(image) => image,
|
||||
|
||||
ImageSource::Data(data) => {
|
||||
let new_bounds = preserve_aspect_ratio(bounds, data.size());
|
||||
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
|
||||
.log_err();
|
||||
}
|
||||
|
||||
ImageSource::Surface(surface) => {
|
||||
let size = size(surface.width().into(), surface.height().into());
|
||||
let new_bounds = preserve_aspect_ratio(bounds, size);
|
||||
// TODO: Add support for corner_radii and grayscale.
|
||||
cx.paint_surface(new_bounds, surface);
|
||||
}
|
||||
};
|
||||
let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
|
||||
cx.with_z_index(1, |cx| {
|
||||
cx.paint_image(bounds, corner_radii, image, self.grayscale)
|
||||
.log_err()
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -143,3 +157,29 @@ impl InteractiveElement for Img {
|
||||
&mut self.interactivity
|
||||
}
|
||||
}
|
||||
|
||||
fn preserve_aspect_ratio(bounds: Bounds<Pixels>, image_size: Size<DevicePixels>) -> Bounds<Pixels> {
|
||||
let image_size = image_size.map(|dimension| Pixels::from(u32::from(dimension)));
|
||||
let image_ratio = image_size.width / image_size.height;
|
||||
let bounds_ratio = bounds.size.width / bounds.size.height;
|
||||
|
||||
let new_size = if bounds_ratio > image_ratio {
|
||||
size(
|
||||
image_size.width * (bounds.size.height / image_size.height),
|
||||
bounds.size.height,
|
||||
)
|
||||
} else {
|
||||
size(
|
||||
bounds.size.width,
|
||||
image_size.height * (bounds.size.width / image_size.width),
|
||||
)
|
||||
};
|
||||
|
||||
Bounds {
|
||||
origin: point(
|
||||
bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
|
||||
bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
|
||||
),
|
||||
size: new_size,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -905,6 +905,12 @@ impl From<Pixels> for usize {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for Pixels {
|
||||
fn from(pixels: usize) -> Self {
|
||||
Pixels(pixels as f32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign,
|
||||
)]
|
||||
@@ -959,6 +965,18 @@ impl From<u64> for DevicePixels {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DevicePixels> for usize {
|
||||
fn from(device_pixels: DevicePixels) -> Self {
|
||||
device_pixels.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for DevicePixels {
|
||||
fn from(device_pixels: usize) -> Self {
|
||||
DevicePixels(device_pixels as i32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
|
||||
#[repr(transparent)]
|
||||
pub struct ScaledPixels(pub(crate) f32);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
|
||||
Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
|
||||
Quad, ScaledPixels, Scene, Shadow, Size, Underline,
|
||||
Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,
|
||||
};
|
||||
use cocoa::{
|
||||
base::{NO, YES},
|
||||
@@ -9,6 +9,9 @@ use cocoa::{
|
||||
quartzcore::AutoresizingMask,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use core_foundation::base::TCFType;
|
||||
use foreign_types::ForeignType;
|
||||
use media::core_video::CVMetalTextureCache;
|
||||
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use objc::{self, msg_send, sel, sel_impl};
|
||||
use smallvec::SmallVec;
|
||||
@@ -27,9 +30,11 @@ pub(crate) struct MetalRenderer {
|
||||
underlines_pipeline_state: metal::RenderPipelineState,
|
||||
monochrome_sprites_pipeline_state: metal::RenderPipelineState,
|
||||
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
|
||||
surfaces_pipeline_state: metal::RenderPipelineState,
|
||||
unit_vertices: metal::Buffer,
|
||||
instances: metal::Buffer,
|
||||
sprite_atlas: Arc<MetalAtlas>,
|
||||
core_video_texture_cache: CVMetalTextureCache,
|
||||
}
|
||||
|
||||
impl MetalRenderer {
|
||||
@@ -143,6 +148,14 @@ impl MetalRenderer {
|
||||
"polychrome_sprite_fragment",
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
let surfaces_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
&library,
|
||||
"surfaces",
|
||||
"surface_vertex",
|
||||
"surface_fragment",
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
|
||||
let command_queue = device.new_command_queue();
|
||||
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
|
||||
@@ -157,9 +170,11 @@ impl MetalRenderer {
|
||||
underlines_pipeline_state,
|
||||
monochrome_sprites_pipeline_state,
|
||||
polychrome_sprites_pipeline_state,
|
||||
surfaces_pipeline_state,
|
||||
unit_vertices,
|
||||
instances,
|
||||
sprite_atlas,
|
||||
core_video_texture_cache: CVMetalTextureCache::new(device.as_ptr()).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +283,14 @@ impl MetalRenderer {
|
||||
command_encoder,
|
||||
);
|
||||
}
|
||||
PrimitiveBatch::Surfaces(surfaces) => {
|
||||
self.draw_surfaces(
|
||||
surfaces,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,6 +816,102 @@ impl MetalRenderer {
|
||||
);
|
||||
*offset = next_offset;
|
||||
}
|
||||
|
||||
fn draw_surfaces(
|
||||
&mut self,
|
||||
surfaces: &[Surface],
|
||||
offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) {
|
||||
command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
SurfaceInputIndex::Vertices as u64,
|
||||
Some(&self.unit_vertices),
|
||||
0,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SurfaceInputIndex::ViewportSize as u64,
|
||||
mem::size_of_val(&viewport_size) as u64,
|
||||
&viewport_size as *const Size<DevicePixels> as *const _,
|
||||
);
|
||||
|
||||
for surface in surfaces {
|
||||
let texture_size = size(
|
||||
DevicePixels::from(surface.image_buffer.width() as i32),
|
||||
DevicePixels::from(surface.image_buffer.height() as i32),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
surface.image_buffer.pixel_format_type(),
|
||||
media::core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
|
||||
);
|
||||
|
||||
let y_texture = self
|
||||
.core_video_texture_cache
|
||||
.create_texture_from_image(
|
||||
surface.image_buffer.as_concrete_TypeRef(),
|
||||
ptr::null(),
|
||||
MTLPixelFormat::R8Unorm,
|
||||
surface.image_buffer.plane_width(0),
|
||||
surface.image_buffer.plane_height(0),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
let cb_cr_texture = self
|
||||
.core_video_texture_cache
|
||||
.create_texture_from_image(
|
||||
surface.image_buffer.as_concrete_TypeRef(),
|
||||
ptr::null(),
|
||||
MTLPixelFormat::RG8Unorm,
|
||||
surface.image_buffer.plane_width(1),
|
||||
surface.image_buffer.plane_height(1),
|
||||
1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
align_offset(offset);
|
||||
let next_offset = *offset + mem::size_of::<Surface>();
|
||||
assert!(
|
||||
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||
"instance buffer exhausted"
|
||||
);
|
||||
|
||||
command_encoder.set_vertex_buffer(
|
||||
SurfaceInputIndex::Surfaces as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SurfaceInputIndex::TextureSize as u64,
|
||||
mem::size_of_val(&texture_size) as u64,
|
||||
&texture_size as *const Size<DevicePixels> as *const _,
|
||||
);
|
||||
command_encoder.set_fragment_texture(
|
||||
SurfaceInputIndex::YTexture as u64,
|
||||
Some(y_texture.as_texture_ref()),
|
||||
);
|
||||
command_encoder.set_fragment_texture(
|
||||
SurfaceInputIndex::CbCrTexture as u64,
|
||||
Some(cb_cr_texture.as_texture_ref()),
|
||||
);
|
||||
|
||||
unsafe {
|
||||
let buffer_contents =
|
||||
(self.instances.contents() as *mut u8).add(*offset) as *mut SurfaceBounds;
|
||||
ptr::write(
|
||||
buffer_contents,
|
||||
SurfaceBounds {
|
||||
bounds: surface.bounds,
|
||||
content_mask: surface.content_mask.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
|
||||
*offset = next_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_pipeline_state(
|
||||
@@ -898,6 +1017,16 @@ enum SpriteInputIndex {
|
||||
AtlasTexture = 4,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
enum SurfaceInputIndex {
|
||||
Vertices = 0,
|
||||
Surfaces = 1,
|
||||
ViewportSize = 2,
|
||||
TextureSize = 3,
|
||||
YTexture = 4,
|
||||
CbCrTexture = 5,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
enum PathRasterizationInputIndex {
|
||||
Vertices = 0,
|
||||
@@ -911,3 +1040,10 @@ pub struct PathSprite {
|
||||
pub color: Hsla,
|
||||
pub tile: AtlasTile,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct SurfaceBounds {
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
}
|
||||
|
||||
@@ -469,6 +469,58 @@ fragment float4 path_sprite_fragment(
|
||||
return color;
|
||||
}
|
||||
|
||||
struct SurfaceVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 texture_position;
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct SurfaceFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 texture_position;
|
||||
};
|
||||
|
||||
vertex SurfaceVertexOutput surface_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint surface_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(SurfaceInputIndex_Vertices)]],
|
||||
constant SurfaceBounds *surfaces [[buffer(SurfaceInputIndex_Surfaces)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(SurfaceInputIndex_ViewportSize)]],
|
||||
constant Size_DevicePixels *texture_size
|
||||
[[buffer(SurfaceInputIndex_TextureSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
SurfaceBounds surface = surfaces[surface_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, surface.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, surface.bounds,
|
||||
surface.content_mask.bounds);
|
||||
// We are going to copy the whole texture, so the texture position corresponds
|
||||
// to the current vertex of the unit triangle.
|
||||
float2 texture_position = unit_vertex;
|
||||
return SurfaceVertexOutput{
|
||||
device_position,
|
||||
texture_position,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 surface_fragment(SurfaceFragmentInput input [[stage_in]],
|
||||
texture2d<float> y_texture
|
||||
[[texture(SurfaceInputIndex_YTexture)]],
|
||||
texture2d<float> cb_cr_texture
|
||||
[[texture(SurfaceInputIndex_CbCrTexture)]]) {
|
||||
constexpr sampler texture_sampler(mag_filter::linear, min_filter::linear);
|
||||
const float4x4 ycbcrToRGBTransform =
|
||||
float4x4(float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
|
||||
float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
|
||||
float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
|
||||
float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f));
|
||||
float4 ycbcr = float4(
|
||||
y_texture.sample(texture_sampler, input.texture_position).r,
|
||||
cb_cr_texture.sample(texture_sampler, input.texture_position).rg, 1.0);
|
||||
|
||||
return ycbcrToRGBTransform * ycbcr;
|
||||
}
|
||||
|
||||
float4 hsla_to_rgba(Hsla hsla) {
|
||||
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
|
||||
float s = hsla.s;
|
||||
|
||||
@@ -25,6 +25,7 @@ pub(crate) struct SceneBuilder {
|
||||
underlines: Vec<Underline>,
|
||||
monochrome_sprites: Vec<MonochromeSprite>,
|
||||
polychrome_sprites: Vec<PolychromeSprite>,
|
||||
surfaces: Vec<Surface>,
|
||||
}
|
||||
|
||||
impl Default for SceneBuilder {
|
||||
@@ -38,6 +39,7 @@ impl Default for SceneBuilder {
|
||||
underlines: Vec::new(),
|
||||
monochrome_sprites: Vec::new(),
|
||||
polychrome_sprites: Vec::new(),
|
||||
surfaces: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,6 +122,7 @@ impl SceneBuilder {
|
||||
(PrimitiveKind::PolychromeSprite, ix) => {
|
||||
self.polychrome_sprites[ix].order = draw_order as DrawOrder
|
||||
}
|
||||
(PrimitiveKind::Surface, ix) => self.surfaces[ix].order = draw_order as DrawOrder,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +132,7 @@ impl SceneBuilder {
|
||||
self.underlines.sort_unstable();
|
||||
self.monochrome_sprites.sort_unstable();
|
||||
self.polychrome_sprites.sort_unstable();
|
||||
self.surfaces.sort_unstable();
|
||||
|
||||
Scene {
|
||||
shadows: mem::take(&mut self.shadows),
|
||||
@@ -137,6 +141,7 @@ impl SceneBuilder {
|
||||
underlines: mem::take(&mut self.underlines),
|
||||
monochrome_sprites: mem::take(&mut self.monochrome_sprites),
|
||||
polychrome_sprites: mem::take(&mut self.polychrome_sprites),
|
||||
surfaces: mem::take(&mut self.surfaces),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +190,10 @@ impl SceneBuilder {
|
||||
sprite.order = layer_id;
|
||||
self.polychrome_sprites.push(sprite);
|
||||
}
|
||||
Primitive::Surface(mut surface) => {
|
||||
surface.order = layer_id;
|
||||
self.surfaces.push(surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +205,7 @@ pub(crate) struct Scene {
|
||||
pub underlines: Vec<Underline>,
|
||||
pub monochrome_sprites: Vec<MonochromeSprite>,
|
||||
pub polychrome_sprites: Vec<PolychromeSprite>,
|
||||
pub surfaces: Vec<Surface>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
@@ -224,6 +234,9 @@ impl Scene {
|
||||
polychrome_sprites: &self.polychrome_sprites,
|
||||
polychrome_sprites_start: 0,
|
||||
polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
|
||||
surfaces: &self.surfaces,
|
||||
surfaces_start: 0,
|
||||
surfaces_iter: self.surfaces.iter().peekable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,6 +260,9 @@ struct BatchIterator<'a> {
|
||||
polychrome_sprites: &'a [PolychromeSprite],
|
||||
polychrome_sprites_start: usize,
|
||||
polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
|
||||
surfaces: &'a [Surface],
|
||||
surfaces_start: usize,
|
||||
surfaces_iter: Peekable<slice::Iter<'a, Surface>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BatchIterator<'a> {
|
||||
@@ -272,6 +288,10 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||
self.polychrome_sprites_iter.peek().map(|s| s.order),
|
||||
PrimitiveKind::PolychromeSprite,
|
||||
),
|
||||
(
|
||||
self.surfaces_iter.peek().map(|s| s.order),
|
||||
PrimitiveKind::Surface,
|
||||
),
|
||||
];
|
||||
orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
|
||||
|
||||
@@ -378,6 +398,21 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||
sprites: &self.polychrome_sprites[sprites_start..sprites_end],
|
||||
})
|
||||
}
|
||||
PrimitiveKind::Surface => {
|
||||
let surfaces_start = self.surfaces_start;
|
||||
let mut surfaces_end = surfaces_start;
|
||||
while self
|
||||
.surfaces_iter
|
||||
.next_if(|surface| surface.order <= max_order)
|
||||
.is_some()
|
||||
{
|
||||
surfaces_end += 1;
|
||||
}
|
||||
self.surfaces_start = surfaces_end;
|
||||
Some(PrimitiveBatch::Surfaces(
|
||||
&self.surfaces[surfaces_start..surfaces_end],
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -391,6 +426,7 @@ pub enum PrimitiveKind {
|
||||
Underline,
|
||||
MonochromeSprite,
|
||||
PolychromeSprite,
|
||||
Surface,
|
||||
}
|
||||
|
||||
pub enum Primitive {
|
||||
@@ -400,6 +436,7 @@ pub enum Primitive {
|
||||
Underline(Underline),
|
||||
MonochromeSprite(MonochromeSprite),
|
||||
PolychromeSprite(PolychromeSprite),
|
||||
Surface(Surface),
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
@@ -411,6 +448,7 @@ impl Primitive {
|
||||
Primitive::Underline(underline) => &underline.bounds,
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
|
||||
Primitive::Surface(surface) => &surface.bounds,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,6 +460,7 @@ impl Primitive {
|
||||
Primitive::Underline(underline) => &underline.content_mask,
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::Surface(surface) => &surface.content_mask,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,6 +479,7 @@ pub(crate) enum PrimitiveBatch<'a> {
|
||||
texture_id: AtlasTextureId,
|
||||
sprites: &'a [PolychromeSprite],
|
||||
},
|
||||
Surfaces(&'a [Surface]),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||
@@ -593,6 +633,32 @@ impl From<PolychromeSprite> for Primitive {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Surface {
|
||||
pub order: u32,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
pub image_buffer: media::core_video::CVImageBuffer,
|
||||
}
|
||||
|
||||
impl Ord for Surface {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.order.cmp(&other.order)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Surface {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Surface> for Primitive {
|
||||
fn from(surface: Surface) -> Self {
|
||||
Primitive::Surface(surface)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PathId(pub(crate) usize);
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ use crate::{
|
||||
MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
|
||||
PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
|
||||
RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
|
||||
Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
|
||||
VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
||||
Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline,
|
||||
UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::HashMap;
|
||||
@@ -18,6 +18,7 @@ use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
StreamExt,
|
||||
};
|
||||
use media::core_video::CVImageBuffer;
|
||||
use parking_lot::RwLock;
|
||||
use slotmap::SlotMap;
|
||||
use smallvec::SmallVec;
|
||||
@@ -1116,6 +1117,23 @@ impl<'a> WindowContext<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Paint a surface into the scene for the current frame at the current z-index.
|
||||
pub fn paint_surface(&mut self, bounds: Bounds<Pixels>, image_buffer: CVImageBuffer) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let bounds = bounds.scale(scale_factor);
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
let window = &mut *self.window;
|
||||
window.current_frame.scene_builder.insert(
|
||||
&window.current_frame.z_index_stack,
|
||||
Surface {
|
||||
order: 0,
|
||||
bounds,
|
||||
content_mask,
|
||||
image_buffer,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Draw pixels to the display for this window based on the contents of its scene.
|
||||
pub(crate) fn draw(&mut self) {
|
||||
let root_view = self.window.root_view.take().unwrap();
|
||||
|
||||
@@ -55,9 +55,8 @@ impl Render for PlayerStory {
|
||||
.border_2()
|
||||
.border_color(player.cursor)
|
||||
.child(
|
||||
img()
|
||||
img("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.rounded_full()
|
||||
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.size_6()
|
||||
.bg(gpui::red()),
|
||||
)
|
||||
@@ -67,51 +66,62 @@ impl Render for PlayerStory {
|
||||
.child(div().flex().gap_1().children(
|
||||
cx.theme().players().0.clone().iter_mut().map(|player| {
|
||||
div()
|
||||
.my_1()
|
||||
.rounded_xl()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_8()
|
||||
.py_0p5()
|
||||
.px_1p5()
|
||||
.bg(player.background)
|
||||
.child(
|
||||
div().relative().neg_mx_1().rounded_full().z_index(3)
|
||||
.my_1()
|
||||
.rounded_xl()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h_8()
|
||||
.py_0p5()
|
||||
.px_1p5()
|
||||
.bg(player.background)
|
||||
.child(
|
||||
div()
|
||||
.relative()
|
||||
.neg_mx_1()
|
||||
.rounded_full()
|
||||
.z_index(3)
|
||||
.border_2()
|
||||
.border_color(player.background)
|
||||
.size(px(28.))
|
||||
.child(
|
||||
img()
|
||||
.rounded_full()
|
||||
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.size(px(24.))
|
||||
.bg(gpui::red()),
|
||||
),
|
||||
).child(
|
||||
div().relative().neg_mx_1().rounded_full().z_index(2)
|
||||
.border_2()
|
||||
.border_color(player.background)
|
||||
.size(px(28.))
|
||||
.child(
|
||||
img()
|
||||
.rounded_full()
|
||||
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.size(px(24.))
|
||||
.bg(gpui::red()),
|
||||
),
|
||||
).child(
|
||||
div().relative().neg_mx_1().rounded_full().z_index(1)
|
||||
.border_2()
|
||||
.border_color(player.background)
|
||||
.size(px(28.))
|
||||
img("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.rounded_full()
|
||||
.size(px(24.))
|
||||
.bg(gpui::red()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
img()
|
||||
.rounded_full()
|
||||
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.size(px(24.))
|
||||
.bg(gpui::red()),
|
||||
),
|
||||
)
|
||||
div()
|
||||
.relative()
|
||||
.neg_mx_1()
|
||||
.rounded_full()
|
||||
.z_index(2)
|
||||
.border_2()
|
||||
.border_color(player.background)
|
||||
.size(px(28.))
|
||||
.child(
|
||||
img("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.rounded_full()
|
||||
.size(px(24.))
|
||||
.bg(gpui::red()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.relative()
|
||||
.neg_mx_1()
|
||||
.rounded_full()
|
||||
.z_index(1)
|
||||
.border_2()
|
||||
.border_color(player.background)
|
||||
.size(px(28.))
|
||||
.child(
|
||||
img("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.rounded_full()
|
||||
.size(px(24.))
|
||||
.bg(gpui::red()),
|
||||
),
|
||||
)
|
||||
}),
|
||||
))
|
||||
.child(Story::label("Player Selections"))
|
||||
|
||||
@@ -21,7 +21,7 @@ impl RenderOnce for Avatar {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
let mut img = img();
|
||||
let mut img = img(self.src);
|
||||
|
||||
if self.shape == Shape::Circle {
|
||||
img = img.rounded_full();
|
||||
@@ -34,8 +34,7 @@ impl RenderOnce for Avatar {
|
||||
div()
|
||||
.size(size)
|
||||
.child(
|
||||
img.source(self.src.clone())
|
||||
.size(size)
|
||||
img.size(size)
|
||||
// todo!(Pull the avatar fallback background from the theme.)
|
||||
.bg(gpui::red()),
|
||||
)
|
||||
|
||||
@@ -326,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
|
||||
}
|
||||
|
||||
@@ -409,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),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -468,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,
|
||||
@@ -546,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,
|
||||
@@ -760,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,
|
||||
@@ -2884,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 {
|
||||
@@ -3385,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));
|
||||
|
||||
Reference in New Issue
Block a user