Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
20 changes: 20 additions & 0 deletions packages/gui/src/components/settings-modal/settings-modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,24 @@ class SettingsModal extends React.Component {
onChange={this.props.onChangeAccurateCoordinates}
/>
</div>
<div className={styles.item}>
<div className={styles.label}>
<FormattedMessage
defaultMessage="High-Quality Pen"
description="Label of high-quality pen"
id="gui.settingsModal.highQualityPen"
/>
<FormattedMessage
defaultMessage="Make pen's canvas always follow actual canvas size."
description="Description of high-quality pen"
id="gui.settingsModal.highQualityPenDescription"
/>
</div>
<Switch
value={this.props.highQualityPen}
onChange={this.props.onChangeHighQualityPen}
/>
</div>
<div className={styles.item}>
<div className={styles.label}>
<FormattedMessage
Expand Down Expand Up @@ -405,6 +423,7 @@ SettingsModal.propTypes = {
autoSave: PropTypes.bool.isRequired,
infiniteCloning: PropTypes.bool.isRequired,
edgelessStage: PropTypes.bool.isRequired,
highQualityPen: PropTypes.bool.isRequired,
unlimitedListLength: PropTypes.bool.isRequired,
unlimitedPenSize: PropTypes.bool.isRequired,
unlimitedSoundStuffs: PropTypes.bool.isRequired,
Expand All @@ -421,6 +440,7 @@ SettingsModal.propTypes = {
onChangeEdgelessStage: PropTypes.func.isRequired,
onChangeStageWidth: PropTypes.func.isRequired,
onChangeStageHeight: PropTypes.func.isRequired,
onChangeHighQualityPen: PropTypes.func.isRequired,
onChangeUnlimitedListLength: PropTypes.func.isRequired,
onChangeUnlimitedPenSize: PropTypes.func.isRequired,
onChangeUnlimitedSoundStuffs: PropTypes.func.isRequired,
Expand Down
8 changes: 8 additions & 0 deletions packages/gui/src/containers/settings-modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class SettingsModal extends React.Component {
'handleChangeAutoSaveInterval',
'handleChangeFramerate',
'handleChangeTheme',
'handleChangeHighQualityPen',
'handleChangeInfiniteCloning',
'handleChangeEdgelessStage',
'handleChangeUnlimitedListLength',
Expand Down Expand Up @@ -74,6 +75,9 @@ class SettingsModal extends React.Component {
handleChangeHideNonVanillaBlocks (value) {
this.props.updateSettings({hideNonVanillaBlocks: value});
}
handleChangeHighQualityPen (value) {
this.props.updateSettings({highQualityPen: value});
}
handleChangeStageWidth (width) {
if (width >= 480) {
this.props.updateSettings({stageWidth: Math.round(width)});
Expand All @@ -90,6 +94,7 @@ class SettingsModal extends React.Component {
autoSaveInterval={this.props.autoSaveInterval}
framerate={this.props.framerate}
theme={this.props.theme}
highQualityPen={this.props.highQualityPen}
infiniteCloning={this.props.infiniteCloning}
edgelessStage={this.props.edgelessStage}
unlimitedListLength={this.props.unlimitedListLength}
Expand All @@ -105,6 +110,7 @@ class SettingsModal extends React.Component {
onChangeTheme={this.handleChangeTheme}
onChangeInfiniteCloning={this.handleChangeInfiniteCloning}
onChangeEdgelessStage={this.handleChangeEdgelessStage}
onChangeHighQualityPen={this.handleChangeHighQualityPen}
onChangeUnlimitedListLength={this.handleChangeUnlimitedListLength}
onChangeUnlimitedPenSize={this.handleChangeUnlimitedPenSize}
onChangeUnlimitedSoundStuffs={this.handleChangeUnlimitedSoundStuffs}
Expand All @@ -120,6 +126,7 @@ class SettingsModal extends React.Component {
SettingsModal.propTypes = {
hideNonVanillaBlocks: PropTypes.bool.isRequired,
autoSave: PropTypes.bool.isRequired,
highQualityPen: PropTypes.bool.isRequired,
infiniteCloning: PropTypes.bool.isRequired,
edgelessStage: PropTypes.bool.isRequired,
unlimitedListLength: PropTypes.bool.isRequired,
Expand All @@ -140,6 +147,7 @@ const mapStateToProps = state => ({
autoSave: state.scratchGui.settings.autoSave,
infiniteCloning: state.scratchGui.settings.infiniteCloning,
edgelessStage: state.scratchGui.settings.edgelessStage,
highQualityPen: state.scratchGui.settings.highQualityPen,
unlimitedListLength: state.scratchGui.settings.unlimitedListLength,
unlimitedPenSize: state.scratchGui.settings.unlimitedPenSize,
unlimitedSoundStuffs: state.scratchGui.settings.unlimitedSoundStuffs,
Expand Down
6 changes: 6 additions & 0 deletions packages/gui/src/lib/vm-manager-hoc.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const vmManagerHOC = function (WrappedComponent) {
});
this.props.vm.setStageWidth(this.props.stageWidth);
this.props.vm.setStageHeight(this.props.stageHeight);
this.props.vm.renderer.setHighQualityPen(this.props.highQualityPen);
}
if (!this.props.isPlayerOnly && !this.props.isStarted) {
this.props.vm.start();
Expand Down Expand Up @@ -96,6 +97,9 @@ const vmManagerHOC = function (WrappedComponent) {
accurateCoordinates: this.props.accurateCoordinates
});
}
if (this.props.highQualityPen !== prevProps.highQualityPen) {
this.props.vm.renderer.setHighQualityPen(this.props.highQualityPen);
}
if (this.props.stageWidth !== prevProps.stageWidth) {
this.props.vm.setStageWidth(this.props.stageWidth);
}
Expand Down Expand Up @@ -177,6 +181,7 @@ const vmManagerHOC = function (WrappedComponent) {
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
username: PropTypes.string,
framerate: PropTypes.number.isRequired,
highQualityPen: PropTypes.bool.isRequired,
infiniteCloning: PropTypes.bool.isRequired,
edgelessStage: PropTypes.bool.isRequired,
unlimitedListLength: PropTypes.bool.isRequired,
Expand All @@ -203,6 +208,7 @@ const vmManagerHOC = function (WrappedComponent) {
framerate: state.scratchGui.settings.framerate,
infiniteCloning: state.scratchGui.settings.infiniteCloning,
edgelessStage: state.scratchGui.settings.edgelessStage,
highQualityPen: state.scratchGui.settings.highQualityPen,
unlimitedListLength: state.scratchGui.settings.unlimitedListLength,
unlimitedPenSize: state.scratchGui.settings.unlimitedPenSize,
unlimitedSoundStuffs: state.scratchGui.settings.unlimitedSoundStuffs,
Expand Down
3 changes: 3 additions & 0 deletions packages/gui/src/reducers/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type SettingsState = {
autoSave: boolean;
infiniteCloning: boolean;
edgelessStage: boolean;
highQualityPen: boolean,
unlimitedListLength: boolean;
unlimitedPenSize: boolean;
unlimitedSoundStuffs: boolean;
Expand All @@ -25,6 +26,7 @@ const defaultState: SettingsState = {
autoSave: false,
infiniteCloning: false,
edgelessStage: false,
highQualityPen: false,
unlimitedListLength: false,
unlimitedPenSize: false,
unlimitedSoundStuffs: false,
Expand Down Expand Up @@ -55,6 +57,7 @@ const parseSavedSettings = (): Partial<SettingsState> => {
autoSave: typeof parsed.autoSave === 'boolean' ? parsed.autoSave : undefined,
infiniteCloning: typeof parsed.infiniteCloning === 'boolean' ? parsed.infiniteCloning : undefined,
edgelessStage: typeof parsed.edgelessStage === 'boolean' ? parsed.edgelessStage : undefined,
highQualityPen: typeof parsed.highQualityPen === 'boolean' ? parsed.highQualityPen : undefined,
unlimitedListLength: typeof parsed.unlimitedListLength === 'boolean' ? parsed.unlimitedListLength : undefined,
unlimitedPenSize: typeof parsed.unlimitedPenSize === 'boolean' ? parsed.unlimitedPenSize : undefined,
unlimitedSoundStuffs:
Expand Down
6 changes: 6 additions & 0 deletions packages/gui/test/unit/util/vm-manager-hoc.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ describe('VMManagerHOC', () => {
vm.setCompatibilityMode = jest.fn();
vm.setLocale = jest.fn();
vm.start = jest.fn();
vm.runtime.renderer = {
setHighQualityPen: jest.fn(),
setEdgelessStage: jest.fn(),
setAccurateCoordinates: jest.fn(),
setStageSize: jest.fn()
};
});
test('when it mounts in player mode, the vm is initialized but not started', () => {
const Component = () => (<div />);
Expand Down
122 changes: 110 additions & 12 deletions packages/render/src/PenSkin.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ class PenSkin extends Skin {
this.onNativeSizeChanged = this.onNativeSizeChanged.bind(this);
this._renderer.on(RenderConstants.Events.NativeSizeChanged, this.onNativeSizeChanged);

this.onCanvasSizeChanged = this.onCanvasSizeChanged.bind(this);
this._renderer.on(RenderConstants.Events.CanvasSizeChanged, this.onCanvasSizeChanged);

this.onUseHighQualityPenChanged = this.onUseHighQualityPenChanged.bind(this);
this._renderer.on(RenderConstants.Events.UseHighQualityPenChanged, this.onUseHighQualityPenChanged);

if (this._renderer.useHighQualityPen) {
this._canvasSize = [this._renderer.gl.canvas.width, this._renderer.gl.canvas.height];
} else {
this._canvasSize = [...renderer.getNativeSize()];
}

this._setCanvasSize(renderer.getNativeSize());
}

Expand All @@ -109,6 +121,8 @@ class PenSkin extends Skin {
*/
dispose () {
this._renderer.removeListener(RenderConstants.Events.NativeSizeChanged, this.onNativeSizeChanged);
this._renderer.removeListener(RenderConstants.Events.CanvasSizeChanged, this.onCanvasSizeChanged);
this._renderer.removeListener(RenderConstants.Events.UseHighQualityPenChanged, this.onUseHighQualityPenChanged);
this._renderer.gl.deleteTexture(this._texture);
this._texture = null;
super.dispose();
Expand Down Expand Up @@ -191,15 +205,15 @@ class PenSkin extends Skin {

twgl.bindFramebufferInfo(gl, this._framebuffer);

gl.viewport(0, 0, this._size[0], this._size[1]);
gl.viewport(0, 0, this._canvasSize[0], this._canvasSize[1]);

const currentShader = this._lineShader;
gl.useProgram(currentShader.program);
twgl.setBuffersAndAttributes(gl, currentShader, this._lineBufferInfo);

const uniforms = {
u_skin: this._texture,
u_stageSize: this._size
u_stageSize: this._canvasSize
};

twgl.setUniforms(currentShader, uniforms);
Expand Down Expand Up @@ -257,15 +271,31 @@ class PenSkin extends Skin {
// can overflow that, because you're squaring the operands, and they could end up as "infinity".
// Even GLSL's `length` function won't save us here:
// https://asawicki.info/news_1596_watch_out_for_reduced_precision_normalizelength_in_opengl_es
const lineDiffX = x1 - x0;
const lineDiffY = y1 - y0;

const scaleX = this._canvasSize[0] / this._size[0];
const scaleY = this._canvasSize[1] / this._size[1];

const diameter = penAttributes.diameter || DefaultPenAttributes.diameter;
const thickness = diameter * Math.min(scaleX, scaleY);

// Adjust for odd-width lines to align with physical pixel centers
const offset = (Math.round(thickness) % 2 === 0) ? 0 : 0.5;

// Scale the points and thickness to the physical canvas size
const scaledX0 = (x0 * scaleX) + offset;
const scaledY0 = (y0 * scaleY) + offset;
const scaledX1 = (x1 * scaleX) + offset;
const scaledY1 = (y1 * scaleY) + offset;

const lineDiffX = scaledX1 - scaledX0;
const lineDiffY = scaledY1 - scaledY0;
const lineLength = Math.sqrt((lineDiffX * lineDiffX) + (lineDiffY * lineDiffY));

const uniforms = {
u_lineColor: __premultipliedColor,
u_lineThickness: penAttributes.diameter || DefaultPenAttributes.diameter,
u_lineThickness: thickness,
u_lineLength: lineLength,
u_penPoints: [x0, -y0, lineDiffX, -lineDiffY]
u_penPoints: [scaledX0, -scaledY0, lineDiffX, -lineDiffY]
};

twgl.setUniforms(currentShader, uniforms);
Expand All @@ -283,6 +313,38 @@ class PenSkin extends Skin {
this._setCanvasSize(event.newSize);
}

/**
* React to a change in the renderer's canvas size.
* @param {object} event - The change event.
*/
onCanvasSizeChanged (event) {
if (!this._renderer.useHighQualityPen) return;

if (this._canvasSize[0] === event.newSize[0] && this._canvasSize[1] === event.newSize[1]) {
return;
}
this._canvasSize = event.newSize;
this._resetBufferSize();
}

/**
* React to a change in the high quality pen mode.
* @param {boolean} enabled - Whether high quality pen is enabled.
*/
onUseHighQualityPenChanged (enabled) {
let newSize;
if (enabled) {
newSize = [this._renderer.gl.canvas.width, this._renderer.gl.canvas.height];
} else {
newSize = [...this._size];
}

if (this._canvasSize[0] !== newSize[0] || this._canvasSize[1] !== newSize[1]) {
this._canvasSize = newSize;
this._resetBufferSize();
}
}

/**
* Set the size of the pen canvas.
* @param {Array<int>} canvasSize - the new width and height for the canvas.
Expand All @@ -295,8 +357,24 @@ class PenSkin extends Skin {
this._rotationCenter[0] = width / 2;
this._rotationCenter[1] = height / 2;

if (!this._renderer.useHighQualityPen) {
this._canvasSize = [...canvasSize];
}

this._resetBufferSize();
}

/**
* Reset the buffer size.
* @private
*/
_resetBufferSize () {
const [width, height] = this._canvasSize;

const gl = this._renderer.gl;

const oldTexture = this._texture;

this._texture = twgl.createTexture(
gl,
{
Expand All @@ -314,15 +392,35 @@ class PenSkin extends Skin {
attachment: this._texture
}
];
if (this._framebuffer) {
twgl.resizeFramebufferInfo(gl, this._framebuffer, attachments, width, height);
} else {
this._framebuffer = twgl.createFramebufferInfo(gl, attachments, width, height);
}
this._framebuffer = twgl.createFramebufferInfo(gl, attachments, width, height);

gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);

if (oldTexture) {
// Draw old canvas to new framebuffer
this._renderer.enterDrawRegion(this._usePenBufferDrawRegionId);
gl.viewport(0, 0, width, height);

const currentShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.default);
gl.useProgram(currentShader.program);
twgl.setBuffersAndAttributes(gl, currentShader, this._renderer._bufferInfo);

const uniforms = {
u_skin: oldTexture,
u_projectionMatrix: twgl.m4.ortho(width / 2, width / -2, height / -2, height / 2, -1, 1),
u_modelMatrix: twgl.m4.scaling(twgl.v3.create(width, height, 0), twgl.m4.identity())
};

twgl.setTextureParameters(gl, oldTexture, {
minMag: gl.NEAREST
});
twgl.setUniforms(currentShader, uniforms);
twgl.drawBufferInfo(gl, this._renderer._bufferInfo, gl.TRIANGLES);

gl.deleteTexture(oldTexture);
}

this._silhouettePixels = new Uint8Array(Math.floor(width * height * 4));
this._silhouetteImageData = new ImageData(width, height);

Expand All @@ -340,7 +438,7 @@ class PenSkin extends Skin {
const gl = this._renderer.gl;
gl.readPixels(
0, 0,
this._size[0], this._size[1],
this._canvasSize[0], this._canvasSize[1],
gl.RGBA, gl.UNSIGNED_BYTE, this._silhouettePixels
);

Expand Down
15 changes: 13 additions & 2 deletions packages/render/src/RenderConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,19 @@ module.exports = {
*/
Events: {
/**
* NativeSizeChanged event
* NativeSizeChanged event, which related to stage size change.
* @constant {string}
*/
NativeSizeChanged: 'NativeSizeChanged'
NativeSizeChanged: 'NativeSizeChanged',
/**
* CanvasSizeChanged event, which related to actual canvas size change.
* @constant {string}
*/
CanvasSizeChanged: 'CanvasSizeChanged',
/**
* UseHighQualityPenChanged event, which related to high quality pen use change.
* @constant {string}
*/
UseHighQualityPenChanged: 'UseHighQualityPenChanged'
}
};
Loading
Loading