2062
|
1 log = debug('timeline')
|
|
2 debug.enable('*')
|
|
3
|
|
4 Drawing = window.Drawing
|
|
5 ROW_COUNT = 7
|
|
6
|
|
7
|
|
8 # plan: in here, turn all the notes into simple js objects with all
|
|
9 # their timing data and whatever's needed for adjusters. From that, do
|
|
10 # the brick layout. update only changing adjusters.
|
|
11 @customElement('light9-timeline-time-zoomed')
|
|
12 class TimeZoomed extends LitElement
|
|
13 @getter_properties:
|
|
14 graph: { type: Object, notify: true }
|
|
15 project: { type: Object }
|
|
16 selection: { type: Object, notify: true }
|
|
17 song: { type: String, notify: true }
|
|
18 viewState: { type: Object, notify: true }
|
|
19 inlineAttrConfigs: { type: Array, value: [] } # only for inlineattrs that should be displayed
|
|
20 imageSamples: { type: Array, value: [] }
|
|
21 @getter_observers: [
|
|
22 '_onGraph(graph, setAdjuster, song, viewState, project)',
|
|
23 'onZoom(viewState)',
|
|
24 '_onViewState(viewState)',
|
|
25 ]
|
|
26 constructor: ->
|
|
27 super()
|
|
28 @numRows = 6
|
|
29 @noteByUriStr = new Map()
|
|
30 @stage = new PIXI.Container()
|
|
31 @stage.interactive=true
|
|
32
|
|
33 @renderer = PIXI.autoDetectRenderer({
|
|
34 backgroundColor: 0x606060,
|
|
35 antialias: true,
|
|
36 forceCanvas: true,
|
|
37 })
|
|
38 @bg = new PIXI.Container()
|
|
39 @stage.addChild(@bg)
|
|
40
|
|
41 @dirty = _.debounce(@_repaint.bind(@), 10)
|
|
42
|
|
43 ready: ->
|
|
44 super.ready()
|
|
45
|
|
46 @imageSamples = ['one']
|
|
47
|
|
48 @addEventListener('iron-resize', @_onResize.bind(@))
|
|
49 Polymer.RenderStatus.afterNextRender(this, @_onResize.bind(@))
|
|
50
|
|
51 @$.rows.appendChild(@renderer.view)
|
|
52
|
|
53 # This works for display, but pixi hit events didn't correctly
|
|
54 # move with the objects, so as a workaround, I extended the top of
|
|
55 # the canvas in _onResize.
|
|
56 #
|
|
57 #ko.computed =>
|
|
58 # @stage.setTransform(0, -(@viewState.rowsY()), 1, 1, 0, 0, 0, 0, 0)
|
|
59
|
|
60 _onResize: ->
|
|
61 @$.rows.firstChild.style.position = 'relative'
|
|
62 @$.rows.firstChild.style.top = -@viewState.rowsY() + 'px'
|
|
63
|
|
64 @renderer.resize(@clientWidth, @clientHeight + @viewState.rowsY())
|
|
65
|
|
66 @dirty()
|
|
67
|
|
68 _onGraph: (graph, setAdjuster, song, viewState, project)->
|
|
69 return unless @song # polymer will call again
|
|
70 @graph.runHandler(@gatherNotes.bind(@), 'zoom notes')
|
|
71
|
|
72 _onViewState: (viewState) ->
|
|
73 @brickLayout = new BrickLayout(@viewState, @numRows)
|
|
74
|
|
75 noteDirty: -> @dirty()
|
|
76
|
|
77 onZoom: ->
|
|
78 updateZoomFlattened = ->
|
|
79 @zoomFlattened = ko.toJS(@viewState.zoomSpec)
|
|
80 ko.computed(updateZoomFlattened.bind(@))
|
|
81
|
|
82 gatherNotes: ->
|
|
83 U = (x) => @graph.Uri(x)
|
|
84 return unless @song?
|
|
85 songNotes = @graph.objects(U(@song), U(':note'))
|
|
86
|
|
87 toRemove = new Set(@noteByUriStr.keys())
|
|
88
|
|
89 for uri in @graph.sortedUris(songNotes)
|
|
90 had = toRemove.delete(uri.value)
|
|
91 if not had
|
|
92 @_addNote(uri)
|
|
93
|
|
94 toRemove.forEach @_delNote.bind(@)
|
|
95
|
|
96 @dirty()
|
|
97
|
|
98 isActiveNote: (note) -> @noteByUriStr.has(note.value)
|
|
99
|
|
100 _repaint: ->
|
|
101 @_drawGrid()
|
|
102 @renderer.render(@stage)
|
|
103
|
|
104 _drawGrid: ->
|
|
105 # maybe someday this has snappable timing markers too
|
|
106 @bg.removeChildren()
|
|
107 gfx = new PIXI.Graphics()
|
|
108 @bg.addChild(gfx)
|
|
109
|
|
110 gfx.lineStyle(1, 0x222222, 1)
|
|
111 for row in [0...@numRows]
|
|
112 y = @brickLayout.rowBottom(row)
|
|
113 gfx.moveTo(0, y)
|
|
114 gfx.lineTo(@clientWidth, y)
|
|
115
|
|
116 _addNote: (uri) ->
|
|
117 U = (x) => @graph.Uri(x)
|
|
118
|
|
119 con = new PIXI.Container()
|
|
120 con.interactive=true
|
|
121 @stage.addChild(con)
|
|
122
|
|
123 note = new Note(@, con, @project, @graph, @selection, uri, @setAdjuster, U(@song), @viewState, @brickLayout)
|
|
124 # this must come before the first Note.draw
|
|
125 @noteByUriStr.set(uri.value, note)
|
|
126 @brickLayout.addNote(note, note.onRowChange.bind(note))
|
|
127 note.initWatchers()
|
|
128
|
|
129 _delNote: (uriStr) ->
|
|
130 n = @noteByUriStr.get(uriStr)
|
|
131 @brickLayout.delNote(n)
|
|
132 @stage.removeChild(n.container)
|
|
133 n.destroy()
|
|
134 @noteByUriStr.delete(uriStr)
|
|
135
|
|
136 onDrop: (effect, pos) ->
|
|
137 U = (x) => @graph.Uri(x)
|
|
138
|
|
139 return unless effect and effect.match(/^http/)
|
|
140
|
|
141 # we could probably accept some initial overrides right on the
|
|
142 # effect uri, maybe as query params
|
|
143
|
|
144 if not @graph.contains(effect, U('rdf:type'), U(':Effect'))
|
|
145 if @graph.contains(effect, U('rdf:type'), U(':LightSample'))
|
|
146 effect = @project.makeEffect(effect)
|
|
147 else
|
|
148 log("drop #{effect} is not an effect")
|
|
149 return
|
|
150
|
|
151 dropTime = @viewState.zoomInX.invert(pos.e(1))
|
|
152
|
|
153 desiredWidthX = @offsetWidth * .3
|
|
154 desiredWidthT = @viewState.zoomInX.invert(desiredWidthX) - @viewState.zoomInX.invert(0)
|
|
155 desiredWidthT = Math.min(desiredWidthT, @viewState.zoomSpec.duration() - dropTime)
|
|
156 @project.makeNewNote(U(@song), U(effect), dropTime, desiredWidthT)
|
|
157
|
|
158 updateInlineAttrs: (note, config) ->
|
|
159 if not config?
|
|
160 index = 0
|
|
161 for c in @inlineAttrConfigs
|
|
162 if c.uri.equals(note)
|
|
163 @splice('inlineAttrConfigs', index)
|
|
164 return
|
|
165 index += 1
|
|
166 else
|
|
167 index = 0
|
|
168 for c in @inlineAttrConfigs
|
|
169 if c.uri.equals(note)
|
|
170 @splice('inlineAttrConfigs', index, 1, config)
|
|
171 return
|
|
172 index += 1
|
|
173 @push('inlineAttrConfigs', config)
|
|
174
|