Compare commits

...

1 Commits

Author SHA1 Message Date
Cursor Agent
1ec6ea68fe Add line height support for Text and BBText objects
Co-authored-by: florian <florian@gdevelop.io>
2025-08-07 14:17:51 +00:00
11 changed files with 154 additions and 1 deletions

View File

@@ -94,6 +94,16 @@ module.exports = {
.setLabel(_('Vertical alignment'))
.setGroup(_('Appearance'));
if (!objectContent.lineHeight) {
objectContent.lineHeight = 0;
}
objectProperties
.getOrCreate('lineHeight')
.setValue(objectContent.lineHeight.toString())
.setType('number')
.setLabel(_('Line height (0 = default)'))
.setGroup(_('Font'));
objectProperties
.getOrCreate('fontFamily')
.setValue(objectContent.fontFamily)
@@ -394,6 +404,19 @@ module.exports = {
expressionLabel: _('Get the wrapping width'),
expressionDescription: _('Get the wrapping width'),
},
{
functionName: 'LineHeight',
iconPath: 'res/actions/characterSize24.png',
type: 'number',
instructionLabel: _('Line height'),
paramLabel: _('Line height (0 = default)'),
conditionDescription: _('Compare the base line height of the text.'),
conditionSentence: _('the base line height'),
actionDescription: _('Set base line height'),
actionSentence: _('the base line height'),
expressionLabel: _('Get the base line height'),
expressionDescription: _('Get the base line height'),
},
];
addSettersAndGettersToObject(object, setterAndGetterProperties, 'BBText');

View File

@@ -32,6 +32,7 @@ namespace gdjs {
wordWrap: runtimeObject._wrapping,
wordWrapWidth: runtimeObject._wrappingWidth,
align: runtimeObject._textAlign as PIXI.TextStyleAlign | undefined,
lineHeight: runtimeObject._lineHeight > 0 ? runtimeObject._lineHeight : undefined,
},
});
instanceContainer
@@ -102,6 +103,13 @@ namespace gdjs {
this._pixiObject.dirty = true;
}
updateLineHeight(): void {
//@ts-ignore Private member usage.
this._pixiObject.textStyles.default.lineHeight =
this._object._lineHeight;
this._pixiObject.dirty = true;
}
updatePosition(): void {
if (this._object.isWrapping() && this._pixiObject.width !== 0) {
const alignmentX =

View File

@@ -20,6 +20,8 @@ namespace gdjs {
/** Alignment of the text: "left", "center" or "right" */
align: 'left' | 'center' | 'right';
verticalTextAlignment: 'top' | 'center' | 'bottom';
/** Line height for multiline text (0 = default) */
lineHeight: number;
};
};
export type BBTextObjectData = ObjectData & BBTextObjectDataType;
@@ -35,6 +37,7 @@ namespace gdjs {
align: string;
vta: string;
hidden: boolean;
lh: number;
};
export type BBTextObjectNetworkSyncData = ObjectNetworkSyncData &
@@ -61,6 +64,7 @@ namespace gdjs {
_textAlign: string;
_verticalTextAlignment: string;
_lineHeight: float;
_renderer: gdjs.BBTextRuntimeObjectRenderer;
@@ -87,6 +91,7 @@ namespace gdjs {
this._textAlign = objectData.content.align;
this._verticalTextAlignment =
objectData.content.verticalTextAlignment || 'top';
this._lineHeight = objectData.content.lineHeight || 0;
this.hidden = !objectData.content.visible;
this._renderer = new gdjs.BBTextRuntimeObjectRenderer(
@@ -142,6 +147,11 @@ namespace gdjs {
newObjectData.content.verticalTextAlignment
);
}
if (
oldObjectData.content.lineHeight !== newObjectData.content.lineHeight
) {
this.setLineHeight(newObjectData.content.lineHeight);
}
return true;
}
@@ -158,6 +168,7 @@ namespace gdjs {
align: this._textAlign,
vta: this._verticalTextAlignment,
hidden: this.hidden,
lh: this._lineHeight,
};
}
@@ -196,6 +207,9 @@ namespace gdjs {
if (this.hidden !== undefined) {
this.hide(networkSyncData.hidden);
}
if (networkSyncData.lh !== undefined) {
this.setLineHeight(networkSyncData.lh);
}
}
override extraInitializationFromInitialInstance(
@@ -397,6 +411,23 @@ namespace gdjs {
: 0)
);
}
/**
* Get line height of the BBText object.
* @return line height (0 = default)
*/
getLineHeight(): number {
return this._lineHeight;
}
/**
* Set line height of the BBText object.
* @param value line height (0 = default)
*/
setLineHeight(value: float): void {
this._lineHeight = value;
this._renderer.updateLineHeight();
}
}
// @ts-ignore
gdjs.registerObject('BBText::BBText', gdjs.BBTextRuntimeObject);

