Changeset - 4a751aaaee52
[Not reviewed]
default
0 2 0
Drew Perttula - 8 years ago 2017-06-09 12:29:35
drewp@bigasterisk.com
big timeline rewrites. hopefully it's faster and less leaky
Ignore-this: f11cbe9b5f0950af7edfb91682470e60
2 files changed with 138 insertions and 102 deletions:
0 comments (0 inline, 0 general)
light9/web/graph.coffee
Show inline comments
 
@@ -219,12 +219,20 @@ class window.SyncedGraph
 
    @_watchers.unsubscribe(subscription)
 

	
 
  runHandler: (func, label) ->
 
    # runs your func once, tracking graph calls. if a future patch
 
    # matches what you queried, we runHandler your func again (and
 
    # forget your queries from the first time).
 

	
 
    # helps with memleak? not sure yet. The point was if two matching
 
    # labels get puushed on, we should run only one. So maybe
 
    # appending a serial number is backwards.
 
    @serial = 1 if not @serial
 
    @serial += 1
 
    #label = label + @serial
 
    
 
    @_autoDeps.runHandler(func, label)
 

	
 
  _singleValue: (s, p) ->
 
    @_autoDeps.askedFor(s, p, null, null)
 
    quads = @graph.findByIRI(s, p)
 
    objs = new Set(q.object for q in quads)
 
@@ -239,12 +247,13 @@ class window.SyncedGraph
 
        throw new Error("too many different values: " + JSON.stringify(quads))
 

	
 
  floatValue: (s, p) ->
 
    key = s + '|' + p
 
    hit = @cachedFloatValues.get(key)
 
    return hit if hit != undefined
 
    #log('float miss', s, p)
 

	
 
    ret = parseFloat(N3.Util.getLiteralValue(@_singleValue(s, p)))
 
    @cachedFloatValues.set(key, ret)
 
    return ret
 
    
 
  stringValue: (s, p) ->
light9/web/timeline/timeline.coffee
Show inline comments
 
@@ -221,13 +221,12 @@ Polymer
 
    shortcut.add "L", =>
 
      @$.adjustersCanvas.updateAllCoords()
 
    shortcut.add 'Delete', =>
 
      for note in @selection.selected()
 
        deleteNote(@graph, @song, note, @selection)
 

	
 

	
 
  makeZoomAdjs: ->
 
    yMid = => @$.audio.offsetTop + @$.audio.offsetHeight / 2
 
    dur = @viewState.zoomSpec.duration
 
    
 
    valForPos = (pos) =>
 
        x = pos.e(1)
 
@@ -397,13 +396,13 @@ Polymer
 
    @graph.runHandler(@update.bind(@), "row notes #{@rowIndex}")
 
  update: (patch) ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    notesForThisRow = []
 
    i = 0
 
    for n in _.sortBy(@graph.objects(@song, U(':note')))
 
    for n in _.sortBy(@graph.objects(@song, U(':note')), 'uri')
 
      if (i % ROW_COUNT) == @rowIndex
 
        notesForThisRow.push(n)
 
      i++
 

	
 
    updateChildren @, notesForThisRow, (newUri) =>
 
      child = document.createElement('light9-timeline-note')
 
@@ -418,23 +417,12 @@ Polymer
 

	
 
  onZoom: ->
 
    for e in @children
 
      e.zoomInX = @zoomInX
 

	
 

	
 
getCurvePoints = (graph, curve, xOffset) ->
 
  worldPts = []
 
  uris = graph.objects(curve, graph.Uri(':point'))
 
  for pt in uris
 
    v = $V([xOffset + graph.floatValue(pt, graph.Uri(':time')),
 
            graph.floatValue(pt, graph.Uri(':value'))])
 
    v.uri = pt
 
    worldPts.push(v)
 
  worldPts.sort((a,b) -> a.e(1) > b.e(1))
 
  return [uris, worldPts]
 

	
 
