aboutsummaryrefslogtreecommitdiff
path: root/src/ui/menus.rs
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-01 15:48:22 +0300
committerGitHub <noreply@github.com>2026-09-01 15:48:22 +0300
commit207be3c771d281baae6e5fa0fb74be9a056e97a2 (patch)
treed7b037ecdf629224a048c841d11659efdc23507d /src/ui/menus.rs
parent8a6d697308162874a236c84aac0b0f8c7ac01f83 (diff)
refactor: render the shell in the client (#3487)
* feat: add opt-in client-rendered shell * feat: add client-owned shell mouse controls * feat: complete client-owned shell chrome * feat: add client-owned shell settings and notifications * feat: run client shell custom commands * feat: render popup terminals in client shell * feat: add client-owned scrollback and copy mode * fix: preserve client worktree trust defaults * feat: render startup diagnostics in client shell * feat: render onboarding in client shell * feat: render product announcements in client shell * feat: render release notes in client shell * feat: render mobile switcher in client shell * feat: render kitty graphics in client shell * fix: preserve client shell lifecycle coherence * refactor: route client shell commands through endpoint connections * refactor: make the client-rendered shell the default * refactor: remove monolithic launch mode * refactor: complete client-rendered shell cutover * perf: retain client pane surface updates * perf: scale retained pane surface updates * perf: remove remote pane input latency * test: make client shell checks platform-independent * fix: prevent client timer starvation * test: fix client shell platform gates * test: accept retained surfaces in cross-area checks * test: make client mode assertions platform-neutral * fix: badge only outdated integrations
Diffstat (limited to 'src/ui/menus.rs')
-rw-r--r--src/ui/menus.rs315
1 files changed, 0 insertions, 315 deletions
diff --git a/src/ui/menus.rs b/src/ui/menus.rs
deleted file mode 100644
index 763c0ea8..00000000
--- a/src/ui/menus.rs
+++ /dev/null
@@ -1,315 +0,0 @@
-use ratatui::{
- layout::{Alignment, Rect},
- style::{Modifier, Style},
- text::{Line, Span},
- widgets::{Clear, List, ListItem, ListState, Paragraph},
- Frame,
-};
-
-use super::widgets::{panel_contrast_fg, render_panel_shell};
-use crate::app::AppState;
-
-fn prefix_rhs_label(bindings: &crate::config::ActionKeybinds) -> String {
- bindings
- .prefix_rhs_label()
- .unwrap_or_else(|| "unset".to_string())
-}
-
-fn keybind_label(bindings: &crate::config::ActionKeybinds) -> String {
- bindings.label().unwrap_or_else(|| "unset".to_string())
-}
-
-fn render_bottom_bar(frame: &mut Frame, area: Rect, line: Line<'_>, bg: ratatui::style::Color) {
- frame.render_widget(Clear, area);
- let buf = frame.buffer_mut();
- for x in area.x..area.x + area.width {
- buf[(x, area.y)].set_style(Style::default().bg(bg));
- }
- frame.render_widget(Paragraph::new(line), area);
-}
-
-pub(super) fn render_prefix_overlay(app: &AppState, frame: &mut Frame, area: Rect) {
- let key = Style::default()
- .fg(app.palette.accent)
- .add_modifier(Modifier::BOLD);
- let dim = Style::default().fg(app.palette.overlay0);
- let mode_style = Style::default()
- .fg(panel_contrast_fg(&app.palette))
- .bg(app.palette.accent)
- .add_modifier(Modifier::BOLD);
-
- let workspace_picker = prefix_rhs_label(&app.keybinds.workspace_picker);
- let help = prefix_rhs_label(&app.keybinds.help);
- let prefix = crate::config::format_key_combo((app.prefix_code, app.prefix_mods));
-
- let line = Line::from(vec![
- Span::styled(" PREFIX ", mode_style),
- Span::raw(" "),
- Span::styled("esc", key),
- Span::styled(" cancel ", dim),
- Span::styled(prefix, key),
- Span::styled(" send prefix ", dim),
- Span::styled(workspace_picker, key),
- Span::styled(" workspace nav ", dim),
- Span::styled(help, key),
- Span::styled(" keybinds", dim),
- ]);
-
- let overlay_y = area.y + area.height.saturating_sub(1);
- let overlay_area = Rect::new(area.x, overlay_y, area.width, 1);
- render_bottom_bar(frame, overlay_area, line, app.palette.panel_bg);
-}
-
-pub(super) fn render_copy_mode_overlay(app: &AppState, frame: &mut Frame, area: Rect) {
- let key = Style::default()
- .fg(app.palette.accent)
- .add_modifier(Modifier::BOLD);
- let dim = Style::default().fg(app.palette.overlay0);
- let mode_style = Style::default()
- .fg(panel_contrast_fg(&app.palette))
- .bg(app.palette.accent)
- .add_modifier(Modifier::BOLD);
-
- let Some(copy_mode) = app.copy_mode.as_ref() else {
- return;
- };
- let line = if let Some(prompt) = copy_mode.search.prompt.as_ref() {
- let marker = match prompt.direction {
- crate::app::state::CopyModeSearchDirection::Forward => "/",
- crate::app::state::CopyModeSearchDirection::Backward => "?",
- };
- Line::from(vec![
- Span::styled(" COPY ", mode_style),
- Span::raw(" "),
- Span::styled(marker, key),
- Span::styled(prompt.query.clone(), Style::default().fg(app.palette.text)),
- Span::styled("█", key),
- Span::styled(" enter search esc cancel", dim),
- ])
- } else {
- let select = if copy_mode.selection.is_some() {
- "selecting"
- } else {
- "select"
- };
- let match_status = copy_mode
- .search
- .current
- .map(|current| format!(" {}/{}", current + 1, copy_mode.search.matches.len()))
- .or_else(|| (!copy_mode.search.query.is_empty()).then(|| " 0/0".to_string()))
- .unwrap_or_default();
- let (exit_keys, exit_label) =
- if copy_mode.search.query.is_empty() && copy_mode.selection.is_none() {
- ("q/esc", " exit")
- } else {
- ("esc", " clear q exit")
- };
- Line::from(vec![
- Span::styled(" COPY ", mode_style),
- Span::raw(" "),
- Span::styled("h/j/k/l w/b/e { }", key),
- Span::styled(" move ", dim),
- Span::styled("/ ?", key),
- Span::styled(" search ", dim),
- Span::styled("n/N", key),
- Span::styled(format!(" repeat{match_status} "), dim),
- Span::styled("v/space", key),
- Span::styled(format!(" {select} "), dim),
- Span::styled("y/enter", key),
- Span::styled(" copy ", dim),
- Span::styled(exit_keys, key),
- Span::styled(exit_label, dim),
- ])
- };
-
- let overlay_y = area.y + area.height.saturating_sub(1);
- let overlay_area = Rect::new(area.x, overlay_y, area.width, 1);
- render_bottom_bar(frame, overlay_area, line, app.palette.panel_bg);
-}
-
-pub(super) fn render_navigate_overlay(app: &AppState, frame: &mut Frame, area: Rect) {
- let key = Style::default()
- .fg(app.palette.accent)
- .add_modifier(Modifier::BOLD);
- let dim = Style::default().fg(app.palette.overlay0);
-
- let mode_style = Style::default()
- .fg(panel_contrast_fg(&app.palette))
- .bg(app.palette.accent)
- .add_modifier(Modifier::BOLD);
-
- let kb = &app.keybinds;
- let new_tab = prefix_rhs_label(&kb.new_tab);
- let split_vertical = prefix_rhs_label(&kb.split_vertical);
- let split_horizontal = prefix_rhs_label(&kb.split_horizontal);
- let close_pane = prefix_rhs_label(&kb.close_pane);
- let zoom = prefix_rhs_label(&kb.zoom);
- let resize = prefix_rhs_label(&kb.resize_mode);
- let help = prefix_rhs_label(&kb.help);
- let settings = prefix_rhs_label(&kb.settings);
- let goto = prefix_rhs_label(&kb.goto);
- let detach = prefix_rhs_label(&kb.detach);
- let workspace_nav = format!(
- "{} / {}",
- keybind_label(&kb.navigate.workspace_up),
- keybind_label(&kb.navigate.workspace_down)
- );
- let line = Line::from(vec![
- Span::styled(" NAVIGATE ", mode_style),
- Span::raw(" "),
- Span::styled("esc", key),
- Span::styled(" back ", dim),
- Span::styled(workspace_nav, key),
- Span::styled(" ws ", dim),
- Span::styled("⇥", key),
- Span::styled(" pane ", dim),
- Span::styled(goto, key),
- Span::styled(" navigator ", dim),
- Span::styled(new_tab, key),
- Span::styled(" new tab ", dim),
- Span::styled(split_vertical, key),
- Span::styled(" split│ ", dim),
- Span::styled(split_horizontal, key),
- Span::styled(" split─ ", dim),
- Span::styled(close_pane, key),
- Span::styled(" close ", dim),
- Span::styled(zoom, key),
- Span::styled(" zoom ", dim),
- Span::styled(resize, key),
- Span::styled(" resize ", dim),
- Span::styled(help, key),
- Span::styled(" keybinds ", dim),
- Span::styled(settings, key),
- Span::styled(" settings ", dim),
- Span::styled(detach, key),
- Span::styled(" detach", dim),
- ]);
-
- let overlay_y = area.y + area.height.saturating_sub(1);
- let overlay_area = Rect::new(area.x, overlay_y, area.width, 1);
- render_bottom_bar(frame, overlay_area, line, app.palette.panel_bg);
-
- if app.update_available.is_some() {
- let status = Line::from(vec![Span::styled(
- " update ready",
- Style::default()
- .fg(app.palette.accent)
- .add_modifier(Modifier::BOLD),
- )]);
- let width = 13u16.min(overlay_area.width);
- let status_area = Rect::new(
- overlay_area.x + overlay_area.width.saturating_sub(width),
- overlay_area.y,
- width,
- overlay_area.height,
- );
- frame.render_widget(Clear, status_area);
- frame.render_widget(
- Paragraph::new(status).alignment(Alignment::Right),
- status_area,
- );
- }
-}
-
-pub(super) fn render_global_launcher_menu(app: &AppState, frame: &mut Frame) {
- let rect = app.global_menu_rect();
- let Some(inner) = render_panel_shell(frame, rect, app.palette.accent, app.palette.panel_bg)
- else {
- return;
- };
-
- let items = app.global_menu_labels();
- for (idx, item) in items.iter().enumerate() {
- let y = inner.y + idx as u16;
- if y >= inner.y + inner.height {
- break;
- }
- let selected = idx == app.global_menu.highlighted;
- let rect = Rect::new(inner.x, y, inner.width, 1);
-
- let selected_style = Style::default()
- .fg(panel_contrast_fg(&app.palette))
- .bg(app.palette.accent)
- .add_modifier(Modifier::BOLD);
- let item_style = if selected {
- selected_style
- } else {
- Style::default().fg(app.palette.text)
- };
- let badge_style = if selected {
- selected_style
- } else {
- Style::default()
- .fg(app.palette.accent)
- .add_modifier(Modifier::BOLD)
- };
-
- let line = if app.global_menu_item_has_badge(item) {
- Line::from(vec![
- Span::styled(" ●", badge_style),
- Span::styled(format!(" {item} "), item_style),
- ])
- } else {
- Line::from(Span::styled(format!(" {item} "), item_style))
- };
- frame.render_widget(Paragraph::new(line).alignment(Alignment::Left), rect);
- }
-}
-
-pub(super) fn render_resize_overlay(app: &AppState, frame: &mut Frame, area: Rect) {
- let key = Style::default()
- .fg(app.palette.accent)
- .add_modifier(Modifier::BOLD);
- let dim = Style::default().fg(app.palette.overlay0);
-
- let mode_style = Style::default()
- .fg(panel_contrast_fg(&app.palette))
- .bg(app.palette.mauve)
- .add_modifier(Modifier::BOLD);
-
- let line = Line::from(vec![
- Span::styled(" RESIZE ", mode_style),
- Span::raw(" "),
- Span::styled("h/l", key),
- Span::styled(" width ", dim),
- Span::styled("j/k", key),
- Span::styled(" height ", dim),
- Span::styled("esc", key),
- Span::styled(" done", dim),
- ]);
-
- let overlay_y = area.y + area.height.saturating_sub(1);
- let overlay_area = Rect::new(area.x, overlay_y, area.width, 1);
- render_bottom_bar(frame, overlay_area, line, app.palette.panel_bg);
-}
-
-pub(super) fn render_context_menu(app: &AppState, frame: &mut Frame) {
- let Some(menu) = &app.context_menu else {
- return;
- };
-
- let p = &app.palette;
- let Some(menu_rect) = app.context_menu_rect() else {
- return;
- };
- let Some(inner) = render_panel_shell(frame, menu_rect, p.accent, p.panel_bg) else {
- return;
- };
-
- let items: Vec<ListItem> = menu
- .items()
- .iter()
- .map(|item| ListItem::new(Line::from(*item)))
- .collect();
- let list = List::new(items)
- .style(Style::default().fg(p.text))
- .highlight_style(
- Style::default()
- .bg(p.accent)
- .fg(panel_contrast_fg(p))
- .add_modifier(Modifier::BOLD),
- )
- .highlight_symbol(" ");
- let mut state = ListState::default().with_selected(Some(menu.list.highlighted));
- frame.render_stateful_widget(list, inner, &mut state);
-}