diff --git a/light9/web/timeline/adjusters.coffee b/light9/web/timeline/adjusters.coffee new file mode 100644 --- /dev/null +++ b/light9/web/timeline/adjusters.coffee @@ -0,0 +1,157 @@ +log = console.log +Drawing = window.Drawing + + +class AdjustersCanvas extends Polymer.Element + @is: 'light9-adjusters-canvas' + @behaviors: [ Polymer.IronResizableBehavior ] + @properties: + adjs: { type: Object, notify: true }, # adjId: Adjustable + @observers: [ + 'updateAllCoords(adjs)' + ] + @listeners: + 'iron-resize': 'resizeUpdate' + connectedCallback: -> + super.connectedCallback() + @adjs = {} + @ctx = @$.canvas.getContext('2d') + + @redraw() + + onDown: (ev) -> + if ev.buttons == 1 + ev.stopPropagation() + start = $V([ev.x, ev.y]) + adj = @_adjAtPoint(start) + if adj + @currentDrag = {start: start, adj: adj} + adj.startDrag() + + onMove: (ev) -> + pos = $V([ev.x, ev.y]) + if @currentDrag + @currentDrag.cur = pos + @currentDrag.adj.continueDrag(@currentDrag.cur.subtract(@currentDrag.start)) + + onUp: (ev) -> + return unless @currentDrag + @currentDrag.adj.endDrag() + @currentDrag = null + + setAdjuster: (adjId, makeAdjustable) -> + # callers register/unregister the Adjustables they want us to make + # adjuster elements for. Caller invents adjId. makeAdjustable is + # a function returning the Adjustable or it is null to clear any + # adjusters with this id. + if not @adjs[adjId] or not makeAdjustable? + if not makeAdjustable? + delete @adjs[adjId] + else + adj = makeAdjustable() + @adjs[adjId] = adj + adj.id = adjId + + @debounce('adj redraw', @redraw.bind(@)) + + window.debug_adjsCount = Object.keys(@adjs).length + + updateAllCoords: -> + @redraw() + + _adjAtPoint: (pt) -> + nearest = @qt.find(pt.e(1), pt.e(2)) + if not nearest? or nearest.distanceFrom(pt) > 50 + return null + return nearest?.adj + + resizeUpdate: (ev) -> + @$.canvas.width = ev.target.offsetWidth + @$.canvas.height = ev.target.offsetHeight + @redraw() + + redraw: (adjs) -> + @debounce('redraw', @_throttledRedraw.bind(@)) + + _throttledRedraw: () -> + console.time('adjs redraw') + @_layoutCenters() + + @ctx.clearRect(0, 0, @$.canvas.width, @$.canvas.height) + + for adjId, adj of @adjs + ctr = adj.getCenter() + target = adj.getTarget() + @_drawConnector(ctr, target) + + @_drawAdjuster(adj.getDisplayValue(), + Math.floor(ctr.e(1)) - 20, Math.floor(ctr.e(2)) - 10, + Math.floor(ctr.e(1)) + 20, Math.floor(ctr.e(2)) + 10) + console.timeEnd('adjs redraw') + + _layoutCenters: -> + # push Adjustable centers around to avoid overlaps + # Todo: also don't overlap inlineattr boxes + # Todo: don't let their connector lines cross each other + @qt = d3.quadtree([], ((d)->d.e(1)), ((d)->d.e(2))) + @qt.extent([[0,0], [8000,8000]]) + for _, adj of @adjs + desired = adj.getSuggestedCenter() + output = desired + for tries in [0...4] + nearest = @qt.find(output.e(1), output.e(2)) + if nearest + dist = output.distanceFrom(nearest) + if dist < 60 + away = output.subtract(nearest).toUnitVector() + toScreenCenter = $V([500,200]).subtract(output).toUnitVector() + output = output.add(away.x(60).add(toScreenCenter.x(10))) + + if -50 < output.e(1) < 20 # mostly for zoom-left + output.setElements([ + Math.max(20, output.e(1)), + output.e(2)]) + + adj.centerOffset = output.subtract(adj.getTarget()) + output.adj = adj + @qt.add(output) + + _drawConnector: (ctr, target) -> + @ctx.strokeStyle = '#aaa' + @ctx.lineWidth = 2 + @ctx.beginPath() + Drawing.line(@ctx, ctr, target) + @ctx.stroke() + + _drawAdjuster: (label, x1, y1, x2, y2) -> + radius = 8 + + @ctx.shadowColor = 'black' + @ctx.shadowBlur = 15 + @ctx.shadowOffsetX = 5 + @ctx.shadowOffsetY = 9 + + @ctx.fillStyle = 'rgba(255, 255, 0, 0.5)' + @ctx.beginPath() + Drawing.roundRect(@ctx, x1, y1, x2, y2, radius) + @ctx.fill() + + @ctx.shadowColor = 'rgba(0,0,0,0)' + + @ctx.strokeStyle = 'yellow' + @ctx.lineWidth = 2 + @ctx.setLineDash([3, 3]) + @ctx.beginPath() + Drawing.roundRect(@ctx, x1, y1, x2, y2, radius) + @ctx.stroke() + @ctx.setLineDash([]) + + @ctx.font = "12px sans" + @ctx.fillStyle = '#000' + @ctx.fillText(label, x1 + 5, y2 - 5, x2 - x1 - 10) + + # coords from a center that's passed in + # # special layout for the thaeter ones with middinh + # l/r arrows + # mouse arrow cursor upon hover, and accent the hovered adjuster + # connector diff --git a/light9/web/timeline/timeline-elements.html b/light9/web/timeline/timeline-elements.html --- a/light9/web/timeline/timeline-elements.html +++ b/light9/web/timeline/timeline-elements.html @@ -283,6 +283,7 @@ + diff --git a/light9/web/timeline/timeline.coffee b/light9/web/timeline/timeline.coffee --- a/light9/web/timeline/timeline.coffee +++ b/light9/web/timeline/timeline.coffee @@ -708,161 +708,6 @@ deleteNote = (graph, song, note, selecti if note in selection.selected() selection.selected(_.without(selection.selected(), note)) - -class AdjustersCanvas extends Polymer.Element - @is: 'light9-adjusters-canvas' - @behaviors: [ Polymer.IronResizableBehavior ] - @properties: - adjs: { type: Object, notify: true }, # adjId: Adjustable - @observers: [ - 'updateAllCoords(adjs)' - ] - @listeners: - 'iron-resize': 'resizeUpdate' - connectedCallback: -> - super.connectedCallback() - @adjs = {} - @ctx = @$.canvas.getContext('2d') - - @redraw() - - onDown: (ev) -> - if ev.buttons == 1 - ev.stopPropagation() - start = $V([ev.x, ev.y]) - adj = @_adjAtPoint(start) - if adj - @currentDrag = {start: start, adj: adj} - adj.startDrag() - - onMove: (ev) -> - pos = $V([ev.x, ev.y]) - if @currentDrag - @currentDrag.cur = pos - @currentDrag.adj.continueDrag(@currentDrag.cur.subtract(@currentDrag.start)) - - onUp: (ev) -> - return unless @currentDrag - @currentDrag.adj.endDrag() - @currentDrag = null - - setAdjuster: (adjId, makeAdjustable) -> - # callers register/unregister the Adjustables they want us to make - # adjuster elements for. Caller invents adjId. makeAdjustable is - # a function returning the Adjustable or it is null to clear any - # adjusters with this id. - if not @adjs[adjId] or not makeAdjustable? - if not makeAdjustable? - delete @adjs[adjId] - else - adj = makeAdjustable() - @adjs[adjId] = adj - adj.id = adjId - - @debounce('adj redraw', @redraw.bind(@)) - - window.debug_adjsCount = Object.keys(@adjs).length - - updateAllCoords: -> - @redraw() - - _adjAtPoint: (pt) -> - nearest = @qt.find(pt.e(1), pt.e(2)) - if not nearest? or nearest.distanceFrom(pt) > 50 - return null - return nearest?.adj - - resizeUpdate: (ev) -> - @$.canvas.width = ev.target.offsetWidth - @$.canvas.height = ev.target.offsetHeight - @redraw() - - redraw: (adjs) -> - @debounce('redraw', @_throttledRedraw.bind(@)) - - _throttledRedraw: () -> - console.time('adjs redraw') - @_layoutCenters() - - @ctx.clearRect(0, 0, @$.canvas.width, @$.canvas.height) - - for adjId, adj of @adjs - ctr = adj.getCenter() - target = adj.getTarget() - @_drawConnector(ctr, target) - - @_drawAdjuster(adj.getDisplayValue(), - Math.floor(ctr.e(1)) - 20, Math.floor(ctr.e(2)) - 10, - Math.floor(ctr.e(1)) + 20, Math.floor(ctr.e(2)) + 10) - console.timeEnd('adjs redraw') - - _layoutCenters: -> - # push Adjustable centers around to avoid overlaps - # Todo: also don't overlap inlineattr boxes - # Todo: don't let their connector lines cross each other - @qt = d3.quadtree([], ((d)->d.e(1)), ((d)->d.e(2))) - @qt.extent([[0,0], [8000,8000]]) - for _, adj of @adjs - desired = adj.getSuggestedCenter() - output = desired - for tries in [0...4] - nearest = @qt.find(output.e(1), output.e(2)) - if nearest - dist = output.distanceFrom(nearest) - if dist < 60 - away = output.subtract(nearest).toUnitVector() - toScreenCenter = $V([500,200]).subtract(output).toUnitVector() - output = output.add(away.x(60).add(toScreenCenter.x(10))) - - if -50 < output.e(1) < 20 # mostly for zoom-left - output.setElements([ - Math.max(20, output.e(1)), - output.e(2)]) - - adj.centerOffset = output.subtract(adj.getTarget()) - output.adj = adj - @qt.add(output) - - _drawConnector: (ctr, target) -> - @ctx.strokeStyle = '#aaa' - @ctx.lineWidth = 2 - @ctx.beginPath() - Drawing.line(@ctx, ctr, target) - @ctx.stroke() - - _drawAdjuster: (label, x1, y1, x2, y2) -> - radius = 8 - - @ctx.shadowColor = 'black' - @ctx.shadowBlur = 15 - @ctx.shadowOffsetX = 5 - @ctx.shadowOffsetY = 9 - - @ctx.fillStyle = 'rgba(255, 255, 0, 0.5)' - @ctx.beginPath() - Drawing.roundRect(@ctx, x1, y1, x2, y2, radius) - @ctx.fill() - - @ctx.shadowColor = 'rgba(0,0,0,0)' - - @ctx.strokeStyle = 'yellow' - @ctx.lineWidth = 2 - @ctx.setLineDash([3, 3]) - @ctx.beginPath() - Drawing.roundRect(@ctx, x1, y1, x2, y2, radius) - @ctx.stroke() - @ctx.setLineDash([]) - - @ctx.font = "12px sans" - @ctx.fillStyle = '#000' - @ctx.fillText(label, x1 + 5, y2 - 5, x2 - x1 - 10) - - # coords from a center that's passed in - # # special layout for the thaeter ones with middinh - # l/r arrows - # mouse arrow cursor upon hover, and accent the hovered adjuster - # connector - class DiagramLayer extends Polymer.Element # note boxes.