//! The right panel, as T3 Code has it: it shows one surface at a time (the
//! subagents, the browser, a device or a terminal), and opens on "Open a surface", a
//! short list to pick one from, each with a key of its own.
use gpui_kit::component::{Icon, IconName, h_flex, v_flex};
use gpui_kit::prelude::FluentBuilder as _;
use gpui_kit::*;
use super::Workspace;
use crate::ui::icons::Lucide;
use crate::ui::metal::{self, MetalStyled as _};
/// What the right panel shows.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Surface {
/// "Open a surface": the list of the others.
Picker,
Subagents,
Browser,
Device,
Terminal,
}
/// The surfaces the picker lists: each with its icon, name and key.
const SURFACES: [(Surface, &str, &str); 4] = [
(Surface::Subagents, "Subagents", "S"),
(Surface::Browser, "Browser", "B"),
(Surface::Device, "Device", "D"),
(Surface::Terminal, "Terminal", "T"),
];
/// How wide a terminal in the right panel is.
pub(super) const TERMINAL_PANEL_WIDTH: f32 = 520.;
impl Workspace {
pub(super) fn surface_open(&self, surface: Surface) -> bool {
self.right_panel == Some(surface)
}
/// The right panel's button: opens "Open a surface", or closes the panel.
pub(super) fn toggle_right_panel(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.right_panel.is_some() {
self.right_panel = None;
} else {
self.right_panel = Some(Surface::Picker);
self.surface_focus.focus(window, cx);
}
cx.notify();
}
/// Shows a surface in the right panel, or closes the panel if it's
/// showing it already.
pub(super) fn toggle_surface(&mut self, surface: Surface, window: &mut Window, cx: &mut Context<Self>) {
if self.right_panel == Some(surface) {
self.right_panel = None;
cx.notify();
return;
}
self.open_surface(surface, window, cx);
}
pub(super) fn open_surface(&mut self, surface: Surface, window: &mut Window, cx: &mut Context<Self>) {
if surface == Surface::Terminal {
if !self.terminal_available(cx) {
return;
}
// The terminals move over from the drawer, if it's open.
self.dock_terminal(window, cx);
}
self.right_panel = Some(surface);
if surface == Surface::Picker {
self.surface_focus.focus(window, cx);
}
cx.notify();
}
/// The picker's keys: S, B, D and T open their surfaces; Escape closes it.
fn surface_key(&mut self, event: &KeyDownEvent, window: &mut Window, cx: &mut Context<Self>) {
let keystroke = &event.keystroke;
if keystroke.modifiers.modified() {
return;
}
if keystroke.key == "escape" {
self.right_panel = None;
cx.notify();
return;
}
if let Some((surface, _, _)) = SURFACES.iter().find(|(_, _, key)| key.eq_ignore_ascii_case(&keystroke.key)) {
cx.stop_propagation();
self.open_surface(*surface, window, cx);
}
}
pub(super) fn render_surface_picker(&self, cx: &mut Context<Self>) -> impl IntoElement {
let p = metal::palette(cx);
let terminal = self.terminal_available(cx);
let rows = SURFACES.iter().map(|(surface, name, key)| {
let surface = *surface;
let enabled = surface != Surface::Terminal || terminal;
let icon = match surface {
Surface::Subagents => Icon::new(Lucide::Bot),
Surface::Browser => Icon::new(IconName::Globe),
Surface::Device => Icon::new(Lucide::Smartphone),
_ => Icon::new(Lucide::SquareTerminal),
};
h_flex()
.id(SharedString::from(format!("surface-{name}")))
.h(px(31.))
.px(px(10.))
.gap(px(10.))
.rounded(px(8.))
.text_size(px(13.5))
.text_color(if enabled { p.ink } else { p.ink3 })
.when(enabled, |this| {
this.cursor_pointer()
.hover(|style| style.bg(p.row_hover))
.on_click(cx.listener(move |this, _, window, cx| this.open_surface(surface, window, cx)))
})
.child(icon.size(px(16.)).text_color(if enabled { p.ink2 } else { p.ink3 }))
.child(div().flex_1().child(*name))
.child(
div()
.flex_none()
.min_w(px(18.))
.h(px(18.))
.px(px(5.))
.flex()
.items_center()
.justify_center()
.rounded(px(4.))
.bg(p.row_hover)
.font_family(metal::MONO)
.text_size(px(10.5))
.text_color(p.ink3)
.child(*key),
)
});
v_flex()
.id("surface-picker")
.key_context("SurfacePicker")
.track_focus(&self.surface_focus)
.on_key_down(cx.listener(Self::surface_key))
.w(px(super::PANEL_WIDTH))
.flex_none()
.h_full()
.p(px(12.))
.pl(px(0.))
.child(
v_flex()
.flex_1()
.justify_center()
.px(px(16.))
.rounded(px(18.))
.raised(p)
.child(
div()
.pb(px(14.))
.text_center()
.text_size(px(14.))
.font_weight(FontWeight::MEDIUM)
.child("Open a surface"),
)
.child(v_flex().gap(px(2.)).children(rows)),
)
}
}