From 2a2c930b74a15b661b18a8f304be76cb98da6113 Mon Sep 17 00:00:00 2001 From: D8H Date: Wed, 2 Oct 2024 11:17:48 +0200 Subject: [PATCH] Add vertical alignment property for text object (#6975) - Fix proportional anchors on custom object children --- .../AnchorBehavior/anchorruntimebehavior.ts | 55 ++++++-- .../tests/anchorruntimebehavior.spec.js | 21 +++ Extensions/TextObject/TextObject.cpp | 16 +++ Extensions/TextObject/TextObject.h | 6 + .../textruntimeobject-pixi-renderer.ts | 10 ++ Extensions/TextObject/textruntimeobject.ts | 47 ++++++- .../tests/LegacyTweenBehavior.spec.js | 1 + .../TweenBehavior/tests/TweenBehavior.spec.js | 1 + GDevelop.js/Bindings/Bindings.idl | 2 + GDevelop.js/__tests__/Serializer.js | 2 +- GDevelop.js/types.d.ts | 2 + GDevelop.js/types/gdtextobject.js | 2 + .../src/ObjectEditor/Editors/TextEditor.js | 129 ++++++++++++------ .../Renderers/CustomObjectLayoutingModel.js | 85 ++++++------ .../CustomObjectLayoutingModel.spec.js | 16 ++- .../Renderers/RenderedTextInstance.js | 23 +++- .../UI/CustomSvgIcons/BottomTextAlignment.js | 19 +++ .../CenterVerticalTextAlignment.js | 25 ++++ .../src/UI/CustomSvgIcons/TopTextAlignment.js | 19 +++ 19 files changed, 379 insertions(+), 102 deletions(-) create mode 100644 newIDE/app/src/UI/CustomSvgIcons/BottomTextAlignment.js create mode 100644 newIDE/app/src/UI/CustomSvgIcons/CenterVerticalTextAlignment.js create mode 100644 newIDE/app/src/UI/CustomSvgIcons/TopTextAlignment.js diff --git a/Extensions/AnchorBehavior/anchorruntimebehavior.ts b/Extensions/AnchorBehavior/anchorruntimebehavior.ts index 9c385646dd3a..472e12474c29 100644 --- a/Extensions/AnchorBehavior/anchorruntimebehavior.ts +++ b/Extensions/AnchorBehavior/anchorruntimebehavior.ts @@ -88,7 +88,6 @@ namespace gdjs { const workingPoint: FloatPoint = gdjs.staticArray( gdjs.AnchorRuntimeBehavior.prototype.doStepPreEvents ) as FloatPoint; - // TODO EBO Make it work with event based objects or hide this behavior for them. let parentMinX = instanceContainer.getUnrotatedViewportMinX(); let parentMinY = instanceContainer.getUnrotatedViewportMinY(); let parentMaxX = instanceContainer.getUnrotatedViewportMaxX(); @@ -111,10 +110,11 @@ namespace gdjs { } //Calculate the distances from the window's bounds. - const topLeftPixel = layer.convertCoords( + const topLeftPixel = this._convertCoords( + instanceContainer, + layer, this.owner.getDrawableX(), this.owner.getDrawableY(), - 0, workingPoint ); @@ -141,10 +141,11 @@ namespace gdjs { } // It's fine to reuse workingPoint as topLeftPixel is no longer used. - const bottomRightPixel = layer.convertCoords( + const bottomRightPixel = this._convertCoords( + instanceContainer, + layer, this.owner.getDrawableX() + this.owner.getWidth(), this.owner.getDrawableY() + this.owner.getHeight(), - 0, workingPoint ); @@ -225,19 +226,21 @@ namespace gdjs { } // It's fine to reuse workingPoint as topLeftPixel is no longer used. - const topLeftCoord = layer.convertInverseCoords( + const topLeftCoord = this._convertInverseCoords( + instanceContainer, + layer, leftPixel, topPixel, - 0, workingPoint ); const left = topLeftCoord[0]; const top = topLeftCoord[1]; - const bottomRightCoord = layer.convertInverseCoords( + const bottomRightCoord = this._convertInverseCoords( + instanceContainer, + layer, rightPixel, bottomPixel, - 0, workingPoint ); const right = bottomRightCoord[0]; @@ -333,6 +336,40 @@ namespace gdjs { } doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {} + + private _convertCoords( + instanceContainer: gdjs.RuntimeInstanceContainer, + layer: gdjs.RuntimeLayer, + x: float, + y: float, + result: FloatPoint + ) { + const isParentACustomObject = + instanceContainer !== instanceContainer.getScene(); + if (isParentACustomObject) { + result[0] = x; + result[1] = y; + return result; + } + return layer.convertCoords(x, y, 0, result); + } + + private _convertInverseCoords( + instanceContainer: gdjs.RuntimeInstanceContainer, + layer: gdjs.RuntimeLayer, + x: float, + y: float, + result: FloatPoint + ) { + const isParentACustomObject = + instanceContainer !== instanceContainer.getScene(); + if (isParentACustomObject) { + result[0] = x; + result[1] = y; + return result; + } + return layer.convertInverseCoords(x, y, 0, result); + } } gdjs.registerBehavior( 'AnchorBehavior::AnchorBehavior', diff --git a/Extensions/AnchorBehavior/tests/anchorruntimebehavior.spec.js b/Extensions/AnchorBehavior/tests/anchorruntimebehavior.spec.js index 9c7c47724681..608a51fcdc22 100644 --- a/Extensions/AnchorBehavior/tests/anchorruntimebehavior.spec.js +++ b/Extensions/AnchorBehavior/tests/anchorruntimebehavior.spec.js @@ -265,5 +265,26 @@ describe('gdjs.AnchorRuntimeBehavior', function () { expect(object.getWidth()).to.equal(2000); expect(object.getHeight()).to.equal(3000); }); + + it('can fill the screen with an object using proportional anchors (with custom origin)', () => { + setGameResolutionSizeAndStep(1000, 500); + + const object = createSpriteWithOriginAtCenter({ + leftEdgeAnchor: 3, + topEdgeAnchor: 3, + rightEdgeAnchor: 3, + bottomEdgeAnchor: 3, + }); + object.setCustomWidthAndHeight(1000, 500); + object.setPosition(500, 250); + runtimeScene.renderAndStep(1000 / 60); + + setGameResolutionSizeAndStep(2000, 3000); + + expect(object.getX()).to.equal(1000); + expect(object.getY()).to.equal(1500); + expect(object.getWidth()).to.equal(2000); + expect(object.getHeight()).to.equal(3000); + }); }); }); diff --git a/Extensions/TextObject/TextObject.cpp b/Extensions/TextObject/TextObject.cpp index f33901302124..dbd66db75dda 100644 --- a/Extensions/TextObject/TextObject.cpp +++ b/Extensions/TextObject/TextObject.cpp @@ -27,6 +27,7 @@ TextObject::TextObject() underlined(false), color("0;0;0"), textAlignment("left"), + verticalTextAlignment("top"), isOutlineEnabled(false), outlineThickness(2), outlineColor("255;255;255"), @@ -69,6 +70,10 @@ bool TextObject::UpdateProperty(const gd::String& propertyName, textAlignment = newValue; return true; } + if (propertyName == "verticalTextAlignment") { + verticalTextAlignment = newValue; + return true; + } if (propertyName == "isOutlineEnabled") { isOutlineEnabled = newValue == "1"; return true; @@ -163,6 +168,15 @@ std::map TextObject::GetProperties() const { .SetGroup(_("Font")) .SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden); + objectProperties["verticalTextAlignment"] + .SetValue(verticalTextAlignment) + .SetType("choice") + .AddExtraInfo("top") + .AddExtraInfo("center") + .AddExtraInfo("bottom") + .SetLabel(_("Vertical alignment")) + .SetGroup(_("Font")); + objectProperties["isOutlineEnabled"] .SetValue(isOutlineEnabled ? "true" : "false") .SetType("boolean") @@ -252,6 +266,7 @@ void TextObject::DoUnserializeFrom(gd::Project& project, SetFontName(content.GetChild("font", 0, "Font").GetValue().GetString()); SetTextAlignment(content.GetChild("textAlignment").GetValue().GetString()); + SetVerticalTextAlignment(content.GetStringAttribute("verticalTextAlignment", "top")); SetCharacterSize(content.GetChild("characterSize", 0, "CharacterSize") .GetValue() .GetInt()); @@ -321,6 +336,7 @@ void TextObject::DoSerializeTo(gd::SerializerElement& element) const { content.AddChild("text").SetValue(GetText()); content.AddChild("font").SetValue(GetFontName()); content.AddChild("textAlignment").SetValue(GetTextAlignment()); + content.AddChild("verticalTextAlignment").SetValue(GetVerticalTextAlignment()); content.AddChild("characterSize").SetValue(GetCharacterSize()); content.AddChild("color").SetValue(GetColor()); diff --git a/Extensions/TextObject/TextObject.h b/Extensions/TextObject/TextObject.h index 37f20435568b..667228988899 100644 --- a/Extensions/TextObject/TextObject.h +++ b/Extensions/TextObject/TextObject.h @@ -62,6 +62,11 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration { textAlignment = textAlignment_; }; + inline const gd::String& GetVerticalTextAlignment() const { return verticalTextAlignment; }; + void SetVerticalTextAlignment(const gd::String& verticalTextAlignment_) { + verticalTextAlignment = verticalTextAlignment_; + }; + bool IsBold() const { return bold; }; void SetBold(bool enable) { bold = enable; }; bool IsItalic() const { return italic; }; @@ -120,6 +125,7 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration { bool bold, italic, underlined; gd::String color; gd::String textAlignment; + gd::String verticalTextAlignment; bool isOutlineEnabled; double outlineThickness; diff --git a/Extensions/TextObject/textruntimeobject-pixi-renderer.ts b/Extensions/TextObject/textruntimeobject-pixi-renderer.ts index 42f7224e7dc8..60770f7b5075 100644 --- a/Extensions/TextObject/textruntimeobject-pixi-renderer.ts +++ b/Extensions/TextObject/textruntimeobject-pixi-renderer.ts @@ -118,6 +118,16 @@ namespace gdjs { this._text.anchor.x = 0.5; } this._text.position.y = this._object.y + this._text.height / 2; + + const alignmentY = + this._object._verticalTextAlignment === 'bottom' + ? 1 + : this._object._verticalTextAlignment === 'center' + ? 0.5 + : 0; + this._text.position.y = + this._object.y + this._text.height * (0.5 - alignmentY); + this._text.anchor.y = 0.5; } updateAngle(): void { diff --git a/Extensions/TextObject/textruntimeobject.ts b/Extensions/TextObject/textruntimeobject.ts index 01a63207f617..e44561aa307d 100644 --- a/Extensions/TextObject/textruntimeobject.ts +++ b/Extensions/TextObject/textruntimeobject.ts @@ -21,6 +21,7 @@ namespace gdjs { /** The text of the object */ text: string; textAlignment: string; + verticalTextAlignment: string; isOutlineEnabled: boolean; outlineThickness: float; @@ -82,7 +83,8 @@ namespace gdjs { _gradient: Array> = []; _gradientType: string = ''; opacity: float = 255; - _textAlign: string = 'left'; + _textAlign: string; + _verticalTextAlignment: string; _wrapping: boolean = false; // A wrapping of 1 makes games crash on Firefox _wrappingWidth: float = 100; @@ -123,7 +125,8 @@ namespace gdjs { this._underlined = content.underlined; this._color = gdjs.rgbOrHexToRGBColor(content.color); this._str = content.text; - this._textAlign = content.textAlignment; + this._textAlign = content.textAlignment || 'left'; + this._verticalTextAlignment = content.verticalTextAlignment || 'top'; this._isOutlineEnabled = content.isOutlineEnabled; this._outlineThickness = content.outlineThickness; @@ -175,6 +178,11 @@ namespace gdjs { if (oldContent.textAlignment !== newContent.textAlignment) { this.setTextAlignment(newContent.textAlignment); } + if ( + oldContent.verticalTextAlignment !== newContent.verticalTextAlignment + ) { + this.setVerticalTextAlignment(newContent.verticalTextAlignment); + } if (oldContent.isOutlineEnabled !== newContent.isOutlineEnabled) { this.setOutlineEnabled(newContent.isOutlineEnabled); } @@ -267,6 +275,9 @@ namespace gdjs { if (networkSyncData.ta !== undefined) { this.setTextAlignment(networkSyncData.ta); } + if (networkSyncData.ta !== undefined) { + this.setVerticalTextAlignment(networkSyncData.ta); + } if (networkSyncData.wrap !== undefined) { this.setWrapping(networkSyncData.wrap); } @@ -594,6 +605,23 @@ namespace gdjs { return this._color[0] + ';' + this._color[1] + ';' + this._color[2]; } + /** + * Set the text alignment on Y axis for multiline text objects. + * @param alignment The text alignment. + */ + setVerticalTextAlignment(alignment: string): void { + this._verticalTextAlignment = alignment; + this._renderer.updateStyle(); + } + + /** + * Get the text alignment on Y axis of text object. + * @return The text alignment. + */ + getVerticalTextAlignment(): string { + return this._verticalTextAlignment; + } + /** * Set the text alignment for multiline text objects. * @param alignment The text alignment. @@ -656,6 +684,21 @@ namespace gdjs { } } + setWidth(width: float): void { + this.setWrappingWidth(width); + } + + getDrawableY(): float { + return ( + this.getY() - + (this._verticalTextAlignment === 'center' + ? this.getHeight() / 2 + : this._verticalTextAlignment === 'bottom' + ? this.getHeight() + : 0) + ); + } + /** * Set the outline for the text object. * @param str color as a "R;G;B" string, for example: "255;0;0" diff --git a/Extensions/TweenBehavior/tests/LegacyTweenBehavior.spec.js b/Extensions/TweenBehavior/tests/LegacyTweenBehavior.spec.js index 7026da0f6147..ee6839646e63 100644 --- a/Extensions/TweenBehavior/tests/LegacyTweenBehavior.spec.js +++ b/Extensions/TweenBehavior/tests/LegacyTweenBehavior.spec.js @@ -135,6 +135,7 @@ describe('gdjs.TweenRuntimeBehavior', () => { color: '0;0;0', text: '', textAlignment: 'left', + verticalTextAlignment: 'top', isOutlineEnabled: false, outlineThickness: 2, outlineColor: '255;255;255', diff --git a/Extensions/TweenBehavior/tests/TweenBehavior.spec.js b/Extensions/TweenBehavior/tests/TweenBehavior.spec.js index 4934df903436..f9f6c40f1f3b 100644 --- a/Extensions/TweenBehavior/tests/TweenBehavior.spec.js +++ b/Extensions/TweenBehavior/tests/TweenBehavior.spec.js @@ -135,6 +135,7 @@ describe('gdjs.TweenRuntimeBehavior', () => { color: '0;0;0', text: '', textAlignment: 'left', + verticalTextAlignment: 'top', isOutlineEnabled: false, outlineThickness: 2, outlineColor: '255;255;255', diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 5cb5e6bf7369..0bca0c2031ef 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -3524,6 +3524,8 @@ interface TextObject { [Const, Ref] DOMString GetColor(); void SetTextAlignment([Const] DOMString textAlignment); [Const, Ref] DOMString GetTextAlignment(); + void SetVerticalTextAlignment([Const] DOMString value); + [Const, Ref] DOMString GetVerticalTextAlignment(); void SetOutlineEnabled(boolean enable); boolean IsOutlineEnabled(); diff --git a/GDevelop.js/__tests__/Serializer.js b/GDevelop.js/__tests__/Serializer.js index eac4945081d4..49a5038f38da 100644 --- a/GDevelop.js/__tests__/Serializer.js +++ b/GDevelop.js/__tests__/Serializer.js @@ -64,7 +64,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":"","textAlignment":"left","characterSize":20.0,"color":{"b":0,"g":0,"r":0},"content":{"bold":false,"isOutlineEnabled":false,"isShadowEnabled":false,"italic":false,"outlineColor":"255;255;255","outlineThickness":2.0,"shadowAngle":90.0,"shadowBlurRadius":2.0,"shadowColor":"0;0;0","shadowDistance":4.0,"shadowOpacity":127.0,"smoothed":true,"underlined":false,"text":"Text of the object, with 官话 characters","font":"","textAlignment":"left","characterSize":20.0,"color":"0;0;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},"content":{"bold":false,"isOutlineEnabled":false,"isShadowEnabled":false,"italic":false,"outlineColor":"255;255;255","outlineThickness":2.0,"shadowAngle":90.0,"shadowBlurRadius":2.0,"shadowColor":"0;0;0","shadowDistance":4.0,"shadowOpacity":127.0,"smoothed":true,"underlined":false,"text":"Text of the object, with 官话 characters","font":"","textAlignment":"left","verticalTextAlignment":"top","characterSize":20.0,"color":"0;0;0"}}' ); }); }); diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index c8c294d356ff..0796b3e59a8a 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -2623,6 +2623,8 @@ export class TextObject extends ObjectConfiguration { getColor(): string; setTextAlignment(textAlignment: string): void; getTextAlignment(): string; + setVerticalTextAlignment(value: string): void; + getVerticalTextAlignment(): string; setOutlineEnabled(enable: boolean): void; isOutlineEnabled(): boolean; setOutlineThickness(value: number): void; diff --git a/GDevelop.js/types/gdtextobject.js b/GDevelop.js/types/gdtextobject.js index 384aab5e7cf8..23a8d5c22562 100644 --- a/GDevelop.js/types/gdtextobject.js +++ b/GDevelop.js/types/gdtextobject.js @@ -17,6 +17,8 @@ declare class gdTextObject extends gdObjectConfiguration { getColor(): string; setTextAlignment(textAlignment: string): void; getTextAlignment(): string; + setVerticalTextAlignment(value: string): void; + getVerticalTextAlignment(): string; setOutlineEnabled(enable: boolean): void; isOutlineEnabled(): boolean; setOutlineThickness(value: number): void; diff --git a/newIDE/app/src/ObjectEditor/Editors/TextEditor.js b/newIDE/app/src/ObjectEditor/Editors/TextEditor.js index 2a69214d90ea..b577ed2dfbb0 100644 --- a/newIDE/app/src/ObjectEditor/Editors/TextEditor.js +++ b/newIDE/app/src/ObjectEditor/Editors/TextEditor.js @@ -7,17 +7,23 @@ import Checkbox from '../../UI/Checkbox'; import { Line, Column } from '../../UI/Grid'; import ColorPicker from '../../UI/ColorField/ColorPicker'; import { MiniToolbarText } from '../../UI/MiniToolbar'; -import { ColumnStackLayout, ResponsiveLineStackLayout } from '../../UI/Layout'; +import { + ColumnStackLayout, + LineStackLayout, + ResponsiveLineStackLayout, +} from '../../UI/Layout'; 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'; +import TopTextAlignment from '../../UI/CustomSvgIcons/TopTextAlignment'; +import CenterVerticalTextAlignment from '../../UI/CustomSvgIcons/CenterVerticalTextAlignment'; +import BottomTextAlignment from '../../UI/CustomSvgIcons/BottomTextAlignment'; import Text from '../../UI/Text'; import { rgbColorToRGBString, @@ -55,6 +61,7 @@ export default class TextEditor extends React.Component { ); const textAlignment = textObjectConfiguration.getTextAlignment(); + const verticalTextAlignment = textObjectConfiguration.getVerticalTextAlignment(); return ( @@ -94,8 +101,6 @@ export default class TextEditor extends React.Component { this.forceUpdate(); }} /> - - Bold} checked={textObjectConfiguration.isBold()} @@ -114,47 +119,85 @@ export default class TextEditor extends React.Component { }} style={styles.checkbox} /> + + - Align text on the left}> - - - Align text on the center}> - - - Align text on the right}> - - + + + - + + + + + + ( const initialInstanceOriginX = (renderedInstance.getOriginX() * initialInstanceWidth) / - renderedInstance.getDefaultWidth(); + Math.max(1, renderedInstance.getWidth()); const initialInstanceMinX = initialInstanceX - initialInstanceOriginX; const initialInstanceMaxX = initialInstanceMinX + initialInstanceWidth; @@ -397,28 +404,23 @@ export const getLayoutedRenderedInstance = ( right = parentCenterX + initialInstanceMaxX - parentInitialCenterX; } - let x, width; - if (rightEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor) { - width = initialInstanceWidth; - const originX = - (renderedInstance.getOriginX() * width) / - renderedInstance.getDefaultWidth(); - x = left + originX; - } else if (leftEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor) { - width = initialInstanceWidth; - const originX = - (renderedInstance.getOriginX() * width) / - renderedInstance.getDefaultWidth(); - x = right - width + originX; - } else { - width = right - left; - const originX = - (renderedInstance.getOriginX() * width) / - renderedInstance.getDefaultWidth(); - x = left + originX; - } - layoutedInstance.x = x; + const width = + rightEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor + ? initialInstanceWidth + : leftEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor + ? initialInstanceWidth + : right - left; layoutedInstance.setCustomWidth(width); + + // The originX according to the new width. + const originX = renderedInstance.getOriginX(); + const x = + rightEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor + ? left + originX + : leftEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor + ? right - width + originX + : left + originX; + layoutedInstance.x = x; } const initialInstanceY = initialInstance.getY(); @@ -438,7 +440,7 @@ export const getLayoutedRenderedInstance = ( const initialInstanceOriginY = (renderedInstance.getOriginY() * initialInstanceHeight) / - renderedInstance.getDefaultHeight(); + Math.max(1, renderedInstance.getHeight()); const initialInstanceMinY = initialInstanceY - initialInstanceOriginY; const initialInstanceMaxY = initialInstanceMinY + initialInstanceHeight; @@ -470,28 +472,23 @@ export const getLayoutedRenderedInstance = ( top = parentCenterY + initialInstanceMinY - parentInitialCenterY; } - let y, height; - if (bottomEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor) { - height = initialInstanceHeight; - const originY = - (renderedInstance.getOriginY() * height) / - renderedInstance.getDefaultHeight(); - y = top + originY; - } else if (topEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor) { - height = initialInstanceHeight; - const originY = - (renderedInstance.getOriginY() * height) / - renderedInstance.getDefaultHeight(); - y = bottom - height + originY; - } else { - height = bottom - top; - const originY = - (renderedInstance.getOriginY() * height) / - renderedInstance.getDefaultHeight(); - y = top + originY; - } - layoutedInstance.y = y; + const height = + bottomEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor + ? initialInstanceHeight + : topEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor + ? initialInstanceHeight + : bottom - top; layoutedInstance.setCustomHeight(height); + + // The originY according to the new height. + const originY = renderedInstance.getOriginY(); + const y = + bottomEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor + ? top + originY + : topEdgeAnchor === gd.CustomObjectConfiguration.NoAnchor + ? bottom - height + originY + : top + originY; + layoutedInstance.y = y; } return renderedInstance; }; diff --git a/newIDE/app/src/ObjectsRendering/Renderers/CustomObjectLayoutingModel.spec.js b/newIDE/app/src/ObjectsRendering/Renderers/CustomObjectLayoutingModel.spec.js index 97801c19e60f..c6418604df01 100644 --- a/newIDE/app/src/ObjectsRendering/Renderers/CustomObjectLayoutingModel.spec.js +++ b/newIDE/app/src/ObjectsRendering/Renderers/CustomObjectLayoutingModel.spec.js @@ -579,6 +579,18 @@ class MockedChildRenderedInstance implements ChildRenderedInstance { this.heightAfterUpdate = defaultHeight; } + getWidth(): number { + return this._instance.hasCustomSize() + ? this._instance.getCustomWidth() + : this.getDefaultWidth(); + } + + getHeight(): number { + return this._instance.hasCustomSize() + ? this._instance.getCustomHeight() + : this.getDefaultHeight(); + } + getDefaultWidth(): number { return this.defaultWidth; } @@ -588,11 +600,11 @@ class MockedChildRenderedInstance implements ChildRenderedInstance { } getOriginX(): number { - return this.originX; + return (this.originX * this.getWidth()) / this.getDefaultWidth(); } getOriginY(): number { - return this.originY; + return (this.originY * this.getHeight()) / this.getDefaultHeight(); } update(): void { diff --git a/newIDE/app/src/ObjectsRendering/Renderers/RenderedTextInstance.js b/newIDE/app/src/ObjectsRendering/Renderers/RenderedTextInstance.js index bf182a0e6a20..fd84ef640cbf 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 { _fontFamily: string = ''; _color: string = '0;0;0'; _textAlignment: string = 'left'; + _verticalTextAlignment: string = 'top'; _isOutlineEnabled = false; _outlineColor = '255;255;255'; @@ -91,6 +92,8 @@ export default class RenderedTextInstance extends RenderedInstance { textObjectConfiguration.isBold() !== this._isBold || textObjectConfiguration.getCharacterSize() !== this._characterSize || textObjectConfiguration.getTextAlignment() !== this._textAlignment || + textObjectConfiguration.getVerticalTextAlignment() !== + this._verticalTextAlignment || textObjectConfiguration.getColor() !== this._color || textObjectConfiguration.isOutlineEnabled() !== this._isOutlineEnabled || textObjectConfiguration.getOutlineColor() !== this._outlineColor || @@ -110,6 +113,7 @@ export default class RenderedTextInstance extends RenderedInstance { this._isBold = textObjectConfiguration.isBold(); this._characterSize = textObjectConfiguration.getCharacterSize(); this._textAlignment = textObjectConfiguration.getTextAlignment(); + this._verticalTextAlignment = textObjectConfiguration.getVerticalTextAlignment(); this._color = textObjectConfiguration.getColor(); this._isOutlineEnabled = textObjectConfiguration.isOutlineEnabled(); @@ -204,8 +208,16 @@ export default class RenderedTextInstance extends RenderedInstance { this._instance.getX() + this._pixiObject.width / 2; this._pixiObject.anchor.x = 0.5; } + const alignmentY = + this._verticalTextAlignment === 'bottom' + ? 1 + : this._verticalTextAlignment === 'center' + ? 0.5 + : 0; this._pixiObject.position.y = - this._instance.getY() + this._pixiObject.height / 2; + this._instance.getY() + this._pixiObject.height * (0.5 - alignmentY); + this._pixiObject.anchor.y = 0.5; + this._pixiObject.rotation = RenderedInstance.toRad( this._instance.getAngle() ); @@ -222,4 +234,13 @@ export default class RenderedTextInstance extends RenderedInstance { getDefaultHeight() { return this._pixiObject.height; } + + getOriginY(): number { + const height = this.getHeight(); + return this._verticalTextAlignment === 'bottom' + ? height + : this._verticalTextAlignment === 'center' + ? height / 2 + : 0; + } } diff --git a/newIDE/app/src/UI/CustomSvgIcons/BottomTextAlignment.js b/newIDE/app/src/UI/CustomSvgIcons/BottomTextAlignment.js new file mode 100644 index 000000000000..74106a8b8af8 --- /dev/null +++ b/newIDE/app/src/UI/CustomSvgIcons/BottomTextAlignment.js @@ -0,0 +1,19 @@ +import React from 'react'; +import SvgIcon from '@material-ui/core/SvgIcon'; + +export default React.memo(props => ( + + + + +)); diff --git a/newIDE/app/src/UI/CustomSvgIcons/CenterVerticalTextAlignment.js b/newIDE/app/src/UI/CustomSvgIcons/CenterVerticalTextAlignment.js new file mode 100644 index 000000000000..600578e67f29 --- /dev/null +++ b/newIDE/app/src/UI/CustomSvgIcons/CenterVerticalTextAlignment.js @@ -0,0 +1,25 @@ +import React from 'react'; +import SvgIcon from '@material-ui/core/SvgIcon'; + +export default React.memo(props => ( + + + + + +)); diff --git a/newIDE/app/src/UI/CustomSvgIcons/TopTextAlignment.js b/newIDE/app/src/UI/CustomSvgIcons/TopTextAlignment.js new file mode 100644 index 000000000000..f69507490814 --- /dev/null +++ b/newIDE/app/src/UI/CustomSvgIcons/TopTextAlignment.js @@ -0,0 +1,19 @@ +import React from 'react'; +import SvgIcon from '@material-ui/core/SvgIcon'; + +export default React.memo(props => ( + + + + +));