Mercurial > code > home > repos > light9
changeset 1515:37dd11031bcf
draw timeline adjusters in canvas
Ignore-this: e9e518e7a0700da1e7771dd559ddc7ed
author | Drew Perttula <drewp@bigasterisk.com> |
---|---|
date | Thu, 06 Apr 2017 10:04:27 +0000 |
parents | d5c3dec3dbd9 |
children | b1fed8880ada |
files | light9/web/timeline/adjustable.coffee light9/web/timeline/timeline-elements.html light9/web/timeline/timeline.coffee |
diffstat | 3 files changed, 195 insertions(+), 105 deletions(-) [+] |
line wrap: on
line diff
--- a/light9/web/timeline/adjustable.coffee Thu Mar 30 09:52:21 2017 +0000 +++ b/light9/web/timeline/adjustable.coffee Thu Apr 06 10:04:27 2017 +0000 @@ -1,8 +1,8 @@ log = console.log class Adjustable - # Some value you can edit in the UI, probably by dragging stuff. May - # have a <light9-timeline-adjuster> associated. This object does the + # Some value you can edit in the UI, probably by dragging + # stuff. Drawn by light9-adjusters-canvas. This object does the # layout and positioning. # # The way dragging should work is that you start in the yellow *adj
--- a/light9/web/timeline/timeline-elements.html Thu Mar 30 09:52:21 2017 +0000 +++ b/light9/web/timeline/timeline-elements.html Thu Apr 06 10:04:27 2017 +0000 @@ -28,7 +28,7 @@ light9-timeline-time-zoomed { flex-grow: 1; } - #dia, #adjusters, #cursorCanvas { + #dia, #adjusters, #cursorCanvas, #adjustersCanvas { position: absolute; left: 0; top: 0; right: 0; bottom: 0; } @@ -65,13 +65,8 @@ zoom-in-x="{{zoomInX}}"> </light9-timeline-time-zoomed> <light9-timeline-diagram-layer id="dia"></light9-timeline-diagram-layer> - <light9-timeline-adjusters id="adjusters" - dia="{{dia}}" - graph="{{graph}}" - song="{{song}}" - set-adjuster="{{setAdjuster}}" - zoom-in-x="{{zoomInX}}"> - </light9-timeline-adjusters> + <light9-adjusters-canvas id="adjustersCanvas" set-adjuster="{{setAdjuster}}"> + </light9-adjusters-canvas> <light9-cursor-canvas id="cursorCanvas"></light9-cursor-canvas> </template> @@ -182,6 +177,17 @@ </template> </dom-module> +<dom-module id="light9-adjusters-canvas"> + <template> + <style> + #canvas, :host { + pointer-events: none; + } + </style> + <canvas id="canvas"></canvas> + </template> +</dom-module> + <!-- seconds labels --> <dom-module id="light9-timeline-time-axis"> @@ -231,7 +237,7 @@ </template> </dom-module> -<!-- All the adjusters you can edit or select. +<!-- All the adjusters you can edit or select. Tells a light9-adjusters-canvas how to draw them. Probabaly doesn't need to be an element. This element manages their layout and suppresion. Owns the selection. Maybe includes selecting things that don't even have adjusters. @@ -245,9 +251,6 @@ } </style> - <div id="all"> - <!-- light9-timeline-adjuster repeated here --> - </div> </template> </dom-module>
--- a/light9/web/timeline/timeline.coffee Thu Mar 30 09:52:21 2017 +0000 +++ b/light9/web/timeline/timeline.coffee Thu Apr 06 10:04:27 2017 +0000 @@ -70,7 +70,7 @@ pos: ko.observable($V([0,0])) @fullZoomX = d3.scaleLinear() @zoomInX = d3.scaleLinear() - @setAdjuster = @$.adjusters.setAdjuster.bind(@$.adjusters) + @setAdjuster = @$.adjustersCanvas.setAdjuster.bind(@$.adjustersCanvas) setInterval(@updateDebugSummary.bind(@), 100) @@ -82,7 +82,6 @@ elemCount = (tag) -> document.getElementsByTagName(tag).length @debug = "#{window.debug_zoomOrLayoutChangedCount} layout change, #{elemCount('light9-timeline-note')} notes, - #{elemCount('light9-timeline-adjuster')} adjusters, #{elemCount('light9-timeline-graph-row')} rows, #{window.debug_adjsCount} adjuster items registered, #{window.debug_adjUpdateDisplay} adjuster updateDisplay calls, @@ -96,6 +95,7 @@ @trackMouse() @bindKeys() @bindWheelZoom() + @forwardMouseEventsToAdjustersCanvas() @makeZoomAdjs() @@ -114,7 +114,7 @@ # todo: these run a lot of work purely for a time change @dia.setTimeAxis(@width(), @$.zoomed.$.audio.offsetTop, @zoomInX) - @$.adjusters.updateAllCoords() + @$.adjustersCanvas.updateAllCoords() # cursor needs update when layout changes, but I don't want # zoom/layout to depend on the playback time @@ -164,6 +164,12 @@ zs.t1(center - left * scale) zs.t2(center + right * scale) + forwardMouseEventsToAdjustersCanvas: -> + ac = @$.adjustersCanvas + @addEventListener('mousedown', ac.onDown.bind(ac)) + @addEventListener('mousemove', ac.onMove.bind(ac)) + @addEventListener('mouseup', ac.onUp.bind(ac)) + animatedZoom: (newT1, newT2, secs) -> fps = 30 oldT1 = @viewState.zoomSpec.t1() @@ -202,7 +208,7 @@ @animatedZoom(newCenter - visSeconds / 2, newCenter + visSeconds / 2, zoomAnimSec) shortcut.add "L", => - @$.adjusters.updateAllCoords() + @$.adjustersCanvas.updateAllCoords() makeZoomAdjs: -> yMid = => @$.audio.offsetTop + @$.audio.offsetHeight / 2 @@ -574,86 +580,9 @@ patch = {delQuads: [{subject: @song, predicate: @graph.Uri(':note'), object: @uri, graph: @song}], addQuads: []} @graph.applyAndSendPatch(patch) - -Polymer - is: "light9-timeline-adjusters" - properties: - adjs: { type: Object, notify: true }, # adjId: Adjustable - dia: { type: Object } - - ready: -> - @adjs = {} - 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('adjsChanged', @adjsChanged.bind(@), 1) - else - for e in @$.all.children - if e.id == adjId - e.updateDisplay() - - window.debug_adjsCount = Object.keys(@adjs).length - - adjsChanged: -> - updateChildren @$.all, Object.keys(@adjs), (newUri) => - child = document.createElement('light9-timeline-adjuster') - child.dia = @dia - child.graph = @graph - child.uri = newUri - child.id = newUri - child.visible = true - child.adj = @adjs[newUri] - return child - @updateAllCoords() - - layoutCenters: -> - # push Adjustable centers around to avoid overlaps - qt = d3.quadtree() - qt.extent([[0,0], [8000,8000]]) - for _, adj of @adjs - desired = adj.getSuggestedCenter() - output = desired - for tries in [0...2] - 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()) - qt.add(output.elements) - - updateAllCoords: -> - @layoutCenters() - - for elem in @querySelectorAll('light9-timeline-adjuster') - elem.updateDisplay() - - -Polymer - is: 'light9-timeline-adjuster' - properties: - graph: { type: Object, notify: true } - adj: { type: Object, notify: true } - id: { type: String, notify: true } +class deleteme + go: -> visible: { type: Boolean, notify: true } displayValue: { type: String } @@ -714,6 +643,28 @@ return out +_line = (ctx, p1, p2) -> + ctx.moveTo(p1.e(1), p1.e(2)) + ctx.lineTo(p2.e(1), p2.e(2)) + +# http://stackoverflow.com/a/4959890 +_roundRect = (ctx, sx,sy,ex,ey,r) -> + d2r = Math.PI/180 + r = ( ( ex - sx ) / 2 ) if ( ex - sx ) - ( 2 * r ) < 0 # ensure that the radius isn't too large for x + r = ( ( ey - sy ) / 2 ) if ( ey - sy ) - ( 2 * r ) < 0 # ensure that the radius isn't too large for y + ctx.beginPath(); + ctx.moveTo(sx+r,sy); + ctx.lineTo(ex-r,sy); + ctx.arc(ex-r,sy+r,r,d2r*270,d2r*360,false); + ctx.lineTo(ex,ey-r); + ctx.arc(ex-r,ey-r,r,d2r*0,d2r*90,false); + ctx.lineTo(sx+r,ey); + ctx.arc(sx+r,ey-r,r,d2r*90,d2r*180,false); + ctx.lineTo(sx,sy+r); + ctx.arc(sx+r,sy+r,r,d2r*180,d2r*270,false); + ctx.closePath(); + + Polymer is: 'light9-cursor-canvas' behaviors: [ Polymer.IronResizableBehavior ] @@ -752,25 +703,21 @@ } @redraw() - _line: (p1, p2) -> - @ctx.moveTo(p1.e(1), p1.e(2)) - @ctx.lineTo(p2.e(1), p2.e(2)) - redraw: -> @ctx.clearRect(0, 0, @$.canvas.width, @$.canvas.height) @ctx.strokeStyle = '#fff' @ctx.lineWidth = 0.5 @ctx.beginPath() - @_line($V([0, @mouseY]), $V([@$.canvas.width, @mouseY]), '#fff', '0.5px') - @_line($V([@mouseX, 0]), $V([@mouseX, @$.canvas.height]), '#fff', '0.5px') + _line(@ctx, $V([0, @mouseY]), $V([@$.canvas.width, @mouseY])) + _line(@ctx, $V([@mouseX, 0]), $V([@mouseX, @$.canvas.height])) @ctx.stroke() if @cursorPath @ctx.strokeStyle = '#ff0303' @ctx.lineWidth = 1.5 @ctx.beginPath() - @_line(@cursorPath.top0, @cursorPath.top1, '#ff0303', 1.5) + _line(@ctx, @cursorPath.top0, @cursorPath.top1) @ctx.stroke() @ctx.fillStyle = '#9c0303' @@ -783,11 +730,151 @@ @ctx.strokeStyle = '#ff0303' @ctx.lineWidth = 3 @ctx.beginPath() - @_line(@cursorPath.bot0, @cursorPath.bot1, '#ff0303', '3px') + _line(@ctx, @cursorPath.bot0, @cursorPath.bot1, '#ff0303', '3px') @ctx.stroke() Polymer + is: 'light9-adjusters-canvas' + behaviors: [ Polymer.IronResizableBehavior ] + properties: + adjs: { type: Object, notify: true }, # adjId: Adjustable + listeners: 'iron-resize': 'update' + ready: -> + @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 + + # this is relying on makeCurveAdjusters always calling setAdjuster + # whenever the values may have changed + @debounce('adjsChanged', @adjsChanged.bind(@), 10) + + window.debug_adjsCount = Object.keys(@adjs).length + + adjsChanged: -> + @updateAllCoords() + + layoutCenters: -> + # push Adjustable centers around to avoid overlaps + # Todo: also don't overlap inlineattr boxes + @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...2] + 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) + + adjAtPoint: (pt) -> + nearest = @qt.find(pt.e(1), pt.e(2)) + if not nearest? or nearest.distanceFrom(pt) > 50 + return null + return nearest?.adj + + updateAllCoords: -> + @layoutCenters() + @redraw() + + update: (ev) -> + @$.canvas.width = ev.target.offsetWidth + @$.canvas.height = ev.target.offsetHeight + @redraw() + + redraw: (adjs) -> + @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) + + + drawConnector: (ctr, target) -> + @ctx.strokeStyle = '#aaa' + @ctx.lineWidth = 2 + @ctx.beginPath() + _line(@ctx, ctr, target) + @ctx.stroke() + + drawAdjuster: (label, x1, y1, x2, y2) -> + radius = 8 + @ctx.fillStyle = 'rgba(255, 255, 0, 0.5)' + @ctx.beginPath() + _roundRect(@ctx, x1, y1, x2, y2, radius) + @ctx.fill() + + @ctx.strokeStyle = 'yellow' + @ctx.lineWidth = 3 + @ctx.setLineDash([3, 3]) + @ctx.beginPath() + _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 + # connector + + +Polymer is: 'light9-timeline-diagram-layer' properties: {} ready: ->