diff options
Diffstat (limited to 'devtools/client/shared/components/splitter')
4 files changed, 358 insertions, 0 deletions
diff --git a/devtools/client/shared/components/splitter/draggable.js b/devtools/client/shared/components/splitter/draggable.js new file mode 100644 index 000000000..9caf93089 --- /dev/null +++ b/devtools/client/shared/components/splitter/draggable.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const React = require("devtools/client/shared/vendor/react"); +const ReactDOM = require("devtools/client/shared/vendor/react-dom"); +const { DOM: dom, PropTypes } = React; + +const Draggable = React.createClass({ + displayName: "Draggable", + + propTypes: { + onMove: PropTypes.func.isRequired, + onStart: PropTypes.func, + onStop: PropTypes.func, + style: PropTypes.object, + className: PropTypes.string + }, + + startDragging(ev) { + ev.preventDefault(); + const doc = ReactDOM.findDOMNode(this).ownerDocument; + doc.addEventListener("mousemove", this.onMove); + doc.addEventListener("mouseup", this.onUp); + this.props.onStart && this.props.onStart(); + }, + + onMove(ev) { + ev.preventDefault(); + // Use viewport coordinates so, moving mouse over iframes + // doesn't mangle (relative) coordinates. + this.props.onMove(ev.clientX, ev.clientY); + }, + + onUp(ev) { + ev.preventDefault(); + const doc = ReactDOM.findDOMNode(this).ownerDocument; + doc.removeEventListener("mousemove", this.onMove); + doc.removeEventListener("mouseup", this.onUp); + this.props.onStop && this.props.onStop(); + }, + + render() { + return dom.div({ + style: this.props.style, + className: this.props.className, + onMouseDown: this.startDragging + }); + } +}); + +module.exports = Draggable; diff --git a/devtools/client/shared/components/splitter/moz.build b/devtools/client/shared/components/splitter/moz.build new file mode 100644 index 000000000..924732aea --- /dev/null +++ b/devtools/client/shared/components/splitter/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DevToolsModules( + 'draggable.js', + 'split-box.css', + 'split-box.js', +) diff --git a/devtools/client/shared/components/splitter/split-box.css b/devtools/client/shared/components/splitter/split-box.css new file mode 100644 index 000000000..ea8fdaa6f --- /dev/null +++ b/devtools/client/shared/components/splitter/split-box.css @@ -0,0 +1,88 @@ +/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.split-box {
+ display: flex;
+ flex: 1;
+ min-width: 0;
+ height: 100%;
+ width: 100%;
+}
+
+.split-box.vert {
+ flex-direction: row;
+}
+
+.split-box.horz {
+ flex-direction: column;
+}
+
+.split-box > .uncontrolled {
+ display: flex;
+ flex: 1;
+ min-width: 0;
+ overflow: auto;
+}
+
+.split-box > .controlled {
+ display: flex;
+ overflow: auto;
+}
+
+.split-box > .splitter {
+ background-image: none;
+ border: 0;
+ border-style: solid;
+ border-color: transparent;
+ background-color: var(--theme-splitter-color);
+ background-clip: content-box;
+ position: relative;
+
+ box-sizing: border-box;
+
+ /* Positive z-index positions the splitter on top of its siblings and makes
+ it clickable on both sides. */
+ z-index: 1;
+}
+
+.split-box.vert > .splitter {
+ min-width: calc(var(--devtools-splitter-inline-start-width) +
+ var(--devtools-splitter-inline-end-width) + 1px);
+
+ border-inline-start-width: var(--devtools-splitter-inline-start-width);
+ border-inline-end-width: var(--devtools-splitter-inline-end-width);
+
+ margin-inline-start: calc(-1 * var(--devtools-splitter-inline-start-width) - 1px);
+ margin-inline-end: calc(-1 * var(--devtools-splitter-inline-end-width));
+
+ cursor: ew-resize;
+}
+
+.split-box.horz > .splitter {
+ min-height: calc(var(--devtools-splitter-top-width) +
+ var(--devtools-splitter-bottom-width) + 1px);
+
+ border-top-width: var(--devtools-splitter-top-width);
+ border-bottom-width: var(--devtools-splitter-bottom-width);
+
+ margin-top: calc(-1 * var(--devtools-splitter-top-width) - 1px);
+ margin-bottom: calc(-1 * var(--devtools-splitter-bottom-width));
+
+ cursor: ns-resize;
+}
+
+.split-box.disabled {
+ pointer-events: none;
+}
+
+/**
+ * Make sure splitter panels are not processing any mouse
+ * events. This is good for performance during splitter
+ * bar dragging.
+ */
+.split-box.dragging > .controlled,
+.split-box.dragging > .uncontrolled {
+ pointer-events: none;
+}
diff --git a/devtools/client/shared/components/splitter/split-box.js b/devtools/client/shared/components/splitter/split-box.js new file mode 100644 index 000000000..85835f3e1 --- /dev/null +++ b/devtools/client/shared/components/splitter/split-box.js @@ -0,0 +1,205 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const React = require("devtools/client/shared/vendor/react"); +const ReactDOM = require("devtools/client/shared/vendor/react-dom"); +const Draggable = React.createFactory(require("devtools/client/shared/components/splitter/draggable")); +const { DOM: dom, PropTypes } = React; + +/** + * This component represents a Splitter. The splitter supports vertical + * as well as horizontal mode. + */ +const SplitBox = React.createClass({ + displayName: "SplitBox", + + propTypes: { + // Custom class name. You can use more names separated by a space. + className: PropTypes.string, + // Initial size of controlled panel. + initialSize: PropTypes.number, + // Left/top panel + startPanel: PropTypes.any, + // Min panel size. + minSize: PropTypes.number, + // Max panel size. + maxSize: PropTypes.number, + // Right/bottom panel + endPanel: PropTypes.any, + // True if the right/bottom panel should be controlled. + endPanelControl: PropTypes.bool, + // Size of the splitter handle bar. + splitterSize: PropTypes.number, + // True if the splitter bar is vertical (default is vertical). + vert: PropTypes.bool + }, + + getDefaultProps() { + return { + splitterSize: 5, + vert: true, + endPanelControl: false + }; + }, + + /** + * The state stores the current orientation (vertical or horizontal) + * and the current size (width/height). All these values can change + * during the component's life time. + */ + getInitialState() { + return { + vert: this.props.vert, + width: this.props.initialWidth || this.props.initialSize, + height: this.props.initialHeight || this.props.initialSize + }; + }, + + // Dragging Events + + /** + * Set 'resizing' cursor on entire document during splitter dragging. + * This avoids cursor-flickering that happens when the mouse leaves + * the splitter bar area (happens frequently). + */ + onStartMove() { + const splitBox = ReactDOM.findDOMNode(this); + const doc = splitBox.ownerDocument; + let defaultCursor = doc.documentElement.style.cursor; + doc.documentElement.style.cursor = (this.state.vert ? "ew-resize" : "ns-resize"); + + splitBox.classList.add("dragging"); + + this.setState({ + defaultCursor: defaultCursor + }); + }, + + onStopMove() { + const splitBox = ReactDOM.findDOMNode(this); + const doc = splitBox.ownerDocument; + doc.documentElement.style.cursor = this.state.defaultCursor; + + splitBox.classList.remove("dragging"); + }, + + /** + * Adjust size of the controlled panel. Depending on the current + * orientation we either remember the width or height of + * the splitter box. + */ + onMove(x, y) { + const node = ReactDOM.findDOMNode(this); + const doc = node.ownerDocument; + const win = doc.defaultView; + + let size; + let { endPanelControl } = this.props; + + if (this.state.vert) { + // Switch the control flag in case of RTL. Note that RTL + // has impact on vertical splitter only. + let dir = win.getComputedStyle(doc.documentElement).direction; + if (dir == "rtl") { + endPanelControl = !endPanelControl; + } + + size = endPanelControl ? + (node.offsetLeft + node.offsetWidth) - x : + x - node.offsetLeft; + + this.setState({ + width: size + }); + } else { + size = endPanelControl ? + (node.offsetTop + node.offsetHeight) - y : + y - node.offsetTop; + + this.setState({ + height: size + }); + } + }, + + // Rendering + + render() { + const vert = this.state.vert; + const { startPanel, endPanel, endPanelControl, minSize, + maxSize, splitterSize } = this.props; + + let style = Object.assign({}, this.props.style); + + // Calculate class names list. + let classNames = ["split-box"]; + classNames.push(vert ? "vert" : "horz"); + if (this.props.className) { + classNames = classNames.concat(this.props.className.split(" ")); + } + + let leftPanelStyle; + let rightPanelStyle; + + // Set proper size for panels depending on the current state. + if (vert) { + leftPanelStyle = { + maxWidth: endPanelControl ? null : maxSize, + minWidth: endPanelControl ? null : minSize, + width: endPanelControl ? null : this.state.width + }; + rightPanelStyle = { + maxWidth: endPanelControl ? maxSize : null, + minWidth: endPanelControl ? minSize : null, + width: endPanelControl ? this.state.width : null + }; + } else { + leftPanelStyle = { + maxHeight: endPanelControl ? null : maxSize, + minHeight: endPanelControl ? null : minSize, + height: endPanelControl ? null : this.state.height + }; + rightPanelStyle = { + maxHeight: endPanelControl ? maxSize : null, + minHeight: endPanelControl ? minSize : null, + height: endPanelControl ? this.state.height : null + }; + } + + // Calculate splitter size + let splitterStyle = { + flex: "0 0 " + splitterSize + "px" + }; + + return ( + dom.div({ + className: classNames.join(" "), + style: style }, + startPanel ? + dom.div({ + className: endPanelControl ? "uncontrolled" : "controlled", + style: leftPanelStyle}, + startPanel + ) : null, + Draggable({ + className: "splitter", + style: splitterStyle, + onStart: this.onStartMove, + onStop: this.onStopMove, + onMove: this.onMove + }), + endPanel ? + dom.div({ + className: endPanelControl ? "controlled" : "uncontrolled", + style: rightPanelStyle}, + endPanel + ) : null + ) + ); + } +}); + +module.exports = SplitBox; |