Skip to content

Commit 035fdb1

Browse files
authored
Merge pull request #4123 from OpenShot/snapping-improvements
WIP: Improved snapping support (Playhead + Trimming)
2 parents df1ddcd + b721d31 commit 035fdb1

File tree

10 files changed

+247
-138
lines changed

10 files changed

+247
-138
lines changed

src/timeline/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
<!-- RULER (right of screen) -->
4848
<div id="scrolling_ruler">
4949
<!-- PLAYHEAD TOP -->
50-
<div tl-playhead class="playhead playhead-top" ng-right-click="showPlayheadMenu(project.playhead_position)" style="left:{{project.playhead_position * pixelsPerSecond}}px;">
50+
<div tl-playhead class="playhead playhead-top" id="playhead" ng-right-click="showPlayheadMenu(project.playhead_position)" style="left:{{project.playhead_position * pixelsPerSecond}}px;">
5151
<div class="playhead-line-small"></div>
5252
</div>
5353
<!-- Ruler extends beyond tracks area at least for a width of vertical scroll bar (or more, like 50px here) -->

src/timeline/js/controllers.js

+35-8
Original file line numberDiff line numberDiff line change
@@ -1107,8 +1107,18 @@ App.controller("TimelineCtrl", function ($scope) {
11071107
continue;
11081108
}
11091109

1110-
diffs.push({"diff": position - clip_left_position, "position": clip_left_position}, // left side of clip
1111-
{"diff": position - clip_right_position, "position": clip_right_position}); // right side of clip
1110+
// left side of clip
1111+
let left_edge_diff = position - clip_left_position;
1112+
if (Math.abs(left_edge_diff) <= threshold) {
1113+
diffs.push({"diff": left_edge_diff, "position": clip_left_position, "id": clip.id, "side": "left"});
1114+
}
1115+
1116+
// right side of clip
1117+
let right_edge_diff = position - clip_right_position;
1118+
if (Math.abs(right_edge_diff) <= threshold) {
1119+
diffs.push({"diff": right_edge_diff, "position": clip_right_position, "id": clip.id, "side": "right"});
1120+
}
1121+
11121122
}
11131123

11141124
// Add transition positions to array
@@ -1123,23 +1133,40 @@ App.controller("TimelineCtrl", function ($scope) {
11231133
continue;
11241134
}
11251135

1126-
diffs.push({"diff": position - tran_left_position, "position": tran_left_position}, // left side of transition
1127-
{"diff": position - tran_right_position, "position": tran_right_position}); // right side of transition
1136+
// left side of transition
1137+
let left_edge_diff = position - tran_left_position;
1138+
if (Math.abs(left_edge_diff) <= threshold) {
1139+
diffs.push({"diff": left_edge_diff, "position": tran_left_position});
1140+
}
1141+
1142+
// right side of transition
1143+
let right_edge_diff = position - tran_right_position;
1144+
if (Math.abs(right_edge_diff) <= threshold) {
1145+
diffs.push({"diff": right_edge_diff, "position": tran_right_position});
1146+
}
1147+
11281148
}
11291149

11301150
// Add marker positions to array
11311151
for (var index = 0; index < $scope.project.markers.length; index++) {
11321152
var marker = $scope.project.markers[index];
11331153
var marker_position = marker.position * $scope.pixelsPerSecond;
11341154

1135-
diffs.push({"diff": position - marker_position, "position": marker_position}, // left side of marker
1136-
{"diff": position - marker_position, "position": marker_position}); // right side of marker
1155+
// marker position
1156+
let left_edge_diff = position - marker_position;
1157+
if (Math.abs(left_edge_diff) <= threshold) {
1158+
diffs.push({"diff": left_edge_diff, "position": marker_position});
1159+
}
11371160
}
11381161

