Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruler refactor #4171

Merged
merged 28 commits into from
Jul 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6e4f089
Coppied changes from playhead-bug branch
Jun 2, 2021
d7893e3
Cleaned up leftover code for PR
Jun 2, 2021
6b74241
removed a console.log
Jun 2, 2021
d0480bf
Changed 'tick_time' to 'ruler_time'
Jun 2, 2021
2034697
Fixed an error that caused the times to be off by 50px
Jun 4, 2021
590b10a
Formatting
Jun 4, 2021
35d9f8b
fixed webkit compatability
Jun 4, 2021
0674d97
Tried matching shades, and showing preview bar
Jun 7, 2021
b3a5c0c
Coppied changes from playhead-bug branch
Jun 2, 2021
2953274
Cleaned up leftover code for PR
Jun 2, 2021
123c004
removed a console.log
Jun 2, 2021
a15c495
Changed 'tick_time' to 'ruler_time'
Jun 2, 2021
e2a2b9b
Fixed an error that caused the times to be off by 50px
Jun 4, 2021
99df6af
Formatting
Jun 4, 2021
948c44f
fixed webkit compatability
Jun 4, 2021
328714a
Tried matching shades, and showing preview bar
Jun 7, 2021
a0a396f
show frame numbers if scrolled in less than 1
JacksonRG Jul 8, 2021
ec56e40
removed minimum zoom factor
JacksonRG Jul 17, 2021
da36153
first try drawing on frames
JacksonRG Jul 20, 2021
87d71e6
WIP saving
JacksonRG Jul 20, 2021
d606c1c
draw tick marks but overlaps sometimes
JacksonRG Jul 21, 2021
4ef7e52
Well tested prime factoring
JacksonRG Jul 21, 2021
a115675
Save primes as they're found
JacksonRG Jul 22, 2021
e57ac9c
Time marks stay when you scroll
JacksonRG Jul 22, 2021
f86fbff
Merge branch 'develop' into ruler-refactor
jonoomph Jul 22, 2021
8ec52b8
get correct timeline length
JacksonRG Jul 23, 2021
efc8f72
force ruler to draw
JacksonRG Jul 29, 2021
9196fe7
Cleanup
JacksonRG Jul 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/timeline/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,20 @@
<!-- JQuery & Bootstrap StyleSheets -->
<link type="text/css" rel="stylesheet" href="media/css/bootstrap.min.css">
</head>
<body tl-body ng-controller="TimelineCtrl" ng-cloak>
<body tl-body ng-controller="TimelineCtrl" ng-cloak onload="forceDrawRuler()">

<!-- RULER NAME (left of screen) -->
<div tl-rulertime id="ruler_label">
<div id="ruler_time">{{playheadTime.hour}}:{{playheadTime.min}}:{{playheadTime.sec}}:{{playheadTime.frame}}</div>
<div id="ruler_time">{{playheadTime.hour}}:{{playheadTime.min}}:{{playheadTime.sec}},{{playheadTime.frame}}</div>
</div>
<!-- RULER (right of screen) -->
<div id="scrolling_ruler">
<!-- PLAYHEAD TOP -->
<div tl-playhead class="playhead playhead-top" id="playhead" ng-right-click="showPlayheadMenu(project.playhead_position)" style="left:{{project.playhead_position * pixelsPerSecond}}px;">
<div class="playhead-line-small"></div>
</div>
<!-- Ruler extends beyond tracks area at least for a width of vertical scroll bar (or more, like 50px here) -->
<canvas tl-ruler id="ruler" width="{{canvasMaxWidth(getTimelineWidth(0) + 6)}}px" height="39"></canvas>
<!-- Ruler is width of the timeline -->
<div tl-ruler id="ruler" style="width: {{project.duration * pixelsPerSecond}}px;"></div>

