changeset 1415:edc17fdcaaf1

big rewrite of adjuster update code. origin & p3 adjusters kind of work. adj layout slightly works Ignore-this: d8b55365cc1ebf43e76aec5fc9f5ec9e
author Drew Perttula <drewp@bigasterisk.com>
date Fri, 10 Jun 2016 11:18:02 +0000
parents c35ec37c3c6e
children ab7b40d20af0
files light9/web/graph.coffee light9/web/timeline/adjustable.coffee light9/web/timeline/timeline-elements.html light9/web/timeline/timeline.coffee
diffstat 4 files changed, 153 insertions(+), 83 deletions(-) [+]
line wrap: on
line diff
--- a/light9/web/graph.coffee	Fri Jun 10 06:56:34 2016 +0000
+++ b/light9/web/graph.coffee	Fri Jun 10 11:18:02 2016 +0000
@@ -95,8 +95,10 @@
     rerunInners = (cur) =>
       toRun = cur.innerHandlers.slice()
       for child in toRun
-        child.innerHandlers = [] # let all children get called again
+
+        #child.innerHandlers = [] # let all children get called again
         @_rerunHandler(child)
+        rerunInners(child)
     rerunInners(@handlers)
 
   askedFor: (s, p, o, g) ->
--- a/light9/web/timeline/adjustable.coffee	Fri Jun 10 06:56:34 2016 +0000
+++ b/light9/web/timeline/adjustable.coffee	Fri Jun 10 11:18:02 2016 +0000
@@ -15,13 +15,19 @@
     #   getTarget -> vec2 of current target position
     #   getSuggestedTargetOffset -> vec2 pixel offset from target
     #   emptyBox -> true if you want no value display
+    
+    # updated later by layout algoritm
+    @centerOffset = $V([0, 0])
 
   getDisplayValue: () ->
     return '' if @config.emptyBox
     d3.format(".4g")(@_getValue())
 
+  getSuggestedCenter: () ->
+    @getTarget().add(@config.getSuggestedTargetOffset())
+
   getCenter: () -> # vec2 of pixels
-    @getTarget().add(@config.getSuggestedTargetOffset())
+    @getTarget().add(@centerOffset)
 
   getTarget: () -> # vec2 of pixels
     @config.getTarget()
@@ -32,17 +38,17 @@
     throw new Error('not implemented')
 
   startDrag: () ->
-    # override
+    @initialTarget = @getTarget()
 
   continueDrag: (pos) ->
-    # pos is vec2 of pixels relative to the drag start
-
-    # override
+    ## pos is vec2 of pixels relative to the drag start
+    @targetDraggedTo = pos.add(@initialTarget)
     
   endDrag: () ->
     # override
 
   _editorCoordinates: () -> # vec2 of mouse relative to <l9-t-editor>
+    return @targetDraggedTo
     ev = d3.event.sourceEvent
 
     if ev.target.tagName == "LIGHT9-TIMELINE-EDITOR"
@@ -66,13 +72,14 @@
     #   observable -> ko.observable we will read and write
     #   getValueForPos(pos) -> what should we set to if the user
     #                          moves target to this coord?
+    super(@config)
 
   _getValue: () ->
     @config.observable()
     
   continueDrag: (pos) ->
     # pos is vec2 of pixels relative to the drag start.
-
+    super(pos)
     epos = @_editorCoordinates()
     newValue = @config.getValueForPos(epos)
     @config.observable(newValue)
@@ -89,7 +96,7 @@
     #   subj
     #   pred
     #   ctx
-    #   getTargetTransform(value) -> getTarget result for value
+    #   getTargetPosForValue(value) -> getTarget result for value
     #   getValueForPos
 
     super(@config)
@@ -105,7 +112,7 @@
     @_currentValue
 
   getTarget: () ->
-    @config.getTargetTransform(@_getValue())
+    @config.getTargetPosForValue(@_getValue())
     
   subscribe: (onChange) ->
     # only works on one subscription at a time
@@ -115,7 +122,7 @@
     
   continueDrag: (pos) ->
     # pos is vec2 of pixels relative to the drag start
