Vectrum
Sign In
160 lines · 5.8 KB · Rust
//! The right panel, as T3 Code has it: it shows one surface at a time (the
//! subagents, the browser 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,
    Terminal,
}

/// The surfaces the picker lists: each with its icon, name and key.
const SURFACES: [(Surface, &str, &str); 3] =
    [(Surface::Subagents, "Subagents", "S"), (Surface::Browser, "Browser", "B"), (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 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),
                _ => 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)),
            )
    }
}