View File

@@ -449,6 +449,16 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions());
obj.AddExpressionAndConditionAndAction("number",
"LineHeight",
_("Line height"),
_("the line height of a text object"),
_("the line height"),
_("Font"),
"res/conditions/characterSize24.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions());
// Support for deprecated "Size" actions/conditions:
obj.AddDuplicatedAction("Size", "Text::SetFontSize").SetHidden();
obj.AddDuplicatedCondition("Size", "Text::FontSize").SetHidden();

View File

@@ -38,6 +38,14 @@ class TextObjectJsExtension : public gd::PlatformExtension {
GetAllExpressionsForObject("TextObject::Text")["FontSize"]
.SetFunctionName("getCharacterSize");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetLineHeight"]
.SetFunctionName("setLineHeight")
.SetGetter("getLineHeight");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::LineHeight"]
.SetFunctionName("getLineHeight");
GetAllExpressionsForObject("TextObject::Text")["LineHeight"]
.SetFunctionName("getLineHeight");
GetAllActionsForObject("TextObject::Text")["TextObject::Font"]
.SetFunctionName("setFontName");

View File

@@ -36,7 +36,8 @@ TextObject::TextObject()
shadowOpacity(127),
shadowAngle(90),
shadowDistance(4),
shadowBlurRadius(2) {}
shadowBlurRadius(2),
lineHeight(0) {}
TextObject::~TextObject() {};
@@ -110,6 +111,10 @@ bool TextObject::UpdateProperty(const gd::String& propertyName,
shadowBlurRadius = newValue.To<double>();
return true;
}
if (propertyName == "lineHeight") {
lineHeight = newValue.To<double>();
return true;
}
return false;
}
@@ -255,6 +260,13 @@ std::map<gd::String, gd::PropertyDescriptor> TextObject::GetProperties() const {
.SetAdvanced()
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
objectProperties["lineHeight"]
.SetValue(gd::String::From(lineHeight))
.SetType("number")
.SetLabel(_("Line height"))
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Font"));
return objectProperties;
}
@@ -304,6 +316,8 @@ void TextObject::DoUnserializeFrom(gd::Project& project,
SetShadowAngle(content.GetIntAttribute("shadowAngle", 90));
SetShadowDistance(content.GetIntAttribute("shadowDistance", 4));
SetShadowBlurRadius(content.GetIntAttribute("shadowBlurRadius", 2));
SetLineHeight(content.GetDoubleAttribute("lineHeight", 0));
}
}
@@ -356,6 +370,7 @@ void TextObject::DoSerializeTo(gd::SerializerElement& element) const {
content.SetAttribute("shadowAngle", shadowAngle);
content.SetAttribute("shadowDistance", shadowDistance);
content.SetAttribute("shadowBlurRadius", shadowBlurRadius);
content.SetAttribute("lineHeight", lineHeight);
}
void TextObject::ExposeResources(gd::ArbitraryResourceWorker& worker) {

View File

@@ -113,6 +113,9 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
void SetShadowBlurRadius(double value) { shadowBlurRadius = value; };
double GetShadowBlurRadius() const { return shadowBlurRadius; };
void SetLineHeight(double value) { lineHeight = value; };
double GetLineHeight() const { return lineHeight; };
private:
virtual void DoUnserializeFrom(gd::Project& project,
const gd::SerializerElement& element) override;
@@ -137,4 +140,5 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
double shadowAngle;
double shadowDistance;
double shadowBlurRadius;
double lineHeight;
};

View File

