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)
-
-
-
-## 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
+