irongit

feat: root takes left two panes in miller browsers and sidebar toggles with ctrl-b

huncholanehuncholaneauthored
parent 14c32d0commit 17552cbc3bff70d7fddacbdebe9cf44345bcbe59Browse files

2 files changed, +50 -26

+18-9src/app.rs
@@ -58,6 +58,7 @@ pub struct JvApp {
5858 new_session_name: String,
5959 show_new_session_dialog: bool,
6060 sidebar_width: f32,
61+ sidebar_visible: bool,
6162 theme_applied: bool,
6263
6364 // File drop handling
@@ -250,6 +251,7 @@ impl JvApp {
250251 new_session_name: String::new(),
251252 show_new_session_dialog: false,
252253 sidebar_width: 240.0,
254+ sidebar_visible: true,
253255 theme_applied: false,
254256 dropped_files: Vec::new(),
255257 frame_times: std::collections::VecDeque::with_capacity(60),
@@ -1189,16 +1191,23 @@ impl eframe::App for JvApp {
11891191 });
11901192 }
11911193
1194+ // Ctrl-B: toggle sidebar
1195+ if ctx.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::B)) {
1196+ self.sidebar_visible = !self.sidebar_visible;
1197+ }
1198+
11921199 // Sidebar
1193- egui::SidePanel::left("sidebar")
1194- .default_width(self.sidebar_width)
1195- .min_width(180.0)
1196- .max_width(400.0)
1197- .resizable(true)
1198- .frame(egui::Frame::new().fill(CatppuccinMocha::MANTLE).inner_margin(12.0))
1199- .show(ctx, |ui| {
1200- self.show_sidebar(ui);
1201- });
1200+ if self.sidebar_visible {
1201+ egui::SidePanel::left("sidebar")
1202+ .default_width(self.sidebar_width)
1203+ .min_width(180.0)
1204+ .max_width(400.0)
1205+ .resizable(true)
1206+ .frame(egui::Frame::new().fill(CatppuccinMocha::MANTLE).inner_margin(12.0))
1207+ .show(ctx, |ui| {
1208+ self.show_sidebar(ui);
1209+ });
1210+ }
12021211
12031212 // Main content
12041213 egui::CentralPanel::default()
+32-17src/views/browser.rs
@@ -192,10 +192,20 @@ impl BrowserView {
192192 }
193193 }
194194
195+ // Estimate max label chars from available width
196+ // ~7px per monospace char at 12pt, use 60% of the current column for the label
197+ let avail_w = ui.available_width() - 12.0;
198+ let current_col_w = if self.path.is_empty() {
199+ avail_w * 0.60 // root: left+center merged
200+ } else {
201+ avail_w * 0.38 // normal center column
202+ };
203+ let max_label_chars = ((current_col_w * 0.65) / 7.0).max(20.0) as usize;
204+
195205 let (current_entries, parent_entries) = if in_focus_root {
196206 // Focus mode root: show pinned items as the root list
197207 let entries: Vec<Entry> = self.focused.iter().map(|fp| {
198- let label = focus_path_label(fp);
208+ let label = focus_path_label(fp, max_label_chars);
199209 let value = resolve_from_files(files, fp);
200210 let (icon, color) = value.map(type_icon_color).unwrap_or(("{}", CatppuccinMocha::OVERLAY0));
201211 let preview = value.map(value_preview).unwrap_or_default();
@@ -442,22 +452,30 @@ impl BrowserView {
442452 };
443453
444454 let col_height = avail.height();
455+ let at_root = self.path.is_empty();
445456 ui.horizontal(|ui| {
446457 ui.set_height(col_height);
447458
448- // Left: parent
449- ui.vertical(|ui| {
450- ui.set_width(col_widths[0]);
451- ui.set_height(col_height);
452- crate::widgets::miller::pane_title(ui, &left_title);
453- self.render_parent_column(ui, &parent_entries, col_height);
454- });
459+ if !at_root {
460+ // Left: parent
461+ ui.vertical(|ui| {
462+ ui.set_width(col_widths[0]);
463+ ui.set_height(col_height);
464+ crate::widgets::miller::pane_title(ui, &left_title);
465+ self.render_parent_column(ui, &parent_entries, col_height);
466+ });
455467
456- Self::draw_separator(ui, col_height);
468+ Self::draw_separator(ui, col_height);
469+ }
457470
458- // Middle: current (with optional filter)
471+ // Middle: current (with optional filter) — takes left+center width at root
472+ let current_width = if at_root {
473+ col_widths[0] + col_widths[1] + 5.0
474+ } else {
475+ col_widths[1]
476+ };
459477 ui.vertical(|ui| {
460- ui.set_width(col_widths[1]);
478+ ui.set_width(current_width);
461479 ui.set_height(col_height);
462480 crate::widgets::miller::pane_title(ui, &mid_title);
463481 let filter_resp = self.filter.show(ui, "? to filter");
@@ -1236,12 +1254,10 @@ fn focus_path_full(fp: &[PathSegment]) -> String {
12361254 }).collect::<Vec<_>>().join(".")
12371255 }
12381256
1239-const FOCUS_LABEL_MAX: usize = 40;
1240-
12411257 /// Short label for a focused path: "first_word...last_chars" if over max, else full.
1242-fn focus_path_label(fp: &[PathSegment]) -> String {
1258+fn focus_path_label(fp: &[PathSegment], max_chars: usize) -> String {
12431259 let full = focus_path_full(fp);
1244- if full.len() <= FOCUS_LABEL_MAX {
1260+ if full.len() <= max_chars {
12451261 return full;
12461262 }
12471263
@@ -1250,9 +1266,8 @@ fn focus_path_label(fp: &[PathSegment]) -> String {
12501266 PathSegment::Index(i) => format!("[{}]", i),
12511267 };
12521268
1253- // "first...tail" where tail fills remaining chars
12541269 let ellipsis = "...";
1255- let budget = FOCUS_LABEL_MAX.saturating_sub(first.len() + ellipsis.len());
1270+ let budget = max_chars.saturating_sub(first.len() + ellipsis.len());
12561271 if budget == 0 {
12571272 return format!("{}{}", first, ellipsis);
12581273 }