mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
9 Commits
d474c2a47e
...
tentative-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ef85ba4468 | ||
![]() |
e775aeecec | ||
![]() |
3c7392c5a7 | ||
![]() |
fbc01c892d | ||
![]() |
8acc018df0 | ||
![]() |
99205be5cf | ||
![]() |
f4c42abd28 | ||
![]() |
196a9ea3b6 | ||
![]() |
afefe54325 |
@@ -621,14 +621,15 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
|
||||
argOutput = GenerateGetBehaviorNameCode(parameter);
|
||||
} else if (metadata.type == "key") {
|
||||
argOutput = "\"" + ConvertToString(parameter) + "\"";
|
||||
} else if (metadata.type == "password" || // Deprecated
|
||||
metadata.type ==
|
||||
"musicfile" || // Should be renamed "largeAudioResource"
|
||||
metadata.type ==
|
||||
"soundfile" || // Should be renamed "audioResource"
|
||||
metadata.type == "police" || // Should be renamed "fontResource"
|
||||
} else if (metadata.type == "audioResource" ||
|
||||
metadata.type == "bitmapFontResource" ||
|
||||
metadata.type == "imageResource") {
|
||||
metadata.type == "fontResource" ||
|
||||
metadata.type == "imageResource" ||
|
||||
metadata.type == "jsonResource" ||
|
||||
metadata.type == "videoResource" ||
|
||||
// Deprecated, old parameter names:
|
||||
metadata.type == "password" || metadata.type == "musicfile" ||
|
||||
metadata.type == "soundfile" || metadata.type == "police") {
|
||||
argOutput = "\"" + ConvertToString(parameter) + "\"";
|
||||
} else if (metadata.type == "mouse") {
|
||||
argOutput = "\"" + ConvertToString(parameter) + "\"";
|
||||
|
@@ -83,14 +83,16 @@ module.exports = {
|
||||
.setValue(objectContent.bitmapFontResourceName)
|
||||
.setType('resource')
|
||||
.addExtraInfo('bitmapFont') //fnt or xml files
|
||||
.setLabel(_('Bitmap Font'));
|
||||
.setLabel(_('Bitmap Font'))
|
||||
.setGroup(_("Font"));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('textureAtlasResourceName')
|
||||
.setValue(objectContent.textureAtlasResourceName)
|
||||
.setType('resource')
|
||||
.addExtraInfo('image')
|
||||
.setLabel(_('Bitmap atlas image'));
|
||||
.setLabel(_('Bitmap atlas image'))
|
||||
.setGroup(_("Font"));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('scale')
|
||||
@@ -102,7 +104,8 @@ module.exports = {
|
||||
.getOrCreate('tint')
|
||||
.setValue(objectContent.tint)
|
||||
.setType('color')
|
||||
.setLabel(_('Font tint'));
|
||||
.setLabel(_('Font tint'))
|
||||
.setGroup(_("Font"));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('wordWrap')
|
||||
|
654
Extensions/TextInput/JsExtension.js
Normal file
654
Extensions/TextInput/JsExtension.js
Normal file
@@ -0,0 +1,654 @@
|
||||
// @flow
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
* ℹ️ Changes in this file are watched and automatically imported if the editor
|
||||
* is running. You can also manually run `node import-GDJS-Runtime.js` (in newIDE/app/scripts).
|
||||
*
|
||||
* The file must be named "JsExtension.js", otherwise GDevelop won't load it.
|
||||
* ⚠️ If you make a change and the extension is not loaded, open the developer console
|
||||
* and search for any errors.
|
||||
*
|
||||
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
|
||||
*/
|
||||
|
||||
/*::
|
||||
// Import types to allow Flow to do static type checking on this file.
|
||||
// Extensions declaration are typed using Flow (like the editor), but the files
|
||||
// for the game engine are checked with TypeScript annotations.
|
||||
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension.setExtensionInformation(
|
||||
'TextInput',
|
||||
_('Text Input'),
|
||||
_('A text field the player can type text into.'),
|
||||
'Florian Rival',
|
||||
'MIT'
|
||||
);
|
||||
|
||||
const textInputObject = new gd.ObjectJsImplementation();
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object
|
||||
textInputObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
if (propertyName === 'initialValue') {
|
||||
objectContent.initialValue = newValue;
|
||||
return true;
|
||||
} else if (propertyName === 'placeholder') {
|
||||
objectContent.placeholder = newValue;
|
||||
return true;
|
||||
} else if (propertyName === 'fontResourceName') {
|
||||
objectContent.fontResourceName = newValue;
|
||||
return true;
|
||||
} else if (propertyName === 'fontSize') {
|
||||
objectContent.fontSize = Math.max(1, parseFloat(newValue));
|
||||
return true;
|
||||
} else if (propertyName === 'inputType') {
|
||||
objectContent.inputType = newValue;
|
||||
return true;
|
||||
} else if (propertyName === 'textColor') {
|
||||
objectContent.textColor = newValue;
|
||||
return true;
|
||||
} else if (propertyName === 'fillColor') {
|
||||
objectContent.fillColor = newValue;
|
||||
return true;
|
||||
} else if (propertyName === 'fillOpacity') {
|
||||
objectContent.fillOpacity = Math.max(
|
||||
0,
|
||||
Math.min(255, parseFloat(newValue))
|
||||
);
|
||||
return true;
|
||||
} else if (propertyName === 'borderColor') {
|
||||
objectContent.borderColor = newValue;
|
||||
return true;
|
||||
} else if (propertyName === 'borderOpacity') {
|
||||
objectContent.borderOpacity = Math.max(
|
||||
0,
|
||||
Math.min(255, parseFloat(newValue))
|
||||
);
|
||||
return true;
|
||||
} else if (propertyName === 'borderWidth') {
|
||||
objectContent.borderWidth = Math.max(0, parseFloat(newValue));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object
|
||||
textInputObject.getProperties = function (objectContent) {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('initialValue')
|
||||
.setValue(objectContent.initialValue)
|
||||
.setType('string')
|
||||
.setLabel(_('Initial value'))
|
||||
.setGroup(_('Content'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('placeholder')
|
||||
.setValue(objectContent.placeholder)
|
||||
.setType('string')
|
||||
.setLabel(_('Placeholder'))
|
||||
.setGroup(_('Content'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('fontResourceName')
|
||||
.setValue(objectContent.fontResourceName || '')
|
||||
.setType('resource')
|
||||
.addExtraInfo('font')
|
||||
.setLabel(_('Font'))
|
||||
.setGroup(_('Font'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('fontSize')
|
||||
.setValue((objectContent.fontSize || 20).toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Font size (px)'))
|
||||
.setGroup(_('Font'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('inputType')
|
||||
.setValue(objectContent.inputType || '')
|
||||
.setType('choice')
|
||||
.addExtraInfo('text')
|
||||
.addExtraInfo('text area')
|
||||
.addExtraInfo('email')
|
||||
.addExtraInfo('password')
|
||||
.addExtraInfo('number')
|
||||
.addExtraInfo('telephone number')
|
||||
.addExtraInfo('url')
|
||||
.addExtraInfo('search')
|
||||
.setLabel(_('Input type'))
|
||||
.setGroup(_('Type'))
|
||||
.setDescription(
|
||||
_(
|
||||
'By default, a "text" is single line. Choose "text area" to allow multiple lines to be entered.'
|
||||
)
|
||||
);
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('textColor')
|
||||
.setValue(objectContent.textColor || '0;0;0')
|
||||
.setType('color')
|
||||
.setLabel(_('Text color'))
|
||||
.setGroup(_('Field appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('fillColor')
|
||||
.setValue(objectContent.fillColor || '255;255;255')
|
||||
.setType('color')
|
||||
.setLabel(_('Fill color'))
|
||||
.setGroup(_('Field appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('fillOpacity')
|
||||
.setValue((objectContent.fillOpacity || 255).toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Fill opacity'))
|
||||
.setGroup(_('Field appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('borderColor')
|
||||
.setValue(objectContent.borderColor || '0;0;0')
|
||||
.setType('color')
|
||||
.setLabel(_('Border color'))
|
||||
.setGroup(_('Field appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('borderOpacity')
|
||||
.setValue((objectContent.borderOpacity || 255).toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Border opacity'))
|
||||
.setGroup(_('Field appearance'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('borderWidth')
|
||||
.setValue((objectContent.borderWidth || 0).toString())
|
||||
.setType('number')
|
||||
.setLabel(_('Border width'))
|
||||
.setGroup(_('Field appearance'));
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
textInputObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
initialValue: '',
|
||||
placeholder: 'Touch to start typing',
|
||||
fontResourceName: '',
|
||||
fontSize: 20,
|
||||
inputType: 'text',
|
||||
textColor: '0;0;0',
|
||||
fillColor: '255;255;255',
|
||||
fillOpacity: 255,
|
||||
borderColor: '0;0;0',
|
||||
borderOpacity: 255,
|
||||
borderWidth: 1,
|
||||
})
|
||||
);
|
||||
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object
|
||||
textInputObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
propertyName,
|
||||
newValue,
|
||||
project,
|
||||
layout
|
||||
) {
|
||||
if (propertyName === 'initialValue') {
|
||||
instance.setRawStringProperty('initialValue', newValue);
|
||||
return true;
|
||||
} else if (propertyName === 'placeholder') {
|
||||
instance.setRawStringProperty('placeholder', newValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object
|
||||
textInputObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance,
|
||||
project,
|
||||
layout
|
||||
) {
|
||||
const instanceProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
instanceProperties
|
||||
.getOrCreate('initialValue')
|
||||
.setValue(instance.getRawStringProperty('initialValue'))
|
||||
.setType('string')
|
||||
.setLabel(_('Initial value'));
|
||||
instanceProperties
|
||||
.getOrCreate('placeholder')
|
||||
.setValue(instance.getRawStringProperty('placeholder'))
|
||||
.setType('string')
|
||||
.setLabel(_('Placeholder'));
|
||||
|
||||
return instanceProperties;
|
||||
};
|
||||
|
||||
const object = extension
|
||||
.addObject(
|
||||
'TextInputObject',
|
||||
_('Text input'),
|
||||
_('A text field the player can type text into.'),
|
||||
'JsPlatform/Extensions/text_input.svg',
|
||||
textInputObject
|
||||
)
|
||||
.setIncludeFile('Extensions/TextInput/textinputruntimeobject.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/TextInput/textinputruntimeobject-pixi-renderer.js'
|
||||
);
|
||||
|
||||
// Properties expressions/conditions/actions:
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'string',
|
||||
'Text',
|
||||
_('Text'),
|
||||
_('the text'),
|
||||
_('the text'),
|
||||
'',
|
||||
'res/conditions/text24.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.useStandardParameters('string')
|
||||
.setFunctionName('setString')
|
||||
.setGetter('getString');
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'string',
|
||||
'Placeholder',
|
||||
_('Placeholder'),
|
||||
_('the placeholder'),
|
||||
_('the placeholder'),
|
||||
'',
|
||||
'res/conditions/text24.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.useStandardParameters('string')
|
||||
.setFunctionName('setPlaceholder')
|
||||
.setGetter('getPlaceholder');
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'Font size',
|
||||
_('Font size'),
|
||||
_('the font size'),
|
||||
_('the font size'),
|
||||
_('Font'),
|
||||
'res/conditions/opacity24.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.useStandardParameters('number')
|
||||
.setFunctionName('setFontSize')
|
||||
.setGetter('getFontSize');
|
||||
|
||||
object
|
||||
.addExpressionAndCondition(
|
||||
'string',
|
||||
'FontResourceName',
|
||||
_('Font name'),
|
||||
_('the font name'),
|
||||
_('the font name'),
|
||||
_('Font'),
|
||||
'res/conditions/font24.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.useStandardParameters('string')
|
||||
.setFunctionName('getFontResourceName');
|
||||
|
||||
// TODO: could this be merged with the previous expression and condition?
|
||||
object
|
||||
.addScopedAction(
|
||||
'SetFontResourceName',
|
||||
_('Font name'),
|
||||
_('Set the font of the object.'),
|
||||
_('Set the font of _PARAM0_ to _PARAM1_'),
|
||||
_('Font'),
|
||||
'res/actions/font24.png',
|
||||
'res/actions/font.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'TextInputObject', false)
|
||||
.addParameter('fontResource', _('Font resource name'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setFontResourceName');
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'string',
|
||||
'InputType',
|
||||
_('Input type'),
|
||||
_('the input type'),
|
||||
_('the input type'),
|
||||
_('Type'),
|
||||
'res/conditions/text24.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.useStandardParameters('string') // TODO: stringWithSelector?
|
||||
.setFunctionName('setInputType')
|
||||
.setGetter('getInputType');
|
||||
|
||||
object
|
||||
.addScopedAction(
|
||||
'SetTextColor',
|
||||
_('Text color'),
|
||||
_('Set the text color of the object.'),
|
||||
_('Set the text color of _PARAM0_ to _PARAM1_'),
|
||||
_('Field appearance'),
|
||||
'res/actions/color24.png',
|
||||
'res/actions/color.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.addParameter('color', _('Color'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setTextColor');
|
||||
|
||||
object
|
||||
.addScopedAction(
|
||||
'SetFillColor',
|
||||
_('Fill color'),
|
||||
_('Set the fill color of the object.'),
|
||||
_('Set the fill color of _PARAM0_ to _PARAM1_'),
|
||||
_('Field appearance'),
|
||||
'res/actions/color24.png',
|
||||
'res/actions/color.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.addParameter('color', _('Color'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setFillColor');
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'FillOpacity',
|
||||
_('Fill opacity'),
|
||||
_('the fill opacity, between 0 (fully transparent) and 255 (opaque)'),
|
||||
_('the fill opacity'),
|
||||
_('Field appearance'),
|
||||
'res/conditions/opacity24.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.useStandardParameters('number')
|
||||
.setFunctionName('setFillOpacity')
|
||||
.setGetter('getFillOpacity');
|
||||
|
||||
object
|
||||
.addScopedAction(
|
||||
'SetBorderColor',
|
||||
_('Border color'),
|
||||
_('Set the border color of the object.'),
|
||||
_('Set the border color of _PARAM0_ to _PARAM1_'),
|
||||
_('Field appearance'),
|
||||
'res/actions/color24.png',
|
||||
'res/actions/color.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.addParameter('color', _('Color'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBorderColor');
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'BorderOpacity',
|
||||
_('Border opacity'),
|
||||
_('the border opacity, between 0 (fully transparent) and 255 (opaque)'),
|
||||
_('the border opacity'),
|
||||
_('Field appearance'),
|
||||
'res/conditions/opacity24.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.useStandardParameters('number')
|
||||
.setFunctionName('setBorderOpacity')
|
||||
.setGetter('getBorderOpacity');
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'BorderWidth',
|
||||
_('Border width'),
|
||||
_('the border width'),
|
||||
_('the border width'),
|
||||
_('Field appearance'),
|
||||
'res/conditions/outlineSize24.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.useStandardParameters('number')
|
||||
.setFunctionName('setBorderWidth')
|
||||
.setGetter('getBorderWidth');
|
||||
|
||||
// TODO: expressions for colors?
|
||||
|
||||
// Other expressions/conditions/actions:
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'Opacity',
|
||||
_('Opacity'),
|
||||
_('the opacity, between 0 (fully transparent) and 255 (opaque)'),
|
||||
_('the opacity'),
|
||||
'',
|
||||
'res/conditions/opacity24.png'
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.useStandardParameters('number')
|
||||
.setFunctionName('setOpacity')
|
||||
.setGetter('getOpacity');
|
||||
|
||||
return extension;
|
||||
},
|
||||
/**
|
||||
* You can optionally add sanity tests that will check the basic working
|
||||
* of your extension behaviors/objects by instanciating behaviors/objects
|
||||
* and setting the property to a given value.
|
||||
*
|
||||
* If you don't have any tests, you can simply return an empty array.
|
||||
*
|
||||
* But it is recommended to create tests for the behaviors/objects properties you created
|
||||
* to avoid mistakes.
|
||||
*/
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
* Register editors for objects.
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerEditorConfigurations: function (
|
||||
objectsEditorService /*: ObjectsEditorService */
|
||||
) {
|
||||
objectsEditorService.registerEditorConfiguration(
|
||||
'TextInput::TextInputObject',
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
|
||||
helpPagePath: '/extensions/extend-gdevelop', // TODO
|
||||
})
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Register renderers for instance of objects on the scene editor.
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerInstanceRenderers: function (
|
||||
objectsRenderingService /*: ObjectsRenderingService */
|
||||
) {
|
||||
const RenderedInstance = objectsRenderingService.RenderedInstance;
|
||||
const PIXI = objectsRenderingService.PIXI;
|
||||
|
||||
const DEFAULT_WIDTH = 300;
|
||||
const DEFAULT_HEIGHT = 30;
|
||||
const TEXT_MASK_PADDING = 2;
|
||||
|
||||
class RenderedTextInputObjectInstance extends RenderedInstance {
|
||||
_pixiText;
|
||||
_pixiTextMask;
|
||||
_pixiGraphics;
|
||||
_fontResourceName = '';
|
||||
_finalTextColor = 0x0;
|
||||
|
||||
constructor(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObject,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
) {
|
||||
super(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObject,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
|
||||
this._pixiGraphics = new PIXI.Graphics();
|
||||
this._pixiTextMask = new PIXI.Graphics();
|
||||
this._pixiText = new PIXI.Text(' ', {
|
||||
align: 'left',
|
||||
fontSize: 20,
|
||||
});
|
||||
this._pixiText.mask = this._pixiTextMask;
|
||||
this._pixiObject = new PIXI.Container();
|
||||
this._pixiObject.addChild(this._pixiGraphics);
|
||||
this._pixiObject.addChild(this._pixiTextMask);
|
||||
this._pixiObject.addChild(this._pixiText);
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
this.update();
|
||||
}
|
||||
|
||||
static getThumbnail(project, resourcesLoader, object) {
|
||||
return 'JsPlatform/Extensions/text_input.svg';
|
||||
}
|
||||
|
||||
update() {
|
||||
const instance = this._instance;
|
||||
const properties = this._associatedObject.getProperties();
|
||||
|
||||
const placeholder =
|
||||
instance.getRawStringProperty('placeholder') ||
|
||||
properties.get('placeholder').getValue();
|
||||
const initialValue =
|
||||
instance.getRawStringProperty('initialValue') ||
|
||||
properties.get('initialValue').getValue();
|
||||
const hasInitialValue = initialValue !== '';
|
||||
this._pixiText.text = hasInitialValue ? initialValue : placeholder;
|
||||
|
||||
const textColor = properties.get('textColor').getValue();
|
||||
const finalTextColor = hasInitialValue
|
||||
? objectsRenderingService.rgbOrHexToHexNumber(textColor)
|
||||
: 0x888888;
|
||||
if (this._finalTextColor !== finalTextColor) {
|
||||
this._finalTextColor = finalTextColor;
|
||||
this._pixiText.style.fill = finalTextColor;
|
||||
this._pixiText.dirty = true;
|
||||
}
|
||||
|
||||
const fontSize = parseFloat(properties.get('fontSize').getValue());
|
||||
if (this._pixiText.style.fontSize !== fontSize) {
|
||||
this._pixiText.style.fontSize = fontSize;
|
||||
this._pixiText.dirty = true;
|
||||
}
|
||||
|
||||
const fontResourceName = properties.get('fontResourceName').getValue();
|
||||
if (this._fontResourceName !== fontResourceName) {
|
||||
this._fontResourceName = fontResourceName;
|
||||
|
||||
this._pixiResourcesLoader
|
||||
.loadFontFamily(this._project, fontResourceName)
|
||||
.then((fontFamily) => {
|
||||
this._pixiText.style.fontFamily = fontFamily;
|
||||
this._pixiText.dirty = true;
|
||||
})
|
||||
.catch((err) => {
|
||||
// Ignore errors
|
||||
console.warn(
|
||||
'Unable to load font family for RenderedTextInputObjectInstance',
|
||||
err
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this._pixiObject.position.x = instance.getX();
|
||||
this._pixiObject.position.y = instance.getY();
|
||||
|
||||
let width = DEFAULT_WIDTH;
|
||||
let height = DEFAULT_HEIGHT;
|
||||
if (instance.hasCustomSize()) {
|
||||
width = instance.getCustomWidth();
|
||||
height = instance.getCustomHeight();
|
||||
}
|
||||
|
||||
const borderWidth = parseFloat(properties.get('borderWidth').getValue()) || 0;
|
||||
|
||||
const textOffset = borderWidth + TEXT_MASK_PADDING;
|
||||
this._pixiTextMask.clear();
|
||||
this._pixiTextMask.drawRect(
|
||||
textOffset,
|
||||
textOffset,
|
||||
width - 2 * textOffset,
|
||||
height - 2 * textOffset
|
||||
);
|
||||
|
||||
const isTextArea =
|
||||
properties.get('inputType').getValue() === 'text area';
|
||||
|
||||
this._pixiText.position.x = textOffset;
|
||||
this._pixiText.position.y = isTextArea
|
||||
? textOffset
|
||||
: height / 2 - this._pixiText.height / 2;
|
||||
|
||||
const fillColor = properties.get('fillColor').getValue();
|
||||
const fillOpacity = parseFloat(properties.get('fillOpacity').getValue());
|
||||
const borderColor = properties.get('borderColor').getValue();
|
||||
const borderOpacity = parseFloat(properties.get('borderOpacity').getValue());
|
||||
|
||||
this._pixiGraphics.clear();
|
||||
this._pixiGraphics.lineStyle(
|
||||
borderWidth,
|
||||
objectsRenderingService.rgbOrHexToHexNumber(borderColor),
|
||||
borderOpacity / 255
|
||||
);
|
||||
this._pixiGraphics.beginFill(
|
||||
objectsRenderingService.rgbOrHexToHexNumber(fillColor),
|
||||
fillOpacity / 255
|
||||
);
|
||||
this._pixiGraphics.drawRect(0, 0, width, height);
|
||||
this._pixiGraphics.endFill();
|
||||
}
|
||||
|
||||
getDefaultWidth() {
|
||||
return DEFAULT_WIDTH;
|
||||
}
|
||||
|
||||
getDefaultHeight() {
|
||||
return DEFAULT_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
objectsRenderingService.registerInstanceRenderer(
|
||||
'TextInput::TextInputObject',
|
||||
RenderedTextInputObjectInstance
|
||||
);
|
||||
},
|
||||
};
|
204
Extensions/TextInput/textinputruntimeobject-pixi-renderer.ts
Normal file
204
Extensions/TextInput/textinputruntimeobject-pixi-renderer.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
namespace gdjs {
|
||||
const userFriendlyToHtmlInputTypes = {
|
||||
text: 'text',
|
||||
email: 'email',
|
||||
password: 'password',
|
||||
number: 'number',
|
||||
'telephone number': 'tel',
|
||||
url: 'url',
|
||||
search: 'search',
|
||||
};
|
||||
|
||||
const formatRgbAndOpacityToCssRgba = (
|
||||
rgbColor: [float, float, float],
|
||||
opacity: float
|
||||
) => {
|
||||
return (
|
||||
'rgba(' +
|
||||
rgbColor[0] +
|
||||
',' +
|
||||
rgbColor[1] +
|
||||
',' +
|
||||
rgbColor[2] +
|
||||
',' +
|
||||
opacity / 255 +
|
||||
')'
|
||||
);
|
||||
};
|
||||
|
||||
class TextInputRuntimeObjectPixiRenderer {
|
||||
private _object: gdjs.TextInputRuntimeObject;
|
||||
private _input: HTMLInputElement | HTMLTextAreaElement;
|
||||
private _runtimeScene: gdjs.RuntimeScene;
|
||||
private _runtimeGame: gdjs.RuntimeGame;
|
||||
|
||||
constructor(runtimeObject: gdjs.TextInputRuntimeObject) {
|
||||
this._object = runtimeObject;
|
||||
this._runtimeScene = runtimeObject.getRuntimeScene();
|
||||
this._runtimeGame = this._runtimeScene.getGame();
|
||||
|
||||
this._input = this._createElement();
|
||||
this._runtimeGame
|
||||
.getRenderer()
|
||||
.getDomElementContainer()!
|
||||
.appendChild(this._input);
|
||||
}
|
||||
|
||||
_createElement() {
|
||||
const isTextArea = this._object.getInputType() === 'text area';
|
||||
this._input = document.createElement(isTextArea ? 'textarea' : 'input');
|
||||
this._input.style.border = '1px solid black';
|
||||
this._input.style.borderRadius = '0px';
|
||||
this._input.style.backgroundColor = 'white';
|
||||
this._input.style.position = 'absolute';
|
||||
this._input.style.resize = 'none';
|
||||
this._input.style.outline = 'none';
|
||||
this._input.style.pointerEvents = 'auto'; // Element can be clicked/touched.
|
||||
|
||||
this._input.addEventListener('input', () => {
|
||||
this._object.onRendererInputValueChanged(this._input.value);
|
||||
});
|
||||
this._input.addEventListener('touchstart', () => {
|
||||
// Focus directly when touching the input on touchscreens.
|
||||
if (document.activeElement !== this._input) this._input.focus();
|
||||
});
|
||||
|
||||
this.updateString();
|
||||
this.updateFont();
|
||||
this.updatePlaceholder();
|
||||
this.updateOpacity();
|
||||
this.updateInputType();
|
||||
this.updateTextColor();
|
||||
this.updateFillColorAndOpacity();
|
||||
this.updateBorderColorAndOpacity();
|
||||
this.updateBorderWidth();
|
||||
|
||||
return this._input;
|
||||
}
|
||||
|
||||
_destroyElement() {
|
||||
if (!this._input) return;
|
||||
this._input.remove();
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
this._destroyElement();
|
||||
}
|
||||
|
||||
updatePreRender() {
|
||||
const layer = this._runtimeScene.getLayer(this._object.getLayer());
|
||||
const runtimeGame = this._runtimeScene.getGame();
|
||||
const runtimeGameRenderer = runtimeGame.getRenderer();
|
||||
const topLeftCanvasCoordinates = layer.convertInverseCoords(
|
||||
this._object.x,
|
||||
this._object.y,
|
||||
0
|
||||
);
|
||||
const bottomRightCanvasCoordinates = layer.convertInverseCoords(
|
||||
this._object.x + this._object.getWidth(),
|
||||
this._object.y + this._object.getHeight(),
|
||||
0
|
||||
);
|
||||
|
||||
// Hide the input entirely if not visible at all
|
||||
const isOutsideCanvas =
|
||||
bottomRightCanvasCoordinates[0] < 0 ||
|
||||
bottomRightCanvasCoordinates[1] < 0 ||
|
||||
topLeftCanvasCoordinates[0] > runtimeGame.getGameResolutionWidth() ||
|
||||
topLeftCanvasCoordinates[1] > runtimeGame.getGameResolutionHeight();
|
||||
if (isOutsideCanvas) {
|
||||
this._input.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
this._input.style.display = 'initial';
|
||||
|
||||
// Position the input on the container on top of the canvas
|
||||
const topLeftPageCoordinates = runtimeGameRenderer.convertCanvasToDomElementContainerCoords(
|
||||
topLeftCanvasCoordinates
|
||||
);
|
||||
const bottomRightPageCoordinates = runtimeGameRenderer.convertCanvasToDomElementContainerCoords(
|
||||
bottomRightCanvasCoordinates
|
||||
);
|
||||
|
||||
const widthInContainer =
|
||||
bottomRightPageCoordinates[0] - topLeftPageCoordinates[0];
|
||||
const heightInContainer =
|
||||
bottomRightPageCoordinates[1] - topLeftPageCoordinates[1];
|
||||
|
||||
this._input.style.left = topLeftPageCoordinates[0] + 'px';
|
||||
this._input.style.top = topLeftPageCoordinates[1] + 'px';
|
||||
this._input.style.width = widthInContainer + 'px';
|
||||
this._input.style.height = heightInContainer + 'px';
|
||||
|
||||
// Automatically adjust the font size to follow the game scale.
|
||||
this._input.style.fontSize =
|
||||
this._object.getFontSize() *
|
||||
runtimeGameRenderer.getCanvasToDomElementContainerHeightScale() +
|
||||
'px';
|
||||
}
|
||||
|
||||
updateString() {
|
||||
this._input.value = this._object.getString();
|
||||
}
|
||||
|
||||
updatePlaceholder() {
|
||||
this._input.placeholder = this._object.getPlaceholder();
|
||||
}
|
||||
|
||||
updateFont() {
|
||||
this._input.style.fontFamily = this._runtimeScene
|
||||
.getGame()
|
||||
.getFontManager()
|
||||
.getFontFamily(this._object.getFontResourceName());
|
||||
}
|
||||
|
||||
updateOpacity() {
|
||||
this._input.style.opacity = '' + this._object.getOpacity() / 255;
|
||||
}
|
||||
|
||||
updateInputType() {
|
||||
const isTextArea = this._input instanceof HTMLTextAreaElement;
|
||||
const shouldBeTextArea = this._object.getInputType() === 'text area';
|
||||
if (isTextArea !== shouldBeTextArea) {
|
||||
this._destroyElement();
|
||||
this._createElement();
|
||||
|
||||
this._runtimeGame
|
||||
.getRenderer()
|
||||
.getDomElementContainer()!
|
||||
.appendChild(this._input);
|
||||
}
|
||||
|
||||
const newType =
|
||||
userFriendlyToHtmlInputTypes[this._object.getInputType()] || 'text';
|
||||
this._input.setAttribute('type', newType);
|
||||
}
|
||||
|
||||
updateTextColor() {
|
||||
this._input.style.color = formatRgbAndOpacityToCssRgba(
|
||||
this._object._getRawTextColor(),
|
||||
255
|
||||
);
|
||||
}
|
||||
|
||||
updateFillColorAndOpacity() {
|
||||
this._input.style.backgroundColor = formatRgbAndOpacityToCssRgba(
|
||||
this._object._getRawFillColor(),
|
||||
this._object.getFillOpacity()
|
||||
);
|
||||
}
|
||||
|
||||
updateBorderColorAndOpacity() {
|
||||
this._input.style.borderColor = formatRgbAndOpacityToCssRgba(
|
||||
this._object._getRawBorderColor(),
|
||||
this._object.getBorderOpacity()
|
||||
);
|
||||
}
|
||||
updateBorderWidth() {
|
||||
this._input.style.borderWidth = this._object.getBorderWidth() + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
export const TextInputRuntimeObjectRenderer = TextInputRuntimeObjectPixiRenderer;
|
||||
export type TextInputRuntimeObjectRenderer = TextInputRuntimeObjectPixiRenderer;
|
||||
}
|
394
Extensions/TextInput/textinputruntimeobject.ts
Normal file
394
Extensions/TextInput/textinputruntimeobject.ts
Normal file
@@ -0,0 +1,394 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Text input object');
|
||||
|
||||
const supportedInputTypes = [
|
||||
'text',
|
||||
'email',
|
||||
'password',
|
||||
'number',
|
||||
'telephone number',
|
||||
'url',
|
||||
'search',
|
||||
'text area',
|
||||
] as const;
|
||||
|
||||
type SupportedInputType = typeof supportedInputTypes[number];
|
||||
|
||||
const parseInputType = (potentialInputType: string): SupportedInputType => {
|
||||
const lowercasedNewInputType = potentialInputType.toLowerCase();
|
||||
|
||||
// @ts-ignore - we're actually checking that this value is correct.
|
||||
if (supportedInputTypes.includes(lowercasedNewInputType))
|
||||
return potentialInputType as SupportedInputType;
|
||||
|
||||
return 'text';
|
||||
};
|
||||
|
||||
/** Base parameters for {@link gdjs.TextInputRuntimeObject} */
|
||||
export interface TextInputObjectData extends ObjectData {
|
||||
/** The base parameters of the TextInput */
|
||||
content: {
|
||||
initialValue: string;
|
||||
placeholder: string;
|
||||
fontResourceName: string;
|
||||
fontSize: float;
|
||||
inputType: SupportedInputType;
|
||||
textColor: string;
|
||||
fillColor: string;
|
||||
fillOpacity: float;
|
||||
borderColor: string;
|
||||
borderOpacity: float;
|
||||
borderWidth: float;
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_WIDTH = 300;
|
||||
const DEFAULT_HEIGHT = 30;
|
||||
|
||||
/**
|
||||
* Shows a text input on the screen the player can type text into.
|
||||
*/
|
||||
export class TextInputRuntimeObject extends gdjs.RuntimeObject {
|
||||
private _string: string;
|
||||
private _placeholder: string;
|
||||
private opacity: float = 255;
|
||||
private _width: float = DEFAULT_WIDTH;
|
||||
private _height: float = DEFAULT_HEIGHT;
|
||||
private _fontResourceName: string;
|
||||
private _fontSize: float;
|
||||
private _inputType: SupportedInputType;
|
||||
private _textColor: [float, float, float];
|
||||
private _fillColor: [float, float, float];
|
||||
private _fillOpacity: float;
|
||||
private _borderColor: [float, float, float];
|
||||
private _borderOpacity: float;
|
||||
private _borderWidth: float;
|
||||
|
||||
_renderer: TextInputRuntimeObjectRenderer;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
objectData: TextInputObjectData
|
||||
) {
|
||||
super(runtimeScene, objectData);
|
||||
|
||||
this._string = objectData.content.initialValue;
|
||||
this._placeholder = objectData.content.placeholder;
|
||||
this._fontResourceName = objectData.content.fontResourceName;
|
||||
this._fontSize = objectData.content.fontSize || 20;
|
||||
this._inputType = parseInputType(objectData.content.inputType);
|
||||
this._textColor = gdjs.rgbOrHexToRGBColor(objectData.content.textColor);
|
||||
this._fillColor = gdjs.rgbOrHexToRGBColor(objectData.content.fillColor);
|
||||
this._fillOpacity = objectData.content.fillOpacity;
|
||||
this._borderColor = gdjs.rgbOrHexToRGBColor(
|
||||
objectData.content.borderColor
|
||||
);
|
||||
this._borderOpacity = objectData.content.borderOpacity;
|
||||
this._borderWidth = objectData.content.borderWidth;
|
||||
|
||||
this._renderer = new gdjs.TextInputRuntimeObjectRenderer(this);
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
return {}; // TODO: this will break when changing layer. Instead, return null but still call updatePreRender?
|
||||
}
|
||||
|
||||
updateFromObjectData(
|
||||
oldObjectData: TextInputObjectData,
|
||||
newObjectData: TextInputObjectData
|
||||
): boolean {
|
||||
if (
|
||||
oldObjectData.content.initialValue !==
|
||||
newObjectData.content.initialValue
|
||||
) {
|
||||
if (this._string === oldObjectData.content.initialValue) {
|
||||
this.setString(newObjectData.content.initialValue);
|
||||
}
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.placeholder !== newObjectData.content.placeholder
|
||||
) {
|
||||
this.setPlaceholder(newObjectData.content.placeholder);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.fontResourceName !==
|
||||
newObjectData.content.fontResourceName
|
||||
) {
|
||||
this.setFontResourceName(newObjectData.content.fontResourceName);
|
||||
}
|
||||
if (oldObjectData.content.fontSize !== newObjectData.content.fontSize) {
|
||||
this.setFontSize(newObjectData.content.fontSize);
|
||||
}
|
||||
if (oldObjectData.content.inputType !== newObjectData.content.inputType) {
|
||||
this.setInputType(newObjectData.content.inputType);
|
||||
}
|
||||
if (oldObjectData.content.textColor !== newObjectData.content.textColor) {
|
||||
this.setTextColor(newObjectData.content.textColor);
|
||||
}
|
||||
if (oldObjectData.content.fillColor !== newObjectData.content.fillColor) {
|
||||
this.setFillColor(newObjectData.content.fillColor);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.fillOpacity !== newObjectData.content.fillOpacity
|
||||
) {
|
||||
this.setFillOpacity(newObjectData.content.fillOpacity);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.borderColor !== newObjectData.content.borderColor
|
||||
) {
|
||||
this.setBorderColor(newObjectData.content.borderColor);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.borderOpacity !==
|
||||
newObjectData.content.borderOpacity
|
||||
) {
|
||||
this.setBorderOpacity(newObjectData.content.borderOpacity);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.borderWidth !==
|
||||
newObjectData.content.borderWidth
|
||||
) {
|
||||
this.setBorderWidth(newObjectData.content.borderWidth);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
updatePreRender(runtimeScene: RuntimeScene): void {
|
||||
this._renderer.updatePreRender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the extra parameters that could be set for an instance.
|
||||
*/
|
||||
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
|
||||
for (const property of initialInstanceData.stringProperties) {
|
||||
if (property.name === 'initialValue') {
|
||||
this.setString(property.value);
|
||||
} else if (property.name === 'placeholder') {
|
||||
this.setPlaceholder(property.value);
|
||||
}
|
||||
}
|
||||
if (initialInstanceData.customSize) {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
}
|
||||
|
||||
setZOrder(z: number): void {
|
||||
// TODO: find how to handle these Z order
|
||||
}
|
||||
|
||||
/**
|
||||
* Set object opacity.
|
||||
*/
|
||||
setOpacity(opacity): void {
|
||||
this.opacity = Math.max(0, Math.min(255, opacity));
|
||||
this._renderer.updateOpacity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object opacity.
|
||||
*/
|
||||
getOpacity() {
|
||||
return this.opacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the width of the object, if applicable.
|
||||
* @param width The new width in pixels.
|
||||
*/
|
||||
setWidth(width: float): void {
|
||||
this._width = width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the height of the object, if applicable.
|
||||
* @param height The new height in pixels.
|
||||
*/
|
||||
setHeight(height: float): void {
|
||||
this._height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the object.
|
||||
* @return The width of the object
|
||||
*/
|
||||
getWidth(): float {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the object.
|
||||
* @return The height of the object
|
||||
*/
|
||||
getHeight(): float {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text entered in the text input.
|
||||
*/
|
||||
getString() {
|
||||
return this._string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the text inside the text input.
|
||||
*/
|
||||
setString(newString: string) {
|
||||
if (newString === this._string) return;
|
||||
|
||||
this._string = newString;
|
||||
this._renderer.updateString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the renderer when the value of the input shown on the screen
|
||||
* was changed (because the user typed something).
|
||||
* This does not propagate back the value to the renderer, which would
|
||||
* result in the cursor being sent back to the end of the text.
|
||||
*
|
||||
* Do not use this if you are not inside the renderer - use `setString` instead.
|
||||
*/
|
||||
onRendererInputValueChanged(inputValue: string) {
|
||||
this._string = inputValue;
|
||||
}
|
||||
|
||||
getFontResourceName() {
|
||||
return this._fontResourceName;
|
||||
}
|
||||
|
||||
setFontResourceName(resourceName: string) {
|
||||
if (this._fontResourceName === resourceName) return;
|
||||
|
||||
this._fontResourceName = resourceName;
|
||||
this._renderer.updateFont();
|
||||
}
|
||||
|
||||
getFontSize() {
|
||||
return this._fontSize;
|
||||
}
|
||||
|
||||
setFontSize(newSize: number) {
|
||||
this._fontSize = newSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the placeholder shown when no text is entered
|
||||
*/
|
||||
getPlaceholder() {
|
||||
return this._placeholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the text inside the text input.
|
||||
*/
|
||||
setPlaceholder(newPlaceholder: string) {
|
||||
if (newPlaceholder === this._placeholder) return;
|
||||
|
||||
this._placeholder = newPlaceholder;
|
||||
this._renderer.updatePlaceholder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of the input.
|
||||
*/
|
||||
getInputType() {
|
||||
return this._inputType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of the input.
|
||||
*/
|
||||
setInputType(newInputType: string) {
|
||||
const lowercasedNewInputType = newInputType.toLowerCase();
|
||||
if (lowercasedNewInputType === this._inputType) return;
|
||||
|
||||
this._inputType = parseInputType(lowercasedNewInputType);
|
||||
this._renderer.updateInputType();
|
||||
}
|
||||
|
||||
setTextColor(newColor: string) {
|
||||
this._textColor = gdjs.rgbOrHexToRGBColor(newColor);
|
||||
this._renderer.updateTextColor();
|
||||
}
|
||||
|
||||
getTextColor(): string {
|
||||
return (
|
||||
this._textColor[0] + ';' + this._textColor[1] + ';' + this._textColor[2]
|
||||
);
|
||||
}
|
||||
|
||||
_getRawTextColor(): [float, float, float] {
|
||||
return this._textColor;
|
||||
}
|
||||
|
||||
setFillColor(newColor: string) {
|
||||
this._fillColor = gdjs.rgbOrHexToRGBColor(newColor);
|
||||
this._renderer.updateFillColorAndOpacity();
|
||||
}
|
||||
|
||||
getFillColor(): string {
|
||||
return (
|
||||
this._fillColor[0] + ';' + this._fillColor[1] + ';' + this._fillColor[2]
|
||||
);
|
||||
}
|
||||
|
||||
_getRawFillColor(): [float, float, float] {
|
||||
return this._fillColor;
|
||||
}
|
||||
|
||||
setFillOpacity(newOpacity: float) {
|
||||
this._fillOpacity = Math.max(0, Math.min(255, newOpacity));
|
||||
this._renderer.updateFillColorAndOpacity();
|
||||
}
|
||||
|
||||
getFillOpacity(): float {
|
||||
return this._fillOpacity;
|
||||
}
|
||||
|
||||
setBorderColor(newColor: string) {
|
||||
this._borderColor = gdjs.rgbOrHexToRGBColor(newColor);
|
||||
this._renderer.updateBorderColorAndOpacity();
|
||||
}
|
||||
|
||||
getBorderColor(): string {
|
||||
return (
|
||||
this._borderColor[0] +
|
||||
';' +
|
||||
this._borderColor[1] +
|
||||
';' +
|
||||
this._borderColor[2]
|
||||
);
|
||||
}
|
||||
|
||||
_getRawBorderColor(): [float, float, float] {
|
||||
return this._borderColor;
|
||||
}
|
||||
|
||||
setBorderOpacity(newOpacity: float) {
|
||||
this._borderOpacity = Math.max(0, Math.min(255, newOpacity));
|
||||
this._renderer.updateBorderColorAndOpacity();
|
||||
}
|
||||
|
||||
getBorderOpacity(): float {
|
||||
return this._borderOpacity;
|
||||
}
|
||||
|
||||
setBorderWidth(width: float) {
|
||||
this._borderWidth = Math.max(0, width);
|
||||
this._renderer.updateBorderWidth();
|
||||
}
|
||||
|
||||
getBorderWidth(): float {
|
||||
return this._borderWidth;
|
||||
}
|
||||
}
|
||||
gdjs.registerObject(
|
||||
'TextInput::TextInputObject',
|
||||
gdjs.TextInputRuntimeObject
|
||||
);
|
||||
}
|
@@ -267,10 +267,8 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a point from the canvas coordinates (For example, the mouse position) to the
|
||||
* "world" coordinates.
|
||||
*
|
||||
* TODO: Update this method to store the result in a static array
|
||||
* Convert a point from the canvas coordinates (for example,
|
||||
* the mouse position) to the scene coordinates.
|
||||
*
|
||||
* @param x The x position, in canvas coordinates.
|
||||
* @param y The y position, in canvas coordinates.
|
||||
@@ -292,6 +290,14 @@ namespace gdjs {
|
||||
return [x + this.getCameraX(cameraId), y + this.getCameraY(cameraId)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a point from the scene coordinates (for example,
|
||||
* an object position) to the canvas coordinates.
|
||||
*
|
||||
* @param x The x position, in scene coordinates.
|
||||
* @param y The y position, in scene coordinates.
|
||||
* @param cameraId The camera number. Currently ignored.
|
||||
*/
|
||||
convertInverseCoords(x: float, y: float, cameraId?: integer): FloatPoint {
|
||||
x -= this.getCameraX(cameraId);
|
||||
y -= this.getCameraY(cameraId);
|
||||
|
@@ -27,7 +27,9 @@ namespace gdjs {
|
||||
|
||||
//Used to track if the window is displayed as fullscreen (see setFullscreen method).
|
||||
_forceFullscreen: any;
|
||||
|
||||
_pixiRenderer: PIXI.Renderer | null = null;
|
||||
private _domElementsContainer: HTMLDivElement | null = null;
|
||||
|
||||
// Current width of the canvas (might be scaled down/up compared to renderer)
|
||||
_canvasWidth: float = 0;
|
||||
@@ -60,26 +62,66 @@ namespace gdjs {
|
||||
* Create a standard canvas inside canvasArea.
|
||||
*
|
||||
*/
|
||||
createStandardCanvas(parentElement) {
|
||||
//Create the renderer and setup the rendering area
|
||||
//"preserveDrawingBuffer: true" is needed to avoid flickering and background issues on some mobile phones (see #585 #572 #566 #463)
|
||||
createStandardCanvas(parentElement: HTMLElement) {
|
||||
// Create the renderer and setup the rendering area.
|
||||
// "preserveDrawingBuffer: true" is needed to avoid flickering
|
||||
// and background issues on some mobile phones (see #585 #572 #566 #463).
|
||||
this._pixiRenderer = PIXI.autoDetectRenderer({
|
||||
width: this._game.getGameResolutionWidth(),
|
||||
height: this._game.getGameResolutionHeight(),
|
||||
preserveDrawingBuffer: true,
|
||||
antialias: false,
|
||||
}) as PIXI.Renderer;
|
||||
parentElement.appendChild(
|
||||
// add the renderer view element to the DOM
|
||||
this._pixiRenderer.view
|
||||
);
|
||||
this._pixiRenderer.view.style['position'] = 'absolute';
|
||||
//Ensure that the canvas has the focus.
|
||||
this._pixiRenderer.view.tabIndex = 1;
|
||||
const gameCanvas = this._pixiRenderer.view;
|
||||
|
||||
// Add the renderer view element to the DOM
|
||||
parentElement.appendChild(gameCanvas);
|
||||
gameCanvas.style.position = 'absolute';
|
||||
|
||||
// Ensure that the canvas has the focus.
|
||||
gameCanvas.tabIndex = 1;
|
||||
|
||||
// Ensure long press can't create a selection
|
||||
gameCanvas.style.userSelect = 'none';
|
||||
gameCanvas.style.outline = 'none'; // No selection/focus ring on the canvas.
|
||||
|
||||
// Set up the container for HTML elements on top of the game canvas.
|
||||
const domElementsContainer = document.createElement('div');
|
||||
domElementsContainer.style.position = 'absolute';
|
||||
domElementsContainer.style.overflow = 'hidden'; // Never show anything outside the container.
|
||||
domElementsContainer.style.outline = 'none'; // No selection/focus ring on this container.
|
||||
domElementsContainer.style.pointerEvents = 'none'; // Clicks go through the container.
|
||||
|
||||
// The container should *never* scroll.
|
||||
// Elements are put inside with the same coordinates (with a scaling factor)
|
||||
// as on the game canvas.
|
||||
domElementsContainer.addEventListener('scroll', (event) => {
|
||||
domElementsContainer.scrollLeft = 0;
|
||||
domElementsContainer.scrollTop = 0;
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// When clicking outside an input, (or other HTML element),
|
||||
// give back focus to the game canvas so that this element is blurred.
|
||||
gameCanvas.addEventListener('pointerdown', () => {
|
||||
gameCanvas.focus();
|
||||
});
|
||||
|
||||
// Prevent magnifying glass on iOS with a long press.
|
||||
domElementsContainer.style['-webkit-user-select'] = 'none';
|
||||
domElementsContainer.addEventListener('touchstart', (event) => {
|
||||
// Prevent the magnifiying glass on iOS 15, which does not honor -webkit-user-select.
|
||||
// See https://bugs.webkit.org/show_bug.cgi?id=231161
|
||||
// This unfortunately also prevent to move the cursor.
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
parentElement.appendChild(domElementsContainer);
|
||||
this._domElementsContainer = domElementsContainer;
|
||||
|
||||
this._resizeCanvas();
|
||||
|
||||
// Handle scale mode
|
||||
// Handle scale mode.
|
||||
if (this._game.getScaleMode() === 'nearest') {
|
||||
this._pixiRenderer.view.style['image-rendering'] = '-moz-crisp-edges';
|
||||
this._pixiRenderer.view.style['image-rendering'] =
|
||||
@@ -89,19 +131,21 @@ namespace gdjs {
|
||||
this._pixiRenderer.view.style['image-rendering'] = 'pixelated';
|
||||
}
|
||||
|
||||
// Handle pixels rounding
|
||||
// Handle pixels rounding.
|
||||
if (this._game.getPixelsRounding()) {
|
||||
PIXI.settings.ROUND_PIXELS = true;
|
||||
}
|
||||
|
||||
//Handle resize
|
||||
const that = this;
|
||||
window.addEventListener('resize', function () {
|
||||
that._game.onWindowInnerSizeChanged();
|
||||
that._resizeCanvas();
|
||||
that._game._notifySceneForResize = true;
|
||||
// Handle resize: immediately adjust the game canvas (and dom element container)
|
||||
// and notify the game (that may want to adjust to the new size of the window).
|
||||
window.addEventListener('resize', () => {
|
||||
this._game.onWindowInnerSizeChanged();
|
||||
this._resizeCanvas();
|
||||
this._game._notifySceneForResize = true;
|
||||
});
|
||||
return this._pixiRenderer;
|
||||
|
||||
// Focus the canvas when created.
|
||||
gameCanvas.focus();
|
||||
}
|
||||
|
||||
static getWindowInnerWidth() {
|
||||
@@ -158,7 +202,7 @@ namespace gdjs {
|
||||
*
|
||||
*/
|
||||
private _resizeCanvas() {
|
||||
if (!this._pixiRenderer) return;
|
||||
if (!this._pixiRenderer || !this._domElementsContainer) return;
|
||||
|
||||
// Set the Pixi renderer size to the game size.
|
||||
// There is no "smart" resizing to be done here: the rendering of the game
|
||||
@@ -205,13 +249,23 @@ namespace gdjs {
|
||||
canvasHeight *= factor;
|
||||
}
|
||||
}
|
||||
this._pixiRenderer.view.style['top'] =
|
||||
|
||||
// Apply the calculations to the canvas element...
|
||||
this._pixiRenderer.view.style.top =
|
||||
this._marginTop + (maxHeight - canvasHeight) / 2 + 'px';
|
||||
this._pixiRenderer.view.style['left'] =
|
||||
this._pixiRenderer.view.style.left =
|
||||
this._marginLeft + (maxWidth - canvasWidth) / 2 + 'px';
|
||||
this._pixiRenderer.view.style.width = canvasWidth + 'px';
|
||||
this._pixiRenderer.view.style.height = canvasHeight + 'px';
|
||||
|
||||
// ...and to the div on top of it showing DOM elements (like inputs).
|
||||
this._domElementsContainer.style.top =
|
||||
this._marginTop + (maxHeight - canvasHeight) / 2 + 'px';
|
||||
this._domElementsContainer.style.left =
|
||||
this._marginLeft + (maxWidth - canvasWidth) / 2 + 'px';
|
||||
this._domElementsContainer.style.width = canvasWidth + 'px';
|
||||
this._domElementsContainer.style.height = canvasHeight + 'px';
|
||||
|
||||
// Store the canvas size for fast access to it.
|
||||
this._canvasWidth = canvasWidth;
|
||||
this._canvasHeight = canvasHeight;
|
||||
@@ -359,6 +413,36 @@ namespace gdjs {
|
||||
return this._isFullscreen || window.screen.height === window.innerHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a point from the canvas coordinates to the dom element container coordinates.
|
||||
*
|
||||
* @param canvasCoords The point in the canvas coordinates.
|
||||
* @returns The point in the dom element container coordinates.
|
||||
*/
|
||||
convertCanvasToDomElementContainerCoords(
|
||||
canvasCoords: FloatPoint
|
||||
): FloatPoint {
|
||||
const pageCoords: FloatPoint = [canvasCoords[0], canvasCoords[1]];
|
||||
|
||||
// Handle the fact that the game is stretched to fill the canvas.
|
||||
pageCoords[0] /=
|
||||
this._game.getGameResolutionWidth() / (this._canvasWidth || 1);
|
||||
pageCoords[1] /=
|
||||
this._game.getGameResolutionHeight() / (this._canvasHeight || 1);
|
||||
|
||||
return pageCoords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the scale factor between the renderer height and the actual canvas height,
|
||||
* which is also the height of the container for DOM elements to be superimposed on top of it.
|
||||
*
|
||||
* Useful to scale font sizes of DOM elements so that they follow the size of the game.
|
||||
*/
|
||||
getCanvasToDomElementContainerHeightScale(): float {
|
||||
return (this._canvasHeight || 1) / this._game.getGameResolutionHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the standard events handler.
|
||||
*/
|
||||
@@ -372,24 +456,9 @@ namespace gdjs {
|
||||
const that = this;
|
||||
|
||||
function getEventPosition(e) {
|
||||
const pos = [0, 0];
|
||||
if (e.pageX) {
|
||||
pos[0] = e.pageX - canvas.offsetLeft;
|
||||
pos[1] = e.pageY - canvas.offsetTop;
|
||||
} else {
|
||||
pos[0] =
|
||||
e.clientX +
|
||||
document.body.scrollLeft +
|
||||
document.documentElement.scrollLeft -
|
||||
canvas.offsetLeft;
|
||||
pos[1] =
|
||||
e.clientY +
|
||||
document.body.scrollTop +
|
||||
document.documentElement.scrollTop -
|
||||
canvas.offsetTop;
|
||||
}
|
||||
const pos = [e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop];
|
||||
|
||||
//Handle the fact that the game is stretched to fill the canvas.
|
||||
// Handle the fact that the game is stretched to fill the canvas.
|
||||
pos[0] *=
|
||||
that._game.getGameResolutionWidth() / (that._canvasWidth || 1);
|
||||
pos[1] *=
|
||||
@@ -429,8 +498,25 @@ namespace gdjs {
|
||||
}
|
||||
})();
|
||||
|
||||
//Keyboard
|
||||
// Keyboard: listen at the document level to capture even when the canvas
|
||||
// is not focused.
|
||||
|
||||
const isFocusingDomElement = () => {
|
||||
// Fast bailout when the game canvas should receive the inputs (i.e: almost always).
|
||||
// Also check the document body or null for activeElement, as all of these should go
|
||||
// to the game.
|
||||
if (
|
||||
document.activeElement === canvas ||
|
||||
document.activeElement === document.body ||
|
||||
document.activeElement === null
|
||||
)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
document.onkeydown = function (e) {
|
||||
if (isFocusingDomElement()) return;
|
||||
|
||||
if (defaultPreventedKeyCodes.includes(e.keyCode)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
@@ -438,6 +524,8 @@ namespace gdjs {
|
||||
manager.onKeyPressed(e.keyCode, e.location);
|
||||
};
|
||||
document.onkeyup = function (e) {
|
||||
if (isFocusingDomElement()) return;
|
||||
|
||||
if (defaultPreventedKeyCodes.includes(e.keyCode)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
@@ -446,11 +534,11 @@ namespace gdjs {
|
||||
};
|
||||
|
||||
//Mouse
|
||||
renderer.view.onmousemove = function (e) {
|
||||
canvas.onmousemove = function (e) {
|
||||
const pos = getEventPosition(e);
|
||||
manager.onMouseMove(pos[0], pos[1]);
|
||||
};
|
||||
renderer.view.onmousedown = function (e) {
|
||||
canvas.onmousedown = function (e) {
|
||||
manager.onMouseButtonPressed(
|
||||
e.button === 2
|
||||
? gdjs.InputManager.MOUSE_RIGHT_BUTTON
|
||||
@@ -463,7 +551,7 @@ namespace gdjs {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
renderer.view.onmouseup = function (e) {
|
||||
canvas.onmouseup = function (e) {
|
||||
manager.onMouseButtonReleased(
|
||||
e.button === 2
|
||||
? gdjs.InputManager.MOUSE_RIGHT_BUTTON
|
||||
@@ -484,7 +572,7 @@ namespace gdjs {
|
||||
},
|
||||
false
|
||||
);
|
||||
renderer.view.oncontextmenu = function (event) {
|
||||
canvas.oncontextmenu = function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
@@ -563,6 +651,10 @@ namespace gdjs {
|
||||
return this._pixiRenderer;
|
||||
}
|
||||
|
||||
getDomElementContainer() {
|
||||
return this._domElementsContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the given URL in the system browser (or a new tab)
|
||||
*/
|
||||
|
2
GDJS/Runtime/types/project-data.d.ts
vendored
2
GDJS/Runtime/types/project-data.d.ts
vendored
@@ -117,7 +117,7 @@ declare interface InstanceNumberProperty {
|
||||
}
|
||||
declare interface InstanceStringProperty {
|
||||
name: string;
|
||||
value: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
declare interface LayerData {
|
||||
|
BIN
GDJS/tests/games/text-input/Bimbo_JVE.ttf
Normal file
BIN
GDJS/tests/games/text-input/Bimbo_JVE.ttf
Normal file
Binary file not shown.
BIN
GDJS/tests/games/text-input/assets/Block.png
Normal file
BIN
GDJS/tests/games/text-input/assets/Block.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 979 B |
BIN
GDJS/tests/games/text-input/assets/Blue block.png
Normal file
BIN
GDJS/tests/games/text-input/assets/Blue block.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
GDJS/tests/games/text-input/assets/Industrial platform.png
Normal file
BIN
GDJS/tests/games/text-input/assets/Industrial platform.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
GDJS/tests/games/text-input/assets/tiled_Industrial platform.png
Normal file
BIN
GDJS/tests/games/text-input/assets/tiled_Industrial platform.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
1793
GDJS/tests/games/text-input/text input playground.json
Normal file
1793
GDJS/tests/games/text-input/text input playground.json
Normal file
File diff suppressed because it is too large
Load Diff
29
newIDE/app/public/JsPlatform/Extensions/text_input.svg
Normal file
29
newIDE/app/public/JsPlatform/Extensions/text_input.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<svg width="64" height="64" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 1H1V15H15V1Z" fill="#27AAE1"/>
|
||||
<path d="M14 13H2V14H14V13Z" fill="white"/>
|
||||
<path d="M14 2H2V3H14V2Z" fill="white"/>
|
||||
<path d="M3 2H2V14H3V2Z" fill="white"/>
|
||||
<path d="M14 2H13V14H14V2Z" fill="white"/>
|
||||
<g opacity="0.1">
|
||||
<path opacity="0.1" d="M15 15L1 1V15H15Z" fill="black"/>
|
||||
<path opacity="0.1" d="M15 1H1L15 15V1Z" fill="white"/>
|
||||
<rect x="9" y="4" width="3" height="1" fill="#2B3990"/>
|
||||
</g>
|
||||
<rect x="9" y="11" width="3" height="1" fill="#2B3990"/>
|
||||
<rect x="10" y="4" width="1" height="8" fill="#2B3990"/>
|
||||
<rect x="9" y="4" width="3" height="1" fill="#2B3990"/>
|
||||
<path d="M6.71338 10.5C6.68408 10.4414 6.65918 10.3726 6.63867 10.2935C6.62109 10.2144 6.60498 10.1294 6.59033 10.0386C6.51709 10.1147 6.43359 10.1865 6.33984 10.2539C6.24902 10.3184 6.14648 10.3755 6.03223 10.4253C5.9209 10.4751 5.79932 10.5146 5.66748 10.5439C5.53857 10.5732 5.39941 10.5879 5.25 10.5879C4.99805 10.5879 4.76953 10.5527 4.56445 10.4824C4.3623 10.4092 4.18799 10.3096 4.0415 10.1836C3.89795 10.0547 3.78662 9.90381 3.70752 9.73096C3.62842 9.55811 3.58887 9.37061 3.58887 9.16846C3.58887 8.65869 3.77783 8.26904 4.15576 7.99951C4.53662 7.72998 5.08154 7.59521 5.79053 7.59521H6.55957V7.2832C6.55957 7.03125 6.47461 6.83203 6.30469 6.68555C6.13477 6.53613 5.89453 6.46143 5.58398 6.46143C5.44336 6.46143 5.32031 6.479 5.21484 6.51416C5.10938 6.54639 5.02148 6.5918 4.95117 6.65039C4.88086 6.70898 4.82812 6.77783 4.79297 6.85693C4.75781 6.93604 4.74023 7.021 4.74023 7.11182H3.68994C3.68994 6.92725 3.73242 6.74854 3.81738 6.57568C3.90527 6.3999 4.03271 6.24463 4.19971 6.10986C4.3667 5.97217 4.57031 5.8623 4.81055 5.78027C5.05371 5.69824 5.33057 5.65723 5.64111 5.65723C5.92236 5.65723 6.18311 5.69238 6.42334 5.7627C6.66357 5.83008 6.87158 5.93262 7.04736 6.07031C7.22314 6.20508 7.36084 6.375 7.46045 6.58008C7.56006 6.78223 7.60986 7.01953 7.60986 7.29199V9.41016C7.60986 9.61816 7.62451 9.80859 7.65381 9.98145C7.68604 10.1514 7.73145 10.2993 7.79004 10.4253V10.5H6.71338ZM5.44775 9.73975C5.58252 9.73975 5.7085 9.72363 5.82568 9.69141C5.9458 9.65625 6.05273 9.6123 6.14648 9.55957C6.24316 9.50391 6.3252 9.44092 6.39258 9.37061C6.46289 9.30029 6.51855 9.22852 6.55957 9.15527V8.26318H5.8916C5.46387 8.26318 5.14746 8.33203 4.94238 8.46973C4.74023 8.60742 4.63916 8.80371 4.63916 9.05859C4.63916 9.15527 4.65527 9.24609 4.6875 9.33105C4.71973 9.41309 4.76807 9.48486 4.83252 9.54639C4.8999 9.60498 4.9834 9.65186 5.08301 9.68701C5.18555 9.72217 5.30713 9.73975 5.44775 9.73975Z" fill="#2B3990"/>
|
||||
<path d="M15 1H1V15H15V1Z" fill="#27AAE1"/>
|
||||
<path d="M15 14H1V15H15V14Z" fill="#2B3990"/>
|
||||
<path d="M15 1H1V2H15V1Z" fill="#2B3990"/>
|
||||
<path d="M2 1H1V15H2V1Z" fill="#2B3990"/>
|
||||
<path d="M15 1H14V15H15V1Z" fill="#2B3990"/>
|
||||
<g opacity="0.1">
|
||||
<path opacity="0.1" d="M15 15L1 1V15H15Z" fill="black"/>
|
||||
<path opacity="0.1" d="M15 1H1L15 15V1Z" fill="white"/>
|
||||
</g>
|
||||
<rect x="9" y="11" width="3" height="1" fill="white"/>
|
||||
<rect x="10" y="4" width="1" height="8" fill="white"/>
|
||||
<rect x="9" y="4" width="3" height="1" fill="white"/>
|
||||
<path d="M6.71338 11C6.68408 10.9414 6.65918 10.8726 6.63867 10.7935C6.62109 10.7144 6.60498 10.6294 6.59033 10.5386C6.51709 10.6147 6.43359 10.6865 6.33984 10.7539C6.24902 10.8184 6.14648 10.8755 6.03223 10.9253C5.9209 10.9751 5.79932 11.0146 5.66748 11.0439C5.53857 11.0732 5.39941 11.0879 5.25 11.0879C4.99805 11.0879 4.76953 11.0527 4.56445 10.9824C4.3623 10.9092 4.18799 10.8096 4.0415 10.6836C3.89795 10.5547 3.78662 10.4038 3.70752 10.231C3.62842 10.0581 3.58887 9.87061 3.58887 9.66846C3.58887 9.15869 3.77783 8.76904 4.15576 8.49951C4.53662 8.22998 5.08154 8.09521 5.79053 8.09521H6.55957V7.7832C6.55957 7.53125 6.47461 7.33203 6.30469 7.18555C6.13477 7.03613 5.89453 6.96143 5.58398 6.96143C5.44336 6.96143 5.32031 6.979 5.21484 7.01416C5.10938 7.04639 5.02148 7.0918 4.95117 7.15039C4.88086 7.20898 4.82812 7.27783 4.79297 7.35693C4.75781 7.43604 4.74023 7.521 4.74023 7.61182H3.68994C3.68994 7.42725 3.73242 7.24854 3.81738 7.07568C3.90527 6.8999 4.03271 6.74463 4.19971 6.60986C4.3667 6.47217 4.57031 6.3623 4.81055 6.28027C5.05371 6.19824 5.33057 6.15723 5.64111 6.15723C5.92236 6.15723 6.18311 6.19238 6.42334 6.2627C6.66357 6.33008 6.87158 6.43262 7.04736 6.57031C7.22314 6.70508 7.36084 6.875 7.46045 7.08008C7.56006 7.28223 7.60986 7.51953 7.60986 7.79199V9.91016C7.60986 10.1182 7.62451 10.3086 7.65381 10.4814C7.68604 10.6514 7.73145 10.7993 7.79004 10.9253V11H6.71338ZM5.44775 10.2397C5.58252 10.2397 5.7085 10.2236 5.82568 10.1914C5.9458 10.1562 6.05273 10.1123 6.14648 10.0596C6.24316 10.0039 6.3252 9.94092 6.39258 9.87061C6.46289 9.80029 6.51855 9.72852 6.55957 9.65527V8.76318H5.8916C5.46387 8.76318 5.14746 8.83203 4.94238 8.96973C4.74023 9.10742 4.63916 9.30371 4.63916 9.55859C4.63916 9.65527 4.65527 9.74609 4.6875 9.83105C4.71973 9.91309 4.76807 9.98486 4.83252 10.0464C4.8999 10.105 4.9834 10.1519 5.08301 10.187C5.18555 10.2222 5.30713 10.2397 5.44775 10.2397Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,51 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import ResourceSelector from '../../ResourcesList/ResourceSelector';
|
||||
import ResourcesLoader from '../../ResourcesLoader';
|
||||
import { type ParameterFieldProps } from './ParameterFieldCommons';
|
||||
|
||||
export default class BitmapFontResourceField extends Component<
|
||||
ParameterFieldProps,
|
||||
void
|
||||
> {
|
||||
_field: ?ResourceSelector;
|
||||
|
||||
focus() {
|
||||
if (this._field) this._field.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (
|
||||
!this.props.resourceSources ||
|
||||
!this.props.onChooseResource ||
|
||||
!this.props.resourceExternalEditors ||
|
||||
!this.props.project
|
||||
) {
|
||||
console.error(
|
||||
'Missing project, resourceSources, onChooseResource or resourceExternalEditors for FontResourceField'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceSelector
|
||||
margin={this.props.isInline ? 'none' : 'dense'}
|
||||
project={this.props.project}
|
||||
resourceSources={this.props.resourceSources}
|
||||
onChooseResource={this.props.onChooseResource}
|
||||
resourceExternalEditors={this.props.resourceExternalEditors}
|
||||
resourcesLoader={ResourcesLoader}
|
||||
resourceKind="font"
|
||||
fullWidth
|
||||
initialResourceName={this.props.value}
|
||||
onChange={this.props.onChange}
|
||||
floatingLabelText={<Trans>Choose the font file to use</Trans>}
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
onApply={this.props.onApply}
|
||||
ref={field => (this._field = field)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@@ -41,6 +41,7 @@ import AudioResourceField from './ParameterFields/AudioResourceField';
|
||||
import VideoResourceField from './ParameterFields/VideoResourceField';
|
||||
import JsonResourceField from './ParameterFields/JsonResourceField';
|
||||
import BitmapFontResourceField from './ParameterFields/BitmapFontResourceField';
|
||||
import FontResourceField from './ParameterFields/FontResourceField';
|
||||
import ColorExpressionField from './ParameterFields/ColorExpressionField';
|
||||
import ForceMultiplierField, {
|
||||
renderInlineForceMultiplier,
|
||||
@@ -79,6 +80,7 @@ const components = {
|
||||
videoResource: VideoResourceField,
|
||||
jsonResource: JsonResourceField,
|
||||
bitmapFontResource: BitmapFontResourceField,
|
||||
fontResource: FontResourceField,
|
||||
color: ColorExpressionField,
|
||||
police: DefaultField, //TODO
|
||||
joyaxis: DefaultField, //TODO
|
||||
@@ -126,6 +128,7 @@ const userFriendlyTypeName: { [string]: MessageDescriptor } = {
|
||||
soundfile: t`Audio resource`,
|
||||
videoResource: t`Video resource`,
|
||||
bitmapFontResource: t`Bitmap font resource`,
|
||||
fontResource: t`Font resource`,
|
||||
jsonResource: t`JSON resource`,
|
||||
color: t`Color`,
|
||||
forceMultiplier: t`Instant or permanent force`,
|
||||
|
@@ -61,6 +61,12 @@ export const getExtraObjectsInformation = (): {
|
||||
message: t`The tilemap must be designed in a separated program, Tiled, that can be downloaded on mapeditor.org. Save your map as a JSON file, then select here the Atlas image that you used and the Tile map JSON file.`,
|
||||
},
|
||||
],
|
||||
'TextInput::TextInputObject': [
|
||||
{
|
||||
kind: 'warning',
|
||||
message: t`The text input will be always shown on top of all other objects in the game - this is a limitation that can't be changed. According to the platform/device or browser running the game, the appearance can also slightly change.`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const getExtraInstructionInformation = (type: string): ?Hint => {
|
||||
|
@@ -40,7 +40,6 @@ import {
|
||||
import RaisedButtonWithSplitMenu from '../../../UI/RaisedButtonWithSplitMenu';
|
||||
import PreferencesContext from '../../Preferences/PreferencesContext';
|
||||
import { type FileMetadataAndStorageProviderName } from '../../../ProjectsStorage';
|
||||
import generateName from '../../../Utils/ProjectNameGenerator';
|
||||
import { sendTutorialOpened } from '../../../Utils/Analytics/EventSender';
|
||||
import { hasPendingNotifications } from '../../../Utils/Notification';
|
||||
|
||||
@@ -162,7 +161,6 @@ export const HomePage = React.memo<Props>(
|
||||
}));
|
||||
|
||||
const windowWidth = useResponsiveWindowWidth();
|
||||
const [newProjectName, setNewProjectName] = React.useState<string>('');
|
||||
const authenticatedUser = React.useContext(AuthenticatedUserContext);
|
||||
const { getRecentProjectFiles } = React.useContext(PreferencesContext);
|
||||
const {
|
||||
@@ -265,12 +263,11 @@ export const HomePage = React.memo<Props>(
|
||||
const openPreCreationDialog = React.useCallback((open: boolean) => {
|
||||
if (open) {
|
||||
setOutputPath(app ? findEmptyPathInDefaultFolder(app) : '');
|
||||
setNewProjectName(generateName());
|
||||
}
|
||||
setPreCreationDialogOpen(open);
|
||||
}, []);
|
||||
|
||||
const createProject = async (i18n: I18nType) => {
|
||||
const createProject = async (i18n: I18nType, newProjectName: string) => {
|
||||
setIsOpening(true);
|
||||
|
||||
try {
|
||||
@@ -534,14 +531,9 @@ export const HomePage = React.memo<Props>(
|
||||
open
|
||||
isOpening={isOpening}
|
||||
onClose={() => openPreCreationDialog(false)}
|
||||
onCreate={() => createProject(i18n)}
|
||||
onClickGenerateProjectName={() =>
|
||||
setNewProjectName(generateName())
|
||||
}
|
||||
onCreate={(projectName) => createProject(i18n, projectName)}
|
||||
outputPath={electron ? outputPath : undefined}
|
||||
onChangeOutputPath={electron ? setOutputPath : undefined}
|
||||
projectName={newProjectName}
|
||||
onChangeProjectName={setNewProjectName}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@@ -19,7 +19,6 @@ import ProjectPreCreationDialog from './ProjectPreCreationDialog';
|
||||
import { findEmptyPathInDefaultFolder } from './LocalPathFinder';
|
||||
import optionalRequire from '../Utils/OptionalRequire.js';
|
||||
import RaisedButton from '../UI/RaisedButton';
|
||||
import generateName from '../Utils/ProjectNameGenerator';
|
||||
const electron = optionalRequire('electron');
|
||||
const app = electron ? electron.remote.app : null;
|
||||
|
||||
@@ -86,7 +85,6 @@ const CreateProjectDialog = ({
|
||||
app ? findEmptyPathInDefaultFolder(app) : ''
|
||||
);
|
||||
const [isOpening, setIsOpening] = React.useState<boolean>(false);
|
||||
const [newProjectName, setNewProjectName] = React.useState<string>('');
|
||||
const [
|
||||
selectedExampleShortHeader,
|
||||
setSelectedExampleShortShortHeader,
|
||||
@@ -99,7 +97,6 @@ const CreateProjectDialog = ({
|
||||
const openPreCreationDialog = React.useCallback((open: boolean) => {
|
||||
if (open) {
|
||||
setOutputPath(app ? findEmptyPathInDefaultFolder(app) : '');
|
||||
setNewProjectName(generateName());
|
||||
}
|
||||
setPreCreationDialogOpen(open);
|
||||
}, []);
|
||||
@@ -158,7 +155,7 @@ const CreateProjectDialog = ({
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
const createProject = async (i18n: I18nType) => {
|
||||
const createProject = async (i18n: I18nType, newProjectName: string) => {
|
||||
setIsOpening(true);
|
||||
|
||||
try {
|
||||
@@ -238,14 +235,9 @@ const CreateProjectDialog = ({
|
||||
open
|
||||
isOpening={isOpening}
|
||||
onClose={() => openPreCreationDialog(false)}
|
||||
onCreate={() => createProject(i18n)}
|
||||
onClickGenerateProjectName={() =>
|
||||
setNewProjectName(generateName())
|
||||
}
|
||||
onCreate={(projectName) => createProject(i18n, projectName)}
|
||||
outputPath={electron ? outputPath : undefined}
|
||||
onChangeOutputPath={electron ? setOutputPath : undefined}
|
||||
projectName={newProjectName}
|
||||
onChangeProjectName={setNewProjectName}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@@ -8,17 +8,15 @@ import RaisedButton from '../UI/RaisedButton';
|
||||
import { Column, Spacer } from '../UI/Grid';
|
||||
import LocalFolderPicker from '../UI/LocalFolderPicker';
|
||||
import TextField from '../UI/TextField';
|
||||
import generateName from '../Utils/ProjectNameGenerator';
|
||||
|
||||
type Props = {|
|
||||
open: boolean,
|
||||
isOpening?: boolean,
|
||||
onClose: () => void,
|
||||
onCreate: () => void | Promise<void>,
|
||||
onClickGenerateProjectName: () => void,
|
||||
onCreate: (projectName: string) => void | Promise<void>,
|
||||
outputPath?: string,
|
||||
onChangeOutputPath?: (outputPath: string) => void,
|
||||
projectName: string,
|
||||
onChangeProjectName: (name: string) => void,
|
||||
|};
|
||||
|
||||
const ProjectPreCreationDialog = ({
|
||||
@@ -26,15 +24,13 @@ const ProjectPreCreationDialog = ({
|
||||
isOpening,
|
||||
onClose,
|
||||
onCreate,
|
||||
onClickGenerateProjectName,
|
||||
outputPath,
|
||||
onChangeOutputPath,
|
||||
projectName,
|
||||
onChangeProjectName,
|
||||
}: Props): React.Node => {
|
||||
const [projectNameError, setProjectNameError] = React.useState<?React.Node>(
|
||||
null
|
||||
);
|
||||
const [projectName, setProjectName] = React.useState<string>(() => generateName());
|
||||
|
||||
const onValidate = React.useCallback(
|
||||
() => {
|
||||
@@ -47,7 +43,7 @@ const ProjectPreCreationDialog = ({
|
||||
);
|
||||
return;
|
||||
}
|
||||
onCreate();
|
||||
onCreate(projectName);
|
||||
},
|
||||
[onCreate, projectName, isOpening]
|
||||
);
|
||||
@@ -55,9 +51,9 @@ const ProjectPreCreationDialog = ({
|
||||
const _onChangeProjectName = React.useCallback(
|
||||
(event, text) => {
|
||||
if (projectNameError) setProjectNameError(null);
|
||||
onChangeProjectName(text);
|
||||
setProjectName(text);
|
||||
},
|
||||
[onChangeProjectName, projectNameError]
|
||||
[setProjectName, projectNameError]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -91,7 +87,9 @@ const ProjectPreCreationDialog = ({
|
||||
value={projectName}
|
||||
onChange={_onChangeProjectName}
|
||||
floatingLabelText={<Trans>Project name</Trans>}
|
||||
endAdornment={<Refresh onClick={onClickGenerateProjectName} />}
|
||||
endAdornment={
|
||||
<Refresh onClick={() => setProjectName(generateName())} />
|
||||
}
|
||||
/>
|
||||
{onChangeOutputPath && (
|
||||
<>
|
||||
|
@@ -108,8 +108,7 @@ export type Field =
|
||||
// The schema is the tree of all fields.
|
||||
export type Schema = Array<Field>;
|
||||
|
||||
// Mandatory props in any case when using the component
|
||||
type MandatoryProps = {|
|
||||
type Props = {|
|
||||
onInstancesModified?: Instances => void,
|
||||
instances: Instances,
|
||||
schema: Schema,
|
||||
@@ -119,19 +118,13 @@ type MandatoryProps = {|
|
||||
// (see getExtraDescription).
|
||||
renderExtraDescriptionText?: (extraDescription: string) => string,
|
||||
unsavedChanges?: ?UnsavedChanges,
|
||||
|};
|
||||
|
||||
type Props =
|
||||
// Mandatory props in all cases:
|
||||
| MandatoryProps
|
||||
// Props to be used when you want to display resources:
|
||||
| {|
|
||||
...MandatoryProps,
|
||||
project: gdProject,
|
||||
resourceSources: Array<ResourceSource>,
|
||||
onChooseResource: ChooseResourceFunction,
|
||||
resourceExternalEditors: Array<ResourceExternalEditor>,
|
||||
|};
|
||||
// Optional context:
|
||||
project?: ?gdProject,
|
||||
resourceSources?: ?Array<ResourceSource>,
|
||||
onChooseResource?: ?ChooseResourceFunction,
|
||||
resourceExternalEditors?: ?Array<ResourceExternalEditor>,
|
||||
|};
|
||||
|
||||
const styles = {
|
||||
columnContainer: {
|
||||
@@ -446,11 +439,16 @@ export default class PropertiesEditor extends React.Component<Props, {||}> {
|
||||
};
|
||||
|
||||
_renderResourceField = (field: ResourceField) => {
|
||||
if (!this.props.project) {
|
||||
if (
|
||||
!this.props.project ||
|
||||
!this.props.resourceSources ||
|
||||
!this.props.onChooseResource ||
|
||||
!this.props.resourceExternalEditors
|
||||
) {
|
||||
console.error(
|
||||
'You tried to display a resource field in a PropertiesEditor that does not support display resources. If you need to display resources, pass additional props (project, resourceSources, etc...)'
|
||||
'You tried to display a resource field in a PropertiesEditor that does not support display resources. If you need to display resources, pass additional props (project, resourceSources, onChooseResource, resourceExternalEditors).'
|
||||
);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const { setValue } = field;
|
||||
@@ -504,6 +502,10 @@ export default class PropertiesEditor extends React.Component<Props, {||}> {
|
||||
<UnsavedChangesContext.Consumer key={field.name}>
|
||||
{unsavedChanges => (
|
||||
<PropertiesEditor
|
||||
project={this.props.project}
|
||||
resourceSources={this.props.resourceSources}
|
||||
onChooseResource={this.props.onChooseResource}
|
||||
resourceExternalEditors={this.props.resourceExternalEditors}
|
||||
schema={field.children}
|
||||
instances={this.props.instances}
|
||||
mode="row"
|
||||
@@ -531,6 +533,12 @@ export default class PropertiesEditor extends React.Component<Props, {||}> {
|
||||
<UnsavedChangesContext.Consumer key={field.name}>
|
||||
{unsavedChanges => (
|
||||
<PropertiesEditor
|
||||
project={this.props.project}
|
||||
resourceSources={this.props.resourceSources}
|
||||
onChooseResource={this.props.onChooseResource}
|
||||
resourceExternalEditors={
|
||||
this.props.resourceExternalEditors
|
||||
}
|
||||
schema={field.children}
|
||||
instances={this.props.instances}
|
||||
mode="column"
|
||||
|
@@ -22,11 +22,6 @@ export const Open = () => {
|
||||
onChangeOutputPath={action('change output path')}
|
||||
onClose={() => action('click on close')()}
|
||||
onCreate={() => action('click on create')()}
|
||||
onClickGenerateProjectName={() =>
|
||||
action('click on generate new project name')()
|
||||
}
|
||||
projectName={projectName}
|
||||
onChangeProjectName={setProjectName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -41,11 +36,6 @@ export const Disabled = () => {
|
||||
onChangeOutputPath={action('change output path')}
|
||||
onClose={() => action('click on close')()}
|
||||
onCreate={() => action('click on create')()}
|
||||
onClickGenerateProjectName={() =>
|
||||
action('click on generate new project name')()
|
||||
}
|
||||
projectName={projectName}
|
||||
onChangeProjectName={setProjectName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user