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.
129 lines
4.1 KiB
React
129 lines
4.1 KiB
React
'use strict';
|
|
|
|
const React = require("react");
|
|
const createReactClass = require("create-react-class");
|
|
const throttleit = require("throttleit");
|
|
const euclideanDistance = require("euclidean-distance");
|
|
|
|
const Window = require("./window");
|
|
|
|
module.exports = createReactClass({
|
|
displayName: "WindowManager",
|
|
getDefaultProps: function () {
|
|
return {
|
|
windowMoveThreshold: 6
|
|
};
|
|
},
|
|
getInitialState: function () {
|
|
return {
|
|
movingWindow: null,
|
|
activeWindow: null,
|
|
windowPositions: {}
|
|
};
|
|
},
|
|
lastMousePositionX: null,
|
|
lastMousePositionY: null,
|
|
componentDidMount: function () {
|
|
/* Due to how synthetic events work in React, we need to separate out the 'get coordinates from event' and 'do something with the coordinates' step; if we throttle the coordinate extraction logic, we'll run into problems when synthetic events have already been cleared for reuse by the time we try to obtain the coordinates. Therefore, `processMouseMove` contains all the throttled logic, whereas the coordinate extraction happens on *every* mousemove event. */
|
|
this.processMouseMove = throttleit(() => {
|
|
/* Yes, the below check is there for a reason; just in case the `movingWindow` state changes between the call to the throttled wrapper and the wrapped function itself. */
|
|
if (this.state.movingWindow != null) {
|
|
let thresholdMet = this.state.movingWindowThresholdMet;
|
|
|
|
/* Since we're outside of a React-controlled handler, we can't rely on React to batch updates. Therefore, we have to do our own impromptu batching. */
|
|
let stateToUpdate = {};
|
|
|
|
if (thresholdMet === false) {
|
|
let origin = [this.state.movingWindowOriginX, this.state.movingWindowOriginY];
|
|
let position = [this.lastMousePositionX, this.lastMousePositionY];
|
|
|
|
if (euclideanDistance(origin, position) > this.props.windowMoveThreshold) {
|
|
thresholdMet = true;
|
|
stateToUpdate.thresholdMet = true;
|
|
}
|
|
}
|
|
|
|
if (thresholdMet === true) {
|
|
/* TODO: Consider handling this move out-of-band, to avoid going through React's rendering cycle for every move event. */
|
|
stateToUpdate.windowPositions = Object.assign(this.state.windowPositions, {
|
|
[this.state.movingWindow]: {
|
|
x: this.lastMousePositionX - this.state.movingWindowBarX,
|
|
y: this.lastMousePositionY - this.state.movingWindowBarY
|
|
}
|
|
});
|
|
}
|
|
|
|
/* FIXME: Move this into the store? */
|
|
this.setState(stateToUpdate);
|
|
}
|
|
}, 10);
|
|
},
|
|
handleMouseMove: function(event) {
|
|
if (this.state.movingWindow != null) {
|
|
this.lastMousePositionX = event.pageX;
|
|
this.lastMousePositionY = event.pageY;
|
|
this.processMouseMove();
|
|
}
|
|
},
|
|
handleMouseUp: function () {
|
|
this.setState({
|
|
movingWindow: null
|
|
});
|
|
},
|
|
handleTitleMouseDown: function (window, event, barX, barY) {
|
|
this.setState({
|
|
movingWindow: window.id,
|
|
movingWindowThresholdMet: false,
|
|
movingWindowBarX: barX,
|
|
movingWindowBarY: barY,
|
|
movingWindowOriginX: event.pageX,
|
|
movingWindowOriginY: event.pageY
|
|
});
|
|
},
|
|
render: function () {
|
|
/* TODO: Tiled layout */
|
|
|
|
return (
|
|
<div className="windowManager" onMouseMove={this.handleMouseMove} onMouseUp={this.handleMouseUp}>
|
|
{this.props.children}
|
|
{this.props.store.getAll().toArray().map((window_) => {
|
|
let x, y;
|
|
|
|
let overrideWindowPosition = this.state.windowPositions[window_.id];
|
|
|
|
if (overrideWindowPosition != null) {
|
|
x = overrideWindowPosition.x;
|
|
y = overrideWindowPosition.y;
|
|
} else {
|
|
x = window_.initialX;
|
|
y = window_.initialY;
|
|
}
|
|
|
|
let windowStyle = {
|
|
transform: `translate(${x}px, ${y}px)`,
|
|
width: window_.width,
|
|
height: window_.height,
|
|
zIndex: window_.zIndex
|
|
};
|
|
|
|
let handlers = {
|
|
onTitleMouseDown: (...args) => {
|
|
return this.handleTitleMouseDown(window_, ...args);
|
|
},
|
|
onMouseDown: () => {
|
|
this.props.store.focus(window_.id);
|
|
},
|
|
onClose: () => {
|
|
this.props.store.close(window_.id);
|
|
}
|
|
};
|
|
|
|
return (<Window key={window_.id} style={windowStyle} title={window_.title} isActive={window_.isActive} {...handlers}>
|
|
{window_.contents}
|
|
</Window>);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
});
|