@@ -1,7 +1,7 @@ |
| 1 | //! Application state and event loop. | 1 | //! Application state and event loop. |
| 2 | | 2 | |
| 3 | use garfield::core::{ | 3 | use garfield::core::{ |
| 4 | - Clipboard, ClipboardOperation, FileOperation, UndoStack, | 4 | + Clipboard, ClipboardOperation, FileOperation, PreviewLoader, UndoStack, |
| 5 | copy_files, move_files, delete_files, create_directory, | 5 | copy_files, move_files, delete_files, create_directory, |
| 6 | trash_files, restore_from_trash, | 6 | trash_files, restore_from_trash, |
| 7 | }; | 7 | }; |
@@ -94,6 +94,8 @@ pub struct App { |
| 94 | undo_stack: UndoStack, | 94 | undo_stack: UndoStack, |
| 95 | /// Pending paste operation with conflicts. | 95 | /// Pending paste operation with conflicts. |
| 96 | pending_paste: Option<PendingPaste>, | 96 | pending_paste: Option<PendingPaste>, |
| | 97 | + /// Async preview loader for column view. |
| | 98 | + preview_loader: PreviewLoader, |
| 97 | } | 99 | } |
| 98 | | 100 | |
| 99 | /// State for a paste operation with conflicts. | 101 | /// State for a paste operation with conflicts. |
@@ -268,6 +270,7 @@ impl App { |
| 268 | pending_delete_paths: Vec::new(), | 270 | pending_delete_paths: Vec::new(), |
| 269 | undo_stack: UndoStack::new(), | 271 | undo_stack: UndoStack::new(), |
| 270 | pending_paste: None, | 272 | pending_paste: None, |
| | 273 | + preview_loader: PreviewLoader::new(), |
| 271 | }; | 274 | }; |
| 272 | | 275 | |
| 273 | app.update_status_bar(); | 276 | app.update_status_bar(); |
@@ -310,8 +313,9 @@ impl App { |
| 310 | } | 313 | } |
| 311 | InputEvent::MouseMove(mouse_event) => { | 314 | InputEvent::MouseMove(mouse_event) => { |
| 312 | let pos = Point::new(mouse_event.position.x, mouse_event.position.y); | 315 | let pos = Point::new(mouse_event.position.x, mouse_event.position.y); |
| 313 | - self.handle_mouse_move(pos); | 316 | + if self.handle_mouse_move(pos) { |
| 314 | - ev.request_redraw(); | 317 | + ev.request_redraw(); |
| | 318 | + } |
| 315 | } | 319 | } |
| 316 | InputEvent::MouseLeave => { | 320 | InputEvent::MouseLeave => { |
| 317 | self.toolbar.clear_hover(); | 321 | self.toolbar.clear_hover(); |
@@ -339,6 +343,21 @@ impl App { |
| 339 | _ => {} | 343 | _ => {} |
| 340 | } | 344 | } |
| 341 | | 345 | |
| | 346 | + // Poll for completed async preview loads |
| | 347 | + if let Some(result) = self.preview_loader.poll() { |
| | 348 | + if let Some(entries) = result.entries { |
| | 349 | + if let Some(pane) = self.focused_pane_mut() { |
| | 350 | + if let Some(tab) = pane.active_tab_mut() { |
| | 351 | + tab.set_preview_entries(&result.path, entries); |
| | 352 | + } |
| | 353 | + } |
| | 354 | + } |
| | 355 | + ev.request_redraw(); |
| | 356 | + } |
| | 357 | + |
| | 358 | + // Check for pending preview requests and submit them |
| | 359 | + self.process_pending_previews(); |
| | 360 | + |
| 342 | if ev.needs_redraw() { | 361 | if ev.needs_redraw() { |
| 343 | let _ = self.render(); | 362 | let _ = self.render(); |
| 344 | ev.redraw_done(); | 363 | ev.redraw_done(); |
@@ -623,42 +642,42 @@ impl App { |
| 623 | } | 642 | } |
| 624 | } | 643 | } |
| 625 | | 644 | |
| 626 | - /// Handle mouse move. | 645 | + /// Handle mouse move. Returns true if a redraw is needed. |
| 627 | - fn handle_mouse_move(&mut self, pos: Point) { | 646 | + fn handle_mouse_move(&mut self, pos: Point) -> bool { |
| 628 | // Handle progress dialog hover | 647 | // Handle progress dialog hover |
| 629 | if self.progress_dialog.is_visible() { | 648 | if self.progress_dialog.is_visible() { |
| 630 | self.progress_dialog.on_mouse_move(pos); | 649 | self.progress_dialog.on_mouse_move(pos); |
| 631 | - return; | 650 | + return true; // Dialogs always redraw for responsiveness |
| 632 | } | 651 | } |
| 633 | | 652 | |
| 634 | // Handle confirm dialog hover | 653 | // Handle confirm dialog hover |
| 635 | if self.confirm_dialog.is_visible() { | 654 | if self.confirm_dialog.is_visible() { |
| 636 | self.confirm_dialog.on_mouse_move(pos); | 655 | self.confirm_dialog.on_mouse_move(pos); |
| 637 | - return; | 656 | + return true; |
| 638 | } | 657 | } |
| 639 | | 658 | |
| 640 | // Handle conflict dialog hover | 659 | // Handle conflict dialog hover |
| 641 | if self.conflict_dialog.is_visible() { | 660 | if self.conflict_dialog.is_visible() { |
| 642 | self.conflict_dialog.on_mouse_move(pos); | 661 | self.conflict_dialog.on_mouse_move(pos); |
| 643 | - return; | 662 | + return true; |
| 644 | } | 663 | } |
| 645 | | 664 | |
| 646 | // Handle input dialog hover | 665 | // Handle input dialog hover |
| 647 | if self.input_dialog.is_visible() { | 666 | if self.input_dialog.is_visible() { |
| 648 | self.input_dialog.on_mouse_move(pos); | 667 | self.input_dialog.on_mouse_move(pos); |
| 649 | - return; | 668 | + return true; |
| 650 | } | 669 | } |
| 651 | | 670 | |
| 652 | // Handle app picker hover | 671 | // Handle app picker hover |
| 653 | if self.app_picker.is_visible() { | 672 | if self.app_picker.is_visible() { |
| 654 | self.app_picker.on_mouse_move(pos); | 673 | self.app_picker.on_mouse_move(pos); |
| 655 | - return; | 674 | + return true; |
| 656 | } | 675 | } |
| 657 | | 676 | |
| 658 | // Handle context menu hover | 677 | // Handle context menu hover |
| 659 | if self.context_menu.is_visible() { | 678 | if self.context_menu.is_visible() { |
| 660 | self.context_menu.on_mouse_move(pos); | 679 | self.context_menu.on_mouse_move(pos); |
| 661 | - return; | 680 | + return true; |
| 662 | } | 681 | } |
| 663 | | 682 | |
| 664 | // Handle sidebar resize in progress | 683 | // Handle sidebar resize in progress |
@@ -667,14 +686,14 @@ impl App { |
| 667 | self.sidebar.set_width(new_width); | 686 | self.sidebar.set_width(new_width); |
| 668 | let size = self.renderer.size(); | 687 | let size = self.renderer.size(); |
| 669 | self.update_layout(size.width, size.height); | 688 | self.update_layout(size.width, size.height); |
| 670 | - return; | 689 | + return true; |
| 671 | } | 690 | } |
| 672 | | 691 | |
| 673 | // Handle pane divider resize in progress | 692 | // Handle pane divider resize in progress |
| 674 | if let Some(path) = &self.pane_resize_path { | 693 | if let Some(path) = &self.pane_resize_path { |
| 675 | let path_clone = path.clone(); | 694 | let path_clone = path.clone(); |
| 676 | self.root_pane.adjust_split_at(&path_clone, pos); | 695 | self.root_pane.adjust_split_at(&path_clone, pos); |
| 677 | - return; | 696 | + return true; |
| 678 | } | 697 | } |
| 679 | | 698 | |
| 680 | // Handle column resize/drag in progress | 699 | // Handle column resize/drag in progress |
@@ -683,18 +702,22 @@ impl App { |
| 683 | if let Some(tab) = pane.active_tab_mut() { | 702 | if let Some(tab) = pane.active_tab_mut() { |
| 684 | tab.on_mouse_move(pos); | 703 | tab.on_mouse_move(pos); |
| 685 | } | 704 | } |
| 686 | - return; | 705 | + return true; |
| 687 | } | 706 | } |
| 688 | } | 707 | } |
| 689 | | 708 | |
| | 709 | + let mut needs_redraw = false; |
| | 710 | + |
| 690 | // Handle tab reorder drag in progress | 711 | // Handle tab reorder drag in progress |
| 691 | if self.tab_bar.dragging_tab().is_some() { | 712 | if self.tab_bar.dragging_tab().is_some() { |
| 692 | self.tab_bar.update_drag(pos); | 713 | self.tab_bar.update_drag(pos); |
| | 714 | + needs_redraw = true; |
| 693 | } | 715 | } |
| 694 | | 716 | |
| 695 | // Handle bookmark reorder drag in progress | 717 | // Handle bookmark reorder drag in progress |
| 696 | if self.sidebar.bookmark_drag_index().is_some() { | 718 | if self.sidebar.bookmark_drag_index().is_some() { |
| 697 | self.sidebar.update_bookmark_drag(pos); | 719 | self.sidebar.update_bookmark_drag(pos); |
| | 720 | + needs_redraw = true; |
| 698 | } | 721 | } |
| 699 | | 722 | |
| 700 | // Handle bookmark drag in progress (dragging from file view) | 723 | // Handle bookmark drag in progress (dragging from file view) |
@@ -715,18 +738,22 @@ impl App { |
| 715 | let is_over_drop_zone = self.sidebar.is_bookmark_drop_zone(pos); | 738 | let is_over_drop_zone = self.sidebar.is_bookmark_drop_zone(pos); |
| 716 | self.sidebar.set_drop_highlight(is_over_drop_zone); | 739 | self.sidebar.set_drop_highlight(is_over_drop_zone); |
| 717 | } | 740 | } |
| | 741 | + needs_redraw = true; |
| 718 | } | 742 | } |
| 719 | | 743 | |
| 720 | - self.toolbar.on_mouse_move(pos); | 744 | + // Check hover states - only redraw if any changed |
| 721 | - self.breadcrumb.on_mouse_move(pos); | 745 | + needs_redraw |= self.toolbar.on_mouse_move(pos); |
| 722 | - self.sidebar.on_mouse_move(pos); | 746 | + needs_redraw |= self.breadcrumb.on_mouse_move(pos); |
| 723 | - self.tab_bar.on_mouse_move(pos); | 747 | + needs_redraw |= self.sidebar.on_mouse_move(pos); |
| | 748 | + needs_redraw |= self.tab_bar.on_mouse_move(pos); |
| 724 | | 749 | |
| 725 | if let Some(pane) = self.focused_pane_mut() { | 750 | if let Some(pane) = self.focused_pane_mut() { |
| 726 | if let Some(tab) = pane.active_tab_mut() { | 751 | if let Some(tab) = pane.active_tab_mut() { |
| 727 | - tab.on_mouse_move(pos); | 752 | + needs_redraw |= tab.on_mouse_move(pos); |
| 728 | } | 753 | } |
| 729 | } | 754 | } |
| | 755 | + |
| | 756 | + needs_redraw |
| 730 | } | 757 | } |
| 731 | | 758 | |
| 732 | /// Handle a key press. | 759 | /// Handle a key press. |
@@ -2447,6 +2474,18 @@ impl App { |
| 2447 | } | 2474 | } |
| 2448 | } | 2475 | } |
| 2449 | | 2476 | |
| | 2477 | + /// Process pending preview requests from column views. |
| | 2478 | + fn process_pending_previews(&mut self) { |
| | 2479 | + // Check focused pane's active tab for pending preview |
| | 2480 | + if let Some(pane) = self.focused_pane_mut() { |
| | 2481 | + if let Some(tab) = pane.active_tab_mut() { |
| | 2482 | + if let Some((path, sort_order, sort_direction)) = tab.take_pending_preview() { |
| | 2483 | + self.preview_loader.load(path, sort_order, sort_direction); |
| | 2484 | + } |
| | 2485 | + } |
| | 2486 | + } |
| | 2487 | + } |
| | 2488 | + |
| 2450 | /// Update status bar. | 2489 | /// Update status bar. |
| 2451 | fn update_status_bar(&mut self) { | 2490 | fn update_status_bar(&mut self) { |
| 2452 | let stats = self.focused_pane() | 2491 | let stats = self.focused_pane() |