Changeset - 7cb53b510a6a
[Not reviewed]
default
0 5 0
Drew Perttula - 7 years ago 2018-05-18 07:54:42
drewp@bigasterisk.com
draw notes, colors. position adjusters better.
Ignore-this: 1ccdb7109cfa67cc0486d9418b42be4e
5 files changed with 77 insertions and 56 deletions:
0 comments (0 inline, 0 general)
light9/web/timeline/adjustable.coffee
Show inline comments
 
@@ -65,97 +65,97 @@ class Adjustable
 
    if ev.touches?.length
 
      ev = ev.touches[0]
 
      
 
    # storing root on the object to remember it across calls in case
 
    # you drag outside the editor.
 
    @root = rootElem.getBoundingClientRect() if rootElem
 
    offsetParentPos = $V([ev.pageX - @root.left, ev.pageY - @root.top])
 

	
 
    return offsetParentPos 
 

	
 
class window.AdjustableFloatObservable extends Adjustable
 
  constructor: (@config) ->
 
    # config also has:
 
    #   observable -> ko.observable we will read and write
 
    #   getValueForPos(pos) -> what should we set to if the user
 
    #                          moves target to this coord?
 
    super()
 
    @ctor2()
 

	
 
  _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)
 

	
 
  subscribe: (onChange) ->
 
    log('AdjustableFloatObservable subscribe', @config)
 
    ko.computed =>
 
      @config.observable()
 
      onChange()
 

	
 
class window.AdjustableFloatObject extends Adjustable
 
  constructor: (@config) ->
 
    # config also has:
 
    #   graph
 
    #   subj
 
    #   pred
 
    #   ctx
 
    #   getTargetPosForValue(value) -> getTarget result for value
 
    #   getValueForPos
 
    super()
 
    @ctor2()
 
    if not @config.ctx?
 
      throw new Error("missing ctx")
 
    @config.graph.runHandler(@_syncValue.bind(@), "adj sync #{@config.subj}")
 
    @config.graph.runHandler(@_syncValue.bind(@), "adj sync #{@config.subj.value}")
 

	
 
  _syncValue: () ->
 
    @_currentValue = @config.graph.floatValue(@config.subj, @config.pred)
 
    @_onChange() if @_onChange
 
    
 
  _getValue: () ->
 
    # this is a big speedup- callers use _getValue about 4x as much as
 
    # the graph changes and graph.floatValue is slow
 
    @_currentValue
 

	
 
  getTarget: () ->
 
    @config.getTargetPosForValue(@_getValue())
 
    
 
  subscribe: (onChange) ->
 
    # only works on one subscription at a time
 
    throw new Error('multi subscribe not implemented') if @_onChange
 
    @_onChange = onChange
 

	
 
  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,
 
                              @config.graph.LiteralRoundedFloat(newValue),
 
                              @config.ctx)
 
                              
 
class window.AdjustableFade extends Adjustable
 
  constructor: (@yForV, @i0, @i1, @note, offset, ctx) ->
 
    super()
 
    @config = {
 
      getSuggestedTargetOffset: -> offset
 
      getTarget: @getTarget.bind(@)
 
      ctx: ctx
 
    }
 
    @ctor2()
 

	
 
  getTarget: ->
 
    mid = @note.worldPts[@i0].x(.5).add(@note.worldPts[@i1].x(.5))
 
    $V([@note.zoomInX(mid.e(1)), @yForV(mid.e(2))])
 

	
 
  _getValue: ->
 
    mid = @note.worldPts[@i0].x(.5).add(@note.worldPts[@i1].x(.5))
 
    mid.e(1)
 

	
 
   continueDrag: (pos) ->
 
    # pos is vec2 of pixels relative to the drag start
 
    super(pos)
light9/web/timeline/timeline-elements.html
Show inline comments
 
