mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
Scrollbars (#602)
This commit is contained in:
parent
2791866a9e
commit
50de05cee4
10 changed files with 297 additions and 56 deletions
|
@ -90,10 +90,6 @@ $border-radius: 0.25rem;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.with-eye-dropper {
|
|
||||||
cursor: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mode-selector {
|
.mode-selector {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: calc(2 * $grid-unit);
|
margin-right: calc(2 * $grid-unit);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import PaperCanvas from '../../containers/paper-canvas.jsx';
|
import PaperCanvas from '../../containers/paper-canvas.jsx';
|
||||||
|
import ScrollableCanvas from '../../containers/scrollable-canvas.jsx';
|
||||||
|
|
||||||
import BitBrushMode from '../../containers/bit-brush-mode.jsx';
|
import BitBrushMode from '../../containers/bit-brush-mode.jsx';
|
||||||
import BitLineMode from '../../containers/bit-line-mode.jsx';
|
import BitLineMode from '../../containers/bit-line-mode.jsx';
|
||||||
|
@ -200,11 +201,10 @@ const PaintEditorComponent = props => (
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{/* Canvas */}
|
{/* Canvas */}
|
||||||
<div
|
<ScrollableCanvas
|
||||||
className={classNames(
|
canvas={props.canvas}
|
||||||
styles.canvasContainer,
|
hideCursor={props.isEyeDropping}
|
||||||
{[styles.withEyeDropper]: props.isEyeDropping}
|
style={styles.canvasContainer}
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<PaperCanvas
|
<PaperCanvas
|
||||||
canvasRef={props.setCanvas}
|
canvasRef={props.setCanvas}
|
||||||
|
@ -231,7 +231,7 @@ const PaintEditorComponent = props => (
|
||||||
</Box>
|
</Box>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
</div>
|
</ScrollableCanvas>
|
||||||
<div className={styles.canvasControls}>
|
<div className={styles.canvasControls}>
|
||||||
{isVector(props.format) ?
|
{isVector(props.format) ?
|
||||||
<Button
|
<Button
|
||||||
|
|
36
src/components/scrollable-canvas/scrollable-canvas.css
Normal file
36
src/components/scrollable-canvas/scrollable-canvas.css
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
$scrollbar-size: 6px;
|
||||||
|
$scrollbar-padding: 1px;
|
||||||
|
|
||||||
|
.vertical-scrollbar, .horizontal-scrollbar {
|
||||||
|
position: absolute;
|
||||||
|
background: #BEBEBECD;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.vertical-scrollbar-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
width: $scrollbar-size;
|
||||||
|
right: $scrollbar-padding;
|
||||||
|
top: $scrollbar-padding;
|
||||||
|
height: calc(100% - $scrollbar-size - $scrollbar-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-scrollbar-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
height: $scrollbar-size;
|
||||||
|
left: $scrollbar-padding;
|
||||||
|
bottom: $scrollbar-padding;
|
||||||
|
width: calc(100% - $scrollbar-size - $scrollbar-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-scrollbar {
|
||||||
|
width: $scrollbar-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-scrollbar {
|
||||||
|
height: $scrollbar-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-cursor {
|
||||||
|
cursor: none;
|
||||||
|
}
|
54
src/components/scrollable-canvas/scrollable-canvas.jsx
Normal file
54
src/components/scrollable-canvas/scrollable-canvas.jsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import styles from './scrollable-canvas.css';
|
||||||
|
|
||||||
|
const ScrollableCanvasComponent = props => (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
props.style,
|
||||||
|
{[styles.hideCursor]: props.hideCursor}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
<div className={styles.horizontalScrollbarWrapper}>
|
||||||
|
<div
|
||||||
|
className={styles.horizontalScrollbar}
|
||||||
|
style={{
|
||||||
|
width: `${props.horizontalScrollLengthPercent}%`,
|
||||||
|
left: `${props.horizontalScrollStartPercent}%`,
|
||||||
|
visibility: `${props.hideCursor ||
|
||||||
|
Math.abs(props.horizontalScrollLengthPercent - 100) < 1e-8 ? 'hidden' : 'visible'}`
|
||||||
|
}}
|
||||||
|
onMouseDown={props.onHorizontalScrollbarMouseDown}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.verticalScrollbarWrapper}>
|
||||||
|
<div
|
||||||
|
className={styles.verticalScrollbar}
|
||||||
|
style={{
|
||||||
|
height: `${props.verticalScrollLengthPercent}%`,
|
||||||
|
top: `${props.verticalScrollStartPercent}%`,
|
||||||
|
visibility: `${props.hideCursor ||
|
||||||
|
Math.abs(props.verticalScrollLengthPercent - 100) < 1e-8 ? 'hidden' : 'visible'}`
|
||||||
|
}}
|
||||||
|
onMouseDown={props.onVerticalScrollbarMouseDown}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
ScrollableCanvasComponent.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
hideCursor: PropTypes.bool,
|
||||||
|
horizontalScrollLengthPercent: PropTypes.number,
|
||||||
|
horizontalScrollStartPercent: PropTypes.number,
|
||||||
|
onHorizontalScrollbarMouseDown: PropTypes.func.isRequired,
|
||||||
|
onVerticalScrollbarMouseDown: PropTypes.func.isRequired,
|
||||||
|
style: PropTypes.string,
|
||||||
|
verticalScrollLengthPercent: PropTypes.number,
|
||||||
|
verticalScrollStartPercent: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScrollableCanvasComponent;
|
|
@ -100,8 +100,11 @@ class PaintEditor extends React.Component {
|
||||||
this.startEyeDroppingLoop();
|
this.startEyeDroppingLoop();
|
||||||
} else if (!this.props.isEyeDropping && prevProps.isEyeDropping) {
|
} else if (!this.props.isEyeDropping && prevProps.isEyeDropping) {
|
||||||
this.stopEyeDroppingLoop();
|
this.stopEyeDroppingLoop();
|
||||||
|
} else if (this.props.isEyeDropping && this.props.viewBounds !== prevProps.viewBounds) {
|
||||||
|
this.props.previousTool.activate();
|
||||||
|
this.props.onDeactivateEyeDropper();
|
||||||
|
this.stopEyeDroppingLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.format === Formats.VECTOR && isBitmap(prevProps.format)) {
|
if (this.props.format === Formats.VECTOR && isBitmap(prevProps.format)) {
|
||||||
this.isSwitchingFormats = false;
|
this.isSwitchingFormats = false;
|
||||||
convertToVector(this.props.clearSelectedItems, this.handleUpdateImage);
|
convertToVector(this.props.clearSelectedItems, this.handleUpdateImage);
|
||||||
|
@ -329,7 +332,6 @@ class PaintEditor extends React.Component {
|
||||||
this.props.previousTool.activate();
|
this.props.previousTool.activate();
|
||||||
this.props.onDeactivateEyeDropper();
|
this.props.onDeactivateEyeDropper();
|
||||||
this.stopEyeDroppingLoop();
|
this.stopEyeDroppingLoop();
|
||||||
this.setState({colorInfo: null});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
startEyeDroppingLoop () {
|
startEyeDroppingLoop () {
|
||||||
|
@ -367,6 +369,7 @@ class PaintEditor extends React.Component {
|
||||||
}
|
}
|
||||||
stopEyeDroppingLoop () {
|
stopEyeDroppingLoop () {
|
||||||
clearInterval(this.intervalId);
|
clearInterval(this.intervalId);
|
||||||
|
this.setState({colorInfo: null});
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
|
@ -442,7 +445,8 @@ PaintEditor.propTypes = {
|
||||||
stack: PropTypes.arrayOf(PropTypes.object).isRequired,
|
stack: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
pointer: PropTypes.number.isRequired
|
pointer: PropTypes.number.isRequired
|
||||||
}),
|
}),
|
||||||
updateViewBounds: PropTypes.func.isRequired
|
updateViewBounds: PropTypes.func.isRequired,
|
||||||
|
viewBounds: PropTypes.instanceOf(paper.Matrix).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -455,7 +459,8 @@ const mapStateToProps = state => ({
|
||||||
previousTool: state.scratchPaint.color.eyeDropper.previousTool,
|
previousTool: state.scratchPaint.color.eyeDropper.previousTool,
|
||||||
selectedItems: state.scratchPaint.selectedItems,
|
selectedItems: state.scratchPaint.selectedItems,
|
||||||
textEditing: state.scratchPaint.textEditTarget !== null,
|
textEditing: state.scratchPaint.textEditTarget !== null,
|
||||||
undoState: state.scratchPaint.undo
|
undoState: state.scratchPaint.undo,
|
||||||
|
viewBounds: state.scratchPaint.viewBounds
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
onKeyPress: event => {
|
onKeyPress: event => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.paper-canvas {
|
.paper-canvas {
|
||||||
width: 480px;
|
width: 480px;
|
||||||
height: 360px;
|
height: 360px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,12 @@ import {isGroup, ungroupItems} from '../helper/group';
|
||||||
import {clearRaster, getRaster, setupLayers} from '../helper/layer';
|
import {clearRaster, getRaster, setupLayers} from '../helper/layer';
|
||||||
import {deleteSelection, getSelectedLeafItems} from '../helper/selection';
|
import {deleteSelection, getSelectedLeafItems} from '../helper/selection';
|
||||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, pan, resetZoom, zoomOnFixedPoint} from '../helper/view';
|
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, resetZoom} from '../helper/view';
|
||||||
import {ensureClockwise, scaleWithStrokes} from '../helper/math';
|
import {ensureClockwise, scaleWithStrokes} from '../helper/math';
|
||||||
import {clearHoveredItem} from '../reducers/hover';
|
import {clearHoveredItem} from '../reducers/hover';
|
||||||
import {clearPasteOffset} from '../reducers/clipboard';
|
import {clearPasteOffset} from '../reducers/clipboard';
|
||||||
import {updateViewBounds} from '../reducers/view-bounds';
|
|
||||||
import {changeFormat} from '../reducers/format';
|
import {changeFormat} from '../reducers/format';
|
||||||
|
import {updateViewBounds} from '../reducers/view-bounds';
|
||||||
import styles from './paper-canvas.css';
|
import styles from './paper-canvas.css';
|
||||||
|
|
||||||
class PaperCanvas extends React.Component {
|
class PaperCanvas extends React.Component {
|
||||||
|
@ -30,7 +29,6 @@ class PaperCanvas extends React.Component {
|
||||||
'setCanvas',
|
'setCanvas',
|
||||||
'importSvg',
|
'importSvg',
|
||||||
'handleKeyDown',
|
'handleKeyDown',
|
||||||
'handleWheel',
|
|
||||||
'switchCostume'
|
'switchCostume'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -214,38 +212,6 @@ class PaperCanvas extends React.Component {
|
||||||
this.props.canvasRef(canvas);
|
this.props.canvasRef(canvas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleWheel (event) {
|
|
||||||
// Multiplier variable, so that non-pixel-deltaModes are supported. Needed for Firefox.
|
|
||||||
// See #529 (or LLK/scratch-blocks#1190).
|
|
||||||
const multiplier = event.deltaMode === 0x1 ? 15 : 1;
|
|
||||||
const deltaX = event.deltaX * multiplier;
|
|
||||||
const deltaY = event.deltaY * multiplier;
|
|
||||||
if (event.metaKey || event.ctrlKey) {
|
|
||||||
// Zoom keeping mouse location fixed
|
|
||||||
const canvasRect = this.canvas.getBoundingClientRect();
|
|
||||||
const offsetX = event.clientX - canvasRect.left;
|
|
||||||
const offsetY = event.clientY - canvasRect.top;
|
|
||||||
const fixedPoint = paper.project.view.viewToProject(
|
|
||||||
new paper.Point(offsetX, offsetY)
|
|
||||||
);
|
|
||||||
zoomOnFixedPoint(-deltaY / 100, fixedPoint);
|
|
||||||
this.props.updateViewBounds(paper.view.matrix);
|
|
||||||
this.props.setSelectedItems(this.props.format);
|
|
||||||
} else if (event.shiftKey && event.deltaX === 0) {
|
|
||||||
// Scroll horizontally (based on vertical scroll delta)
|
|
||||||
// This is needed as for some browser/system combinations which do not set deltaX.
|
|
||||||
// See #156.
|
|
||||||
const dx = deltaY / paper.project.view.zoom;
|
|
||||||
pan(dx, 0);
|
|
||||||
this.props.updateViewBounds(paper.view.matrix);
|
|
||||||
} else {
|
|
||||||
const dx = deltaX / paper.project.view.zoom;
|
|
||||||
const dy = deltaY / paper.project.view.zoom;
|
|
||||||
pan(dx, dy);
|
|
||||||
this.props.updateViewBounds(paper.view.matrix);
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<canvas
|
<canvas
|
||||||
|
@ -253,7 +219,6 @@ class PaperCanvas extends React.Component {
|
||||||
height="360px"
|
height="360px"
|
||||||
ref={this.setCanvas}
|
ref={this.setCanvas}
|
||||||
width="480px"
|
width="480px"
|
||||||
onWheel={this.handleWheel}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
174
src/containers/scrollable-canvas.jsx
Normal file
174
src/containers/scrollable-canvas.jsx
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
import paper from '@scratch/paper';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import ScrollableCanvasComponent from '../components/scrollable-canvas/scrollable-canvas.jsx';
|
||||||
|
|
||||||
|
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, clampViewBounds, pan, zoomOnFixedPoint} from '../helper/view';
|
||||||
|
import {updateViewBounds} from '../reducers/view-bounds';
|
||||||
|
import {redrawSelectionBox} from '../reducers/selected-items';
|
||||||
|
|
||||||
|
import {getEventXY} from '../lib/touch-utils';
|
||||||
|
import bindAll from 'lodash.bindall';
|
||||||
|
|
||||||
|
class ScrollableCanvas extends React.Component {
|
||||||
|
static get ZOOM_INCREMENT () {
|
||||||
|
return 0.5;
|
||||||
|
}
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
|
'handleHorizontalScrollbarMouseDown',
|
||||||
|
'handleHorizontalScrollbarMouseMove',
|
||||||
|
'handleHorizontalScrollbarMouseUp',
|
||||||
|
'handleVerticalScrollbarMouseDown',
|
||||||
|
'handleVerticalScrollbarMouseMove',
|
||||||
|
'handleVerticalScrollbarMouseUp',
|
||||||
|
'handleWheel'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
componentDidMount () {
|
||||||
|
if (this.props.canvas) {
|
||||||
|
this.props.canvas.addEventListener('wheel', this.handleWheel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (nextProps.canvas) {
|
||||||
|
if (this.props.canvas) {
|
||||||
|
this.props.canvas.removeEventListener('wheel', this.handleWheel);
|
||||||
|
}
|
||||||
|
nextProps.canvas.addEventListener('wheel', this.handleWheel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleHorizontalScrollbarMouseDown (event) {
|
||||||
|
this.initialMouseX = getEventXY(event).x;
|
||||||
|
this.initialScreenX = paper.view.matrix.tx;
|
||||||
|
window.addEventListener('mousemove', this.handleHorizontalScrollbarMouseMove);
|
||||||
|
window.addEventListener('mouseup', this.handleHorizontalScrollbarMouseUp);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
handleHorizontalScrollbarMouseMove (event) {
|
||||||
|
const dx = this.initialMouseX - getEventXY(event).x;
|
||||||
|
paper.view.matrix.tx = this.initialScreenX + (dx * paper.view.zoom * 2);
|
||||||
|
clampViewBounds();
|
||||||
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
handleHorizontalScrollbarMouseUp () {
|
||||||
|
window.removeEventListener('mousemove', this.handleHorizontalScrollbarMouseMove);
|
||||||
|
window.removeEventListener('mouseup', this.handleHorizontalScrollbarMouseUp);
|
||||||
|
this.initialMouseX = null;
|
||||||
|
this.initialScreenX = null;
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
handleVerticalScrollbarMouseDown (event) {
|
||||||
|
this.initialMouseY = getEventXY(event).y;
|
||||||
|
this.initialScreenY = paper.view.matrix.ty;
|
||||||
|
window.addEventListener('mousemove', this.handleVerticalScrollbarMouseMove);
|
||||||
|
window.addEventListener('mouseup', this.handleVerticalScrollbarMouseUp);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
handleVerticalScrollbarMouseMove (event) {
|
||||||
|
const dy = this.initialMouseY - getEventXY(event).y;
|
||||||
|
paper.view.matrix.ty = this.initialScreenY + (dy * paper.view.zoom * 2);
|
||||||
|
clampViewBounds();
|
||||||
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
handleVerticalScrollbarMouseUp (event) {
|
||||||
|
window.removeEventListener('mousemove', this.handleVerticalScrollbarMouseMove);
|
||||||
|
window.removeEventListener('mouseup', this.handleVerticalScrollbarMouseUp);
|
||||||
|
this.initialMouseY = null;
|
||||||
|
this.initialScreenY = null;
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
handleWheel (event) {
|
||||||
|
// Multiplier variable, so that non-pixel-deltaModes are supported. Needed for Firefox.
|
||||||
|
// See #529 (or LLK/scratch-blocks#1190).
|
||||||
|
const multiplier = event.deltaMode === 0x1 ? 15 : 1;
|
||||||
|
const deltaX = event.deltaX * multiplier;
|
||||||
|
const deltaY = event.deltaY * multiplier;
|
||||||
|
if (event.metaKey || event.ctrlKey) {
|
||||||
|
// Zoom keeping mouse location fixed
|
||||||
|
const canvasRect = this.props.canvas.getBoundingClientRect();
|
||||||
|
const offsetX = event.clientX - canvasRect.left;
|
||||||
|
const offsetY = event.clientY - canvasRect.top;
|
||||||
|
const fixedPoint = paper.view.viewToProject(
|
||||||
|
new paper.Point(offsetX, offsetY)
|
||||||
|
);
|
||||||
|
zoomOnFixedPoint(-deltaY / 1000, fixedPoint);
|
||||||
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
|
this.props.redrawSelectionBox(); // Selection handles need to be resized after zoom
|
||||||
|
} else if (event.shiftKey && event.deltaX === 0) {
|
||||||
|
// Scroll horizontally (based on vertical scroll delta)
|
||||||
|
// This is needed as for some browser/system combinations which do not set deltaX.
|
||||||
|
// See #156.
|
||||||
|
const dx = deltaY / paper.view.zoom;
|
||||||
|
pan(dx, 0);
|
||||||
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
|
} else {
|
||||||
|
const dx = deltaX / paper.view.zoom;
|
||||||
|
const dy = deltaY / paper.view.zoom;
|
||||||
|
pan(dx, dy);
|
||||||
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
let widthPercent = 0;
|
||||||
|
let heightPercent = 0;
|
||||||
|
let topPercent = 0;
|
||||||
|
let leftPercent = 0;
|
||||||
|
if (paper.project) {
|
||||||
|
const {x, y, width, height} = paper.view.bounds;
|
||||||
|
widthPercent = Math.min(100, 100 * width / ART_BOARD_WIDTH);
|
||||||
|
heightPercent = Math.min(100, 100 * height / ART_BOARD_HEIGHT);
|
||||||
|
const centerX = (x + (width / 2)) / ART_BOARD_WIDTH;
|
||||||
|
const centerY = (y + (height / 2)) / ART_BOARD_HEIGHT;
|
||||||
|
topPercent = Math.max(0, (100 * centerY) - (heightPercent / 2));
|
||||||
|
leftPercent = Math.max(0, (100 * centerX) - (widthPercent / 2));
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ScrollableCanvasComponent
|
||||||
|
hideCursor={this.props.hideCursor}
|
||||||
|
horizontalScrollLengthPercent={widthPercent}
|
||||||
|
horizontalScrollStartPercent={leftPercent}
|
||||||
|
style={this.props.style}
|
||||||
|
verticalScrollLengthPercent={heightPercent}
|
||||||
|
verticalScrollStartPercent={topPercent}
|
||||||
|
onHorizontalScrollbarMouseDown={this.handleHorizontalScrollbarMouseDown}
|
||||||
|
onVerticalScrollbarMouseDown={this.handleVerticalScrollbarMouseDown}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</ScrollableCanvasComponent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollableCanvas.propTypes = {
|
||||||
|
canvas: PropTypes.instanceOf(Element),
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
hideCursor: PropTypes.bool,
|
||||||
|
redrawSelectionBox: PropTypes.func.isRequired,
|
||||||
|
style: PropTypes.string,
|
||||||
|
updateViewBounds: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
viewBounds: state.scratchPaint.viewBounds
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
redrawSelectionBox: () => {
|
||||||
|
dispatch(redrawSelectionBox());
|
||||||
|
},
|
||||||
|
updateViewBounds: matrix => {
|
||||||
|
dispatch(updateViewBounds(matrix));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ScrollableCanvas);
|
|
@ -9,7 +9,7 @@ const SVG_ART_BOARD_HEIGHT = 360;
|
||||||
const ART_BOARD_WIDTH = 480 * 2;
|
const ART_BOARD_WIDTH = 480 * 2;
|
||||||
const ART_BOARD_HEIGHT = 360 * 2;
|
const ART_BOARD_HEIGHT = 360 * 2;
|
||||||
|
|
||||||
const _clampViewBounds = () => {
|
const clampViewBounds = () => {
|
||||||
const {left, right, top, bottom} = paper.project.view.bounds;
|
const {left, right, top, bottom} = paper.project.view.bounds;
|
||||||
if (left < 0) {
|
if (left < 0) {
|
||||||
paper.project.view.scrollBy(new paper.Point(-left, 0));
|
paper.project.view.scrollBy(new paper.Point(-left, 0));
|
||||||
|
@ -37,7 +37,7 @@ const zoomOnFixedPoint = (deltaZoom, fixedPoint) => {
|
||||||
.subtract(preZoomCenter);
|
.subtract(preZoomCenter);
|
||||||
view.zoom = newZoom;
|
view.zoom = newZoom;
|
||||||
view.translate(postZoomOffset.multiply(-1));
|
view.translate(postZoomOffset.multiply(-1));
|
||||||
_clampViewBounds();
|
clampViewBounds();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zoom keeping the selection center (if any) fixed.
|
// Zoom keeping the selection center (if any) fixed.
|
||||||
|
@ -62,12 +62,12 @@ const zoomOnSelection = deltaZoom => {
|
||||||
|
|
||||||
const resetZoom = () => {
|
const resetZoom = () => {
|
||||||
paper.project.view.zoom = .5;
|
paper.project.view.zoom = .5;
|
||||||
_clampViewBounds();
|
clampViewBounds();
|
||||||
};
|
};
|
||||||
|
|
||||||
const pan = (dx, dy) => {
|
const pan = (dx, dy) => {
|
||||||
paper.project.view.scrollBy(new paper.Point(dx, dy));
|
paper.project.view.scrollBy(new paper.Point(dx, dy));
|
||||||
_clampViewBounds();
|
clampViewBounds();
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -75,6 +75,7 @@ export {
|
||||||
ART_BOARD_WIDTH,
|
ART_BOARD_WIDTH,
|
||||||
SVG_ART_BOARD_WIDTH,
|
SVG_ART_BOARD_WIDTH,
|
||||||
SVG_ART_BOARD_HEIGHT,
|
SVG_ART_BOARD_HEIGHT,
|
||||||
|
clampViewBounds,
|
||||||
pan,
|
pan,
|
||||||
resetZoom,
|
resetZoom,
|
||||||
zoomOnSelection,
|
zoomOnSelection,
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import log from '../log/log';
|
import log from '../log/log';
|
||||||
const CHANGE_SELECTED_ITEMS = 'scratch-paint/select/CHANGE_SELECTED_ITEMS';
|
const CHANGE_SELECTED_ITEMS = 'scratch-paint/select/CHANGE_SELECTED_ITEMS';
|
||||||
|
const REDRAW_SELECTION_BOX = 'scratch-paint/select/REDRAW_SELECTION_BOX';
|
||||||
const initialState = [];
|
const initialState = [];
|
||||||
|
|
||||||
const reducer = function (state, action) {
|
const reducer = function (state, action) {
|
||||||
if (typeof state === 'undefined') state = initialState;
|
if (typeof state === 'undefined') state = initialState;
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case REDRAW_SELECTION_BOX:
|
||||||
|
if (state.length > 0) return state.slice(0); // Sends an update even though the items haven't changed
|
||||||
|
return state;
|
||||||
case CHANGE_SELECTED_ITEMS:
|
case CHANGE_SELECTED_ITEMS:
|
||||||
if (!action.selectedItems || !(action.selectedItems instanceof Array)) {
|
if (!action.selectedItems || !(action.selectedItems instanceof Array)) {
|
||||||
log.warn(`No selected items or wrong format provided: ${action.selectedItems}`);
|
log.warn(`No selected items or wrong format provided: ${action.selectedItems}`);
|
||||||
|
@ -44,9 +48,15 @@ const clearSelectedItems = function () {
|
||||||
selectedItems: []
|
selectedItems: []
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
const redrawSelectionBox = function () {
|
||||||
|
return {
|
||||||
|
type: REDRAW_SELECTION_BOX
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
reducer as default,
|
reducer as default,
|
||||||
|
redrawSelectionBox,
|
||||||
setSelectedItems,
|
setSelectedItems,
|
||||||
clearSelectedItems,
|
clearSelectedItems,
|
||||||
CHANGE_SELECTED_ITEMS
|
CHANGE_SELECTED_ITEMS
|
||||||
|
|
Loading…
Reference in a new issue