Polymer
 
  is: 'light9-timeline-note'
 
  behaviors: [ Polymer.IronResizableBehavior ]
 
  listeners: 'iron-resize': 'update'
 
  properties:
 
    graph: { type: Object, notify: true }
 
@@ -446,17 +434,17 @@ Polymer
 
    inlineRect: { type: Object, notify: true }
 
  observers: [
 
    'onUri(graph, dia, uri, zoomInX, setAdjuster)'
 
    'update(graph, dia, uri, zoomInX, setAdjuster)'
 
    ]
 
  ready: ->
 
    @adjusterIds = {}
 
    @adjusterIds = {} # id : true
 

	
 
  detached: ->
 
    log('detatch', @uri)
 
    @dia.clearElem(@uri, ['/area', '/label'])
 
    @dia.clearNote(@uri)
 
    @isDetached = true
 
    @clearAdjusters()
 

	
 
  clearAdjusters: ->
 
    for i in Object.keys(@adjusterIds)
 
      @setAdjuster(i, null)
 
@@ -474,116 +462,141 @@ Polymer
 
          timeEditFor = add.subject
 
          if @worldPts and timeEditFor not in @pointUris
 
            return false
 
    return true
 
            
 
  update: (patch) ->
 
    # update our note DOM and SVG elements based on the graph
 
    if not @patchCouldAffectMe(patch)
 
      # as autodep still fires all handlers on all patches, we just
 
      # need any single dep to cause another callback. (without this,
 
      # we would no longer be registered at all)
 
      @graph.subjects(@uri, @uri, @uri)
 
      return
 
    if @isDetached?
 
      return
 
 
 
    @updateDisplay()
 

	
 
  updateDisplay: ->
 
      
 
    # update our note DOM and SVG elements based on the graph
 
    @_updateDisplay()
 

	
 
  _updateDisplay: ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    # @offsetTop causes some CSS layout to run!
 
    yForV = (v) => @offsetTop + (1 - v) * @offsetHeight
 

	
 
    originTime = @graph.floatValue(@uri, U(':originTime'))
 
    effect = @graph.uriValue(@uri, U(':effectClass'))
 
    for curve in @graph.objects(@uri, U(':curve'))
 
      if @graph.uriValue(curve, U(':attr')) == U(':strength')
 
        @updateStrengthCurveEtc(originTime, curve, yForV, effect)
 
        
 
  updateStrengthCurveEtc: (originTime, curve, yForV, effect) ->
 
    U = (x) => @graph.Uri(x)
 
    [@pointUris, @worldPts] = getCurvePoints(@graph, curve, originTime) # (song time, value)
 

	
 
    curveWidth = =>
 
      tMin = @graph.floatValue(@worldPts[0].uri, U(':time'))
 
      tMax = @graph.floatValue(@worldPts[3].uri, U(':time'))
 
      tMax - tMin            
 
        [@pointUris, @worldPts] = @_getCurvePoints(curve, originTime)
 
        curveWidthCalc = () => @_curveWidth(@worldPts)
 
    
 
        screenPts = ($V([@zoomInX(pt.e(1)), @offsetTop + (1 - pt.e(2)) * @offsetHeight]) for pt in @worldPts)
 
    
 
        @dia.setNote(@uri, screenPts, effect)
 
        @_updateAdjusters(screenPts, curveWidthCalc, yForV)
 
        @_updateInlineAttrs(screenPts)
 
        
 
  _updateAdjusters: (screenPts, curveWidthCalc, yForV) ->   
 
    if screenPts[screenPts.length - 1].e(1) - screenPts[0].e(1) < 100
 
      @clearAdjusters()
 
    else
 
      @_makeOffsetAdjuster(yForV, curveWidthCalc)
 
      @_makeCurvePointAdjusters(yForV, @worldPts)
 

	
 
    screenPts = ($V([@zoomInX(pt.e(1)), @offsetTop + (1 - pt.e(2)) * @offsetHeight]) for pt in @worldPts)
 
    @dia.setNote(@uri, screenPts, effect)
 

	
 
  _updateInlineAttrs: (screenPts) ->
 
    leftX = Math.max(2, screenPts[Math.min(1, screenPts.length - 1)].e(1) + 5)
 
    rightX = screenPts[Math.min(2, screenPts.length - 1)].e(1) - 5
 
    if screenPts.length < 3
 
      rightX = leftX + 120
 
    w = 430
 
    h = 80
 
    wasHidden = @inlineRect?.display == 'none'
 
    @inlineRect = {
 
      left: leftX,
 
      top: @offsetTop + @offsetHeight - h - 5,
 
      width: w,
 
      height: h,
 
      display: if rightX - leftX > w then 'block' else 'none'
 
      }
 
    if @inlineRect.display != 'none'
 
    if wasHidden and @inlineRect.display != 'none'
 
      @async =>
 
        @querySelector('light9-timeline-note-inline-attrs')?.displayed()
 
    
 
  _getCurvePoints: (curve, xOffset) ->
 
    worldPts = []
 
    uris = @graph.objects(curve, @graph.Uri(':point'))
 
    for pt in uris
 
      v = $V([xOffset + @graph.floatValue(pt, @graph.Uri(':time')),
 
              @graph.floatValue(pt, @graph.Uri(':value'))])
 
      v.uri = pt
 
      worldPts.push(v)
 
    worldPts.sort((a,b) -> a.e(1) > b.e(1))
 
    return [uris, worldPts]
 

	
 
    if screenPts[screenPts.length - 1].e(1) - screenPts[0].e(1) < 100
 
      @clearAdjusters()
 
      # also kill their connectors
 
      return
 
  _curveWidth: (worldPts) ->
 
    tMin = @graph.floatValue(worldPts[0].uri, @graph.Uri(':time'))
 
    tMax = @graph.floatValue(worldPts[3].uri, @graph.Uri(':time'))
 
    tMax - tMin
 
    
 
  _makeCurvePointAdjusters: (yForV, worldPts) ->
 
    for pointNum in [0, 1, 2, 3]
 
      @_makePointAdjuster(yForV, worldPts, pointNum)
 

	
 
    @makeCurveAdjusters(curveWidth, yForV, @worldPts)
 
    
 
  makeCurveAdjusters: (curveWidth, yForV, worldPts) ->
 
  _makePointAdjuster: (yForV, worldPts, pointNum) ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    if true
 
      @adjusterIds[@uri+'/offset'] = true
 
      @setAdjuster(@uri+'/offset', => new AdjustableFloatObject({
 
    adjId = @uri + '/p' + pointNum
 
    @adjusterIds[adjId] = true
 
    @setAdjuster adjId, =>
 
      adj = new AdjustableFloatObject({
 
        graph: @graph
 
        subj: worldPts[pointNum].uri
 
        pred: U(':time')
 
        ctx: U(@song)
 
        getTargetPosForValue: (value) =>
 
          $V([@zoomInX(value), yForV(worldPts[pointNum].e(2))])
 
        getValueForPos: (pos) =>
 
          origin = @graph.floatValue(@uri, U(':originTime'))
 
          (@zoomInX.invert(pos.e(1)) - origin)
 
        getSuggestedTargetOffset: () => @_suggestedOffset(worldPts[pointNum]),
 
      })
 
      adj._getValue = (=>
 
        # note: don't use originTime from the closure- we need the
 
        # graph dependency
 
        adj._currentValue + @graph.floatValue(@uri, U(':originTime'))
 
        )
 
      adj
 

	
 
  _makeOffsetAdjuster: (yForV, curveWidthCalc) ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    adjId = @uri + '/offset'
 
    @adjusterIds[adjId] = true
 
    @setAdjuster adjId, => 
 
      adj = new AdjustableFloatObject({
 
        graph: @graph
 
        subj: @uri
 
        pred: U(':originTime')
 
        ctx: U(@song)
 
        getDisplayValue: (v, dv) => "o=#{dv}"
 
        getTargetPosForValue: (value) =>
 
          # display bug: should be working from pt[0].t, not from origin
 
          $V([@zoomInX(value + curveWidth() / 2), yForV(.5)])
 
          $V([@zoomInX(value + curveWidthCalc() / 2), yForV(.5)])
 
        getValueForPos: (pos) =>
 
          @zoomInX.invert(pos.e(1)) - curveWidth() / 2
 
          @zoomInX.invert(pos.e(1)) - curveWidthCalc() / 2
 
        getSuggestedTargetOffset: () => $V([-10, 0])
 
      }))
 

	
 
    for pointNum in [0, 1, 2, 3]
 
      do (pointNum) =>
 
        @adjusterIds[@uri+'/p'+pointNum] = true
 
        @setAdjuster(@uri+'/p'+pointNum, =>
 
            adj = new AdjustableFloatObject({
 
              graph: @graph
 
              subj: worldPts[pointNum].uri
 
              pred: U(':time')
 
              ctx: U(@song)
 
              getTargetPosForValue: (value) =>
 
                $V([@zoomInX(value),
 
                    yForV(worldPts[pointNum].e(2))])
 
              getValueForPos: (pos) =>
 
                origin = @graph.floatValue(@uri, U(':originTime'))
 
                (@zoomInX.invert(pos.e(1)) - origin)
 
              getSuggestedTargetOffset: () => $V([0, (if worldPts[pointNum].e(2) > .5 then 30 else -30)])
 
            })
 
            adj._getValue = (=>
 
              # note: don't use originTime from the closure- we need the
 
              # graph dependency
 
              adj._currentValue + @graph.floatValue(@uri, U(':originTime'))
 
              )
 
            adj
 
          )
 

	
 
      })
 
      adj
 
    
 
  _suggestedOffset: (pt) ->
 
    if pt.e(2) > .5
 
      $V([0, 30])
 
    else
 
      $V([0, -30])
 
    
 
    
 

	
 