11391162
// Add playhead position to array
11401163
var playhead_pixel_position = $scope.project.playhead_position * $scope.pixelsPerSecond;
11411164
var playhead_diff = position - playhead_pixel_position;
1142-
diffs.push({"diff": playhead_diff, "position": playhead_pixel_position});
1165+
if (!ignore_ids.hasOwnProperty("ruler") && !ignore_ids.hasOwnProperty("playhead")) {
1166+
if (Math.abs(playhead_diff) <= threshold) {
1167+
diffs.push({"diff": playhead_diff, "position": playhead_pixel_position});
1168+
}
1169+
}
11431170

11441171
// Loop through diffs (and find the smallest one)
11451172
for (var diff_index = 0; diff_index < diffs.length; diff_index++) {
@@ -1148,7 +1175,7 @@ App.controller("TimelineCtrl", function ($scope) {
11481175
var abs_diff = Math.abs(diff);
11491176

11501177
// Check if this clip is nearby
1151-
if (abs_diff < smallest_abs_diff && abs_diff <= threshold) {
1178+
if (abs_diff < smallest_abs_diff && abs_diff <= threshold && diff_position) {
11521179
// This one is smaller
11531180
smallest_diff = diff;
11541181
smallest_abs_diff = abs_diff;

src/timeline/js/directives/clip.js

+52-53
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
*/
2828

2929

30+
/*global setSelections, setBoundingBox, moveBoundingBox, bounding_box */
3031
// Init variables
3132
var dragging = false;
3233
var resize_disabled = false;
@@ -55,6 +56,12 @@ App.directive("tlClip", function ($timeout) {
5556
start: function (e, ui) {
5657
dragging = true;
5758

59+
// Set selections
60+
setSelections(scope, element, $(this).attr("id"));
61+
62+
// Set bounding box
63+
setBoundingBox(scope, $(this), "trimming");
64+
5865
//determine which side is being changed
5966
var parentOffset = element.offset();
6067
var mouseLoc = e.pageX - parentOffset.left;
@@ -92,6 +99,9 @@ App.directive("tlClip", function ($timeout) {
9299
return;
93100
}
94101

102+
// Hide snapline (if any)
103+
scope.hideSnapline();
104+
95105
// Hide keyframe points
96106
if (dragLoc === "right") {
97107
// Make the keyframe points visible again
@@ -100,7 +110,7 @@ App.directive("tlClip", function ($timeout) {
100110
}
101111

102112
//get amount changed in width
103-
var delta_x = ui.originalSize.width - ui.size.width;
113+
var delta_x = ui.originalSize.width - ui.element.width();
104114
var delta_time = delta_x / scope.pixelsPerSecond;
105115

106116
//change the clip end/start based on which side was dragged
@@ -131,12 +141,7 @@ App.directive("tlClip", function ($timeout) {
131141

132142
//apply the new start, end and length to the clip's scope
133143
scope.$apply(function () {
134-
// Get the nearest starting frame position to the clip position (this helps to prevent cutting
135-
// in-between frames, and thus less likely to repeat or skip a frame).
136-
new_position = (Math.round((new_position * scope.project.fps.num) / scope.project.fps.den) * scope.project.fps.den ) / scope.project.fps.num;
137-
new_right = (Math.round((new_right * scope.project.fps.num) / scope.project.fps.den) * scope.project.fps.den ) / scope.project.fps.num;
138-
new_left = (Math.round((new_left * scope.project.fps.num) / scope.project.fps.den) * scope.project.fps.den ) / scope.project.fps.num;
139-
144+
// Apply clip scope changes
140145
if (scope.clip.end !== new_right) {
141146
scope.clip.end = new_right;
142147
}
@@ -168,36 +173,51 @@ App.directive("tlClip", function ($timeout) {
168173
return;
169174
}
170175

171-
// get amount changed in width
172-
var delta_x = parseFloat(ui.originalSize.width) - ui.size.width;
173-
var delta_time = delta_x / scope.pixelsPerSecond;
176+
// Calculate the clip bounding box movement and apply snapping rules
177+
let cursor_position = e.pageX - $("#ruler").offset().left;
178+
let results = moveBoundingBox(scope, bounding_box.left, bounding_box.top,
179+
cursor_position - bounding_box.left, cursor_position - bounding_box.top,
180+
cursor_position, cursor_position, "trimming");
174181

175-
// change the clip end/start based on which side was dragged
176-
var new_left = scope.clip.start;
177-
var new_right = scope.clip.end;
182+
// Calculate delta from current mouse position
183+
let new_position = cursor_position;
184+
new_position = results.position.left;
185+
let delta_x = 0;
186+
if (dragLoc === "left") {
187+
delta_x = (parseFloat(ui.originalPosition.left)) - new_position;
188+
} else if (dragLoc === "right") {
189+
delta_x = (parseFloat(ui.originalPosition.left) + parseFloat(ui.originalSize.width)) - new_position;
190+
}
191+
192+
// Calculate the pixel locations of the left and right side
193+
var new_left = scope.clip.start * scope.pixelsPerSecond;
194+
var new_right = scope.clip.end * scope.pixelsPerSecond;
178195

179196
if (dragLoc === "left") {
180-
// changing the start of the clip
181-
new_left += delta_time;
182-
if (new_left < 0) {
183-
ui.element.width(ui.size.width + (new_left * scope.pixelsPerSecond));
184-
ui.element.css("left", ui.position.left - (new_left * scope.pixelsPerSecond));
185-
}
186-
else {
187-
ui.element.width(ui.size.width);
197+
// Adjust left side of clip
198+
// Don't allow less than 0.0 start
199+
let zero_left_x = (scope.clip.position - scope.clip.start) * scope.pixelsPerSecond;
200+
let proposed_left_x = ui.originalPosition.left - delta_x;
201+
if (proposed_left_x < zero_left_x) {
202+
// prevent less than 0.0
203+
delta_x = ui.originalPosition.left - zero_left_x;
188204
}
205+
206+
// Position and size clip
207+
ui.element.css("left", ui.originalPosition.left - delta_x);
208+
ui.element.width(new_right - (new_left - delta_x));
189209
}
190210
else {
191-
// changing the end of the clips
192-
new_right -= delta_time;
193-
if (new_right > scope.clip.duration) {
194-
211+
// Adjust right side of clip
212+
new_right -= delta_x;
213+
let duration_pixels = scope.clip.duration * scope.pixelsPerSecond;
214+
if (new_right > duration_pixels) {
195215
// change back to actual duration (for the preview below)
196-
new_right = scope.clip.duration;
197-
ui.element.width(new_right * scope.pixelsPerSecond);
216+
new_right = duration_pixels;
217+
ui.element.width(new_right);
198218
}
199219
else {
200-
ui.element.width(ui.size.width);
220+
ui.element.width((new_right - new_left));
201221
}
202222
}
203223

@@ -237,31 +257,9 @@ App.directive("tlClip", function ($timeout) {
237257
start: function (event, ui) {
238258
previous_drag_position = null;
239259
dragging = true;
240-
if (!element.hasClass("ui-selected")) {
241-
// Clear previous selections?
242-
var clear_selections = false;
243-
if ($(".ui-selected").length > 0) {
244-
clear_selections = true;
245-
}
246260

247-
// selectClip, selectTransition
248-
var id = $(this).attr("id");
249-
if (element.hasClass("clip")) {
250-
// Select this clip, unselect all others
251-
scope.selectTransition("", clear_selections);
252-
scope.selectClip(id, clear_selections);
253-
254-
}
255-
else if (element.hasClass("transition")) {
256-
// Select this transition, unselect all others
257-
scope.selectClip("", clear_selections);
258-
scope.selectTransition(id, clear_selections);
259-
}
260-
}
261-
262-
// Apply scope up to this point
263-
scope.$apply(function () {
264-
});
261+
// Set selections
262+
setSelections(scope, element, $(this).attr("id"));
265263

266264
var scrolling_tracks = $("#scrolling_tracks");
267265
var vert_scroll_offset = scrolling_tracks.scrollTop();
@@ -271,7 +269,8 @@ App.directive("tlClip", function ($timeout) {
271269
bounding_box = {};
272270

273271
// Init all other selected clips (prepare to drag them)
274-
$(".ui-selected").each(function () {
272+
// This creates a bounding box which contains all selected clips
273+
$(".ui-selected, #" + $(this).attr("id")).each(function () {
275274
// Init all clips whether selected or not
276275
start_clips[$(this).attr("id")] = {
277276
"top": $(this).position().top + vert_scroll_offset,

src/timeline/js/directives/playhead.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
*/
2828

2929

30+
/*global setSelections, setBoundingBox, moveBoundingBox, bounding_box */
3031
// Handles the playhead dragging
3132
var playhead_y_max = null;
3233

@@ -37,10 +38,28 @@ App.directive("tlPlayhead", function () {
3738
// get the default top position so we can lock it in place vertically
3839
playhead_y_max = element.position().top;
3940

41+
element.on("mousedown", function (e) {
42+
// Set bounding box for the playhead
43+
setBoundingBox(scope, $(this), "playhead");
44+
});
45+
4046
// Move playhead to new position (if it's not currently being animated)
4147
element.on("mousemove", function (e) {
4248
if (e.which === 1 && !scope.playhead_animating) { // left button
43-
var playhead_seconds = (e.pageX - $("#ruler").offset().left) / scope.pixelsPerSecond;
49+
// Calculate the playhead bounding box movement and apply snapping rules
50+
let cursor_position = e.pageX - $("#ruler").offset().left;
51+
let results = moveBoundingBox(scope, bounding_box.left, bounding_box.top,
52+
cursor_position - bounding_box.left, cursor_position - bounding_box.top,
53+
cursor_position, cursor_position, "playhead");
54+
55+
// Only apply snapping when SHIFT is pressed
56+
let new_position = cursor_position;
57+
if (e.shiftKey) {
58+
new_position = results.position.left;
59+
}
60+
61+
// Move playhead
62+
let playhead_seconds = new_position / scope.pixelsPerSecond;
4463
scope.movePlayhead(playhead_seconds);
4564
scope.previewFrame(playhead_seconds);
4665
}

src/timeline/js/directives/ruler.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
*/
2828

2929

30+
/*global setSelections, setBoundingBox, moveBoundingBox, bounding_box */
3031
// Variables for panning by middle click
3132
var is_scrolling = false;
3233
var starting_scrollbar = {x: 0, y: 0};
@@ -115,7 +116,6 @@ App.directive("tlBody", function () {
115116
}
116117
});
117118

118-
119119
}
120120
};
121121
});
@@ -150,11 +150,29 @@ App.directive("tlRuler", function ($timeout) {
150150

151151
});
152152

153+
element.on("mousedown", function (e) {
154+
// Set bounding box for the playhead position
155+
setBoundingBox(scope, $(this), "playhead");
156+
});
157+
153158
// Move playhead to new position (if it's not currently being animated)
154159
element.on("mousemove", function (e) {
155160
if (e.which === 1 && !scope.playhead_animating) { // left button
156-
var playhead_seconds = (e.pageX - element.offset().left) / scope.pixelsPerSecond;
157-
// Update playhead
161+
// Calculate the playhead bounding box movement
162+
let cursor_position = e.pageX - $("#ruler").offset().left;
163+
let new_position = cursor_position;
164+
if (e.shiftKey) {
165+
// Only apply playhead shapping when SHIFT is pressed
166+
let results = moveBoundingBox(scope, bounding_box.left, bounding_box.top,
167+
cursor_position - bounding_box.left, cursor_position - bounding_box.top,
168+
cursor_position, cursor_position, "playhead");
169+
170+
// Update position to snapping position
171+
new_position = results.position.left;
172+
}
173+
174+
// Move playhead
175+
let playhead_seconds = new_position / scope.pixelsPerSecond;
158176
scope.movePlayhead(playhead_seconds);
159177
scope.previewFrame(playhead_seconds);
160178
}

0 commit comments

Comments
 (0)