@@ -23,103 +23,101 @@
 
         border: 1px solid black;
 
         overflow: hidden;
 
     }
 
     light9-timeline-audio {
 
         width: 100%;
 
         height: 30px;
 
     }
 
     light9-timeline-time-zoomed {
 
         flex-grow: 1;
 
     }
 
     #coveredByDiagram {
 
         position: relative;
 
         display: flex;
 
         flex-direction: column;
 
         height: 100%;
 
     }
 
     #dia, #adjusters, #cursorCanvas, #adjustersCanvas {
 
         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>
 
    <div id="coveredByDiagram">
 
      <light9-timeline-audio id="audio"
 
                             graph="{{graph}}"
 
                             show="{{show}}"
 
                             song="{{song}}"></light9-timeline-audio>
 
      <light9-timeline-time-zoomed id="zoomed"
 
                                   graph="{{graph}}"
 
                                   project="{{project}}"
 
                                   selection="{{selection}}"
 
                                   dia="{{dia}}"
 
                                   set-adjuster="{{setAdjuster}}"
 
                                   song="{{song}}"
 
                                   show="{{show}}"
 
                                   view-state="{{viewState}}">
 
      </light9-timeline-time-zoomed>
 
      <light9-timeline-diagram-layer id="dia" selection="{{selection}}"></light9-timeline-diagram-layer>
 
      <light9-adjusters-canvas id="adjustersCanvas" set-adjuster="{{setAdjuster}}">
 
      </light9-adjusters-canvas>
 
      <light9-cursor-canvas id="cursorCanvas" view-state="{{viewState}}"></light9-cursor-canvas>
 
    </div>
 
  </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%;
 
         flex-direction: column;
 
     }
 
     #top {
 
     }
 
     #rows {
 
         height: 100%;
 
     }
 
     #rows.dragging {
 
         background: rgba(126, 52, 245, 0.0784);
 
     }
 
     light9-timeline-time-axis {
 
     }
 
     light9-timeline-audio {
 
         width: 100%;
 
         height: 100px;
 
     }
 
     light9-timeline-graph-row {
 
         flex-grow: 1;
 
     }
 
    </style>
 
    <div id="top">
 
      <light9-timeline-time-axis id="time" view-state="{{viewState}}"></light9-timeline-time-axis>
 
      <light9-timeline-audio id="audio"
 
                             graph="{{graph}}"
 
                             song="{{song}}"
 
                             show="{{show}}"
 
                             zoom="{{zoomFlattened}}">
 
      </light9-timeline-audio>
 
    </div>
 
    <div id="rows"></div>
 
  </template>
 
</dom-module>
 

	
 
@@ -227,56 +225,57 @@
 
     We compute X coords from the zoom setting.
 
     diagram-layer draws the note body. -->
 
<dom-module id="light9-timeline-note">
 
  <template>
 
    <style>
 
     :host {
 
         display: block;
 
         background: green;
 
         /* outline: 2px solid red; */
 
     }
 
    </style>
 
    <light9-timeline-note-inline-attrs rect="{{inlineRect}}"
 
                                       graph="{{graph}}"
 
                                       selection="{{selection}}"
 
                                       song="{{song}}"
 
                                       uri="{{uri}}"
 
                                       effect="{{effect}}"
 
    >
 
    </light9-timeline-note-inline-attrs>
 
  </template>
 
</dom-module>
 

	
 
<!-- 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.
 
     Maybe manages the layout of other labels and text too, to avoid overlaps.
 
   -->
 
<dom-module id="light9-timeline-adjusters">
 
  <template>
 
    <style>
 
     :host {
 
         pointer-events: none; /* restored on the individual adjusters */
 
     }
 

	
 
    </style>
 
  </template>
 
</dom-module>
 

	
 

	
 
<script src="/lib/async/dist/async.js"></script>
 
<script src="/lib/knockout/dist/knockout.js"></script>
 
<script src="/lib/shortcut/index.js"></script>
 
<script src="/lib/sylvester/sylvester.js"></script>
 
<script src="/lib/underscore/underscore-min.js"></script>
 
<script src="/node_modules/d3/dist/d3.min.js"></script>
 
<script src="/node_modules/n3/n3-browser.js"></script> 
 
