Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resizable stage #9753

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions src/components/box/resizeableBox.jsx
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new "Box" wrapper which gives a drag-resizable handle on the left side of the container

Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import PropTypes from 'prop-types';
import React, {useEffect, useRef, useState, useCallback} from 'react';

import Box from './box.jsx';

const useMouseDrag = (ref, onDrag, onDragEnd) => {
useEffect(() => {
if (!onDrag) return;
let isDragging = false;
const onMouseMove = e => {
e.preventDefault();
if (!isDragging) return;
if (onDrag) onDrag(e);
};
const onMouseUp = e => {
isDragging = false;
if (onDragEnd) onDragEnd(e);
};
const onMouseDown = e => {
e.preventDefault();
isDragging = true;
};
ref.current.addEventListener('mousedown', onMouseDown);
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
return () => {
ref.current.removeEventListener('mousedown', onMouseDown);
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};
}, [onDrag]);
};


const ResizableBox = props => {
const {
children,
minWidth = 420,
maxWidth = 480,
defaultWidth = 480,
onResize,
initialSize,
...rest
} = props;

const boxRef = useRef(null);
const handleRef = useRef(null);
const [width, setWidth] = useState(defaultWidth);

// whenever the initial size changes, lets override whatever the current width is
useEffect(() => {
setWidth(initialSize);
}, [initialSize]);

// This is a workaround to force other elements to resize that
// are listening to the window resize event (such as the editor).
useEffect(() => {
window.dispatchEvent(new Event('resize'));
}, [width]);

const onDrag = useCallback(e => {
if (!boxRef.current) return;
const rect = boxRef.current.getBoundingClientRect();
const newWidth = Math.max(Math.min(rect.width - e.movementX, maxWidth), minWidth);
onResize(newWidth);
setWidth(newWidth);
}, [minWidth, maxWidth]);


useMouseDrag(handleRef, onDrag);

return (
<Box
componentRef={boxRef}
style={{
position: 'relative',
width: width,
minWidth: minWidth
}}
{...rest}
>
<div
ref={handleRef}
style={{
position: 'absolute',
left: 0,
top: 0,
bottom: 0,
width: '8px',
cursor: 'ew-resize'
}}
/>
{children}
</Box>
);
};

ResizableBox.propTypes = {
children: PropTypes.node,
minWidth: PropTypes.number,
maxWidth: PropTypes.number,
defaultWidth: PropTypes.number,
onResize: PropTypes.func,
initialSize: PropTypes.number
};

export default ResizableBox;
3 changes: 0 additions & 3 deletions src/components/gui/gui.css
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,6 @@
/* pad entire wrapper to the left and right; allow children to fill width */
padding-left: $space;
padding-right: $space;

/* this will only ever be as wide as the stage */
flex-basis: 0;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needed to get rid of this to have dynamic sizing

}

