diff --git a/light9/web/timeline/brick_layout.coffee b/light9/web/timeline/brick_layout.coffee
new file mode 100644
--- /dev/null
+++ b/light9/web/timeline/brick_layout.coffee
@@ -0,0 +1,52 @@
+
+class window.BrickLayout
+ constructor: (@viewState, @numRows) ->
+ @noteRow = {} # uristr: row, t0, t1, onRowChange
+
+ addNote: (n, onRowChange) ->
+ @noteRow[n.uri.value] = {row: 0, t0: 0, t1: 0, onRowChange: onRowChange}
+
+ setNoteSpan: (n, t0, t1) ->
+ @noteRow[n.uri.value].t0 = t0
+ @noteRow[n.uri.value].t1 = t1
+ @_recompute()
+
+ delNote: (n) ->
+ delete @noteRow[n.uri.value]
+ @_recompute()
+
+ _recompute: ->
+ for u, row of @noteRow
+ row.prev = row.row
+ row.row = null
+ overlap = (a, b) -> a.t0 < b.t1 and a.t1 > b.t0
+
+ notesByWidth = _.sortBy(
+ ({dur: row.t1 - row.t0 + row.t0 * .0001, uri: u} for u, row of @noteRow),
+ 'dur')
+ notesByWidth.reverse()
+
+ for n in notesByWidth
+ blockedRows = new Set()
+ for u, other of @noteRow
+ if other.row != null
+ if overlap(other, @noteRow[n.uri])
+ blockedRows.add(other.row)
+
+ for r in [0 ... @numRows]
+ if not blockedRows.has(r)
+ @noteRow[n.uri].row = r
+ break
+ if @noteRow[n.uri].row == null
+ log("warning: couldn't place #{n.uri}")
+ @noteRow[n.uri].row = 0
+ if @noteRow[n.uri].row != @noteRow[n.uri].prev
+ @noteRow[n.uri].onRowChange()
+
+ rowBottom: (row) -> @viewState.rowsY() + 20 + 150 * row + 140
+
+ yForVFor: (n) ->
+ row = @noteRow[n.uri.value].row
+ rowBottom = @rowBottom(row)
+ rowTop = rowBottom - 140
+ (v) => rowBottom + (rowTop - rowBottom) * v
diff --git a/light9/web/timeline/timeline-elements.html b/light9/web/timeline/timeline-elements.html
--- a/light9/web/timeline/timeline-elements.html
+++ b/light9/web/timeline/timeline-elements.html
@@ -205,6 +205,7 @@
+
diff --git a/light9/web/timeline/timeline.coffee b/light9/web/timeline/timeline.coffee
--- a/light9/web/timeline/timeline.coffee
+++ b/light9/web/timeline/timeline.coffee
@@ -277,31 +277,6 @@ coffeeElementSetup(class TimelineEditor
)
-class BrickLayout
- constructor: (@viewState, @numRows) ->
- @noteRow = {} # uristr: row, t0, t1
- addNote: (n) ->
- @noteRow[n.uri.value] = {row: 0, t0: 0, t1: 0}
-
- setNoteSpan: (n, t0, t1) ->
- @noteRow[n.uri.value].t0 = t0
- @noteRow[n.uri.value].t1 = t1
- @_recompute()
- delNote: (n) ->
- delete @noteRow[n.uri.value]
- _recompute: ->
- notesByWidth = _.sortBy([{dur: row.t1 - row.t0 + row.t0 * .0001, uri: u} for u, row of @noteRow], 'dur')
- notesByWidth.reverse()
- for n in notesByWidth
- @noteRow[n.uri].row = 0
-
- rowBottom: (row) -> @viewState.rowsY() + 20 + 150 * row + 140
- yForVFor: (n) ->
- row = @noteRow[n.uri.value].row
- rowBottom = @rowBottom(row)
- rowTop = rowBottom - 140
- (v) => rowBottom + (rowTop - rowBottom) * v
-
# 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.
@@ -370,7 +345,6 @@ coffeeElementSetup(class TimeZoomed exte
onZoom: ->
updateZoomFlattened = ->
- log('updateZoomFlattened')
@zoomFlattened = ko.toJS(@viewState.zoomSpec)
ko.computed(updateZoomFlattened.bind(@))
@@ -418,7 +392,7 @@ coffeeElementSetup(class TimeZoomed exte
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)
+ @brickLayout.addNote(note, note.onRowChange.bind(note))
note.initWatchers()
_delNote: (uriStr) ->
@@ -488,6 +462,7 @@ coffeeElementSetup(class TimeAxis extend
class Note
constructor: (@parentElem, @container, @project, @graph, @selection, @uri, @setAdjuster, @song, @viewState, @brickLayout) ->
@adjusterIds = new Set() # id string
+ @updateSoon = _.debounce(@update.bind(@), 30)
initWatchers: ->
@graph.runHandler(@update.bind(@), "note update #{@uri.value}")
@@ -534,19 +509,25 @@ class Note
worldPts: worldPts
screenPts: screenPts
effect: effect
+ hover: @uri.equals(@selection.hover())
+ selected: @selection.selected().filter((s) => s.equals(@uri)).length
}
+ onRowChange: ->
+ @clearAdjusters()
+ @updateSoon()
+
redraw: (params) ->
+ # no observable or graph deps in here
@container.removeChildren()
@graphics = new PIXI.Graphics({nativeLines: false})
@graphics.interactive = true
@container.addChild(@graphics)
- if @uri.equals(@selection.hover())
+ if params.hover
@_traceBorder(params.screenPts, 12, 0x888888)
- @selection.selected().forEach (s) =>
- if s.equals(@uri)
- @_traceBorder(params.screenPts, 6, 0xff2900)
+ if params.selected
+ @_traceBorder(params.screenPts, 6, 0xff2900)
shape = new PIXI.Polygon(params.screenPts)
@graphics.beginFill(@_noteColor(params.effect), .313)
@@ -554,6 +535,8 @@ class Note
@graphics.endFill()
@_traceBorder(params.screenPts, 2, 0xffd900)
+
+ @_addMouseBindings()
update: ->
if not @parentElem.isActiveNote(@uri)
@@ -569,8 +552,6 @@ class Note
@redraw(params)
- @_addMouseBindings()
-
curveWidthCalc = () => @project.curveWidth(@worldPts)
@_updateAdjusters(params.screenPts, @worldPts, curveWidthCalc,
params.yForV, @viewState.zoomInX, @song)
diff --git a/light9/web/timeline/viewstate.coffee b/light9/web/timeline/viewstate.coffee
--- a/light9/web/timeline/viewstate.coffee
+++ b/light9/web/timeline/viewstate.coffee
@@ -30,7 +30,6 @@ class window.ViewState
@maintainZoomLimitsAndScales() # before other handlers run
maintainZoomLimitsAndScales: () ->
- log('maintainZoomLimitsAndScales')
# not for cursor updates
if @zoomSpec.t1() < 0