diff --git a/js/ui/menu-size-adjust.js b/js/ui/menu-size-adjust.js new file mode 100644 index 0000000..84caa63 --- /dev/null +++ b/js/ui/menu-size-adjust.js @@ -0,0 +1,118 @@ +export default class MenuResizer { + constructor(menu) { + const menuHandleClass = 'menu-size-adjust-handle'; + const menuElm = menu.getRootPane(); + const menuHandle = document.createElement('div'); + menuHandle.classList.add(menuHandleClass); + menuElm.appendChild(menuHandle); + + const timelineHandleClass = 'timeline-size-adjust-handle'; + const timelineElm = menu.getTimeline().getRootPane(); + const timelineHandle = document.createElement('div'); + timelineHandle.classList.add(timelineHandleClass); + timelineElm.appendChild(timelineHandle); + + const styleElm = document.createElement('style'); + document.body.appendChild(styleElm); + const stylesheet = styleElm.sheet; + // don't enable this feature on mobile + if (menu.isCoverFullWidth()) { + this.stylesheet.disabled = true; + } + stylesheet.insertRule(` + .${menuHandleClass} { + display: block; + } + `); + stylesheet.insertRule(` + .${timelineHandleClass} { + display: block; + } + `); + window.addEventListener('resize', () => this.onresize()); + + this.menu = menu; + this.stylesheet = stylesheet; + this.menuElm = menuElm; + this.menuHandle = menuHandle; + this.timelineElm = timelineElm; + this.timelineHandle = timelineHandle; + + this.setupMenuResizing(); + this.setupTimelineResizing(); + } + + onresize() { + // TODO update dimensions to match new window size + } + + setupMenuResizing() { + const cssRules = this.stylesheet.cssRules; + // FIXME this whole approach of continuously adding and removing + // rules is prone to index-invalidation issues. + // However, since the toggle button has two identities: + // 1. open menu *outside* menu itself + // 2. close menu being (seemingly) part of it + // setting the style directly is impossible since we need the + // :checked selector + let toggleBtnLeft = 0; + const toggleBtnSelector = '.menu-container > input:checked + label[title="Toggle menu"]'; + let toggleBtnRule = this.stylesheet.insertRule(` + ${toggleBtnSelector} { + /* No rule needed yet */ + } + `, cssRules.length); + + this.menuHandle.addEventListener('mousedown', (evt) => { + let prevX = evt.clientX; + let menuWidth = this.menuElm.offsetWidth; + let timelineWidth = this.timelineElm.offsetWidth; + const onDrag = (evt) => { + const delta = evt.clientX - prevX; + prevX = evt.clientX; + + menuWidth += delta; + this.menuElm.setAttribute('style', `width: ${menuWidth}px`); + + timelineWidth -= delta; + this.timelineElm.style.width = `${timelineWidth}px`; + this.timelineElm.style.right = '0'; + this.timelineElm.style.left = 'unset'; + + toggleBtnLeft += delta; + this.stylesheet.removeRule(toggleBtnRule); + toggleBtnRule = this.stylesheet.insertRule(` + ${toggleBtnSelector} { + transform: translateX(-100%) translateX(${toggleBtnLeft}px) !important; + } + `, cssRules.length); + }; + const onDragEnd = (evt) => { + document.documentElement.removeEventListener('mousemove', onDrag); + document.documentElement.removeEventListener('mouseup', onDragEnd); + }; + document.documentElement.addEventListener('mousemove', onDrag); + document.documentElement.addEventListener('mouseup', onDragEnd); + }); + } + + setupTimelineResizing() { + this.timelineHandle.addEventListener('mousedown', (evt) => { + let prevY = evt.clientY; + let timelineHeight = this.timelineElm.offsetHeight; + const onDrag = (evt) => { + const delta = evt.clientY - prevY; + prevY = evt.clientY; + + timelineHeight -= delta; + this.timelineElm.style.height = `${timelineHeight}px`; + }; + const onDragEnd = (evt) => { + document.documentElement.removeEventListener('mousemove', onDrag); + document.documentElement.removeEventListener('mouseup', onDragEnd); + }; + document.documentElement.addEventListener('mousemove', onDrag); + document.documentElement.addEventListener('mouseup', onDragEnd); + }); + } +} diff --git a/js/ui/menu.js b/js/ui/menu.js index a9852a1..070bb10 100644 --- a/js/ui/menu.js +++ b/js/ui/menu.js @@ -3,6 +3,7 @@ import Config from '../config'; import Timeline from './timeline'; import Control from './control'; import PresetSelect from './menu-preset-select'; +import MenuResizer from './menu-size-adjust'; import { parseHtml } from './util'; import EffectConfig from '../effects/effect-config'; @@ -437,6 +438,8 @@ export default class MainMenu { } this.timeline.loadTimeline(tracks); + this.menuResizer = new MenuResizer(this); + this.submittedConfig = this.readConfig(); } @@ -483,6 +486,16 @@ export default class MainMenu { return parseInt(menuWidth, 10) === window.innerWidth; } + /// @return the dom element serving as the top-level container of all + /// menu elements and also defining the menu's bounding box + getRootPane() { + return this.menuContent; + } + + getTimeline() { + return this.timeline; + } + notifyChange() { this.applyBtn.disabled = false; } diff --git a/js/ui/timeline.js b/js/ui/timeline.js index da9731b..f8dc061 100644 --- a/js/ui/timeline.js +++ b/js/ui/timeline.js @@ -763,4 +763,9 @@ export default class Timeline { } } } + /// @return the dom element serving as the top-level container of all + /// timeline elements and also defining the timeline's bounding box + getRootPane() { + return this.element; + } } diff --git a/sass/_menu-size-adjust.scss b/sass/_menu-size-adjust.scss new file mode 100644 index 0000000..01d8b16 --- /dev/null +++ b/sass/_menu-size-adjust.scss @@ -0,0 +1,25 @@ +@import 'mixins/unselectable'; + +.menu-size-adjust-handle { + position: absolute; + top: 0; + right: 0; + width: 3px; + height: 100%; + cursor: ew-resize; + @include unselectable; + + display: none; /* this will be overridden by js */ +} + +.timeline-size-adjust-handle { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 3px; + cursor: ns-resize; + @include unselectable; + + display: none; /* this will be overridden by js */ +} diff --git a/sass/_menu.scss b/sass/_menu.scss index 31d0bd0..52ec758 100644 --- a/sass/_menu.scss +++ b/sass/_menu.scss @@ -5,6 +5,8 @@ @import 'mixins/button'; @import 'mixins/icons'; +@import 'menu-size-adjust'; + $menu-background-color: rgba(255, 255, 255, 0.9); $menu-apply-btn-height: 10%;