.target-wrapper {
Expand Down
49 changes: 44 additions & 5 deletions src/components/gui/gui.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import classNames from 'classnames';
import omit from 'lodash.omit';
import PropTypes from 'prop-types';
import React from 'react';
import React, {useState, useEffect, useRef, useCallback} from 'react';
import {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl';
import {connect} from 'react-redux';
import MediaQuery from 'react-responsive';
Expand All @@ -17,6 +17,7 @@ import SoundTab from '../../containers/sound-tab.jsx';
import StageWrapper from '../../containers/stage-wrapper.jsx';
import Loader from '../loader/loader.jsx';
import Box from '../box/box.jsx';
import ResizableBox from '../box/resizeableBox.jsx';
import MenuBar from '../menu-bar/menu-bar.jsx';
import CostumeLibrary from '../../containers/costume-library.jsx';
import BackdropLibrary from '../../containers/backdrop-library.jsx';
Expand All @@ -41,6 +42,8 @@ import codeIcon from './icon--code.svg';
import costumesIcon from './icon--costumes.svg';
import soundsIcon from './icon--sounds.svg';

import {setStageSize} from '../../reducers/stage-size';

const messages = defineMessages({
addExtension: {
id: 'gui.gui.addExtension',
Expand Down Expand Up @@ -130,6 +133,28 @@ const GUIComponent = props => {
return <Box {...componentProps}>{children}</Box>;
}

// This is the width of the stage as set by the user. used as the "large" stage size
const [stageUserWidth, setStageUserWidth] = useState(480);

// callback to set the stage size mode based on the user's defined width
const {onSetStageSmall, onSetStageLarge} = props;
const onResize = useCallback(size => {
const stageSize = resolveStageSize(stageSizeMode, false);
if (size < 480 && stageSize !== STAGE_SIZE_MODES.small) {
onSetStageSmall();
}
else if (stageSize !== STAGE_SIZE_MODES.large) {
onSetStageLarge();
}
setStageUserWidth(size);
}, [onSetStageSmall, onSetStageLarge, stageSizeMode]);


const getStageSize = () => {
const size = stageSizeMode === STAGE_SIZE_MODES.large ? Math.max(stageUserWidth, 480 + 16 + 2) : 240 + 16 + 2;
return size;
};

const tabClassNames = {
tabs: styles.tabs,
tab: classNames(tabStyles.reactTabsTab, styles.tab),
Expand Down Expand Up @@ -348,7 +373,13 @@ const GUIComponent = props => {
) : null}
</Box>

<Box className={classNames(styles.stageAndTargetWrapper, styles[stageSize])}>
<ResizableBox
className={classNames(styles.stageAndTargetWrapper, styles[stageSize])}
onResize={onResize}
minWidth={240 + 16 + 2}
maxWidth={(480 * 4) + 16 + 2}
initialSize={getStageSize()}
>
<StageWrapper
isFullScreen={isFullScreen}
isRendererSupported={isRendererSupported}
Expand All @@ -362,7 +393,7 @@ const GUIComponent = props => {
vm={vm}
/>
</Box>
</Box>
</ResizableBox>
</Box>
</Box>
<DragLayer />
Expand Down Expand Up @@ -436,7 +467,9 @@ GUIComponent.propTypes = {
telemetryModalVisible: PropTypes.bool,
theme: PropTypes.string,
tipsLibraryVisible: PropTypes.bool,
vm: PropTypes.instanceOf(VM).isRequired
vm: PropTypes.instanceOf(VM).isRequired,
onSetStageLarge: PropTypes.func.isRequired,
onSetStageSmall: PropTypes.func.isRequired,
};
GUIComponent.defaultProps = {
backpackHost: null,
Expand Down Expand Up @@ -469,6 +502,12 @@ const mapStateToProps = state => ({
theme: state.scratchGui.theme.theme
});

const mapDispatchToProps = dispatch => ({
onSetStageLarge: () => dispatch(setStageSize(STAGE_SIZE_MODES.large)),
onSetStageSmall: () => dispatch(setStageSize(STAGE_SIZE_MODES.small))
});

export default injectIntl(connect(
mapStateToProps
mapStateToProps,
mapDispatchToProps
)(GUIComponent));
25 changes: 22 additions & 3 deletions src/components/monitor-list/monitor-list.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,32 @@ import {stageSizeToTransform} from '../../lib/screen-utils';

import styles from './monitor-list.css';

// Use static `monitor-overlay` class for bounds of draggables
// This is just a dummy div which doesn't scale to make the
// bounds tracking play nicer with react-draggable
const MonitorOverlay = () => (
<div
className="monitor-overlay"
style={{
width: 480,
height: 360,
position: 'absolute',
top: 0,
left: 0,
visibility: 'hidden'
}}
/>
);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

react-draggable really did not like using a scaled container as the bounding reference, but we needed the scaling both visually as well as for proper drag deltas for the monitors. This dummy div of a constant width is a hacky solution that makes react-draggable happy. A better solution would likely be way more involved, perhaps replacing react-draggable entirely.

const MonitorList = props => (
<Box
// Use static `monitor-overlay` class for bounds of draggables
className={classNames(styles.monitorList, 'monitor-overlay')}
className={styles.monitorList}
style={{
width: props.stageSize.width,
height: props.stageSize.height
}}
>
<MonitorOverlay />
<Box
className={styles.monitorListScaler}
style={stageSizeToTransform(props.stageSize)}
Expand All @@ -41,6 +58,7 @@ const MonitorList = props => (
x={monitorData.x}
y={monitorData.y}
onDragEnd={props.onMonitorChange}
scale={props.stageSize.scale}
/>
))}
</Box>
Expand All @@ -55,7 +73,8 @@ MonitorList.propTypes = {
width: PropTypes.number,
height: PropTypes.number,
widthDefault: PropTypes.number,
heightDefault: PropTypes.number
heightDefault: PropTypes.number,
scale: PropTypes.number
}).isRequired
};

Expand Down
4 changes: 3 additions & 1 deletion src/components/monitor/monitor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const MonitorComponent = props => (
defaultClassNameDragging={styles.dragging}
disabled={!props.draggable}
onStop={props.onDragEnd}
scale={props.scale}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drilling this scale prop to the draggable element corrects the drag deltas for different stage sizes

>
<Box
className={styles.monitorContainer}
Expand Down Expand Up @@ -149,7 +150,8 @@ MonitorComponent.propTypes = {
onSetModeToLarge: PropTypes.func,
onSetModeToSlider: PropTypes.func,
onSliderPromptOpen: PropTypes.func,
theme: PropTypes.string.isRequired
theme: PropTypes.string.isRequired,
scale: PropTypes.number
};

MonitorComponent.defaultProps = {
Expand Down
5 changes: 5 additions & 0 deletions src/components/sprite-info/sprite-info.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
border-top-left-radius: $space;
border-top-right-radius: $space;
border-bottom: 1px solid $ui-black-transparent;
display: flex;
flex-direction: column;
align-items: center;
}

.row {
display: flex;
justify-content: space-between;
gap: 0.25rem;
width: 100%;
}

.row-primary {
Expand Down
Loading