Polymer
 
  is: "light9-timeline-note-inline-attrs"
 
  properties:
 
@@ -630,13 +643,13 @@ Polymer
 
      @graph.applyAndSendPatch(patch)
 
    
 
  addHandler: ->
 
    @graph.runHandler(@update.bind(@))
 
    
 
  update: ->
 
    console.time('attrs update')
 
    #console.time('attrs update')
 
    U = (x) => @graph.Uri(x)
 
    @effect = @graph.uriValue(@uri, U(':effectClass'))
 
    @effectLabel = @graph.stringValue(@effect, U('rdfs:label')) or (@effect.replace(/.*\//, ''))
 
    @noteLabel = @uri.replace(/.*\//, '')
 

	
 
    existingColorScaleSetting = null
 
@@ -647,13 +660,13 @@ Polymer
 
        @colorScaleFromGraph = value
 
        @colorScale = value
 
        existingColorScaleSetting = setting
 
    if existingColorScaleSetting == null
 
      @colorScaleFromGraph = '#ffffff'
 
      @colorScale = '#ffffff'
 
    console.timeEnd('attrs update')
 
    #console.timeEnd('attrs update')
 

	
 

	
 
  onDel: ->
 
    deleteNote(@graph, @song, @uri, @selection)
 

	
 

	
 
@@ -778,13 +791,13 @@ Polymer
 
        delete @adjs[adjId]
 
      else
 
        adj = makeAdjustable()
 
        @adjs[adjId] = adj
 
        adj.id = adjId
 

	
 
    @redraw()
 
    @debounce('adj redraw', @redraw.bind(@))
 

	
 
    window.debug_adjsCount = Object.keys(@adjs).length
 

	
 
  updateAllCoords: ->
 
    @redraw()
 

	
 
@@ -912,67 +925,81 @@ Polymer
 
      for k,v of attrs
 
        elem.setAttribute(k, v)
 
      if moreBuild
 
        moreBuild(elem)
 
    return elem
 

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

	
 
  anyPointsInView: (pts) ->
 
  _anyPointsInView: (pts) ->
 
    for pt in pts
 
      # wrong:
 
      if pt.e(1) > -100 && pt.e(1) < 2500
 
        return true
 
    return false
 
    
 
  setNote: (uri, curvePts, effect, classes) ->
 
  setNote: (uri, curvePts, effect) ->
 
    @debounce("setNote #{uri}", () => @_setNoteThrottle(uri, curvePts, effect))
 
    
 
  _setNoteThrottle: (uri, curvePts, effect) ->
 
    areaId = uri + '/area'
 
    labelId = uri + '/label'
 
    if not @anyPointsInView(curvePts)
 
      @clearElem(uri, ['/area', '/label'])
 
    if not @_anyPointsInView(curvePts)
 
      @clearNote(uri)
 
      return
 
    # for now these need to be pretty transparent since they're
 
    # drawing on top of the inline-attrs widget :(
 

	
 
    attrs = @_noteAttrs(effect)
 
    elem = @getOrCreateElem areaId, 'notes', 'path', attrs, (elem) =>
 
      @_addNoteListeners(elem, uri)
 
    elem.setAttribute('d', Drawing.svgPathFromPoints(curvePts))
 
    @_updateNotePathClasses(uri, elem)
 

	
 
  _addNoteListeners: (elem, uri) ->
 
    log("new note listeneers", uri)
 
    elem.addEventListener 'mouseenter', =>
 
      @selection.hover(uri)
 
    elem.addEventListener 'mousedown', (ev) =>
 
      sel = @selection.selected()
 
      if ev.getModifierState('Control')
 
        if uri in sel
 
          sel = _.without(sel, uri)
 
        else
 
          sel.push(uri)
 
      else
 
        sel = [uri]
 
      @selection.selected(sel)
 
    elem.addEventListener 'mouseleave', =>
 
      @selection.hover(null)
 

	
 
  _noteAttrs: (effect) ->
 
    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
 

	
 
    attrs = {style: "fill:hsla(#{hue}, #{sat}%, 58%, 0.313);"}
 
    elem = @getOrCreateElem areaId, 'notes', 'path', attrs, (elem) =>
 
      elem.addEventListener 'mouseenter', =>
 
        @selection.hover(uri)
 
      elem.addEventListener 'mousedown', (ev) =>
 
        sel = @selection.selected()
 
        if ev.getModifierState('Control')
 
          if uri in sel
 
            sel = _.without(sel, uri)
 
          else
 
            sel.push(uri)
 
        else
 
          sel = [uri]
 
        @selection.selected(sel)
 
      elem.addEventListener 'mouseleave', =>
 
        @selection.hover(null)
 
    elem.setAttribute('d', Drawing.svgPathFromPoints(curvePts))
 
    @updateNotePathClasses(uri, elem)
 
    {style: "fill:hsla(#{hue}, #{sat}%, 58%, 0.313);"}
 

	
 
  clearNote: (uri) ->
 
    @_clearElem(uri, ['/area'])
 

	
 
  updateNotePathClasses: (uri, elem) ->
 
  _noteInDiagram: (uri) ->
 
    return !!@elemById[uri + '/area']
 

	
 
  _updateNotePathClasses: (uri, elem) ->
 
    ko.computed =>
 
      return if not @_noteInDiagram(uri)
 
      classes = 'light9-timeline-diagram-layer ' + (if @selection.hover() == uri then 'hover' else '') + ' '  + (if uri in @selection.selected() then 'selected' else '')
 
      elem.setAttribute('class', classes)
 
    
 
    #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)
0 comments (0 inline, 0 general)