diff options
Diffstat (limited to 'Source/WebCore/Modules/modern-media-controls/controls/tracks-panel.js')
-rw-r--r-- | Source/WebCore/Modules/modern-media-controls/controls/tracks-panel.js | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/Source/WebCore/Modules/modern-media-controls/controls/tracks-panel.js b/Source/WebCore/Modules/modern-media-controls/controls/tracks-panel.js new file mode 100644 index 000000000..4a69bb6e2 --- /dev/null +++ b/Source/WebCore/Modules/modern-media-controls/controls/tracks-panel.js @@ -0,0 +1,305 @@ + +class TracksPanel extends LayoutNode +{ + + constructor() + { + super(`<div class="tracks-panel"></div>`); + this._backgroundTint = new BackgroundTint; + this._rightX = 0; + this._bottomY = 0; + } + + // Public + + get presented() + { + return !!this.parent; + } + + presentInParent(node) + { + if (this.parent === node) + return; + + this.children = this._childrenFromDataSource(); + + node.addChild(this); + + this.element.removeEventListener("transitionend", this); + this.element.classList.remove("fade-out"); + + this._mousedownTarget().addEventListener("mousedown", this, true); + window.addEventListener("keydown", this, true); + + this._focusedTrackNode = null; + } + + hide() + { + if (!this.presented) + return; + + this._mousedownTarget().removeEventListener("mousedown", this, true); + window.removeEventListener("keydown", this, true); + + this.element.addEventListener("transitionend", this); + + // Ensure a transition will indeed happen by starting it only on the next frame. + window.requestAnimationFrame(() => { this.element.classList.add("fade-out"); }); + } + + get bottomY() + { + return this._bottomY; + } + + set bottomY(bottomY) + { + if (this._bottomY === bottomY) + return; + + this._bottomY = bottomY; + this.markDirtyProperty("bottomY"); + } + + get rightX() + { + return this._rightX; + } + + set rightX(x) + { + if (this._rightX === x) + return; + + this._rightX = x; + this.markDirtyProperty("rightX"); + } + + // Protected + + trackNodeSelectionAnimationDidEnd(trackNode) + { + if (this.uiDelegate && typeof this.uiDelegate.tracksPanelSelectionDidChange === "function") + this.uiDelegate.tracksPanelSelectionDidChange(trackNode.index, trackNode.sectionIndex); + } + + mouseMovedOverTrackNode(trackNode) + { + this._focusTrackNode(trackNode); + } + + mouseExitedTrackNode(trackNode) + { + this._focusedTrackNode.element.blur(); + delete this._focusedTrackNode; + } + + commitProperty(propertyName) + { + if (propertyName === "rightX") + this.element.style.right = `${this._rightX}px`; + else if (propertyName === "bottomY") + this.element.style.bottom = `${this._bottomY}px`; + else + super.commitProperty(propertyName); + } + + handleEvent(event) + { + switch (event.type) { + case "mousedown": + this._handleMousedown(event); + break; + case "keydown": + this._handleKeydown(event); + break; + case "transitionend": + this.remove(); + break; + } + } + + // Private + + _mousedownTarget() + { + const mediaControls = this.parentOfType(MacOSFullscreenMediaControls); + if (mediaControls) + return mediaControls.element; + return window; + } + + _childrenFromDataSource() + { + const children = [this._backgroundTint]; + + this._trackNodes = []; + + const dataSource = this.dataSource; + if (!dataSource) + return children; + + const numberOfSections = dataSource.tracksPanelNumberOfSections(); + if (numberOfSections === 0) + return children; + + for (let sectionIndex = 0; sectionIndex < numberOfSections; ++sectionIndex) { + let sectionNode = new LayoutNode(`<section></section>`); + sectionNode.addChild(new LayoutNode(`<h3>${dataSource.tracksPanelTitleForSection(sectionIndex)}</h3>`)); + + let tracksListNode = sectionNode.addChild(new LayoutNode(`<ul></ul>`)); + let numberOfTracks = dataSource.tracksPanelNumberOfTracksInSection(sectionIndex); + for (let trackIndex = 0; trackIndex < numberOfTracks; ++trackIndex) { + let trackTitle = dataSource.tracksPanelTitleForTrackInSection(trackIndex, sectionIndex); + let trackSelected = dataSource.tracksPanelIsTrackInSectionSelected(trackIndex, sectionIndex); + let trackNode = tracksListNode.addChild(new TrackNode(trackIndex, sectionIndex, trackTitle, trackSelected, this)); + this._trackNodes.push(trackNode); + } + children.push(sectionNode); + } + + return children; + } + + _handleMousedown(event) + { + if (this.element.contains(event.target)) + return; + + this._dismiss(); + + event.preventDefault(); + event.stopPropagation(); + } + + _handleKeydown(event) + { + switch (event.key) { + case "Home": + case "PageUp": + this._focusFirstTrackNode(); + break; + case "End": + case "PageDown": + this._focusLastTrackNode(); + break; + case "ArrowDown": + if (event.altKey || event.metaKey) + this._focusLastTrackNode(); + else + this._focusNextTrackNode(); + break; + case "ArrowUp": + if (event.altKey || event.metaKey) + this._focusFirstTrackNode(); + else + this._focusPreviousTrackNode(); + break; + case " ": + case "Enter": + if (this._focusedTrackNode) + this._focusedTrackNode.activate(); + break; + case "Escape": + this._dismiss(); + break; + } + } + + _dismiss() + { + if (this.parent && typeof this.parent.hideTracksPanel === "function") + this.parent.hideTracksPanel(); + } + + _focusTrackNode(trackNode) + { + if (!trackNode || trackNode === this._focusedTrackNode) + return; + + trackNode.element.focus(); + this._focusedTrackNode = trackNode; + } + + _focusPreviousTrackNode() + { + const previousIndex = this._focusedTrackNode ? this._trackNodes.indexOf(this._focusedTrackNode) - 1 : this._trackNodes.length - 1; + this._focusTrackNode(this._trackNodes[previousIndex]); + } + + _focusNextTrackNode() + { + this._focusTrackNode(this._trackNodes[this._trackNodes.indexOf(this._focusedTrackNode) + 1]); + } + + _focusFirstTrackNode() + { + this._focusTrackNode(this._trackNodes[0]); + } + + _focusLastTrackNode() + { + this._focusTrackNode(this._trackNodes[this._trackNodes.length - 1]); + } + +} + +class TrackNode extends LayoutNode +{ + + constructor(index, sectionIndex, title, selected, panel) + { + super(`<li tabindex="0">${title}</li>`); + + this.index = index; + this.sectionIndex = sectionIndex; + this._panel = panel; + this._selected = selected; + + if (selected) + this.element.classList.add("selected"); + + this.element.addEventListener("mousemove", this); + this.element.addEventListener("mouseleave", this); + this.element.addEventListener("click", this); + } + + // Public + + activate() + { + this.element.addEventListener("animationend", this); + this.element.classList.add("animated"); + } + + // Protected + + handleEvent(event) + { + switch (event.type) { + case "mousemove": + this._panel.mouseMovedOverTrackNode(this); + break; + case "mouseleave": + this._panel.mouseExitedTrackNode(this); + break; + case "click": + this.activate(); + break; + case "animationend": + this._animationDidEnd(); + break; + } + } + + // Private + + _animationDidEnd() + { + this.element.removeEventListener("animationend", this); + this._panel.trackNodeSelectionAnimationDidEnd(this); + } + +} |