This commit is contained in:
+421
-91
@@ -1,151 +1,481 @@
|
||||
use floem::{Application, kurbo::Size, peniko::Color, prelude::*, window::WindowConfig};
|
||||
use std::time::Duration;
|
||||
|
||||
use floem::{
|
||||
Application,
|
||||
action::exec_after,
|
||||
event::EventPropagation,
|
||||
kurbo::Size,
|
||||
peniko::Color,
|
||||
prelude::*,
|
||||
reactive::{Effect, RwSignal, SignalGet, SignalTrack, SignalUpdate, SignalWith},
|
||||
style::Position,
|
||||
window::WindowConfig,
|
||||
};
|
||||
use lumbridge_spike_model::{
|
||||
FOOTER_CENTER, FOOTER_LEFT, FOOTER_RIGHT, PANES, PaneFixture, WORKSPACES,
|
||||
FOOTER_CENTER, FOOTER_RIGHT, FocusDirection, PaneId, PaneState, ShellAction, ShellModel,
|
||||
SurfaceKind, WORKSPACES,
|
||||
};
|
||||
|
||||
const BG: Color = Color::from_rgb8(12, 14, 19);
|
||||
const PANEL: Color = Color::from_rgb8(18, 23, 34);
|
||||
const PANEL_ALT: Color = Color::from_rgb8(23, 29, 41);
|
||||
const BORDER: Color = Color::from_rgb8(41, 50, 68);
|
||||
const TEXT: Color = Color::from_rgb8(217, 226, 242);
|
||||
const MUTED: Color = Color::from_rgb8(127, 139, 163);
|
||||
const ACCENT: Color = Color::from_rgb8(119, 189, 251);
|
||||
const BG: Color = Color::from_rgb8(9, 12, 18);
|
||||
const PANEL: Color = Color::from_rgb8(16, 22, 32);
|
||||
const PANEL_ALT: Color = Color::from_rgb8(21, 29, 41);
|
||||
const PANEL_ACTIVE: Color = Color::from_rgb8(24, 35, 52);
|
||||
const BORDER: Color = Color::from_rgb8(38, 50, 70);
|
||||
const BORDER_QUIET: Color = Color::from_rgb8(28, 38, 54);
|
||||
const TEXT: Color = Color::from_rgb8(219, 229, 244);
|
||||
const MUTED: Color = Color::from_rgb8(130, 144, 168);
|
||||
const ACCENT: Color = Color::from_rgb8(104, 181, 248);
|
||||
const ATTENTION: Color = Color::from_rgb8(241, 185, 106);
|
||||
const SUCCESS: Color = Color::from_rgb8(112, 214, 168);
|
||||
const OS_MOD: Modifiers = if cfg!(target_os = "macos") {
|
||||
Modifiers::META
|
||||
} else {
|
||||
Modifiers::CONTROL
|
||||
};
|
||||
|
||||
fn pane_card(pane: PaneFixture) -> impl IntoView {
|
||||
let header = Stack::horizontal((
|
||||
Label::new(pane.title).style(|s| s.flex_grow(1.0).color(TEXT)),
|
||||
Label::new(pane.badge).style(|s| s.font_size(11.0).color(ACCENT)),
|
||||
))
|
||||
.style(|s| {
|
||||
s.items_center()
|
||||
.height(34.0)
|
||||
.padding_horiz(10.0)
|
||||
.background(PANEL_ALT)
|
||||
.border_bottom(1.0)
|
||||
.border_color(BORDER)
|
||||
fn dispatch(model: RwSignal<ShellModel>, action: ShellAction) {
|
||||
model.update(|model| {
|
||||
model.dispatch(action);
|
||||
});
|
||||
}
|
||||
|
||||
let lines =
|
||||
Stack::vertical(pane.lines.map(|line| {
|
||||
Label::new(line).style(|s| s.font_size(12.0).color(TEXT).margin_bottom(5.0))
|
||||
}));
|
||||
|
||||
Stack::vertical((
|
||||
header,
|
||||
Label::new(pane.target).style(|s| s.font_size(11.0).color(MUTED).padding(10.0)),
|
||||
lines.style(|s| s.padding_horiz(10.0).padding_bottom(10.0)),
|
||||
))
|
||||
fn pane_card(id: PaneId, model: RwSignal<ShellModel>) -> impl IntoView {
|
||||
dyn_container(
|
||||
move || model.with(|model| (model.pane(id).clone(), model.selected_pane() == id)),
|
||||
move |(pane, selected)| render_pane_card(pane, selected, model),
|
||||
)
|
||||
.style(|s| {
|
||||
s.flex_basis(0)
|
||||
.flex_grow(1.0)
|
||||
.min_width(0.0)
|
||||
.min_height(0.0)
|
||||
.margin(4.0)
|
||||
.background(PANEL)
|
||||
.border(1.0)
|
||||
.border_color(BORDER)
|
||||
.border_radius(6.0)
|
||||
})
|
||||
}
|
||||
|
||||
fn app_view() -> impl IntoView {
|
||||
let sidebar_items = Stack::vertical(WORKSPACES.map(|name| {
|
||||
Label::new(name).style(move |s| {
|
||||
s.width_full()
|
||||
.padding_vert(8.0)
|
||||
.padding_horiz(12.0)
|
||||
.margin_bottom(3.0)
|
||||
.color(if name == "Lumbridge Code" {
|
||||
TEXT
|
||||
} else {
|
||||
MUTED
|
||||
})
|
||||
.apply_if(name == "Lumbridge Code", |s| {
|
||||
s.background(PANEL_ALT).border_radius(6.0)
|
||||
})
|
||||
fn render_pane_card(pane: PaneState, selected: bool, model: RwSignal<ShellModel>) -> impl IntoView {
|
||||
let id = pane.id();
|
||||
let needs_input = pane.needs_input();
|
||||
let state_color = if needs_input {
|
||||
ATTENTION
|
||||
} else if matches!(pane.kind(), SurfaceKind::Terminal) {
|
||||
SUCCESS
|
||||
} else {
|
||||
ACCENT
|
||||
};
|
||||
let state_label = if matches!(pane.kind(), SurfaceKind::Terminal) {
|
||||
pane.status().label()
|
||||
} else {
|
||||
pane.fixture().badge
|
||||
};
|
||||
let start = pane.lines().len().saturating_sub(12);
|
||||
let lines = Stack::from_iter(pane.lines()[start..].iter().cloned().map(|line| {
|
||||
Label::new(line).style(|s| {
|
||||
s.font_size(12.0)
|
||||
.color(TEXT)
|
||||
.margin_bottom(4.0)
|
||||
.min_width(0.0)
|
||||
})
|
||||
}));
|
||||
}))
|
||||
.style(|s| s.flex_col().padding_horiz(10.0).padding_bottom(10.0));
|
||||
|
||||
let sidebar = Stack::vertical((
|
||||
Label::new("WORKSPACES").style(|s| s.font_size(11.0).color(MUTED).padding(12.0)),
|
||||
sidebar_items.style(|s| s.padding_horiz(8.0)),
|
||||
Label::new("HOSTS").style(|s| {
|
||||
s.font_size(11.0)
|
||||
.color(MUTED)
|
||||
.padding(12.0)
|
||||
.margin_top(12.0)
|
||||
}),
|
||||
Label::new("● metal · connected").style(|s| s.color(ACCENT).padding_horiz(14.0)),
|
||||
Label::new("● amd-server · connected")
|
||||
.style(|s| s.color(MUTED).padding_horiz(14.0).margin_top(8.0)),
|
||||
Label::new("○ spark-1 · sleeping")
|
||||
.style(|s| s.color(MUTED).padding_horiz(14.0).margin_top(8.0)),
|
||||
let header = Stack::horizontal((
|
||||
Stack::horizontal((
|
||||
Label::new(format!("{}", id.index() + 1))
|
||||
.style(|s| s.font_size(11.0).color(MUTED).margin_right(8.0)),
|
||||
Label::new(pane.fixture().title)
|
||||
.style(|s| s.flex_grow(1.0).font_size(13.0).color(TEXT)),
|
||||
))
|
||||
.style(|s| s.items_center().min_width(0.0).flex_grow(1.0)),
|
||||
Label::new(state_label).style(move |s| s.font_size(10.0).color(state_color)),
|
||||
))
|
||||
.style(move |s| {
|
||||
s.items_center()
|
||||
.height(36.0)
|
||||
.padding_horiz(10.0)
|
||||
.background(if selected { PANEL_ACTIVE } else { PANEL_ALT })
|
||||
.border_bottom(1.0)
|
||||
.border_color(BORDER_QUIET)
|
||||
});
|
||||
|
||||
let target = Stack::horizontal((
|
||||
Label::new(pane.fixture().target).style(|s| s.flex_grow(1.0).color(MUTED)),
|
||||
Label::new(if selected { "FOCUSED" } else { "" })
|
||||
.style(|s| s.font_size(10.0).color(ACCENT)),
|
||||
))
|
||||
.style(|s| {
|
||||
s.width(220.0)
|
||||
s.items_center()
|
||||
.padding_horiz(10.0)
|
||||
.padding_vert(8.0)
|
||||
.font_size(11.0)
|
||||
});
|
||||
|
||||
Stack::vertical((header, target, lines))
|
||||
.style(move |s| {
|
||||
s.width_full()
|
||||
.height_full()
|
||||
.min_width(0.0)
|
||||
.min_height(0.0)
|
||||
.background(if selected { PANEL_ACTIVE } else { PANEL })
|
||||
.border(if selected { 2.0 } else { 1.0 })
|
||||
.border_color(if selected || needs_input {
|
||||
state_color
|
||||
} else {
|
||||
BORDER
|
||||
})
|
||||
.border_radius(5.0)
|
||||
.keyboard_navigable()
|
||||
})
|
||||
.on_event_stop(listener::Click, move |_, _| {
|
||||
dispatch(model, ShellAction::SelectPane(id));
|
||||
})
|
||||
}
|
||||
|
||||
fn sidebar() -> impl IntoView {
|
||||
let worktrees = Stack::from_iter(WORKSPACES.into_iter().enumerate().map(|(index, name)| {
|
||||
Stack::vertical((
|
||||
Label::new(name),
|
||||
Label::new(if index == 0 { "main · metal" } else { "" })
|
||||
.style(|s| s.font_size(10.0).color(MUTED).margin_top(2.0)),
|
||||
))
|
||||
.style(move |s| {
|
||||
s.width_full()
|
||||
.padding_vert(7.0)
|
||||
.padding_horiz(10.0)
|
||||
.margin_bottom(3.0)
|
||||
.color(if index == 0 { TEXT } else { MUTED })
|
||||
.apply_if(index == 0, |s| s.background(PANEL_ALT).border_radius(5.0))
|
||||
})
|
||||
}))
|
||||
.style(|s| s.flex_col().padding_horiz(8.0));
|
||||
|
||||
Stack::vertical((
|
||||
Label::new("ATTENTION · 1").style(|s| s.font_size(10.0).color(ATTENTION).padding(12.0)),
|
||||
Stack::vertical((
|
||||
Label::new("Claude Code · UI").style(|s| s.color(TEXT)),
|
||||
Label::new("Waiting for a split decision")
|
||||
.style(|s| s.font_size(10.0).color(MUTED).margin_top(3.0)),
|
||||
))
|
||||
.style(|s| {
|
||||
s.margin_horiz(8.0)
|
||||
.margin_bottom(12.0)
|
||||
.padding(10.0)
|
||||
.background(PANEL_ACTIVE)
|
||||
.border(1.0)
|
||||
.border_color(ATTENTION)
|
||||
.border_radius(5.0)
|
||||
}),
|
||||
Label::new("WORKTREES").style(|s| s.font_size(10.0).color(MUTED).padding(12.0)),
|
||||
worktrees,
|
||||
Label::new("RUNTIMES").style(|s| {
|
||||
s.font_size(10.0)
|
||||
.color(MUTED)
|
||||
.padding_horiz(12.0)
|
||||
.padding_vert(9.0)
|
||||
.margin_top(8.0)
|
||||
}),
|
||||
Label::new("● metal · connected").style(|s| s.color(SUCCESS).padding_horiz(12.0)),
|
||||
Label::new("● amd-server · connected")
|
||||
.style(|s| s.color(MUTED).padding_horiz(12.0).margin_top(7.0)),
|
||||
Label::new("○ spark-1 · sleeping")
|
||||
.style(|s| s.color(MUTED).padding_horiz(12.0).margin_top(7.0)),
|
||||
Empty::new().style(|s| s.flex_grow(1.0)),
|
||||
Stack::vertical((
|
||||
Label::new("Buzz · lumbridgecode"),
|
||||
Label::new("connected · signed identity").style(|s| s.color(SUCCESS).margin_top(3.0)),
|
||||
))
|
||||
.style(|s| {
|
||||
s.margin(10.0)
|
||||
.padding(10.0)
|
||||
.font_size(10.0)
|
||||
.color(MUTED)
|
||||
.background(PANEL_ALT)
|
||||
.border_radius(5.0)
|
||||
}),
|
||||
))
|
||||
.style(|s| {
|
||||
s.width(248.0)
|
||||
.height_full()
|
||||
.flex_shrink(0.0)
|
||||
.background(PANEL)
|
||||
.border_right(1.0)
|
||||
.border_color(BORDER)
|
||||
.border_color(BORDER_QUIET)
|
||||
})
|
||||
}
|
||||
|
||||
fn tabs() -> impl IntoView {
|
||||
Stack::horizontal((
|
||||
Label::new("Agent workspace").style(|s| {
|
||||
s.height_full()
|
||||
.items_center()
|
||||
.padding_horiz(12.0)
|
||||
.border_bottom(2.0)
|
||||
.border_color(ACCENT)
|
||||
.color(TEXT)
|
||||
}),
|
||||
Label::new("Architecture.md").style(|s| s.padding_horiz(12.0).color(MUTED)),
|
||||
Label::new("Review").style(|s| s.padding_horiz(12.0).color(MUTED)),
|
||||
Empty::new().style(|s| s.flex_grow(1.0)),
|
||||
Label::new("6 surfaces · 3 remote · 1 waiting")
|
||||
.style(|s| s.font_size(10.0).padding_horiz(12.0).color(MUTED)),
|
||||
))
|
||||
.style(|s| {
|
||||
s.height(38.0)
|
||||
.items_center()
|
||||
.font_size(12.0)
|
||||
.background(PANEL)
|
||||
.border_bottom(1.0)
|
||||
.border_color(BORDER_QUIET)
|
||||
})
|
||||
}
|
||||
|
||||
fn command_palette(query: RwSignal<String>) -> impl IntoView {
|
||||
let input = TextInput::new(query)
|
||||
.placeholder("Type a command…")
|
||||
.style(|s| {
|
||||
s.width_full()
|
||||
.height(44.0)
|
||||
.padding_horiz(12.0)
|
||||
.color(TEXT)
|
||||
.background(PANEL_ALT)
|
||||
.border(0.0)
|
||||
});
|
||||
let input_id = input.id();
|
||||
exec_after(Duration::from_millis(1), move |_| input_id.request_focus());
|
||||
|
||||
let card = Stack::vertical((
|
||||
input,
|
||||
Stack::vertical((
|
||||
Stack::vertical((
|
||||
Label::new("Focus next pane").style(|s| s.color(TEXT)),
|
||||
Label::new("Workspace · navigation")
|
||||
.style(|s| s.font_size(10.0).color(MUTED).margin_top(3.0)),
|
||||
))
|
||||
.style(|s| s.padding(10.0).background(PANEL_ACTIVE).border_radius(5.0)),
|
||||
Label::new("Open attention request").style(|s| s.padding(10.0).color(MUTED)),
|
||||
Label::new("Share selected pane to Buzz…").style(|s| s.padding(10.0).color(MUTED)),
|
||||
))
|
||||
.style(|s| {
|
||||
s.padding(8.0)
|
||||
.border_top(1.0)
|
||||
.border_bottom(1.0)
|
||||
.border_color(BORDER)
|
||||
}),
|
||||
Stack::horizontal((
|
||||
Label::new("Enter to run").style(|s| s.flex_grow(1.0)),
|
||||
Label::new("Esc to close"),
|
||||
))
|
||||
.style(|s| {
|
||||
s.padding_horiz(12.0)
|
||||
.padding_vert(8.0)
|
||||
.font_size(10.0)
|
||||
.color(MUTED)
|
||||
}),
|
||||
))
|
||||
.style(|s| {
|
||||
s.width(620.0)
|
||||
.margin_top(92.0)
|
||||
.background(PANEL_ALT)
|
||||
.border(1.0)
|
||||
.border_color(ACCENT)
|
||||
.border_radius(8.0)
|
||||
});
|
||||
|
||||
Container::new(card).style(|s| {
|
||||
s.position(Position::Absolute)
|
||||
.inset(0.0)
|
||||
.items_start()
|
||||
.justify_center()
|
||||
.background(Color::from_rgba8(5, 7, 11, 220))
|
||||
.z_index(50)
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_key(
|
||||
model: RwSignal<ShellModel>,
|
||||
query: RwSignal<String>,
|
||||
KeyboardEvent {
|
||||
code,
|
||||
key,
|
||||
modifiers,
|
||||
..
|
||||
}: &KeyboardEvent,
|
||||
) -> EventPropagation {
|
||||
if *code == Code::KeyK && modifiers.contains(OS_MOD) {
|
||||
query.set(String::new());
|
||||
dispatch(model, ShellAction::OpenCommandPalette);
|
||||
return EventPropagation::Stop;
|
||||
}
|
||||
|
||||
if model.with(|model| model.command_palette().is_open()) {
|
||||
if *key == Key::Named(NamedKey::Escape) {
|
||||
query.set(String::new());
|
||||
dispatch(model, ShellAction::CloseCommandPalette);
|
||||
return EventPropagation::Stop;
|
||||
}
|
||||
return EventPropagation::Continue;
|
||||
}
|
||||
|
||||
let action = match key {
|
||||
Key::Named(NamedKey::ArrowLeft) => Some(ShellAction::MoveFocus(FocusDirection::Left)),
|
||||
Key::Named(NamedKey::ArrowRight) => Some(ShellAction::MoveFocus(FocusDirection::Right)),
|
||||
Key::Named(NamedKey::ArrowUp) => Some(ShellAction::MoveFocus(FocusDirection::Up)),
|
||||
Key::Named(NamedKey::ArrowDown) => Some(ShellAction::MoveFocus(FocusDirection::Down)),
|
||||
Key::Character(character) if modifiers.is_empty() => match character.as_str() {
|
||||
"h" => Some(ShellAction::MoveFocus(FocusDirection::Left)),
|
||||
"l" => Some(ShellAction::MoveFocus(FocusDirection::Right)),
|
||||
"k" => Some(ShellAction::MoveFocus(FocusDirection::Up)),
|
||||
"j" => Some(ShellAction::MoveFocus(FocusDirection::Down)),
|
||||
"1" => Some(ShellAction::SelectPane(PaneId::CodexRuntime)),
|
||||
"2" => Some(ShellAction::SelectPane(PaneId::ClaudeUi)),
|
||||
"3" => Some(ShellAction::SelectPane(PaneId::PiDocs)),
|
||||
"4" => Some(ShellAction::SelectPane(PaneId::Architecture)),
|
||||
"5" => Some(ShellAction::SelectPane(PaneId::AcpPreview)),
|
||||
"6" => Some(ShellAction::SelectPane(PaneId::RuntimeReview)),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(action) = action {
|
||||
dispatch(model, action);
|
||||
EventPropagation::Stop
|
||||
} else {
|
||||
EventPropagation::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn app_view() -> impl IntoView {
|
||||
let model = RwSignal::new(ShellModel::default());
|
||||
let query = RwSignal::new(String::new());
|
||||
let timer_pulse = RwSignal::new(());
|
||||
|
||||
Effect::new(move |_| {
|
||||
timer_pulse.track();
|
||||
exec_after(Duration::from_millis(650), move |_| {
|
||||
dispatch(model, ShellAction::SyntheticStreamTick);
|
||||
timer_pulse.set(());
|
||||
});
|
||||
});
|
||||
|
||||
Effect::new(move |_| {
|
||||
let query = query.get();
|
||||
model.update(|model| {
|
||||
if model.command_palette().is_open() && model.command_palette().query() != query {
|
||||
model.dispatch(ShellAction::SetCommandPaletteQuery(query));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let top_row = Stack::horizontal((
|
||||
pane_card(PANES[0]),
|
||||
pane_card(PANES[1]),
|
||||
pane_card(PANES[2]),
|
||||
pane_card(PaneId::CodexRuntime, model),
|
||||
pane_card(PaneId::ClaudeUi, model),
|
||||
pane_card(PaneId::PiDocs, model),
|
||||
))
|
||||
.style(|s| s.flex_basis(0).flex_grow(1.0).min_height(0.0).width_full());
|
||||
let bottom_row = Stack::horizontal((
|
||||
pane_card(PANES[3]),
|
||||
pane_card(PANES[4]),
|
||||
pane_card(PANES[5]),
|
||||
pane_card(PaneId::Architecture, model),
|
||||
pane_card(PaneId::AcpPreview, model),
|
||||
pane_card(PaneId::RuntimeReview, model),
|
||||
))
|
||||
.style(|s| s.flex_basis(0).flex_grow(1.0).min_height(0.0).width_full());
|
||||
let panes = Stack::vertical((top_row, bottom_row)).style(|s| {
|
||||
let grid = Stack::vertical((top_row, bottom_row)).style(|s| {
|
||||
s.flex_basis(0)
|
||||
.flex_grow(1.0)
|
||||
.min_width(0.0)
|
||||
.height_full()
|
||||
.min_height(0.0)
|
||||
.padding(4.0)
|
||||
});
|
||||
|
||||
let header = Stack::horizontal((
|
||||
Label::new("Lumbridge").style(|s| s.font_size(18.0).color(TEXT).flex_grow(1.0)),
|
||||
Label::new("UI spike · Floem").style(|s| s.color(MUTED).flex_grow(1.0)),
|
||||
Label::new("⌘K Command Palette").style(|s| s.color(ACCENT)),
|
||||
Label::new("Lumbridge").style(|s| s.font_size(18.0).color(TEXT)),
|
||||
Label::new("Lumbridge Code / main")
|
||||
.style(|s| s.color(MUTED).margin_left(12.0).flex_grow(1.0)),
|
||||
Label::new("metal · runtime online")
|
||||
.style(|s| s.font_size(10.0).color(SUCCESS).margin_right(18.0)),
|
||||
Label::new("Ctrl/⌘ K · Commands")
|
||||
.style(|s| s.color(ACCENT).keyboard_navigable())
|
||||
.on_event_stop(listener::Click, move |_, _| {
|
||||
query.set(String::new());
|
||||
dispatch(model, ShellAction::OpenCommandPalette);
|
||||
}),
|
||||
))
|
||||
.style(|s| {
|
||||
s.height(46.0)
|
||||
s.height(48.0)
|
||||
.padding_horiz(14.0)
|
||||
.items_center()
|
||||
.background(PANEL_ALT)
|
||||
.border_bottom(1.0)
|
||||
.border_color(BORDER)
|
||||
.border_color(BORDER_QUIET)
|
||||
});
|
||||
|
||||
let body = Stack::horizontal((sidebar, panes))
|
||||
.style(|s| s.flex_basis(0).flex_grow(1.0).min_height(0.0));
|
||||
let body = Stack::horizontal((
|
||||
sidebar(),
|
||||
Stack::vertical((tabs(), grid)).style(|s| {
|
||||
s.flex_basis(0)
|
||||
.flex_grow(1.0)
|
||||
.min_width(0.0)
|
||||
.min_height(0.0)
|
||||
}),
|
||||
))
|
||||
.style(|s| s.flex_basis(0).flex_grow(1.0).min_height(0.0));
|
||||
|
||||
let footer = Stack::horizontal((
|
||||
Label::new(FOOTER_LEFT).style(|s| s.flex_grow(1.0)),
|
||||
Label::derived(move || {
|
||||
model.with(|model| {
|
||||
let counters = model.counters();
|
||||
format!(
|
||||
"rev {} · {} focus moves · {} surface updates · {} lines",
|
||||
model.revision(),
|
||||
counters.focus_moves,
|
||||
counters.surface_updates,
|
||||
counters.terminal_lines_appended
|
||||
)
|
||||
})
|
||||
})
|
||||
.style(|s| s.flex_grow(1.0)),
|
||||
Label::new(FOOTER_CENTER).style(|s| s.flex_grow(1.0)),
|
||||
Label::new(FOOTER_RIGHT),
|
||||
))
|
||||
.style(|s| {
|
||||
s.height(30.0)
|
||||
s.height(32.0)
|
||||
.padding_horiz(10.0)
|
||||
.items_center()
|
||||
.font_size(11.0)
|
||||
.font_size(10.0)
|
||||
.color(MUTED)
|
||||
.background(PANEL_ALT)
|
||||
.border_top(1.0)
|
||||
.border_color(BORDER)
|
||||
.border_color(BORDER_QUIET)
|
||||
});
|
||||
|
||||
Stack::vertical((header, body, footer))
|
||||
.style(|s| s.width_full().height_full().background(BG))
|
||||
.window_title(|| "Lumbridge · Floem spike".to_owned())
|
||||
let palette = dyn_container(
|
||||
move || model.with(|model| model.command_palette().is_open()),
|
||||
move |open| {
|
||||
if open {
|
||||
command_palette(query).into_any()
|
||||
} else {
|
||||
Empty::new().into_any()
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let root = Stack::vertical((header, body, footer, palette))
|
||||
.style(|s| {
|
||||
s.position(Position::Relative)
|
||||
.width_full()
|
||||
.height_full()
|
||||
.background(BG)
|
||||
.color(TEXT)
|
||||
.keyboard_navigable()
|
||||
})
|
||||
.on_event(listener::KeyDown, move |_, event| {
|
||||
handle_key(model, query, event)
|
||||
})
|
||||
.window_title(|| "Lumbridge · Floem workspace".to_owned());
|
||||
|
||||
let root_id = root.id();
|
||||
exec_after(Duration::from_millis(1), move |_| root_id.request_focus());
|
||||
root
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -154,9 +484,9 @@ fn main() {
|
||||
|_| app_view(),
|
||||
Some(
|
||||
WindowConfig::default()
|
||||
.size(Size::new(1280.0, 800.0))
|
||||
.size(Size::new(1500.0, 960.0))
|
||||
.min_size(Size::new(900.0, 600.0))
|
||||
.title("Lumbridge · Floem spike"),
|
||||
.title("Lumbridge · Floem workspace"),
|
||||
),
|
||||
)
|
||||
.run();
|
||||
|
||||
Reference in New Issue
Block a user