mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-23 05:52:42 -05:00
Rtl text tool (#651)
This commit is contained in:
parent
3701f99d93
commit
6f5c47686d
3 changed files with 69 additions and 32 deletions
|
@ -34,20 +34,25 @@ class TextMode extends React.Component {
|
|||
}
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.tool && nextProps.colorState !== this.props.colorState) {
|
||||
this.tool.setColorState(nextProps.colorState);
|
||||
}
|
||||
if (this.tool && nextProps.selectedItems !== this.props.selectedItems) {
|
||||
this.tool.onSelectionChanged(nextProps.selectedItems);
|
||||
}
|
||||
if (this.tool && !nextProps.textEditTarget && this.props.textEditTarget) {
|
||||
this.tool.onTextEditCancelled();
|
||||
}
|
||||
if (this.tool && !nextProps.viewBounds.equals(this.props.viewBounds)) {
|
||||
this.tool.onViewBoundsChanged(nextProps.viewBounds);
|
||||
}
|
||||
if (this.tool && nextProps.font !== this.props.font) {
|
||||
this.tool.setFont(nextProps.font);
|
||||
if (this.tool) {
|
||||
if (nextProps.colorState !== this.props.colorState) {
|
||||
this.tool.setColorState(nextProps.colorState);
|
||||
}
|
||||
if (nextProps.selectedItems !== this.props.selectedItems) {
|
||||
this.tool.onSelectionChanged(nextProps.selectedItems);
|
||||
}
|
||||
if (!nextProps.textEditTarget && this.props.textEditTarget) {
|
||||
this.tool.onTextEditCancelled();
|
||||
}
|
||||
if (!nextProps.viewBounds.equals(this.props.viewBounds)) {
|
||||
this.tool.onViewBoundsChanged(nextProps.viewBounds);
|
||||
}
|
||||
if (nextProps.font !== this.props.font) {
|
||||
this.tool.setFont(nextProps.font);
|
||||
}
|
||||
if (nextProps.rtl !== this.props.rtl) {
|
||||
this.tool.setRtl(nextProps.rtl);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextProps.isTextModeActive && !this.props.isTextModeActive) {
|
||||
|
@ -97,6 +102,7 @@ class TextMode extends React.Component {
|
|||
this.props.changeFont,
|
||||
nextProps.isBitmap
|
||||
);
|
||||
this.tool.setRtl(this.props.rtl);
|
||||
this.tool.setColorState(nextProps.colorState);
|
||||
this.tool.setFont(nextProps.font);
|
||||
this.tool.activate();
|
||||
|
@ -142,6 +148,7 @@ TextMode.propTypes = {
|
|||
onChangeFillColor: PropTypes.func.isRequired,
|
||||
onChangeStrokeColor: PropTypes.func.isRequired,
|
||||
onUpdateImage: PropTypes.func.isRequired,
|
||||
rtl: PropTypes.bool,
|
||||
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item)),
|
||||
setSelectedItems: PropTypes.func.isRequired,
|
||||
setTextEditTarget: PropTypes.func.isRequired,
|
||||
|
@ -156,6 +163,7 @@ const mapStateToProps = (state, ownProps) => ({
|
|||
isTextModeActive: ownProps.isBitmap ?
|
||||
state.scratchPaint.mode === Modes.BIT_TEXT :
|
||||
state.scratchPaint.mode === Modes.TEXT,
|
||||
rtl: state.scratchPaint.layout.rtl,
|
||||
selectedItems: state.scratchPaint.selectedItems,
|
||||
textEditTarget: state.scratchPaint.textEditTarget,
|
||||
viewBounds: state.scratchPaint.viewBounds
|
||||
|
|
|
@ -143,17 +143,37 @@ class TextTool extends paper.Tool {
|
|||
if (this.mode !== TextTool.TEXT_EDIT_MODE) {
|
||||
return;
|
||||
}
|
||||
const matrix = this.textBox.matrix;
|
||||
this.element.style.transform =
|
||||
`translate(0px, ${this.textBox.internalBounds.y}px)
|
||||
matrix(${viewMtx.a}, ${viewMtx.b}, ${viewMtx.c}, ${viewMtx.d},
|
||||
${viewMtx.tx}, ${viewMtx.ty})
|
||||
matrix(${matrix.a}, ${matrix.b}, ${matrix.c}, ${matrix.d},
|
||||
${matrix.tx}, ${matrix.ty})`;
|
||||
this.calculateMatrix(viewMtx);
|
||||
}
|
||||
calculateMatrix (viewMtx) {
|
||||
const textBoxMtx = this.textBox.matrix;
|
||||
const calculated = new paper.Matrix();
|
||||
|
||||
// In RTL, the element is moved relative to its parent's right edge instead of its left
|
||||
// edge. We need to correct for this in order for the element to overlap the object in paper.
|
||||
let tx = 0;
|
||||
if (this.rtl && this.element.parentElement) {
|
||||
tx = -this.element.parentElement.clientWidth;
|
||||
}
|
||||
// The transform origin in paper is x at justification side, y at the baseline of the text.
|
||||
// The offset from (0, 0) to the upper left corner is recorded by internalBounds
|
||||
// (so this.textBox.internalBounds.y is negative).
|
||||
// Move the transform origin down to the text baseline to match paper
|
||||
this.element.style.transformOrigin = `${-this.textBox.internalBounds.x}px ${-this.textBox.internalBounds.y}px`;
|
||||
// Start by translating the element up so that its (0, 0) is now at the text baseline, like in paper
|
||||
calculated.translate(tx, this.textBox.internalBounds.y);
|
||||
calculated.append(viewMtx);
|
||||
calculated.append(textBoxMtx);
|
||||
this.element.style.transform = `matrix(${calculated.a}, ${calculated.b}, ${calculated.c}, ${calculated.d},
|
||||
${calculated.tx}, ${calculated.ty})`;
|
||||
}
|
||||
setColorState (colorState) {
|
||||
this.colorState = colorState;
|
||||
}
|
||||
/** @param {boolean} isRtl True if paint editor is in right-to-left layout (e.g. Hebrew language) */
|
||||
setRtl (isRtl) {
|
||||
this.rtl = isRtl;
|
||||
}
|
||||
handleMouseMove (event) {
|
||||
const hitResults = paper.project.hitTestAll(event.point, this.getTextEditHitOptions());
|
||||
if (hitResults.length) {
|
||||
|
@ -283,6 +303,12 @@ class TextTool extends paper.Tool {
|
|||
// Prevent line from wrapping
|
||||
this.element.style.width = `${this.textBox.internalBounds.width + 1}px`;
|
||||
this.element.style.height = `${this.textBox.internalBounds.height}px`;
|
||||
// The transform origin needs to be updated in RTL because this.textBox.internalBounds.x
|
||||
// changes as you type
|
||||
if (this.rtl) {
|
||||
this.element.style.transformOrigin =
|
||||
`${-this.textBox.internalBounds.x}px ${-this.textBox.internalBounds.y}px`;
|
||||
}
|
||||
}
|
||||
beginSelect () {
|
||||
if (this.textBox) {
|
||||
|
@ -308,18 +334,17 @@ class TextTool extends paper.Tool {
|
|||
this.element.style.fontSize = `${this.textBox.fontSize}px`;
|
||||
this.element.style.lineHeight = this.textBox.leading / this.textBox.fontSize;
|
||||
|
||||
const viewMtx = paper.view.matrix;
|
||||
|
||||
this.element.style.display = 'initial';
|
||||
this.element.value = textBox.content ? textBox.content : '';
|
||||
this.element.style.transformOrigin =
|
||||
`${-this.textBox.internalBounds.x}px ${-this.textBox.internalBounds.y}px`;
|
||||
this.element.style.transform =
|
||||
`translate(0px, ${this.textBox.internalBounds.y}px)
|
||||
matrix(${viewMtx.a}, ${viewMtx.b}, ${viewMtx.c}, ${viewMtx.d},
|
||||
${viewMtx.tx}, ${viewMtx.ty})
|
||||
matrix(${textBox.matrix.a}, ${textBox.matrix.b}, ${textBox.matrix.c}, ${textBox.matrix.d},
|
||||
${textBox.matrix.tx}, ${textBox.matrix.ty})`;
|
||||
this.calculateMatrix(paper.view.matrix);
|
||||
|
||||
if (this.rtl) {
|
||||
// make both the textbox and the textarea element grow to the left
|
||||
this.textBox.justification = 'right';
|
||||
} else {
|
||||
this.textBox.justification = 'left';
|
||||
}
|
||||
|
||||
this.element.focus({preventScroll: true});
|
||||
this.eventListener = this.handleTextInput.bind(this);
|
||||
this.element.addEventListener('input', this.eventListener);
|
||||
|
|
|
@ -30,12 +30,16 @@ class Playground extends React.Component {
|
|||
'handleUpdateName',
|
||||
'handleUpdateImage'
|
||||
]);
|
||||
// Append ?dir=rtl to URL to get RTL layout
|
||||
const match = location.search.match(/dir=([^&]+)/);
|
||||
const rtl = match && match[1] == 'rtl';
|
||||
this.state = {
|
||||
name: 'meow',
|
||||
rotationCenterX: 20,
|
||||
rotationCenterY: 400,
|
||||
imageFormat: 'svg', // 'svg', 'png', or 'jpg'
|
||||
image: svgString // svg string or data URI
|
||||
image: svgString, // svg string or data URI
|
||||
rtl: rtl
|
||||
};
|
||||
}
|
||||
handleUpdateName (name) {
|
||||
|
|
Loading…
Reference in a new issue