Files @ 4bab5bbce195
Branch filter:

Location: light9/web/timeline/TimelineEditor.coffee

drewp@bigasterisk.com
show-specific changes
log = debug('timeline')
debug.enable('*')

Drawing = window.Drawing
ROW_COUNT = 7


@customElement('light9-timeline-editor')
class TimelineEditor extends LitElement
  @getter_properties:
    viewState: { type: Object }
    debug: {type: String}
    graph: {type: Object, notify: true}
    project: {type: Object}
    setAdjuster: {type: Function, notify: true}
    playerSong: {type: String, notify: true}
    followPlayerSong: {type: Boolean, notify: true, value: true}
    song: {type: String, notify: true}
    show: {type: String, notify: true}
    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(song, playerSong, songTime, viewState)',
    '_onSetAdjuster(setAdjuster)',
  ]
  constructor: ->
    super()
    @viewState = new ViewState()
    window.viewState = @viewState

  ready: ->
    super.ready()
    @addEventListener 'mousedown', (ev) => @$.adjustersCanvas.onDown(ev)
    @addEventListener 'mousemove', (ev) => @$.adjustersCanvas.onMove(ev)
    @addEventListener 'mouseup', (ev) => @$.adjustersCanvas.onUp(ev)

    ko.options.deferUpdates = true

    @selection = {hover: ko.observable(null), selected: ko.observable([])}

    window.debug_zoomOrLayoutChangedCount = 0
    window.debug_adjUpdateDisplay = 0

    ko.computed(@zoomOrLayoutChanged.bind(@))

    @trackMouse()
    @bindKeys()
    @bindWheelZoom(@)

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

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

    Polymer.RenderStatus.afterNextRender this, =>
      setupDrop(@$.zoomed.$.rows, @$.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: (song, playerSong, t) ->
    if song != playerSong
      @viewState.cursor.t(0)
      return
    @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)
    @show = showRoot

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

    # shouldn't need this- deps should get it
    @$.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()

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

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

        # should be controlled by a checkbox next to follow-player-song-choice
        @sendMouseToVidref() unless window.location.hash.match(/novidref/)

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

  bindWheelZoom: (elem) ->
    elem.addEventListener 'mousewheel', (ev) =>
      @viewState.onMouseWheel(ev.deltaY)

  bindKeys: ->
    shortcut.add "Ctrl+P", (ev) =>
      @$.music.seekPlayOrPause(@viewState.latestMouseTime())
    shortcut.add "Ctrl+Escape", => @viewState.frameAll()
    shortcut.add "Shift+Escape", => @viewState.frameToEnd()
    shortcut.add "Escape", => @viewState.frameCursor()
    shortcut.add "L", =>
      @$.adjustersCanvas.updateAllCoords()
    shortcut.add 'Delete', =>
      for note in @selection.selected()
        @project.deleteNote(@graph.Uri(@song), note, @selection)

  makeZoomAdjs: ->
    yMid = => @$.audio.offsetTop + @$.audio.offsetHeight / 2

    valForPos = (pos) =>
      x = pos.e(1)
      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, 10])
      getValueForPos: valForPos
    }))

    @setAdjuster('zoom-right', => new AdjustableFloatObservable({
      observable: @viewState.zoomSpec.t2,
      getTarget: () =>
        $V([@viewState.fullZoomX(@viewState.zoomSpec.t2()), yMid()])
      getSuggestedTargetOffset: () => $V([50, 10])
      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
    }))