@@ -7,6 +7,7 @@ use gartk_core::{InputEvent, Key, Point, Rect, Theme}; |
| 7 | 7 | use gartk_render::{Renderer, Surface}; |
| 8 | 8 | use gartk_x11::{Connection, EventLoop, EventLoopConfig, Window, WindowConfig}; |
| 9 | 9 | use std::path::PathBuf; |
| 10 | +use std::time::Instant; |
| 10 | 11 | use x11rb::protocol::xproto::{ConnectionExt, ImageFormat}; |
| 11 | 12 | |
| 12 | 13 | /// Height of the breadcrumb bar. |
@@ -50,6 +51,10 @@ pub struct App { |
| 50 | 51 | should_quit: bool, |
| 51 | 52 | /// Pane divider resize in progress (split pane pointer path). |
| 52 | 53 | pane_resize_path: Option<Vec<bool>>, |
| 54 | + /// Last click time for double-click detection. |
| 55 | + last_click_time: Option<Instant>, |
| 56 | + /// Last click position for double-click detection. |
| 57 | + last_click_pos: Option<Point>, |
| 53 | 58 | } |
| 54 | 59 | |
| 55 | 60 | impl App { |
@@ -175,6 +180,8 @@ impl App { |
| 175 | 180 | help_modal, |
| 176 | 181 | should_quit: false, |
| 177 | 182 | pane_resize_path: None, |
| 183 | + last_click_time: None, |
| 184 | + last_click_pos: None, |
| 178 | 185 | }; |
| 179 | 186 | |
| 180 | 187 | app.update_status_bar(); |
@@ -329,9 +336,32 @@ impl App { |
| 329 | 336 | } |
| 330 | 337 | } |
| 331 | 338 | |
| 332 | | - if let Some(pane) = self.focused_pane_mut() { |
| 333 | | - if let Some(tab) = pane.active_tab_mut() { |
| 334 | | - tab.on_click(pos, modifiers); |
| 339 | + // Check for double-click to enter directory |
| 340 | + let now = Instant::now(); |
| 341 | + let is_double_click = if let (Some(last_time), Some(last_pos)) = (self.last_click_time, self.last_click_pos) { |
| 342 | + let elapsed = now.duration_since(last_time); |
| 343 | + let distance = ((pos.x - last_pos.x).pow(2) + (pos.y - last_pos.y).pow(2)) as f64; |
| 344 | + elapsed.as_millis() < 400 && distance.sqrt() < 5.0 |
| 345 | + } else { |
| 346 | + false |
| 347 | + }; |
| 348 | + |
| 349 | + // Update click tracking |
| 350 | + self.last_click_time = Some(now); |
| 351 | + self.last_click_pos = Some(pos); |
| 352 | + |
| 353 | + if is_double_click { |
| 354 | + // Double-click: enter the selected item |
| 355 | + self.enter_selected(); |
| 356 | + // Clear click tracking to prevent triple-click |
| 357 | + self.last_click_time = None; |
| 358 | + self.last_click_pos = None; |
| 359 | + } else { |
| 360 | + // Single click: handle selection |
| 361 | + if let Some(pane) = self.focused_pane_mut() { |
| 362 | + if let Some(tab) = pane.active_tab_mut() { |
| 363 | + tab.on_click(pos, modifiers); |
| 364 | + } |
| 335 | 365 | } |
| 336 | 366 | } |
| 337 | 367 | } |
@@ -506,11 +536,15 @@ impl App { |
| 506 | 536 | return; |
| 507 | 537 | } |
| 508 | 538 | Key::Char('d') | Key::Char('D') => { |
| 509 | | - if let Some(pane) = self.focused_pane() { |
| 510 | | - if let Some(tab) = pane.active_tab() { |
| 511 | | - let current = tab.current_path().clone(); |
| 512 | | - self.sidebar.toggle_bookmark(¤t); |
| 513 | | - } |
| 539 | + // Bookmark the selected item (must be a directory) |
| 540 | + let bookmark_path = self.focused_pane() |
| 541 | + .and_then(|pane| pane.active_tab()) |
| 542 | + .and_then(|tab| tab.selected_entry()) |
| 543 | + .filter(|e| e.is_dir()) |
| 544 | + .map(|e| e.path.clone()); |
| 545 | + |
| 546 | + if let Some(path) = bookmark_path { |
| 547 | + self.sidebar.toggle_bookmark(&path); |
| 514 | 548 | } |
| 515 | 549 | return; |
| 516 | 550 | } |
@@ -1060,6 +1094,9 @@ impl App { |
| 1060 | 1094 | // Draw status bar |
| 1061 | 1095 | self.status_bar.render(&self.renderer)?; |
| 1062 | 1096 | |
| 1097 | + // Draw toolbar tooltip overlay (on top of other UI) |
| 1098 | + self.toolbar.render_tooltip_overlay(&self.renderer)?; |
| 1099 | + |
| 1063 | 1100 | // Draw help modal overlay (on top of everything) |
| 1064 | 1101 | self.help_modal.render(&self.renderer)?; |
| 1065 | 1102 | |