diff --git a/Extensions/TextObject/TextObject.cpp b/Extensions/TextObject/TextObject.cpp index 278536ad92..63830cda40 100644 --- a/Extensions/TextObject/TextObject.cpp +++ b/Extensions/TextObject/TextObject.cpp @@ -29,7 +29,8 @@ TextObject::TextObject() underlined(false), colorR(0), colorG(0), - colorB(0) + colorB(0), + textAlignment("left") { } @@ -39,6 +40,7 @@ void TextObject::DoUnserializeFrom(gd::Project& project, const gd::SerializerElement& element) { SetString(element.GetChild("string", 0, "String").GetValue().GetString()); SetFontName(element.GetChild("font", 0, "Font").GetValue().GetString()); + SetTextAlignment(element.GetChild("textAlignment").GetValue().GetString()); SetCharacterSize(element.GetChild("characterSize", 0, "CharacterSize") .GetValue() .GetInt()); @@ -56,6 +58,7 @@ void TextObject::DoUnserializeFrom(gd::Project& project, void TextObject::DoSerializeTo(gd::SerializerElement& element) const { element.AddChild("string").SetValue(GetString()); element.AddChild("font").SetValue(GetFontName()); + element.AddChild("textAlignment").SetValue(GetTextAlignment()); element.AddChild("characterSize").SetValue(GetCharacterSize()); element.AddChild("color") .SetAttribute("r", (int)GetColorR()) diff --git a/Extensions/TextObject/TextObject.h b/Extensions/TextObject/TextObject.h index 71c5634a0e..93d8541880 100644 --- a/Extensions/TextObject/TextObject.h +++ b/Extensions/TextObject/TextObject.h @@ -53,6 +53,9 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration { */ void SetFontName(const gd::String& resourceName) { fontName = resourceName; }; + inline const gd::String& GetTextAlignment() const { return textAlignment; }; + void SetTextAlignment(const gd::String& textAlignment_) { textAlignment = textAlignment_; }; + bool IsBold() const { return bold; }; void SetBold(bool enable) { bold = enable; }; bool IsItalic() const { return italic; }; @@ -87,6 +90,7 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration { unsigned int colorR; unsigned int colorG; unsigned int colorB; + gd::String textAlignment; }; #endif // TEXTOBJECT_H diff --git a/Extensions/TextObject/textruntimeobject-pixi-renderer.ts b/Extensions/TextObject/textruntimeobject-pixi-renderer.ts index 4df001b56c..a899f233c1 100644 --- a/Extensions/TextObject/textruntimeobject-pixi-renderer.ts +++ b/Extensions/TextObject/textruntimeobject-pixi-renderer.ts @@ -94,7 +94,25 @@ namespace gdjs { } updatePosition(): void { - this._text.position.x = this._object.x + this._text.width / 2; + if (this._object.isWrapping()) { + const alignmentX = + this._object._textAlign === 'right' + ? 1 + : this._object._textAlign === 'center' + ? 0.5 + : 0; + + const width = this._object.getWrappingWidth(); + + // A vector from the custom size center to the renderer center. + const centerToCenterX = (width - this._text.width) * (alignmentX - 0.5); + + this._text.position.x = this._object.x + width / 2; + this._text.anchor.x = 0.5 - centerToCenterX / this._text.width; + } else { + this._text.position.x = this._object.x + this._text.width / 2; + this._text.anchor.x = 0.5; + } this._text.position.y = this._object.y + this._text.height / 2; } diff --git a/Extensions/TextObject/textruntimeobject.ts b/Extensions/TextObject/textruntimeobject.ts index ea1f36b912..aa0af824e6 100644 --- a/Extensions/TextObject/textruntimeobject.ts +++ b/Extensions/TextObject/textruntimeobject.ts @@ -26,6 +26,7 @@ namespace gdjs { }; /** The text of the object */ string: string; + textAlignment: string; }; export type TextObjectData = ObjectData & TextObjectDataType; @@ -82,6 +83,7 @@ namespace gdjs { textObjectData.color.b, ]; this._str = textObjectData.string; + this._textAlign = textObjectData.textAlignment; this._renderer = new gdjs.TextRuntimeObjectRenderer( this, instanceContainer @@ -127,6 +129,9 @@ namespace gdjs { if (oldObjectData.underlined !== newObjectData.underlined) { return false; } + if (oldObjectData.textAlignment !== newObjectData.textAlignment) { + this.setTextAlignment(newObjectData.textAlignment); + } return true; } @@ -288,7 +293,7 @@ namespace gdjs { * Get width of the text. */ getWidth(): float { - return this._renderer.getWidth(); + return this._wrapping ? this._wrappingWidth : this._renderer.getWidth(); } /** diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index c4f91f8554..be970f1b45 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -2871,6 +2871,8 @@ interface TextObject { unsigned long GetColorR(); unsigned long GetColorG(); unsigned long GetColorB(); + void SetTextAlignment([Const] DOMString textAlignment); + [Const, Ref] DOMString GetTextAlignment(); }; TextObject implements ObjectConfiguration; diff --git a/GDevelop.js/__tests__/Serializer.js b/GDevelop.js/__tests__/Serializer.js index d908982f32..abfb73ed9a 100644 --- a/GDevelop.js/__tests__/Serializer.js +++ b/GDevelop.js/__tests__/Serializer.js @@ -65,7 +65,7 @@ describe('libGD.js object serialization', function() { obj.delete(); expect(jsonObject).toBe( - '{"bold":false,"italic":false,"smoothed":true,"underlined":false,"string":"Text of the object, with 官话 characters","font":"","characterSize":20.0,"color":{"b":0,"g":0,"r":0}}' + '{"bold":false,"italic":false,"smoothed":true,"underlined":false,"string":"Text of the object, with 官话 characters","font":"","textAlignment":"left","characterSize":20.0,"color":{"b":0,"g":0,"r":0}}' ); }); }); diff --git a/GDevelop.js/types/gdtextobject.js b/GDevelop.js/types/gdtextobject.js index e849935675..3455c91ef7 100644 --- a/GDevelop.js/types/gdtextobject.js +++ b/GDevelop.js/types/gdtextobject.js @@ -17,6 +17,8 @@ declare class gdTextObject extends gdObjectConfiguration { getColorR(): number; getColorG(): number; getColorB(): number; + setTextAlignment(textAlignment: string): void; + getTextAlignment(): string; delete(): void; ptr: number; }; \ No newline at end of file diff --git a/newIDE/app/src/ObjectEditor/Editors/TextEditor.js b/newIDE/app/src/ObjectEditor/Editors/TextEditor.js index 0247aaa64b..e211c560a0 100644 --- a/newIDE/app/src/ObjectEditor/Editors/TextEditor.js +++ b/newIDE/app/src/ObjectEditor/Editors/TextEditor.js @@ -7,10 +7,18 @@ import Checkbox from '../../UI/Checkbox'; import { Line, Column } from '../../UI/Grid'; import ColorPicker from '../../UI/ColorField/ColorPicker'; import MiniToolbar, { MiniToolbarText } from '../../UI/MiniToolbar'; +import Text from '../../UI/Text'; import ResourceSelector from '../../ResourcesList/ResourceSelector'; import ResourcesLoader from '../../ResourcesLoader'; import { type EditorProps } from './EditorProps.flow'; import SemiControlledTextField from '../../UI/SemiControlledTextField'; +import ButtonGroup from '@material-ui/core/ButtonGroup'; +import Button from '@material-ui/core/Button'; +import Tooltip from '@material-ui/core/Tooltip'; +import LeftTextAlignment from '../../UI/CustomSvgIcons/LeftTextAlignment'; +import CenterTextAlignment from '../../UI/CustomSvgIcons/CenterTextAlignment'; +import RightTextAlignment from '../../UI/CustomSvgIcons/RightTextAlignment'; + const gd = global.gd; const toolbarItemStyle = { @@ -38,6 +46,8 @@ export default class TextEditor extends React.Component { objectConfiguration ); + const textAlignment = textObjectConfiguration.getTextAlignment(); + return ( @@ -97,26 +107,64 @@ export default class TextEditor extends React.Component { }} style={styles.checkbox} /> - - Font: - - { - textObjectConfiguration.setFontName(resourceName); - this.forceUpdate(); - }} - hintText={Choose a font} - style={styles.resourcesSelector} - /> + + Align text on the left}> + + + Align text on the center}> + + + Align text on the right}> + + + + + + { + textObjectConfiguration.setFontName(resourceName); + this.forceUpdate(); + }} + floatingLabelText={Choose a font} + hintText={Choose a font} + /> + + diff --git a/newIDE/app/src/ObjectsRendering/Renderers/RenderedTextInstance.js b/newIDE/app/src/ObjectsRendering/Renderers/RenderedTextInstance.js index c826f57d37..872bcd6274 100644 --- a/newIDE/app/src/ObjectsRendering/Renderers/RenderedTextInstance.js +++ b/newIDE/app/src/ObjectsRendering/Renderers/RenderedTextInstance.js @@ -20,6 +20,7 @@ export default class RenderedTextInstance extends RenderedInstance { _colorR: number = 0; _colorG: number = 0; _colorB: number = 0; + _textAlignment: string = 'left'; constructor( project: gdProject, @@ -76,6 +77,7 @@ export default class RenderedTextInstance extends RenderedInstance { textObjectConfiguration.isItalic() !== this._isItalic || textObjectConfiguration.isBold() !== this._isBold || textObjectConfiguration.getCharacterSize() !== this._characterSize || + textObjectConfiguration.getTextAlignment() !== this._textAlignment || this._instance.hasCustomSize() !== this._wrapping || (this._instance.getCustomWidth() !== this._wrappingWidth && this._wrapping) @@ -83,6 +85,7 @@ export default class RenderedTextInstance extends RenderedInstance { this._isItalic = textObjectConfiguration.isItalic(); this._isBold = textObjectConfiguration.isBold(); this._characterSize = textObjectConfiguration.getCharacterSize(); + this._textAlignment = textObjectConfiguration.getTextAlignment(); this._wrapping = this._instance.hasCustomSize(); this._wrappingWidth = this._instance.getCustomWidth(); this._styleFontDirty = true; @@ -118,6 +121,7 @@ export default class RenderedTextInstance extends RenderedInstance { this._pixiObject.style.wordWrapWidth = this._wrappingWidth <= 1 ? 1 : this._wrappingWidth; this._pixiObject.style.breakWords = true; + this._pixiObject.style.align = this._textAlignment; // Manually ask the PIXI object to re-render as we changed a style property // see http://www.html5gamedevs.com/topic/16924-change-text-style-post-render/ @@ -141,8 +145,28 @@ export default class RenderedTextInstance extends RenderedInstance { this._pixiObject.dirty = true; } - this._pixiObject.position.x = - this._instance.getX() + this._pixiObject.width / 2; + if (this._instance.hasCustomSize()) { + const alignmentX = + this._textAlignment === 'right' + ? 1 + : this._textAlignment === 'center' + ? 0.5 + : 0; + + const width = this._instance.getCustomWidth(); + + // A vector from the custom size center to the renderer center. + const centerToCenterX = + (width - this._pixiObject.width) * (alignmentX - 0.5); + + this._pixiObject.position.x = this._instance.getX() + width / 2; + this._pixiObject.anchor.x = + 0.5 - centerToCenterX / this._pixiObject.width; + } else { + this._pixiObject.position.x = + this._instance.getX() + this._pixiObject.width / 2; + this._pixiObject.anchor.x = 0.5; + } this._pixiObject.position.y = this._instance.getY() + this._pixiObject.height / 2; this._pixiObject.rotation = RenderedInstance.toRad( diff --git a/newIDE/app/src/UI/CustomSvgIcons/CenterTextAlignment.js b/newIDE/app/src/UI/CustomSvgIcons/CenterTextAlignment.js new file mode 100644 index 0000000000..3212714b4d --- /dev/null +++ b/newIDE/app/src/UI/CustomSvgIcons/CenterTextAlignment.js @@ -0,0 +1,31 @@ +import React from 'react'; +import SvgIcon from '@material-ui/core/SvgIcon'; + +export default React.memo(props => ( + + + + + + +)); diff --git a/newIDE/app/src/UI/CustomSvgIcons/LeftTextAlignment.js b/newIDE/app/src/UI/CustomSvgIcons/LeftTextAlignment.js new file mode 100644 index 0000000000..fdbe0d50af --- /dev/null +++ b/newIDE/app/src/UI/CustomSvgIcons/LeftTextAlignment.js @@ -0,0 +1,31 @@ +import React from 'react'; +import SvgIcon from '@material-ui/core/SvgIcon'; + +export default React.memo(props => ( + + + + + + +)); diff --git a/newIDE/app/src/UI/CustomSvgIcons/RightTextAlignment.js b/newIDE/app/src/UI/CustomSvgIcons/RightTextAlignment.js new file mode 100644 index 0000000000..f8a4b408d7 --- /dev/null +++ b/newIDE/app/src/UI/CustomSvgIcons/RightTextAlignment.js @@ -0,0 +1,31 @@ +import React from 'react'; +import SvgIcon from '@material-ui/core/SvgIcon'; + +export default React.memo(props => ( + + + + + + +));