-    
+    super(pos)
     newValue = @config.getValueForPos(@_editorCoordinates())
     
     @config.graph.patchObject(@config.subj, @config.pred,
--- a/light9/web/timeline/timeline-elements.html	Fri Jun 10 06:56:34 2016 +0000
+++ b/light9/web/timeline/timeline-elements.html	Fri Jun 10 11:18:02 2016 +0000
@@ -52,6 +52,7 @@
     <light9-timeline-time-zoomed id="zoomed"
                                  graph="{{graph}}"
                                  dia="{{dia}}"
+                                 set-adjuster="{{setAdjuster}}"
                                  song="{{song}}"
                                  show="{{show}}"
                                  zoom="{{viewState.zoomSpec}}"
@@ -62,7 +63,7 @@
                                dia="{{dia}}"
                                graph="{{graph}}"
                                song="{{song}}"
-                               parent-adjs="{{adjs}}"
+                               set-adjuster="{{setAdjuster}}"
                                zoom-in-x="{{zoomInX}}">
     </light9-timeline-adjusters>
   </template>
@@ -105,6 +106,7 @@
         <template is="dom-repeat" items="{{rows}}">
           <light9-timeline-graph-row graph="{{graph}}"
                                      dia="{{dia}}"
+                                     set-adjuster="{{setAdjuster}}"
                                      song="{{song}}"
                                      zoom-in-x="{{zoomInX}}"
                                      row-index="{{item}}"
@@ -194,6 +196,7 @@
       <light9-timeline-note graph="{{graph}}"
                             dia="{{dia}}"
                             uri="{{item}}"
+                            set-adjuster="{{setAdjuster}}"
                             song="{{song}}"
                             zoom-in-x="{{zoomInX}}">
       </light9-timeline-note>
@@ -232,10 +235,8 @@
      }
 
     </style>
-    <template is="dom-repeat" items="{{adjs}}">
-      <light9-timeline-adjuster dia="{{dia}}"
-                                adj="{{item}}"></light9-timeline-adjuster>
-    </template>
+    <div id="all">
+    </div>
   </template>
 </dom-module>
 
--- a/light9/web/timeline/timeline.coffee	Fri Jun 10 06:56:34 2016 +0000
+++ b/light9/web/timeline/timeline.coffee	Fri Jun 10 11:18:02 2016 +0000
@@ -8,6 +8,7 @@
     viewState: { type: Object }
     debug: {type: String}
     graph: {type: Object, notify: true}
+    setAdjuster: {type: Function, notify: true}
     playerSong: {type: String, notify: true}
     followPlayerSong: {type: Boolean, notify: true, value: true}
     song: {type: String, notify: true}
@@ -44,17 +45,19 @@
         pos: ko.observable($V([0,0]))
     @fullZoomX = d3.scaleLinear()
     @zoomInX = d3.scaleLinear()
+    @setAdjuster = @$.adjusters.setAdjuster.bind(@$.adjusters)
+    
   attached: ->
     @dia = @$.dia
     ko.computed(@zoomOrLayoutChanged.bind(@)).extend({rateLimit: 5})
     ko.computed(@songTimeChanged.bind(@))
 
-    @adjs = @makeZoomAdjs()
-
     @trackMouse()
     @bindKeys()
     @bindWheelZoom()
 
+    @makeZoomAdjs()
+
   zoomOrLayoutChanged: ->
     @fullZoomX.domain([0, @viewState.zoomSpec.duration()])
     @fullZoomX.range([0, @width()])
@@ -154,27 +157,28 @@
                       newCenter + visSeconds / 2, zoomAnimSec)
 
   makeZoomAdjs: ->
+    log('makeZoomAdjs', @adjs)
     yMid = @$.audio.offsetTop + @$.audio.offsetHeight / 2
     dur = @viewState.zoomSpec.duration
     
     valForPos = (pos) =>
         x = pos.e(1)
         t = @fullZoomX.invert(x)
-    left = new AdjustableFloatObservable({
+    @setAdjuster('zoom-left', new AdjustableFloatObservable({
       observable: @viewState.zoomSpec.t1,
       getTarget: () =>
         $V([@fullZoomX(@viewState.zoomSpec.t1()), yMid])
       getSuggestedTargetOffset: () => $V([-50, 0])
       getValueForPos: valForPos
-    })
+    }))
 
