diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-01 15:48:22 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-01 15:48:22 +0300 |
| commit | 207be3c771d281baae6e5fa0fb74be9a056e97a2 (patch) | |
| tree | d7b037ecdf629224a048c841d11659efdc23507d /src/ui/menus.rs | |
| parent | 8a6d697308162874a236c84aac0b0f8c7ac01f83 (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.rs | 315 |
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); -} |
