title_bar: Improve design for call controls (#58691)
For the longest time, we've had a labeled "Share" button on the title bar whenever you're in a call. However, it was extremely easy to click that button thinking you'd be sharing _your screen_ instead of your project. At the end of the day, there wasn't any specific cue that'd allow you to know what you'd be sharing. This is why people just assumed you'd share the screen, which is arguably way more common than sharing the project. We've seen users bump into this confusion countless times, and this also even happens to us (the whole Zed staff) weekly whenever we're in meetings. So, this PR changes that button to be an icon button instead, using a folder icon to better communicate it means sharing the project. I also took advantage of the opportunity to change the approach we use for adding these buttons, using `when` instead of `children.push`; I think the former is just easier to understand, allowing to more easily picture the UI in your (my) head. Lastly, also improved what we display in the tooltip for the project share button, by showing the names of all projects you'll be sharing, as well as the tooltip that displayed call diagnostics. | Before | After | |--------|--------| | <img width="778" height="360" alt="Screenshot 2026-06-05 at 3 48 2@2x" src="https://github.com/user-attachments/assets/363593fb-79f8-42e9-83b9-345bc731846f" /> | <img width="778" height="360" alt="Screenshot 2026-06-05 at 3 48 3@2x" src="https://github.com/user-attachments/assets/32afc412-a1fb-45a4-a19e-74e4923c0c8b" /> | Release Notes: - Improved call controls design, making it clearer what button shares _the project_ vs. _the screen_.
This commit is contained in:
1 parent
918e68017f
commit
f816f346e4
5 files changed
+294
-224
No files matched your search
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.99994 11.3581V7.54178" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.90948 8.98791L7.99999 7.07844L6.09053 8.98791" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.1715 13.387C13.5144 13.387 13.8432 13.2535 14.0858 13.0157C14.3281 12.7781 14.4644 12.4557 14.4644 12.1195V5.78184C14.4644 5.44567 14.3281 5.12328 14.0858 4.88557C13.8432 4.64787 13.5144 4.51432 13.1715 4.51432H8.06464C7.84841 4.5164 7.63512 4.46528 7.44426 4.36563C7.2534 4.26598 7.09108 4.121 6.97217 3.94394L6.44855 3.18342C6.33083 3.00817 6.17058 2.86431 5.98215 2.76476C5.79373 2.6652 5.58304 2.61308 5.36901 2.61304H2.82852C2.48562 2.61304 2.15677 2.74658 1.91433 2.98428C1.67186 3.22199 1.53564 3.54439 1.53564 3.88056V12.1195C1.53564 12.4557 1.67186 12.7781 1.91433 13.0157C2.15677 13.2535 2.48562 13.387 2.82852 13.387H13.1715Z" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.99994 11.3581V7.54178" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.90948 8.98791L7.99999 7.07844L6.09053 8.98791" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.1715 13.387C13.5144 13.387 13.8432 13.2535 14.0858 13.0157C14.3281 12.7781 14.4644 12.4557 14.4644 12.1195V5.78184C14.4644 5.44567 14.3281 5.12328 14.0858 4.88557C13.8432 4.64787 13.5144 4.51432 13.1715 4.51432H8.06464C7.84841 4.5164 7.63512 4.46528 7.44426 4.36563C7.2534 4.26598 7.09108 4.121 6.97217 3.94394L6.44855 3.18342C6.33083 3.00817 6.17058 2.86431 5.98215 2.76476C5.79373 2.6652 5.58304 2.61308 5.36901 2.61304H2.82852C2.48562 2.61304 2.15677 2.74658 1.91433 2.98428C1.67186 3.22199 1.53564 3.54439 1.53564 3.88056V12.1195C1.53564 12.4557 1.67186 12.7781 1.91433 13.0157C2.15677 13.2535 2.48562 13.387 2.82852 13.387H13.1715Z" fill="#D9D9D9" fill-opacity="0.15" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -140,6 +140,8 @@ pub enum IconName {
|
||||
FolderOpen,
|
||||
FolderOpenAdd,
|
||||
FolderSearch,
|
||||
FolderShare,
|
||||
FolderShared,
|
||||
Font,
|
||||
FontSize,
|
||||
FontWeight,
|
||||
|
||||
+281
-223
@@ -5,7 +5,7 @@ use call::{ActiveCall, Room};
|
||||
use channel::ChannelStore;
|
||||
use client::{User, proto::PeerId};
|
||||
use gpui::{
|
||||
AnyElement, Hsla, IntoElement, MouseButton, Path, ScreenCaptureSource, Styled, TaskExt,
|
||||
AnyElement, Empty, Hsla, IntoElement, MouseButton, Path, ScreenCaptureSource, Styled, TaskExt,
|
||||
WeakEntity, canvas, point,
|
||||
};
|
||||
use gpui::{App, Task, Window};
|
||||
@@ -18,7 +18,8 @@ use settings::{Settings as _, SettingsLocation};
|
||||
use theme::ActiveTheme;
|
||||
use ui::{
|
||||
Avatar, AvatarAudioStatusIndicator, ContextMenu, ContextMenuItem, Divider, DividerColor,
|
||||
Facepile, PopoverMenu, SplitButton, SplitButtonStyle, TintColor, Tooltip, prelude::*,
|
||||
Facepile, KeyBinding, PopoverMenu, SplitButton, SplitButtonStyle, TintColor, Tooltip,
|
||||
prelude::*,
|
||||
};
|
||||
use util::rel_path::RelPath;
|
||||
use workspace::{ParticipantLocation, notifications::DetachAndPromptErr};
|
||||
@@ -336,9 +337,9 @@ impl TitleBar {
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Vec<AnyElement> {
|
||||
) -> AnyElement {
|
||||
let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() else {
|
||||
return Vec::new();
|
||||
return Empty.into_any_element();
|
||||
};
|
||||
|
||||
let is_connecting_to_project = self
|
||||
@@ -372,8 +373,6 @@ impl TitleBar {
|
||||
.channel_id()
|
||||
.and_then(|channel_id| channel_store.read(cx).channel_for_id(channel_id).cloned());
|
||||
|
||||
let mut children = Vec::new();
|
||||
|
||||
let effective_quality = stats.effective_quality.unwrap_or(ConnectionQuality::Lost);
|
||||
let (signal_icon, signal_color, quality_label) = match effective_quality {
|
||||
ConnectionQuality::Excellent => {
|
||||
@@ -386,246 +385,305 @@ impl TitleBar {
|
||||
|
||||
let quality_label: SharedString = quality_label.into();
|
||||
|
||||
children.push(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
IconButton::new("leave-call", IconName::Exit)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(Tooltip::text("Leave Call"))
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(move |_, _window, cx| {
|
||||
ActiveCall::global(cx)
|
||||
.update(cx, |call, cx| call.hang_up(cx))
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
)
|
||||
.child(Divider::vertical().color(DividerColor::Border))
|
||||
.into_any_element(),
|
||||
);
|
||||
|
||||
children.push(
|
||||
IconButton::new("call-quality", signal_icon)
|
||||
.icon_size(IconSize::Small)
|
||||
.when_some(signal_color, |button, color| button.icon_color(color))
|
||||
.tooltip(move |_window, cx| {
|
||||
let quality_label = quality_label.clone();
|
||||
let latency = format_stat(stats.latency_ms, |v| format!("{:.0}ms", v));
|
||||
let jitter = format_stat(stats.jitter_ms, |v| format!("{:.0}ms", v));
|
||||
let packet_loss = format_stat(stats.packet_loss_pct, |v| format!("{:.1}%", v));
|
||||
let input_lag =
|
||||
format_stat(stats.input_lag.map(|d| d.as_secs_f64() * 1000.0), |v| {
|
||||
format!("{:.1}ms", v)
|
||||
});
|
||||
|
||||
Tooltip::with_meta(
|
||||
format!("Connection: {quality_label}"),
|
||||
Some(&ShowCallStats),
|
||||
format!(
|
||||
"Latency: {latency} · Jitter: {jitter} · Loss: {packet_loss} · Input lag: {input_lag}",
|
||||
),
|
||||
cx,
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.pr_1p5()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
IconButton::new("leave-call", IconName::Exit)
|
||||
.tooltip(Tooltip::text("Leave Call"))
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(move |_, _window, cx| {
|
||||
ActiveCall::global(cx)
|
||||
.update(cx, |call, cx| call.hang_up(cx))
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
)
|
||||
})
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(Box::new(ShowCallStats), cx);
|
||||
})
|
||||
.into_any_element(),
|
||||
);
|
||||
.child(Divider::vertical().color(DividerColor::Border)),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("call-quality", signal_icon)
|
||||
.icon_size(IconSize::Small)
|
||||
.when_some(signal_color, |button, color| button.icon_color(color))
|
||||
.tooltip(Tooltip::element(move |window, cx| {
|
||||
let quality_label = quality_label.clone();
|
||||
let latency = format_stat(stats.latency_ms, |v| format!("{:.0}ms", v));
|
||||
let jitter = format_stat(stats.jitter_ms, |v| format!("{:.0}ms", v));
|
||||
let packet_loss =
|
||||
format_stat(stats.packet_loss_pct, |v| format!("{:.1}%", v));
|
||||
let input_lag =
|
||||
format_stat(stats.input_lag.map(|d| d.as_secs_f64() * 1000.0), |v| {
|
||||
format!("{:.1}ms", v)
|
||||
});
|
||||
|
||||
if is_local && can_share_projects && !is_connecting_to_project {
|
||||
let is_sharing_disabled = channel.is_some_and(|channel| match channel.visibility {
|
||||
proto::ChannelVisibility::Public => project.visible_worktrees(cx).any(|worktree| {
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
let key_binding = KeyBinding::for_action(&ShowCallStats, cx);
|
||||
let has_key_binding = key_binding.has_binding(window);
|
||||
|
||||
let settings_location = Some(SettingsLocation {
|
||||
worktree_id,
|
||||
path: RelPath::empty(),
|
||||
});
|
||||
let stat_row = |label: &'static str, value: String| {
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.gap_4()
|
||||
.child(Label::new(label).size(LabelSize::Small).color(Color::Muted))
|
||||
.child(Label::new(value).size(LabelSize::Small))
|
||||
};
|
||||
|
||||
WorktreeSettings::get(settings_location, cx).prevent_sharing_in_public_channels
|
||||
}),
|
||||
proto::ChannelVisibility::Members => false,
|
||||
});
|
||||
|
||||
children.push(
|
||||
Button::new(
|
||||
"toggle_sharing",
|
||||
if is_shared { "Unshare" } else { "Share" },
|
||||
v_flex()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_4()
|
||||
.justify_between()
|
||||
.child(Label::new(format!("Connection: {quality_label}")))
|
||||
.when(has_key_binding, |this| this.child(key_binding)),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.child(stat_row("Latency", latency))
|
||||
.child(stat_row("Jitter", jitter))
|
||||
.child(stat_row("Packet loss", packet_loss))
|
||||
.child(stat_row("Input lag", input_lag)),
|
||||
)
|
||||
.into_any_element()
|
||||
}))
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(Box::new(ShowCallStats), cx);
|
||||
}),
|
||||
)
|
||||
.when(can_use_microphone, |this| {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
"mute-microphone",
|
||||
if is_muted {
|
||||
IconName::MicMute
|
||||
} else {
|
||||
IconName::Mic
|
||||
},
|
||||
)
|
||||
.tooltip(move |_window, cx| {
|
||||
if is_muted {
|
||||
if is_deafened {
|
||||
Tooltip::with_meta(
|
||||
"Unmute Microphone",
|
||||
None,
|
||||
"Audio will be unmuted",
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::simple("Unmute Microphone", cx)
|
||||
}
|
||||
} else {
|
||||
Tooltip::simple("Mute Microphone", cx)
|
||||
}
|
||||
})
|
||||
.icon_size(IconSize::Small)
|
||||
.toggle_state(is_muted)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Error))
|
||||
.on_click(move |_, _window, cx| toggle_mute(cx)),
|
||||
)
|
||||
.tooltip(Tooltip::text(if is_shared {
|
||||
"Stop sharing project with call participants"
|
||||
} else {
|
||||
"Share project with call participants"
|
||||
}))
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.toggle_state(is_shared)
|
||||
.label_size(LabelSize::Small)
|
||||
.when(is_sharing_disabled, |parent| {
|
||||
parent.disabled(true).tooltip(Tooltip::text(
|
||||
"This project may not be shared in a public channel.",
|
||||
))
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
if is_shared {
|
||||
this.unshare_project(window, cx);
|
||||
} else {
|
||||
this.share_project(cx);
|
||||
}
|
||||
}))
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
|
||||
if can_use_microphone {
|
||||
children.push(
|
||||
})
|
||||
.child(
|
||||
IconButton::new(
|
||||
"mute-microphone",
|
||||
if is_muted {
|
||||
IconName::MicMute
|
||||
"mute-sound",
|
||||
if is_deafened {
|
||||
IconName::AudioOff
|
||||
} else {
|
||||
IconName::Mic
|
||||
IconName::AudioOn
|
||||
},
|
||||
)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Error))
|
||||
.icon_size(IconSize::Small)
|
||||
.toggle_state(is_deafened)
|
||||
.tooltip(move |_window, cx| {
|
||||
if is_muted {
|
||||
if is_deafened {
|
||||
Tooltip::with_meta(
|
||||
"Unmute Microphone",
|
||||
None,
|
||||
"Audio will be unmuted",
|
||||
cx,
|
||||
)
|
||||
if is_deafened {
|
||||
let label = "Unmute Audio";
|
||||
|
||||
if !muted_by_user {
|
||||
Tooltip::with_meta(label, None, "Microphone will be unmuted", cx)
|
||||
} else {
|
||||
Tooltip::simple("Unmute Microphone", cx)
|
||||
Tooltip::simple(label, cx)
|
||||
}
|
||||
} else {
|
||||
Tooltip::simple("Mute Microphone", cx)
|
||||
let label = "Mute Audio";
|
||||
|
||||
if !muted_by_user {
|
||||
Tooltip::with_meta(label, None, "Microphone will be muted", cx)
|
||||
} else {
|
||||
Tooltip::simple(label, cx)
|
||||
}
|
||||
}
|
||||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.toggle_state(is_muted)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Error))
|
||||
.on_click(move |_, _window, cx| toggle_mute(cx))
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
.on_click(move |_, _, cx| toggle_deafen(cx)),
|
||||
)
|
||||
.when(
|
||||
is_local && can_share_projects && !is_connecting_to_project,
|
||||
|this| {
|
||||
let is_sharing_disabled =
|
||||
channel.is_some_and(|channel| match channel.visibility {
|
||||
proto::ChannelVisibility::Public => {
|
||||
project.visible_worktrees(cx).any(|worktree| {
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
|
||||
children.push(
|
||||
IconButton::new(
|
||||
"mute-sound",
|
||||
if is_deafened {
|
||||
IconName::AudioOff
|
||||
} else {
|
||||
IconName::AudioOn
|
||||
let settings_location = Some(SettingsLocation {
|
||||
worktree_id,
|
||||
path: RelPath::empty(),
|
||||
});
|
||||
|
||||
WorktreeSettings::get(settings_location, cx)
|
||||
.prevent_sharing_in_public_channels
|
||||
})
|
||||
}
|
||||
proto::ChannelVisibility::Members => false,
|
||||
});
|
||||
|
||||
let icon = if is_shared {
|
||||
IconName::FolderShared
|
||||
} else {
|
||||
IconName::FolderShare
|
||||
};
|
||||
|
||||
let folder_names = project
|
||||
.visible_worktrees(cx)
|
||||
.filter_map(|worktree| {
|
||||
worktree
|
||||
.read(cx)
|
||||
.root_name()
|
||||
.file_name()
|
||||
.map(|name| name.to_string())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let folder_list = folder_names.join(", ");
|
||||
|
||||
let unshare_meta: SharedString = if folder_list.is_empty() {
|
||||
"Stop sharing project with call participants".into()
|
||||
} else {
|
||||
format!("Stop sharing {folder_list} with call participants").into()
|
||||
};
|
||||
let share_meta: SharedString = if folder_list.is_empty() {
|
||||
"Share active project with call participants".into()
|
||||
} else {
|
||||
format!("Share {folder_list} with call participants").into()
|
||||
};
|
||||
|
||||
this.child(
|
||||
IconButton::new("toggle_sharing", icon)
|
||||
.icon_size(IconSize::Small)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.toggle_state(is_shared)
|
||||
.map(|this| {
|
||||
if is_shared {
|
||||
this.tooltip(move |_, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Unshare Project",
|
||||
None,
|
||||
unshare_meta.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(
|
||||
move |this, _, window, cx| {
|
||||
this.unshare_project(window, cx);
|
||||
},
|
||||
))
|
||||
} else if is_sharing_disabled {
|
||||
this.disabled(true).tooltip(Tooltip::text(
|
||||
"This project may not be shared in a public channel.",
|
||||
))
|
||||
} else {
|
||||
this.tooltip(move |_, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Share Project",
|
||||
None,
|
||||
share_meta.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(
|
||||
move |this, _, _, cx| {
|
||||
this.share_project(cx);
|
||||
},
|
||||
))
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Error))
|
||||
.icon_size(IconSize::Small)
|
||||
.toggle_state(is_deafened)
|
||||
.tooltip(move |_window, cx| {
|
||||
if is_deafened {
|
||||
let label = "Unmute Audio";
|
||||
.when(can_use_microphone && screen_sharing_supported, |parent| {
|
||||
#[cfg(target_os = "linux")]
|
||||
let is_wayland = gpui::guess_compositor() == "Wayland";
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let is_wayland = false;
|
||||
|
||||
if !muted_by_user {
|
||||
Tooltip::with_meta(label, None, "Microphone will be unmuted", cx)
|
||||
let trigger = IconButton::new("screen-share", IconName::Screen)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.toggle_state(is_screen_sharing)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.tooltip(Tooltip::text(if is_screen_sharing {
|
||||
"Stop Sharing Screen"
|
||||
} else {
|
||||
Tooltip::simple(label, cx)
|
||||
}
|
||||
"Share Screen"
|
||||
}))
|
||||
.on_click(move |_, window, cx| {
|
||||
let should_share = ActiveCall::global(cx)
|
||||
.read(cx)
|
||||
.room()
|
||||
.is_some_and(|room| !room.read(cx).is_sharing_screen());
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if is_wayland
|
||||
&& let Some(room) = ActiveCall::global(cx).read(cx).room().cloned()
|
||||
{
|
||||
let task = room.update(cx, |room, cx| {
|
||||
if should_share {
|
||||
room.share_screen_wayland(cx)
|
||||
} else {
|
||||
room.unshare_screen(true, cx)
|
||||
.map(|()| Task::ready(Ok(())))
|
||||
.unwrap_or_else(|e| Task::ready(Err(e)))
|
||||
}
|
||||
});
|
||||
task.detach_and_prompt_err(
|
||||
"Sharing Screen Failed",
|
||||
window,
|
||||
cx,
|
||||
|e, _, _| Some(format!("{e:?}")),
|
||||
);
|
||||
}
|
||||
}
|
||||
if !is_wayland {
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let screen = if should_share {
|
||||
cx.update(|_, cx| pick_default_screen(cx))?.await
|
||||
} else {
|
||||
Ok(None)
|
||||
};
|
||||
cx.update(|window, cx| {
|
||||
toggle_screen_sharing(screen, window, cx)
|
||||
})?;
|
||||
|
||||
Result::<_, anyhow::Error>::Ok(())
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
});
|
||||
|
||||
if is_wayland {
|
||||
parent.child(trigger)
|
||||
} else {
|
||||
let label = "Mute Audio";
|
||||
|
||||
if !muted_by_user {
|
||||
Tooltip::with_meta(label, None, "Microphone will be muted", cx)
|
||||
} else {
|
||||
Tooltip::simple(label, cx)
|
||||
}
|
||||
parent.child(
|
||||
SplitButton::new(
|
||||
trigger.render(window, cx),
|
||||
self.render_screen_list().into_any_element(),
|
||||
)
|
||||
.style(SplitButtonStyle::Transparent),
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |_, _, cx| toggle_deafen(cx))
|
||||
.into_any_element(),
|
||||
);
|
||||
|
||||
if can_use_microphone && screen_sharing_supported {
|
||||
#[cfg(target_os = "linux")]
|
||||
let is_wayland = gpui::guess_compositor() == "Wayland";
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let is_wayland = false;
|
||||
|
||||
let trigger = IconButton::new("screen-share", IconName::Screen)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.toggle_state(is_screen_sharing)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.tooltip(Tooltip::text(if is_screen_sharing {
|
||||
"Stop Sharing Screen"
|
||||
} else {
|
||||
"Share Screen"
|
||||
}))
|
||||
.on_click(move |_, window, cx| {
|
||||
let should_share = ActiveCall::global(cx)
|
||||
.read(cx)
|
||||
.room()
|
||||
.is_some_and(|room| !room.read(cx).is_sharing_screen());
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if is_wayland
|
||||
&& let Some(room) = ActiveCall::global(cx).read(cx).room().cloned()
|
||||
{
|
||||
let task = room.update(cx, |room, cx| {
|
||||
if should_share {
|
||||
room.share_screen_wayland(cx)
|
||||
} else {
|
||||
room.unshare_screen(true, cx)
|
||||
.map(|()| Task::ready(Ok(())))
|
||||
.unwrap_or_else(|e| Task::ready(Err(e)))
|
||||
}
|
||||
});
|
||||
task.detach_and_prompt_err(
|
||||
"Sharing Screen Failed",
|
||||
window,
|
||||
cx,
|
||||
|e, _, _| Some(format!("{e:?}")),
|
||||
);
|
||||
}
|
||||
}
|
||||
if !is_wayland {
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let screen = if should_share {
|
||||
cx.update(|_, cx| pick_default_screen(cx))?.await
|
||||
} else {
|
||||
Ok(None)
|
||||
};
|
||||
cx.update(|window, cx| toggle_screen_sharing(screen, window, cx))?;
|
||||
|
||||
Result::<_, anyhow::Error>::Ok(())
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
});
|
||||
|
||||
if is_wayland {
|
||||
children.push(trigger.into_any_element());
|
||||
} else {
|
||||
children.push(
|
||||
SplitButton::new(
|
||||
trigger.render(window, cx),
|
||||
self.render_screen_list().into_any_element(),
|
||||
)
|
||||
.style(SplitButtonStyle::Transparent)
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
children.push(div().pr_2().into_any_element());
|
||||
|
||||
children
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_screen_list(&self) -> impl IntoElement {
|
||||
|
||||
@@ -362,7 +362,7 @@ impl Render for TitleBar {
|
||||
})
|
||||
.gap_1()
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.children(self.render_call_controls(window, cx))
|
||||
.child(self.render_call_controls(window, cx))
|
||||
.children(self.render_connection_status(status, cx))
|
||||
.child(self.update_version.clone())
|
||||
.when(
|
||||
|
||||
Reference in new issue
Block a user