返回 CodeWhale
dock_tabs_legacy.rs
根目录 / crates / tui / src / tui / work_surface / render / dock_tabs_legacy.rs
1 //! Frozen current owning renderer, used only for adoption acceptance.
2 use super::*;
3
4 #[derive(Debug, Clone)]
5 struct DockTab {
6 target: DockTabTarget,
7 label: std::borrow::Cow<'static, str>,
8 count: usize,
9 }
10
11 pub(super) fn render_dock_tabs(frame: &mut Frame, area: Rect, app: &mut App) {
12 let width = usize::from(area.width);
13 let mut entries = Vec::new();
14 for panel in RailPanel::ORDER {
15 let count = dock_tab_count(app, panel);
16 let useful = count.is_some_and(|count| count > 0)
17 || super::super::views::view_always_has_content(panel);
18 if useful || panel == app.work_surface.panel {
19 entries.push(DockTab {
20 target: DockTabTarget::Panel(panel),
21 label: match panel {
22 RailPanel::Tasks => "Tasks",
23 RailPanel::Agents => "Fleet",
24 RailPanel::Background => "Jobs",
25 RailPanel::Files => "Files",
26 RailPanel::Notepad => "Notes",
27 RailPanel::Context => "Context",
28 RailPanel::Git => "Git",
29 RailPanel::Price => "Cost",
30 }
31 .into(),
32 count: count.unwrap_or(0),
33 });
34 }
35 }
36
37 let close_mark = if crate::tui::color_compat::ascii_safe_enabled() {
38 "x"
39 } else {
40 "×"
41 };
42 // #6502: name Esc beside the close control only while Esc really closes
43 // the dock — the dock owns keyboard focus and has something to close
44 // (`input::handle_key`). Otherwise Esc belongs to the composer and stops
45 // the running turn, which the posture bar already says with the turn
46 // status; a bare `×` here keeps the two from reading as one shortcut.
47 let esc_closes = app.work_surface.focused
48 && !super::super::interaction::opened_detail_on_screen(app)
49 && (app.work_surface.explicit_view || !visible_rows_for_panel(app).is_empty());
50 let close = if esc_closes && area.width >= 60 {
51 format!(" Esc {close_mark} ")
52 } else {
53 format!(" {close_mark} ")
54 };
55 let close_width = close.width().min(width);
56 let mut show_counts = true;
57 let fits = |tabs: &[DockTab], counts: bool| {
58 tabs.iter()
59 .map(|tab| {
60 UnicodeWidthStr::width(tab.label.as_ref())
61 + if counts && tab.count > 0 {
62 1 + tab.count.to_string().len()
63 } else {
64 0
65 }
66 + 2
67 })
68 .sum::<usize>()
69 .saturating_add(tabs.len().saturating_sub(1).saturating_mul(2))
70 .saturating_add(close_width + 2)
71 <= width
72 };
73 if !fits(&entries, true) {
74 show_counts = false;
75 }
76 // Shed from the right (price, git, context, notepad, files… in reverse
77 // cycle order), never the active tab: a narrow dock keeps the work views.
78 while !fits(&entries, show_counts) && entries.len() > 1 {
79 let remove = entries
80 .iter()
81 .rposition(|tab| tab.target != DockTabTarget::Panel(app.work_surface.panel));
82 let Some(index) = remove else { break };
83 entries.remove(index);
84 }
85
86 let tab_y = if app.work_surface.effective_placement == WorkSurfacePlacement::Bottom {
87 area.y
88 .saturating_add(1)
89 .min(area.bottom().saturating_sub(1))
90 } else {
91 area.y
92 };
93 let tab_area = Rect {
94 x: area.x,
95 y: tab_y,
96 width: area.width,
97 height: 1,
98 };
99 let close_area = Rect {
100 x: tab_area.right().saturating_sub(close_width as u16),
101 y: tab_y,
102 width: close_width as u16,
103 height: 1,
104 };
105 app.work_surface.dock_tabs.clear();
106 for tab in &entries {
107 let label = if show_counts && tab.count > 0 {
108 format!("{} {}", tab.label, tab.count)
109 } else {
110 tab.label.to_string()
111 };
112 let tab_width = u16::try_from(UnicodeWidthStr::width(label.as_str()).saturating_add(2))
113 .unwrap_or(u16::MAX)
114 .min(tab_area.width);
115 let x = tab_area.x.saturating_add(
116 app.work_surface
117 .dock_tabs
118 .last()
119 .map(|hitbox| hitbox.area.right().saturating_sub(tab_area.x) + 2)
120 .unwrap_or(1),
121 );
122 if x.saturating_add(tab_width) > close_area.x {
123 break;
124 }
125 let hitbox = Rect {
126 x,
127 y: tab_y,
128 width: tab_width,
129 height: 1,
130 };
131 let active = tab.target == DockTabTarget::Panel(app.work_surface.panel);
132 let pressed = app.work_surface.pressed_tab == Some(tab.target);
133 let hovered = app.work_surface.hovered_tab == Some(tab.target);
134 let style = if active || pressed {
135 Style::default()
136 .fg(app.ui_theme.text_body)
137 .bg(app.ui_theme.selection_bg)
138 .add_modifier(Modifier::BOLD)
139 } else if hovered {
140 Style::default()
141 .fg(app.ui_theme.text_body)
142 .bg(app.ui_theme.elevated_bg)
143 .add_modifier(Modifier::UNDERLINED)
144 } else {
145 chrome_style(&app.ui_theme, ChromeInk::Metadata)
146 };
147 Paragraph::new(Line::from(Span::styled(format!(" {label} "), style)))
148 .render(hitbox, frame.buffer_mut());
149 app.work_surface.dock_tabs.push(DockTabHitbox {
150 target: tab.target,
151 area: hitbox,
152 });
153 }
154 let close_style = if app.work_surface.hovered_tab == Some(DockTabTarget::Close) {
155 chrome_style(&app.ui_theme, ChromeInk::Info)
156 .bg(app.ui_theme.elevated_bg)
157 .add_modifier(Modifier::UNDERLINED)
158 } else {
159 chrome_style(&app.ui_theme, ChromeInk::MetadataHint)
160 };
161 Paragraph::new(Line::from(Span::styled(close, close_style)))
162 .render(close_area, frame.buffer_mut());
163 app.work_surface.dock_tabs.push(DockTabHitbox {
164 target: DockTabTarget::Close,
165 area: close_area,
166 });
167 }
168
168 lines RUST