diff options
Diffstat (limited to 'tools/timeline_editor')
| -rw-r--r-- | tools/timeline_editor/index.html | 116 |
1 files changed, 113 insertions, 3 deletions
diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html index fb813a5..f5277d8 100644 --- a/tools/timeline_editor/index.html +++ b/tools/timeline_editor/index.html @@ -234,6 +234,36 @@ color: #d4d4d4; } + .effect-handle { + position: absolute; + top: 0; + width: 6px; + height: 100%; + background: rgba(78, 201, 176, 0.8); + cursor: ew-resize; + display: none; + z-index: 10; + } + + .effect.selected .effect-handle { + display: block; + } + + .effect-handle.left { + left: 0; + border-radius: 3px 0 0 3px; + } + + .effect-handle.right { + right: 0; + border-radius: 0 3px 3px 0; + } + + .effect-handle:hover { + background: rgba(78, 201, 176, 1); + width: 8px; + } + .properties-panel { position: fixed; top: 80px; @@ -416,6 +446,8 @@ let isDragging = false; let dragOffset = { x: 0, y: 0 }; let lastActiveSeqIndex = -1; + let isDraggingHandle = false; + let handleType = null; // 'left' or 'right' // DOM elements const timeline = document.getElementById('timeline'); @@ -701,7 +733,11 @@ } // Show only class name, full info on hover - effectDiv.innerHTML = `<small>${effect.className}</small>`; + effectDiv.innerHTML = ` + <div class="effect-handle left"></div> + <small>${effect.className}</small> + <div class="effect-handle right"></div> + `; effectDiv.title = `${effect.className}\n${timeDisplay}\nPriority: ${effect.priority}\n${effect.args || '(no args)'}`; if (selectedItem && selectedItem.type === 'effect' && @@ -709,9 +745,26 @@ effectDiv.classList.add('selected'); } - effectDiv.addEventListener('mousedown', (e) => { + // Handle resizing (only for selected effects) + const leftHandle = effectDiv.querySelector('.effect-handle.left'); + const rightHandle = effectDiv.querySelector('.effect-handle.right'); + + leftHandle.addEventListener('mousedown', (e) => { + e.stopPropagation(); + startHandleDrag(e, 'left', seqIndex, effectIndex); + }); + + rightHandle.addEventListener('mousedown', (e) => { e.stopPropagation(); - startDrag(e, 'effect', seqIndex, effectIndex); + startHandleDrag(e, 'right', seqIndex, effectIndex); + }); + + effectDiv.addEventListener('mousedown', (e) => { + // Only drag if not clicking on a handle + if (!e.target.classList.contains('effect-handle')) { + e.stopPropagation(); + startDrag(e, 'effect', seqIndex, effectIndex); + } }); effectDiv.addEventListener('click', (e) => { e.stopPropagation(); @@ -782,6 +835,58 @@ document.removeEventListener('mouseup', stopDrag); } + // Handle dragging (for resizing effects) + function startHandleDrag(e, type, seqIndex, effectIndex) { + e.preventDefault(); + isDraggingHandle = true; + handleType = type; + selectedItem = { type: 'effect', seqIndex, effectIndex, index: seqIndex }; + renderTimeline(); + updateProperties(); + + document.addEventListener('mousemove', onHandleDrag); + document.addEventListener('mouseup', stopHandleDrag); + } + + function onHandleDrag(e) { + if (!isDraggingHandle || !selectedItem) return; + + const timelineRect = timeline.getBoundingClientRect(); + const newX = e.clientX - timelineRect.left; + let newTime = Math.max(0, newX / pixelsPerSecond); + + // Snap to beat when in beat mode + if (showBeats) { + const beatDuration = 60.0 / bpm; + const nearestBeat = Math.round(newTime / beatDuration); + newTime = nearestBeat * beatDuration; + } + + const seq = sequences[selectedItem.seqIndex]; + const effect = seq.effects[selectedItem.effectIndex]; + + // Convert to relative time + const relativeTime = newTime - seq.startTime; + + if (handleType === 'left') { + // Adjust start time, keep end time fixed + effect.startTime = Math.max(0, Math.min(Math.round(relativeTime * 100) / 100, effect.endTime - 0.1)); + } else if (handleType === 'right') { + // Adjust end time, keep start time fixed + effect.endTime = Math.max(effect.startTime + 0.1, Math.round(relativeTime * 100) / 100); + } + + renderTimeline(); + updateProperties(); + } + + function stopHandleDrag() { + isDraggingHandle = false; + handleType = null; + document.removeEventListener('mousemove', onHandleDrag); + document.removeEventListener('mouseup', stopHandleDrag); + } + // Selection function selectItem(type, seqIndex, effectIndex = null) { selectedItem = { type, index: seqIndex, seqIndex, effectIndex }; @@ -803,6 +908,10 @@ const seq = sequences[selectedItem.index]; propertiesContent.innerHTML = ` <div class="property-group"> + <label>Name</label> + <input type="text" id="propName" value="${seq.name || ''}" placeholder="Sequence name"> + </div> + <div class="property-group"> <label>Start Time (seconds)</label> <input type="number" id="propStartTime" value="${seq.startTime}" step="0.1" min="0"> </div> @@ -845,6 +954,7 @@ if (selectedItem.type === 'sequence') { const seq = sequences[selectedItem.index]; + seq.name = document.getElementById('propName').value; seq.startTime = parseFloat(document.getElementById('propStartTime').value); seq.priority = parseInt(document.getElementById('propPriority').value); } else if (selectedItem.type === 'effect') { |
