Changeset - d5c3dec3dbd9
[Not reviewed]
default
0 2 0
Drew Perttula - 8 years ago 2017-03-30 09:52:21
drewp@bigasterisk.com
move time and mouse cursors to a <canvas> tag
Ignore-this: da8de9cb12a93dffa0b61a13f0358efa
2 files changed with 88 insertions and 38 deletions:
0 comments (0 inline, 0 general)
light9/web/timeline/timeline-elements.html
Show inline comments
 
@@ -7,92 +7,93 @@
 
<link rel="import" href="../edit-choice.html">
 

	
 

	
 
<!-- Whole editor- include this on your page.
 
     Most coordinates are relative to this element.
 
   -->
 
<dom-module id="light9-timeline-editor">
 
  <template>
 
    <style>
 
     :host {
 
         background: #444;
 
         display: flex;
 
         flex-direction: column;
 
         position: relative;
 
         border: 1px solid black;
 
         overflow: hidden;
 
     }
 
     light9-timeline-audio {
 
         width: 100%;
 
         height: 30px;
 
     }
 
     light9-timeline-time-zoomed {
 
         flex-grow: 1;
 
     }
 
     light9-timeline-diagram-layer, light9-timeline-adjusters {
 
     #dia, #adjusters, #cursorCanvas {
 
         position: absolute;
 
         left: 0; top: 0; right: 0; bottom: 0;
 
     }
 
     #debug {
 
      background: white;
 
      font-family: monospace;
 
      font-size: 125%;
 
      height: 15px;
 
     }
 
    </style>
 
    <div>
 
      <rdfdb-synced-graph graph="{{graph}}"></rdfdb-synced-graph>
 
      <light9-music id="music"
 
                    song="{{playerSong}}"
 
                    t="{{songTime}}"
 
                    playing="{{songPlaying}}"
 
                    duration="{{songDuration}}"></light9-music>
 
      timeline editor: song <edit-choice graph="{{graph}}" uri="{{song}}"></edit-choice>
 
      <label><input type="checkbox" checked="{{followPlayerSong::change}}" > follow player song choice</label>
 
    </div>
 
    <div id="debug">[[debug]]</div>
 
    <iron-ajax id="vidrefTime" url="/vidref/time" method="PUT" content-type="application/json"></iron-ajax>
 
    <light9-timeline-audio id="audio"
 
                           graph="{{graph}}"
 
                           show="{{show}}"
 
                           song="{{song}}"></light9-timeline-audio>
 
    <light9-timeline-time-zoomed id="zoomed"
 
                                 graph="{{graph}}"
 
                                 dia="{{dia}}"
 
                                 set-adjuster="{{setAdjuster}}"
 
                                 song="{{song}}"
 
                                 show="{{show}}"
 
                                 zoom="{{viewState.zoomSpec}}"
 
                                 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-cursor-canvas id="cursorCanvas"></light9-cursor-canvas>
 
  </template>
 
 
 
</dom-module>
 

	
 
<!-- the whole section that pans/zooms in time (most of the editor) -->
 
<dom-module id="light9-timeline-time-zoomed">
 
  <template>
 
    <style>
 
     :host {
 
         display: flex;
 
         height: 100%;
 
     }
 
     div {
 
         display: flex;
 
         flex-direction: column;
 
         height: 100%;
 
     }
 
     #rows.dragging {
 
background: rgba(126, 52, 245, 0.0784313725490196);
 
         }
 
     light9-timeline-audio {
 
         width: 100%;
 
         height: 100px;
 
     }
 
@@ -144,58 +145,64 @@ background: rgba(126, 52, 245, 0.0784313
 
         pointer-events: none;
 
     }
 
     svg {
 
         width: 100%;
 
         height: 100%;
 
         pointer-events: none;
 
     }
 
    </style>
 
    <svg xmlns="http://www.w3.org/2000/svg"
 
         xmlns:svg="http://www.w3.org/2000/svg"
 
         xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" >
 
      <g id="layer1">
 
        <text
 
            xml:space="preserve"
 
            style="font-size:13px;line-height:125%;font-family:'Verana Sans';-inkscape-font-specification:'Verana Sans';text-align:start;text-anchor:start;fill:#000000;"
 
            x="-338.38403"
 
            y="631.3988"
 
            id="text4290"
 
            sodipodi:linespacing="125%" ><tspan sodipodi:role="line" id="tspan4292" x="-338.38403" y="631.3988">spotchase</tspan></text>
 
        <g id="timeAxis" transform="translate(0,40)"></g>
 
        <g id="mouse"></g>
 
        <g id="notes"></g>
 
        <g id="noteLabels"></g>
 
        <g id="connectors"></g>
 
        <g id="cursor">
 
          <path id="cursor1" style="fill:none; stroke:#ff0303; stroke-width:1.5; stroke-linecap:butt;" />
 
          <path id="cursor2" style="fill:#9c0303;" />
 
          <path id="cursor3" style="fill:none; stroke:#ff0303; stroke-width:3; stroke-linecap:butt;" />
 
        </g>
 
      </g>
 
    </svg>
 
  </template>
 
