You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openNG/src/client/components/window-manager.jsx

235 lines
6.6 KiB
JavaScript

'use strict';
const React = require("react");
const classnames = require("classnames");
const defaultValue = require("default-value");
const Window = require("./window");
const { useDraggable, mouseMove } = require("../hooks/draggable");
let windowRefs = new Map();
function trackRef(window_) {
return function setRef(element) {
/* FIXME: Verify that this doesn't leak memory! */
if (element != null) {
windowRefs.set(window_, element);
} else {
windowRefs.delete(window_);
}
};
}
function SideBar({elementRef, side, onMouseOver, onMouseOut, highlight}) {
function onMouseOverHandler(event) {
onMouseOver(event, side);
}
function onMouseOutHandler(event) {
onMouseOut(event, side);
}
return (
<div ref={elementRef} className={classnames("sidebar", `side-${side}`, {highlight: highlight})} onMouseOver={onMouseOverHandler} onMouseOut={onMouseOutHandler}>
</div>
);
}
function WindowSpace({children}) {
return (
<div className="windowSpace">
{children}
</div>
);
}
function ContentSpace({children}) {
return (
<div className="contentSpace">
{children}
</div>
);
}
module.exports = function WindowManager({store, children, windowMoveThreshold}) {
let [isResizing, setIsResizing] = React.useState(false);
let [isMoving, setIsMoving] = React.useState(false);
let [lastDragType, setLastDragType] = React.useState(null);
let [dragOperationData, setDragOperationData] = React.useState(null);
let [draggedWindow, setDraggedWindow] = React.useState(null);
let [highlightBar, setHighlightBar] = React.useState(null);
let [leftBarRef, setLeftBarRef] = React.useState(null);
let [rightBarRef, setRightBarRef] = React.useState(null);
let [leftBarBounds, setLeftBarBounds] = React.useState(null);
let [rightBarBounds, setRightBarBounds] = React.useState(null);
let [newX, newY] = useDraggable({
isDragging: isResizing || isMoving,
operationData: dragOperationData,
threshold: defaultValue(windowMoveThreshold, 6),
onChange: (xValue, yValue, mouseX, mouseY) => {
/* NOTE: we handle this out-of-band, to avoid going through React's rendering cycle for every move event. */
let element = windowRefs.get(draggedWindow);
if (isMoving) {
/* NOTE: We *only* change the highlightBar state if necessary, because otherwise we would still go through a rendering cycle for every move event after all. */
if (isInLeftBar(mouseX, mouseY)) {
if (highlightBar !== "left") {
setHighlightBar("left");
}
} else if (isInRightBar(mouseX, mouseY)) {
if (highlightBar !== "right") {
setHighlightBar("right");
}
} else {
if (highlightBar != null) {
setHighlightBar(null);
}
}
element.style.transform = `translate(${xValue}px, ${yValue}px)`;
} else if (isResizing) {
element.style.width = `${xValue}px`;
element.style.height = `${yValue}px`;
}
}
});
React.useEffect(() => {
if (leftBarRef != null) {
setLeftBarBounds(leftBarRef.getBoundingClientRect());
} else {
setLeftBarBounds(null);
}
if (rightBarRef != null) {
setRightBarBounds(rightBarRef.getBoundingClientRect());
} else {
setRightBarBounds(null);
}
}, [leftBarRef, rightBarRef]);
React.useEffect(() => {
if (draggedWindow != null) {
if (lastDragType === "move") {
store.setPosition(draggedWindow, newX, newY);
} else if (lastDragType === "resize") {
store.setDimensions(draggedWindow, newX, newY);
}
setDraggedWindow(null);
}
}, [newX, newY]);
function isInArea(x, y, bounds) {
return (x >= bounds.left && x < bounds.right && y >= bounds.top && y < bounds.bottom);
}
function isInLeftBar(x, y) {
return isInArea(x, y, leftBarBounds);
}
function isInRightBar(x, y) {
return isInArea(x, y, rightBarBounds);
}
/* FIXME: Consider sidestepping React entirely for the mousemove event handling. How much do synthetic events slow things down? */
function handleDragStart(type, window_, event, currentX, currentY) {
setDraggedWindow(window_.id);
setDragOperationData({
initialMouseX: event.pageX,
initialMouseY: event.pageY,
initialValueX: currentX, // width or X pos
initialValueY: currentY // height or Y pos
});
if (type === "move") {
setIsMoving(true);
} else if (type === "resize") {
setIsResizing(true);
}
setLastDragType(type);
/* NOTE: The below is to ensure that the window doesn't jump, if the user clicks the titlebar but never moves the mouse. No mousemove event is fired in that scenario, so if we don't set the last-known coordinates here, the window would jump based on whatever coordinate the mouse was last at during the *previous* window drag operation. */
mouseMove(event.pageX, event.pageY);
}
function handleTitleMouseDown (window_, event) {
return handleDragStart("move", window_, event, window_.x, window_.y);
}
function handleResizerMouseDown(window_, event) {
return handleDragStart("resize", window_, event, window_.width, window_.height);
}
function handleMouseUp () {
if (draggedWindow != null) {
setIsMoving(false);
setIsResizing(false);
}
}
function handleMouseMove(event) {
mouseMove(event.pageX, event.pageY);
}
function onMouseOverBar(_event, side) {
if (isMoving) {
setHighlightBar(side);
}
}
function onMouseOutBar(_event, _side) {
setHighlightBar(null);
}
/* TODO: Tiled layout */
return (
<div className="windowManager" onMouseMove={handleMouseMove} onMouseUp={handleMouseUp}>
<ContentSpace>
{children}
</ContentSpace>
<WindowSpace>
<SideBar elementRef={setLeftBarRef} side="left" highlight={highlightBar === "left"} onMouseOver={onMouseOverBar} onMouseOut={onMouseOutBar} />
{store.getFloating().toArray().map((window_) => {
let windowStyle = {
transform: `translate(${window_.x}px, ${window_.y}px)`,
width: window_.width,
height: window_.height,
zIndex: window_.zIndex
};
let handlers = {
onTitleMouseDown: (...args) => {
return handleTitleMouseDown(window_, ...args);
},
onResizerMouseDown: (...args) => {
return handleResizerMouseDown(window_, ...args);
},
onMouseDown: () => {
store.focus(window_.id);
},
onClose: () => {
store.close(window_.id);
}
};
return (
<Window elementRef={trackRef(window_.id)} key={window_.id} style={windowStyle} title={window_.title} isActive={window_.isActive} resizable={window_.resizable} {...handlers}>
{window_.contents}
</Window>
);
})}
<SideBar elementRef={setRightBarRef} side="right" highlight={highlightBar === "right"} onMouseOver={onMouseOverBar} onMouseOut={onMouseOutBar} />
</WindowSpace>
</div>
);
};