<script src="/node_modules/pixi.js/dist/pixi.min.js"></script>
 
<script src="/node_modules/tinycolor2/dist/tinycolor-min.js"></script>
 

	
 
<script src="drawing.js"></script>
 
<script src="../coffee_element.js"></script>
 
<script src="viewstate.js"></script>
 
<script src="adjustable.js"></script>
 
<script src="adjusters.js"></script>
 
<script src="timeline.js"></script>
 
<script src="cursor_canvas.js"></script>
light9/web/timeline/timeline.coffee
Show inline comments
 
@@ -105,96 +105,98 @@ coffeeElementSetup(class TimelineEditor 
 
    followPlayerSong: {type: Boolean, notify: true, value: true}
 
    song: {type: String, notify: true}
 
    show: {value: 'http://light9.bigasterisk.com/show/dance2017'}
 
    songTime: {type: Number, notify: true}
 
    songDuration: {type: Number, notify: true}
 
    songPlaying: {type: Boolean, notify: true}
 
    selection: {type: Object, notify: true}
 
  @getter_observers: [
 
    '_onSong(playerSong, followPlayerSong)',
 
    '_onGraph(graph)',
 
    '_onSongDuration(songDuration, viewState)',
 
    '_onSongTime(songTime, viewState)',
 
    '_onSetAdjuster(setAdjuster)',
 
  ]
 
  constructor: ->
 
    super()
 
    @viewState = new ViewState()
 
    window.viewState = @viewState
 
    
 
  ready: ->
 
    super.ready()
 
    
 
    ko.options.deferUpdates = true;
 
    
 
    @dia = @$.dia
 
     
 
    @selection = {hover: ko.observable(null), selected: ko.observable([])}
 

	
 
    window.debug_zoomOrLayoutChangedCount = 0
 
    window.debug_adjUpdateDisplay = 0
 
    
 
    ko.computed(@zoomOrLayoutChanged.bind(@))
 

	
 
    @trackMouse()
 
    @bindKeys()
 
    @bindWheelZoom(@$.adjustersCanvas)
 

	
 
    setInterval(@updateDebugSummary.bind(@), 100)
 

	
 
    @addEventListener('iron-resize', @_onIronResize.bind(@))
 
    Polymer.RenderStatus.afterNextRender(this, @_onIronResize.bind(@))
 

	
 
    #zoomed = @$.zoomed 
 
    #setupDrop(@dia.shadowRoot.querySelector('svg'),
 
    #          zoomed.$.rows, @, zoomed.onDrop.bind(zoomed))
 
            
 
  _onIronResize: ->
 
    @viewState.setWidth(@offsetWidth)
 
    @viewState.coveredByDiagramTop(@$.coveredByDiagram.offsetTop)
 
    @viewState.rowsY(@$.zoomed.$.rows.offsetTop) if @$.zoomed?.$?.rows?
 
    @viewState.audioY(@$.audio.offsetTop)
 
    @viewState.audioH(@$.audio.offsetHeight)
 
    if @$.zoomed?.$?.time?
 
      @viewState.zoomedTimeY(@$.zoomed.$.time.offsetTop)
 
      @viewState.zoomedTimeH(@$.zoomed.$.time.offsetHeight)
 
    
 
  _onSongTime: (t) ->
 
    @viewState.cursor.t(t)
 
    
 
  _onSongDuration: (d) ->
 
    d = 700 if d < 1 # bug is that asco isn't giving duration, but 0 makes the scale corrupt
 
    @viewState.zoomSpec.duration(d)
 
    
 
  _onSong: (s) ->
 
    @song = @playerSong if @followPlayerSong
 
    
 
  _onGraph: (graph) ->
 
    @project = new Project(graph)
 

	
 
  _onSetAdjuster: () ->
 
    @makeZoomAdjs()
 
    
 
  updateDebugSummary: ->
 
    elemCount = (tag) -> document.getElementsByTagName(tag).length
 
    @debug = "#{window.debug_zoomOrLayoutChangedCount} layout change,
 
     #{elemCount('light9-timeline-note')} notes,
 
     #{@selection.selected().length} selected
 
     #{elemCount('light9-timeline-graph-row')} rows,
 
     #{window.debug_adjsCount} adjuster items registered,
 
     #{window.debug_adjUpdateDisplay} adjuster updateDisplay calls,
 
    "
 

	
 
  zoomOrLayoutChanged: ->
 
    vs = @viewState
 
    dependOn = [vs.zoomSpec.t1(), vs.zoomSpec.t2(), vs.width()]
 

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

	
 
  trackMouse: ->
 
    # not just for show- we use the mouse pos sometimes
 
    for evName in ['mousemove', 'touchmove']
 
      @addEventListener evName, (ev) =>
 
        ev.preventDefault()
 
@@ -240,344 +242,360 @@ coffeeElementSetup(class TimelineEditor 
 
        t = @viewState.fullZoomX.invert(x)
 
    @setAdjuster('zoom-left', => new AdjustableFloatObservable({
 
      observable: @viewState.zoomSpec.t1,
 
      getTarget: () =>
 
        $V([@viewState.fullZoomX(@viewState.zoomSpec.t1()), yMid()])
 
      getSuggestedTargetOffset: () => $V([50, 0])
 
      getValueForPos: valForPos
 
    }))
 

	
 
    @setAdjuster('zoom-right', => new AdjustableFloatObservable({
 
      observable: @viewState.zoomSpec.t2,
 
      getTarget: () =>
 
        $V([@viewState.fullZoomX(@viewState.zoomSpec.t2()), yMid()])
 
      getSuggestedTargetOffset: () => $V([-50, 0])
 
      getValueForPos: valForPos
 
    }))
 

	
 
    panObs = ko.pureComputed({
 
        read: () =>
 
          (@viewState.zoomSpec.t1() + @viewState.zoomSpec.t2()) / 2
 
        write: (value) =>
 
          zs = @viewState.zoomSpec
 
          span = zs.t2() - zs.t1()
 
          zs.t1(value - span / 2)
 
          zs.t2(value + span / 2)
 
      })
 

	
 
    @setAdjuster('zoom-pan', => new AdjustableFloatObservable({
 
      observable: panObs
 
      emptyBox: true
 
      # fullzoom is not right- the sides shouldn't be able to go
 
      # offscreen
 
      getTarget: () => $V([@viewState.fullZoomX(panObs()), yMid()])
 
      getSuggestedTargetOffset: () => $V([0, 0])
 
      getValueForPos: valForPos
 
      }))
 
)
 

	
 

	
 
# plan: in here, turn all the notes into simple js objects with all
 
# their timing data and whatever's needed for adjusters. From that, do
 
# the brick layout. update only changing adjusters.
 
coffeeElementSetup(class TimeZoomed extends Polymer.mixinBehaviors([Polymer.IronResizableBehavior], Polymer.Element)
 
  @is: 'light9-timeline-time-zoomed'
 
  @getter_properties:
 
    graph: { type: Object, notify: true }
 
    project: { type: Object }
 
    selection: { type: Object, notify: true }
 
    dia: { type: Object, notify: true }
 
    song: { type: String, notify: true }
 
    viewState: { type: Object, notify: true }
 
  @getter_observers: [
 
    'onGraph(graph, setAdjuster, song, viewState, project)',
 
    'onZoom(viewState)',
 
    '_onGraph(graph, setAdjuster, song, viewState, project)',
 
  ]
 
  constructor: ->
 
    super()
 
    @notes = []
 
    @stage = new PIXI.Container()
 
    
 
    @renderer = PIXI.autoDetectRenderer({
 
         backgroundColor: 0x606060,
 
        antialias: true,
 
        forceCanvas: true,
 
    })
 
     
 
  ready: ->
 
    super.ready()
 
     
 
    @addEventListener('iron-resize', @update.bind(@))
 
    Polymer.RenderStatus.afterNextRender(this, @update.bind(@))
 
    @addEventListener('iron-resize', @_onResize.bind(@))
 
    Polymer.RenderStatus.afterNextRender(this, @_onResize.bind(@))
 
    
 
    @$.rows.appendChild(@renderer.view);
 
  
 
  update: ->
 
    ko.computed =>
 
      @stage.setTransform(0, -(@viewState.rowsY()), 1, 1, 0, 0, 0, 0, 0)
 
      
 
  _onResize: ->
 
    @renderer.resize(@clientWidth, @clientHeight)
 
    @renderer.render(@stage)
 

	
 
  onZoom: ->
 
    updateZoomFlattened = ->
 
      log('updateZoomFlattened')
 
      @zoomFlattened = ko.toJS(@viewState.zoomSpec)
 
    ko.computed(updateZoomFlattened.bind(@))
 
  
 
  onGraph: ->
 
  _onGraph: ->
 
    @graph.runHandler(@gatherNotes.bind(@), 'zoom notes')
 
    # not working- worked around in zoomOrLayoutChanged
 
    #ko.computed(@gatherNotes.bind(@))
 
    
 
  gatherNotes: ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    log('assign rows',@song, 'graph has', @graph.quads().length)
 
    log('assign rows',@song)
 

	
 
    @stage.children.splice(0)
 
    @stage.removeChildren()
 
    n.destroy() for n in @notes
 
    @notes = []
 
    
 
    noteNum = 0
 
    for uri in _.sortBy(@graph.objects(@song, U(':note')), 'uri')
 
      note = new Note(@project, @graph, @selection, uri, @setAdjuster, @song, @viewState, @stage, @offsetTop + 150 * (noteNum % 4))
 
      note.draw()
 
    for uri in _.sortBy(@graph.objects(@song, U(':note')), 'id')
 
      con = new PIXI.Container()
 
      @stage.addChild(con)
 
      row = noteNum % 6
 
      rowTop = @viewState.rowsY() + 20 + 150 * row
 
      note = new Note(con, @project, @graph, @selection, uri, @setAdjuster, @song, @viewState, rowTop, rowTop + 140)
 
      @notes.push(note)
 
      noteNum = noteNum + 1
 
 
 
    @renderer.render(@stage)
 
    
 
  onDrop: (effect, pos) ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    return unless effect and effect.match(/^http/)
 

	
 
    # we could probably accept some initial overrides right on the
 
    # effect uri, maybe as query params
 

	
 
    if not @graph.contains(effect, RDF + 'type', U(':Effect'))
 
      if @graph.contains(effect, RDF + 'type', U(':LightSample'))
 
        effect = @project.makeEffect(effect)
 
      else
 
        log("drop #{effect} is not an effect")
 
        return
 

	
 
    dropTime = @viewState.zoomInX.invert(pos.e(1))
 

	
 
    desiredWidthX = @offsetWidth * .3
 
    desiredWidthT = @viewState.zoomInX.invert(desiredWidthX) - @viewState.zoomInX.invert(0)
 
    desiredWidthT = Math.min(desiredWidthT, @zoom.duration() - dropTime)
 
    @project.makeNewNote(effect, dropTime, desiredWidthT)
 
)
 

	
 

	
 
coffeeElementSetup(class TimeAxis extends Polymer.Element
 
  @is: "light9-timeline-time-axis",
 
  @getter_properties:
 
    viewState: { type: Object, notify: true, observer: "onViewState" }
 
  onViewState: ->
 
    ko.computed =>
 
      dependOn = [@viewState.zoomSpec.t1(), @viewState.zoomSpec.t2()]
 
      pxPerTick = 50
 
      axis = d3.axisTop(@viewState.zoomInX).ticks(@viewState.width() / pxPerTick)
 
      d3.select(@$.axis).call(axis)
 
)
 

	
 

	
 
# Maintains a pixi object and some adjusters corresponding to a note
 
# in the graph.
 
class Note
 
  constructor: (@project, @graph, @selection, @uri, @setAdjuster, @song, @viewState, @stage, @rowTopY) ->
 
  constructor: (@container, @project, @graph, @selection, @uri, @setAdjuster, @song, @viewState, @rowTopY, @rowBotY) ->
 
    @adjusterIds = {} # id : true
 
    @draw()
 

	
 
  destroy: ->
 
    log('destroy', @uri)
 
    # pixi rm
 
    log('destroy', @uri.value)
 
    @isDetached = true
 
    @clearAdjusters()
 

	
 
  clearAdjusters: ->
 
    for i in Object.keys(@adjusterIds)
 
      @setAdjuster(i, null)
 

	
 
  getCurvePoints: (subj, curveAttr) ->
 
    U = (x) => @graph.Uri(x)
 
    originTime = @graph.floatValue(subj, U(':originTime'))
 

	
 
    for curve in @graph.objects(subj, U(':curve'))
 
      if @graph.uriValue(curve, U(':attr')).equals(curveAttr)
 

	
 
        return @project.getCurvePoints(curve, originTime)
 
    throw new Error("curve #{@uri.value} has no attr #{curveAttr.value}")
 

	
 
  draw: ->
 
    U = (x) => @graph.Uri(x)
 
    originTime = @graph.floatValue(@uri, U(':originTime'))
 
    [pointUris, worldPts] = @getCurvePoints(@uri, U(':strength'))
 
    effect = @graph.uriValue(@uri, U(':effectClass'))
 
    graphics = new PIXI.Graphics({nativeLines: true})
 

	
 
    for curve in @graph.objects(@uri, U(':curve'))
 
      if @graph.uriValue(curve, U(':attr')).equals(U(':strength'))
 
    yForV = (v) => @rowBotY + (@rowTopY - @rowBotY) * v
 
    dependOn = [@viewState.zoomSpec.t1(), @viewState.zoomSpec.t2(), @viewState.width()]
 
    screenPts = (new PIXI.Point(@viewState.zoomInX(pt.e(1)), yForV(pt.e(2))) for pt in worldPts)
 

	
 
        [pointUris, worldPts] = @project.getCurvePoints(curve, originTime)
 
        curveWidthCalc = () => @project.curveWidth(worldPts)
 
    @container.removeChildren()    
 
    graphics = new PIXI.Graphics({nativeLines: false})
 
    @container.addChild(graphics)
 

	
 
        h = 150 #@offsetHeight
 
        yForV = (v) => @rowTopY + (1 - v) * h
 
        dependOn = [@viewState.zoomSpec.t1(), @viewState.zoomSpec.t2(), @viewState.width()]
 
        screenPts = ($V([@viewState.zoomInX(pt.e(1)), yForV(pt.e(2))]) for pt in worldPts)
 
        graphics.beginFill(0xFF3300)
 
        graphics.lineStyle(4, 0xffd900, 1)
 
    shape = new PIXI.Polygon(screenPts)
 
    graphics.beginFill(@_noteColor(effect), .313)
 
    graphics.drawShape(shape)
 
    graphics.endFill()
 

	
 
        graphics.moveTo(screenPts[0].e(1), screenPts[0].e(2))
 
    graphics.lineStyle(2, 0xffd900, 1)
 
    graphics.moveTo(screenPts[0].x, screenPts[0].y)
 
        for p in screenPts.slice(1)
 
          graphics.lineTo(p.e(1), p.e(2))
 
       graphics.endFill()
 
      graphics.lineTo(p.x, p.y)
 
    
 
    curveWidthCalc = () => @project.curveWidth(worldPts)
 
       @_updateAdjusters(screenPts, worldPts, curveWidthCalc, yForV, @song)
 
       @_updateInlineAttrs(screenPts)
 
    @stage.addChild(graphics)
 
    
 
  onUri: ->
 
    @graph.runHandler(@update.bind(@), "note updates #{@uri}")
 

	
 
  patchCouldAffectMe: (patch) ->
 
    if patch and patch.addQuads # sometimes patch is a polymer-sent value. @update is used as a listener too
 
      if patch.addQuads.length == patch.delQuads.length == 1
 
        add = patch.addQuads[0]
 
        del = patch.delQuads[0]
 
        if (add.predicate.equals(del.predicate) and del.predicate.equals(@graph.Uri(':time')) and add.subject.equals(del.subject))
 
          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()
 

	
 
  _updateAdjusters: (screenPts, worldPts, curveWidthCalc, yForV, ctx) ->
 
    if screenPts[screenPts.length - 1].e(1) - screenPts[0].e(1) < 100
 
    if screenPts[screenPts.length - 1].x - screenPts[0].x < 100
 
      @clearAdjusters()
 
    else
 
      @_makeOffsetAdjuster(yForV, curveWidthCalc, ctx)
 
      @_makeCurvePointAdjusters(yForV, worldPts, ctx)
 
      #@_makeFadeAdjusters(yForV, ctx)
 
      @_makeFadeAdjusters(yForV, ctx, worldPts)
 

	
 
  _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
 
    leftX = Math.max(2, screenPts[Math.min(1, screenPts.length - 1)].x + 5)
 
    rightX = screenPts[Math.min(2, screenPts.length - 1)].x - 5
 
    if screenPts.length < 3
 
      rightX = leftX + 120
 
    w = 250
 
    h = 110
 
    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 wasHidden and @inlineRect.display != 'none'
 
      @async =>
 
        @querySelector('light9-timeline-note-inline-attrs')?.displayed()
 
    
 
  _makeCurvePointAdjusters: (yForV, worldPts, ctx) ->
 
    for pointNum in [0...worldPts.length]
 
      @_makePointAdjuster(yForV, worldPts, pointNum, ctx)
 

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

	
 
    adjId = @uri + '/p' + pointNum
 
    adjId = @uri.value + '/p' + pointNum
 
    @adjusterIds[adjId] = true
 
    @setAdjuster adjId, =>
 
      adj = new AdjustableFloatObject({
 
        graph: @graph
 
        subj: worldPts[pointNum].uri
 
        pred: U(':time')
 
        ctx: ctx
 
        getTargetPosForValue: (value) =>
 
          $V([@viewState.zoomInX(value), yForV(worldPts[pointNum].e(2))])
 
        getValueForPos: (pos) =>
 
          origin = @graph.floatValue(@uri, U(':originTime'))
 
          (@viewState.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, ctx) ->
 
    U = (x) => @graph.Uri(x)
 

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

	
 
  _makeFadeAdjusters: (yForV, ctx) ->
 
    @_makeFadeAdjuster(yForV, ctx, @uri + '/fadeIn', 0, 1, $V([-50, -10]))
 
    n = @worldPts.length
 
    @_makeFadeAdjuster(yForV, ctx, @uri + '/fadeOut', n - 2, n - 1, $V([50, -10]))
 
  _makeFadeAdjusters: (yForV, ctx, worldPts) ->
 
    U = (x) => @graph.Uri(x)
 
    @_makeFadeAdjuster(yForV, ctx, @uri.value + '/fadeIn', 0, 1, $V([-50, -10]))
 
    n = worldPts.length
 
    @_makeFadeAdjuster(yForV, ctx, @uri.value + '/fadeOut', n - 2, n - 1, $V([50, -10]))
 

	
 
  _makeFadeAdjuster: (yForV, ctx, adjId, i0, i1, offset) ->
 
    return # not ready- AdjustableFade looks in Note object
 
    @adjusterIds[adjId] = true
 
    @setAdjuster adjId, => new AdjustableFade(yForV, i0, i1, @, offset, ctx)
 
    
 
  _suggestedOffset: (pt) ->
 
    if pt.e(2) > .5
 
      $V([0, 30])
 
    else
 
      $V([0, -30])
 
    
 
  
 
  
 
  _addNoteListeners: (elem, 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) ->
 
  _noteColor: (effect) ->
 
    effect = effect.value
 
    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
 

	
 
    {style: "fill:hsla(#{hue}, #{sat}%, 58%, 0.313);"}
 
    return parseInt(tinycolor.fromRatio({h: hue / 360, s: sat / 100, l: .58}).toHex(), 16)
 

	
 
  _noteInDiagram: (uri) ->
 
    return !!@elemById[uri + '/area']
 
    return !!@elemById[uri.value + '/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)
 
    #elem.innerHTML = effectLabel;
light9/web/timeline/viewstate.coffee
Show inline comments
 
class window.ViewState
 
  constructor: () ->
 
    # caller updates all these observables
 
    @zoomSpec =
 
      duration: ko.observable(100) # current song duration
 
      t1: ko.observable(0)
 
      t2: ko.observable(100)
 
    @cursor =
 
      t: ko.observable(20) # songTime
 
    @mouse =
 
      pos: ko.observable($V([0,0]))
 
    @width = ko.observable(500)
 
    @coveredByDiagramTop = ko.observable(0) # page coords
 
    # all these are relative to #coveredByDiagram:
 
    @audioY = ko.observable(0)
 
    @audioH = ko.observable(0)
 
    @zoomedTimeY = ko.observable(0)
 
    @zoomedTimeH = ko.observable(0)
 
    @rowsY = ko.observable(0)
 
      
 
    @fullZoomX = d3.scaleLinear()
 
    @zoomInX = d3.scaleLinear()
 

	
 
    @zoomAnimSec = .1
 

	
 
    ko.computed(@maintainZoomLimitsAndScales.bind(@))    
 
 
 
  setWidth: (w) ->
 
    @width(w)
 
    @maintainZoomLimitsAndScales() # before other handlers run
 
    
 
  maintainZoomLimitsAndScales: () ->
 
    log('maintainZoomLimitsAndScales')
 
    # not for cursor updates
 

	
 
    if @zoomSpec.t1() < 0
 
      @zoomSpec.t1(0)
 
    if @zoomSpec.duration() and @zoomSpec.t2() > @zoomSpec.duration()
 
      @zoomSpec.t2(@zoomSpec.duration())
 

	
 
    @fullZoomX.domain([0, @zoomSpec.duration()])
 
    @fullZoomX.range([0, @width()])
 

	
 
    @zoomInX.domain([@zoomSpec.t1(), @zoomSpec.t2()])
 
    @zoomInX.range([0, @width()])
 
    
 
  latestMouseTime: ->
 
    @zoomInX.invert(@mouse.pos().e(1))
 

	
 
  onMouseWheel: (deltaY) ->
 
    zs = @zoomSpec
 

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

	
 
    zs.t1(center - left * scale)
 
    zs.t2(center + right * scale)
 
    log('view to', ko.toJSON(@))
 

	
 
  frameCursor: ->
 
    zs = @zoomSpec
 
    visSeconds = zs.t2() - zs.t1()
 
    margin = visSeconds * .4
 
    # buggy: really needs t1/t2 to limit their ranges
 
    if @cursor.t() < zs.t1() or @cursor.t() > zs.t2() - visSeconds * .6
package.json
Show inline comments
 
{
 
  "name": "light9",
 
  "version": "1.0.0",
 
  "repository": "...",
 
  "license": "MIT",
 
  "description": "Mini instructions:",
 
  "main": "index.js",
 
  "directories": {
 
    "test": "test"
 
  },
 
  "dependencies": {
 
    "@webcomponents/shadycss": "^1.1.3",
 
    "@webcomponents/webcomponentsjs": "^1.2.0",
 
    "bower": "^1.8.4",
 
    "browserify": "^16.2.0",
 
    "chai": "^3.5.0",
 
    "coffeescript": "^2.3.0",
 
    "d3": "^5.1.0",
 
    "mocha": "^2.5.3",
 
    "n3": "^1.0.0-alpha",
 
    "pixi.js": "^4.7.3"
 
    "pixi.js": "^4.7.3",
 
    "tinycolor2": "^1.4.1"
 
  },
 
  "devDependencies": {
 
    "mocha": "^2.5.3"
 
  },
 
  "scripts": {
 
    "test": "mocha"
 
  }
 
}
0 comments (0 inline, 0 general)