@@ -64,6 +64,7 @@ namespace gdjs {
style.wordWrap = this._object._wrapping;
style.wordWrapWidth = this._object._wrappingWidth;
style.breakWords = true;
style.lineHeight = this._object._lineHeight > 0 ? this._object._lineHeight : undefined;
style.stroke = gdjs.rgbToHexNumber(
this._object._outlineColor[0],
this._object._outlineColor[1],

View File

@@ -22,6 +22,7 @@ namespace gdjs {
text: string;
textAlignment: string;
verticalTextAlignment: string;
lineHeight: number;
isOutlineEnabled: boolean;
outlineThickness: float;
@@ -34,6 +35,7 @@ namespace gdjs {
shadowDistance: float;
shadowAngle: float;
shadowBlurRadius: float;
lineHeight: float;
};
};
@@ -62,6 +64,7 @@ namespace gdjs {
sha: float;
shb: float;
pad: integer;
lh: float;
};
export type TextObjectNetworkSyncData = ObjectNetworkSyncData &
@@ -89,6 +92,7 @@ namespace gdjs {
_wrapping: boolean = false;
// A wrapping of 1 makes games crash on Firefox
_wrappingWidth: float = 100;
_lineHeight: float;
_isOutlineEnabled: boolean;
_outlineThickness: float;
@@ -102,6 +106,7 @@ namespace gdjs {
_shadowBlur: float;
_padding: integer = 5;
_lineHeight: float = 0;
_str: string;
_renderer: gdjs.TextRuntimeObjectRenderer;
@@ -128,6 +133,7 @@ namespace gdjs {
this._str = content.text;
this._textAlign = content.textAlignment || 'left';
this._verticalTextAlignment = content.verticalTextAlignment || 'top';
this._lineHeight = content.lineHeight || 0;
this._isOutlineEnabled = content.isOutlineEnabled;
this._outlineThickness = content.outlineThickness;
@@ -211,6 +217,9 @@ namespace gdjs {
if (oldContent.shadowBlurRadius !== newContent.shadowBlurRadius) {
this.setShadowBlurRadius(newContent.shadowBlurRadius);
}
if (oldContent.lineHeight !== newContent.lineHeight) {
this.setLineHeight(newContent.lineHeight);
}
return true;
}
@@ -239,6 +248,7 @@ namespace gdjs {
sha: this._shadowAngle,
shb: this._shadowBlur,
pad: this._padding,
lh: this._lineHeight,
};
}
@@ -315,6 +325,9 @@ namespace gdjs {
if (networkSyncData.pad !== undefined) {
this.setPadding(networkSyncData.pad);
}
if (networkSyncData.lh !== undefined) {
this.setLineHeight(networkSyncData.lh);
}
}
override getRendererObject() {
@@ -929,6 +942,23 @@ namespace gdjs {
this._padding = value;
this._renderer.updateStyle();
}
/**
* Get the line height of the text object.
* @return the line height
*/
getLineHeight(): number {
return this._lineHeight;
}
/**
* Set the line height of the text object.
* @param value the line height
*/
setLineHeight(value: float): void {
this._lineHeight = value;
this._renderer.updateStyle();
}
}
gdjs.registerObject('TextObject::Text', gdjs.TextRuntimeObject);
}

View File

@@ -218,6 +218,24 @@ export default class TextEditor extends React.Component<EditorProps, void> {
floatingLabelText={<Trans>Choose a font</Trans>}
hintText={<Trans>Choose a font</Trans>}
/>
<Line noMargin>
<SemiControlledTextField
floatingLabelText={<Trans>Line height (0 = default)</Trans>}
floatingLabelFixed
id="text-object-line-height"
type="number"
commitOnBlur
fullWidth
value={textObjectConfiguration.getLineHeight()}
onChange={value => {
textObjectConfiguration.setLineHeight(
parseInt(value, 10) || 0
);
this.forceUpdate();
this.props.onSizeUpdated();
}}
/>
</Line>
<Line noMargin>
<SemiControlledTextField
floatingLabelText={<Trans>Initial text to display</Trans>}

View File

@@ -21,6 +21,7 @@ export default class RenderedTextInstance extends RenderedInstance {
_color: string = '0;0;0';
_textAlignment: string = 'left';
_verticalTextAlignment: string = 'top';
_lineHeight: number = 1;
_isOutlineEnabled = false;
_outlineColor = '255;255;255';
@@ -32,6 +33,7 @@ export default class RenderedTextInstance extends RenderedInstance {
_shadowColor = '0;0;0';
_shadowOpacity = 127;
_shadowBlurRadius = 2;
_lineHeight = 0;
constructor(
project: gdProject,
@@ -112,6 +114,7 @@ export default class RenderedTextInstance extends RenderedInstance {
textObjectConfiguration.getShadowOpacity() !== this._shadowOpacity ||
textObjectConfiguration.getShadowBlurRadius() !==
this._shadowBlurRadius ||
textObjectConfiguration.getLineHeight() !== this._lineHeight ||
this._instance.hasCustomSize() !== this._wrapping ||
(this.getCustomWidth() !== this._wrappingWidth && this._wrapping)
) {
@@ -135,6 +138,7 @@ export default class RenderedTextInstance extends RenderedInstance {
this._wrapping = this._instance.hasCustomSize();
this._wrappingWidth = this.getCustomWidth();
this._lineHeight = textObjectConfiguration.getLineHeight();
this._styleFontDirty = true;
}
@@ -172,6 +176,7 @@ export default class RenderedTextInstance extends RenderedInstance {
style.wordWrapWidth = this._wrappingWidth <= 1 ? 1 : this._wrappingWidth;
style.breakWords = true;
style.align = this._textAlignment;
style.lineHeight = this._lineHeight > 0 ? this._lineHeight : undefined;
style.stroke = rgbStringToHexNumber(this._outlineColor);
style.strokeThickness = this._isOutlineEnabled