-    right = new AdjustableFloatObservable({
+    @setAdjuster('zoom-right', new AdjustableFloatObservable({
       observable: @viewState.zoomSpec.t2,
       getTarget: () =>
         $V([@fullZoomX(@viewState.zoomSpec.t2()), yMid])
       getSuggestedTargetOffset: () => $V([50, 0])
       getValueForPos: valForPos
-    })
+    }))
 
     panObs = ko.pureComputed({
         read: () =>
@@ -186,7 +190,7 @@
           zs.t2(value + span / 2)
       })
 
-    pan = new AdjustableFloatObservable({
+    @setAdjuster('zoom-pan', new AdjustableFloatObservable({
       observable: panObs
       emptyBox: true
       # fullzoom is not right- the sides shouldn't be able to go
@@ -194,11 +198,8 @@
       getTarget: () => $V([@fullZoomX(panObs()), yMid])
       getSuggestedTargetOffset: () => $V([0, 0])
       getValueForPos: valForPos
-      })
+      }))
       
-    return [left, right, pan]
-
-
 
 Polymer
   is: 'light9-timeline-time-zoomed'
@@ -288,6 +289,17 @@
     for note in @graph.objects(@song, U(':note'))
       @push('noteUris', note)
 
+
+getCurvePoints = (graph, curve, xOffset) ->
+  worldPts = []
+  for pt in graph.objects(curve, graph.Uri(':point'))
+    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 worldPts
+
 Polymer
   is: 'light9-timeline-note'
   behaviors: [ Polymer.IronResizableBehavior ]
@@ -297,9 +309,10 @@
     dia: { type: Object, notify: true }
     uri: { type: String, notify: true }
     zoomInX: { type: Object, notify: true }
+    setAdjuster: {type: Function, notify: true}
   observers: [
     'onUri(graph, dia, uri)'
-    'update(graph, dia, uri, zoomInX)'
+    'update(graph, dia, uri, zoomInX, setAdjuster)'
     ]
   ready: ->
 
@@ -318,15 +331,32 @@
       originTime = @graph.floatValue(@uri, U(':originTime'))
       for curve in @graph.objects(@uri, U(':curve'))
         if @graph.uriValue(curve, U(':attr')) == U(':strength')
+          worldPts = getCurvePoints(@graph, curve, originTime)
+          @setAdjuster(@uri+'/offset', new AdjustableFloatObject({
+            graph: @graph
+            subj: @uri
+            pred: @graph.Uri(':originTime')
+            ctx: @graph.Uri(@song)
+            getTargetPosForValue: (value) => $V([@zoomInX(value), 600])
+            getValueForPos: (pos) => @zoomInX.invert(pos.e(1))
+            getSuggestedTargetOffset: () => $V([0, -80])
+          }))
+         
+          @setAdjuster(@uri+'/p3', adj = new AdjustableFloatObject({
+            graph: @graph
+            subj: worldPts[3].uri
+            pred: @graph.Uri(':time')
+            ctx: @graph.Uri(@song)
+            getTargetPosForValue: (value) => $V([@zoomInX(value), 600])
+            getValueForPos: (pos) => (@zoomInX.invert(pos.e(1)) - originTime)
+            getSuggestedTargetOffset: () => $V([0, -80])
+          }))
+          adj._getValue = (=>
+            # note: don't use originTime from the closure- we need the
+            # graph dependency
+            adj._currentValue + @graph.floatValue(@uri, U(':originTime'))
+            )
           
-          for pt in @graph.objects(curve, U(':point'))
-
-            worldPts.push($V([
-              originTime + @graph.floatValue(pt, U(':time')),
-              @graph.floatValue(pt, U(':value'))
-              ]))
-      worldPts.sort((a,b) -> a.e(1) > b.e(1))
-
       screenPos = (pt) =>
         $V([@zoomInX(pt.e(1)), @offsetTop + (1 - pt.e(2)) * @offsetHeight])
 
@@ -339,76 +369,106 @@
 Polymer
   is: "light9-timeline-adjusters"
   properties:
-    adjs: { type: Array }, # our computed list
-    parentAdjs: { type: Array }, # incoming requests
-    graph: { type: Object, notify: true }
-    song: { type: String, notify: true }
-    zoomInX: { type: Object, notify: true }
+    adjs: { type: Object, notify: true }, # adjId: Adjustable
     dia: { type: Object }
