summaryrefslogtreecommitdiff
path: root/tools/timeline_editor
diff options
context:
space:
mode:
Diffstat (limited to 'tools/timeline_editor')
-rw-r--r--tools/timeline_editor/index.html116
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') {