@@ -8,13 +8,15 @@ use crate::file_system::{
88 copy_files_start as ops_copy_files_start, delete_files_start as ops_delete_files_start,
99 find_file_index as ops_find_file_index, get_file_at as ops_get_file_at, get_file_range as ops_get_file_range,
1010 get_listing_stats as ops_get_listing_stats, get_max_filename_width as ops_get_max_filename_width,
11- get_operation_status as ops_get_operation_status, get_total_count as ops_get_total_count ,
12- list_active_operations as ops_list_active_operations , list_directory_end as ops_list_directory_end ,
13- list_directory_start_streaming as ops_list_directory_start_streaming,
11+ get_operation_status as ops_get_operation_status, get_paths_at_indices as ops_get_paths_at_indices ,
12+ get_total_count as ops_get_total_count , list_active_operations as ops_list_active_operations ,
13+ list_directory_end as ops_list_directory_end , list_directory_start_streaming as ops_list_directory_start_streaming,
1414 list_directory_start_with_volume as ops_list_directory_start_with_volume, move_files_start as ops_move_files_start,
1515 resort_listing as ops_resort_listing,
1616} ;
1717use std:: path:: PathBuf ;
18+ use std:: sync:: mpsc:: channel;
19+ use tauri:: Manager ;
1820
1921/// Checks if a path exists.
2022///
@@ -378,6 +380,90 @@ pub fn get_operation_status(operation_id: String) -> Option<OperationStatus> {
378380 ops_get_operation_status ( & operation_id)
379381}
380382
383+ // ============================================================================
384+ // Drag operations
385+ // ============================================================================
386+
387+ /// Starts a native drag operation for selected files from a cached listing.
388+ ///
389+ /// This initiates the drag from Rust directly, avoiding IPC transfer of file paths.
390+ /// The paths are looked up from LISTING_CACHE using the provided indices.
391+ ///
392+ /// # Arguments
393+ /// * `app` - Tauri app handle for accessing the window
394+ /// * `listing_id` - The listing ID from `list_directory_start`
395+ /// * `selected_indices` - Frontend indices of selected files
396+ /// * `include_hidden` - Whether hidden files are shown (affects index mapping)
397+ /// * `has_parent` - Whether the ".." entry is shown at index 0
398+ /// * `mode` - Drag mode: "copy" or "move"
399+ /// * `icon_path` - Path to the drag preview icon (temp file)
400+ #[ tauri:: command]
401+ pub fn start_selection_drag (
402+ app : tauri:: AppHandle ,
403+ listing_id : String ,
404+ selected_indices : Vec < usize > ,
405+ include_hidden : bool ,
406+ has_parent : bool ,
407+ mode : String ,
408+ icon_path : String ,
409+ ) -> Result < ( ) , String > {
410+ // Get file paths from the cached listing
411+ let paths = ops_get_paths_at_indices ( & listing_id, & selected_indices, include_hidden, has_parent) ?;
412+
413+ if paths. is_empty ( ) {
414+ return Err ( "No valid files to drag" . to_string ( ) ) ;
415+ }
416+
417+ // Get the main window
418+ let window = app. get_webview_window ( "main" ) . ok_or ( "Main window not found" ) ?;
419+
420+ // Determine drag mode (Send-safe)
421+ let is_copy_mode = mode == "copy" ;
422+
423+ // Store icon path for use in closure (PathBuf is Send)
424+ let icon_path_buf = PathBuf :: from ( icon_path) ;
425+
426+ // Use a channel to get the result from the main thread
427+ let ( tx, rx) = channel ( ) ;
428+
429+ // Run on main thread (required by macOS for drag operations)
430+ // Create DragItem inside the closure since it's not Send
431+ app. run_on_main_thread ( move || {
432+ // Build DragItem inside the closure (not Send due to Data variant)
433+ let item = drag:: DragItem :: Files ( paths) ;
434+
435+ // Load icon from file path
436+ let icon = drag:: Image :: File ( icon_path_buf) ;
437+
438+ // Create options with the drag mode
439+ let options = drag:: Options {
440+ skip_animatation_on_cancel_or_failure : false ,
441+ mode : if is_copy_mode {
442+ drag:: DragMode :: Copy
443+ } else {
444+ drag:: DragMode :: Move
445+ } ,
446+ } ;
447+
448+ let result = drag:: start_drag (
449+ & window,
450+ item,
451+ icon,
452+ |_result, _cursor_pos| {
453+ // Callback when drag completes - we don't need to do anything here
454+ } ,
455+ options,
456+ ) ;
457+ let _ = tx. send ( result) ;
458+ } )
459+ . map_err ( |e| format ! ( "Failed to run on main thread: {}" , e) ) ?;
460+
461+ // Wait for the result
462+ rx. recv ( )
463+ . map_err ( |_| "Failed to receive drag result" ) ?
464+ . map_err ( |e| format ! ( "Drag operation failed: {}" , e) )
465+ }
466+
381467/// Expands tilde (~) to the user's home directory.
382468fn expand_tilde ( path : & str ) -> String {
383469 if ( path. starts_with ( "~/" ) || path == "~" )
0 commit comments