-  observers: [
-    'update(parentAdjs, graph, song, dia)'
-    'onGraph(graph, song)'
-    ]
-  onGraph: (graph, song, zoomInX) ->
-    graph.runHandler(@update.bind(@), "adjuster update")
-  update: (parentAdjs, graph, song, dia) ->
-    U = (x) -> @graph.Uri(x)
-    @adjs = (@parentAdjs || []).slice()
-    for note in @graph.objects(@song, U(':note'))
-      @push('adjs', new AdjustableFloatObject({
-        graph: @graph
-        subj: note
-        pred: @graph.Uri(':originTime')
-        ctx: @graph.Uri(@song)
-        getTargetTransform: (value) => $V([@zoomInX(value), 600])
-        getValueForPos: (pos) => @zoomInX.invert(pos.e(1))
-        getSuggestedTargetOffset: () => $V([0, -80])
-      }))
+
+  ready: ->
+    @adjs = {}
+    
+  setAdjuster: (adjId, adjustable) ->
+    # callers register/unregister the Adjustables they want us
+    # to make adjuster elements for. Caller invents adjId.
+    adjustable.id = adjId
+    if not @adjs[adjId] or not adjustable?
+      @adjs[adjId] = adjustable
+      @debounce('adjsChanged', @adjsChanged.bind(@), 1)
     
+  adjsChanged: ->
+
+    added = removed = 0
+    newIds = Object.keys(@adjs)
+
+    parent = @$.all
+    haveIds = []
+    for c in parent.children
+      id = c.getAttribute('id')
+      if newIds.indexOf(id) == -1
+        c.remove()
+        removed++
+        # ...?
+      else
+        haveIds.push(id)
+
+    for id, adj of @adjs
+      if haveIds.indexOf(id) == -1
+        log('need new one for', id)
+        child = document.createElement('light9-timeline-adjuster')
+        child.dia = @dia
+        child.graph = @graph
+        child.setAttribute('id', id)
+        child.adj = adj # set this  last
+        parent.appendChild(child)
+        added++
+    log("adjsChanged removed #{removed} added #{added}") if added or removed
+
+  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()
     
 
-_adjusterSerial = 0
-
 Polymer
   is: 'light9-timeline-adjuster'
   properties:
-    adj:
-      type: Object
-      notify: true
-      observer: 'onAdj'
-    target:
-      type: Object
-      notify: true
-    displayValue:
-      type: String
-    centerStyle:
-      type: Object
-    spanClass:
-      type: String
-      value: ''
+    graph: { type: Object, notify: true }
+    adj: { type: Object, notify: true, observer: 'onAdj' }
+    target: { type: Object, notify: true }
+    displayValue: { type: String }
+    centerStyle: { type: Object }
+    spanClass: { type: String, value: '' }
 
   onAdj: (adj) ->
-    # currently I think this subscription never gets to matter since
-    # we might be rebuilding all adj elements on every update
+    log('onAdj', @id)
     @adj.subscribe(@updateDisplay.bind(this))
-    @updateDisplay()
+    @graph.runHandler(@updateDisplay.bind(@))
 
   updateDisplay: () ->
+    log('updateDisplay', @id)
     @spanClass = if @adj.config.emptyBox then 'empty' else ''
     @displayValue = @adj.getDisplayValue()
     center = @adj.getCenter()
     target = @adj.getTarget()
+    log('ct', center.elements, target.elements)
     return if isNaN(center.e(1))
     @centerStyle = {x: center.e(1), y: center.e(2)}
-    @dia?.setAdjusterConnector(@myId, center, target)
+    @dia?.setAdjusterConnector(@adj.id + '/conn', center, target)
         
   attached: ->
-    @myId = 'adjuster-' + _adjusterSerial
-    _adjusterSerial += 1
-    
     drag = d3.drag()
     sel = d3.select(@$.label)
     sel.call(drag)
@@ -422,7 +482,7 @@
     @updateDisplay()
 
   detached: ->
-    @dia.clearElem(@myId)
+    @dia.clearElem(@adj.id + '/conn')
 
 
 svgPathFromPoints = (pts) ->