diff --git a/README.md b/README.md index 42d7abf..0c6ebb8 100644 --- a/README.md +++ b/README.md @@ -1,134 +1,6 @@ -# Timeliner +# Timeliner GUI -Timeliner is a graphical graphical tool to help create and prototype animations quickly. It is useful for adjusting variables and checking out how the effects changes over time with keyframing and easing/tweening functions. It may also have some similarities with the timeline component of adobe flash, after effects, edge animate or other animation software. +GUI-only fork of [Josh Koo's Timeliner](http://www.github.com/zz85/timeliner.git). -It is written in javascript and meant to work with different javascript libraries or webgl frameworks, in 1d, 2d, or 3d. It is built primary for myself, but feel free to send me suggestions or requests. +The class Controller class in `test.html`` defines the skeleton by which to use the GUI with an existing animation system. -Follow [me](https://twitter.com/blurspline) on twitter for updates. - -## Demo - -# [Example](http://zz85.github.io/timeliner/test.html) - -[Video](https://plus.google.com/117614030945250277079/posts/BiWe8Z7nHdk?pid=6086039289973564578&oid=117614030945250277079) - -![screenshot](screenshot.png) - -## Another js timeline library? - -Below are some existing javascript timeline libraries which I think are pretty good. I decided to write mine partly to scratch my itch and partly to challange myself technically. There are challenges in writing one, but its nice to be in control of your own tools. - -1. [Timeline.js](https://github.com/vorg/timeline.js) by Marcin Ignac -2. [Keytime Editor](https://github.com/mattdesl/keytime-editor/) by Matt DesLauriers -3. [Frame.js](https://github.com/mrdoob/frame.js/) by Ricardo Cabello -(Side note: mrdoob's [talk on this](http://2013.jsconf.asia/blog/2013/11/8/jsconfasia-2013-mrdoob-ricardo-cabello-framejs) also showcase interesting editors used by the demoscene) -4. [TweenTime](https://github.com/idflood/TweenTime/) by idflood. - -I think the current version is much a work in progress. However Ben Schwarz says that a cat dies everytime code doesn't get publish during cssconf asia 2014, so I thought it would be a good idea to release this early. - -## Philosophy - -I wrote Timeliner to be as lightweight and embedable as possible. Styles, HTML, icons are all embeded in a single javascript file. This means it could work as an included script, bookmarklet, or part of a bigger project. I intent to have interoperablility with other controls tools like dat.gui or gui.js. - -## Usage - -Include the timeliner.js file. - -```js - -``` - -Load data by code, file upload or loading from saved localStorage. - -```js -// target is a "pojo" which gets updated when values change. -var target = { - name1: 1, - name2: 2, - name3: 3 -}; - -// initialize timeliner -var timeliner = new Timeliner(target); -timeliner.addLayer('name1'); -timeliner.addLayer('name2'); -timeliner.addLayer('name3'); -``` - -### Add a keyframe - -1. double click on the timeline - -or - -1. Select a time on the timeline -2. Click on the keyframe - -### Add a tween -1. Select time between 2 keyframes -2. Select easing type from the dropdown - -## Releases - -1.5.0 -- Fix package.json dependencies -- Easy way to move keyframes (reimplemented block dragging) - -1.4.0 -- Bug fix (insert keyframes should interpolate) -- ghosting / onioning skinning tweened values -- Icon and layout tweaks -- Basic time & vertical scrolling -- Simple Ghosting / Onion Skinning Support [Example](http://zz85.github.io/timeliner/test_ghosts.html) - -1.3.0 -- autosave -- load (localstorage, new, autosave, filesystem) -- save (export, localstorage, download) -- ui tweaks - -1.2.0 -- icons using extracted fontawesome data -- slightly npm-ify -- [window management](http://codepen.io/zz85/pen/gbOoVP) -- basic keyboard shortcuts -- basic hdpi -- basic touch support - -1.1.0 -- undo / redo (basic) - -1.0.0 -- slider time scale (basic) -- fix positioning mouse events -- basic play toggled with pause button -- basic hook playback to target object -- basic playback and pause -- semi-allow layer panel to repaint on data change -- show current easing of layers -- update tween props on insert -- show tween values on cursor movement -- edit tween (basic) -- insert tween (basic) -- drag keyframe -- insert keyframe on value adjust -- adjust value -- remove keyframe -- insert keyframe -- adjust values (basic) - -## TODO -- slider units -- usability improvements -- better marking time when scaling time -- better keyboard shortcuts -- move tween blocks -- custom context / popup menu -- attempt virtual-dom / v.rendering -- consider immutable js or localstorage for undo stack -- curve editor -- graph editor -- support audio -- support guestures -- remote control -- a whole ton more \ No newline at end of file diff --git a/package.json b/package.json index 76c9671..fcd013c 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,35 @@ { - "name": "timeliner", - "version": "1.4.0", - "description": "simple js animation timeline library", + "name": "timeliner_gui", + "version": "0.0.2", + "description": "Timeliner GUI", "main": "timeliner.js", "scripts": { - "build": "browserify src/*.js --full-path=false -o timeliner.js", - "mini": "browserify src/*.js -g uglifyify --full-path=false -o timeliner.min.js", - "watch": "watchify src/*.js -o timeliner.js -v", + "build": "browserify src/*.js --full-path=false -o timeliner_gui.js", + "mini": "browserify src/*.js -g uglifyify --full-path=false -o timeliner_gui.min.js", + "watch": "watchify src/*.js -o timeliner_gui.js -v", "start": "npm run watch", "test": "echo \"Error: no tests :(\" && exit 1" }, "repository": { "type": "git", - "url": "https://github.com/zz85/timeliner.git" + "url": "https://github.com/tschw/timeliner_gui.git" }, "keywords": [ "timeline", "animation", "keyframe", - "tween", - "ease", "controls", "gui" ], - "author": "joshua koo", + "author": "tschw (the fork)", + "contributors": [ + "Joshua 'zz85' Koo (original author)" + ], "license": "MIT", "bugs": { - "url": "https://github.com/zz85/timeliner/issues" + "url": "https://github.com/tschw/timeliner_gui/issues" }, - "homepage": "https://github.com/zz85/timeliner", + "homepage": "https://github.com/tschw/timeliner_gui", "devDependencies": { "do.js": "^1.0.0", "uglifyify": "^2.6.0" diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index 233933d..0000000 Binary files a/screenshot.png and /dev/null differ diff --git a/src/datastore.js b/src/datastore.js deleted file mode 100644 index 4f33acd..0000000 --- a/src/datastore.js +++ /dev/null @@ -1,90 +0,0 @@ -var package_json = require('../package.json'), -Do = require('do.js'); - -// Data Store with a source of truth -function DataStore() { - this.DELIMITER = ':'; - this.blank(); - this.onOpen = new Do(); - this.onSave = new Do(); -} - -DataStore.prototype.blank = function() { - var data = {}; - - data.version = package_json.version; - data.modified = new Date().toString(); - data.title = 'Untitled'; - - data.layers = []; - - this.data = data; -}; - -DataStore.prototype.update = function() { - var data = this.data; - - data.version = package_json.version; - data.modified = new Date().toString(); -}; - -DataStore.prototype.setJSONString = function(data) { - this.data = JSON.parse(data); -}; - -DataStore.prototype.setJSON = function(data) { - this.data = data; -}; - -DataStore.prototype.getJSONString = function(format) { - return JSON.stringify(this.data, null, format); -}; - -DataStore.prototype.getValue = function(paths) { - var descend = paths.split(this.DELIMITER); - var reference = this.data; - for (var i = 0, il = descend.length; i < il; i++) { - var path = descend[i]; - if (reference[path] === undefined) { - console.warn('Cant find ' + paths); - return; - } - reference = reference[path]; - } - return reference; -}; - -DataStore.prototype.setValue = function(paths, value) { - var descend = paths.split(this.DELIMITER); - var reference = this.data; - for (var i = 0, il = descend.length - 1; path = descend[i], i < il ; i++) { - reference = reference[path]; - } - - reference[path] = value; -}; - -DataStore.prototype.get = function(path, suffix) { - if (suffix) path = suffix + this.DELIMITER + path; - return new DataProx(this, path); -}; - -function DataProx(store, path) { - this.path = path; - this.store = store; -} - -DataProx.prototype = { - get value() { - return this.store.getValue(this.path); - }, - set value(val) { - this.store.setValue(this.path, val); - } -}; - -DataProx.prototype.get = function(path) { - return this.store.get(path, this.path); -}; - -module.exports = DataStore; \ No newline at end of file diff --git a/src/handle_drag.js b/src/handle_drag.js deleted file mode 100644 index 65f943d..0000000 --- a/src/handle_drag.js +++ /dev/null @@ -1,105 +0,0 @@ -function handleDrag(element, ondown, onmove, onup, down_criteria) { - var pointer = null; - var bounds = element.getBoundingClientRect(); - - element.addEventListener('mousedown', onMouseDown); - - function onMouseDown(e) { - handleStart(e); - - if (down_criteria && !down_criteria(pointer)) { - pointer = null; - return; - } - - - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - - ondown(pointer); - - e.preventDefault(); - } - - function onMouseMove(e) { - handleMove(e); - pointer.moved = true; - onmove(pointer); - } - - function handleStart(e) { - bounds = element.getBoundingClientRect(); - var currentx = e.clientX, currenty = e.clientY; - pointer = { - startx: currentx, - starty: currenty, - x: currentx, - y: currenty, - dx: 0, - dy: 0, - offsetx: currentx - bounds.left, - offsety: currenty - bounds.top, - moved: false - }; - } - - function handleMove(e) { - bounds = element.getBoundingClientRect(); - var currentx = e.clientX, - currenty = e.clientY, - offsetx = currentx - bounds.left, - offsety = currenty - bounds.top; - pointer.x = currentx; - pointer.y = currenty; - pointer.dx = e.clientX - pointer.startx; - pointer.dy = e.clientY - pointer.starty; - pointer.offsetx = offsetx; - pointer.offsety = offsety; - } - - function onMouseUp(e) { - handleMove(e); - onup(pointer); - pointer = null; - - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - } - - element.addEventListener('touchstart', onTouchStart); - - function onTouchStart(te) { - - if (te.touches.length == 1) { - - var e = te.touches[0]; - if (down_criteria && !down_criteria(e)) return; - te.preventDefault(); - handleStart(e); - ondown(pointer); - } - - element.addEventListener('touchmove', onTouchMove); - element.addEventListener('touchend', onTouchEnd); - } - - function onTouchMove(te) { - var e = te.touches[0]; - onMouseMove(e); - } - - function onTouchEnd(e) { - // var e = e.touches[0]; - onMouseUp(e); - element.removeEventListener('touchmove', onTouchMove); - element.removeEventListener('touchend', onTouchEnd); - } - - - this.release = function() { - element.removeEventListener('mousedown', onMouseDown); - element.removeEventListener('touchstart', onTouchStart); - }; -} - -module.exports = handleDrag; \ No newline at end of file diff --git a/src/layer_cabinet.js b/src/layer_cabinet.js index c25944a..32befe1 100644 --- a/src/layer_cabinet.js +++ b/src/layer_cabinet.js @@ -1,26 +1,25 @@ -var Settings = require('./settings'), - LayerUI = require('./ui/layer_view'), - IconButton = require('./icon_button'), +var LayoutConstants = require('./layout_constants'), + LayerUI = require('./layer_view'), + IconButton = require('./widget/icon_button'), style = require('./utils').style, Theme = require('./theme'), STORAGE_PREFIX = require('./utils').STORAGE_PREFIX, - NumberUI = require('./ui/number') + NumberUI = require('./widget/number') ; -function LayerCabinet(data, dispatcher) { - var layer_store = data.get('layers'); - +function LayerCabinet(context) { + var div = document.createElement('div'); var top = document.createElement('div'); - top.style.cssText = 'margin: 0px; top: 0; left: 0; height: ' + Settings.MARKER_TRACK_HEIGHT + 'px'; + top.style.cssText = 'margin: 0px; top: 0; left: 0; height: ' + LayoutConstants.MARKER_TRACK_HEIGHT + 'px'; // top.style.textAlign = 'right'; var layer_scroll = document.createElement('div'); style(layer_scroll, { position: 'absolute', - top: Settings.MARKER_TRACK_HEIGHT + 'px', - // height: (Settings.height - Settings.MARKER_TRACK_HEIGHT) + 'px' + top: LayoutConstants.MARKER_TRACK_HEIGHT + 'px', + // height: (height - LayoutConstants.MARKER_TRACK_HEIGHT) + 'px' left: 0, right: 0, bottom: 0, @@ -31,39 +30,61 @@ function LayerCabinet(data, dispatcher) { var playing = false; - var play_button = new IconButton(16, 'play', 'play', dispatcher); + + var button_styles = { + width: '22px', + height: '22px', + padding: '2px' + }; + + var op_button_styles = { + width: '32px', + padding: '3px 4px 3px 4px' + }; + + + var dispatcher = context.dispatcher, + controller = context.controller; + + var play_button = new IconButton(16, 'play', "Play", dispatcher); + style(play_button.dom, button_styles, { marginTop: '2px' } ); play_button.onClick(function(e) { e.preventDefault(); dispatcher.fire('controls.toggle_play'); }); - var stop_button = new IconButton(16, 'stop', 'stop', dispatcher); + var stop_button = new IconButton(16, 'stop', "Stop", dispatcher); + style(stop_button.dom, button_styles, { marginTop: '2px' } ); stop_button.onClick(function(e) { dispatcher.fire('controls.stop'); }); +/* var undo_button = new IconButton(16, 'undo', 'undo', dispatcher); + style(undo_button.dom, op_button_styles); undo_button.onClick(function() { dispatcher.fire('controls.undo'); }); var redo_button = new IconButton(16, 'repeat', 'redo', dispatcher); + style(redo_button.dom, op_button_styles); redo_button.onClick(function() { dispatcher.fire('controls.redo'); }); - +*/ var range = document.createElement('input'); range.type = "range"; - range.min = 0; - range.max = 100; + range.value = 0; + range.min = -1; + range.max = +1; + range.step = 0.125; - range.value = convertTimeToPercent(Settings.time_scale); - - range.step = 0.01; style(range, { - width: '70px' + width: '80px', + margin: '0px', + marginLeft: '2px', + marginRight: '2px' }); - var draggingRange = 0; @@ -85,32 +106,19 @@ function LayerCabinet(data, dispatcher) { var time_options = { min: 0, - step: 0.01 + step: 0.125 }; var currentTime = new NumberUI(time_options); var totalTime = new NumberUI(time_options); - var currentTimeStore = data.get('ui:currentTime'); - var totalTimeStore = data.get('ui:totalTime'); - - // UI2StoreBind(view, datastore) { - // view.onChange.do(function(v) { - // datastore.value = view; - // }) - - // datastore.onChange.do(function(v) { - // view.setValue = v; - // }) - // } - currentTime.onChange.do(function(value, done) { dispatcher.fire('time.update', value); - // repaint(); + currentTime.paint(); }); totalTime.onChange.do(function(value, done) { - totalTimeStore.value = value; - // repaint(); + dispatcher.fire('totalTime.update', value); + totalTime.paint(); }); // Play Controls @@ -120,7 +128,7 @@ function LayerCabinet(data, dispatcher) { top.appendChild(play_button.dom); top.appendChild(stop_button.dom); top.appendChild(range); - + var operations_div = document.createElement('div'); style(operations_div, { @@ -130,8 +138,10 @@ function LayerCabinet(data, dispatcher) { top.appendChild(operations_div); // top.appendChild(document.createElement('br')); +/* // open _alt var file_open = new IconButton(16, 'folder_open_alt', 'Open', dispatcher); + style(file_open.dom, op_button_styles); operations_div.appendChild(file_open.dom); function populateOpen() { @@ -188,7 +198,7 @@ function LayerCabinet(data, dispatcher) { dispatcher.on('save:done', populateOpen); var dropdown = document.createElement('select'); - + style(dropdown, { position: 'absolute', // right: 0, @@ -232,34 +242,38 @@ function LayerCabinet(data, dispatcher) { // // new // var file_alt = new IconButton(16, 'file_alt', 'New', dispatcher); // operations_div.appendChild(file_alt.dom); - // save + var save = new IconButton(16, 'save', 'Save', dispatcher); + style(save.dom, op_button_styles); operations_div.appendChild(save.dom); save.onClick(function() { dispatcher.fire('save'); }); - // save as + // save as var save_as = new IconButton(16, 'paste', 'Save as', dispatcher); + style(save_as.dom, op_button_styles); operations_div.appendChild(save_as.dom); save_as.onClick(function() { dispatcher.fire('save_as'); }); - +*/ // download json (export) - var download_alt = new IconButton(16, 'download_alt', 'Download / Export JSON to file', dispatcher); + var download_alt = new IconButton(16, 'download_alt', 'Download animation', dispatcher); + style(download_alt.dom, op_button_styles); operations_div.appendChild(download_alt.dom); download_alt.onClick(function() { dispatcher.fire('export'); }); - var upload_alt = new IconButton(16, 'upload_alt', 'Load from file', dispatcher); + var upload_alt = new IconButton(16, 'upload_alt', 'Upload animation', dispatcher); + style(upload_alt.dom, op_button_styles); operations_div.appendChild(upload_alt.dom); upload_alt.onClick(function() { dispatcher.fire('openfile'); }); - +/* var span = document.createElement('span'); span.style.width = '20px'; span.style.display = 'inline-block'; @@ -268,9 +282,9 @@ function LayerCabinet(data, dispatcher) { operations_div.appendChild(undo_button.dom); operations_div.appendChild(redo_button.dom); operations_div.appendChild(document.createElement('br')); - +*/ // Cloud Download / Upload edit pencil - + /* // // show layer // var eye_open = new IconButton(16, 'eye_open', 'eye_open', dispatcher); @@ -314,18 +328,12 @@ function LayerCabinet(data, dispatcher) { } function changeRange() { - var t = range.value / range.max; - // 800px - 10 minutes - 100% - // 50% - 5 minutes - // 10* - // 100% - 60s / 1 second - - // TODO: scale time correctly + //console.log("range.value", range.value); - dispatcher.fire('update.scale', convertPercentToTime(t)); - } + dispatcher.fire('update.scale', 6 * Math.pow(100, range.value)); + } - var layer_uis = [], visible_layers = 0; + var layer_uis = []; var unused_layers = []; this.layers = layer_uis; @@ -334,20 +342,19 @@ function LayerCabinet(data, dispatcher) { playing = v; if (playing) { play_button.setIcon('pause'); - play_button.setTip('pause'); + play_button.setTip('Pause'); } else { play_button.setIcon('play'); - play_button.setTip('play'); + play_button.setTip('Play'); } }; - this.setState = function(state) { + this.updateState = function() { + + var layers = context.controller.getChannelNames(); - layer_store = state; - layers = layer_store.value; - // layers = state; - console.log(layer_uis.length, layers); + //console.log(layer_uis.length, layers); var i, layer; for (i = 0; i < layers.length; i++) { layer = layers[i]; @@ -359,51 +366,48 @@ function LayerCabinet(data, dispatcher) { layer_ui.dom.style.display = 'block'; } else { // new - layer_ui = new LayerUI(layer, dispatcher); + layer_ui = new LayerUI(context, layer); layer_scroll.appendChild(layer_ui.dom); } layer_uis.push(layer_ui); } - // layer_uis[i].setState(layer); + layer_uis[i].setState(layer); } - console.log('Total layers (view, hidden, total)', layer_uis.length, unused_layers.length, - layer_uis.length + unused_layers.length); + //console.log('Total layers (view, hidden, total)', layer_uis.length, unused_layers.length, + // layer_uis.length + unused_layers.length); }; - function repaint(s) { + function repaint() { - s = currentTimeStore.value; - currentTime.setValue(s); - totalTime.setValue(totalTimeStore.value); + var layers = context.controller.getChannelNames(); + var time = context.currentTime; + currentTime.setValue(time); + totalTime.setValue(context.totalTime); currentTime.paint(); totalTime.paint(); - var i; - - s = s || 0; - for (i = layer_uis.length; i-- > 0;) { + // TODO needed? + for (var i = layer_uis.length; i-- > 0;) { // quick hack if (i >= layers.length) { layer_uis[i].dom.style.display = 'none'; unused_layers.push(layer_uis.pop()); continue; } - + // console.log('yoz', states.get(i).value); - layer_uis[i].setState(layers[i], layer_store.get(i)); + layer_uis[i].setState(layers[i]); // layer_uis[i].setState('layers'+':'+i); - layer_uis[i].repaint(s); + layer_uis[i].repaint(time); } - visible_layers = layer_uis.length; - } this.repaint = repaint; - this.setState(layer_store); + this.updateState(); this.scrollTo = function(x) { layer_scroll.scrollTop = x * (layer_scroll.scrollHeight - layer_scroll.clientHeight); @@ -414,4 +418,4 @@ function LayerCabinet(data, dispatcher) { repaint(); } -module.exports = LayerCabinet; \ No newline at end of file +module.exports = LayerCabinet; diff --git a/src/layer_view.js b/src/layer_view.js new file mode 100644 index 0000000..a582090 --- /dev/null +++ b/src/layer_view.js @@ -0,0 +1,70 @@ +var Theme = require('./theme'), + LayoutConstants = require('./layout_constants'), + utils = require('./utils'); + +function LayerView(context, channelName) { + + var dom = document.createElement('div'); + var label = document.createElement('span'); + + label.style.cssText = 'font-size: 12px; padding: 4px;'; + + var height = (LayoutConstants.LINE_HEIGHT - 1); + + var keyframe_button = document.createElement('button'); + keyframe_button.innerHTML = '◈'; // '♦' ◇ 9679 9670 9672 + keyframe_button.style.cssText = 'background: none; font-size: 12px; padding: 0px; font-family: monospace; float: right; width: 20px; height: ' + height + 'px; border-style:none; outline: none;'; // border-style:inset; + + keyframe_button.addEventListener('click', function(e) { + context.dispatcher.fire('keyframe', channelName); + }); + + /* + // Prev Keyframe + var button = document.createElement('button'); + button.textContent = '<'; + button.style.cssText = 'font-size: 12px; padding: 1px; '; + dom.appendChild(button); + + // Next Keyframe + button = document.createElement('button'); + button.textContent = '>'; + button.style.cssText = 'font-size: 12px; padding: 1px; '; + dom.appendChild(button); + */ + + dom.appendChild(label); + dom.appendChild(keyframe_button); + + dom.style.cssText = 'margin: 0px; border-bottom:1px solid ' + Theme.b + '; top: 0; left: 0; height: ' + height + 'px; line-height: ' + height + 'px; color: ' + Theme.c; + this.dom = dom; + + + var repaint = function repaint(time) { + + keyframe_button.style.color = Theme.b; + + if (time == null || context.draggingKeyframe || channelName == null ) return; + + var keyTimes = context.controller.getChannelKeyTimes(channelName); + + if ( utils.binarySearch( keyTimes, time ) >= 0 ) { + + keyframe_button.style.color = Theme.c; + } + + }; + + this.repaint = repaint; + + this.setState = function(name) { + + channelName = name; + label.textContent = name; + + repaint(); + }; + +} + +module.exports = LayerView; diff --git a/src/layout_constants.js b/src/layout_constants.js new file mode 100644 index 0000000..ba4fc6c --- /dev/null +++ b/src/layout_constants.js @@ -0,0 +1,11 @@ + +// Dimensions +module.exports = { + LINE_HEIGHT: 26, + DIAMOND_SIZE: 10, + MARKER_TRACK_HEIGHT: 60, + WIDTH: 600, + HEIGHT: 200, + LEFT_PANE_WIDTH: 250, + TIME_SCALE: 60 // number of pixels to 1 secon, +}; diff --git a/src/save_format.js b/src/save_format.js deleted file mode 100644 index d299808..0000000 --- a/src/save_format.js +++ /dev/null @@ -1,55 +0,0 @@ -/* Layer Schema */ -/* -var layer_1 = [ - { - name: 'abc', - props: { - min: - max: - step: - real_step: - }, - values: [ - [t, v, ''], - {time: t, value: v, tween: bla, _color: 'red'}, - {time: t, value: v, tween: bla}, - {time: t, value: v, tween: bla}, - {time: t, value: v, tween: bla}, - {time: t, value: v}, - ], - ui: { - mute: true, // mute - solo: true, - - }, - tmp: { - value: value, - _color: - } - } - ,... -] currently_playing, scale. -*/ - -/* Timeline Data Schema */ - -var sample = { - version: '1.2.0', - modified: new Date, - - name: 'sample', - title: 'Sample Title', - - ui: { - current_time: 1, - duration: 100, - - position: '0:0:0', - bounds: '10 10 100 100', - snap: 'full | left-half | top-half | right-half | bottom-half' - }, - - layers: [{ - - }] -}; \ No newline at end of file diff --git a/src/settings.js b/src/settings.js deleted file mode 100644 index ac3d3b0..0000000 --- a/src/settings.js +++ /dev/null @@ -1,14 +0,0 @@ - -var DEFAULT_TIME_SCALE = 60; - -// Dimensions -module.exports = { - LINE_HEIGHT: 26, - DIAMOND_SIZE: 12, - MARKER_TRACK_HEIGHT: 60, - width: 600, - height: 200, - TIMELINE_SCROLL_HEIGHT: 0, - LEFT_PANE_WIDTH: 250, - time_scale: DEFAULT_TIME_SCALE // number of pixels to 1 secon, -}; \ No newline at end of file diff --git a/src/timeline_panel.js b/src/timeline_panel.js index c18c02e..ad5a306 100644 --- a/src/timeline_panel.js +++ b/src/timeline_panel.js @@ -1,48 +1,33 @@ -var - Settings = require('./settings'), +var LayoutConstants = require('./layout_constants'), Theme = require('./theme'), utils = require('./utils'), - proxy_ctx = utils.proxy_ctx, - Tweens = require('./tween'), - handleDrag = require('./handle_drag'); + proxy_ctx = utils.proxy_ctx; - var - LINE_HEIGHT = Settings.LINE_HEIGHT, - DIAMOND_SIZE = Settings.DIAMOND_SIZE, - MARKER_TRACK_HEIGHT = Settings.MARKER_TRACK_HEIGHT, - - LEFT_PANE_WIDTH = Settings.LEFT_PANE_WIDTH, - time_scale = Settings.time_scale; +var LINE_HEIGHT = LayoutConstants.LINE_HEIGHT, + DIAMOND_SIZE = LayoutConstants.DIAMOND_SIZE, + MARKER_TRACK_HEIGHT = LayoutConstants.MARKER_TRACK_HEIGHT, + LEFT_PANE_WIDTH = LayoutConstants.LEFT_PANE_WIDTH, + time_scale = LayoutConstants.TIME_SCALE; - var frame_start = 0; // this is the current scroll position. + +var frame_start = 0; // this is the current scroll position. // TODO // dirty rendering // drag block // drag current time // pointer on timescale -var subds, subd_type, subd1, subd2, subd3; +var tickMark1, tickMark2, tickMark3; function time_scaled() { - /* - * Subdivison LOD - * time_scale refers to number of pixels per unit - * Eg. 1 inch - 60s, 1 inch - 60fps, 1 inch - 6 mins - */ - - var a = time_scale / 60; // bigger wider, smaller narrower (40 - 80) - var b = time_scale / 20; // (1x or 2x a) - var c = time_scale / 5; // (4x or 5x a) - - subds = [a, b, c, time_scale > 100 ? 'frames' : 'seconds']; - - // console.log(subds, subds[0] / time_scale, subds[1] / time_scale); - - subd1 = subds[0]; // big ticks / labels - subd2 = subds[1]; // medium ticks - subd3 = subds[2]; // small ticks - subd_type = subds[3]; + + var div = 60; + + tickMark1 = time_scale / div; + tickMark2 = 2 * tickMark1; + tickMark3 = 8 * tickMark1; + } time_scaled(); @@ -52,26 +37,36 @@ time_scaled(); // Timeline Panel /**************************/ -function TimelinePanel(data, dispatcher) { +function TimelinePanel(context) { + + var dispatcher = context.dispatcher; + + var scrollTop = 0, scrollLeft = 0; var dpr = window.devicePixelRatio; var canvas = document.createElement('canvas'); - - var scrollTop = 0, scrollLeft = 0, SCROLL_HEIGHT; - var layers = data.get('layers').value; - this.scrollTo = function(s, y) { - scrollTop = s * Math.max(layers.length * LINE_HEIGHT - SCROLL_HEIGHT, 0); + var layers; + + this.updateState = function() { + layers = context.controller.getChannelNames(); + repaint(); + }; + + this.updateState(); + + this.scrollTo = function(s) { + scrollTop = s * Math.max(layers.length * LINE_HEIGHT - context.scrollHeight, 0); repaint(); }; this.resize = function() { dpr = window.devicePixelRatio; - canvas.width = Settings.width * dpr; - canvas.height = Settings.height * dpr; - canvas.style.width = Settings.width + 'px'; - canvas.style.height = Settings.height + 'px'; - SCROLL_HEIGHT = Settings.height - MARKER_TRACK_HEIGHT; + canvas.width = context.width * dpr; + canvas.height = context.height * dpr; + canvas.style.width = context.width + 'px'; + canvas.style.height = context.height + 'px'; + context.scrollHeight = context.height - MARKER_TRACK_HEIGHT; }; this.dom = canvas; @@ -83,67 +78,31 @@ function TimelinePanel(data, dispatcher) { var current_frame; // currently in seconds // var currentTime = 0; // in frames? could have it in string format (0:00:00:1-60) - + var LEFT_GUTTER = 20; var i, x, y, il, j; var needsRepaint = false; var renderItems = []; - function EasingRect(x1, y1, x2, y2, frame, frame2, values, layer, j) { - var self = this; - - this.path = function() { - ctx_wrap.beginPath() - .rect(x1, y1, x2-x1, y2-y1) - .closePath(); - }; - - this.paint = function() { - this.path(); - ctx.fillStyle = frame._color; - ctx.fill(); - }; - - this.mouseover = function() { - canvas.style.cursor = 'pointer'; // pointer move ew-resize - }; + var timeDrag = 0; + var channelDrag; - this.mouseout = function() { - canvas.style.cursor = 'default'; - }; - - this.mousedrag = function(e) { - var t1 = x_to_time(x1 + e.dx); - t1 = Math.max(0, t1); - // TODO limit moving to neighbours - frame.time = t1; - - var t2 = x_to_time(x2 + e.dx); - t2 = Math.max(0, t2); - frame2.time = t2; - - dispatcher.fire('time.update', t1); - }; - } - - function Diamond(frame, y) { - var x, y2; - - x = time_to_x(frame.time); - y2 = y + LINE_HEIGHT * 0.5 - DIAMOND_SIZE / 2; + function Diamond(t, x, y) { var self = this; var isOver = false; + this.time = t; + this.path = function(ctx_wrap) { ctx_wrap .beginPath() - .moveTo(x, y2) - .lineTo(x + DIAMOND_SIZE / 2, y2 + DIAMOND_SIZE / 2) - .lineTo(x, y2 + DIAMOND_SIZE) - .lineTo(x - DIAMOND_SIZE / 2, y2 + DIAMOND_SIZE / 2) + .moveTo(x, y) + .lineTo(x + DIAMOND_SIZE / 2, y + DIAMOND_SIZE / 2) + .lineTo(x, y + DIAMOND_SIZE) + .lineTo(x - DIAMOND_SIZE / 2, y + DIAMOND_SIZE / 2) .closePath(); }; @@ -170,14 +129,27 @@ function TimelinePanel(data, dispatcher) { self.paint(ctx_wrap); }; - this.mousedrag = function(e) { - var t = x_to_time(x + e.dx); - t = Math.max(0, t); - // TODO limit moving to neighbours - frame.time = t; - dispatcher.fire('time.update', t); - // console.log('frame', frame); - // console.log(s, format_friendly_seconds(s), this); + this.mousedrag = function(e, domEvent) { + + if ( channelDrag !== undefined ) { + + var t = x_to_time(e.offsetx), + delta = Math.max(t - timeDrag, - timeDrag), + shift = domEvent.shiftKey; + + if ( delta ) { + + context.draggingKeyframe = true; + + context.controller.moveKeyframe( channelDrag, timeDrag, delta, shift ); + + timeDrag += delta; + repaint(); + + } + + } + }; } @@ -189,7 +161,9 @@ function TimelinePanel(data, dispatcher) { function drawLayerContents() { - renderItems = []; + + renderItems.length = 0; + // horizontal Layer lines for (i = 0, il = layers.length; i <= il; i++) { ctx.strokeStyle = Theme.b; @@ -202,63 +176,30 @@ function TimelinePanel(data, dispatcher) { .lineTo(width, y) .stroke(); } - - var frame, frame2, j; - - // Draw Easing Rects + // Draw Diamonds for (i = 0; i < il; i++) { // check for keyframes var layer = layers[i]; - var values = layer.values; + var times = context.controller.getChannelKeyTimes( layer ); y = i * LINE_HEIGHT; - for (j = 0; j < values.length - 1; j++) { - frame = values[j]; - frame2 = values[j + 1]; - - // Draw Tween Rect - x = time_to_x(frame.time); - x2 = time_to_x(frame2.time); - - if (!frame.tween || frame.tween == 'none') continue; - - var y1 = y + 2; - var y2 = y + LINE_HEIGHT - 2; - - renderItems.push(new EasingRect(x, y1, x2, y2, frame, frame2)); - - // // draw easing graph - // var color = parseInt(frame._color.substring(1,7), 16); - // color = 0xffffff ^ color; - // color = color.toString(16); // convert to hex - // color = '#' + ('000000' + color).slice(-6); - - // ctx.strokeStyle = color; - // var x3; - // ctx.beginPath(); - // ctx.moveTo(x, y2); - // var dy = y1 - y2; - // var dx = x2 - x; - - // for (x3=x; x3 < x2; x3++) { - // ctx.lineTo(x3, y2 + Tweens[frame.tween]((x3 - x)/dx) * dy); - // } - // ctx.stroke(); - } + // TODO use upper and lower bound here + + for (var j = 0; j < times.length; j++) { + + var time = times[ j ]; - for (j = 0; j < values.length; j++) { - // Dimonds - frame = values[j]; - renderItems.push(new Diamond(frame, y)); + renderItems.push(new Diamond( + time, time_to_x( time ), + y + LINE_HEIGHT * 0.5 - DIAMOND_SIZE / 2)); } } - // render items - var item; + // render for (i = 0, il = renderItems.length; i < il; i++) { - item = renderItems[i]; + var item = renderItems[i]; item.paint(ctx_wrap); } } @@ -274,10 +215,8 @@ function TimelinePanel(data, dispatcher) { function drawScroller() { var w = width; - var totalTime = data.get('ui:totalTime').value; - var pixels_per_second = data.get('ui:timeScale').value; - - var viewTime = w / pixels_per_second; + var totalTime = context.totalTime; + var viewTime = w / time_scale; var k = w / totalTime; // pixels per seconds @@ -291,7 +230,7 @@ function TimelinePanel(data, dispatcher) { scroller.grip_length = viewTime * k; var h = TOP_SCROLL_TRACK; - scroller.left = data.get('ui:scrollTime').value * k; + scroller.left = context.scrollTime * k; scroller.left = Math.min(Math.max(0, scroller.left), w - scroller.grip_length); ctx.beginPath(); @@ -305,7 +244,7 @@ function TimelinePanel(data, dispatcher) { ctx.rect(scroller.left, 5, scroller.grip_length, h); ctx.fill(); - var r = current_frame * k; + var r = current_frame * k; // ctx.fillStyle = Theme.a; // 'yellow'; // ctx.fillRect(0, 5, w, 2); @@ -323,14 +262,16 @@ function TimelinePanel(data, dispatcher) { } - function setTimeScale() { - var v = data.get('ui:timeScale').value; + function setTimeScale(v) { + if (time_scale !== v) { time_scale = v; time_scaled(); } } + this.setTimeScale = setTimeScale; + var over = null; var mousedownItem = null; @@ -365,12 +306,11 @@ function TimelinePanel(data, dispatcher) { } } - - // console.log(pointer) } function pointerEvents() { + if (!pointer) return; ctx_wrap @@ -378,7 +318,7 @@ function TimelinePanel(data, dispatcher) { .scale(dpr, dpr) .translate(0, MARKER_TRACK_HEIGHT) .beginPath() - .rect(0, 0, Settings.width, SCROLL_HEIGHT) + .rect(0, 0, context.width, context.scrollHeight) .translate(-scrollLeft, -scrollTop) .clip() .run(check) @@ -386,15 +326,16 @@ function TimelinePanel(data, dispatcher) { } function _paint() { - if (!needsRepaint) { + + if (! needsRepaint) { pointerEvents(); return; } - setTimeScale(); + setTimeScale(context.timeScale); - current_frame = data.get('ui:currentTime').value; - frame_start = data.get('ui:scrollTime').value; + current_frame = context.currentTime; + frame_start = context.scrollTime; /**************************/ // background @@ -404,24 +345,24 @@ function TimelinePanel(data, dispatcher) { ctx.save(); ctx.scale(dpr, dpr); - // + // ctx.lineWidth = 1; // .5, 1, 2 - width = Settings.width, - height = Settings.height; + width = context.width, + height = context.height; - var units = time_scale / subd1; + var units = time_scale / tickMark1; var offsetUnits = (frame_start * time_scale) % units; var count = (width - LEFT_GUTTER + offsetUnits) / units; - // console.log('time_scale', time_scale, 'subd1', subd1, 'units', units, 'offsetUnits', offsetUnits, frame_start); - + // console.log('time_scale', time_scale, 'tickMark1', tickMark1, 'units', units, 'offsetUnits', offsetUnits, frame_start); + // time_scale = pixels to 1 second (40) - // subd1 = marks per second (marks / s) + // tickMark1 = marks per second (marks / s) // units = pixels to every mark (40) - + // labels only for (i = 0; i < count; i++) { x = i * units + LEFT_GUTTER - offsetUnits; @@ -437,11 +378,11 @@ function TimelinePanel(data, dispatcher) { ctx.textAlign = 'center'; var t = (i * units - offsetUnits) / time_scale + frame_start; - t = utils.format_friendly_seconds(t, subd_type); + t = utils.format_friendly_seconds(t); ctx.fillText(t, x, 38); } - units = time_scale / subd2; + units = time_scale / tickMark2; count = (width - LEFT_GUTTER + offsetUnits) / units; // marker lines - main @@ -454,10 +395,10 @@ function TimelinePanel(data, dispatcher) { ctx.stroke(); } - var mul = subd3 / subd2; - units = time_scale / subd3; + var mul = tickMark3 / tickMark2; + units = time_scale / tickMark3; count = (width - LEFT_GUTTER + offsetUnits) / units; - + // small ticks for (i = 0; i < count; i++) { if (i % mul === 0) continue; @@ -468,13 +409,13 @@ function TimelinePanel(data, dispatcher) { ctx.lineTo(x, MARKER_TRACK_HEIGHT - 10); ctx.stroke(); } - + // Encapsulate a scroll rect for the layers ctx_wrap .save() .translate(0, MARKER_TRACK_HEIGHT) .beginPath() - .rect(0, 0, Settings.width, SCROLL_HEIGHT) + .rect(0, 0, context.width, context.scrollHeight) .translate(-scrollLeft, -scrollTop) .clip() .run(drawLayerContents) @@ -495,7 +436,7 @@ function TimelinePanel(data, dispatcher) { ctx.moveTo(x, base_line); ctx.lineTo(x, height); ctx.stroke(); - + ctx.fillStyle = 'red'; // black ctx.textAlign = 'center'; ctx.beginPath(); @@ -526,11 +467,11 @@ function TimelinePanel(data, dispatcher) { function x_to_time(x) { - var units = time_scale / subd3; + var units = time_scale / tickMark3; // return frame_start + (x - LEFT_GUTTER) / time_scale; - return frame_start + ((x - LEFT_GUTTER) / units | 0) / subd3; + return frame_start + ((x - LEFT_GUTTER) / units | 0) / tickMark3; } function time_to_x(s) { @@ -564,7 +505,7 @@ function TimelinePanel(data, dispatcher) { dispatcher.fire('keyframe', layers[track], current_frame); - + }); function onMouseMove(e) { @@ -587,21 +528,26 @@ function TimelinePanel(data, dispatcher) { }); var mousedown2 = false, mouseDownThenMove = false; - handleDrag(canvas, function down(e) { + utils.handleDrag(canvas, function down(e) { mousedown2 = true; pointer = { x: e.offsetx, y: e.offsety }; pointerEvents(); + if (mousedownItem instanceof Diamond) { + timeDrag = mousedownItem.time; + channelDrag = layers[ y_to_track(e.offsety) ]; + if (!channelDrag) mousedownItem = null; + } dispatcher.fire('time.update', x_to_time(e.offsetx)); // Hit criteria - }, function move(e) { + }, function move(e, domEvent) { mousedown2 = false; if (mousedownItem) { mouseDownThenMove = true; if (mousedownItem.mousedrag) { - mousedownItem.mousedrag(e); + mousedownItem.mousedrag(e,domEvent); } } else { dispatcher.fire('time.update', x_to_time(e.offsetx)); @@ -613,22 +559,19 @@ function TimelinePanel(data, dispatcher) { mousedown2 = false; mousedownItem = null; mouseDownThenMove = false; + context.draggingKeyframe = false; + repaint(); } ); - this.setState = function(state) { - layers = state.value; - repaint(); - }; - /** Handles dragging for scroll bar **/ var draggingx; - handleDrag(canvas, function down(e) { + utils.handleDrag(canvas, function down(e) { draggingx = scroller.left; }, function move(e) { - data.get('ui:scrollTime').value = Math.max(0, (draggingx + e.dx) / scroller.k); + context.scrollTime = Math.max(0, (draggingx + e.dx) / scroller.k); repaint(); }, function up() { }, function(e) { @@ -641,4 +584,4 @@ function TimelinePanel(data, dispatcher) { } -module.exports = TimelinePanel; \ No newline at end of file +module.exports = TimelinePanel; diff --git a/src/timeliner.js b/src/timeliner.js index 08da18e..338aa84 100644 --- a/src/timeliner.js +++ b/src/timeliner.js @@ -7,18 +7,17 @@ var undo = require('./undo'), Theme = require('./theme'), UndoManager = undo.UndoManager, UndoState = undo.UndoState, - Settings = require('./settings'), + LayoutConstants = require('./layout_constants'), utils = require('./utils'), LayerCabinet = require('./layer_cabinet'), TimelinePanel = require('./timeline_panel'), package_json = require('../package.json'), - IconButton = require('./icon_button'), + IconButton = require('./widget/icon_button'), style = utils.style, saveToFile = utils.saveToFile, openAs = utils.openAs, STORAGE_PREFIX = utils.STORAGE_PREFIX, - ScrollBar = require('./ui/scrollbar'), - DataStore = require('./datastore') + ScrollBar = require('./widget/scrollbar') ; var Z_INDEX = 999; @@ -27,8 +26,6 @@ function LayerProp(name) { this.name = name; this.values = []; - this._value = 0; - this._color = '#' + (Math.random() * 0xffffff | 0).toString(16); /* this.max @@ -37,98 +34,89 @@ function LayerProp(name) { */ } -function Timeliner(target) { - // Aka Layer Manager / Controller +function Timeliner( controller ) { - // Should persist current time too. - var data = new DataStore(); - var layer_store = data.get('layers'); - var layers = layer_store.value; + var dispatcher = new Dispatcher(); - window._data = data; + controller.timeliner = this; + controller.init( this ); - var dispatcher = new Dispatcher(); + var context = { + + width: LayoutConstants.WIDTH, + height: LayoutConstants.HEIGHT, + scrollHeight: 0, + + totalTime: 20.0, + timeScale: 6, + + currentTime: 0.0, + scrollTime: 0.0, + + dispatcher: dispatcher, + + controller: controller + + }; - var timeline = new TimelinePanel(data, dispatcher); - var layer_panel = new LayerCabinet(data, dispatcher); + var timeline = new TimelinePanel(context); + var layer_panel = new LayerCabinet(context); var undo_manager = new UndoManager(dispatcher); + var scrollbar = new ScrollBar(0, 10); + + var div = document.createElement('div'); + + controller.setDuration(context.totalTime); + +/* setTimeout(function() { // hack! undo_manager.save(new UndoState(data, 'Loaded'), true); }); +*/ + dispatcher.on('keyframe', function(channelName) { - dispatcher.on('keyframe', function(layer, value) { - var index = layers.indexOf(layer); - - var t = data.get('ui:currentTime').value; - var v = utils.findTimeinLayer(layer, t); + var time = context.currentTime; - // console.log(v, '...keyframe index', index, utils.format_friendly_seconds(t), typeof(v)); - // console.log('layer', layer, value); + if ( time == null || channelName == null ) return; - if (typeof(v) === 'number') { - layer.values.splice(v, 0, { - time: t, - value: value, - _color: '#' + (Math.random() * 0xffffff | 0).toString(16) - }); + var keyTimes = controller.getChannelKeyTimes( channelName, time ); - undo_manager.save(new UndoState(data, 'Add Keyframe')); + if ( utils.binarySearch( keyTimes, time ) < 0 ) { + + controller.setKeyframe( channelName, time ); + +// undo_manager.save(new UndoState(data, 'Add Keyframe')); } else { - console.log('remove from index', v); - layer.values.splice(v.index, 1); - undo_manager.save(new UndoState(data, 'Remove Keyframe')); + controller.delKeyframe( channelName, time ); + +// undo_manager.save(new UndoState(data, 'Remove Keyframe')); } - repaintAll(); + repaintAll(); // TODO repaint one channel would be enough }); dispatcher.on('keyframe.move', function(layer, value) { - undo_manager.save(new UndoState(data, 'Move Keyframe')); +// undo_manager.save(new UndoState(data, 'Move Keyframe')); }); - // dispatcher.fire('value.change', layer, me.value); - dispatcher.on('value.change', function(layer, value, dont_save) { - var t = data.get('ui:currentTime').value; - - var v = utils.findTimeinLayer(layer, t); - - console.log(v, 'value.change', layer, value, utils.format_friendly_seconds(t), typeof(v)); - if (typeof(v) === 'number') { - layer.values.splice(v, 0, { - time: t, - value: value, - _color: '#' + (Math.random() * 0xffffff | 0).toString(16) - }); - if (!dont_save) undo_manager.save(new UndoState(data, 'Add value')); - } else { - v.object.value = value; - if (!dont_save) undo_manager.save(new UndoState(data, 'Update value')); - } - - repaintAll(); - }); - dispatcher.on('ease', function(layer, ease_type) { - var t = data.get('ui:currentTime').value; - var v = utils.timeAtLayer(layer, t); - // console.log('Ease Change > ', layer, value, v); - if (v && v.entry) { - v.entry.tween = ease_type; - } + var start_play = null, + played_from = 0; // requires some more tweaking - undo_manager.save(new UndoState(data, 'Add Ease')); + var setCurrentTime = function setCurrentTime(value) { + var time = Math.min(Math.max(value, 0), context.totalTime); + context.currentTime = time; + controller.setDisplayTime( time ); + if (start_play) start_play = performance.now() - value * 1000; repaintAll(); - }); + } - var start_play = null, - played_from = 0; // requires some more tweaking - dispatcher.on('controls.toggle_play', function() { if (start_play) { pausePlaying(); @@ -150,7 +138,7 @@ function Timeliner(target) { function startPlaying() { // played_from = timeline.current_frame; - start_play = performance.now() - data.get('ui:currentTime').value * 1000; + start_play = performance.now() - context.currentTime * 1000; layer_panel.setControlStatus(true); // dispatcher.fire('controls.status', true); } @@ -166,64 +154,61 @@ function Timeliner(target) { setCurrentTime(0); }); - var currentTimeStore = data.get('ui:currentTime'); dispatcher.on('time.update', setCurrentTime); - function setCurrentTime(value) { - currentTimeStore.value = value; - - if (start_play) start_play = performance.now() - value * 1000; - repaintAll(); - // layer_panel.repaint(s); - } - - dispatcher.on('target.notify', function(name, value) { - if (target) target[name] = value; + dispatcher.on('totalTime.update', function(value) { + context.totalTime = value; + controller.setDuration(value); + timeline.repaint(); }); dispatcher.on('update.scale', function(v) { - console.log('range', v); - data.get('ui:timeScale').value = v; - // timeline.setTimeScale(v); + context.timeScale = v; + timeline.setTimeScale(v); timeline.repaint(); }); // handle undo / redo dispatcher.on('controls.undo', function() { +/* var history = undo_manager.undo(); data.setJSONString(history.state); - + updateState(); +*/ }); dispatcher.on('controls.redo', function() { +/* var history = undo_manager.redo(); data.setJSONString(history.state); - + updateState(); +*/ }); /* Paint Routines */ + var needsResize = true; function paint() { requestAnimationFrame(paint); - + if (start_play) { var t = (performance.now() - start_play) / 1000; setCurrentTime(t); - if (t > data.get('ui:totalTime').value) { + if (t > context.totalTime) { // simple loop start_play = performance.now(); } } if (needsResize) { - div.style.width = width + 'px'; - div.style.height = height + 'px'; + div.style.width = context.width + 'px'; + div.style.height = context.height + 'px'; restyle(layer_panel.dom, timeline.dom); @@ -244,6 +229,7 @@ function Timeliner(target) { */ function save(name) { +/* if (!name) name = 'autosave'; var json = data.getJSONString(); @@ -254,19 +240,20 @@ function Timeliner(target) { } catch (e) { console.log('Cannot save', name, json); } +*/ } function saveAs(name) { - if (!name) name = data.get('name').value; + if (!name) name = context.name; name = prompt('Pick a name to save to (localStorage)', name); if (name) { - data.data.name = name; + context.name = name; save(name); } } function saveSimply() { - var name = data.get('name').value; + var name = context.name; if (name) { save(name); } else { @@ -275,17 +262,31 @@ function Timeliner(target) { } function exportJSON() { - var json = data.getJSONString(); - var ret = prompt('Hit OK to download otherwise Copy and Paste JSON', json); - console.log(JSON.stringify(data.data, null, '\t')); - if (!ret) return; + var structs = controller.serialize(); +// var ret = prompt('Hit OK to download otherwise Copy and Paste JSON'); +// if (!ret) { +// console.log(JSON.stringify(structs, null, '\t')); +// return; +// } + + var fileName = 'animation.json'; + + saveToFile(JSON.stringify(structs, null, '\t'), fileName); + + } + + function load(structs) { + + controller.deserialize(structs); + + // TODO reset context + +// undo_manager.clear(); +// undo_manager.save(new UndoState(data, 'Loaded'), true); - // make json downloadable - json = data.getJSONString('\t'); - var fileName = 'timeliner-test' + '.json'; + updateState(); - saveToFile(json, fileName); } function loadJSONString(o) { @@ -294,35 +295,17 @@ function Timeliner(target) { load(json); } - function load(o) { - data.setJSON(o); - // - if (data.getValue('ui') === undefined) { - data.setValue('ui', { - currentTime: 0, - totalTime: 20, - scrollTime: 0, - timeScale: 40 - }); - } - - undo_manager.clear(); - undo_manager.save(new UndoState(data, 'Loaded'), true); - - updateState(); - } - function updateState() { - layers = layer_store.value; // FIXME: support Arrays - layer_panel.setState(layer_store); - timeline.setState(layer_store); + layer_panel.updateState(); + timeline.updateState(); repaintAll(); } function repaintAll() { - var content_height = layers.length * Settings.LINE_HEIGHT; - scrollbar.setLength(Settings.TIMELINE_SCROLL_HEIGHT / content_height); + var layers = context.controller.getChannelNames(); + var content_height = layers.length * LayoutConstants.LINE_HEIGHT; + scrollbar.setLength(context.scrollHeight / content_height); layer_panel.repaint(); timeline.repaint(); @@ -331,7 +314,7 @@ function Timeliner(target) { function promptImport() { var json = prompt('Paste JSON in here to Load'); if (!json) return; - console.log('Loading.. ', json); + //console.log('Loading.. ', json); loadJSONString(json); } @@ -351,7 +334,7 @@ function Timeliner(target) { data.blank(); updateState(); }); - + dispatcher.on('openfile', function() { openAs(function(data) { // console.log('loaded ' + data); @@ -365,7 +348,7 @@ function Timeliner(target) { dispatcher.on('save', saveSimply); dispatcher.on('save_as', saveAs); - // Expose API + // Expose API this.save = save; this.load = load; @@ -373,36 +356,64 @@ function Timeliner(target) { Start DOM Stuff (should separate file) */ - var div = document.createElement('div'); - div.style.cssText = 'position: absolute;'; - div.style.top = '16px'; + style(div, { + textAlign: 'left', + lineHeight: '1em', + position: 'absolute', + top: '22px' + }); var pane = document.createElement('div'); - + style(pane, { position: 'fixed', + top: '20px', + left: '20px', margin: 0, + border: '1px solid ' + Theme.a, padding: 0, - fontFamily: 'monospace', - zIndex: Z_INDEX, - border: '2px solid ' + Theme.a, - fontSize: '12px', - color: Theme.d, overflow: 'hidden', - top: '20px', - left: '20px' + backgroundColor: Theme.a, + color: Theme.d, + zIndex: Z_INDEX, + fontFamily: 'monospace', + fontSize: '12px' }); - pane.style.backgroundColor = Theme.a; + + var header_styles = { + position: 'absolute', + top: '0px', + width: '100%', + height: '22px', + lineHeight: '22px', + overflow: 'hidden' + }; + + var button_styles = { + width: '20px', + height: '20px', + padding: '2px', + marginRight: '2px' + }; var pane_title = document.createElement('div'); + style(pane_title, header_styles, { + borderBottom: '1px solid ' + Theme.b, + textAlign: 'center' + }); var title_bar = document.createElement('span'); pane_title.appendChild(title_bar); + title_bar.innerHTML = package_json.description + " " + package_json.version; + pane_title.appendChild(title_bar); + + var top_right_bar = document.createElement('div'); + style(top_right_bar, header_styles, { + textAlign: 'right' + }); - var top_right_bar = document.createElement('span'); - top_right_bar.style.float = 'right'; pane_title.appendChild(top_right_bar); // resize minimize @@ -410,56 +421,51 @@ function Timeliner(target) { // top_right_bar.appendChild(resize_small.dom); // resize full - var resize_full = new IconButton(10, 'resize_full', 'maximize', dispatcher); + var resize_full = new IconButton(10, 'resize_full', 'Maximize', dispatcher); + style(resize_full.dom, button_styles, { marginRight: '2px' }); top_right_bar.appendChild(resize_full.dom); - - style(pane_title, { - position: 'absolute', - width: '100%', - textAlign: 'left', - top: '0px', - height: '15px', - borderBottom: '1px solid ' + Theme.b, - overflow: 'hidden' - }); - - title_bar.innerHTML = 'Timeliner ' + package_json.version; var pane_status = document.createElement('div'); - style(pane_status, { + var footer_styles = { position: 'absolute', - height: '15px', - bottom: '0', width: '100%', + height: '22px', + lineHeight: '22px', + bottom: '0', // padding: '2px', - background: Theme.a, - borderTop: '1px solid ' + Theme.b, fontSize: '11px' + }; + + style(pane_status, footer_styles, { + borderTop: '1px solid ' + Theme.b, + background: Theme.a, }); pane.appendChild(div); pane.appendChild(pane_status); pane.appendChild(pane_title); - var button_styles = { - padding: '2px' - }; - var label_status = document.createElement('span'); - label_status.textContent = 'hello!'; + label_status.textContent = 'Hello!'; label_status.style.marginLeft = '10px'; - this.setStatus = function(text) { + dispatcher.on('status', function(text) { label_status.textContent = text; - }; + }); + dispatcher.on('state:save', function(description) { dispatcher.fire('status', description); save('autosave'); }); - dispatcher.on('status', this.setStatus); + + var bottom_right = document.createElement('div'); + style(bottom_right, footer_styles, { + textAlign: 'right' + }); + // var button_save = document.createElement('button'); // style(button_save, button_styles); @@ -483,19 +489,17 @@ function Timeliner(target) { // bottom_right.appendChild(button_save); // bottom_right.appendChild(button_open); - var bottom_right = document.createElement('span'); - bottom_right.style.float = 'right'; - pane_status.appendChild(label_status); pane_status.appendChild(bottom_right); - /**/ + +/* // zoom in var zoom_in = new IconButton(12, 'zoom_in', 'zoom in', dispatcher); // zoom out var zoom_out = new IconButton(12, 'zoom_out', 'zoom out', dispatcher); // settings - var cog = new IconButton(12, 'cog', 'settings', dispatcher); + var cog = new IconButton(12, 'cog', 'settings', dispatcher); // bottom_right.appendChild(zoom_in.dom); // bottom_right.appendChild(zoom_out.dom); @@ -507,10 +511,11 @@ function Timeliner(target) { var name = prompt('Layer name?'); addLayer(name); - undo_manager.save(new UndoState(data, 'Layer added')); + // undo_manager.save(new UndoState(data, 'Layer added')); repaintAll(); }); + style(plus.dom, button_styles); bottom_right.appendChild(plus.dom); @@ -527,8 +532,10 @@ function Timeliner(target) { } } }); + style(trash.dom, button_styles, { marginRight: '2px' }); bottom_right.appendChild(trash.dom); +*/ // pane_status.appendChild(document.createTextNode(' | TODO ')); @@ -537,7 +544,7 @@ function Timeliner(target) { */ var ghostpane = document.createElement('div'); - ghostpane.id = 'ghostpane'; + //ghostpane.id = 'ghostpane'; style(ghostpane, { background: '#999', opacity: 0.2, @@ -557,7 +564,6 @@ function Timeliner(target) { div.appendChild(layer_panel.dom); div.appendChild(timeline.dom); - var scrollbar = new ScrollBar(200, 10); div.appendChild(scrollbar.dom); // percentages @@ -599,7 +605,7 @@ function Timeliner(target) { document.addEventListener('keydown', function(e) { var play = e.keyCode == 32; // space - var enter = e.keyCode == 13; // + var enter = e.keyCode == 13; // var undo = e.metaKey && e.keyCode == 91 && !e.shiftKey; var active = document.activeElement; @@ -621,114 +627,85 @@ function Timeliner(target) { // Esc = stop. FIXME: should rewind head to last played from or Last pointed from? dispatcher.fire('controls.pause'); } - else console.log(e.keyCode); + //else console.log(e.keyCode); }); - var needsResize = true; - - function resize(width, height) { - // data.get('ui:bounds').value = { - // width: width, - // height: height - // }; + function resize(newWidth, newHeight) { // TODO: remove ugly hardcodes - width -= 4; - height -= 32; - - Settings.width = width - Settings.LEFT_PANE_WIDTH; - Settings.height = height; - - Settings.TIMELINE_SCROLL_HEIGHT = height - Settings.MARKER_TRACK_HEIGHT; - var scrollable_height = Settings.TIMELINE_SCROLL_HEIGHT; + context.width = newWidth - LayoutConstants.LEFT_PANE_WIDTH - 4; + context.height = newHeight - 44; + context.scrollHeight = context.height - LayoutConstants.MARKER_TRACK_HEIGHT; + scrollbar.setHeight(context.scrollHeight - 2); - scrollbar.setHeight(scrollable_height - 2); - // scrollbar.setThumb - style(scrollbar.dom, { - top: Settings.MARKER_TRACK_HEIGHT + 'px', - left: (width - 16) + 'px', + top: LayoutConstants.MARKER_TRACK_HEIGHT + 'px', + left: (newWidth - 16 - 4) + 'px', }); needsResize = true; } function restyle(left, right) { - left.style.cssText = 'position: absolute; left: 0px; top: 0px; height: ' + Settings.height + 'px;'; + left.style.cssText = 'position: absolute; left: 0px; top: 0px; height: ' + context.height + 'px;'; style(left, { // background: Theme.a, overflow: 'hidden' }); - left.style.width = Settings.LEFT_PANE_WIDTH + 'px'; + left.style.width = LayoutConstants.LEFT_PANE_WIDTH + 'px'; // right.style.cssText = 'position: absolute; top: 0px;'; right.style.position = 'absolute'; right.style.top = '0px'; - right.style.left = Settings.LEFT_PANE_WIDTH + 'px'; + right.style.left = LayoutConstants.LEFT_PANE_WIDTH + 'px'; } +/* function addLayer(name) { var layer = new LayerProp(name); layers = layer_store.value; layers.push(layer); - layer_panel.setState(layer_store); + layer_panel.updateState(); } this.addLayer = addLayer; +*/ - this.setTarget = function(t) { - timeline = t; - }; - - function getValueRanges(ranges, interval) { - interval = interval ? interval : 0.15; - ranges = ranges ? ranges : 2; - - // not optimized! - var t = data.get('ui:currentTime').value; - - var values = []; - - for (var u = -ranges; u <= ranges; u++) { - // if (u == 0) continue; - var o = {}; + this.dispose = function dispose() { - for (var l = 0; l < layers.length; l++) { - var layer = layers[l]; - var m = utils.timeAtLayer(layer, t + u * interval); - o[layer.name] = m.value; - } - - values.push(o); + var domParent = pane.parentElement; + domParent.removeChild(pane); + domParent.removeChild(ghostpane); - } - - return values; - } - - this.getValues = getValueRanges; + }; (function DockingWindow() { "use strict"; // Minimum resizable area - var minWidth = 100; - var minHeight = 80; + var minWidth = 120; + var minHeight = 100; // Thresholds var FULLSCREEN_MARGINS = 2; - var SNAP_MARGINS = 8; + var SNAP_MARGINS = 12; var MARGINS = 2; + var DEFAULT_SNAP = 'snap-bottom-edge'; + // End of what's configurable. var clicked = null; var onRightEdge, onBottomEdge, onLeftEdge, onTopEdge; - var preSnapped; + var preSnapped = { + width: LayoutConstants.WIDTH, + height: LayoutConstants.HEIGHT + }; + var snapType = DEFAULT_SNAP; - var b, x, y; + var x, y, b = pane.getBoundingClientRect(); var redraw = false; @@ -736,7 +713,6 @@ function Timeliner(target) { // var ghostpane = document.getElementById('ghostpane'); var mouseOnTitle = false; - var snapType; pane_title.addEventListener('mouseover', function() { mouseOnTitle = true; @@ -789,19 +765,24 @@ function Timeliner(target) { ghostpane.style.opacity = 0; } - setBounds(pane, 0, 0, Settings.width, Settings.height); - setBounds(ghostpane, 0, 0, Settings.width, Settings.height); + setBounds(pane, 0, 0, context.width, context.height); + setBounds(ghostpane, 0, 0, context.width, context.height); // Mouse events pane.addEventListener('mousedown', onMouseDown); + pane.addEventListener('mouseover', onMouseOver); + pane.addEventListener('mouseout',onMouseOut); document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); - // Touch events + // Touch events pane.addEventListener('touchstart', onTouchDown); document.addEventListener('touchmove', onTouchMove); document.addEventListener('touchend', onTouchEnd); + var mouseOver = false; + function onMouseOver(e) { mouseOver = true; } + function onMouseOut(e) { mouseOver = false; } function onTouchDown(e) { onDown(e.touches[0]); @@ -809,7 +790,7 @@ function Timeliner(target) { } function onTouchMove(e) { - onMove(e.touches[0]); + onMove(e.touches[0]); } function onTouchEnd(e) { @@ -843,8 +824,8 @@ function Timeliner(target) { if (isResizing || isMoving) { e.preventDefault(); - e.stopPropagation(); } + e.stopPropagation(); } function canMove() { @@ -871,6 +852,10 @@ function Timeliner(target) { calc(e); redraw = true; + + if (mouseOver) { + e.stopPropagation(); + } } function animate() { @@ -890,7 +875,7 @@ function Timeliner(target) { var currentWidth = Math.max(clicked.cx - e.clientX + clicked.w, minWidth); if (currentWidth > minWidth) { pane.style.width = currentWidth + 'px'; - pane.style.left = e.clientX + 'px'; + pane.style.left = e.clientX + 'px'; } } @@ -898,7 +883,7 @@ function Timeliner(target) { var currentHeight = Math.max(clicked.cy - e.clientY + clicked.h, minHeight); if (currentHeight > minHeight) { pane.style.height = currentHeight + 'px'; - pane.style.top = e.clientY + 'px'; + pane.style.top = e.clientY + 'px'; } } @@ -917,19 +902,19 @@ function Timeliner(target) { ghostpane.style.opacity = 0.2; break; case 'snap-top-edge': - setBounds(ghostpane, 0, 0, window.innerWidth, window.innerHeight / 2); + setBounds(ghostpane, 0, 0, window.innerWidth, window.innerHeight * 0.25); ghostpane.style.opacity = 0.2; break; case 'snap-left-edge': - setBounds(ghostpane, 0, 0, window.innerWidth / 2, window.innerHeight); + setBounds(ghostpane, 0, 0, window.innerWidth * 0.35, window.innerHeight); ghostpane.style.opacity = 0.2; break; case 'snap-right-edge': - setBounds(ghostpane, window.innerWidth / 2, 0, window.innerWidth / 2, window.innerHeight); + setBounds(ghostpane, window.innerWidth * 0.65, 0, window.innerWidth * 0.35, window.innerHeight); ghostpane.style.opacity = 0.2; break; case 'snap-bottom-edge': - setBounds(ghostpane, 0, window.innerHeight / 2, window.innerWidth, window.innerHeight / 2); + setBounds(ghostpane, 0, window.innerHeight * 0.75, window.innerWidth, window.innerHeight * 0.25); ghostpane.style.opacity = 0.2; break; default: @@ -1014,26 +999,28 @@ function Timeliner(target) { animate(); function resizeEdges() { + var x, y, w, h; switch(snapType) { case 'full-screen': - // hintFull(); - setBounds(pane, 0, 0, window.innerWidth, window.innerHeight); + x = 0, y = 0, w = window.innerWidth, h = window.innerHeight; break; case 'snap-top-edge': - // hintTop(); - setBounds(pane, 0, 0, window.innerWidth, window.innerHeight / 2); + x = 0, y = 0, w = window.innerWidth, h = window.innerHeight * 0.25; break; case 'snap-left-edge': - // hintLeft(); - setBounds(pane, 0, 0, window.innerWidth / 2, window.innerHeight); + x = 0, y = 0, w = window.innerWidth * 0.35, h = window.innerHeight; break; case 'snap-right-edge': - setBounds(pane, window.innerWidth / 2, 0, window.innerWidth / 2, window.innerHeight); + x = window.innerWidth * 0.65, y = 0, w = window.innerWidth * 0.35, h = window.innerHeight; break; case 'snap-bottom-edge': - setBounds(pane, 0, window.innerHeight / 2, window.innerWidth, window.innerHeight / 2); + x = 0, y = window.innerHeight * 0.75, w = window.innerWidth, h = window.innerHeight * 0.25; break; + default: + return; } + setBounds(pane, x, y, w, h); + setBounds(ghostpane, x, y, w, h); } function onUp(e) { @@ -1058,9 +1045,17 @@ function Timeliner(target) { clicked = null; + if (mouseOver) { + e.stopPropagation(); + } } + + resizeEdges(); + })(); } -window.Timeliner = Timeliner; \ No newline at end of file +Timeliner.binarySearch = utils.binarySearch; + +window.Timeliner = Timeliner; diff --git a/src/tween.js b/src/tween.js deleted file mode 100644 index 1f953d2..0000000 --- a/src/tween.js +++ /dev/null @@ -1,24 +0,0 @@ -/**************************/ -// Tweens -/**************************/ - -var Tweens = { - none: function(k) { - return 0; - }, - linear: function(k) { - return k; - }, - quadEaseIn: function(k) { - return k * k; - }, - quadEaseOut: function(k) { - return - k * ( k - 2 ); - }, - quadEaseInOut: function(k) { - if ( ( k *= 2 ) < 1 ) return 0.5 * k * k; - return - 0.5 * ( --k * ( k - 2 ) - 1 ); - } -}; - -module.exports = Tweens; \ No newline at end of file diff --git a/src/ui/layer_view.js b/src/ui/layer_view.js deleted file mode 100644 index 99f42ac..0000000 --- a/src/ui/layer_view.js +++ /dev/null @@ -1,140 +0,0 @@ -var - Theme = require('../theme'), - NumberUI = require('./number'), - Tweens = require('../tween'), - Settings = require('../settings'), - utils = require('../utils') -; - -// TODO - tagged by index instead, work off layers. - -function LayerView(layer, dispatcher) { - var dom = document.createElement('div'); - - var label = document.createElement('span'); - - label.style.cssText = 'font-size: 12px; padding: 4px;'; - - var dropdown = document.createElement('select'); - var option; - dropdown.style.cssText = 'font-size: 10px; width: 60px; margin: 0; float: right; text-align: right;'; - - for (var k in Tweens) { - option = document.createElement('option'); - option.text = k; - dropdown.appendChild(option); - } - - dropdown.addEventListener('change', function(e) { - dispatcher.fire('ease', layer, dropdown.value); - }); - - var keyframe_button = document.createElement('button'); - keyframe_button.innerHTML = '◈'; // '♦' ◇ 9679 9670 9672 - keyframe_button.style.cssText = 'background: none; font-size: 12px; padding: 0px; font-family: monospace; float: right; width: 20px; border-style:none; outline: none;'; // border-style:inset; - - keyframe_button.addEventListener('click', function(e) { - console.log('clicked:keyframing...', state.get('_value').value); - dispatcher.fire('keyframe', layer, state.get('_value').value); - }); - - /* - // Prev Keyframe - var button = document.createElement('button'); - button.textContent = '<'; - button.style.cssText = 'font-size: 12px; padding: 1px; '; - dom.appendChild(button); - - // Next Keyframe - button = document.createElement('button'); - button.textContent = '>'; - button.style.cssText = 'font-size: 12px; padding: 1px; '; - dom.appendChild(button); - - // Mute - button = document.createElement('button'); - button.textContent = 'M'; - button.style.cssText = 'font-size: 12px; padding: 1px; '; - dom.appendChild(button); - - // Solo - button = document.createElement('button'); - button.textContent = 'S'; - button.style.cssText = 'font-size: 12px; padding: 1px; '; - dom.appendChild(button); - */ - - var number = new NumberUI(layer, dispatcher); - - number.onChange.do(function(value, done) { - state.get('_value').value = value; - dispatcher.fire('value.change', layer, value, done); - }); - - utils.style(number.dom, { - float: 'right' - }); - - dom.appendChild(label); - dom.appendChild(keyframe_button); - dom.appendChild(number.dom); - dom.appendChild(dropdown); - - dom.style.cssText = 'margin: 0px; border-bottom:1px solid ' + Theme.b + '; top: 0; left: 0; height: ' + (Settings.LINE_HEIGHT - 1 ) + 'px; color: ' + Theme.c; - this.dom = dom; - - this.repaint = repaint; - var state; - - this.setState = function(l, s) { - layer = l; - state = s; - - var tmp_value = state.get('_value'); - if (tmp_value.value === undefined) { - tmp_value.value = 0; - } - - number.setValue(tmp_value.value); - label.textContent = state.get('name').value; - - repaint(); - }; - - function repaint(s) { - - dropdown.style.opacity = 0; - dropdown.disabled = true; - keyframe_button.style.color = Theme.b; - // keyframe_button.disabled = false; - // keyframe_button.style.borderStyle = 'solid'; - - var tween = null; - var o = utils.timeAtLayer(layer, s); - - if (!o) return; - - if (o.can_tween) { - dropdown.style.opacity = 1; - dropdown.disabled = false; - // if (o.tween) - dropdown.value = o.tween ? o.tween : 'none'; - if (dropdown.value === 'none') dropdown.style.opacity = 0.5; - } - - if (o.keyframe) { - keyframe_button.style.color = Theme.c; - // keyframe_button.disabled = true; - // keyframe_button.style.borderStyle = 'inset'; - } - - state.get('_value').value = o.value; - number.setValue(o.value); - number.paint(); - - dispatcher.fire('target.notify', layer.name, o.value); - } - -} - -module.exports = LayerView; diff --git a/src/utils.js b/src/utils.js index fb0a3ec..249b614 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,3 @@ -var - Tweens = require('./tween'); - module.exports = { STORAGE_PREFIX: 'timeliner-', Z_INDEX: 999, @@ -8,18 +5,151 @@ module.exports = { saveToFile: saveToFile, openAs: openAs, format_friendly_seconds: format_friendly_seconds, - findTimeinLayer: findTimeinLayer, - timeAtLayer: timeAtLayer, - proxy_ctx: proxy_ctx + proxy_ctx: proxy_ctx, + handleDrag: handleDrag, + binarySearch: binarySearch }; /**************************/ // Utils /**************************/ -function style(element, styles) { - for (var s in styles) { - element.style[s] = styles[s]; +function binarySearch(arr, num) { + + var l = 0, r = arr.length, found = false; + + while ( l < r ) { + + var m = ( l + r ) >> 1; + + if ( arr[ m ] < num ) { + + l = m + 1; + + } else { + + r = m; + + found = arr[ m ] === num; + + } + + } + + return found ? l : ~l; + +} + +function handleDrag(element, ondown, onmove, onup, down_criteria) { + var pointer = null; + var bounds = element.getBoundingClientRect(); + + element.addEventListener('mousedown', onMouseDown); + + function onMouseDown(e) { + handleStart(e); + + if (down_criteria && !down_criteria(pointer,e)) { + pointer = null; + return; + } + + + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + + ondown(pointer,e); + + e.preventDefault(); + } + + function onMouseMove(e) { + handleMove(e); + pointer.moved = true; + onmove(pointer, e); + } + + function handleStart(e) { + bounds = element.getBoundingClientRect(); + var currentx = e.clientX, currenty = e.clientY; + pointer = { + startx: currentx, + starty: currenty, + x: currentx, + y: currenty, + dx: 0, + dy: 0, + offsetx: currentx - bounds.left, + offsety: currenty - bounds.top, + moved: false + }; + } + + function handleMove(e) { + bounds = element.getBoundingClientRect(); + var currentx = e.clientX, + currenty = e.clientY, + offsetx = currentx - bounds.left, + offsety = currenty - bounds.top; + pointer.x = currentx; + pointer.y = currenty; + pointer.dx = e.clientX - pointer.startx; + pointer.dy = e.clientY - pointer.starty; + pointer.offsetx = offsetx; + pointer.offsety = offsety; + } + + function onMouseUp(e) { + handleMove(e); + onup(pointer,e); + pointer = null; + + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + } + + element.addEventListener('touchstart', onTouchStart); + + function onTouchStart(te) { + + if (te.touches.length == 1) { + + var e = te.touches[0]; + if (down_criteria && !down_criteria(e)) return; + te.preventDefault(); + handleStart(e); + ondown(pointer,e); + } + + element.addEventListener('touchmove', onTouchMove); + element.addEventListener('touchend', onTouchEnd); + } + + function onTouchMove(te) { + var e = te.touches[0]; + onMouseMove(e); + } + + function onTouchEnd(e) { + // var e = e.touches[0]; + onMouseUp(e); + element.removeEventListener('touchmove', onTouchMove); + element.removeEventListener('touchend', onTouchEnd); + } + + + this.release = function() { + element.removeEventListener('mousedown', onMouseDown); + element.removeEventListener('touchstart', onTouchStart); + }; +} + +function style(element, var_args) { + for (var i = 1; i < arguments.length; ++i) { + var styles = arguments[i]; + for (var s in styles) { + element.style[s] = styles[s]; + } } } @@ -50,7 +180,7 @@ var input, openCallback; function handleFileSelect(evt) { var files = evt.target.files; // FileList object - console.log('handle file select', files.length); + //console.log('handle file select', files.length); var f = files[0]; if (!f) return; @@ -58,7 +188,7 @@ function handleFileSelect(evt) { // if (!f.type.match('application/json')) { // return; // } - console.log('match', f.type); + //console.log('match', f.type); var reader = new FileReader(); @@ -75,7 +205,7 @@ function handleFileSelect(evt) { function openAs(callback, target) { - console.log('openfile...'); + //console.log('openfile...'); openCallback = callback; if (!input) { @@ -125,117 +255,6 @@ function format_friendly_seconds(s, type) { return str; } -// get object at time -function findTimeinLayer(layer, time) { - var values = layer.values; - var i, il; - - // TODO optimize by checking time / binary search - - for (i=0, il=values.length; i time) { - return i; - } - } - - return i; -} - - -function timeAtLayer(layer, t) { - // Find the value of layer at t seconds. - // this expect layer to be sorted - // not the most optimized for now, but would do. - - var values = layer.values; - var i, il, entry, prev_entry; - - il = values.length; - - // can't do anything - if (il === 0) return; - - // find boundary cases - entry = values[0]; - if (t < entry.time) { - return { - value: entry.value, - can_tween: false, // cannot tween - keyframe: false // not on keyframe - }; - } - - for (i=0; i 1, - value: entry.value, - keyframe: true - }; - } - return { - // index: i, - entry: entry, - tween: entry.tween, - can_tween: il > 1, - value: entry.value, - keyframe: true // il > 1 - }; - } - if (t < entry.time) { - // possibly a tween - if (!prev_entry.tween) { // or if value is none - return { - value: prev_entry.value, - tween: false, - entry: prev_entry, - can_tween: true, - keyframe: false - }; - } - - // calculate tween - var time_diff = entry.time - prev_entry.time; - var value_diff = entry.value - prev_entry.value; - var tween = prev_entry.tween; - - var dt = t - prev_entry.time; - var k = dt / time_diff; - var new_value = prev_entry.value + Tweens[tween](k) * value_diff; - - return { - entry: prev_entry, - value: new_value, - tween: prev_entry.tween, - can_tween: true, - keyframe: false - }; - } - } - // time is after all entries - return { - value: entry.value, - can_tween: false, - keyframe: false - }; - -} - - function proxy_ctx(ctx) { // Creates a proxy 2d context wrapper which // allows the fluent / chaining API. diff --git a/src/font.json b/src/widget/font.json similarity index 100% rename from src/font.json rename to src/widget/font.json diff --git a/src/icon_button.js b/src/widget/icon_button.js similarity index 94% rename from src/icon_button.js rename to src/widget/icon_button.js index a821f1b..d7ceaa1 100644 --- a/src/icon_button.js +++ b/src/widget/icon_button.js @@ -1,6 +1,6 @@ var font = require('./font.json'), - Theme = require('./theme'), - style = require('./utils').style; + Theme = require('../theme'), + style = require('../utils').style; var dp; @@ -63,7 +63,7 @@ function IconButton(size, icon, tooltip, dp) { this.setIcon = function(icon) { me.icon = icon; - if (!font.fonts[icon]) console.warn('Font icon not found!'); + if (!font.fonts[icon]) console.error('Font icon not found!'); this.resize(); }; @@ -81,7 +81,7 @@ function IconButton(size, icon, tooltip, dp) { e.stopPropagation(); longHoldTimer = setTimeout(function() { if (longHoldTimer) { - console.log('LONG HOLD-ED!'); + //console.log('LONG HOLD-ED!'); f(); } }, LONG_HOLD_DURATION); @@ -90,7 +90,7 @@ function IconButton(size, icon, tooltip, dp) { function clearLongHoldTimer() { clearTimeout(longHoldTimer); } - + button.addEventListener('mousedown', startHold); button.addEventListener('touchstart', startHold); button.addEventListener('mouseup', clearLongHoldTimer); @@ -122,7 +122,7 @@ function IconButton(size, icon, tooltip, dp) { button.addEventListener('mouseover', function() { // button.style.background = up; style(button, borders); - + ctx.fillStyle = Theme.d; // me.dropshadow = true; ctx.shadowColor = Theme.b; @@ -131,7 +131,7 @@ function IconButton(size, icon, tooltip, dp) { ctx.shadowOffsetY = 1 * dpr; me.draw(); - if (tooltip && dp) dp.fire('status', 'button: ' + tooltip); + if (tooltip && dp) dp.fire('status', tooltip); }); button.addEventListener('mousedown', function() { @@ -149,7 +149,7 @@ function IconButton(size, icon, tooltip, dp) { button.addEventListener('mouseout', function() { // ctx.fillStyle = Theme.c; - + button.style.background = normal; style(button, no_borders); @@ -233,4 +233,4 @@ IconButton.prototype.draw = function() { */ }; -module.exports = IconButton; \ No newline at end of file +module.exports = IconButton; diff --git a/src/ui/number.js b/src/widget/number.js similarity index 88% rename from src/ui/number.js rename to src/widget/number.js index 68635d6..f691d5b 100644 --- a/src/ui/number.js +++ b/src/widget/number.js @@ -1,7 +1,7 @@ var Theme = require('../theme'), Do = require('do.js'), - handleDrag = require('../handle_drag'), - style = require('../utils').style + style = require('../utils').style, + handleDrag = require('../utils').handleDrag ; /**************************/ @@ -41,7 +41,7 @@ function NumberUI(config) { this.onChange = new Do(); span.addEventListener('change', function(e) { - console.log('input changed', span.value); + //console.log('input changed', span.value); value = parseFloat(span.value, 10); fireChange(); @@ -87,8 +87,8 @@ function NumberUI(config) { }; this.paint = function() { - if (value) span.value = value.toFixed(precision); + if (value != null) span.value = value.toFixed(precision); }; } -module.exports = NumberUI; \ No newline at end of file +module.exports = NumberUI; diff --git a/src/ui/scrollbar.js b/src/widget/scrollbar.js similarity index 98% rename from src/ui/scrollbar.js rename to src/widget/scrollbar.js index 4778975..ff5c07b 100644 --- a/src/ui/scrollbar.js +++ b/src/widget/scrollbar.js @@ -1,6 +1,5 @@ var SimpleEvent = require('do.js'); var utils = require('../utils'); -console.log(utils); // ********** class: ScrollBar ****************** // /* @@ -132,4 +131,4 @@ function ScrollBar(h, w, dispatcher) { } -module.exports = ScrollBar; \ No newline at end of file +module.exports = ScrollBar; diff --git a/test.html b/test.html index fe28dbe..9a48982 100644 --- a/test.html +++ b/test.html @@ -1,56 +1,127 @@ + - - -
- + - \ No newline at end of file + diff --git a/test_ghosts.html b/test_ghosts.html deleted file mode 100644 index 175ecb1..0000000 --- a/test_ghosts.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - -
- - - - - - \ No newline at end of file diff --git a/timeliner.js b/timeliner.js deleted file mode 100644 index 9914512..0000000 --- a/timeliner.js +++ /dev/null @@ -1,3453 +0,0 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0;) { - // quick hack - if (i >= layers.length) { - layer_uis[i].dom.style.display = 'none'; - unused_layers.push(layer_uis.pop()); - continue; - } - - // console.log('yoz', states.get(i).value); - layer_uis[i].setState(layers[i], layer_store.get(i)); - // layer_uis[i].setState('layers'+':'+i); - layer_uis[i].repaint(s); - } - - visible_layers = layer_uis.length; - - } - - this.repaint = repaint; - this.setState(layer_store); - - this.scrollTo = function(x) { - layer_scroll.scrollTop = x * (layer_scroll.scrollHeight - layer_scroll.clientHeight); - }; - - this.dom = div; - - repaint(); -} - -module.exports = LayerCabinet; -},{"./icon_button":"/Users/joshua/dev/gits/timeliner/src/icon_button.js","./settings":"/Users/joshua/dev/gits/timeliner/src/settings.js","./theme":"/Users/joshua/dev/gits/timeliner/src/theme.js","./ui/layer_view":"/Users/joshua/dev/gits/timeliner/src/ui/layer_view.js","./ui/number":"/Users/joshua/dev/gits/timeliner/src/ui/number.js","./utils":"/Users/joshua/dev/gits/timeliner/src/utils.js"}],"/Users/joshua/dev/gits/timeliner/src/save_format.js":[function(require,module,exports){ -/* Layer Schema */ -/* -var layer_1 = [ - { - name: 'abc', - props: { - min: - max: - step: - real_step: - }, - values: [ - [t, v, ''], - {time: t, value: v, tween: bla, _color: 'red'}, - {time: t, value: v, tween: bla}, - {time: t, value: v, tween: bla}, - {time: t, value: v, tween: bla}, - {time: t, value: v}, - ], - ui: { - mute: true, // mute - solo: true, - - }, - tmp: { - value: value, - _color: - } - } - ,... -] currently_playing, scale. -*/ - -/* Timeline Data Schema */ - -var sample = { - version: '1.2.0', - modified: new Date, - - name: 'sample', - title: 'Sample Title', - - ui: { - current_time: 1, - duration: 100, - - position: '0:0:0', - bounds: '10 10 100 100', - snap: 'full | left-half | top-half | right-half | bottom-half' - }, - - layers: [{ - - }] -}; -},{}],"/Users/joshua/dev/gits/timeliner/src/settings.js":[function(require,module,exports){ - -var DEFAULT_TIME_SCALE = 60; - -// Dimensions -module.exports = { - LINE_HEIGHT: 26, - DIAMOND_SIZE: 12, - MARKER_TRACK_HEIGHT: 60, - width: 600, - height: 200, - TIMELINE_SCROLL_HEIGHT: 0, - LEFT_PANE_WIDTH: 250, - time_scale: DEFAULT_TIME_SCALE // number of pixels to 1 secon, -}; -},{}],"/Users/joshua/dev/gits/timeliner/src/theme.js":[function(require,module,exports){ -module.exports = { - // photoshop colors - a: '#343434', - b: '#535353', - c: '#b8b8b8', - d: '#d6d6d6', -}; -},{}],"/Users/joshua/dev/gits/timeliner/src/timeline_panel.js":[function(require,module,exports){ -var - Settings = require('./settings'), - Theme = require('./theme'), - utils = require('./utils'), - Tweens = require('./tween'), - handleDrag = require('./handle_drag'); - - var - LINE_HEIGHT = Settings.LINE_HEIGHT, - DIAMOND_SIZE = Settings.DIAMOND_SIZE, - MARKER_TRACK_HEIGHT = Settings.MARKER_TRACK_HEIGHT, - - LEFT_PANE_WIDTH = Settings.LEFT_PANE_WIDTH, - time_scale = Settings.time_scale; - - - var frame_start = 0; // this is the current scroll position. -/* -Aka. Subdivison LOD -// Eg. 1 inch - 60s, 1 inch - 60fps, 1 inch - 6 mins -*/ -// TODO: refactor to use a nicer scale - -var subds, subd_type, subd1, subd2, subd3; - -function time_scaled() { - if (time_scale > 350) { - subds = [12, 12, 60, 'frames']; - } else if (time_scale > 250) { - subds = [6, 12, 60, 'frames']; - } else if (time_scale > 200) { - subds = [6, 6, 30, 'frames']; - } else if (time_scale > 150) { - subds = [4, 4, 20, 'frames']; - } else if (time_scale > 100) { - subds = [4, 4, 8, 'frames']; - } else if (time_scale > 90) { - subds = [4, 4, 8, 'seconds']; - } else if (time_scale > 60) { - subds = [2, 4, 8, 'seconds']; - } else if (time_scale > 40) { - subds = [1, 2, 10, 'seconds']; - } else if (time_scale > 30) { - subds = [1, 2, 10, 'seconds']; - } else if (time_scale > 10) { - subds = [1, 1, 4, 'seconds']; - } else if (time_scale > 4) { - subds = [1/5, 1/5, 1/5, 'seconds']; - } else if (time_scale > 3) { - subds = [1/10, 1/10, 1/5, 'seconds']; - } else if (time_scale > 1) { - subds = [1/20, 1/20, 1/10, 'seconds']; - } else if (time_scale >= 1) { - subds = [1/30, 1/30, 1/15, 'seconds']; - } else { // 1s per pixel - subds = [1/60, 1/60, 1/15, 'seconds']; - } - - console.log(subds); - - - subd1 = subds[0]; // big ticks / labels - subd2 = subds[1]; // medium ticks - subd3 = subds[2]; // small ticks - subd_type = subds[3]; -} - -time_scaled(); - - -/**************************/ -// Timeline Panel -/**************************/ - -function TimelinePanel(data, dispatcher) { - - var dpr = window.devicePixelRatio; - var canvas = document.createElement('canvas'); - - var scrollTop = 0, scrollLeft = 0, SCROLL_HEIGHT; - var layers = data.get('layers').value; - - this.scrollTo = function(s, y) { - scrollTop = s * Math.max(layers.length * LINE_HEIGHT - SCROLL_HEIGHT, 0); - repaint(); - }; - - this.resize = function() { - dpr = window.devicePixelRatio; - canvas.width = Settings.width * dpr; - canvas.height = Settings.height * dpr; - canvas.style.width = Settings.width + 'px'; - canvas.style.height = Settings.height + 'px'; - SCROLL_HEIGHT = Settings.height - MARKER_TRACK_HEIGHT; - }; - - this.dom = canvas; - this.resize(); - - var ctx = canvas.getContext('2d'); - - var current_frame; // currently in seconds - // var currentTime = 0; // in frames? could have it in string format (0:00:00:1-60) - - - var LEFT_GUTTER = 20; - var i, x, y, il, j; - - var needsRepaint = false; - - function repaint() { - needsRepaint = true; - } - - - function drawLayerContents() { - // horizontal Layer lines - for (i = 0, il = layers.length; i <= il; i++) { - ctx.strokeStyle = Theme.b; - ctx.beginPath(); - y = i * LINE_HEIGHT; - y = ~~y - 0.5; - - ctx.moveTo(0, y); - ctx.lineTo(width, y); - ctx.stroke(); - } - - // Draw Easing Rects - for (i = 0; i < il; i++) { - // check for keyframes - var layer = layers[i]; - var values = layer.values; - - y = i * LINE_HEIGHT; - - for (var j = 0; j < values.length - 1; j++) { - var frame = values[j]; - var frame2 = values[j + 1]; - ctx.fillStyle = frame._color; // Theme.c - - // Draw Tween Rect - x = time_to_x(frame.time); - x2 = time_to_x(frame2.time); - - if (!frame.tween || frame.tween == 'none') continue; - - var y1 = y + 2; - var y2 = y + LINE_HEIGHT - 2; - // console.log('concert', frame.time, '->', x, y2); - ctx.beginPath(); - ctx.moveTo(x, y1); - ctx.lineTo(x2, y1); - ctx.lineTo(x2, y2); - ctx.lineTo(x, y2); - ctx.closePath(); - ctx.fill(); - - // draw easing graph - var color = parseInt(frame._color.substring(1,7), 16); - color = 0xffffff ^ color; - color = color.toString(16); // convert to hex - color = '#' + ('000000' + color).slice(-6); - - ctx.strokeStyle = color; - var x3; - ctx.beginPath(); - ctx.moveTo(x, y2); - var dy = y1 - y2; - var dx = x2 - x; - - for (x3=x; x3 < x2; x3++) { - ctx.lineTo(x3, y2 + Tweens[frame.tween]((x3 - x)/dx) * dy); - } - ctx.stroke(); - } - - ctx.fillStyle = Theme.d; - ctx.strokeStyle = Theme.b; - - var j, frame; - - for (j = 0; j < values.length; j++) { - frame = values[j]; - - // Draw Diamond - x = time_to_x(frame.time); - - y2 = y + LINE_HEIGHT * 0.5 - DIAMOND_SIZE / 2; - // console.log('concert', frame.time, '->', x, y2); - ctx.beginPath(); - ctx.moveTo(x, y2); - ctx.lineTo(x + DIAMOND_SIZE / 2, y2 + DIAMOND_SIZE / 2); - ctx.lineTo(x, y2 + DIAMOND_SIZE); - ctx.lineTo(x - DIAMOND_SIZE / 2, y2 + DIAMOND_SIZE / 2); - ctx.closePath(); - ctx.fill(); - - } - - } - } - - var TOP_SCROLL_TRACK = 20; - var scroller = { - left: 0, - grip_length: 0, - k: 1 - }; - var left; - - function drawScroller() { - var w = width; - - var totalTime = data.get('ui:totalTime').value; - var pixels_per_second = data.get('ui:timeScale').value; - - var viewTime = w / pixels_per_second; - - - var k = w / totalTime; // pixels per seconds - scroller.k = k; - - // 800 / 5 = 180 - - // var k = Math.min(viewTime / totalTime, 1); - // var grip_length = k * w; - - scroller.grip_length = viewTime * k; - var h = TOP_SCROLL_TRACK; - - scroller.left = data.get('ui:scrollTime').value * k; - scroller.left = Math.min(Math.max(0, scroller.left), w - scroller.grip_length); - - ctx.beginPath(); - ctx.fillStyle = Theme.b; // 'cyan'; - ctx.rect(0, 5, w, h); - ctx.fill(); - - ctx.fillStyle = Theme.c; // 'yellow'; - - ctx.beginPath(); - ctx.rect(scroller.left, 5, scroller.grip_length, h); - ctx.fill(); - - var r = current_frame * k; - - // ctx.fillStyle = Theme.a; // 'yellow'; - // ctx.fillRect(0, 5, w, 2); - - ctx.fillStyle = 'red'; - ctx.fillRect(0, 5, r, 2); - - // ctx.strokeStyle = 'red'; - // ctx.lineWidth = 2; - // ctx.beginPath(); - // ctx.moveTo(r, 5); - // ctx.lineTo(r, 15); - // ctx.stroke(); - - } - - - function setTimeScale() { - var v = data.get('ui:timeScale').value; - if (time_scale !== v) { - time_scale = v; - time_scaled(); - } - } - - function _paint() { - if (!needsRepaint) return; - - setTimeScale(); - - current_frame = data.get('ui:currentTime').value; - frame_start = data.get('ui:scrollTime').value; - - /**************************/ - // background - - ctx.fillStyle = Theme.a; - //ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.save(); - ctx.scale(dpr, dpr); - - // - - ctx.lineWidth = 1; // .5, 1, 2 - - width = Settings.width, - height = Settings.height; - - var units = time_scale / subd1; - var offsetUnits = (frame_start * time_scale) % units; - - var count = (width - LEFT_GUTTER + offsetUnits) / units; - - // console.log('time_scale', time_scale, 'subd1', subd1, 'units', units, 'offsetUnits', offsetUnits, frame_start); - - // time_scale = pixels to 1 second (40) - // subd1 = marks per second (marks / s) - // units = pixels to every mark (40) - - // labels only - for (i = 0; i < count; i++) { - x = i * units + LEFT_GUTTER - offsetUnits; - - // vertical lines - ctx.strokeStyle = Theme.b; - ctx.beginPath(); - ctx.moveTo(x, 0); - ctx.lineTo(x, height); - ctx.stroke(); - - ctx.fillStyle = Theme.d; - ctx.textAlign = 'center'; - - var t = (i * units - offsetUnits) / time_scale + frame_start; - t = utils.format_friendly_seconds(t, subd_type); - ctx.fillText(t, x, 38); - } - - units = time_scale / subd2; - count = (width - LEFT_GUTTER + offsetUnits) / units; - - // marker lines - main - for (i = 0; i < count; i++) { - ctx.strokeStyle = Theme.c; - ctx.beginPath(); - x = i * units + LEFT_GUTTER - offsetUnits; - ctx.moveTo(x, MARKER_TRACK_HEIGHT - 0); - ctx.lineTo(x, MARKER_TRACK_HEIGHT - 16); - ctx.stroke(); - } - - var mul = subd3 / subd2; - units = time_scale / subd3; - count = (width - LEFT_GUTTER + offsetUnits) / units; - - // small ticks - for (i = 0; i < count; i++) { - if (i % mul === 0) continue; - ctx.strokeStyle = Theme.c; - ctx.beginPath(); - x = i * units + LEFT_GUTTER - offsetUnits; - ctx.moveTo(x, MARKER_TRACK_HEIGHT - 0); - ctx.lineTo(x, MARKER_TRACK_HEIGHT - 10); - ctx.stroke(); - } - - // Encapsulate a scroll rect for the layers - ctx.save(); - ctx.translate(0, MARKER_TRACK_HEIGHT); - ctx.beginPath(); - ctx.rect(0, 0, Settings.width, SCROLL_HEIGHT); - ctx.translate(-scrollLeft, -scrollTop); - ctx.clip(); - drawLayerContents(); - ctx.restore(); - - - drawScroller(); - - // Current Marker / Cursor - ctx.strokeStyle = 'red'; // Theme.c - x = (current_frame - frame_start) * time_scale + LEFT_GUTTER; - - var txt = utils.format_friendly_seconds(current_frame); - var textWidth = ctx.measureText(txt).width; - - var base_line = MARKER_TRACK_HEIGHT- 5, half_rect = textWidth / 2 + 4; - - ctx.beginPath(); - ctx.moveTo(x, base_line); - ctx.lineTo(x, height); - ctx.stroke(); - - ctx.fillStyle = 'red'; // black - ctx.textAlign = 'center'; - ctx.beginPath(); - ctx.moveTo(x, base_line + 5); - ctx.lineTo(x + 5, base_line); - ctx.lineTo(x + half_rect, base_line); - ctx.lineTo(x + half_rect, base_line - 14); - ctx.lineTo(x - half_rect, base_line - 14); - ctx.lineTo(x - half_rect, base_line); - ctx.lineTo(x - 5, base_line); - ctx.closePath(); - ctx.fill(); - - ctx.fillStyle = 'white'; - ctx.fillText(txt, x, base_line - 4); - - ctx.restore(); - - needsRepaint = false; - - } - - function y_to_track(y) { - if (y - MARKER_TRACK_HEIGHT < 0) return -1; - return (y - MARKER_TRACK_HEIGHT + scrollTop) / LINE_HEIGHT | 0; - } - - - function x_to_time(x) { - - var units = time_scale / subd3; - - // return frame_start + (x - LEFT_GUTTER) / time_scale; - - return frame_start + ((x - LEFT_GUTTER) / units | 0) / subd3; - } - - function time_to_x(s) { - var ds = s - frame_start; - ds *= time_scale; - ds += LEFT_GUTTER; - - return ds; - } - - - var me = this; - this.repaint = repaint; - this._paint = _paint; - - repaint(); - - var mousedown = false, selection = false; - - var dragObject; - var canvasBounds; - - canvas.addEventListener('touchstart', function(ev) { - e = ev.touches[0]; - pointerStart(e); - ev.preventDefault(); - }); - - canvas.addEventListener('touchmove', function(ev) { - e = ev.touches[0]; - onMouseMove(e); - }); - - function pointerStart(e) { - canvasBounds = canvas.getBoundingClientRect(); - var mx = e.clientX - canvasBounds.left , my = e.clientY - canvasBounds.top; - - if (my <= TOP_SCROLL_TRACK) return false; - - mousedown = true; - - var track = y_to_track(my); - var s = x_to_time(mx); - - dragObject = null; - - console.log('track', track, 't', s, layers[track]); - - if (layers[track]) { - var tmp = utils.findTimeinLayer(layers[track], s); - var tmp2 = utils.timeAtLayer(layers[track], s); - - console.log('drag start', tmp, tmp2); - - if (typeof(tmp) !== 'number') dragObject = tmp; - } - - onPointerDrag(mx, my); - return true; - } - - canvas.addEventListener('mousedown', function(e) { - - var hit = pointerStart(e); - if (!hit) return; - - - e.preventDefault(); - - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - }); - - canvas.addEventListener('dblclick', function(e) { - console.log('dblclick!'); - // canvasBounds = canvas.getBoundingClientRect(); - var mx = e.clientX - canvasBounds.left , my = e.clientY - canvasBounds.top; - - - var track = y_to_track(my); - var s = x_to_time(mx); - - - dispatcher.fire('keyframe', layers[track], current_frame); - - }); - - - var draggingx; - handleDrag(canvas, function down(e) { - draggingx = scroller.left; - }, function move(e) { - data.get('ui:scrollTime').value = Math.max(0, (draggingx + e.dx) / scroller.k); - repaint(); - }, function up() { - }, function(e) { - var bar = e.offsetx >= scroller.left && e.offsetx <= scroller.left + scroller.grip_length; - return e.offsety <= TOP_SCROLL_TRACK && bar; - } - ); - - function onMouseUp(e) { - // canvasBounds = canvas.getBoundingClientRect(); - var mx = e.clientX - canvasBounds.left , my = e.clientY - canvasBounds.top; - - onPointerDrag(mx, my); - if (dragObject) { - dispatcher.fire('keyframe.move'); - } - mousedown = false; - dragObject = null; - - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - } - - function onMouseMove(e) { - // canvasBounds = canvas.getBoundingClientRect(); - var mx = e.clientX - canvasBounds.left , my = e.clientY - canvasBounds.top; - - // offsetY d.getBoundingClientRect() d.offsetLeft - // console.log('...', mx, my, div.offsetLeft); - onPointerDrag(mx, my); - - } - - function onPointerDrag(x, y) { - - if (x < LEFT_GUTTER) x = LEFT_GUTTER; - if (x > width) return; - current = x; // <---- ??!??!! - - var s = x_to_time(x); - if (dragObject) { - dragObject.object.time = s; // hack! // needs reorder upon mouse up! - } else { - - } - - // Move the cursor; - dispatcher.fire('time.update', s); - - // console.log(s, format_friendly_seconds(s), this); - } - - this.setState = function(state) { - layers = state.value; - repaint(); - }; - -} - -module.exports = TimelinePanel; -},{"./handle_drag":"/Users/joshua/dev/gits/timeliner/src/handle_drag.js","./settings":"/Users/joshua/dev/gits/timeliner/src/settings.js","./theme":"/Users/joshua/dev/gits/timeliner/src/theme.js","./tween":"/Users/joshua/dev/gits/timeliner/src/tween.js","./utils":"/Users/joshua/dev/gits/timeliner/src/utils.js"}],"/Users/joshua/dev/gits/timeliner/src/timeliner.js":[function(require,module,exports){ -/* - * @author Joshua Koo http://joshuakoo.com - */ - -var undo = require('./undo'), - Dispatcher = require('./dispatcher'), - Theme = require('./theme'), - UndoManager = undo.UndoManager, - UndoState = undo.UndoState, - Settings = require('./settings'), - utils = require('./utils'), - LayerCabinet = require('./layer_cabinet'), - TimelinePanel = require('./timeline_panel'), - package_json = require('../package.json'), - IconButton = require('./icon_button'), - style = utils.style, - saveToFile = utils.saveToFile, - openAs = utils.openAs, - STORAGE_PREFIX = utils.STORAGE_PREFIX, - ScrollBar = require('./ui/scrollbar'), - DataStore = require('./datastore') - ; - -var Z_INDEX = 999; - -function LayerProp(name) { - this.name = name; - this.values = []; - - this._value = 0; - - this._color = '#' + (Math.random() * 0xffffff | 0).toString(16); - /* - this.max - this.min - this.step - */ -} - -function Timeliner(target) { - // Aka Layer Manager / Controller - - // Should persist current time too. - var data = new DataStore(); - var layer_store = data.get('layers'); - var layers = layer_store.value; - - window._data = data; - - var dispatcher = new Dispatcher(); - - var timeline = new TimelinePanel(data, dispatcher); - var layer_panel = new LayerCabinet(data, dispatcher); - - var undo_manager = new UndoManager(dispatcher); - - setTimeout(function() { - // hack! - undo_manager.save(new UndoState(data, 'Loaded'), true); - }); - - dispatcher.on('keyframe', function(layer, value) { - var index = layers.indexOf(layer); - - var t = data.get('ui:currentTime').value; - var v = utils.findTimeinLayer(layer, t); - - // console.log(v, '...keyframe index', index, utils.format_friendly_seconds(t), typeof(v)); - // console.log('layer', layer, value); - - if (typeof(v) === 'number') { - layer.values.splice(v, 0, { - time: t, - value: value, - _color: '#' + (Math.random() * 0xffffff | 0).toString(16) - }); - - undo_manager.save(new UndoState(data, 'Add Keyframe')); - } else { - console.log('remove from index', v); - layer.values.splice(v.index, 1); - - undo_manager.save(new UndoState(data, 'Remove Keyframe')); - } - - repaintAll(); - - }); - - dispatcher.on('keyframe.move', function(layer, value) { - undo_manager.save(new UndoState(data, 'Move Keyframe')); - }); - - // dispatcher.fire('value.change', layer, me.value); - dispatcher.on('value.change', function(layer, value, dont_save) { - var t = data.get('ui:currentTime').value; - - var v = utils.findTimeinLayer(layer, t); - - console.log(v, 'value.change', layer, value, utils.format_friendly_seconds(t), typeof(v)); - if (typeof(v) === 'number') { - layer.values.splice(v, 0, { - time: t, - value: value, - _color: '#' + (Math.random() * 0xffffff | 0).toString(16) - }); - if (!dont_save) undo_manager.save(new UndoState(data, 'Add value')); - } else { - v.object.value = value; - if (!dont_save) undo_manager.save(new UndoState(data, 'Update value')); - } - - repaintAll(); - }); - - dispatcher.on('ease', function(layer, ease_type) { - var t = data.get('ui:currentTime').value; - var v = utils.timeAtLayer(layer, t); - // console.log('Ease Change > ', layer, value, v); - if (v && v.entry) { - v.entry.tween = ease_type; - } - - undo_manager.save(new UndoState(data, 'Add Ease')); - - repaintAll(); - }); - - var start_play = null, - played_from = 0; // requires some more tweaking - - dispatcher.on('controls.toggle_play', function() { - if (start_play) { - pausePlaying(); - } else { - startPlaying(); - } - }); - - dispatcher.on('controls.restart_play', function() { - if (!start_play) { - startPlaying(); - } - - setCurrentTime(played_from); - }); - - dispatcher.on('controls.play', startPlaying); - dispatcher.on('controls.pause', pausePlaying); - - function startPlaying() { - // played_from = timeline.current_frame; - start_play = performance.now() - data.get('ui:currentTime').value * 1000; - layer_panel.setControlStatus(true); - // dispatcher.fire('controls.status', true); - } - - function pausePlaying() { - start_play = null; - layer_panel.setControlStatus(false); - // dispatcher.fire('controls.status', false); - } - - dispatcher.on('controls.stop', function() { - if (start_play !== null) pausePlaying(); - setCurrentTime(0); - }); - - var currentTimeStore = data.get('ui:currentTime'); - dispatcher.on('time.update', setCurrentTime); - - function setCurrentTime(value) { - currentTimeStore.value = value; - - if (start_play) start_play = performance.now() - value * 1000; - repaintAll(); - // layer_panel.repaint(s); - } - - dispatcher.on('target.notify', function(name, value) { - if (target) target[name] = value; - }); - - dispatcher.on('update.scale', function(v) { - console.log('range', v); - data.get('ui:timeScale').value = v; - // timeline.setTimeScale(v); - timeline.repaint(); - }); - - // handle undo / redo - dispatcher.on('controls.undo', function() { - var history = undo_manager.undo(); - data.setJSONString(history.state); - - updateState(); - }); - - dispatcher.on('controls.redo', function() { - var history = undo_manager.redo(); - data.setJSONString(history.state); - - updateState(); - }); - - /* - Paint Routines - */ - - function paint() { - requestAnimationFrame(paint); - - if (start_play) { - var t = (performance.now() - start_play) / 1000; - setCurrentTime(t); - - - if (t > data.get('ui:totalTime').value) { - // simple loop - start_play = performance.now(); - } - } - - if (needsResize) { - div.style.width = width + 'px'; - div.style.height = height + 'px'; - - restyle(layer_panel.dom, timeline.dom); - - timeline.resize(); - repaintAll(); - needsResize = false; - - dispatcher.fire('resize'); - } - - timeline._paint(); - } - - paint(); - - /* - End Paint Routines - */ - - function save(name) { - if (!name) name = 'autosave'; - - var json = data.getJSONString(); - - try { - localStorage[STORAGE_PREFIX + name] = json; - dispatcher.fire('save:done'); - } catch (e) { - console.log('Cannot save', name, json); - } - } - - function saveAs(name) { - if (!name) name = data.get('name').value; - name = prompt('Pick a name to save to (localStorage)', name); - if (name) { - data.data.name = name; - save(name); - } - } - - function saveSimply() { - var name = data.get('name').value; - if (name) { - save(name); - } else { - saveAs(name); - } - } - - function exportJSON() { - var json = data.getJSONString(); - var ret = prompt('Hit OK to download otherwise Copy and Paste JSON', json); - if (!ret) return; - - // make json downloadable - json = data.getJSONString('\t'); - var fileName = 'timeliner-test' + '.json'; - - saveToFile(json, fileName); - } - - function loadJSONString(o) { - // should catch and check errors here - var json = JSON.parse(o); - load(json); - } - - function load(o) { - data.setJSON(o); - // - if (data.getValue('ui') === undefined) { - data.setValue('ui', { - currentTime: 0, - totalTime: 20, - scrollTime: 0, - timeScale: 40 - }); - } - - undo_manager.clear(); - undo_manager.save(new UndoState(data, 'Loaded'), true); - - updateState(); - } - - function updateState() { - layers = layer_store.value; // FIXME: support Arrays - layer_panel.setState(layer_store); - timeline.setState(layer_store); - - repaintAll(); - } - - function repaintAll() { - var content_height = layers.length * Settings.LINE_HEIGHT; - scrollbar.setLength(Settings.TIMELINE_SCROLL_HEIGHT / content_height); - - layer_panel.repaint(); - timeline.repaint(); - } - - function promptImport() { - var json = prompt('Paste JSON in here to Load'); - if (!json) return; - console.log('Loading.. ', json); - loadJSONString(json); - } - - function open(title) { - if (title) { - loadJSONString(localStorage[STORAGE_PREFIX + title]); - } - } - - dispatcher.on('import', function() { - promptImport(); - }.bind(this)); - - dispatcher.on('new', function() { - data.blank(); - updateState(); - }); - - dispatcher.on('openfile', function() { - openAs(function(data) { - // console.log('loaded ' + data); - loadJSONString(data); - }, div); - }); - - dispatcher.on('open', open); - dispatcher.on('export', exportJSON); - - dispatcher.on('save', saveSimply); - dispatcher.on('save_as', saveAs); - - // Expose API - this.save = save; - this.load = load; - - /* - Start DOM Stuff (should separate file) - */ - - var div = document.createElement('div'); - div.style.cssText = 'position: absolute;'; - div.style.top = '16px'; - - var pane = document.createElement('div'); - - style(pane, { - position: 'fixed', - margin: 0, - padding: 0, - fontFamily: 'monospace', - zIndex: Z_INDEX, - border: '2px solid ' + Theme.a, - fontSize: '12px', - color: Theme.d, - overflow: 'hidden', - top: '20px', - left: '20px' - }); - - pane.style.backgroundColor = Theme.a; - - var pane_title = document.createElement('div'); - - var title_bar = document.createElement('span'); - pane_title.appendChild(title_bar); - - - var top_right_bar = document.createElement('span'); - top_right_bar.style.float = 'right'; - pane_title.appendChild(top_right_bar); - - // resize minimize - // var resize_small = new IconButton(10, 'resize_small', 'minimize', dispatcher); - // top_right_bar.appendChild(resize_small.dom); - - // resize full - var resize_full = new IconButton(10, 'resize_full', 'maximize', dispatcher); - top_right_bar.appendChild(resize_full.dom); - - style(pane_title, { - position: 'absolute', - width: '100%', - textAlign: 'left', - top: '0px', - height: '15px', - borderBottom: '1px solid ' + Theme.b, - overflow: 'hidden' - }); - - title_bar.innerHTML = 'Timeliner ' + package_json.version; - - var pane_status = document.createElement('div'); - - style(pane_status, { - position: 'absolute', - height: '15px', - bottom: '0', - width: '100%', - // padding: '2px', - background: Theme.a, - borderTop: '1px solid ' + Theme.b, - fontSize: '11px' - }); - - pane.appendChild(div); - pane.appendChild(pane_status); - pane.appendChild(pane_title); - - var button_styles = { - padding: '2px' - }; - - var label_status = document.createElement('span'); - label_status.textContent = 'hello!'; - label_status.style.marginLeft = '10px'; - - this.setStatus = function(text) { - label_status.textContent = text; - }; - - dispatcher.on('state:save', function(description) { - dispatcher.fire('status', description); - save('autosave'); - }); - - dispatcher.on('status', this.setStatus); - - // var button_save = document.createElement('button'); - // style(button_save, button_styles); - // button_save.textContent = 'Save'; - // button_save.onclick = function() { - // save(); - // }; - - // var button_load = document.createElement('button'); - // style(button_load, button_styles); - // button_load.textContent = 'Import'; - // button_load.onclick = this.promptLoad; - - // var button_open = document.createElement('button'); - // style(button_open, button_styles); - // button_open.textContent = 'Open'; - // button_open.onclick = this.promptOpen; - - - // bottom_right.appendChild(button_load); - // bottom_right.appendChild(button_save); - // bottom_right.appendChild(button_open); - - var bottom_right = document.createElement('span'); - bottom_right.style.float = 'right'; - - pane_status.appendChild(label_status); - pane_status.appendChild(bottom_right); - - /**/ - // zoom in - var zoom_in = new IconButton(12, 'zoom_in', 'zoom in', dispatcher); - // zoom out - var zoom_out = new IconButton(12, 'zoom_out', 'zoom out', dispatcher); - // settings - var cog = new IconButton(12, 'cog', 'settings', dispatcher); - - // bottom_right.appendChild(zoom_in.dom); - // bottom_right.appendChild(zoom_out.dom); - // bottom_right.appendChild(cog.dom); - - // add layer - var plus = new IconButton(12, 'plus', 'New Layer', dispatcher); - plus.onClick(function() { - var name = prompt('Layer name?'); - addLayer(name); - - undo_manager.save(new UndoState(data, 'Layer added')); - - repaintAll(); - }); - bottom_right.appendChild(plus.dom); - - - // trash - var trash = new IconButton(12, 'trash', 'Delete save', dispatcher); - trash.onClick(function() { - var name = data.get('name').value; - if (name && localStorage[STORAGE_PREFIX + name]) { - var ok = confirm('Are you sure you wish to delete ' + name + '?'); - if (ok) { - delete localStorage[STORAGE_PREFIX + name]; - dispatcher.fire('status', name + ' deleted'); - dispatcher.fire('save:done'); - } - } - }); - bottom_right.appendChild(trash.dom); - - - // pane_status.appendChild(document.createTextNode(' | TODO ')); - - /* - End DOM Stuff - */ - - var ghostpane = document.createElement('div'); - ghostpane.id = 'ghostpane'; - style(ghostpane, { - background: '#999', - opacity: 0.2, - position: 'fixed', - margin: 0, - padding: 0, - zIndex: (Z_INDEX - 1), - // transition: 'all 0.25s ease-in-out', - transitionProperty: 'top, left, width, height, opacity', - transitionDuration: '0.25s', - transitionTimingFunction: 'ease-in-out' - }); - - document.body.appendChild(pane); - document.body.appendChild(ghostpane); - - div.appendChild(layer_panel.dom); - div.appendChild(timeline.dom); - - var scrollbar = new ScrollBar(200, 10); - div.appendChild(scrollbar.dom); - - // percentages - scrollbar.onScroll.do(function(type, scrollTo) { - switch(type) { - case 'scrollto': - layer_panel.scrollTo(scrollTo); - timeline.scrollTo(scrollTo); - break; - // case 'pageup': - // scrollTop -= pageOffset; - // me.draw(); - // me.updateScrollbar(); - // break; - // case 'pagedown': - // scrollTop += pageOffset; - // me.draw(); - // me.updateScrollbar(); - // break; - } - }); - - - - // document.addEventListener('keypress', function(e) { - // console.log('kp', e); - // }); - // document.addEventListener('keyup', function(e) { - // if (undo) console.log('UNDO'); - - // console.log('kd', e); - // }); - - // TODO: Keyboard Shortcuts - // Esc - Stop and review to last played from / to the start? - // Space - play / pause from current position - // Enter - play all - // k - keyframe - - document.addEventListener('keydown', function(e) { - var play = e.keyCode == 32; // space - var enter = e.keyCode == 13; // - var undo = e.metaKey && e.keyCode == 91 && !e.shiftKey; - - var active = document.activeElement; - // console.log( active.nodeName ); - - if (active.nodeName.match(/(INPUT|BUTTON|SELECT)/)) { - active.blur(); - } - - if (play) { - dispatcher.fire('controls.toggle_play'); - } - else if (enter) { - // FIXME: Return should play from the start or last played from? - dispatcher.fire('controls.restart_play'); - // dispatcher.fire('controls.undo'); - } - else if (e.keyCode == 27) { - // Esc = stop. FIXME: should rewind head to last played from or Last pointed from? - dispatcher.fire('controls.pause'); - } - else console.log(e.keyCode); - }); - - var needsResize = true; - - function resize(width, height) { - // data.get('ui:bounds').value = { - // width: width, - // height: height - // }; - // TODO: remove ugly hardcodes - width -= 4; - height -= 32; - - Settings.width = width - Settings.LEFT_PANE_WIDTH; - Settings.height = height; - - Settings.TIMELINE_SCROLL_HEIGHT = height - Settings.MARKER_TRACK_HEIGHT; - var scrollable_height = Settings.TIMELINE_SCROLL_HEIGHT; - - scrollbar.setHeight(scrollable_height - 2); - // scrollbar.setThumb - - style(scrollbar.dom, { - top: Settings.MARKER_TRACK_HEIGHT + 'px', - left: (width - 16) + 'px', - }); - - needsResize = true; - } - - function restyle(left, right) { - left.style.cssText = 'position: absolute; left: 0px; top: 0px; height: ' + Settings.height + 'px;'; - style(left, { - // background: Theme.a, - overflow: 'hidden' - }); - left.style.width = Settings.LEFT_PANE_WIDTH + 'px'; - - // right.style.cssText = 'position: absolute; top: 0px;'; - right.style.position = 'absolute'; - right.style.top = '0px'; - right.style.left = Settings.LEFT_PANE_WIDTH + 'px'; - } - - function addLayer(name) { - var layer = new LayerProp(name); - - layers = layer_store.value; - layers.push(layer); - - layer_panel.setState(layer_store); - } - - this.addLayer = addLayer; - - this.setTarget = function(t) { - timeline = t; - }; - - function getValueRanges(ranges, interval) { - interval = interval ? interval : 0.15; - ranges = ranges ? ranges : 2; - - // not optimized! - var t = data.get('ui:currentTime').value; - - var values = []; - - for (var u = -ranges; u <= ranges; u++) { - // if (u == 0) continue; - var o = {}; - - for (var l = 0; l < layers.length; l++) { - var layer = layers[l]; - var m = utils.timeAtLayer(layer, t + u * interval); - o[layer.name] = m.value; - } - - values.push(o); - - } - - return values; - } - - this.getValues = getValueRanges; - - (function DockingWindow() { - "use strict"; - - // Minimum resizable area - var minWidth = 100; - var minHeight = 80; - - // Thresholds - var FULLSCREEN_MARGINS = 2; - var SNAP_MARGINS = 8; - var MARGINS = 2; - - // End of what's configurable. - - var clicked = null; - var onRightEdge, onBottomEdge, onLeftEdge, onTopEdge; - - var preSnapped; - - var b, x, y; - - var redraw = false; - - // var pane = document.getElementById('pane'); - // var ghostpane = document.getElementById('ghostpane'); - - var mouseOnTitle = false; - var snapType; - - pane_title.addEventListener('mouseover', function() { - mouseOnTitle = true; - }); - - pane_title.addEventListener('mouseout', function() { - mouseOnTitle = false; - }); - - resize_full.onClick(function() { - // TOOD toggle back to restored size - if (!preSnapped) preSnapped = { - width: b.width, - height: b.height - }; - - snapType = 'full-screen'; - resizeEdges(); - }); - - // pane_status.addEventListener('mouseover', function() { - // mouseOnTitle = true; - // }); - - // pane_status.addEventListener('mouseout', function() { - // mouseOnTitle = false; - // }); - - window.addEventListener('resize', function() { - if (snapType) - resizeEdges(); - else - needsResize = true; - }); - - // utils - function setBounds(element, x, y, w, h) { - element.style.left = x + 'px'; - element.style.top = y + 'px'; - element.style.width = w + 'px'; - element.style.height = h + 'px'; - - if (element === pane) { - resize(w, h); - } - } - - function hintHide() { - setBounds(ghostpane, b.left, b.top, b.width, b.height); - ghostpane.style.opacity = 0; - } - - setBounds(pane, 0, 0, Settings.width, Settings.height); - setBounds(ghostpane, 0, 0, Settings.width, Settings.height); - - // Mouse events - pane.addEventListener('mousedown', onMouseDown); - document.addEventListener('mousemove', onMove); - document.addEventListener('mouseup', onUp); - - // Touch events - pane.addEventListener('touchstart', onTouchDown); - document.addEventListener('touchmove', onTouchMove); - document.addEventListener('touchend', onTouchEnd); - - - function onTouchDown(e) { - onDown(e.touches[0]); - e.preventDefault(); - } - - function onTouchMove(e) { - onMove(e.touches[0]); - } - - function onTouchEnd(e) { - if (e.touches.length == 0) onUp(e.changedTouches[0]); - } - - function onMouseDown(e) { - onDown(e); - } - - function onDown(e) { - calc(e); - - var isResizing = onRightEdge || onBottomEdge || onTopEdge || onLeftEdge; - var isMoving = !isResizing && canMove(); - - clicked = { - x: x, - y: y, - cx: e.clientX, - cy: e.clientY, - w: b.width, - h: b.height, - isResizing: isResizing, - isMoving: isMoving, - onTopEdge: onTopEdge, - onLeftEdge: onLeftEdge, - onRightEdge: onRightEdge, - onBottomEdge: onBottomEdge - }; - - if (isResizing || isMoving) { - e.preventDefault(); - e.stopPropagation(); - } - } - - function canMove() { - return mouseOnTitle; - // return x > 0 && x < b.width && y > 0 && y < b.height - // && y < 18; - } - - function calc(e) { - b = pane.getBoundingClientRect(); - x = e.clientX - b.left; - y = e.clientY - b.top; - - onTopEdge = y < MARGINS; - onLeftEdge = x < MARGINS; - onRightEdge = x >= b.width - MARGINS; - onBottomEdge = y >= b.height - MARGINS; - } - - var e; // current mousemove event - - function onMove(ee) { - e = ee; - calc(e); - - redraw = true; - } - - function animate() { - - requestAnimationFrame(animate); - - if (!redraw) return; - - redraw = false; - - if (clicked && clicked.isResizing) { - - if (clicked.onRightEdge) pane.style.width = Math.max(x, minWidth) + 'px'; - if (clicked.onBottomEdge) pane.style.height = Math.max(y, minHeight) + 'px'; - - if (clicked.onLeftEdge) { - var currentWidth = Math.max(clicked.cx - e.clientX + clicked.w, minWidth); - if (currentWidth > minWidth) { - pane.style.width = currentWidth + 'px'; - pane.style.left = e.clientX + 'px'; - } - } - - if (clicked.onTopEdge) { - var currentHeight = Math.max(clicked.cy - e.clientY + clicked.h, minHeight); - if (currentHeight > minHeight) { - pane.style.height = currentHeight + 'px'; - pane.style.top = e.clientY + 'px'; - } - } - - hintHide(); - - resize(b.width, b.height); - - return; - } - - if (clicked && clicked.isMoving) { - - switch(checks()) { - case 'full-screen': - setBounds(ghostpane, 0, 0, window.innerWidth, window.innerHeight); - ghostpane.style.opacity = 0.2; - break; - case 'snap-top-edge': - setBounds(ghostpane, 0, 0, window.innerWidth, window.innerHeight / 2); - ghostpane.style.opacity = 0.2; - break; - case 'snap-left-edge': - setBounds(ghostpane, 0, 0, window.innerWidth / 2, window.innerHeight); - ghostpane.style.opacity = 0.2; - break; - case 'snap-right-edge': - setBounds(ghostpane, window.innerWidth / 2, 0, window.innerWidth / 2, window.innerHeight); - ghostpane.style.opacity = 0.2; - break; - case 'snap-bottom-edge': - setBounds(ghostpane, 0, window.innerHeight / 2, window.innerWidth, window.innerHeight / 2); - ghostpane.style.opacity = 0.2; - break; - default: - hintHide(); - } - - if (preSnapped) { - setBounds(pane, - e.clientX - preSnapped.width / 2, - e.clientY - Math.min(clicked.y, preSnapped.height), - preSnapped.width, - preSnapped.height - ); - return; - } - - // moving - pane.style.top = (e.clientY - clicked.y) + 'px'; - pane.style.left = (e.clientX - clicked.x) + 'px'; - - return; - } - - // This code executes when mouse moves without clicking - - // style cursor - if (onRightEdge && onBottomEdge || onLeftEdge && onTopEdge) { - pane.style.cursor = 'nwse-resize'; - } else if (onRightEdge && onTopEdge || onBottomEdge && onLeftEdge) { - pane.style.cursor = 'nesw-resize'; - } else if (onRightEdge || onLeftEdge) { - pane.style.cursor = 'ew-resize'; - } else if (onBottomEdge || onTopEdge) { - pane.style.cursor = 'ns-resize'; - } else if (canMove()) { - pane.style.cursor = 'move'; - } else { - pane.style.cursor = 'default'; - } - } - - function checks() { - /* - var rightScreenEdge, bottomScreenEdge; - - rightScreenEdge = window.innerWidth - MARGINS; - bottomScreenEdge = window.innerHeight - MARGINS; - - // Edge Checkings - // hintFull(); - if (b.top < FULLSCREEN_MARGINS || b.left < FULLSCREEN_MARGINS || b.right > window.innerWidth - FULLSCREEN_MARGINS || b.bottom > window.innerHeight - FULLSCREEN_MARGINS) - return 'full-screen'; - - // hintTop(); - if (b.top < MARGINS) return 'snap-top-edge'; - - // hintLeft(); - if (b.left < MARGINS) return 'snap-left-edge'; - - // hintRight(); - if (b.right > rightScreenEdge) return 'snap-right-edge'; - - // hintBottom(); - if (b.bottom > bottomScreenEdge) return 'snap-bottom-edge'; - */ - - if (e.clientY < FULLSCREEN_MARGINS) return 'full-screen'; - - if (e.clientY < SNAP_MARGINS) return 'snap-top-edge'; - - // hintLeft(); - if (e.clientX < SNAP_MARGINS) return 'snap-left-edge'; - - // hintRight(); - if (window.innerWidth - e.clientX < SNAP_MARGINS) return 'snap-right-edge'; - - // hintBottom(); - if (window.innerHeight- e.clientY < SNAP_MARGINS) return 'snap-bottom-edge'; - - } - - animate(); - - function resizeEdges() { - switch(snapType) { - case 'full-screen': - // hintFull(); - setBounds(pane, 0, 0, window.innerWidth, window.innerHeight); - break; - case 'snap-top-edge': - // hintTop(); - setBounds(pane, 0, 0, window.innerWidth, window.innerHeight / 2); - break; - case 'snap-left-edge': - // hintLeft(); - setBounds(pane, 0, 0, window.innerWidth / 2, window.innerHeight); - break; - case 'snap-right-edge': - setBounds(pane, window.innerWidth / 2, 0, window.innerWidth / 2, window.innerHeight); - break; - case 'snap-bottom-edge': - setBounds(pane, 0, window.innerHeight / 2, window.innerWidth, window.innerHeight / 2); - break; - } - } - - function onUp(e) { - calc(e); - - if (clicked && clicked.isMoving) { - // Snap - snapType = checks(); - if (snapType) { - preSnapped = { - width: b.width, - height: b.height - }; - resizeEdges(); - } else { - preSnapped = null; - } - - hintHide(); - - } - - clicked = null; - - } - })(); - -} - -window.Timeliner = Timeliner; -},{"../package.json":"/Users/joshua/dev/gits/timeliner/package.json","./datastore":"/Users/joshua/dev/gits/timeliner/src/datastore.js","./dispatcher":"/Users/joshua/dev/gits/timeliner/src/dispatcher.js","./icon_button":"/Users/joshua/dev/gits/timeliner/src/icon_button.js","./layer_cabinet":"/Users/joshua/dev/gits/timeliner/src/layer_cabinet.js","./settings":"/Users/joshua/dev/gits/timeliner/src/settings.js","./theme":"/Users/joshua/dev/gits/timeliner/src/theme.js","./timeline_panel":"/Users/joshua/dev/gits/timeliner/src/timeline_panel.js","./ui/scrollbar":"/Users/joshua/dev/gits/timeliner/src/ui/scrollbar.js","./undo":"/Users/joshua/dev/gits/timeliner/src/undo.js","./utils":"/Users/joshua/dev/gits/timeliner/src/utils.js"}],"/Users/joshua/dev/gits/timeliner/src/tween.js":[function(require,module,exports){ -/**************************/ -// Tweens -/**************************/ - -var Tweens = { - none: function(k) { - return 0; - }, - linear: function(k) { - return k; - }, - quadEaseIn: function(k) { - return k * k; - }, - quadEaseOut: function(k) { - return - k * ( k - 2 ); - }, - quadEaseInOut: function(k) { - if ( ( k *= 2 ) < 1 ) return 0.5 * k * k; - return - 0.5 * ( --k * ( k - 2 ) - 1 ); - } -}; - -module.exports = Tweens; -},{}],"/Users/joshua/dev/gits/timeliner/src/ui/layer_view.js":[function(require,module,exports){ -var - Theme = require('../theme'), - NumberUI = require('./number'), - Tweens = require('../tween'), - Settings = require('../settings'), - utils = require('../utils') -; - -// TODO - tagged by index instead, work off layers. - -function LayerView(layer, dispatcher) { - var dom = document.createElement('div'); - - var label = document.createElement('span'); - - label.style.cssText = 'font-size: 12px; padding: 4px;'; - - var dropdown = document.createElement('select'); - var option; - dropdown.style.cssText = 'font-size: 10px; width: 60px; margin: 0; float: right; text-align: right;'; - - for (var k in Tweens) { - option = document.createElement('option'); - option.text = k; - dropdown.appendChild(option); - } - - dropdown.addEventListener('change', function(e) { - dispatcher.fire('ease', layer, dropdown.value); - }); - - var keyframe_button = document.createElement('button'); - keyframe_button.innerHTML = '◈'; // '♦' ◇ 9679 9670 9672 - keyframe_button.style.cssText = 'background: none; font-size: 12px; padding: 0px; font-family: monospace; float: right; width: 20px; border-style:none; outline: none;'; // border-style:inset; - - keyframe_button.addEventListener('click', function(e) { - console.log('clicked:keyframing...', state.get('_value').value); - dispatcher.fire('keyframe', layer, state.get('_value').value); - }); - - /* - // Prev Keyframe - var button = document.createElement('button'); - button.textContent = '<'; - button.style.cssText = 'font-size: 12px; padding: 1px; '; - dom.appendChild(button); - - // Next Keyframe - button = document.createElement('button'); - button.textContent = '>'; - button.style.cssText = 'font-size: 12px; padding: 1px; '; - dom.appendChild(button); - - // Mute - button = document.createElement('button'); - button.textContent = 'M'; - button.style.cssText = 'font-size: 12px; padding: 1px; '; - dom.appendChild(button); - - // Solo - button = document.createElement('button'); - button.textContent = 'S'; - button.style.cssText = 'font-size: 12px; padding: 1px; '; - dom.appendChild(button); - */ - - var number = new NumberUI(layer, dispatcher); - - number.onChange.do(function(value, done) { - state.get('_value').value = value; - dispatcher.fire('value.change', layer, value, done); - }); - - utils.style(number.dom, { - float: 'right' - }); - - dom.appendChild(label); - dom.appendChild(keyframe_button); - dom.appendChild(number.dom); - dom.appendChild(dropdown); - - dom.style.cssText = 'margin: 0px; border-bottom:1px solid ' + Theme.b + '; top: 0; left: 0; height: ' + (Settings.LINE_HEIGHT - 1 ) + 'px; color: ' + Theme.c; - this.dom = dom; - - this.repaint = repaint; - var state; - - this.setState = function(l, s) { - layer = l; - state = s; - - var tmp_value = state.get('_value'); - if (tmp_value.value === undefined) { - tmp_value.value = 0; - } - - number.setValue(tmp_value.value); - label.textContent = state.get('name').value; - - repaint(); - }; - - function repaint(s) { - - dropdown.style.opacity = 0; - dropdown.disabled = true; - keyframe_button.style.color = Theme.b; - // keyframe_button.disabled = false; - // keyframe_button.style.borderStyle = 'solid'; - - var tween = null; - var o = utils.timeAtLayer(layer, s); - - if (!o) return; - - if (o.can_tween) { - dropdown.style.opacity = 1; - dropdown.disabled = false; - // if (o.tween) - dropdown.value = o.tween ? o.tween : 'none'; - if (dropdown.value === 'none') dropdown.style.opacity = 0.5; - } - - if (o.keyframe) { - keyframe_button.style.color = Theme.c; - // keyframe_button.disabled = true; - // keyframe_button.style.borderStyle = 'inset'; - } - - state.get('_value').value = o.value; - number.setValue(o.value); - number.paint(); - - dispatcher.fire('target.notify', layer.name, o.value); - } - -} - -module.exports = LayerView; - -},{"../settings":"/Users/joshua/dev/gits/timeliner/src/settings.js","../theme":"/Users/joshua/dev/gits/timeliner/src/theme.js","../tween":"/Users/joshua/dev/gits/timeliner/src/tween.js","../utils":"/Users/joshua/dev/gits/timeliner/src/utils.js","./number":"/Users/joshua/dev/gits/timeliner/src/ui/number.js"}],"/Users/joshua/dev/gits/timeliner/src/ui/number.js":[function(require,module,exports){ -var Theme = require('../theme'), - Do = require('do.js'), - handleDrag = require('../handle_drag'), - style = require('../utils').style - ; - -/**************************/ -// NumberUI -/**************************/ - -function NumberUI(config) { - config = config || {}; - var min = config.min === undefined ? -Infinity : config.min; - var step = config.step || 0.1; - var precision = config.precision || 3; - // Range - // Max - - var span = document.createElement('input'); - // span.type = 'number'; // spinner - - style(span, { - textAlign: 'center', - fontSize: '10px', - padding: '1px', - cursor: 'ns-resize', - width: '40px', - margin: 0, - marginRight: '10px', - appearance: 'none', - outline: 'none', - border: 0, - background: 'none', - borderBottom: '1px dotted '+ Theme.c, - color: Theme.c - }); - - var me = this; - var state, value = 0, unchanged_value; - - this.onChange = new Do(); - - span.addEventListener('change', function(e) { - console.log('input changed', span.value); - value = parseFloat(span.value, 10); - - fireChange(); - }); - - handleDrag(span, onDown, onMove, onUp); - - function onUp(e) { - if (e.moved) fireChange(); - else { - // single click - span.focus(); - } - } - - function onMove(e) { - var dx = e.dx; - var dy = e.dy; - - var stepping = 1 * step; - // value = unchanged_value + dx * 0.000001 + dy * -10 * 0.01; - value = unchanged_value + dx * stepping + dy * -stepping; - - value = Math.max(min, value); - - // value = +value.toFixed(precision); // or toFixed toPrecision - me.onChange.fire(value, true); - } - - function onDown(e) { - unchanged_value = value; - } - - function fireChange() { - me.onChange.fire(value); - } - - this.dom = span; - - // public - this.setValue = function(v) { - value = v; - }; - - this.paint = function() { - if (value) span.value = value.toFixed(precision); - }; -} - -module.exports = NumberUI; -},{"../handle_drag":"/Users/joshua/dev/gits/timeliner/src/handle_drag.js","../theme":"/Users/joshua/dev/gits/timeliner/src/theme.js","../utils":"/Users/joshua/dev/gits/timeliner/src/utils.js","do.js":"/Users/joshua/dev/gits/timeliner/node_modules/do.js/do.js"}],"/Users/joshua/dev/gits/timeliner/src/ui/scrollbar.js":[function(require,module,exports){ -var SimpleEvent = require('do.js'); -var utils = require('../utils'); -console.log(utils); - -// ********** class: ScrollBar ****************** // -/* - Simple UI widget that displays a scrolltrack - and slider, that fires some scroll events -*/ -// *********************************************** - -var scrolltrack_style = { - // float: 'right', - position: 'absolute', - // right: '0', - // top: '0', - // bottom: '0', - background: '-webkit-gradient(linear, left top, right top, color-stop(0, rgb(29,29,29)), color-stop(0.6, rgb(50,50,50)) )', - border: '1px solid rgb(29, 29, 29)', - // zIndex: '1000', - textAlign: 'center', - cursor: 'pointer' -}; - -var scrollbar_style = { - background: '-webkit-gradient(linear, left top, right top, color-stop(0.2, rgb(88,88,88)), color-stop(0.6, rgb(64,64,64)) )', - border: '1px solid rgb(25,25,25)', - // position: 'absolute', - position: 'relative', - borderRadius: '6px' -}; - -function ScrollBar(h, w, dispatcher) { - - var SCROLLBAR_WIDTH = w ? w : 12; - var SCROLLBAR_MARGIN = 3; - var SCROLL_WIDTH = SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2; - var MIN_BAR_LENGTH = 25; - - var scrolltrack = document.createElement('div'); - utils.style(scrolltrack, scrolltrack_style); - - var scrolltrackHeight = h - 2; - scrolltrack.style.height = scrolltrackHeight + 'px' ; - scrolltrack.style.width = SCROLL_WIDTH; //SCROLLBAR_WIDTH; - - // var scrollTop = 0; - var scrollbar = document.createElement('div'); - // scrollbar.className = 'scrollbar'; - utils.style(scrollbar, scrollbar_style); - scrollbar.style.width = SCROLLBAR_WIDTH; - scrollbar.style.height = h / 2; - scrollbar.style.top = 0; - scrollbar.style.left = SCROLLBAR_MARGIN + 'px'; // 0; //S - - scrolltrack.appendChild(scrollbar); - - var me = this; - - var bar_length, bar_y; - - // Sets lengths of scrollbar by percentage - this.setLength = function(l) { - // limit 0..1 - l = Math.max(Math.min(1, l), 0); - l *= scrolltrackHeight; - bar_length = Math.max(l, MIN_BAR_LENGTH); - scrollbar.style.height = bar_length; - }; - - this.setHeight = function(height) { - h = height; - - scrolltrackHeight = h - 2; - scrolltrack.style.height = scrolltrackHeight + 'px' ; - }; - - // Moves scrollbar to position by Percentage - this.setPosition = function(p) { - p = Math.max(Math.min(1, p), 0); - var emptyTrack = scrolltrackHeight - bar_length; - bar_y = p * emptyTrack; - scrollbar.style.top = bar_y; - }; - - this.setLength(1); - this.setPosition(0); - this.onScroll = new SimpleEvent(); - - var mouse_down_grip; - - function onDown(event) { - event.preventDefault(); - - if (event.target == scrollbar) { - mouse_down_grip = event.clientY; - document.addEventListener('mousemove', onMove, false); - document.addEventListener('mouseup', onUp, false); - } else { - if (event.clientY < bar_y) { - me.onScroll.fire('pageup'); - } else if (event.clientY > (bar_y + bar_length)) { - me.onScroll.fire('pagedown'); - } - // if want to drag scroller to empty track instead - // me.setPosition(event.clientY / (scrolltrackHeight - 1)); - } - } - - function onMove(event) { - event.preventDefault(); - - // event.target == scrollbar - var emptyTrack = scrolltrackHeight - bar_length; - var scrollto = (event.clientY - mouse_down_grip) / emptyTrack; - - // clamp limits to 0..1 - if (scrollto > 1) scrollto = 1; - if (scrollto < 0) scrollto = 0; - me.setPosition(scrollto); - me.onScroll.fire('scrollto', scrollto); - } - - function onUp(event) { - onMove(event); - document.removeEventListener('mousemove', onMove, false); - document.removeEventListener('mouseup', onUp, false); - } - - scrolltrack.addEventListener('mousedown', onDown, false); - this.dom = scrolltrack; - -} - -module.exports = ScrollBar; -},{"../utils":"/Users/joshua/dev/gits/timeliner/src/utils.js","do.js":"/Users/joshua/dev/gits/timeliner/node_modules/do.js/do.js"}],"/Users/joshua/dev/gits/timeliner/src/undo.js":[function(require,module,exports){ -/**************************/ -// Undo Manager -/**************************/ - -function UndoState(state, description) { - // this.state = JSON.stringify(state); - this.state = state.getJSONString(); - this.description = description; -} - -function UndoManager(dispatcher, max) { - this.dispatcher = dispatcher; - this.MAX_ITEMS = max || 100; - this.clear(); -} - -UndoManager.prototype.save = function(state, suppress) { - var states = this.states; - var next_index = this.index + 1; - var to_remove = states.length - next_index; - states.splice(next_index, to_remove, state); - - if (states.length > this.MAX_ITEMS) { - states.shift(); - } - - this.index = states.length - 1; - - // console.log('Undo State Saved: ', state.description); - if (!suppress) this.dispatcher.fire('state:save', state.description); -}; - -UndoManager.prototype.clear = function() { - this.states = []; - this.index = -1; - // FIXME: leave default state or always leave one state? -}; - -UndoManager.prototype.canUndo = function() { - return this.index > 0; - // && this.states.length > 1 -}; - -UndoManager.prototype.canRedo = function() { - return this.index < this.states.length - 1; -}; - -UndoManager.prototype.undo = function() { - if (this.canUndo()) { - this.dispatcher.fire('status', 'Undo: ' + this.get().description); - this.index--; - } else { - this.dispatcher.fire('status', 'Nothing to undo'); - } - - return this.get(); -}; - -UndoManager.prototype.redo = function() { - if (this.canRedo()) { - this.index++; - this.dispatcher.fire('status', 'Redo: ' + this.get().description); - } else { - this.dispatcher.fire('status', 'Nothing to redo'); - } - - return this.get(); -}; - -UndoManager.prototype.get = function() { - return this.states[this.index]; -}; - -module.exports = { - UndoState: UndoState, - UndoManager: UndoManager -}; -},{}],"/Users/joshua/dev/gits/timeliner/src/utils.js":[function(require,module,exports){ -var - Tweens = require('./tween'); - -module.exports = { - STORAGE_PREFIX: 'timeliner-', - Z_INDEX: 999, - style: style, - saveToFile: saveToFile, - openAs: openAs, - format_friendly_seconds: format_friendly_seconds, - findTimeinLayer: findTimeinLayer, - timeAtLayer: timeAtLayer -}; - -/**************************/ -// Utils -/**************************/ - -function style(element, styles) { - for (var s in styles) { - element.style[s] = styles[s]; - } -} - -function saveToFile(string, filename) { - var a = document.createElement("a"); - document.body.appendChild(a); - a.style = "display: none"; - - var blob = new Blob([string], { type: 'octet/stream' }), // application/json - url = window.URL.createObjectURL(blob); - - a.href = url; - a.download = filename; - - fakeClick(a); - - setTimeout(function() { - // cleanup and revoke - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - }, 500); -} - - - -var input, openCallback; - -function handleFileSelect(evt) { - var files = evt.target.files; // FileList object - - console.log('handle file select', files.length); - - var f = files[0]; - if (!f) return; - // Can try to do MINE match - // if (!f.type.match('application/json')) { - // return; - // } - console.log('match', f.type); - - var reader = new FileReader(); - - // Closure to capture the file information. - reader.onload = function(e) { - var data = e.target.result; - openCallback(data); - }; - - reader.readAsText(f); - - input.value = ''; -} - - -function openAs(callback, target) { - console.log('openfile...'); - openCallback = callback; - - if (!input) { - input = document.createElement('input'); - input.style.display = 'none'; - input.type = 'file'; - input.addEventListener('change', handleFileSelect); - target = target || document.body; - target.appendChild(input); - } - - fakeClick(input); -} - -function fakeClick(target) { - var e = document.createEvent("MouseEvents"); - e.initMouseEvent( - 'click', true, false, window, 0, 0, 0, 0, 0, - false, false, false, false, 0, null - ); - target.dispatchEvent(e); -} - -function format_friendly_seconds(s, type) { - // TODO Refactor to 60fps??? - // 20 mins * 60 sec = 1080 - // 1080s * 60fps = 1080 * 60 < Number.MAX_SAFE_INTEGER - - var raw_secs = s | 0; - var secs_micro = s % 60; - var secs = raw_secs % 60; - var raw_mins = raw_secs / 60 | 0; - var mins = raw_mins % 60; - var hours = raw_mins / 60 | 0; - - var secs_str = (secs / 100).toFixed(2).substring(2); - - var str = mins + ':' + secs_str; - - if (s % 1 > 0) { - var t2 = (s % 1) * 60; - if (type === 'frames') str = secs + '+' + t2.toFixed(0) + 'f'; - else str += ((s % 1).toFixed(2)).substring(1); - // else str = mins + ':' + secs_micro; - // else str = secs_micro + 's'; /// .toFixed(2) - } - return str; -} - -// get object at time -function findTimeinLayer(layer, time) { - var values = layer.values; - var i, il; - - // TODO optimize by checking time / binary search - - for (i=0, il=values.length; i time) { - return i; - } - } - - return i; -} - - -function timeAtLayer(layer, t) { - // Find the value of layer at t seconds. - // this expect layer to be sorted - // not the most optimized for now, but would do. - - var values = layer.values; - var i, il, entry, prev_entry; - - il = values.length; - - // can't do anything - if (il === 0) return; - - // find boundary cases - entry = values[0]; - if (t < entry.time) { - return { - value: entry.value, - can_tween: false, // cannot tween - keyframe: false // not on keyframe - }; - } - - for (i=0; i 1, - value: entry.value, - keyframe: true - }; - } - return { - // index: i, - entry: entry, - tween: entry.tween, - can_tween: il > 1, - value: entry.value, - keyframe: true // il > 1 - }; - } - if (t < entry.time) { - // possibly a tween - if (!prev_entry.tween) { // or if value is none - return { - value: prev_entry.value, - tween: false, - entry: prev_entry, - can_tween: true, - keyframe: false - }; - } - - // calculate tween - var time_diff = entry.time - prev_entry.time; - var value_diff = entry.value - prev_entry.value; - var tween = prev_entry.tween; - - var dt = t - prev_entry.time; - var k = dt / time_diff; - var new_value = prev_entry.value + Tweens[tween](k) * value_diff; - - return { - entry: prev_entry, - target_tween: entry, - value: new_value, - tween: prev_entry.tween, - can_tween: true, - keyframe: false - }; - } - } - // time is after all entries - return { - value: entry.value, - can_tween: false, - keyframe: false - }; - -} - -},{"./tween":"/Users/joshua/dev/gits/timeliner/src/tween.js"}]},{},["/Users/joshua/dev/gits/timeliner/src/datastore.js","/Users/joshua/dev/gits/timeliner/src/dispatcher.js","/Users/joshua/dev/gits/timeliner/src/handle_drag.js","/Users/joshua/dev/gits/timeliner/src/icon_button.js","/Users/joshua/dev/gits/timeliner/src/layer_cabinet.js","/Users/joshua/dev/gits/timeliner/src/save_format.js","/Users/joshua/dev/gits/timeliner/src/settings.js","/Users/joshua/dev/gits/timeliner/src/theme.js","/Users/joshua/dev/gits/timeliner/src/timeline_panel.js","/Users/joshua/dev/gits/timeliner/src/timeliner.js","/Users/joshua/dev/gits/timeliner/src/tween.js","/Users/joshua/dev/gits/timeliner/src/undo.js","/Users/joshua/dev/gits/timeliner/src/utils.js"]);