Mercurial > code > home > repos > light9
diff web/timeline/TimelineEditor.coffee @ 2376:4556eebe5d73
topdir reorgs; let pdm have its src/ dir; separate vite area from light9/
author | drewp@bigasterisk.com |
---|---|
date | Sun, 12 May 2024 19:02:10 -0700 |
parents | light9/web/timeline/TimelineEditor.coffee@611c3e97de2f |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/timeline/TimelineEditor.coffee Sun May 12 19:02:10 2024 -0700 @@ -0,0 +1,194 @@ +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 + }))