Changeset - 3ddd5b4964b3
[Not reviewed]
default
0 2 0
drewp@bigasterisk.com - 7 years ago 2018-05-31 00:47:07
drewp@bigasterisk.com
fix time adjs crossing each other. more simulation steps for adjs.
Ignore-this: 8a14c5c954ca1dd9dbc1180386fa875a
2 files changed with 24 insertions and 14 deletions:
0 comments (0 inline, 0 general)
light9/web/timeline/adjusters.coffee
Show inline comments
 
log = console.log
 
Drawing = window.Drawing
 

	
 
maxDist = 60
 

	
 
coffeeElementSetup(class AdjustersCanvas extends Polymer.mixinBehaviors([Polymer.IronResizableBehavior], Polymer.Element)
 
  @is: 'light9-adjusters-canvas'
 
  @getter_properties:
 
    setAdjuster: {type: Function, notify: true }
 
  @getter_observers: [
 
    'updateAllCoords(adjs)'
 
  ]
 
  constructor: ->
 
    super()
 
    @redraw = _.throttle(@_throttledRedraw.bind(@), 60, {leading: false})
 
    @redraw = _.throttle(@_throttledRedraw.bind(@), 30, {leading: false})
 
    @adjs = {}
 
    
 
  ready: ->
 
    super.ready()
 
    @addEventListener('iron-resize', @resizeUpdate.bind(@))
 
    @ctx = @$.canvas.getContext('2d')
 
    
 
    @redraw()
 
    @setAdjuster = @_setAdjuster.bind(@)
 

	
 
    # These don't fire; TimelineEditor calls the handlers for us.
 
    @addEventListener('mousedown', @onDown.bind(@))
 
    @addEventListener('mousemove', @onMove.bind(@))
 
    @addEventListener('mouseup', @onUp.bind(@))
 
   
 
  onDown: (ev) ->
 
    if ev.buttons == 1
 
      start = $V([ev.x, ev.y])
 
      adj = @_adjAtPoint(start)
 
      if adj
 
        ev.stopPropagation()
 
        @currentDrag = {start: start, adj: adj}
 
        adj.startDrag()
 

	
 
  onMove: (ev) ->
 
    pos = $V([ev.x, ev.y])
 
    if @currentDrag
 
      @currentDrag.cur = pos
 
      @currentDrag.adj.continueDrag(
 
        @currentDrag.cur.subtract(@currentDrag.start))
 

	
 
  onUp: (ev) ->
 
    return unless @currentDrag
 
    @currentDrag.adj.endDrag()
 
    @currentDrag = null
 
    
 
  _setAdjuster: (adjId, makeAdjustable) ->
 
    # callers register/unregister the Adjustables they want us to make
 
    # adjuster elements for. Caller invents adjId.  makeAdjustable is
 
    # a function returning the Adjustable or it is null to clear any
 
    # adjusters with this id.
 
    if not @adjs[adjId] or not makeAdjustable?
 
      if not makeAdjustable?
 
        delete @adjs[adjId]
 
      else
 
        adj = makeAdjustable()
 
        @adjs[adjId] = adj
 
        adj.id = adjId
 

	
 
    @redraw()
 

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

	
 
  updateAllCoords: ->
 
    @redraw()
 

	
 
  _adjAtPoint: (pt) ->
 
    nearest = @qt.find(pt.e(1), pt.e(2))
 
    if nearest?
 
      log('near', nearest.distanceFrom(pt))
 
    if not nearest? or nearest.distanceFrom(pt) > maxDist
 
      return null
 
    return nearest?.adj
 

	
 
  resizeUpdate: (ev) ->
 
    @$.canvas.width = ev.target.offsetWidth
 
    @$.canvas.height = ev.target.offsetHeight
 
    @canvasCenter = $V([@$.canvas.width / 2, @$.canvas.height / 2])
 
    @redraw()
 

	
 
  _throttledRedraw: () ->
 
    return unless @ctx?
 
    console.time('adjs redraw')
 
    @_layoutCenters()
 
    
 
    @ctx.clearRect(0, 0, @$.canvas.width, @$.canvas.height)
 

	
 
    for adjId, adj of @adjs
 
      ctr = adj.getHandle()
 
      target = adj.getTarget()
 
      if target.e(1) < 0 or target.e(1) > @$.canvas.width or target.e(2) < 0 or target.e(2) > @$.canvas.height
 
      if @_isOffScreen(target)
 
        continue
 
      @_drawConnector(ctr, target)
 
      
 
      @_drawAdjuster(adj.getDisplayValue(),
 
                     Math.floor(ctr.e(1)) - 20, Math.floor(ctr.e(2)) - 10,
 
                     Math.floor(ctr.e(1)) + 20, Math.floor(ctr.e(2)) + 10)
 
                     ctr.e(1) - 20, ctr.e(2) - 10,
 
                     ctr.e(1) + 20, ctr.e(2) + 10)
 
    console.timeEnd('adjs redraw')
 

	
 
  _layoutCenters: ->
 
    # push Adjustable centers around to avoid overlaps
 
    # Todo: also don't overlap inlineattr boxes
 
    # Todo: don't let their connector lines cross each other
 
    @qt = d3.quadtree([], ((d)->d.e(1)), ((d)->d.e(2)))
 
    @qt.extent([[0,0], [8000,8000]])
 

	
 
    for _, adj of @adjs
 
      adj.handle = adj.getSuggestedHandle()
 
      adj.handle = @_clampOnScreen(adj.getSuggestedHandle())
 
      
 
    for tries in [0...5]
 
    numTries = 8
 
    for tries in [0...numTries]
 
      for _, adj of @adjs
 
        current = adj.handle
 
        @qt.remove(current)
 
        nearest = @qt.find(current.e(1), current.e(2), maxDist)
 
        if nearest
 
          dist = current.distanceFrom(nearest)
 
          if dist < maxDist
 
            away = current.subtract(nearest).toUnitVector()
 
            toScreenCenter = @canvasCenter.subtract(current).toUnitVector()
 
            current = current.add(away.x(20).add(toScreenCenter.x(2)))
 
            marg = 10
 
            current = $V([Math.max(marg, Math.min(@$.canvas.width - marg, current.e(1))),
 
                         Math.max(marg, Math.min(@$.canvas.height - marg, current.e(2)))])
 
            current = @_stepAway(current, nearest, 1 / numTries)
 
            adj.handle = current
 
        current.adj = adj
 
        @qt.add(current)
 

	
 
      #if -50 < output.e(1) < 20 # mostly for zoom-left
 
      #  output.setElements([
 
      #    Math.max(20, output.e(1)),
 
      #    output.e(2)])
 
        
 
  _stepAway: (current, nearest, dx) ->
 
    away = current.subtract(nearest).toUnitVector()
 
    toScreenCenter = @canvasCenter.subtract(current).toUnitVector()
 
    goalSpacingPx = 20
 
    @_clampOnScreen(current.add(away.x(goalSpacingPx * dx)))
 

	
 
  _isOffScreen: (pos) ->
 
    pos.e(1) < 0 or pos.e(1) > @$.canvas.width or pos.e(2) < 0 or pos.e(2) > @$.canvas.height
 

	
 
  _clampOnScreen: (pos) ->    
 
    marg = 30
 
    $V([Math.max(marg, Math.min(@$.canvas.width - marg, pos.e(1))),
 
        Math.max(marg, Math.min(@$.canvas.height - marg, pos.e(2)))])
 
                        
 
  _drawConnector: (ctr, target) ->
 
    @ctx.strokeStyle = '#aaa'
 
    @ctx.lineWidth = 2
 
    @ctx.beginPath()
 
    Drawing.line(@ctx, ctr, target)
 
    @ctx.stroke()
 
    
 
  _drawAdjuster: (label, x1, y1, x2, y2) ->
 
    radius = 8
 

	
 
    @ctx.shadowColor = 'black'
 
    @ctx.shadowBlur = 15
 
    @ctx.shadowOffsetX = 5
 
    @ctx.shadowOffsetY = 9
 
    
 
    @ctx.fillStyle = 'rgba(255, 255, 0, 0.5)'
 
    @ctx.beginPath()
 
    Drawing.roundRect(@ctx, x1, y1, x2, y2, radius)
 
    @ctx.fill()
 

	
 
    @ctx.shadowColor = 'rgba(0,0,0,0)'
 
        
 
    @ctx.strokeStyle = 'yellow'
 
    @ctx.lineWidth = 2
 
    @ctx.setLineDash([3, 3])
 
    @ctx.beginPath()
 
    Drawing.roundRect(@ctx, x1, y1, x2, y2, radius)
 
    @ctx.stroke()
 
    @ctx.setLineDash([])
 

	
 
    @ctx.font = "12px sans"
 
    @ctx.fillStyle = '#000'
 
    @ctx.fillText(label, x1 + 5, y2 - 5, x2 - x1 - 10)
 

	
 
    # coords from a center that's passed in
 
    # # special layout for the thaeter ones with middinh
 
    # l/r arrows
 
    # mouse arrow cursor upon hover, and accent the hovered adjuster
 
    # connector
 
)
 
\ No newline at end of file
light9/web/timeline/timeline.coffee
Show inline comments
 
@@ -198,105 +198,105 @@ coffeeElementSetup(class TimelineEditor 
 
    # 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: @latestMouseTime(), source: 'timeline'}
 
      @$.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])
 
      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])
 
      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
 
    }))
 
)
 

	
 

	
 
# 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 }
 
    song: { type: String, notify: true }
 
    viewState: { type: Object, notify: true }
 
    inlineAttrConfigs: { type: Array, value: [] } # only for inlineattrs that should be displayed
 
  @getter_observers: [
 
    '_onGraph(graph, setAdjuster, song, viewState, project)',
 
    'onZoom(viewState)',
 
  ]
 
  constructor: ->
 
    super()
 
    @noteByUriStr = new Map()
 
    @stage = new PIXI.Container()
 
    @stage.interactive=true
 

	
 
    @renderer = PIXI.autoDetectRenderer({
0 comments (0 inline, 0 general)