<!-- MARKERS -->
<span class="ruler_marker" id="marker_for_{{marker.id}}">
Expand Down
4 changes: 2 additions & 2 deletions src/timeline/js/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ App.controller("TimelineCtrl", function ($scope) {
// Use JQuery to move playhead (for performance reasons) - scope.apply is too expensive here
$(".playhead-top").css("left", ($scope.project.playhead_position * $scope.pixelsPerSecond) + "px");
$(".playhead-line").css("left", ($scope.project.playhead_position * $scope.pixelsPerSecond) + "px");
$("#ruler_time").text($scope.playheadTime.hour + ":" + $scope.playheadTime.min + ":" + $scope.playheadTime.sec + ":" + $scope.playheadTime.frame);
$("#ruler_time").text($scope.playheadTime.hour + ":" + $scope.playheadTime.min + ":" + $scope.playheadTime.sec + "," + $scope.playheadTime.frame);
};

// Move the playhead to a specific frame
Expand Down Expand Up @@ -281,7 +281,7 @@ App.controller("TimelineCtrl", function ($scope) {

// Change the scale and apply to scope
$scope.setScroll = function (normalizedScrollValue) {
var timeline_length = Math.min(32767, $scope.getTimelineWidth(0));
var timeline_length = $scope.getTimelineWidth(0);
var scrolling_tracks = $("#scrolling_tracks");
var horz_scroll_offset = normalizedScrollValue * timeline_length;
scrolling_tracks.scrollLeft(horz_scroll_offset);
Expand Down
115 changes: 59 additions & 56 deletions src/timeline/js/directives/ruler.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ var scroll_left_pixels = 0;
App.directive("tlScrollableTracks", function () {
return {
restrict: "A",

link: function (scope, element, attrs) {

// Sync ruler to track scrolling
Expand All @@ -62,14 +61,18 @@ App.directive("tlScrollableTracks", function () {
// Send scrollbar position to Qt
if (scope.Qt) {
// Calculate scrollbar positions (left and right edge of scrollbar)
var timeline_length = Math.min(32767, scope.getTimelineWidth(0));
var timeline_length = scope.getTimelineWidth(0);
var left_scrollbar_edge = scroll_left_pixels / timeline_length;
var right_scrollbar_edge = (scroll_left_pixels + element.width()) / timeline_length;

// Send normalized scrollbar positions to Qt
timeline.ScrollbarChanged([left_scrollbar_edge, right_scrollbar_edge, timeline_length, element.width()]);
}

scope.$apply( () => {
scope.scrollLeft = element[0].scrollLeft;
})

});

// Initialize panning when middle mouse is clicked
Expand Down Expand Up @@ -100,7 +103,6 @@ App.directive("tlScrollableTracks", function () {
element.removeClass("drag_cursor");
});


}
};
});
Expand Down Expand Up @@ -147,7 +149,6 @@ App.directive("tlRuler", function ($timeout) {
scope.playhead_animating = false;
});
});

});

