11use super :: command:: { sync_manager_to_ui, with_history_manager} ;
22use crate :: {
3- logic:: toast, logic_cb,
3+ global_store,
4+ logic:: toast,
5+ logic_cb,
46 slint_generatedAppWindow:: {
57 AppWindow , SelectedSegmentIndex as UISelectedSegmentIndex ,
68 VideoEditorTrackSegment as UIVideoEditorTrackSegment ,
@@ -10,28 +12,65 @@ use crate::{
1012use slint:: { ComponentHandle , Model , VecModel } ;
1113use std:: time:: Duration ;
1214use video_editor:: {
15+ Error ,
1316 commands:: {
1417 batch:: BatchCommand ,
1518 segment:: {
1619 MoveSegmentToTimeCommand , RemoveSegmentCommand , ShrinkSegmentLeftCommand ,
17- ShrinkSegmentRightCommand , StretchSegmentLeftCommand , StretchSegmentRightCommand ,
20+ ShrinkSegmentRightCommand , SplitSegmentCommand , StretchSegmentLeftCommand ,
21+ StretchSegmentRightCommand ,
1822 } ,
1923 } ,
2024 tracks:: track:: Track ,
21- Error ,
2225} ;
2326
2427pub fn init ( ui : & AppWindow ) {
2528 logic_cb ! ( video_editor_add_selected_segment, ui, index) ;
26- logic_cb ! ( video_editor_stretch_segment_left, ui, index, diff_duration_ms) ;
27- logic_cb ! ( video_editor_shrink_segment_left, ui, index, diff_duration_ms) ;
28- logic_cb ! ( video_editor_stretch_segment_right, ui, index, diff_duration_ms) ;
29- logic_cb ! ( video_editor_shrink_segment_right, ui, index, diff_duration_ms) ;
30- logic_cb ! ( video_editor_move_segment_to_left_timeoffet, ui, index, diff_duration_ms) ;
31- logic_cb ! ( video_editor_move_segment_to_right_timeoffset, ui, index, diff_duration_ms) ;
32- logic_cb ! ( video_editor_is_selected_segment, ui, selected_segment, index) ;
29+ logic_cb ! (
30+ video_editor_stretch_segment_left,
31+ ui,
32+ index,
33+ diff_duration_ms
34+ ) ;
35+ logic_cb ! (
36+ video_editor_shrink_segment_left,
37+ ui,
38+ index,
39+ diff_duration_ms
40+ ) ;
41+ logic_cb ! (
42+ video_editor_stretch_segment_right,
43+ ui,
44+ index,
45+ diff_duration_ms
46+ ) ;
47+ logic_cb ! (
48+ video_editor_shrink_segment_right,
49+ ui,
50+ index,
51+ diff_duration_ms
52+ ) ;
53+ logic_cb ! (
54+ video_editor_move_segment_to_left_timeoffet,
55+ ui,
56+ index,
57+ diff_duration_ms
58+ ) ;
59+ logic_cb ! (
60+ video_editor_move_segment_to_right_timeoffset,
61+ ui,
62+ index,
63+ diff_duration_ms
64+ ) ;
65+ logic_cb ! (
66+ video_editor_is_selected_segment,
67+ ui,
68+ selected_segment,
69+ index
70+ ) ;
3371 logic_cb ! ( video_editor_selected_segment, ui) ;
3472 logic_cb ! ( video_editor_remove_segments, ui) ;
73+ logic_cb ! ( video_editor_split_segment, ui) ;
3574}
3675
3776fn video_editor_add_selected_segment ( ui : & AppWindow , index : UISelectedSegmentIndex ) {
@@ -46,7 +85,8 @@ fn video_editor_add_selected_segment(ui: &AppWindow, index: UISelectedSegmentInd
4685
4786 if index. modifiers . control {
4887 if is_selected {
49- selected_indices. retain ( |s| !( s. track_index == index. track_index && s. index == index. index ) ) ;
88+ selected_indices
89+ . retain ( |s| !( s. track_index == index. track_index && s. index == index. index ) ) ;
5090 } else {
5191 selected_indices. push ( index. clone ( ) ) ;
5292 }
@@ -58,7 +98,10 @@ fn video_editor_add_selected_segment(ui: &AppWindow, index: UISelectedSegmentInd
5898 let start = last. index . min ( index. index ) ;
5999 let end = last. index . max ( index. index ) ;
60100 for i in start..=end {
61- if !selected_indices. iter ( ) . any ( |s| s. track_index == index. track_index && s. index == i) {
101+ if !selected_indices
102+ . iter ( )
103+ . any ( |s| s. track_index == index. track_index && s. index == i)
104+ {
62105 selected_indices. push ( UISelectedSegmentIndex {
63106 track_index : index. track_index ,
64107 index : i,
@@ -149,14 +192,15 @@ fn video_editor_selected_segment(ui: &AppWindow) -> UIVideoEditorTrackSegment {
149192
150193 if let Some ( ( track_idx, seg_idx) ) = selected_indices. first ( ) {
151194 if let Some ( segment_arc) = with_history_manager ( |state| {
152- state. tracks_manager . get ( * track_idx) . and_then ( |track| {
153- match track {
195+ state
196+ . tracks_manager
197+ . get ( * track_idx)
198+ . and_then ( |track| match track {
154199 Track :: Video ( inner) => inner. track . segments . get ( * seg_idx) . cloned ( ) ,
155200 Track :: Audio ( inner) => inner. track . segments . get ( * seg_idx) . cloned ( ) ,
156201 Track :: Subtitle ( inner) => inner. track . segments . get ( * seg_idx) . cloned ( ) ,
157202 Track :: Overlay ( inner) => inner. track . segments . get ( * seg_idx) . cloned ( ) ,
158- }
159- } )
203+ } )
160204 } ) {
161205 let ui_segment: UIVideoEditorTrackSegment = segment_arc. into ( ) ;
162206 return ui_segment;
@@ -196,9 +240,7 @@ fn video_editor_remove_segments(ui: &AppWindow) {
196240 for ( track_idx, mut seg_indices) in segments_per_track {
197241 seg_indices. sort_by ( |a, b| b. cmp ( a) ) ; // Sort descending
198242 for seg_idx in seg_indices {
199- batch_command. add_command ( Box :: new ( RemoveSegmentCommand :: new (
200- track_idx, seg_idx,
201- ) ) ) ;
243+ batch_command. add_command ( Box :: new ( RemoveSegmentCommand :: new ( track_idx, seg_idx) ) ) ;
202244 }
203245 }
204246
@@ -221,11 +263,68 @@ fn video_editor_remove_segments(ui: &AppWindow) {
221263 } ) ;
222264 }
223265 Err ( e) => {
224- toast:: async_toast_warn (
225- ui_weak,
226- format ! ( "Failed to remove segments: {}" , e) ,
227- ) ;
266+ toast:: async_toast_warn ( ui_weak, format ! ( "Failed to remove segments: {}" , e) ) ;
267+ }
268+ }
269+ } ) ;
270+ }
271+
272+ fn video_editor_split_segment ( ui : & AppWindow ) {
273+ let ui_weak = ui. as_weak ( ) ;
274+ let timeline_offset_ms = global_store ! ( ui) . get_video_editor_timeline_offset ( ) ;
275+ let selected_segments = get_selected_segment_indices ( & ui) ;
276+
277+ if selected_segments. len ( ) != 1 {
278+ crate :: toast_warn!( ui, "Please select exactly one segment to split" . to_string( ) ) ;
279+ return ;
280+ }
281+
282+ tokio:: spawn ( async move {
283+ let ( track_index, segment_index) = selected_segments[ 0 ] ;
284+
285+ let result: Result < ( ) , String > = with_history_manager ( |state| {
286+ if track_index >= state. tracks_manager . len ( ) {
287+ return Err ( format ! ( "Invalid track index: {}" , track_index) ) ;
288+ }
289+
290+ if let Some ( track) = state. tracks_manager . get ( track_index) {
291+ let segments = match track {
292+ Track :: Video ( inner) => & inner. track . segments ,
293+ Track :: Audio ( inner) => & inner. track . segments ,
294+ Track :: Subtitle ( inner) => & inner. track . segments ,
295+ Track :: Overlay ( inner) => & inner. track . segments ,
296+ } ;
297+
298+ if segment_index >= segments. len ( ) {
299+ return Err ( format ! ( "Invalid segment index: {}" , segment_index) ) ;
300+ }
301+
302+ let seg = & segments[ segment_index] ;
303+
304+ let segment_start = seg. timeline_offset . as_millis ( ) as i32 ;
305+ let split_position = timeline_offset_ms - segment_start;
306+
307+ if split_position <= 0 || split_position >= seg. duration . as_millis ( ) as i32 {
308+ return Err ( "Timeline cursor must be within the segment" . to_string ( ) ) ;
309+ }
310+
311+ let split_duration = Duration :: from_millis ( split_position as u64 ) ;
312+ let command = SplitSegmentCommand :: new ( track_index, segment_index, split_duration) ;
313+ state
314+ . history_manager
315+ . execute ( & mut state. tracks_manager , Box :: new ( command) )
316+ . map_err ( |e| e. to_string ( ) )
317+ } else {
318+ Err ( "Track not found" . to_string ( ) )
319+ }
320+ } ) ;
321+
322+ match result {
323+ Ok ( _) => {
324+ sync_manager_to_ui ( ui_weak. clone ( ) ) ;
325+ toast:: async_toast_success ( ui_weak, "Segment split successfully" . to_string ( ) ) ;
228326 }
327+ Err ( e) => toast:: async_toast_warn ( ui_weak, e. to_string ( ) ) ,
229328 }
230329 } ) ;
231330}
@@ -246,7 +345,10 @@ fn modify_segment_duration(
246345
247346 let result = with_history_manager ( |state| {
248347 if track_idx >= state. tracks_manager . len ( ) {
249- return Err ( Error :: IndexOutOfBounds ( track_idx, state. tracks_manager . len ( ) ) ) ;
348+ return Err ( Error :: IndexOutOfBounds (
349+ track_idx,
350+ state. tracks_manager . len ( ) ,
351+ ) ) ;
250352 }
251353
252354 let track = state
@@ -274,12 +376,8 @@ fn modify_segment_duration(
274376 . history_manager
275377 . execute ( & mut state. tracks_manager , Box :: new ( command) )
276378 } else {
277- let command = ShrinkSegmentLeftCommand :: new (
278- track_idx,
279- seg_idx,
280- duration,
281- shift_timeline,
282- ) ;
379+ let command =
380+ ShrinkSegmentLeftCommand :: new ( track_idx, seg_idx, duration, shift_timeline) ;
283381 state
284382 . history_manager
285383 . execute ( & mut state. tracks_manager , Box :: new ( command) )
@@ -335,7 +433,10 @@ fn move_segment(ui: &AppWindow, index: UISelectedSegmentIndex, diff_ms: i32) {
335433
336434 let result = with_history_manager ( |state| {
337435 if track_idx >= state. tracks_manager . len ( ) {
338- return Err ( Error :: IndexOutOfBounds ( track_idx, state. tracks_manager . len ( ) ) ) ;
436+ return Err ( Error :: IndexOutOfBounds (
437+ track_idx,
438+ state. tracks_manager . len ( ) ,
439+ ) ) ;
339440 }
340441
341442 let track = state
@@ -358,8 +459,7 @@ fn move_segment(ui: &AppWindow, index: UISelectedSegmentIndex, diff_ms: i32) {
358459 current_offset + duration
359460 } ;
360461
361- let command =
362- MoveSegmentToTimeCommand :: new ( track_idx, seg_idx, new_offset, false ) ;
462+ let command = MoveSegmentToTimeCommand :: new ( track_idx, seg_idx, new_offset, false ) ;
363463
364464 state
365465 . history_manager
@@ -389,4 +489,5 @@ fn get_selected_segment_indices(ui: &AppWindow) -> Vec<(usize, usize)> {
389489 }
390490 } )
391491 . collect ( )
392- }
492+ }
493+
0 commit comments