</dom-module>
 

	
 
<dom-module id="light9-cursor-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">
 
  <template>
 
    <style>
 
     div {
 
         width: 100%;
 
         height: 31px;
 
     }
 
    </style>
 
    <div></div>
 
  </template>
 
</dom-module>
 

	
 
<!-- one row of notes -->
 
<dom-module id="light9-timeline-graph-row">
 
  <template>
 
    <style>
 
     :host {
 
         border-top: 1px solid black;
 
         display: flex;
 
     }
 
    </style>
 
    <!-- light9-timeline-note repeated here -->
light9/web/timeline/timeline.coffee
Show inline comments
 
@@ -100,67 +100,67 @@ Polymer
 
    @makeZoomAdjs()
 

	
 
  zoomOrLayoutChanged: ->
 
    # not for cursor updates
 

	
 
    window.debug_zoomOrLayoutChangedCount++
 
    @fullZoomX.domain([0, @viewState.zoomSpec.duration()])
 
    @fullZoomX.range([0, @width()])
 

	
 
    # had trouble making notes update when this changes
 
    zoomInX = d3.scaleLinear()
 
    zoomInX.domain([@viewState.zoomSpec.t1(), @viewState.zoomSpec.t2()])
 
    zoomInX.range([0, @width()])
 
    @zoomInX = zoomInX
 

	
 
    # todo: these run a lot of work purely for a time change    
 
    @dia.setTimeAxis(@width(), @$.zoomed.$.audio.offsetTop, @zoomInX)
 
    @$.adjusters.updateAllCoords()
 

	
 
    # cursor needs update when layout changes, but I don't want
 
    # zoom/layout to depend on the playback time
 
    setTimeout(@songTimeChanged.bind(@), 1)
 

	
 
  songTimeChanged: ->
 
    @dia.setCursor(@$.audio.offsetTop, @$.audio.offsetHeight,
 
    @$.cursorCanvas.setCursor(@$.audio.offsetTop, @$.audio.offsetHeight,
 
                     @$.zoomed.$.time.offsetTop,
 
                     @$.zoomed.$.time.offsetHeight,
 
                     @fullZoomX, @zoomInX, @viewState.cursor)
 
    
 
  trackMouse: ->
 
    # not just for show- we use the mouse pos sometimes
 
    for evName in ['mousemove', 'touchmove']
 
      @addEventListener evName, (ev) =>
 
        ev.preventDefault()
 

	
 
        # todo: consolidate with _editorCoordinates version
 
        if ev.touches?.length
 
          ev = ev.touches[0]
 

	
 
        @root = @getBoundingClientRect()
 
        @viewState.mouse.pos($V([ev.pageX - @root.left, ev.pageY - @root.top]))
 

	
 
        @dia.setMouse(@viewState.mouse.pos())
 
        @$.cursorCanvas.setMouse(@viewState.mouse.pos())
 
        @sendMouseToVidref()
 

	
 
  sendMouseToVidref: ->
 
    now = Date.now()
 
    if (!@$.vidrefLastSent? || @$.vidrefLastSent < now - 200) && !@songPlaying
 
      @$.vidrefTime.body = {t: @latestMouseTime(), source: 'timeline'}
 
      @$.vidrefTime.generateRequest()
 
      @$.vidrefLastSent = now
 

	
 
  latestMouseTime: ->
 
    @zoomInX.invert(@viewState.mouse.pos().e(1))
 

	
 
  bindWheelZoom: ->
 
    @$.zoomed.addEventListener 'mousewheel', (ev) =>
 
      zs = @viewState.zoomSpec
 

	
 
      center = @latestMouseTime()
 
      left = center - zs.t1()
 
      right = zs.t2() - center
 
      scale = Math.pow(1.005, ev.deltaY)
 

	
 
      zs.t1(center - left * scale)
 
      zs.t2(center + right * scale)
 

	
 
@@ -694,64 +694,131 @@ Polymer
 
    drag.on 'drag', () =>
 
      @adj?.continueDrag($V([d3.event.x, d3.event.y]))
 
    drag.on('end', () => @adj?.endDrag())
 

	
 
    @updateDisplay()
 

	
 
  detached: -> @clearElements()
 
  clearElements: ->
 
    @dia.clearElem(@adj.id, ['/conn'])
 

	
 

	
 
svgPathFromPoints = (pts) ->
 
  out = ''
 
  pts.forEach (p) ->
 
    p = p.elements if p.elements # for vec2
 
    if out.length == 0
 
      out = 'M '
 
    else
 
      out += 'L '
 
    out += '' + p[0] + ',' + p[1] + ' '
 
    return
 
  out
 

	
 
Polymer
 
  is: 'light9-cursor-canvas'
 
  behaviors: [ Polymer.IronResizableBehavior ]
 
  listeners: 'iron-resize': 'update'
 

	
 
  ready: ->
 
    @mouseX = 0
 
    @mouseY = 0
 
    @cursorPath = null
 
    @ctx = @$.canvas.getContext('2d')
 

	
 
  update: (ev) ->
 
    @$.canvas.width = ev.target.offsetWidth
 
    @$.canvas.height = ev.target.offsetHeight
 
    @redraw()
 

	
 
  setMouse: (pos) ->
 
    @mouseX = pos.e(1)
 
    @mouseY = pos.e(2)
 
    @redraw()
 

	
 
  setCursor: (y1, h1, y2, h2, fullZoomX, zoomInX, cursor) ->
 
    
 
    xZoomedOut = fullZoomX(cursor.t())
 
    xZoomedIn = zoomInX(cursor.t())
 

	
 
    @cursorPath = {
 
      top0: $V([xZoomedOut, y1])
 
      top1: $V([xZoomedOut, y1 + h1])
 
      mid0: $V([xZoomedIn + 2, y2 + h2])
 
      mid1: $V([xZoomedIn - 2, y2 + h2])
 
      mid2: $V([xZoomedOut - 1, y1 + h1])
 
      mid3: $V([xZoomedOut + 1, y1 + h1])
 
      bot0: $V([xZoomedIn, y2 + h2])
 
      bot1: $V([xZoomedIn, @offsetParent.offsetHeight])
 
    }
 
    @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')
 
    @ctx.stroke()
 

	
 
    if @cursorPath
 
      @ctx.strokeStyle = '#ff0303'
 
      @ctx.lineWidth = 1.5
 
      @ctx.beginPath()
 
      @_line(@cursorPath.top0, @cursorPath.top1, '#ff0303', 1.5)
 
      @ctx.stroke()
 

	
 
      @ctx.fillStyle = '#9c0303'
 
      @ctx.beginPath()
 
      @ctx.moveTo(@cursorPath.mid0.e(1), @cursorPath.mid0.e(2))
 
      @ctx.lineTo(p.e(1), p.e(2)) for p in [
 
        @cursorPath.mid1, @cursorPath.mid2, @cursorPath.mid3]
 
      @ctx.fill()
 
      
 
      @ctx.strokeStyle = '#ff0303'
 
      @ctx.lineWidth = 3
 
      @ctx.beginPath()
 
      @_line(@cursorPath.bot0, @cursorPath.bot1, '#ff0303', '3px')
 
      @ctx.stroke()
 
    
 
    
 
Polymer
 
  is: 'light9-timeline-diagram-layer'
 
  properties: {}
 
  ready: ->
 
    @elemById = {}
 

	
 
  setTimeAxis: (width, yTop, scale) ->
 
    pxPerTick = 50
 
    axis = d3.axisTop(scale).ticks(width / pxPerTick)
 
    d3.select(@$.timeAxis).attr('transform', 'translate(0,'+yTop+')').call(axis)
 

	
 
  setMouse: (pos) ->
 
    elem = @getOrCreateElem('mouse-x', 'mouse', 'path', {style: "fill:none;stroke:#fff;stroke-width:0.5;"})
 
    elem.setAttribute('d', svgPathFromPoints([[-9999, pos.e(2)], [9999, pos.e(2)]]))
 
    elem = @getOrCreateElem('mouse-y', 'mouse', 'path', {style: "fill:none;stroke:#fff;stroke-width:0.5;"})
 
    elem.setAttribute('d', svgPathFromPoints([[pos.e(1), -9999], [pos.e(1), 9999]]))   
 

	
 
  getOrCreateElem: (uri, groupId, tag, attrs) ->
 
    elem = @elemById[uri]
 
    if !elem
 
      elem = @elemById[uri] = document.createElementNS("http://www.w3.org/2000/svg", tag)
 
      @$[groupId].appendChild(elem)
 
      elem.setAttribute('id', uri)
 
      for k,v of attrs
 
        elem.setAttribute(k, v)
 
    return elem
 

	
 
  clearElem: (uri, suffixes) -> # todo: caller shouldn't have to know suffixes!
 
    for suff in suffixes
 
      elem = @elemById[uri+suff]
 
      if elem
 
        elem.remove()
 
        delete @elemById[uri+suff]
 

	
 
  anyPointsInView: (pts) ->
 
    for pt in pts
 
      # wrong:
 
      if pt.e(1) > -100 && pt.e(1) < 2500
 
        return true
 
    return false
 
    
 
@@ -763,55 +830,31 @@ Polymer
 
      return
 
    # for now these need to be pretty transparent since they're
 
    # drawing on top of the inline-attrs widget :(
 

	
 
    if effect in ['http://light9.bigasterisk.com/effect/blacklight',
 
      'http://light9.bigasterisk.com/effect/strobewarm']
 
      hue = 0
 
      sat = 100
 
    else        
 
      hash = 0
 
      for i in [(effect.length-10)...effect.length]
 
        hash += effect.charCodeAt(i)
 
      hue = (hash * 8) % 360
 
      sat = 40 + (hash % 20) # don't conceal colorscale too much
 
    
 
    elem = @getOrCreateElem(areaId, 'notes', 'path',
 
      {style:"fill:hsla(#{hue}, #{sat}%, 58%, 0.313); stroke:#000000; stroke-width:1.5;"})
 
    elem.setAttribute('d', svgPathFromPoints(curvePts))
 

	
 
    #elem = @getOrCreateElem(uri+'/label', 'noteLabels', 'text', {style: "font-size:13px;line-height:125%;font-family:'Verana Sans';text-align:start;text-anchor:start;fill:#000000;"})
 
    #elem.setAttribute('x', curvePts[0].e(1)+20)
 
    #elem.setAttribute('y', curvePts[0].e(2)-10)
 
    #elem.innerHTML = effectLabel;
 

	
 
  setCursor: (y1, h1, y2, h2, fullZoomX, zoomInX, cursor) ->
 
    @cursorPath =
 
      top: @querySelector('#cursor1')
 
      mid: @querySelector('#cursor2')
 
      bot: @querySelector('#cursor3')
 
    return if !@cursorPath.top
 
    
 
    xZoomedOut = fullZoomX(cursor.t())
 
    xZoomedIn = zoomInX(cursor.t())
 
    @cursorPath.top.setAttribute 'd', svgPathFromPoints([
 
      [xZoomedOut, y1]
 
      [xZoomedOut, y1 + h1]
 
    ])
 
    @cursorPath.mid.setAttribute 'd', svgPathFromPoints([
 
      [xZoomedIn + 2, y2 + h2]
 
      [xZoomedIn - 2, y2 + h2]
 
      [xZoomedOut - 1, y1 + h1]
 
      [xZoomedOut + 1, y1 + h1]
 
    ]) + ' Z'
 
    @cursorPath.bot.setAttribute 'd', svgPathFromPoints([
 
      [xZoomedIn, y2 + h2]
 
      [xZoomedIn, @offsetParent.offsetHeight]
 
    ])
 

	
 
  setAdjusterConnector: (uri, center, target) ->
 
    id = uri + '/adj'
 
    if not @anyPointsInView([center, target])
 
      @clearElem(id, [''])
 
      return
 
    elem = @getOrCreateElem(uri, 'connectors', 'path', {style: "fill:none;stroke:#d4d4d4;stroke-width:0.9282527;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2.78475821, 2.78475821;stroke-dashoffset:0;"})
 
    elem.setAttribute('d', svgPathFromPoints([center, target]))
0 comments (0 inline, 0 general)