Skip to content

Commit ca35a9f

Browse files
committed
Fixed playhead and ruler dragging to be global (i.e. you can drag outside the timeline without interrupting the drag operation).
1 parent 8f378dc commit ca35a9f

File tree

2 files changed

+55
-36
lines changed

2 files changed

+55
-36
lines changed

src/timeline/js/directives/playhead.js

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,26 +37,24 @@ App.directive("tlPlayhead", function () {
3737
link: function (scope, element, attrs) {
3838
// get the default top position so we can lock it in place vertically
3939
playhead_y_max = element.position().top;
40+
var isDragging = false;
4041

4142
element.on("mousedown", function (e) {
4243
// Set bounding box for the playhead
4344
setBoundingBox(scope, $("#playhead"), "playhead");
44-
if (scope.Qt) {
45-
// Disable caching thread during scrubbing
46-
timeline.DisableCacheThread();
47-
}
48-
});
4945

50-
element.on("contextmenu", function (e) {
46+
// Start dragging
47+
isDragging = true;
48+
5149
if (scope.Qt) {
52-
// Enable caching thread after scrubbing
53-
timeline.EnableCacheThread();
50+
// Disable caching thread during scrubbing
51+
timeline.DisableCacheThread();
5452
}
5553
});
5654

57-
// Move playhead to new position (if it's not currently being animated)
58-
element.on("mousemove", function (e) {
59-
if (e.which === 1 && !scope.playhead_animating && !scope.getDragging()) { // left button
55+
// Global mousemove listener
56+
$(document).on("mousemove", function (e) {
57+
if (isDragging && e.which === 1 && !scope.playhead_animating && !scope.getDragging()) { // left button is held
6058
// Calculate the playhead bounding box movement and apply snapping rules
6159
let cursor_position = e.pageX - $("#ruler").offset().left;
6260
let results = moveBoundingBox(scope, bounding_box.left, bounding_box.top,
@@ -71,11 +69,23 @@ App.directive("tlPlayhead", function () {
7169

7270
// Move playhead
7371
let playhead_seconds = snapToFPSGridTime(scope, pixelToTime(scope, new_position));
72+
playhead_seconds = Math.min(Math.max(0.0, playhead_seconds), scope.project.duration);
7473
scope.movePlayhead(playhead_seconds);
7574
scope.previewFrame(playhead_seconds);
7675
}
7776
});
7877

78+
// Global mouseup listener to stop dragging
79+
$(document).on("mouseup", function (e) {
80+
if (isDragging) {
81+
isDragging = false;
82+
83+
if (scope.Qt) {
84+
// Enable caching thread after scrubbing
85+
timeline.EnableCacheThread();
86+
}
87+
}
88+
});
7989
}
8090
};
8191
});

src/timeline/js/directives/ruler.js

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
*/
2828

2929

30-
/*global setSelections, setBoundingBox, moveBoundingBox, bounding_box */
30+
/*global App, timeline, secondsToTime, setSelections, setBoundingBox, moveBoundingBox, bounding_box */
3131
// Variables for panning by middle click
3232
var is_scrolling = false;
3333
var starting_scrollbar = {x: 0, y: 0};
@@ -39,7 +39,6 @@ var scroll_left_pixels = 0;
3939

4040
// This container allows for tracks to be scrolled (with synced ruler)
4141
// and allows for panning of the timeline with the middle mouse button
42-
/*global App, timeline, secondsToTime*/
4342
App.directive("tlScrollableTracks", function () {
4443
return {
4544
restrict: "A",
@@ -154,11 +153,24 @@ App.directive("tlRuler", function ($timeout) {
154153
return {
155154
restrict: "A",
156155
link: function (scope, element, attrs) {
157-
//on click of the ruler canvas, jump playhead to the clicked spot
156+
var isDragging = false;
157+
158+
// Start dragging when mousedown on the ruler
158159
element.on("mousedown", function (e) {
160+
// Set bounding box for the playhead position
161+
setBoundingBox(scope, $("#playhead"), "playhead");
162+
isDragging = true;
163+
164+
if (scope.Qt) {
165+
// Disable caching thread during scrubbing
166+
timeline.DisableCacheThread();
167+
}
168+
159169
// Get playhead position
160170
var playhead_left = e.pageX - element.offset().left;
161171
var playhead_seconds = snapToFPSGridTime(scope, pixelToTime(scope, playhead_left));
172+
playhead_seconds = Math.min(Math.max(0.0, playhead_seconds), scope.project.duration);
173+
var playhead_snapped_target = playhead_seconds * scope.pixelsPerSecond;
162174

163175
// Immediately preview frame (don't wait for animated playhead)
164176
scope.previewFrame(playhead_seconds);
@@ -170,8 +182,8 @@ App.directive("tlRuler", function ($timeout) {
170182

171183
// Animate to new position (and then update scope)
172184
scope.playhead_animating = true;
173-
$(".playhead-line").animate({left: playhead_left}, 200);
174-
$(".playhead-top").animate({left: playhead_left}, 200, function () {
185+
$(".playhead-line").animate({left: playhead_snapped_target}, 150);
186+
$(".playhead-top").animate({left: playhead_snapped_target}, 150, function () {
175187
// Update playhead
176188
scope.movePlayhead(playhead_seconds);
177189

@@ -182,30 +194,14 @@ App.directive("tlRuler", function ($timeout) {
182194
});
183195
});
184196

185-
element.on("mousedown", function (e) {
186-
// Set bounding box for the playhead position
187-
setBoundingBox(scope, $("#playhead"), "playhead");
188-
if (scope.Qt) {
189-
// Disable caching thread during scrubbing
190-
timeline.DisableCacheThread();
191-
}
192-
});
193-
194-
element.on("contextmenu", function (e) {
195-
if (scope.Qt) {
196-
// Enable caching thread after scrubbing
197-
timeline.EnableCacheThread();
198-
}
199-
});
200-
201-
// Move playhead to new position (if it's not currently being animated)
202-
element.on("mousemove", function (e) {
203-
if (e.which === 1 && !scope.playhead_animating && !scope.getDragging()) { // left button
197+
// Global mousemove listener
198+
$(document).on("mousemove", function (e) {
199+
if (isDragging && e.which === 1 && !scope.playhead_animating && !scope.getDragging()) { // left button is held
204200
// Calculate the playhead bounding box movement
205201
let cursor_position = e.pageX - $("#ruler").offset().left;
206202
let new_position = cursor_position;
207203
if (e.shiftKey) {
208-
// Only apply playhead shapping when SHIFT is pressed
204+
// Only apply playhead snapping when SHIFT is pressed
209205
let results = moveBoundingBox(scope, bounding_box.left, bounding_box.top,
210206
cursor_position - bounding_box.left, cursor_position - bounding_box.top,
211207
cursor_position, cursor_position, "playhead");
@@ -216,11 +212,24 @@ App.directive("tlRuler", function ($timeout) {
216212

217213
// Move playhead
218214
let playhead_seconds = new_position / scope.pixelsPerSecond;
215+
playhead_seconds = Math.min(Math.max(0.0, playhead_seconds), scope.project.duration);
219216
scope.movePlayhead(playhead_seconds);
220217
scope.previewFrame(playhead_seconds);
221218
}
222219
});
223220

221+
// Global mouseup listener to stop dragging
222+
$(document).on("mouseup", function (e) {
223+
if (isDragging) {
224+
isDragging = false;
225+
226+
if (scope.Qt) {
227+
// Enable caching thread after scrubbing
228+
timeline.EnableCacheThread();
229+
}
230+
}
231+
});
232+
224233
/**
225234
* Draw frame precision alternating banding on each track (when zoomed in)
226235
*/

0 commit comments

Comments
 (0)