element.on("mousedown", function (e) {
Expand Down Expand Up @@ -178,60 +179,62 @@ App.directive("tlRuler", function ($timeout) {
}
});

//watch the scale value so it will be able to draw the ruler after changes,
//otherwise the canvas is just reset to blank
scope.$watch("project.scale + markers.length + project.duration", function (val) {
if (val) {

$timeout(function () {
//get all scope variables we need for the ruler
var scale = scope.project.scale;
var tick_pixels = scope.project.tick_pixels;
var each_tick = tick_pixels / 2;
// Don't go over the max supported canvas size
var pixel_length = Math.min(32767,scope.getTimelineWidth(1024));

//draw the ruler
var ctx = element[0].getContext("2d");
//clear the canvas first
ctx.clearRect(0, 0, element.width(), element.height());
//set number of ticks based 2 for each pixel_length
var num_ticks = pixel_length / 50;

ctx.lineWidth = 1;
ctx.strokeStyle = "#c8c8c8";
ctx.lineCap = "round";

//loop em and draw em
for (var x = 0; x < num_ticks + 1; x++) {
ctx.beginPath();

//if it's even, make the line longer
var line_top = 0;
if (x % 2 === 0) {
line_top = 18;
//if it's not the first line, set the time text
if (x !== 0) {
//get time for this tick
var time = (scale * x) / 2;
var time_text = secondsToTime(time, scope.project.fps.num, scope.project.fps.den);

//write time on the canvas, centered above long tick
ctx.fillStyle = "#c8c8c8";
ctx.font = "0.9em";
ctx.fillText(time_text["hour"] + ":" + time_text["min"] + ":" + time_text["sec"], x * each_tick - 22, 11);
}
} else {
//shorter line
line_top = 28;
}

ctx.moveTo(x * each_tick, 39);
ctx.lineTo(x * each_tick, line_top);
ctx.stroke();
/**
* Draw times on the ruler
* Always starts on a second
* Draws to the right edge of the screen
*/
function drawTimes() {
// Delete old tick marks
ruler = $('#ruler');
$("#ruler span").remove();

startPos = scope.scrollLeft;
endPos = scope.scrollLeft + $("body").width();

fps = scope.project.fps.num / scope.project.fps.den;
time = [ startPos / scope.pixelsPerSecond, endPos / scope.pixelsPerSecond];
time[0] -= time[0]%2;
time[1] -= time[1]%1 - 1;

startFrame = time[0] * Math.round(fps);
endFrame = time[1] * Math.round(fps);

fpt = framesPerTick(scope.pixelsPerSecond, scope.project.fps.num ,scope.project.fps.den);
frame = startFrame;
while ( frame <= endFrame){
t = frame / fps;
pos = t * scope.pixelsPerSecond;
tickSpan = $('<span style="left:'+pos+'px;"></span>');
tickSpan.addClass("tick_mark");

if ((frame - startFrame) % (fpt * 2) == 0) {
// Alternating long marks with times marked
timeSpan = $('<span style="left:'+pos+'px;"></span>');
timeSpan.addClass("ruler_time");
timeText = secondsToTime(t, scope.project.fps.num, scope.project.fps.den);
timeSpan[0].innerText = timeText['hour'] + ':' +
timeText['min'] + ':' +
timeText['sec'];
if (fpt < Math.round(fps)) {
timeSpan[0].innerText += ',' + timeText['frame'];
}
}, 0);
tickSpan[0].style['height'] = '20px';
}
ruler.append(timeSpan);
ruler.append(tickSpan);

frame += fpt;
}
return;
};

scope.$watch("project.scale + project.duration + scrollLeft", function (val) {
if (val) {
$timeout(function () {
drawTimes();
return;
} , 0);
}
});

Expand Down
94 changes: 93 additions & 1 deletion src/timeline/js/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ function secondsToTime(secs, fps_num, fps_den) {
var week = Math.floor(day / 7);
day = day % 7;

var frame = Math.round((milli / 1000.0) * (fps_num / fps_den)) + 1;
var frame = Math.floor((milli / 1000.0) * (fps_num / fps_den)) + 1;
return {
"week": padNumber(week, 2),
"day": padNumber(day, 2),
Expand Down Expand Up @@ -356,6 +356,88 @@ function moveBoundingBox(scope, previous_x, previous_y, x_offset, y_offset, left
return {"position": snapping_result, "x_offset": x_offset, "y_offset": y_offset};
}

/**
* Primes are used for factoring.
* Store any that have been found for future use.
*/
global_primes = new Set();

/**
* Creates a list of all primes less than n.
* Stores primes in a set for better performance in the future.
* If some primes have been found, start with that list,
* and check the remaining numbers up to n.
* @param {any number} n
* @returns the list of all primes less than n
*/
function primesUpTo(n) {
n = Math.floor(n);
if (Array.from(global_primes).pop() >= n) { // All primes already found
return Array.from(global_primes).filter( x => { return x < n });
}
start = 2; // 0 and 1 can't be prime
primes = [...Array(n+1).keys()]; // List from 0 to n
if (Array.from(global_primes).length) { // Some primes already found
start = Array.from(global_primes).pop() + 1;
primes = primes.slice(start,primes.length -1);
primes = Array.from(global_primes).concat(primes);
} else {
primes = primes.slice(start,primes.length -1);
}
primes.forEach( p => { // Sieve of Eratosthenes method of prime factoring
primes = primes.filter( test => { return (test % p != 0) || (test == p) } );
global_primes.add(p);
});
return primes;
}

/**
* Every integer is either prime,
* is the product of some list of primes.
* @param {integer to factor} n
* @returns the list of prime factors of n
*/
function primeFactorsOf(n) {
n = Math.floor(n);
factors = [];
primes = primesUpTo(n);
primes.push(n);
while (n != 1) {
if (n % primes[0] == 0) {
n = n/primes[0];
factors.push(primes[0]);
} else {
primes.shift();
}
}
return factors;
}

/**
* From the pixels per second of the project,
* Find a number of frames between each ruler mark,
* such that the tick marks remain at least 50px apart.
*
* Increases the number of frames by factors of FPS.
* This way each tick should land neatly on a second mark
* @param {Pixels per second} pps
* @param fps_num
* @param fps_den
* @returns
*/
function framesPerTick(pps, fps_num, fps_den) {
fps = fps_num / fps_den;
frames = 1;
seconds = () => { return frames / fps };
pixels = () => { return seconds() * pps };
factors = primeFactorsOf(Math.round(fps));
while (pixels() < 40) {
frames *= factors.shift() || 2;
}

return frames;
}

function setSelections(scope, element, id) {
if (!element.hasClass("ui-selected")) {
// Clear previous selections?
Expand Down Expand Up @@ -386,3 +468,13 @@ function setSelections(scope, element, id) {
// Apply scope up to this point
scope.$apply(function () {});
}

/**
* <body> of index.html calls this on load.
* Garauntees that the ruler is drawn when timeline first loads
*/
function forceDrawRuler() {
var scroll = document.querySelector('#scrolling_tracks').scrollLeft;
document.querySelector('#scrolling_tracks').scrollLeft = 10;
document.querySelector('#scrolling_tracks').scrollLeft = scroll;
}
27 changes: 23 additions & 4 deletions src/timeline/media/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,32 @@ img {
}

/* Ruler */
#scrolling_ruler { overflow: hidden; position: relative;line-height: 4px; }
#scrolling_ruler { overflow: hidden; position: relative; line-height: 4px; height:43px;}
#scrolling_tracks { height: 316px; overflow: scroll; position: relative; }
#ruler_label { height: 39px; width: 140px; float: left; margin-bottom: 4px; }
#ruler_ticks { background-color:#000; }
#ruler_time { font-size: 13pt; color: #c8c8c8; padding-top: 14px; padding-left: 17px; }
#progress_container {margin-left:140px; overflow: hidden; height: 13px;}
#ruler_time { font-size: 13pt; color: #999; padding-top: 14px; padding-left: 17px; }
#progress{ position: absolute; bottom: 0;}
.drag_cursor { cursor: move; }
#ruler {
position: relative;
height: 39px;
background-position: -50px;
}
.tick_mark {
position: absolute;
height: 14px;
width : 1px;
bottom: 3px;
background-color: #acacac;
background-position: -50px;
}
.ruler_time {
color: #c8c8c8;
top: 6px;
font-size: 0.8em;
position: absolute;
transform: translate(-50%,0);
}

/* Tracks */
#track_controls { width: 140px; position: relative; float: left; height: 316px; overflow: hidden;}
Expand Down
24 changes: 7 additions & 17 deletions src/windows/views/zoom_slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,21 +399,6 @@ def wheelEvent(self, event):

def setZoomFactor(self, zoom_factor):
"""Set the current zoom factor"""
# Get max width of timeline
project_duration = get_app().project.get("duration")
tick_pixels = 100
min_zoom_factor = 1.0
max_zoom_factor = 64.0
if self.scrollbar_position[3] > 0.0:
# Calculate the new zoom factor, based on pixels per tick
max_zoom_factor = project_duration / (self.scrollbar_position[3] / tick_pixels)

# Constrain zoom factor to min/max limits
if zoom_factor < min_zoom_factor:
zoom_factor = min_zoom_factor
if zoom_factor > max_zoom_factor:
zoom_factor = max_zoom_factor

# Force recalculation of clips
self.zoom_factor = zoom_factor

Expand All @@ -428,8 +413,10 @@ def zoomIn(self):
"""Zoom into timeline"""
if self.zoom_factor >= 10.0:
new_factor = self.zoom_factor - 5.0
else:
elif self.zoom_factor >= 4.0:
new_factor = self.zoom_factor - 2.0
else:
new_factor = self.zoom_factor * 0.8

# Emit zoom signal
self.setZoomFactor(new_factor)
Expand All @@ -438,8 +425,11 @@ def zoomOut(self):
"""Zoom out of timeline"""
if self.zoom_factor >= 10.0:
new_factor = self.zoom_factor + 5.0
else:
elif self.zoom_factor >= 4.0:
new_factor = self.zoom_factor + 2.0
else:
# Ensure zoom is reversable when using only keyboard zoom
new_factor = min(self.zoom_factor * 1.25, 4.0)

# Emit zoom signal
self.setZoomFactor(new_factor)
Expand Down