2062
|
1 log = debug('timeline')
|
|
2 debug.enable('*')
|
|
3
|
|
4 Drawing = window.Drawing
|
|
5 ROW_COUNT = 7
|
|
6
|
|
7
|
|
8 @customElement('light9-timeline-editor')
|
|
9 class TimelineEditor extends LitElement
|
|
10 @getter_properties:
|
|
11 viewState: { type: Object }
|
|
12 debug: {type: String}
|
|
13 graph: {type: Object, notify: true}
|
|
14 project: {type: Object}
|
|
15 setAdjuster: {type: Function, notify: true}
|
|
16 playerSong: {type: String, notify: true}
|
|
17 followPlayerSong: {type: Boolean, notify: true, value: true}
|
|
18 song: {type: String, notify: true}
|
|
19 show: {type: String, notify: true}
|
|
20 songTime: {type: Number, notify: true}
|
|
21 songDuration: {type: Number, notify: true}
|
|
22 songPlaying: {type: Boolean, notify: true}
|
|
23 selection: {type: Object, notify: true}
|
|
24 @getter_observers: [
|
|
25 '_onSong(playerSong, followPlayerSong)',
|
|
26 '_onGraph(graph)',
|
|
27 '_onSongDuration(songDuration, viewState)',
|
|
28 '_onSongTime(song, playerSong, songTime, viewState)',
|
|
29 '_onSetAdjuster(setAdjuster)',
|
|
30 ]
|
|
31 constructor: ->
|
|
32 super()
|
|
33 @viewState = new ViewState()
|
|
34 window.viewState = @viewState
|
|
35
|
|
36 ready: ->
|
|
37 super.ready()
|
|
38 @addEventListener 'mousedown', (ev) => @$.adjustersCanvas.onDown(ev)
|
|
39 @addEventListener 'mousemove', (ev) => @$.adjustersCanvas.onMove(ev)
|
|
40 @addEventListener 'mouseup', (ev) => @$.adjustersCanvas.onUp(ev)
|
|
41
|
|
42 ko.options.deferUpdates = true
|
|
43
|
|
44 @selection = {hover: ko.observable(null), selected: ko.observable([])}
|
|
45
|
|
46 window.debug_zoomOrLayoutChangedCount = 0
|
|
47 window.debug_adjUpdateDisplay = 0
|
|
48
|
|
49 ko.computed(@zoomOrLayoutChanged.bind(@))
|
|
50
|
|
51 @trackMouse()
|
|
52 @bindKeys()
|
|
53 @bindWheelZoom(@)
|
|
54
|
|
55 setInterval(@updateDebugSummary.bind(@), 100)
|
|
56
|
|
57 @addEventListener('iron-resize', @_onIronResize.bind(@))
|
|
58 Polymer.RenderStatus.afterNextRender(this, @_onIronResize.bind(@))
|
|
59
|
|
60 Polymer.RenderStatus.afterNextRender this, =>
|
|
61 setupDrop(@$.zoomed.$.rows, @$.zoomed.$.rows, @, @$.zoomed.onDrop.bind(@$.zoomed))
|
|
62
|
|
63 _onIronResize: ->
|
|
64 @viewState.setWidth(@offsetWidth)
|
|
65 @viewState.coveredByDiagramTop(@$.coveredByDiagram.offsetTop)
|
|
66 @viewState.rowsY(@$.zoomed.$.rows.offsetTop) if @$.zoomed?.$?.rows?
|
|
67 @viewState.audioY(@$.audio.offsetTop)
|
|
68 @viewState.audioH(@$.audio.offsetHeight)
|
|
69 if @$.zoomed?.$?.time?
|
|
70 @viewState.zoomedTimeY(@$.zoomed.$.time.offsetTop)
|
|
71 @viewState.zoomedTimeH(@$.zoomed.$.time.offsetHeight)
|
|
72
|
|
73 _onSongTime: (song, playerSong, t) ->
|
|
74 if song != playerSong
|
|
75 @viewState.cursor.t(0)
|
|
76 return
|
|
77 @viewState.cursor.t(t)
|
|
78
|
|
79 _onSongDuration: (d) ->
|
|
80 d = 700 if d < 1 # bug is that asco isn't giving duration, but 0 makes the scale corrupt
|
|
81 @viewState.zoomSpec.duration(d)
|
|
82
|
|
83 _onSong: (s) ->
|
|
84 @song = @playerSong if @followPlayerSong
|
|
85
|
|
86 _onGraph: (graph) ->
|
|
87 @project = new Project(graph)
|
2160
|
88 @show = showRoot
|
2062
|
89
|
|
90 _onSetAdjuster: () ->
|
|
91 @makeZoomAdjs()
|
|
92
|
|
93 updateDebugSummary: ->
|
|
94 elemCount = (tag) -> document.getElementsByTagName(tag).length
|
|
95 @debug = "#{window.debug_zoomOrLayoutChangedCount} layout change,
|
|
96 #{elemCount('light9-timeline-note')} notes,
|
|
97 #{@selection.selected().length} selected
|
|
98 #{elemCount('light9-timeline-graph-row')} rows,
|
|
99 #{window.debug_adjsCount} adjuster items registered,
|
|
100 #{window.debug_adjUpdateDisplay} adjuster updateDisplay calls,
|
|
101 "
|
|
102
|
|
103 zoomOrLayoutChanged: ->
|
|
104 vs = @viewState
|
|
105 dependOn = [vs.zoomSpec.t1(), vs.zoomSpec.t2(), vs.width()]
|
|
106
|
|
107 # shouldn't need this- deps should get it
|
|
108 @$.zoomed.gatherNotes() if @$.zoomed?.gatherNotes?
|
|
109
|
|
110 # todo: these run a lot of work purely for a time change
|
|
111 if @$.zoomed?.$?.audio?
|
|
112 #@dia.setTimeAxis(vs.width(), @$.zoomed.$.audio.offsetTop, vs.zoomInX)
|
|
113 @$.adjustersCanvas.updateAllCoords()
|
|
114
|
|
115 trackMouse: ->
|
|
116 # not just for show- we use the mouse pos sometimes
|
|
117 for evName in ['mousemove', 'touchmove']
|
|
118 @addEventListener evName, (ev) =>
|
|
119 ev.preventDefault()
|
|
120
|
|
121 # todo: consolidate with _editorCoordinates version
|
|
122 if ev.touches?.length
|
|
123 ev = ev.touches[0]
|
|
124
|
|
125 root = @$.cursorCanvas.getBoundingClientRect()
|
|
126 @viewState.mouse.pos($V([ev.pageX - root.left, ev.pageY - root.top]))
|
|
127
|
|
128 # should be controlled by a checkbox next to follow-player-song-choice
|
|
129 @sendMouseToVidref() unless window.location.hash.match(/novidref/)
|
|
130
|
|
131 sendMouseToVidref: ->
|
|
132 now = Date.now()
|
|
133 if (!@$.vidrefLastSent? || @$.vidrefLastSent < now - 200) && !@songPlaying
|
|
134 @$.vidrefTime.body = {t: @viewState.latestMouseTime(), source: 'timeline', song: @song}
|
|
135 @$.vidrefTime.generateRequest()
|
|
136 @$.vidrefLastSent = now
|
|
137
|
|
138 bindWheelZoom: (elem) ->
|
|
139 elem.addEventListener 'mousewheel', (ev) =>
|
|
140 @viewState.onMouseWheel(ev.deltaY)
|
|
141
|
|
142 bindKeys: ->
|
|
143 shortcut.add "Ctrl+P", (ev) =>
|
|
144 @$.music.seekPlayOrPause(@viewState.latestMouseTime())
|
|
145 shortcut.add "Ctrl+Escape", => @viewState.frameAll()
|
|
146 shortcut.add "Shift+Escape", => @viewState.frameToEnd()
|
|
147 shortcut.add "Escape", => @viewState.frameCursor()
|
|
148 shortcut.add "L", =>
|
|
149 @$.adjustersCanvas.updateAllCoords()
|
|
150 shortcut.add 'Delete', =>
|
|
151 for note in @selection.selected()
|
|
152 @project.deleteNote(@graph.Uri(@song), note, @selection)
|
|
153
|
|
154 makeZoomAdjs: ->
|
|
155 yMid = => @$.audio.offsetTop + @$.audio.offsetHeight / 2
|
|
156
|
|
157 valForPos = (pos) =>
|
|
158 x = pos.e(1)
|
|
159 t = @viewState.fullZoomX.invert(x)
|
|
160 @setAdjuster('zoom-left', => new AdjustableFloatObservable({
|
|
161 observable: @viewState.zoomSpec.t1,
|
|
162 getTarget: () =>
|
|
163 $V([@viewState.fullZoomX(@viewState.zoomSpec.t1()), yMid()])
|
|
164 getSuggestedTargetOffset: () => $V([-50, 10])
|
|
165 getValueForPos: valForPos
|
|
166 }))
|
|
167
|
|
168 @setAdjuster('zoom-right', => new AdjustableFloatObservable({
|
|
169 observable: @viewState.zoomSpec.t2,
|
|
170 getTarget: () =>
|
|
171 $V([@viewState.fullZoomX(@viewState.zoomSpec.t2()), yMid()])
|
|
172 getSuggestedTargetOffset: () => $V([50, 10])
|
|
173 getValueForPos: valForPos
|
|
174 }))
|
|
175
|
|
176 panObs = ko.pureComputed({
|
|
177 read: () =>
|
|
178 (@viewState.zoomSpec.t1() + @viewState.zoomSpec.t2()) / 2
|
|
179 write: (value) =>
|
|
180 zs = @viewState.zoomSpec
|
|
181 span = zs.t2() - zs.t1()
|
|
182 zs.t1(value - span / 2)
|
|
183 zs.t2(value + span / 2)
|
|
184 })
|
|
185
|
|
186 @setAdjuster('zoom-pan', => new AdjustableFloatObservable({
|
|
187 observable: panObs
|
|
188 emptyBox: true
|
|
189 # fullzoom is not right- the sides shouldn't be able to go
|
|
190 # offscreen
|
|
191 getTarget: () => $V([@viewState.fullZoomX(panObs()), yMid()])
|
|
192 getSuggestedTargetOffset: () => $V([0, 0])
|
|
193 getValueForPos: valForPos
|
|
194 }))
|