@@ -8,9 +8,10 @@ import {
88 createEffect ,
99 createMemo ,
1010 createRoot ,
11+ createSignal ,
1112 For ,
1213 Match ,
13- mergeProps ,
14+ onCleanup ,
1415 Show ,
1516 Switch ,
1617} from "solid-js" ;
@@ -204,18 +205,40 @@ export function ClipTrack(
204205 >
205206 < For each = { segments ( ) } >
206207 { ( segment , i ) => {
207- const prevDuration = ( ) =>
208- segments ( )
209- . slice ( 0 , i ( ) )
210- . reduce ( ( t , s ) => t + ( s . end - s . start ) / s . timescale , 0 ) ;
211-
212- const relativeSegment = mergeProps ( segment , ( ) => ( {
213- start : prevDuration ( ) ,
214- end : segment . end - segment . start + prevDuration ( ) ,
215- } ) ) ;
208+ const [ startHandleDrag , setStartHandleDrag ] = createSignal < null | {
209+ offset : number ;
210+ initialStart : number ;
211+ } > ( null ) ;
212+
213+ const prefixOffsets = createMemo ( ( ) => {
214+ const segs = segments ( ) ;
215+ const out : number [ ] = new Array ( segs . length ) ;
216+ let sum = 0 ;
217+ for ( let k = 0 ; k < segs . length ; k ++ ) {
218+ out [ k ] = sum ;
219+ sum += ( segs [ k ] . end - segs [ k ] . start ) / segs [ k ] . timescale ;
220+ }
221+ return out ;
222+ } ) ;
223+ const prevDuration = createMemo ( ( ) => prefixOffsets ( ) [ i ( ) ] ?? 0 ) ;
224+
225+ const relativeSegment = createMemo ( ( ) => {
226+ const ds = startHandleDrag ( ) ;
227+ const offset = ds ? ds . offset / segment . timescale : 0 ;
228+
229+ return {
230+ start : Math . max ( prevDuration ( ) + offset , 0 ) ,
231+ end :
232+ prevDuration ( ) +
233+ offset +
234+ ( segment . end - segment . start ) / segment . timescale ,
235+ timescale : segment . timescale ,
236+ recordingSegment : segment . recordingSegment ,
237+ } ;
238+ } ) ;
216239
217- const segmentX = useSegmentTranslateX ( ( ) => relativeSegment ) ;
218- const segmentWidth = useSegmentWidth ( ( ) => relativeSegment ) ;
240+ const segmentX = useSegmentTranslateX ( relativeSegment ) ;
241+ const segmentWidth = useSegmentWidth ( relativeSegment ) ;
219242
220243 const segmentRecording = ( s = i ( ) ) =>
221244 editorInstance . recordings . segments [
@@ -360,7 +383,7 @@ export function ClipTrack(
360383 : "border-transparent" ,
361384 ) }
362385 innerClass = "ring-blue-9"
363- segment = { relativeSegment }
386+ segment = { relativeSegment ( ) }
364387 onMouseDown = { ( e ) => {
365388 e . stopPropagation ( ) ;
366389
@@ -465,9 +488,14 @@ export function ClipTrack(
465488 position = "start"
466489 class = "opacity-0 group-hover:opacity-100"
467490 onMouseDown = { ( downEvent ) => {
468- const start = segment . start ;
469-
470491 if ( split ( ) ) return ;
492+
493+ const initialStart = segment . start ;
494+ setStartHandleDrag ( {
495+ offset : 0 ,
496+ initialStart,
497+ } ) ;
498+
471499 const maxSegmentDuration =
472500 editorInstance . recordings . segments [
473501 segment . recordingSegment ?? 0
@@ -498,35 +526,51 @@ export function ClipTrack(
498526
499527 function update ( event : MouseEvent ) {
500528 const newStart =
501- start +
502- ( event . clientX - downEvent . clientX ) * secsPerPixel ( ) ;
529+ initialStart +
530+ ( event . clientX - downEvent . clientX ) *
531+ secsPerPixel ( ) *
532+ segment . timescale ;
533+
534+ const clampedStart = Math . min (
535+ Math . max (
536+ newStart ,
537+ prevSegmentIsSameClip ? prevSegment . end : 0 ,
538+ segment . end - maxDuration ,
539+ ) ,
540+ segment . end - 1 ,
541+ ) ;
542+
543+ setStartHandleDrag ( {
544+ offset : clampedStart - initialStart ,
545+ initialStart,
546+ } ) ;
503547
504548 setProject (
505549 "timeline" ,
506550 "segments" ,
507551 i ( ) ,
508552 "start" ,
509- Math . min (
510- Math . max (
511- newStart ,
512- prevSegmentIsSameClip ? prevSegment . end : 0 ,
513- segment . end - maxDuration ,
514- ) ,
515- segment . end - 1 ,
516- ) ,
553+ clampedStart ,
517554 ) ;
518555 }
519556
520557 const resumeHistory = projectHistory . pause ( ) ;
521558 createRoot ( ( dispose ) => {
559+ onCleanup ( ( ) => {
560+ resumeHistory ( ) ;
561+ console . log ( "NUL" ) ;
562+ setStartHandleDrag ( null ) ;
563+ onHandleReleased ( ) ;
564+ } ) ;
565+
522566 createEventListenerMap ( window , {
523567 mousemove : update ,
524568 mouseup : ( e ) => {
525- dispose ( ) ;
526- resumeHistory ( ) ;
527569 update ( e ) ;
528- onHandleReleased ( ) ;
570+ dispose ( ) ;
529571 } ,
572+ blur : ( ) => dispose ( ) ,
573+ mouseleave : ( ) => dispose ( ) ,
530574 } ) ;
531575 } ) ;
532576 } }
@@ -583,9 +627,11 @@ export function ClipTrack(
583627 : false ;
584628
585629 function update ( event : MouseEvent ) {
586- const newEnd =
587- end +
588- ( event . clientX - downEvent . clientX ) * secsPerPixel ( ) ;
630+ const deltaRecorded =
631+ ( event . clientX - downEvent . clientX ) *
632+ secsPerPixel ( ) *
633+ segment . timescale ;
634+ const newEnd = end + deltaRecorded ;
589635
590636 setProject (
591637 "timeline" ,
@@ -595,7 +641,8 @@ export function ClipTrack(
595641 Math . max (
596642 Math . min (
597643 newEnd ,
598- segment . end + availableTimelineDuration ,
644+ // availableTimelineDuration is in timeline seconds; convert to recorded seconds
645+ end + availableTimelineDuration * segment . timescale ,
599646 nextSegmentIsSameClip
600647 ? nextSegment . start
601648 : maxSegmentDuration ,
@@ -615,6 +662,16 @@ export function ClipTrack(
615662 update ( e ) ;
616663 onHandleReleased ( ) ;
617664 } ,
665+ blur : ( ) => {
666+ dispose ( ) ;
667+ resumeHistory ( ) ;
668+ onHandleReleased ( ) ;
669+ } ,
670+ mouseleave : ( ) => {
671+ dispose ( ) ;
672+ resumeHistory ( ) ;
673+ onHandleReleased ( ) ;
674+ } ,
618675 } ) ;
619676 } ) ;
620677 } }
@@ -716,7 +773,7 @@ function CutOffsetButton(props: {
716773 { props . value === 0 ? (
717774 < IconCapScissors class = "size-3.5" />
718775 ) : (
719- < > { formatTime ( props . value ) } </ >
776+ formatTime ( props . value )
720777 ) }
721778 </ button >
722779 ) ;
0 commit comments