This commit is contained in:
+627
-86
@@ -1,85 +1,445 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use gpui::{
|
||||
App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px,
|
||||
rgb, size,
|
||||
App, Application, Bounds, Context, FocusHandle, KeyBinding, KeyDownEvent, Window, WindowBounds,
|
||||
WindowOptions, actions, div, prelude::*, px, rgb, size,
|
||||
};
|
||||
use lumbridge_spike_model::{
|
||||
FOOTER_CENTER, FOOTER_LEFT, FOOTER_RIGHT, PANES, PaneFixture, WORKSPACES,
|
||||
ActionOutcome, FOOTER_RIGHT, FocusDirection, PaneId, PaneState, ShellAction, ShellModel,
|
||||
SurfaceKind, WORKSPACES,
|
||||
};
|
||||
|
||||
const BG: u32 = 0x0c0e13;
|
||||
const PANEL: u32 = 0x121722;
|
||||
const PANEL_ALT: u32 = 0x171d29;
|
||||
const BORDER: u32 = 0x293244;
|
||||
const TEXT: u32 = 0xd9e2f2;
|
||||
const MUTED: u32 = 0x7f8ba3;
|
||||
const ACCENT: u32 = 0x77bdfb;
|
||||
const BG: u32 = 0x090c12;
|
||||
const PANEL: u32 = 0x101620;
|
||||
const PANEL_ALT: u32 = 0x151d29;
|
||||
const PANEL_ACTIVE: u32 = 0x182334;
|
||||
const BORDER: u32 = 0x263246;
|
||||
const BORDER_QUIET: u32 = 0x1c2636;
|
||||
const TEXT: u32 = 0xdbe5f4;
|
||||
const MUTED: u32 = 0x8290a8;
|
||||
const ACCENT: u32 = 0x68b5f8;
|
||||
const ATTENTION: u32 = 0xf1b96a;
|
||||
const SUCCESS: u32 = 0x70d6a8;
|
||||
const TIMING_SAMPLE_LIMIT: usize = 256;
|
||||
|
||||
struct LumbridgeShell;
|
||||
actions!(
|
||||
lumbridge,
|
||||
[
|
||||
FocusLeft,
|
||||
FocusRight,
|
||||
FocusUp,
|
||||
FocusDown,
|
||||
OpenPalette,
|
||||
ClosePalette,
|
||||
SelectPane1,
|
||||
SelectPane2,
|
||||
SelectPane3,
|
||||
SelectPane4,
|
||||
SelectPane5,
|
||||
SelectPane6,
|
||||
]
|
||||
);
|
||||
|
||||
fn pane_card(pane: &PaneFixture) -> impl IntoElement {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.min_w_0()
|
||||
.min_h_0()
|
||||
.overflow_hidden()
|
||||
.bg(rgb(PANEL))
|
||||
.border_1()
|
||||
.border_color(rgb(BORDER))
|
||||
.rounded_md()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.px_3()
|
||||
.h(px(34.0))
|
||||
.bg(rgb(PANEL_ALT))
|
||||
.border_b_1()
|
||||
.border_color(rgb(BORDER))
|
||||
.text_sm()
|
||||
.text_color(rgb(TEXT))
|
||||
.child(pane.title)
|
||||
.child(div().text_xs().text_color(rgb(ACCENT)).child(pane.badge)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.py_2()
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child(pane.target),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.px_3()
|
||||
.pb_3()
|
||||
.text_sm()
|
||||
.text_color(rgb(TEXT))
|
||||
.children(pane.lines.into_iter()),
|
||||
struct LumbridgeShell {
|
||||
model: ShellModel,
|
||||
timing: RenderTiming,
|
||||
root_focus: FocusHandle,
|
||||
pane_focus: [FocusHandle; 6],
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RenderTiming {
|
||||
pending_dispatch: Option<Instant>,
|
||||
dispatch_to_element_micros: VecDeque<u128>,
|
||||
}
|
||||
|
||||
impl RenderTiming {
|
||||
fn mark_dispatch(&mut self) {
|
||||
self.pending_dispatch = Some(Instant::now());
|
||||
}
|
||||
|
||||
fn observe_element_build(&mut self) {
|
||||
let Some(started) = self.pending_dispatch.take() else {
|
||||
return;
|
||||
};
|
||||
if self.dispatch_to_element_micros.len() == TIMING_SAMPLE_LIMIT {
|
||||
self.dispatch_to_element_micros.pop_front();
|
||||
}
|
||||
self.dispatch_to_element_micros
|
||||
.push_back(started.elapsed().as_micros());
|
||||
}
|
||||
|
||||
fn summary(&self) -> String {
|
||||
if self.dispatch_to_element_micros.is_empty() {
|
||||
return "dispatch→element collecting…".to_owned();
|
||||
}
|
||||
let mut samples = self
|
||||
.dispatch_to_element_micros
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
samples.sort_unstable();
|
||||
let percentile = |percent: usize| {
|
||||
let index = ((samples.len() - 1) * percent) / 100;
|
||||
samples[index]
|
||||
};
|
||||
format!(
|
||||
"dispatch→element p50 {}µs · p95 {}µs · n{}",
|
||||
percentile(50),
|
||||
percentile(95),
|
||||
samples.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl LumbridgeShell {
|
||||
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let pane_focus = std::array::from_fn(|index| {
|
||||
cx.focus_handle()
|
||||
.tab_index(index as isize + 1)
|
||||
.tab_stop(true)
|
||||
});
|
||||
let root_focus = cx.focus_handle();
|
||||
window.focus(&pane_focus[0]);
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
loop {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(650))
|
||||
.await;
|
||||
if this
|
||||
.update(cx, |shell, cx| {
|
||||
shell.dispatch(ShellAction::SyntheticStreamTick);
|
||||
cx.notify();
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
model: ShellModel::default(),
|
||||
timing: RenderTiming::default(),
|
||||
root_focus,
|
||||
pane_focus,
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch(&mut self, action: ShellAction) -> ActionOutcome {
|
||||
self.timing.mark_dispatch();
|
||||
self.model.dispatch(action)
|
||||
}
|
||||
|
||||
fn select_pane(&mut self, pane: PaneId, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.dispatch(ShellAction::SelectPane(pane));
|
||||
window.focus(&self.pane_focus[pane.index()]);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn move_focus(
|
||||
&mut self,
|
||||
direction: FocusDirection,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let outcome = self.dispatch(ShellAction::MoveFocus(direction));
|
||||
window.focus(&self.pane_focus[outcome.selected_pane.index()]);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn focus_left(&mut self, _: &FocusLeft, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_focus(FocusDirection::Left, window, cx);
|
||||
}
|
||||
|
||||
fn focus_right(&mut self, _: &FocusRight, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_focus(FocusDirection::Right, window, cx);
|
||||
}
|
||||
|
||||
fn focus_up(&mut self, _: &FocusUp, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_focus(FocusDirection::Up, window, cx);
|
||||
}
|
||||
|
||||
fn focus_down(&mut self, _: &FocusDown, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_focus(FocusDirection::Down, window, cx);
|
||||
}
|
||||
|
||||
fn open_palette(&mut self, _: &OpenPalette, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.dispatch(ShellAction::OpenCommandPalette);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn close_palette(&mut self, _: &ClosePalette, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.dispatch(ShellAction::CloseCommandPalette);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_key_down(&mut self, event: &KeyDownEvent, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.model.command_palette().is_open() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut query = self.model.command_palette().query().to_owned();
|
||||
match event.keystroke.key.as_str() {
|
||||
"backspace" => {
|
||||
query.pop();
|
||||
}
|
||||
"enter" => {
|
||||
self.dispatch(ShellAction::CloseCommandPalette);
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
if let Some(character) = event.keystroke.key_char.as_deref()
|
||||
&& !event.keystroke.modifiers.control
|
||||
&& !event.keystroke.modifiers.platform
|
||||
{
|
||||
query.push_str(character);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.dispatch(ShellAction::SetCommandPaletteQuery(query));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn pane_card(
|
||||
pane: &PaneState,
|
||||
selected: bool,
|
||||
focus: FocusHandle,
|
||||
cx: &mut Context<Self>,
|
||||
) -> gpui::AnyElement {
|
||||
let id = pane.id();
|
||||
let needs_input = pane.needs_input();
|
||||
let label = match pane.kind() {
|
||||
SurfaceKind::Terminal => pane.status().label(),
|
||||
_ => pane.fixture().badge,
|
||||
};
|
||||
let state_color = if needs_input {
|
||||
ATTENTION
|
||||
} else if matches!(pane.kind(), SurfaceKind::Terminal) {
|
||||
SUCCESS
|
||||
} else {
|
||||
ACCENT
|
||||
};
|
||||
let line_start = pane.lines().len().saturating_sub(12);
|
||||
|
||||
div()
|
||||
.id(("pane", id.index()))
|
||||
.track_focus(&focus)
|
||||
.tab_index(id.index() as isize + 1)
|
||||
.flex()
|
||||
.flex_col()
|
||||
.min_w_0()
|
||||
.min_h_0()
|
||||
.overflow_hidden()
|
||||
.bg(rgb(if selected { PANEL_ACTIVE } else { PANEL }))
|
||||
.border_1()
|
||||
.border_color(rgb(if selected || needs_input {
|
||||
state_color
|
||||
} else {
|
||||
BORDER
|
||||
}))
|
||||
.rounded(px(5.0))
|
||||
.when(selected, |view| view.border_2())
|
||||
.on_click(cx.listener(move |shell, _, window, cx| {
|
||||
shell.select_pane(id, window, cx);
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.px_3()
|
||||
.h(px(36.0))
|
||||
.flex_none()
|
||||
.bg(rgb(if selected { PANEL_ACTIVE } else { PANEL_ALT }))
|
||||
.border_b_1()
|
||||
.border_color(rgb(BORDER_QUIET))
|
||||
.text_sm()
|
||||
.text_color(rgb(TEXT))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child(format!("{}", id.index() + 1)),
|
||||
)
|
||||
.child(pane.fixture().title),
|
||||
)
|
||||
.child(div().text_xs().text_color(rgb(state_color)).child(label)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.px_3()
|
||||
.py_2()
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child(pane.fixture().target)
|
||||
.when(selected, |view| view.child("FOCUSED")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.px_3()
|
||||
.pb_3()
|
||||
.text_sm()
|
||||
.text_color(rgb(TEXT))
|
||||
.children(pane.lines()[line_start..].iter().cloned()),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn command_palette(&self) -> impl IntoElement {
|
||||
let query = self.model.command_palette().query();
|
||||
let prompt = if query.is_empty() {
|
||||
"Type a command…".to_owned()
|
||||
} else {
|
||||
query.to_owned()
|
||||
};
|
||||
|
||||
div()
|
||||
.absolute()
|
||||
.inset_0()
|
||||
.flex()
|
||||
.justify_center()
|
||||
.items_start()
|
||||
.pt(px(92.0))
|
||||
.bg(gpui::black().opacity(0.72))
|
||||
.child(
|
||||
div()
|
||||
.w(px(620.0))
|
||||
.overflow_hidden()
|
||||
.bg(rgb(PANEL_ALT))
|
||||
.border_1()
|
||||
.border_color(rgb(ACCENT))
|
||||
.rounded(px(8.0))
|
||||
.child(
|
||||
div()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.border_b_1()
|
||||
.border_color(rgb(BORDER))
|
||||
.text_color(rgb(if query.is_empty() { MUTED } else { TEXT }))
|
||||
.child(prompt),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.p_2()
|
||||
.text_sm()
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.py_2()
|
||||
.rounded(px(5.0))
|
||||
.bg(rgb(PANEL_ACTIVE))
|
||||
.child("Focus next pane")
|
||||
.child(
|
||||
div()
|
||||
.mt_1()
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("Workspace · navigation"),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.py_2()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("Open attention request"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.py_2()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("Share selected pane to Buzz…"),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.px_4()
|
||||
.py_2()
|
||||
.border_t_1()
|
||||
.border_color(rgb(BORDER))
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("Enter to run")
|
||||
.child("Esc to close"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for LumbridgeShell {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let attention = self.model.pane(PaneId::ClaudeUi);
|
||||
let sidebar = div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.w(px(220.0))
|
||||
.w(px(248.0))
|
||||
.flex_none()
|
||||
.bg(rgb(PANEL))
|
||||
.border_r_1()
|
||||
.border_color(rgb(BORDER))
|
||||
.border_color(rgb(BORDER_QUIET))
|
||||
.child(
|
||||
div()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.text_sm()
|
||||
.pt_4()
|
||||
.pb_2()
|
||||
.text_xs()
|
||||
.text_color(rgb(ATTENTION))
|
||||
.child("ATTENTION · 1"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.mx_2()
|
||||
.mb_3()
|
||||
.px_3()
|
||||
.py_2()
|
||||
.rounded(px(5.0))
|
||||
.bg(rgb(PANEL_ACTIVE))
|
||||
.border_1()
|
||||
.border_color(rgb(ATTENTION))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(rgb(TEXT))
|
||||
.child(attention.fixture().title),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.mt_1()
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("Waiting for a split decision"),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_4()
|
||||
.py_2()
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("WORKSPACES"),
|
||||
.child("WORKTREES"),
|
||||
)
|
||||
.children(WORKSPACES.into_iter().enumerate().map(|(index, name)| {
|
||||
div()
|
||||
@@ -87,30 +447,130 @@ impl Render for LumbridgeShell {
|
||||
.mb_1()
|
||||
.px_3()
|
||||
.py_2()
|
||||
.rounded_md()
|
||||
.rounded(px(5.0))
|
||||
.when(index == 0, |view| {
|
||||
view.bg(rgb(PANEL_ALT)).text_color(rgb(TEXT))
|
||||
})
|
||||
.when(index != 0, |view| view.text_color(rgb(MUTED)))
|
||||
.child(name)
|
||||
.when(index == 0, |view| {
|
||||
view.child(
|
||||
div()
|
||||
.mt_1()
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("main · metal"),
|
||||
)
|
||||
})
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.mt_4()
|
||||
.mt_3()
|
||||
.px_4()
|
||||
.py_2()
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("HOSTS")
|
||||
.child("RUNTIMES"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_4()
|
||||
.py_1()
|
||||
.text_sm()
|
||||
.text_color(rgb(SUCCESS))
|
||||
.child("metal · connected"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_4()
|
||||
.py_1()
|
||||
.text_sm()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("amd-server · connected"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_4()
|
||||
.py_1()
|
||||
.text_sm()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("spark-1 · sleeping"),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
div()
|
||||
.m_3()
|
||||
.p_3()
|
||||
.rounded(px(5.0))
|
||||
.bg(rgb(PANEL_ALT))
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("Buzz · lumbridgecode")
|
||||
.child(
|
||||
div()
|
||||
.mt_2()
|
||||
.text_color(rgb(ACCENT))
|
||||
.child("● metal · connected"),
|
||||
)
|
||||
.child(div().mt_2().child("● amd-server · connected"))
|
||||
.child(div().mt_2().child("○ spark-1 · sleeping")),
|
||||
.mt_1()
|
||||
.text_color(rgb(SUCCESS))
|
||||
.child("connected · signed identity"),
|
||||
),
|
||||
);
|
||||
|
||||
let tabs = div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.h(px(38.0))
|
||||
.flex_none()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.bg(rgb(PANEL))
|
||||
.border_b_1()
|
||||
.border_color(rgb(BORDER_QUIET))
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.px_3()
|
||||
.border_b_2()
|
||||
.border_color(rgb(ACCENT))
|
||||
.text_sm()
|
||||
.child("Agent workspace"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.text_sm()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("Architecture.md"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.text_sm()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("Review"),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("6 surfaces · 3 remote · 1 waiting"),
|
||||
);
|
||||
|
||||
let pane_cards = self
|
||||
.model
|
||||
.panes()
|
||||
.iter()
|
||||
.map(|pane| {
|
||||
Self::pane_card(
|
||||
pane,
|
||||
self.model.selected_pane() == pane.id(),
|
||||
self.pane_focus[pane.id().index()].clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let grid = div()
|
||||
.grid()
|
||||
.grid_cols(3)
|
||||
@@ -118,9 +578,48 @@ impl Render for LumbridgeShell {
|
||||
.gap_2()
|
||||
.p_2()
|
||||
.size_full()
|
||||
.children(PANES.iter().map(pane_card));
|
||||
.children(pane_cards);
|
||||
|
||||
div()
|
||||
let counters = self.model.counters();
|
||||
let footer_left = format!(
|
||||
"rev {} · {} focus moves · {} surface updates · {} lines",
|
||||
self.model.revision(),
|
||||
counters.focus_moves,
|
||||
counters.surface_updates,
|
||||
counters.terminal_lines_appended
|
||||
);
|
||||
let timing = self.timing.summary();
|
||||
|
||||
let root = div()
|
||||
.id("lumbridge-shell")
|
||||
.relative()
|
||||
.track_focus(&self.root_focus)
|
||||
.key_context("LumbridgeShell")
|
||||
.on_action(cx.listener(Self::focus_left))
|
||||
.on_action(cx.listener(Self::focus_right))
|
||||
.on_action(cx.listener(Self::focus_up))
|
||||
.on_action(cx.listener(Self::focus_down))
|
||||
.on_action(cx.listener(Self::open_palette))
|
||||
.on_action(cx.listener(Self::close_palette))
|
||||
.on_action(cx.listener(|shell, _: &SelectPane1, window, cx| {
|
||||
shell.select_pane(PaneId::CodexRuntime, window, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|shell, _: &SelectPane2, window, cx| {
|
||||
shell.select_pane(PaneId::ClaudeUi, window, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|shell, _: &SelectPane3, window, cx| {
|
||||
shell.select_pane(PaneId::PiDocs, window, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|shell, _: &SelectPane4, window, cx| {
|
||||
shell.select_pane(PaneId::Architecture, window, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|shell, _: &SelectPane5, window, cx| {
|
||||
shell.select_pane(PaneId::AcpPreview, window, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|shell, _: &SelectPane6, window, cx| {
|
||||
shell.select_pane(PaneId::RuntimeReview, window, cx);
|
||||
}))
|
||||
.on_key_down(cx.listener(Self::on_key_down))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.size_full()
|
||||
@@ -130,63 +629,105 @@ impl Render for LumbridgeShell {
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.h(px(46.0))
|
||||
.h(px(48.0))
|
||||
.flex_none()
|
||||
.px_4()
|
||||
.bg(rgb(PANEL_ALT))
|
||||
.border_b_1()
|
||||
.border_color(rgb(BORDER))
|
||||
.border_color(rgb(BORDER_QUIET))
|
||||
.child(div().text_lg().child("Lumbridge"))
|
||||
.child(
|
||||
div()
|
||||
.ml_3()
|
||||
.text_sm()
|
||||
.text_color(rgb(MUTED))
|
||||
.child("UI spike · GPUI"),
|
||||
.child("Lumbridge Code / main"),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
div()
|
||||
.mr_4()
|
||||
.text_xs()
|
||||
.text_color(rgb(SUCCESS))
|
||||
.child("metal · runtime online"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(rgb(ACCENT))
|
||||
.child("⌘K Command Palette"),
|
||||
.child("Ctrl/⌘ K · Commands"),
|
||||
),
|
||||
)
|
||||
.child(div().flex().flex_1().min_h_0().child(sidebar).child(grid))
|
||||
.child(
|
||||
div().flex().flex_1().min_h_0().child(sidebar).child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.child(tabs)
|
||||
.child(grid),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.h(px(30.0))
|
||||
.h(px(32.0))
|
||||
.flex_none()
|
||||
.px_3()
|
||||
.bg(rgb(PANEL_ALT))
|
||||
.border_t_1()
|
||||
.border_color(rgb(BORDER))
|
||||
.border_color(rgb(BORDER_QUIET))
|
||||
.text_xs()
|
||||
.text_color(rgb(MUTED))
|
||||
.child(FOOTER_LEFT)
|
||||
.child(FOOTER_CENTER)
|
||||
.child(footer_left)
|
||||
.child(timing)
|
||||
.child(FOOTER_RIGHT),
|
||||
)
|
||||
.when(self.model.command_palette().is_open(), |view| {
|
||||
view.child(self.command_palette())
|
||||
});
|
||||
self.timing.observe_element_build();
|
||||
root
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
let bounds = Bounds::centered(None, size(px(1280.0), px(800.0)), cx);
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("left", FocusLeft, Some("LumbridgeShell")),
|
||||
KeyBinding::new("right", FocusRight, Some("LumbridgeShell")),
|
||||
KeyBinding::new("up", FocusUp, Some("LumbridgeShell")),
|
||||
KeyBinding::new("down", FocusDown, Some("LumbridgeShell")),
|
||||
KeyBinding::new("h", FocusLeft, Some("LumbridgeShell")),
|
||||
KeyBinding::new("l", FocusRight, Some("LumbridgeShell")),
|
||||
KeyBinding::new("k", FocusUp, Some("LumbridgeShell")),
|
||||
KeyBinding::new("j", FocusDown, Some("LumbridgeShell")),
|
||||
KeyBinding::new("secondary-k", OpenPalette, Some("LumbridgeShell")),
|
||||
KeyBinding::new("escape", ClosePalette, Some("LumbridgeShell")),
|
||||
KeyBinding::new("1", SelectPane1, Some("LumbridgeShell")),
|
||||
KeyBinding::new("2", SelectPane2, Some("LumbridgeShell")),
|
||||
KeyBinding::new("3", SelectPane3, Some("LumbridgeShell")),
|
||||
KeyBinding::new("4", SelectPane4, Some("LumbridgeShell")),
|
||||
KeyBinding::new("5", SelectPane5, Some("LumbridgeShell")),
|
||||
KeyBinding::new("6", SelectPane6, Some("LumbridgeShell")),
|
||||
]);
|
||||
|
||||
let bounds = Bounds::centered(None, size(px(1500.0), px(960.0)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
titlebar: Some(gpui::TitlebarOptions {
|
||||
title: Some("Lumbridge · GPUI spike".into()),
|
||||
title: Some("Lumbridge · GPUI workspace".into()),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
|_, cx| cx.new(|_| LumbridgeShell),
|
||||
|window, cx| cx.new(|cx| LumbridgeShell::new(window, cx)),
|
||||
)
|
||||
.expect("GPUI spike window should open");
|
||||
.expect("GPUI workspace window should open");
|
||||
cx.activate(true);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user