From 5fa235a556d384117840b7088575012dcd1787dd Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 17 Nov 2014 15:46:39 -0500 Subject: polygonal lasso --- .bowerrc | 2 +- assets/javascripts/math/point.js | 9 ++ assets/javascripts/math/vec2.js | 114 ++++------------------ assets/javascripts/util/coords.js | 33 +++++++ assets/javascripts/util/keys.js | 165 +++++++++++++++++++++++++++++++ assets/javascripts/util/minotaur.js | 66 +++++++++++++ assets/javascripts/util/mouse.js | 171 +++++++++++++++++++++++++++++++++ assets/javascripts/util/permissions.js | 49 ++++++++++ assets/javascripts/util/uid.js | 29 ++++++ assets/javascripts/util/undostack.js | 54 +++++++++++ assets/javascripts/util/wheel.js | 69 +++++++++++++ assets/test/lasso/index.html | 88 +++++++++++++++++ 12 files changed, 753 insertions(+), 96 deletions(-) create mode 100644 assets/javascripts/util/coords.js create mode 100644 assets/javascripts/util/keys.js create mode 100644 assets/javascripts/util/minotaur.js create mode 100644 assets/javascripts/util/mouse.js create mode 100644 assets/javascripts/util/permissions.js create mode 100644 assets/javascripts/util/uid.js create mode 100644 assets/javascripts/util/undostack.js create mode 100644 assets/javascripts/util/wheel.js create mode 100644 assets/test/lasso/index.html diff --git a/.bowerrc b/.bowerrc index f21ed66..e35b1f5 100644 --- a/.bowerrc +++ b/.bowerrc @@ -1,3 +1,3 @@ { - "directory": "public/assets/javascripts/vendor/bower_components" + "directory": "assets/javascripts/vendor/bower_components" } diff --git a/assets/javascripts/math/point.js b/assets/javascripts/math/point.js index 354be00..5a5d0db 100644 --- a/assets/javascripts/math/point.js +++ b/assets/javascripts/math/point.js @@ -5,6 +5,15 @@ this.a = a this.b = b } + + point.prototype.distanceTo = function(p){ + return Math.sqrt(this.dot(p)) + } + point.prototype.dot = function(p){ + return Math.pow(this.a - p.a, 2) + Math.pow(this.b - p.b, 2) + } + + point.prototype.magnitude = function(){ return this.b-this.a } diff --git a/assets/javascripts/math/vec2.js b/assets/javascripts/math/vec2.js index 3e1f463..550d0db 100644 --- a/assets/javascripts/math/vec2.js +++ b/assets/javascripts/math/vec2.js @@ -6,18 +6,6 @@ } else { point = require('./point') - FRONT = 0x1, BACK = 0x2, LEFT = 0x4, RIGHT = 0x8, FLOOR = 0x10, CEILING = 0x20 - TOP = CEILING, BOTTOM = FLOOR - function sidesToString(sides){ - var s = "" - if (sides & FRONT) s += "front " - if (sides & BACK) s += "back " - if (sides & LEFT) s += "left " - if (sides & RIGHT) s += "right " - if (sides & TOP) s += "top " - if (sides & BOTTOM) s += "bottom " - return s - } } var vec2 = function (x0,y0,x1,y1){ @@ -33,8 +21,6 @@ this.x = new point(x0,x1) this.y = new point(y0,y1) } - this.translation = new point(0,0) - this.sides = FRONT | BACK | LEFT | RIGHT } vec2.prototype.clone = function(){ return new vec2( this.x.clone(), this.y.clone() ) @@ -75,24 +61,7 @@ this.translation.a = this.translation.b = 0 return this } - vec2.prototype.resize = function(translation, sides){ - var translation = translation || this.translation - sides = sides || translation.sides - - if (sides & LEFT) { - this.x.a += translation.a - } - if (sides & RIGHT) { - this.x.b += translation.a - } - if (sides & FRONT) { - this.y.a += translation.b - } - if (sides & BACK) { - this.y.b += translation.b - } - this.translation.a = this.translation.b = 0 - } + vec2.prototype.contains = function(x,y){ return this.x.contains(x) && this.y.contains(y) } @@ -122,22 +91,22 @@ this.a.zero() this.b.zero() } - vec2.prototype.nearEdge = function (x, y, r) { - var edges = 0 - if (x < this.x.a+r) { - edges |= LEFT - } - else if (x > this.x.b-r) { - edges |= RIGHT - } - if (y < this.y.a+r) { - edges |= FRONT - } - else if (y > this.y.b-r) { - edges |= BACK - } - return edges - } +// vec2.prototype.nearEdge = function (x, y, r) { +// var edges = 0 +// if (x < this.x.a+r) { +// edges |= LEFT +// } +// else if (x > this.x.b-r) { +// edges |= RIGHT +// } +// if (y < this.y.a+r) { +// edges |= FRONT +// } +// else if (y > this.y.b-r) { +// edges |= BACK +// } +// return edges +// } vec2.prototype.width = function(){ return this.x.length() } vec2.prototype.height = function(){ return this.y.length() } vec2.prototype.delta = function(){ return new point( this.x.magnitude(), this.y.magnitude() ) } @@ -165,13 +134,11 @@ return this } vec2.prototype.toString = function(){ - var sides = sidesToString(this.sides) - var s = "[" + this.x.toString() + " " + this.y.toString() + "] " + sides + var s = "[" + this.x.toString() + " " + this.y.toString() + "] " return s } vec2.prototype.exactString = function(){ - var sides = sidesToString(this.sides) - var s = "[" + this.x.exactString() + " " + this.y.exactString() + "] " + sides + var s = "[" + this.x.exactString() + " " + this.y.exactString() + "] " return s } @@ -183,49 +150,6 @@ this.y.quantize(n) return this } - vec2.prototype.split = function(r){ - var rz = this - var splits = [] - var sides = this.sides - - // bisect (or trisect) two overlapping rectangles - var x_intervals = this.x.split( r.x, LEFT, RIGHT ) - var y_intervals = this.y.split( r.y, FRONT, BACK ) - - // generate rectangular regions by crossing the two sets of vectors - x_intervals.forEach(function(x, i){ - y_intervals.forEach(function(y, i){ - var rn = new vec2(x[0], y[0]) - rn.id = rz.id - rn.sides = ((x[1] | y[1]) & sides) - rn.focused = rz.focused - splits.push(rn) - - // cull extra walls from overlapping regions - if (r.x.contains(rn.x.a) && r.x.contains(rn.x.b)) { - if (rz.y.a == rn.y.a && r.y.containsCenter(rn.y.a)) { // top edges - rn.sides &= ~ FRONT - } - if (rz.y.b == rn.y.b && r.y.containsCenter(rn.y.b)) { // bottom edges - rn.sides &= ~ BACK - } - } - - if (r.y.contains(rn.y.a) && r.y.contains(rn.y.b)) { - if (rz.x.a == rn.x.a && r.x.containsCenter(rn.x.a)) { // left edges - rn.sides &= ~ LEFT - } - - if (rz.x.b == rn.x.b && r.x.containsCenter(rn.x.b) ) { // right edges - rn.sides &= ~ RIGHT - } - } - - }) - }) - - return splits - } if ('window' in this) { window.vec2 = vec2 diff --git a/assets/javascripts/util/coords.js b/assets/javascripts/util/coords.js new file mode 100644 index 0000000..74b7fda --- /dev/null +++ b/assets/javascripts/util/coords.js @@ -0,0 +1,33 @@ +function offsetFromPoint(event, element) { + function a(width) { + var l = 0, r = 200; + while (r - l > 0.0001) { + var mid = (r + l) / 2; + var a = document.createElement('div'); + a.style.cssText = 'position: absolute;left:0;top:0;background: red;z-index: 1000;'; + a.style[width ? 'width' : 'height'] = mid.toFixed(3) + '%'; + a.style[width ? 'height' : 'width'] = '100%'; + element.appendChild(a); + var x = document.elementFromPoint(event.clientX, event.clientY); + element.removeChild(a); + if (x === a) { + r = mid; + } else { + if (r === 200) { + return null; + } + l = mid; + } + } + return mid; + } + var l = a(1), + t = a(0); + return l && t ? { + left: l / 100, + top: t / 100, + toString: function () { + return 'left: ' + l + '%, top: ' + t + '%'; + } + } : null; +} diff --git a/assets/javascripts/util/keys.js b/assets/javascripts/util/keys.js new file mode 100644 index 0000000..62d763f --- /dev/null +++ b/assets/javascripts/util/keys.js @@ -0,0 +1,165 @@ +var keys = (function(){ + + var base = new function(){} + base.tube = new Tube () + base.debug = false + + base.on = function(){ + base.tube.on.apply(base.tube, arguments) + } + + base.off = function(){ + base.tube.off.apply(base.tube, arguments) + } + + $(window).keydown(function(e){ + var key = KEY_NAMES[e.keyCode]; + switch (key) { + case undefined: + break; + default: + if (keys.debug) console.log(key) + base.tube(key, e) + break; + } + }) + var KEYMAP = { + STRG: 17, + CTRL: 17, + CTRLRIGHT: 18, + CTRLR: 18, + SHIFT: 16, + RETURN: 13, + ENTER: 13, + BACKSPACE: 8, + BCKSP:8, + ALT: 18, + ALTR: 17, + ALTRIGHT: 17, + SPACE: 32, + WIN: 91, + MAC: 91, + FN: null, + UP: 38, + DOWN: 40, + LEFT: 37, + RIGHT: 39, + ESC: 27, + DEL: 46, + F1: 112, + F2: 113, + F3: 114, + F4: 115, + F5: 116, + F6: 117, + F7: 118, + F8: 119, + F9: 120, + F10: 121, + F11: 122, + F12: 123 + }, + KEYCODES = { + 'backspace' : '8', + 'tab' : '9', + 'enter' : '13', + 'shift' : '16', + 'ctrl' : '17', + 'alt' : '18', + 'pause_break' : '19', + 'caps_lock' : '20', + 'escape' : '27', + 'page_up' : '33', + 'page down' : '34', + 'end' : '35', + 'home' : '36', + 'left_arrow' : '37', + 'up_arrow' : '38', + 'right_arrow' : '39', + 'down_arrow' : '40', + 'insert' : '45', + 'delete' : '46', + '0' : '48', + '1' : '49', + '2' : '50', + '3' : '51', + '4' : '52', + '5' : '53', + '6' : '54', + '7' : '55', + '8' : '56', + '9' : '57', + 'a' : '65', + 'b' : '66', + 'c' : '67', + 'd' : '68', + 'e' : '69', + 'f' : '70', + 'g' : '71', + 'h' : '72', + 'i' : '73', + 'j' : '74', + 'k' : '75', + 'l' : '76', + 'm' : '77', + 'n' : '78', + 'o' : '79', + 'p' : '80', + 'q' : '81', + 'r' : '82', + 's' : '83', + 't' : '84', + 'u' : '85', + 'v' : '86', + 'w' : '87', + 'x' : '88', + 'y' : '89', + 'z' : '90', + 'left_window key' : '91', + 'right_window key' : '92', + 'select_key' : '93', + 'numpad 0' : '96', + 'numpad 1' : '97', + 'numpad 2' : '98', + 'numpad 3' : '99', + 'numpad 4' : '100', + 'numpad 5' : '101', + 'numpad 6' : '102', + 'numpad 7' : '103', + 'numpad 8' : '104', + 'numpad 9' : '105', + 'multiply' : '106', + 'add' : '107', + 'subtract' : '109', + 'decimal point' : '110', + 'divide' : '111', + 'f1' : '112', + 'f2' : '113', + 'f3' : '114', + 'f4' : '115', + 'f5' : '116', + 'f6' : '117', + 'f7' : '118', + 'f8' : '119', + 'f9' : '120', + 'f10' : '121', + 'f11' : '122', + 'f12' : '123', + 'num_lock' : '144', + 'scroll_lock' : '145', + 'semi_colon' : '186', + 'equal_sign' : '187', + 'comma' : '188', + 'dash' : '189', + 'period' : '190', + 'forward_slash' : '191', + 'grave_accent' : '192', + 'open_bracket' : '219', + 'backslash' : '220', + 'closebracket' : '221', + 'single_quote' : '222' + }, + KEY_NAMES = invert_hash(KEYCODES) + + return base +})() \ No newline at end of file diff --git a/assets/javascripts/util/minotaur.js b/assets/javascripts/util/minotaur.js new file mode 100644 index 0000000..d165ccc --- /dev/null +++ b/assets/javascripts/util/minotaur.js @@ -0,0 +1,66 @@ +(function(){ + + var Monitor = function () { + var base = this + base.$el = $("#minotaur") + base.timeout = null + base.delay = 2500 + base.objects = {} + + base.init = function () { + base.$el.removeClass() + base.$el.click(base.save) + } + + base.watch = function (object) { + base.objects[object.type] = base.objects[object.type] || {} + base.objects[object.type][object._id] = object + base.clear() + base.timeout = setTimeout(base.save, base.delay) + } + + base.unwatch = function (object) { + if (base.objects[object.type] && base.objects[object.type][object._id]) { + delete base.objects[object.type][object._id] + } + } + + base.clear = function () { + if (base.timeout) clearTimeout(base.timeout) + base.timeout = false + } + + base.save = function () { + var saving = false + base.clear() + + for (var type in base.objects) { + for (var id in base.objects[type]) { + var obj = base.objects[type][id] + if (obj) { + obj.save(null, function(){ base.hide() }, function(){}) + } + delete base.objects[type][id] + saving = true + } + } + + saving ? base.show() : base.hide() + } + + base.show = function () { + base.$el.removeClass().addClass('saving') + } + + base.hide = function () { + setTimeout(function(){ + base.$el.removeClass() + }, 500) + } + + base.init(); + } + + window.Minotaur = new Monitor (); + +})() diff --git a/assets/javascripts/util/mouse.js b/assets/javascripts/util/mouse.js new file mode 100644 index 0000000..d113d10 --- /dev/null +++ b/assets/javascripts/util/mouse.js @@ -0,0 +1,171 @@ +/* + usage: + + base.mouse = new mouse({ + el: document.querySelector("#map"), + down: function(e, cursor){ + // do something with val + // cursor.x.a + // cursor.y.a + }, + move: function(e, cursor){ + // var delta = cursor.delta() + // delta.a (x) + // delta.b (y) + }, + up: function(e, cursor, new_cursor){ + // cursor.x.a + // cursor.y.a + }, + }) + +*/ + +function mouse (opt) { + var base = this + + opt = defaults(opt, { + el: null, + down: null, + move: null, + drag: null, + enter: null, + up: null, + rightclick: null, + propagate: false, + locked: false, + use_offset: true, + val: 0, + }) + + base.down = false + + base.creating = false + base.dragging = false + + base.cursor = new vec2(0,0,0,0) + + base.tube = new Tube () + opt.down && base.tube.on("down", opt.down) + opt.move && base.tube.on("move", opt.move) + opt.drag && base.tube.on("drag", opt.drag) + opt.enter && base.tube.on("enter", opt.enter) + opt.leave && base.tube.on("leave", opt.leave) + opt.up && base.tube.on("up", opt.up) + opt.rightclick && base.tube.on("rightclick", opt.rightclick) + + var offset = (opt.use_offset && opt.el) ? opt.el.getBoundingClientRect() : null + + base.init = function (){ + base.bind() + } + + base.on = function(){ + base.tube.on.apply(base.tube, arguments) + } + + base.off = function(){ + base.tube.off.apply(base.tube, arguments) + } + + base.bind = function(){ + if (opt.el) { + opt.el.addEventListener("mousedown", base.mousedown) + opt.el.addEventListener("contextmenu", base.contextmenu) + } + window.addEventListener("mousemove", base.mousemove) + window.addEventListener("mouseup", base.mouseup) + } + + base.bind_el = function(el){ + el.addEventListener("mousedown", base.mousedown) + el.addEventListener("mousemove", base.mousemove) + } + base.unbind_el = function(el){ + el.removeEventListener("mousedown", base.mousedown) + el.removeEventListener("mousemove", base.mousemove) + } + + function positionFromMouse(e) { + if (offset) { + return new point(e.pageX - offset.left, e.pageY - offset.top) + } + else { + return new point(e.pageX, e.pageY) + } + } + + base.mousedown = function(e){ + if (opt.use_offset) { + offset = this.getBoundingClientRect() + } + + var pos = positionFromMouse(e) + + var x = pos.a, y = pos.b + base.cursor = new vec2 (x,y, x,y) + base.down = true + e.clickAccepted = true + + base.tube("down", e, base.cursor) + + if (e.clickAccepted) { + e.stopPropagation() + } + else { + base.down = false + } + } + base.mousemove = function(e){ + if (opt.use_offset && ! offset) return + + var pos = positionFromMouse(e) + + if (e.shiftKey) { + pos.quantize(10) + } + + var x = pos.a, y = pos.b + + if (base.down) { + base.cursor.x.b = x + base.cursor.y.b = y + base.tube("drag", e, base.cursor) + e.stopPropagation() + } + else { + base.cursor.x.a = base.cursor.x.b = x + base.cursor.y.a = base.cursor.y.b = y + base.tube("move", e, base.cursor) + } + } + base.mouseenter = function(e, target, index){ + if (! base.down) return + if (opt.use_offset && ! offset) return + base.tube("enter", e, target, base.cursor) + } + base.mouseleave = function(e, target){ + if (! base.down) return + if (opt.use_offset && ! offset) return + base.tube("leave", e, target, base.cursor) + } + base.mouseup = function(e){ + var pos, new_cursor + + if (base.down) { + e.stopPropagation() + base.down = false + pos = positionFromMouse(e) + new_cursor = new vec2 (pos.a, pos.b) + base.tube("up", e, base.cursor, new_cursor) + base.cursor = new_cursor + } + } + base.contextmenu = function(e){ + e.preventDefault() + base.tube("rightclick", e, base.cursor) + } + + base.init() +} + diff --git a/assets/javascripts/util/permissions.js b/assets/javascripts/util/permissions.js new file mode 100644 index 0000000..9e3ef4d --- /dev/null +++ b/assets/javascripts/util/permissions.js @@ -0,0 +1,49 @@ + +var Permissions = function(ops){ + var base = this + base.keys = _.keys(ops) + base.keys.forEach(function(op){ + base[op] = ops[op] + }) +} + +Permissions.prototype.toggle = function (key) { + var base = this + var state = ! base[key] + base.keys.forEach(function(op){ + base[op] = op == key ? state : false + }) + return state +} + +Permissions.prototype.assign = function (key, state) { + var base = this + base.keys.forEach(function(op){ + base[op] = op == key ? state : false + }) + return state +} + +Permissions.prototype.add = function (key) { + var base = this + base[key] = true +} + +Permissions.prototype.remove = function (key) { + var base = this + base[key] = false +} + +Permissions.prototype.clear = function () { + var base = this + base.keys.forEach(function(op){ + base[op] = false + }) +} + +Permissions.prototype.log = function () { + var base = this + base.keys.forEach(function(op){ + console.log(op, base[op]) + }) +} \ No newline at end of file diff --git a/assets/javascripts/util/uid.js b/assets/javascripts/util/uid.js new file mode 100644 index 0000000..0c0b176 --- /dev/null +++ b/assets/javascripts/util/uid.js @@ -0,0 +1,29 @@ +(function(){ + + var UidGenerator = function(list){ + var id = 0 + var generator = function(s){ + s = s || "" + var ss + while (1) { + ss = s + (id++) + if (! (ss in list)) { + return ss + } + } + } + generator.setList = function(newList){ + list = newList + } + return generator + } + + if ('window' in this) { + window.UidGenerator = UidGenerator + } + else { + module.exports = UidGenerator + } + +})() + diff --git a/assets/javascripts/util/undostack.js b/assets/javascripts/util/undostack.js new file mode 100644 index 0000000..040a4eb --- /dev/null +++ b/assets/javascripts/util/undostack.js @@ -0,0 +1,54 @@ +(function(){ + + var UndoStack = function(){ + this.debug = true + this.stack = [] + this.types = {} + this.pointer = -1 + } + UndoStack.prototype.push = function(action){ + this.pointer++ + this.stack[this.pointer] = action + this.purge() + this.debug && console.log("push", action.type) + } + UndoStack.prototype.purge = function(){ + if (this.stack.length-1 == this.pointer) return + this.stack.length = this.pointer+1 + } + UndoStack.prototype.undo = function(){ + if (this.pointer == -1) return false + var action = this.stack[this.pointer] + this.debug && console.log("undo", action.type) + this.types[ action.type ].undo(action.undo) + this.pointer-- + return this.pointer > -1 + } + UndoStack.prototype.redo = function(){ + if (this.pointer == this.stack.length-1) return false + this.pointer++ + var action = this.stack[this.pointer] + this.debug && console.log("redo", action.type) + this.types[ action.type ].redo(action.redo) + return this.pointer < this.stack.length-1 + } + UndoStack.prototype.register = function(actions){ + if (actions.length) { + actions.forEach(this.registerOne.bind(this)) + } + else { + this.registerOne(actions) + } + } + UndoStack.prototype.registerOne = function(action){ + if (! action.redo) { action.redo = action.undo } + this.types[ action.type ] = action + } + if ('window' in this) { + window.UndoStack = new UndoStack + } + else { + module.exports = new UndoStack + } + +})() diff --git a/assets/javascripts/util/wheel.js b/assets/javascripts/util/wheel.js new file mode 100644 index 0000000..712d470 --- /dev/null +++ b/assets/javascripts/util/wheel.js @@ -0,0 +1,69 @@ +/* + usage: + + base.wheel = new wheel({ + el: document.querySelector("#map"), + update: function(e, val, delta){ + // do something with val + }, + }) + +*/ + +function wheel (opt) { + opt = defaults(opt, { + el: document, + fn: function(e, val, delta){}, + propagate: false, + locked: false, + reversible: true, + ratio: 0.02, + val: 0, + }) + + opt.el.addEventListener('wheel', onMouseWheel, false); +// opt.el.addEventListener('mousewheel', onMouseWheel, false); + opt.el.addEventListener('DOMMouseScroll', onMouseWheel, false); + + function onMouseWheel (e) { + if (opt.locked) { + return + } + + if (! opt.propagate) { + e.stopPropagation() + e.preventDefault() + } + + var deltaX = 0, deltaY = 0; + + // WebKit + if ( event.deltaY ) { + deltaY -= event.deltaY * opt.ratio + deltaX -= event.deltaX * opt.ratio + } + else if ( event.wheelDeltaY ) { + deltaY -= event.wheelDeltaY * opt.ratio + deltaX -= event.wheelDeltaX * opt.ratio + } + // Opera / Explorer 9 + else if ( event.wheelDelta ) { + deltaY -= event.wheelDelta * opt.ratio + } + // Firefox + else if ( event.detail ) { + deltaY += event.detail * 2 + } + if (! opt.reversible && (deltaY < 0 && deltaX < 0)) return; + + // opt.val = clamp(opt.val + delta, opt.min, opt.max) + + opt.update(e, deltaY, deltaX) + } + + opt.lock = function(){ opt.locked = true } + opt.unlock = function(){ opt.locked = false } + + return opt +} + diff --git a/assets/test/lasso/index.html b/assets/test/lasso/index.html new file mode 100644 index 0000000..16cb27f --- /dev/null +++ b/assets/test/lasso/index.html @@ -0,0 +1,88 @@ + + +
+ + + + + + + + + + + -- cgit v1.2.3-70-g09d2