Files @ 1082f0725c32
Branch filter:

Location: light9/web/timeline/TimeZoomed.coffee

drewp@bigasterisk.com
fix PlayerState semantics
log = debug('timeline')
debug.enable('*')

Drawing = window.Drawing
ROW_COUNT = 7


# 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.
@customElement('light9-timeline-time-zoomed')
class TimeZoomed extends LitElement
  @getter_properties:
    graph: { type: Object, notify: true }
    project: { type: Object }
    selection: { type: Object, notify: true }
    song: { type: String, notify: true }
    viewState: { type: Object, notify: true }
    inlineAttrConfigs: { type: Array, value: [] } # only for inlineattrs that should be displayed
    imageSamples: { type: Array, value: [] }
  @getter_observers: [
    '_onGraph(graph, setAdjuster, song, viewState, project)',
    'onZoom(viewState)',
    '_onViewState(viewState)',
  ]
  constructor: ->
    super()
    @numRows = 6
    @noteByUriStr = new Map()
    @stage = new PIXI.Container()
    @stage.interactive=true

    @renderer = PIXI.autoDetectRenderer({
      backgroundColor: 0x606060,
      antialias: true,
      forceCanvas: true,
    })
    @bg = new PIXI.Container()
    @stage.addChild(@bg)

    @dirty = _.debounce(@_repaint.bind(@), 10)

  ready: ->
    super.ready()

    @imageSamples = ['one']

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

    @$.rows.appendChild(@renderer.view)

    # This works for display, but pixi hit events didn't correctly
    # move with the objects, so as a workaround, I extended the top of
    # the canvas in _onResize.
    #
    #ko.computed =>
    #  @stage.setTransform(0, -(@viewState.rowsY()), 1, 1, 0, 0, 0, 0, 0)

  _onResize: ->
    @$.rows.firstChild.style.position = 'relative'
    @$.rows.firstChild.style.top = -@viewState.rowsY() + 'px'

    @renderer.resize(@clientWidth, @clientHeight + @viewState.rowsY())

    @dirty()

  _onGraph: (graph, setAdjuster, song, viewState, project)->
    return unless @song # polymer will call again
    @graph.runHandler(@gatherNotes.bind(@), 'zoom notes')

  _onViewState: (viewState) ->
    @brickLayout = new BrickLayout(@viewState, @numRows)

  noteDirty: -> @dirty()
    
  onZoom: ->
    updateZoomFlattened = ->
      @zoomFlattened = ko.toJS(@viewState.zoomSpec)
    ko.computed(updateZoomFlattened.bind(@))

  gatherNotes: ->
    U = (x) => @graph.Uri(x)
    return unless @song?
    songNotes = @graph.objects(U(@song), U(':note'))

    toRemove = new Set(@noteByUriStr.keys())
    
    for uri in @graph.sortedUris(songNotes)
      had = toRemove.delete(uri.value)
      if not had
        @_addNote(uri)

    toRemove.forEach @_delNote.bind(@)

    @dirty()

  isActiveNote: (note) -> @noteByUriStr.has(note.value)

  _repaint: ->
    @_drawGrid()  
    @renderer.render(@stage)

  _drawGrid: ->
    # maybe someday this has snappable timing markers too
    @bg.removeChildren()
    gfx = new PIXI.Graphics()
    @bg.addChild(gfx)

    gfx.lineStyle(1, 0x222222, 1)
    for row in [0...@numRows]
      y = @brickLayout.rowBottom(row)
      gfx.moveTo(0, y)
      gfx.lineTo(@clientWidth, y)

  _addNote: (uri) ->
    U = (x) => @graph.Uri(x)
    
    con = new PIXI.Container()
    con.interactive=true
    @stage.addChild(con)
    
    note = new Note(@, con, @project, @graph, @selection, uri, @setAdjuster, U(@song), @viewState, @brickLayout)
    # this must come before the first Note.draw
    @noteByUriStr.set(uri.value, note)
    @brickLayout.addNote(note, note.onRowChange.bind(note))
    note.initWatchers()

  _delNote: (uriStr) ->
    n = @noteByUriStr.get(uriStr)
    @brickLayout.delNote(n)
    @stage.removeChild(n.container)
    n.destroy()
    @noteByUriStr.delete(uriStr)
            
  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, U('rdf:type'), U(':Effect'))
      if @graph.contains(effect, U('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, @viewState.zoomSpec.duration() - dropTime)
    @project.makeNewNote(U(@song), U(effect), dropTime, desiredWidthT)

  updateInlineAttrs: (note, config) ->
    if not config?
      index = 0
      for c in @inlineAttrConfigs
        if c.uri.equals(note)
          @splice('inlineAttrConfigs', index)
          return
        index += 1
    else
      index = 0
      for c in @inlineAttrConfigs
        if c.uri.equals(note)
          @splice('inlineAttrConfigs', index, 1, config)
          return
        index += 1
      @push('inlineAttrConfigs', config)