@@ -10,7 +10,7 @@ use crate::{
1010 } ,
1111 conversion:: {
1212 audio_filter_to_json_detail, image_filter_to_json_detail,
13- subtitle_filter_to_json_detail, video_filter_to_json_detail,
13+ subtitle_filter_to_json_detail, track_to_filter_type , video_filter_to_json_detail,
1414 } ,
1515 project:: { MARKED_FILTERS_ID , PRESET_FILTERS_ID } ,
1616 segment:: refresh_affected_segments,
@@ -130,13 +130,16 @@ pub fn init(ui: &AppWindow) {
130130 ve_filter_cb ! ( remove_filter, ui, index) ;
131131 ve_filter_cb ! ( toggle_filter, ui, index) ;
132132 ve_filter_cb ! ( copy_filter, ui, index) ;
133+ ve_filter_cb ! ( cut_filter, ui, index) ;
134+ ve_filter_cb ! ( paste_filter, ui) ;
133135 ve_filter_cb ! ( add_filter, ui, filter_name) ;
134136 ve_filter_cb ! ( create_preset_filter, ui, name) ;
135137 ve_filter_cb ! ( add_preset_filter, ui, filter) ;
136138 ve_filter_cb ! ( remove_preset_filter, ui, index, filter_type) ;
137139 ve_filter_cb ! ( add_cache_preset_filter, ui, filter) ;
138140 ve_filter_cb ! ( remove_cache_preset_filter, ui, filter) ;
139141 ve_filter_cb ! ( toggle_mark_filter, ui, filter) ;
142+ ve_filter_cb ! ( refresh_filter_list, ui) ;
140143}
141144
142145fn inner_init ( ui : & AppWindow ) {
@@ -533,6 +536,198 @@ pub fn copy_filter(ui: &AppWindow, index: i32) {
533536 }
534537}
535538
539+ pub fn cut_filter ( ui : & AppWindow , index : i32 ) {
540+ let merged_index = index as usize ;
541+ let selected_segments = get_selected_segment_indices ( ui) ;
542+ let Some ( ( track_idx, seg_idx) ) = selected_segments. last ( ) else {
543+ return ;
544+ } ;
545+
546+ let Some ( ( filter_type, filter_index) ) =
547+ get_filter_type_and_local_index ( * track_idx, * seg_idx, merged_index)
548+ else {
549+ return ;
550+ } ;
551+
552+ let filter_data: Option < ( bool , String , String ) > = with_history_manager ( |state| {
553+ let track = state. tracks_manager . get ( * track_idx) ?;
554+ let segment = track. get_segment ( * seg_idx) . ok ( ) ?;
555+
556+ match filter_type {
557+ FilterType :: Video => segment. video_filters . get ( filter_index) . map ( |f| {
558+ let detail = video_filter_to_json_detail ( & f. inner ) ;
559+ ( f. enabled ( ) , f. inner . name ( ) . to_string ( ) , detail)
560+ } ) ,
561+ FilterType :: Audio => segment. audio_filters . get ( filter_index) . map ( |f| {
562+ let detail = audio_filter_to_json_detail ( & f. inner ) ;
563+ ( f. enabled ( ) , f. inner . name ( ) . to_string ( ) , detail)
564+ } ) ,
565+ FilterType :: Subtitle => segment. subtitle_filters . get ( filter_index) . map ( |f| {
566+ let detail = subtitle_filter_to_json_detail ( & f. inner ) ;
567+ ( f. enabled ( ) , f. inner . name ( ) . to_string ( ) , detail)
568+ } ) ,
569+ FilterType :: Image => segment. image_filters . get ( filter_index) . map ( |f| {
570+ let detail = image_filter_to_json_detail ( & f. inner ) ;
571+ ( f. enabled ( ) , f. inner . name ( ) . to_string ( ) , detail)
572+ } ) ,
573+ }
574+ } ) ;
575+
576+ let filter_name = match filter_data {
577+ Some ( ( enabled, name, detail) ) => {
578+ let segment_filter = UISegmentFilter {
579+ ty : filter_type. into ( ) ,
580+ enabled,
581+ name : SharedString :: from ( name. clone ( ) ) ,
582+ detail : SharedString :: from ( detail) ,
583+ } ;
584+ global_ve_filter ! ( ui) . set_cache_copied_filter ( segment_filter) ;
585+ name
586+ }
587+ None => return ,
588+ } ;
589+
590+ let mut batch_command = BatchCommand :: new ( format ! ( "Cut filter '{}'" , filter_name) ) ;
591+
592+ for ( track_idx, seg_idx) in & selected_segments {
593+ let Some ( ( ft, fi) ) = get_filter_type_and_local_index ( * track_idx, * seg_idx, merged_index)
594+ else {
595+ continue ;
596+ } ;
597+
598+ if ft != filter_type {
599+ continue ;
600+ }
601+
602+ let command = match ft {
603+ FilterType :: Video => Box :: new ( RemoveFilterCommand :: new_video ( * track_idx, * seg_idx, fi) ) ,
604+ FilterType :: Audio => Box :: new ( RemoveFilterCommand :: new_audio ( * track_idx, * seg_idx, fi) ) ,
605+ FilterType :: Subtitle => {
606+ Box :: new ( RemoveFilterCommand :: new_subtitle ( * track_idx, * seg_idx, fi) )
607+ }
608+ FilterType :: Image => Box :: new ( RemoveFilterCommand :: new_image ( * track_idx, * seg_idx, fi) ) ,
609+ } ;
610+ batch_command. add_command ( command) ;
611+ }
612+
613+ let result = with_history_manager ( |state| {
614+ state
615+ . history_manager
616+ . execute ( & mut state. tracks_manager , Box :: new ( batch_command) )
617+ } ) ;
618+
619+ match result {
620+ Ok ( execute_result) => {
621+ sync_manager_to_ui ( ui) ;
622+ refresh_affected_segments ( ui, execute_result. affected_segments ) ;
623+ refresh_preview ( ui) ;
624+
625+ global_ve_filter ! ( ui) . set_selected_filter_index ( -1 ) ;
626+ global_store ! ( ui) . set_video_editor_segment_filter_flag (
627+ !global_store ! ( ui) . get_video_editor_segment_filter_flag ( ) ,
628+ ) ;
629+ crate :: toast_success!( ui, format!( "Cut filter: {}" , filter_name) ) ;
630+ }
631+ Err ( e) => crate :: toast_warn!( ui, format!( "Failed to cut filter: {}" , e) ) ,
632+ }
633+ }
634+
635+ pub fn paste_filter ( ui : & AppWindow ) {
636+ let cached_filter = global_ve_filter ! ( ui) . get_cache_copied_filter ( ) ;
637+ let filter_name = cached_filter. name . to_string ( ) ;
638+
639+ if filter_name. is_empty ( ) {
640+ crate :: toast_warn!( ui, "No filter copied to paste" ) ;
641+ return ;
642+ }
643+
644+ let selected_segments = get_selected_segment_indices ( ui) ;
645+ let Some ( ( track_idx, _seg_idx) ) = selected_segments. last ( ) else {
646+ return ;
647+ } ;
648+
649+ let track_type: Option < FilterType > = with_history_manager ( |state| {
650+ let track = state. tracks_manager . get ( * track_idx) ?;
651+ Some ( track_to_filter_type ( & track) )
652+ } ) ;
653+
654+ let Some ( track_type) = track_type else {
655+ return ;
656+ } ;
657+
658+ let copied_type: FilterType = cached_filter. ty . into ( ) ;
659+
660+ // Validate compatibility between copied filter type and track type
661+ if !is_filter_compatible ( & copied_type, & track_type) {
662+ crate :: toast_warn!(
663+ ui,
664+ format!(
665+ "Cannot paste {} filter to {} track" ,
666+ copied_type. to_string( ) ,
667+ track_type. to_string( )
668+ )
669+ ) ;
670+ return ;
671+ }
672+
673+ let paste_type = copied_type;
674+ let filter_detail = cached_filter. detail . to_string ( ) ;
675+ let enabled = cached_filter. enabled ;
676+
677+ let mut batch_command = BatchCommand :: new ( format ! ( "Paste filter '{}'" , filter_name) ) ;
678+
679+ for ( track_idx, seg_idx) in & selected_segments {
680+ if let Some ( cmd) = create_filter_command_with_detail (
681+ * track_idx,
682+ * seg_idx,
683+ paste_type. clone ( ) ,
684+ & filter_name,
685+ & filter_detail,
686+ enabled,
687+ ) {
688+ batch_command. add_command ( cmd) ;
689+ }
690+ }
691+
692+ let result = with_history_manager ( |state| {
693+ state
694+ . history_manager
695+ . execute ( & mut state. tracks_manager , Box :: new ( batch_command) )
696+ } ) ;
697+
698+ match result {
699+ Ok ( execute_result) => {
700+ sync_manager_to_ui ( ui) ;
701+ refresh_affected_segments ( ui, execute_result. affected_segments ) ;
702+ refresh_preview ( ui) ;
703+
704+ global_ve_filter ! ( ui) . set_selected_filter_index ( -1 ) ;
705+ global_ve_filter ! ( ui) . invoke_refresh_filter_list ( ) ;
706+ crate :: toast_success!( ui, format!( "Pasted filter: {}" , filter_name) ) ;
707+ }
708+ Err ( e) => crate :: toast_warn!( ui, format!( "Failed to paste filter: {}" , e) ) ,
709+ }
710+ }
711+
712+ pub fn refresh_filter_list ( ui : & AppWindow ) {
713+ global_store ! ( ui) . set_video_editor_segment_filter_flag (
714+ !global_store ! ( ui) . get_video_editor_segment_filter_flag ( ) ,
715+ ) ;
716+ }
717+
718+ fn is_filter_compatible ( filter_type : & FilterType , track_type : & FilterType ) -> bool {
719+ match ( filter_type, track_type) {
720+ ( FilterType :: Video , FilterType :: Video ) => true ,
721+ ( FilterType :: Video , FilterType :: Image ) => true ,
722+ ( FilterType :: Image , FilterType :: Image ) => true ,
723+ ( FilterType :: Image , FilterType :: Video ) => true ,
724+ ( FilterType :: Audio , FilterType :: Audio ) => true ,
725+ ( FilterType :: Audio , FilterType :: Video ) => true ,
726+ ( FilterType :: Subtitle , FilterType :: Subtitle ) => true ,
727+ _ => false ,
728+ }
729+ }
730+
536731fn add_filter ( ui : & AppWindow , entry : UIFilterEntry ) {
537732 let filter_name = entry. name . to_string ( ) ;
538733 let filter_type: FilterType = entry. ty . into ( ) ;
0 commit comments