mirror of
https://github.com/Heretek-AI/GDevelop-Heretek.git
synced 2026-10-12 02:02:42 +00:00
Instances can now be hidden at start from the properties panel (#8884)
+ AI tool can now set instance variables
This commit is contained in:
@@ -77,6 +77,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.SetResourceName("InGameEditor-OrbitCameraIcon")
|
||||
.SetFilePath("InGameEditor/OrbitCameraIcon.svg")
|
||||
.SetKind("internal-in-game-editor-only-svg");
|
||||
extension.AddInGameEditorResource()
|
||||
.SetResourceName("InGameEditor-HiddenInstanceIcon")
|
||||
.SetFilePath("InGameEditor/HiddenInstanceIcon.svg")
|
||||
.SetKind("internal-in-game-editor-only-svg");
|
||||
|
||||
gd::ObjectMetadata& obj = extension.AddObject<gd::ObjectConfiguration>(
|
||||
"", _("Base object"), _("Base object"), "res/functions/object_black.svg");
|
||||
|
||||
@@ -68,6 +68,7 @@ void InitialInstance::UnserializeFrom(gd::Project &project,
|
||||
SetLocked(element.GetBoolAttribute("locked", false));
|
||||
SetSealed(element.GetBoolAttribute("sealed", false));
|
||||
SetShouldKeepRatio(element.GetBoolAttribute("keepRatio", false));
|
||||
SetHidden(element.GetBoolAttribute("hidden", false));
|
||||
|
||||
persistentUuid = element.GetStringAttribute("persistentUuid");
|
||||
if (persistentUuid.empty()) ResetPersistentUuid();
|
||||
@@ -154,6 +155,7 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
|
||||
if (IsLocked()) element.SetAttribute("locked", IsLocked());
|
||||
if (IsSealed()) element.SetAttribute("sealed", IsSealed());
|
||||
if (ShouldKeepRatio()) element.SetAttribute("keepRatio", ShouldKeepRatio());
|
||||
if (IsHidden()) element.SetAttribute("hidden", IsHidden());
|
||||
|
||||
if (persistentUuid.empty()) persistentUuid = UUID::MakeUuid4();
|
||||
element.SetStringAttribute("persistentUuid", persistentUuid);
|
||||
|
||||
@@ -267,6 +267,19 @@ class GD_CORE_API InitialInstance {
|
||||
*/
|
||||
void SetShouldKeepRatio(bool enable = true) { keepRatio = enable; }
|
||||
|
||||
/**
|
||||
* \brief Return true if the instance is hidden when the scene starts.
|
||||
*/
|
||||
bool IsHidden() const { return hidden; };
|
||||
|
||||
/**
|
||||
* \brief Set if the instance is hidden when the scene starts.
|
||||
*
|
||||
* A hidden instance is still created but not displayed, until it is
|
||||
* shown with the "Show" action.
|
||||
*/
|
||||
void SetHidden(bool enable = true) { hidden = enable; }
|
||||
|
||||
///@}
|
||||
|
||||
/** \name Variable management
|
||||
@@ -483,6 +496,8 @@ class GD_CORE_API InitialInstance {
|
||||
bool sealed = false; ///< True if the instance is sealed
|
||||
bool keepRatio = true; ///< True if the instance's dimensions
|
||||
/// should keep the same ratio.
|
||||
bool hidden = false; ///< True if the instance is hidden when
|
||||
/// the scene starts.
|
||||
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
|
||||
/// useful for hot reloading.
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1" y="1" width="22" height="22" rx="5" fill="black" fill-opacity="0.6"/>
|
||||
<ellipse cx="12" cy="12" rx="8" ry="4.7" stroke="white" stroke-width="2"/>
|
||||
<circle cx="12" cy="12" r="2.3" fill="white"/>
|
||||
<line x1="3.5" y1="20.5" x2="20.5" y2="3.5" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 408 B |
@@ -849,6 +849,10 @@ namespace gdjs {
|
||||
private _selection = new Selection();
|
||||
private _selectionBoxes: Map<RuntimeObject, ObjectSelectionBoxHelper> =
|
||||
new Map();
|
||||
// Markers displayed on instances that are hidden when the scene starts
|
||||
// (which stay visible in the editor).
|
||||
private _hiddenInstanceMarkers: Map<RuntimeObject, THREE.Sprite> =
|
||||
new Map();
|
||||
private _objectMover = new ObjectMover(this);
|
||||
|
||||
private _wasMouseLeftButtonPressed = false;
|
||||
@@ -1130,6 +1134,7 @@ namespace gdjs {
|
||||
this._selectedLayerName = '';
|
||||
// Clear any reference to `RuntimeObject` from the unloaded scene.
|
||||
this._selectionBoxes.clear();
|
||||
this._hiddenInstanceMarkers.clear();
|
||||
this._selectionControls = null;
|
||||
this._draggedNewObject = null;
|
||||
this._draggedSelectedObject = null;
|
||||
@@ -2073,6 +2078,107 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a crossed-eye marker on every instance that is hidden when the
|
||||
* scene starts: those instances stay visible in the editor (so they can
|
||||
* be seen and manipulated), and the marker is what tells them apart.
|
||||
*/
|
||||
private _updateHiddenInstanceMarkers() {
|
||||
const editedInstanceContainer = this.getEditedInstanceContainer();
|
||||
const stillHiddenObjects = new Set<RuntimeObject>();
|
||||
if (editedInstanceContainer) {
|
||||
for (const object of editedInstanceContainer.getAdhocListOfAllInstances()) {
|
||||
if (!object.persistentUuid) continue;
|
||||
const instanceData = this._getInstanceData(object.persistentUuid);
|
||||
if (!instanceData || !instanceData.hidden) continue;
|
||||
|
||||
const objectLayer = this.getEditorLayer(object.getLayer());
|
||||
const threeGroup =
|
||||
objectLayer && objectLayer.getRenderer().getThreeGroup();
|
||||
if (!threeGroup) continue;
|
||||
|
||||
let marker = this._hiddenInstanceMarkers.get(object);
|
||||
if (!marker) {
|
||||
marker = makeHiddenInstanceMarker(this._runtimeGame);
|
||||
this._hiddenInstanceMarkers.set(object, marker);
|
||||
}
|
||||
// Also handles the object having changed of layer.
|
||||
if (marker.parent !== threeGroup) {
|
||||
threeGroup.add(marker);
|
||||
}
|
||||
const width = object.getWidth();
|
||||
const height = object.getHeight();
|
||||
const depth = is3D(object) ? object.getDepth() : 0;
|
||||
const boundingRadius =
|
||||
Math.sqrt(width * width + height * height + depth * depth) / 2;
|
||||
|
||||
// The marker size follows the size of the object (so it never
|
||||
// dwarfs a small object seen from afar, and stays legible on a big
|
||||
// object seen from close), within absolute limits.
|
||||
let markerSize = Math.min(Math.max(boundingRadius * 0.4, 16), 128);
|
||||
|
||||
// Start from the true center of the object (the world can be
|
||||
// navigated from any angle in 3D)...
|
||||
marker.position.set(
|
||||
object.getCenterXInScene(),
|
||||
object.getCenterYInScene(),
|
||||
is3D(object) ? object.getZ() + object.getDepth() / 2 : 0
|
||||
);
|
||||
// ...then move the marker towards the camera, just out of the
|
||||
// object's bounding sphere: on screen it stays at the center of the
|
||||
// object, but the object can't occlude its own marker - while
|
||||
// other objects in front of it properly do (the marker is
|
||||
// depth-tested).
|
||||
const threeCamera = objectLayer.getRenderer().getThreeCamera();
|
||||
if (threeCamera) {
|
||||
const cameraLocalPosition = threeGroup.worldToLocal(
|
||||
threeCamera.getWorldPosition(temporaryVector3)
|
||||
);
|
||||
const towardsCamera = cameraLocalPosition.sub(marker.position);
|
||||
const cameraDistance = towardsCamera.length();
|
||||
if (cameraDistance > 0) {
|
||||
// Don't go past the camera if it's inside the bounding sphere.
|
||||
const offset = Math.min(
|
||||
boundingRadius * 1.05,
|
||||
cameraDistance * 0.9
|
||||
);
|
||||
marker.position.addScaledVector(
|
||||
towardsCamera.multiplyScalar(1 / cameraDistance),
|
||||
offset
|
||||
);
|
||||
|
||||
// When the camera gets close to a (big) object, the marker
|
||||
// would take a lot of space on screen: cap its size to a
|
||||
// fraction of the viewport height.
|
||||
if (threeCamera instanceof THREE.PerspectiveCamera) {
|
||||
const markerCameraDistance = Math.max(
|
||||
cameraDistance - offset,
|
||||
1
|
||||
);
|
||||
const viewportWorldHeightAtMarker =
|
||||
2 *
|
||||
markerCameraDistance *
|
||||
Math.tan(THREE.MathUtils.degToRad(threeCamera.fov / 2));
|
||||
markerSize = Math.min(
|
||||
markerSize,
|
||||
viewportWorldHeightAtMarker * 0.05
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
marker.scale.set(markerSize, markerSize, 1);
|
||||
stillHiddenObjects.add(object);
|
||||
}
|
||||
}
|
||||
|
||||
this._hiddenInstanceMarkers.forEach((marker, object) => {
|
||||
if (!stillHiddenObjects.has(object)) {
|
||||
marker.removeFromParent();
|
||||
this._hiddenInstanceMarkers.delete(object);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _updateSelectionOutline({
|
||||
objectUnderCursor,
|
||||
}: {
|
||||
@@ -2658,6 +2764,9 @@ namespace gdjs {
|
||||
depth: depth === defaultDepth ? undefined : depth,
|
||||
locked: oldData ? oldData.locked : false,
|
||||
sealed: oldData ? oldData.sealed : false,
|
||||
// Not modified by the InGameEditor (which always shows instances,
|
||||
// even those hidden at start), but must be preserved:
|
||||
hidden: oldData ? oldData.hidden : undefined,
|
||||
// TODO: how to transmit/should we transmit other properties?
|
||||
numberProperties: [],
|
||||
stringProperties: [],
|
||||
@@ -3549,6 +3658,7 @@ namespace gdjs {
|
||||
this._updateSelectionBox();
|
||||
this._handleSelection({ objectUnderCursor });
|
||||
this._updateSelectionOutline({ objectUnderCursor });
|
||||
this._updateHiddenInstanceMarkers();
|
||||
// Custom objects only update their position at the end of the frame
|
||||
// because they don't override position setters like built-in objects do.
|
||||
// Since the instance position is not yet set when `onCreated` is called,
|
||||
@@ -5011,6 +5121,79 @@ namespace gdjs {
|
||||
}
|
||||
};
|
||||
|
||||
const temporaryVector3 = new THREE.Vector3();
|
||||
|
||||
// The material is shared by all the markers, and stays invisible until the
|
||||
// icon texture is loaded.
|
||||
let hiddenInstanceMarkerMaterial: THREE.SpriteMaterial | null = null;
|
||||
let hiddenInstanceMarkerTextureLoadStarted = false;
|
||||
|
||||
const getOrLoadHiddenInstanceMarkerMaterial = (
|
||||
game: RuntimeGame
|
||||
): THREE.SpriteMaterial => {
|
||||
if (!hiddenInstanceMarkerMaterial) {
|
||||
hiddenInstanceMarkerMaterial = new THREE.SpriteMaterial({
|
||||
// The marker has a size in the world, based on the size of the object
|
||||
// (see `_updateHiddenInstanceMarkers`): like the object, it gets
|
||||
// smaller when far away and bigger when close.
|
||||
sizeAttenuation: true,
|
||||
// The marker is depth-tested: an object between the camera and the
|
||||
// hidden instance occludes its marker (the marker is moved towards the
|
||||
// camera, out of the instance itself, so the instance can't occlude
|
||||
// its own marker - see `_updateHiddenInstanceMarkers`). Writing the
|
||||
// depth also protects the marker from the plane showing the 2D
|
||||
// objects, which is rendered afterwards (with
|
||||
// `renderOrder = Number.MAX_SAFE_INTEGER`).
|
||||
depthTest: true,
|
||||
depthWrite: true,
|
||||
// Discard the fully transparent pixels of the marker, so they don't
|
||||
// write the depth (which would punch an invisible rectangle in the
|
||||
// plane showing the 2D objects, rendered afterwards).
|
||||
alphaTest: 0.1,
|
||||
transparent: true,
|
||||
// Nothing to show until the icon texture is loaded.
|
||||
visible: false,
|
||||
});
|
||||
}
|
||||
if (!hiddenInstanceMarkerTextureLoadStarted) {
|
||||
hiddenInstanceMarkerTextureLoadStarted = true;
|
||||
|
||||
const url = getSvgIconUrl(game, 'InGameEditor-HiddenInstanceIcon');
|
||||
if (url) {
|
||||
const image = new Image();
|
||||
image.crossOrigin = 'anonymous';
|
||||
image.onload = () => {
|
||||
if (!hiddenInstanceMarkerMaterial) return;
|
||||
|
||||
// Rasterize the SVG at a fixed resolution (its intrinsic size is
|
||||
// small), so the texture stays crisp at the biggest marker sizes.
|
||||
const size = 128;
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
const context = canvas.getContext('2d');
|
||||
if (!context) return;
|
||||
context.drawImage(image, 0, 0, size, size);
|
||||
|
||||
hiddenInstanceMarkerMaterial.map = new THREE.CanvasTexture(canvas);
|
||||
hiddenInstanceMarkerMaterial.visible = true;
|
||||
hiddenInstanceMarkerMaterial.needsUpdate = true;
|
||||
};
|
||||
image.src = url;
|
||||
}
|
||||
}
|
||||
return hiddenInstanceMarkerMaterial;
|
||||
};
|
||||
|
||||
const makeHiddenInstanceMarker = (game: RuntimeGame): THREE.Sprite => {
|
||||
const marker = new THREE.Sprite(
|
||||
getOrLoadHiddenInstanceMarkerMaterial(game)
|
||||
);
|
||||
// Never picked by the editor raycasts (used to select objects).
|
||||
marker.raycast = () => {};
|
||||
return marker;
|
||||
};
|
||||
|
||||
class ObjectSelectionBoxHelper {
|
||||
object: gdjs.RuntimeObject;
|
||||
dummyObject3DForObject2D: THREE.Object3D | null = null;
|
||||
|
||||
@@ -379,6 +379,12 @@ namespace gdjs {
|
||||
|
||||
newObject.setZOrder(instanceData.zOrder);
|
||||
newObject.setLayer(instanceData.layer);
|
||||
// Instances hidden at start are not hidden in the in-game editor:
|
||||
// they must stay visible to be seen and manipulated (like in the
|
||||
// 2D editor).
|
||||
if (instanceData.hidden && !this.getGame().isInGameEdition()) {
|
||||
newObject.hide(true);
|
||||
}
|
||||
newObject
|
||||
.getVariables()
|
||||
.initFrom(instanceData.initialVariables, true);
|
||||
|
||||
@@ -1857,6 +1857,15 @@ namespace gdjs {
|
||||
runtimeObject.setLayer(newInstance.layer);
|
||||
somethingChanged = true;
|
||||
}
|
||||
if (
|
||||
// Instances hidden at start are not hidden in the in-game editor:
|
||||
// they must stay visible to be seen and manipulated.
|
||||
!this._runtimeGame.isInGameEdition() &&
|
||||
!oldInstance.hidden !== !newInstance.hidden
|
||||
) {
|
||||
runtimeObject.hide(!!newInstance.hidden);
|
||||
somethingChanged = true;
|
||||
}
|
||||
if (gdjs.Base3DHandler && gdjs.Base3DHandler.is3D(runtimeObject)) {
|
||||
if (oldInstance.z !== newInstance.z) {
|
||||
runtimeObject.setZ(newInstance.z || 0);
|
||||
|
||||
Vendored
+2
@@ -445,6 +445,8 @@ declare interface InstanceData extends InstancePersistentUuidData {
|
||||
layer: string;
|
||||
locked?: boolean;
|
||||
sealed?: boolean;
|
||||
/** True if the instance starts hidden (it can be shown with the "Show" action). */
|
||||
hidden?: boolean;
|
||||
name: string;
|
||||
|
||||
x: number;
|
||||
|
||||
@@ -310,6 +310,99 @@ describe('gdjs.HotReloader._hotReloadRuntimeGame', () => {
|
||||
expect(instances[1].getY()).to.be(234);
|
||||
});
|
||||
|
||||
it('can hide then show again an instance at hot-reload', async () => {
|
||||
const oldProjectData = createProjectData({
|
||||
layouts: [
|
||||
createSceneData({
|
||||
instances: [{ persistentUuid: '1', x: 111, y: 123 }],
|
||||
}),
|
||||
],
|
||||
});
|
||||
const runtimeGame = new gdjs.RuntimeGame(oldProjectData);
|
||||
const hotReloader = new gdjs.HotReloader(runtimeGame);
|
||||
await runtimeGame.loadFirstAssetsAndStartBackgroundLoading(
|
||||
'Scene1',
|
||||
() => {}
|
||||
);
|
||||
runtimeGame._sceneStack.push('Scene1');
|
||||
const scene = runtimeGame.getSceneStack().getCurrentScene();
|
||||
if (!scene) throw new Error("Couldn't set a current scene for testing.");
|
||||
|
||||
expect(scene.getInstancesOf('MyObject')[0].isVisible()).to.be(true);
|
||||
|
||||
// Mark the instance as hidden at start (the serializer only writes the
|
||||
// attribute when true).
|
||||
const hiddenProjectData = createProjectData({
|
||||
layouts: [
|
||||
createSceneData({
|
||||
instances: [{ persistentUuid: '1', x: 111, y: 123, hidden: true }],
|
||||
}),
|
||||
],
|
||||
});
|
||||
await hotReloader._hotReloadRuntimeGame(
|
||||
oldProjectData,
|
||||
hiddenProjectData,
|
||||
[],
|
||||
runtimeGame
|
||||
);
|
||||
expect(scene.getInstancesOf('MyObject')[0].isVisible()).to.be(false);
|
||||
|
||||
// Mark it as visible again: the attribute is absent from the data, like
|
||||
// the serializer produces.
|
||||
const visibleAgainProjectData = createProjectData({
|
||||
layouts: [
|
||||
createSceneData({
|
||||
instances: [{ persistentUuid: '1', x: 111, y: 123 }],
|
||||
}),
|
||||
],
|
||||
});
|
||||
await hotReloader._hotReloadRuntimeGame(
|
||||
hiddenProjectData,
|
||||
visibleAgainProjectData,
|
||||
[],
|
||||
runtimeGame
|
||||
);
|
||||
expect(scene.getInstancesOf('MyObject')[0].isVisible()).to.be(true);
|
||||
});
|
||||
|
||||
it('keeps instances hidden at start visible when in the in-game editor', async () => {
|
||||
const oldProjectData = createProjectData({
|
||||
layouts: [
|
||||
createSceneData({
|
||||
instances: [{ persistentUuid: '1', x: 111, y: 123 }],
|
||||
}),
|
||||
],
|
||||
});
|
||||
const runtimeGame = new gdjs.RuntimeGame(oldProjectData);
|
||||
// Simulate the in-game editor (where hidden instances must stay visible,
|
||||
// to be seen and manipulated).
|
||||
runtimeGame.isInGameEdition = () => true;
|
||||
const hotReloader = new gdjs.HotReloader(runtimeGame);
|
||||
await runtimeGame.loadFirstAssetsAndStartBackgroundLoading(
|
||||
'Scene1',
|
||||
() => {}
|
||||
);
|
||||
runtimeGame._sceneStack.push('Scene1');
|
||||
const scene = runtimeGame.getSceneStack().getCurrentScene();
|
||||
if (!scene) throw new Error("Couldn't set a current scene for testing.");
|
||||
|
||||
const hiddenProjectData = createProjectData({
|
||||
layouts: [
|
||||
createSceneData({
|
||||
instances: [{ persistentUuid: '1', x: 111, y: 123, hidden: true }],
|
||||
}),
|
||||
],
|
||||
});
|
||||
await hotReloader._hotReloadRuntimeGame(
|
||||
oldProjectData,
|
||||
hiddenProjectData,
|
||||
[],
|
||||
runtimeGame
|
||||
);
|
||||
// The instance is hidden at start, but stays visible in the editor.
|
||||
expect(scene.getInstancesOf('MyObject')[0].isVisible()).to.be(true);
|
||||
});
|
||||
|
||||
it('can move instances of a global object at hot-reload', async () => {
|
||||
const oldProjectData = createProjectData({
|
||||
globalObjects: [createSpriteData({ name: 'MyGlobalObject' })],
|
||||
|
||||
@@ -1489,6 +1489,8 @@ interface InitialInstance {
|
||||
void SetSealed(boolean seal);
|
||||
boolean ShouldKeepRatio();
|
||||
void SetShouldKeepRatio(boolean keepRatio);
|
||||
boolean IsHidden();
|
||||
void SetHidden(boolean hidden);
|
||||
long GetZOrder();
|
||||
void SetZOrder(long zOrder);
|
||||
long GetOpacity();
|
||||
|
||||
@@ -775,6 +775,9 @@ describe('libGD.js', function () {
|
||||
expect(initialInstance.getLayer()).toBe('MyLayer');
|
||||
initialInstance.setLocked(true);
|
||||
expect(initialInstance.isLocked()).toBe(true);
|
||||
expect(initialInstance.isHidden()).toBe(false);
|
||||
initialInstance.setHidden(true);
|
||||
expect(initialInstance.isHidden()).toBe(true);
|
||||
initialInstance.setHasCustomSize(true);
|
||||
expect(initialInstance.hasCustomSize()).toBe(true);
|
||||
initialInstance.setCustomWidth(34);
|
||||
@@ -820,6 +823,7 @@ describe('libGD.js', function () {
|
||||
expect(initialInstance2.getZOrder()).toBe(12);
|
||||
expect(initialInstance2.getLayer()).toBe('MyLayer');
|
||||
expect(initialInstance2.isLocked()).toBe(true);
|
||||
expect(initialInstance2.isHidden()).toBe(true);
|
||||
expect(initialInstance2.hasCustomSize()).toBe(true);
|
||||
expect(initialInstance2.hasCustomDepth()).toBe(false);
|
||||
expect(initialInstance2.getCustomWidth()).toBe(34);
|
||||
|
||||
Vendored
+2
@@ -1255,6 +1255,8 @@ export class InitialInstance extends EmscriptenObject {
|
||||
setSealed(seal: boolean): void;
|
||||
shouldKeepRatio(): boolean;
|
||||
setShouldKeepRatio(keepRatio: boolean): void;
|
||||
isHidden(): boolean;
|
||||
setHidden(hidden: boolean): void;
|
||||
getZOrder(): number;
|
||||
setZOrder(zOrder: number): void;
|
||||
getOpacity(): number;
|
||||
|
||||
@@ -21,6 +21,8 @@ declare class gdInitialInstance {
|
||||
setSealed(seal: boolean): void;
|
||||
shouldKeepRatio(): boolean;
|
||||
setShouldKeepRatio(keepRatio: boolean): void;
|
||||
isHidden(): boolean;
|
||||
setHidden(hidden: boolean): void;
|
||||
getZOrder(): number;
|
||||
setZOrder(zOrder: number): void;
|
||||
getOpacity(): number;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1" y="1" width="22" height="22" rx="5" fill="black" fill-opacity="0.6"/>
|
||||
<ellipse cx="12" cy="12" rx="8" ry="4.7" stroke="white" stroke-width="2"/>
|
||||
<circle cx="12" cy="12" r="2.3" fill="white"/>
|
||||
<line x1="3.5" y1="20.5" x2="20.5" y2="3.5" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 408 B |
@@ -101,7 +101,7 @@ export const useRefreshLimits = (
|
||||
// The tools of the orchestrator AND of the sub-agents it creates server-side.
|
||||
// Only bump it once the matching prompts and generation-api are deployed;
|
||||
// reverting it is the flip-back (every past version stays served).
|
||||
export const AI_ORCHESTRATOR_TOOLS_VERSION = 'v12';
|
||||
export const AI_ORCHESTRATOR_TOOLS_VERSION = 'v13';
|
||||
|
||||
/**
|
||||
* A pending request for the user to approve (or refuse) a project-modifying
|
||||
|
||||
@@ -143,7 +143,7 @@ describe('add_or_edit_variable', () => {
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe(
|
||||
'Invalid "variable_scope": "world". Use `scene`, `object`, `group` or `global`.'
|
||||
'Invalid "variable_scope": "world". Use `scene`, `object`, `group`, `instance` or `global`.'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -288,3 +288,198 @@ describe('add_or_edit_variable', () => {
|
||||
expect(project.getVariables().has('orphan')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('add_or_edit_variable (instance scope)', () => {
|
||||
let project: gdProject;
|
||||
let testScene: gdLayout;
|
||||
let doorInstance1: gdInitialInstance;
|
||||
let doorInstance2: gdInitialInstance;
|
||||
let playerInstance: gdInitialInstance;
|
||||
|
||||
const getIdOf = (instance: gdInitialInstance): string =>
|
||||
instance.getPersistentUuid().slice(0, 10);
|
||||
|
||||
beforeEach(() => {
|
||||
// $FlowFixMe[invalid-constructor]
|
||||
project = new gd.ProjectHelper.createNewGDJSProject();
|
||||
testScene = project.insertNewLayout('TestScene', 0);
|
||||
testScene.getObjects().insertNewObject(project, 'Sprite', 'Door', 0);
|
||||
testScene.getObjects().insertNewObject(project, 'Sprite', 'Player', 0);
|
||||
|
||||
doorInstance1 = testScene.getInitialInstances().insertNewInitialInstance();
|
||||
doorInstance1.setObjectName('Door');
|
||||
doorInstance2 = testScene.getInitialInstances().insertNewInitialInstance();
|
||||
doorInstance2.setObjectName('Door');
|
||||
playerInstance = testScene.getInitialInstances().insertNewInitialInstance();
|
||||
playerInstance.setObjectName('Player');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
project.delete();
|
||||
});
|
||||
|
||||
const addOrEditVariable = async (args: any) =>
|
||||
editorFunctions.add_or_edit_variable.launchFunction({
|
||||
...makeFakeLaunchFunctionOptionsWithProject(project),
|
||||
args,
|
||||
});
|
||||
|
||||
it('sets a variable on a single instance, leaving the others and the object untouched', async () => {
|
||||
const result = await addOrEditVariable({
|
||||
variable_scope: 'instance',
|
||||
scene_name: 'TestScene',
|
||||
instance_id: getIdOf(doorInstance1),
|
||||
variable_name_or_path: 'LevelNumber',
|
||||
value: '3',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toContain('Added instance');
|
||||
expect(result.message).toContain(`"${getIdOf(doorInstance1)}" (Door)`);
|
||||
expect(result.message).toContain('variable "LevelNumber" (Number) = 3');
|
||||
|
||||
expect(doorInstance1.getVariables().has('LevelNumber')).toBe(true);
|
||||
expect(
|
||||
doorInstance1
|
||||
.getVariables()
|
||||
.get('LevelNumber')
|
||||
.getValue()
|
||||
).toBe(3);
|
||||
// The other instances and the shared object variables are untouched.
|
||||
expect(doorInstance2.getVariables().has('LevelNumber')).toBe(false);
|
||||
expect(playerInstance.getVariables().has('LevelNumber')).toBe(false);
|
||||
expect(
|
||||
testScene
|
||||
.getObjects()
|
||||
.getObject('Door')
|
||||
.getVariables()
|
||||
.has('LevelNumber')
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('sets several variables (including nested paths) on one instance', async () => {
|
||||
const result = await addOrEditVariable({
|
||||
variable_scope: 'instance',
|
||||
scene_name: 'TestScene',
|
||||
instance_id: getIdOf(doorInstance1),
|
||||
variables: [
|
||||
{ variable_name_or_path: 'IsOpen', value: 'false' },
|
||||
{ variable_name_or_path: 'Config.Speed', value: '12' },
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(
|
||||
doorInstance1
|
||||
.getVariables()
|
||||
.get('IsOpen')
|
||||
.getBool()
|
||||
).toBe(false);
|
||||
// Nested paths work, backed by the same ApplyVariableChange helpers.
|
||||
expect(
|
||||
doorInstance1
|
||||
.getVariables()
|
||||
.get('Config')
|
||||
.getChild('Speed')
|
||||
.getValue()
|
||||
).toBe(12);
|
||||
// The other instances are untouched: one call changes one instance.
|
||||
expect(doorInstance2.getVariables().has('IsOpen')).toBe(false);
|
||||
expect(playerInstance.getVariables().has('IsOpen')).toBe(false);
|
||||
});
|
||||
|
||||
it('deletes an instance variable', async () => {
|
||||
doorInstance1
|
||||
.getVariables()
|
||||
.insertNew('Obsolete', 0)
|
||||
.setValue(1);
|
||||
|
||||
const result = await addOrEditVariable({
|
||||
variable_scope: 'instance',
|
||||
scene_name: 'TestScene',
|
||||
instance_id: getIdOf(doorInstance1),
|
||||
variables: [
|
||||
{ variable_name_or_path: 'Obsolete', delete_this_variable: true },
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toContain('Deleted instance');
|
||||
expect(doorInstance1.getVariables().has('Obsolete')).toBe(false);
|
||||
});
|
||||
|
||||
it('fails without scene_name', async () => {
|
||||
const result = await addOrEditVariable({
|
||||
variable_scope: 'instance',
|
||||
instance_id: getIdOf(doorInstance1),
|
||||
variable_name_or_path: 'LevelNumber',
|
||||
value: '3',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toContain('Missing "scene_name"');
|
||||
});
|
||||
|
||||
it('fails without instance_id', async () => {
|
||||
const result = await addOrEditVariable({
|
||||
variable_scope: 'instance',
|
||||
scene_name: 'TestScene',
|
||||
variable_name_or_path: 'LevelNumber',
|
||||
value: '3',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toContain('Missing "instance_id"');
|
||||
expect(result.message).toContain('describe_instances');
|
||||
});
|
||||
|
||||
it('fails (and changes nothing) when the id is unknown', async () => {
|
||||
const result = await addOrEditVariable({
|
||||
variable_scope: 'instance',
|
||||
scene_name: 'TestScene',
|
||||
instance_id: 'ffffffffff',
|
||||
variable_name_or_path: 'LevelNumber',
|
||||
value: '3',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toContain('ffffffffff');
|
||||
expect(result.message).toContain('describe_instances');
|
||||
expect(doorInstance1.getVariables().has('LevelNumber')).toBe(false);
|
||||
});
|
||||
|
||||
it('fails when an id belongs to another object than object_name', async () => {
|
||||
const result = await addOrEditVariable({
|
||||
variable_scope: 'instance',
|
||||
scene_name: 'TestScene',
|
||||
object_name: 'Door',
|
||||
instance_id: getIdOf(playerInstance),
|
||||
variable_name_or_path: 'LevelNumber',
|
||||
value: '3',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toContain('not an instance of object "Door"');
|
||||
expect(result.message).toContain('(instance of "Player")');
|
||||
expect(playerInstance.getVariables().has('LevelNumber')).toBe(false);
|
||||
});
|
||||
|
||||
it('inspect_variables works with the instance scope', async () => {
|
||||
doorInstance1
|
||||
.getVariables()
|
||||
.insertNew('LevelNumber', 0)
|
||||
.setValue(7);
|
||||
|
||||
const result = await editorFunctions.inspect_variables.launchFunction({
|
||||
...makeFakeLaunchFunctionOptionsWithProject(project),
|
||||
args: {
|
||||
variable_scope: 'instance',
|
||||
scene_name: 'TestScene',
|
||||
instance_id: getIdOf(doorInstance1),
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(JSON.stringify(result.variables)).toContain('LevelNumber');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,3 +115,98 @@ describe('describe_instances', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('describe_instances (per-instance state)', () => {
|
||||
let project: gdProject;
|
||||
let testScene: gdLayout;
|
||||
|
||||
beforeEach(() => {
|
||||
// $FlowFixMe[invalid-constructor]
|
||||
project = new gd.ProjectHelper.createNewGDJSProject();
|
||||
testScene = project.insertNewLayout('TestScene', 0);
|
||||
testScene.getObjects().insertNewObject(project, 'Sprite', 'Player', 0);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
project.delete();
|
||||
});
|
||||
|
||||
const describeInstances = async () => {
|
||||
const result: EditorFunctionGenericOutput = await editorFunctions.describe_instances.launchFunction(
|
||||
{
|
||||
...makeFakeLaunchFunctionOptionsWithProject(project),
|
||||
args: { scene_name: 'TestScene' },
|
||||
}
|
||||
);
|
||||
expect(result.success).toBe(true);
|
||||
return result.instances || [];
|
||||
};
|
||||
|
||||
it('reports the per-instance variables, only when the instance has some', async () => {
|
||||
const plainInstance = testScene
|
||||
.getInitialInstances()
|
||||
.insertNewInitialInstance();
|
||||
plainInstance.setObjectName('Player');
|
||||
const instanceWithVariables = testScene
|
||||
.getInitialInstances()
|
||||
.insertNewInitialInstance();
|
||||
instanceWithVariables.setObjectName('Player');
|
||||
instanceWithVariables
|
||||
.getVariables()
|
||||
.insertNew('LevelNumber', 0)
|
||||
.setValue(3);
|
||||
|
||||
const instances = await describeInstances();
|
||||
expect(instances).toHaveLength(2);
|
||||
const plain = instances.find(
|
||||
instance => instance.id === plainInstance.getPersistentUuid().slice(0, 10)
|
||||
);
|
||||
const withVariables = instances.find(
|
||||
instance =>
|
||||
instance.id === instanceWithVariables.getPersistentUuid().slice(0, 10)
|
||||
);
|
||||
if (!plain || !withVariables)
|
||||
throw new Error('Expected to find both instances in the description.');
|
||||
|
||||
// No key at all when the instance has no variable of its own.
|
||||
expect(plain.initialVariables).toBeUndefined();
|
||||
expect(withVariables.initialVariables).toHaveLength(1);
|
||||
expect(withVariables.initialVariables[0]).toMatchObject({
|
||||
name: 'LevelNumber',
|
||||
type: 'number',
|
||||
value: 3,
|
||||
});
|
||||
// Internal per-instance properties stay unexposed.
|
||||
instances.forEach(instance => {
|
||||
expect(instance.numberProperties).toBeUndefined();
|
||||
expect(instance.stringProperties).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('reports hidden only when the instance starts hidden', async () => {
|
||||
const visibleInstance = testScene
|
||||
.getInitialInstances()
|
||||
.insertNewInitialInstance();
|
||||
visibleInstance.setObjectName('Player');
|
||||
const hiddenInstance = testScene
|
||||
.getInitialInstances()
|
||||
.insertNewInitialInstance();
|
||||
hiddenInstance.setObjectName('Player');
|
||||
hiddenInstance.setHidden(true);
|
||||
|
||||
const instances = await describeInstances();
|
||||
const visible = instances.find(
|
||||
instance =>
|
||||
instance.id === visibleInstance.getPersistentUuid().slice(0, 10)
|
||||
);
|
||||
const hidden = instances.find(
|
||||
instance =>
|
||||
instance.id === hiddenInstance.getPersistentUuid().slice(0, 10)
|
||||
);
|
||||
if (!visible || !hidden)
|
||||
throw new Error('Expected to find both instances in the description.');
|
||||
|
||||
expect(visible.hidden).toBeUndefined();
|
||||
expect(hidden.hidden).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -346,3 +346,137 @@ describe('put_2d_instances (modifications of existing instances)', () => {
|
||||
expect(positions).toEqual([{ x: 0, y: 50 }, { x: 300, y: 50 }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('put_2d_instances (instances_hidden)', () => {
|
||||
let project: gdProject;
|
||||
let testScene: gdLayout;
|
||||
|
||||
beforeEach(() => {
|
||||
// $FlowFixMe[invalid-constructor]
|
||||
project = new gd.ProjectHelper.createNewGDJSProject();
|
||||
testScene = project.insertNewLayout('TestScene', 0);
|
||||
testScene.getObjects().insertNewObject(project, 'Sprite', 'Player', 0);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
project.delete();
|
||||
});
|
||||
|
||||
const getRawInstances = (scene: gdLayout): Array<gdInitialInstance> => {
|
||||
const instances = [];
|
||||
const functor = new gd.InitialInstanceJSFunctor();
|
||||
// $FlowFixMe[cannot-write]
|
||||
functor.invoke = instancePtr => {
|
||||
const instance: gdInitialInstance = gd.wrapPointer(
|
||||
// $FlowFixMe[incompatible-type]
|
||||
instancePtr,
|
||||
gd.InitialInstance
|
||||
);
|
||||
instances.push(instance);
|
||||
};
|
||||
// $FlowFixMe[incompatible-type]
|
||||
scene.getInitialInstances().iterateOverInstances(functor);
|
||||
functor.delete();
|
||||
return instances;
|
||||
};
|
||||
|
||||
const putInstances = async (args: any) =>
|
||||
editorFunctions.put_2d_instances.launchFunction({
|
||||
...makeFakeLaunchFunctionOptionsWithProject(project),
|
||||
args: {
|
||||
scene_name: 'TestScene',
|
||||
object_name: 'Player',
|
||||
layer_name: '',
|
||||
...args,
|
||||
},
|
||||
});
|
||||
|
||||
it('creates instances hidden at start', async () => {
|
||||
const result = await putInstances({
|
||||
brush_kind: 'point',
|
||||
brush_position: '100,200',
|
||||
new_instances_count: 1,
|
||||
instances_hidden: true,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toEqual(expect.stringContaining('hidden at start'));
|
||||
const [created] = getRawInstances(testScene);
|
||||
expect(created.isHidden()).toBe(true);
|
||||
});
|
||||
|
||||
it('hides then shows an existing instance with the none brush', async () => {
|
||||
await putInstances({
|
||||
brush_kind: 'point',
|
||||
brush_position: '100,200',
|
||||
new_instances_count: 1,
|
||||
});
|
||||
const [created] = getRawInstances(testScene);
|
||||
expect(created.isHidden()).toBe(false);
|
||||
const id = created.getPersistentUuid().slice(0, 10);
|
||||
|
||||
const hideResult = await putInstances({
|
||||
brush_kind: 'none',
|
||||
existing_instance_ids: id,
|
||||
instances_hidden: true,
|
||||
});
|
||||
expect(hideResult.success).toBe(true);
|
||||
expect(hideResult.message).toEqual(
|
||||
expect.stringContaining(
|
||||
'Marked 1 instance of "Player" as hidden at start'
|
||||
)
|
||||
);
|
||||
expect(created.isHidden()).toBe(true);
|
||||
|
||||
const showResult = await putInstances({
|
||||
brush_kind: 'none',
|
||||
existing_instance_ids: id,
|
||||
instances_hidden: false,
|
||||
});
|
||||
expect(showResult.success).toBe(true);
|
||||
expect(showResult.message).toEqual(
|
||||
expect.stringContaining(
|
||||
'Marked 1 instance of "Player" as visible at start'
|
||||
)
|
||||
);
|
||||
expect(created.isHidden()).toBe(false);
|
||||
});
|
||||
|
||||
it('leaves the hidden flag untouched when instances_hidden is omitted', async () => {
|
||||
await putInstances({
|
||||
brush_kind: 'point',
|
||||
brush_position: '100,200',
|
||||
new_instances_count: 1,
|
||||
instances_hidden: true,
|
||||
});
|
||||
const [created] = getRawInstances(testScene);
|
||||
const id = created.getPersistentUuid().slice(0, 10);
|
||||
|
||||
const result = await putInstances({
|
||||
brush_kind: 'none',
|
||||
existing_instance_ids: id,
|
||||
instances_z_order: 5,
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
expect(created.isHidden()).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts instances_hidden alone as a change on existing instances', async () => {
|
||||
await putInstances({
|
||||
brush_kind: 'point',
|
||||
brush_position: '100,200',
|
||||
new_instances_count: 1,
|
||||
});
|
||||
const [created] = getRawInstances(testScene);
|
||||
|
||||
// instances_hidden must count as a requested mutation (no
|
||||
// "no change was requested" failure), even when it is the only parameter.
|
||||
const result = await putInstances({
|
||||
brush_kind: 'none',
|
||||
existing_instance_ids: created.getPersistentUuid().slice(0, 10),
|
||||
instances_hidden: true,
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
expect(created.isHidden()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -329,3 +329,86 @@ describe('put_3d_instances (modifications of existing instances)', () => {
|
||||
expect(instances[0].z).toBe(30);
|
||||
});
|
||||
});
|
||||
|
||||
describe('put_3d_instances (instances_hidden)', () => {
|
||||
let project: gdProject;
|
||||
let testScene: gdLayout;
|
||||
|
||||
beforeEach(() => {
|
||||
makeTestExtensions(gd);
|
||||
// $FlowFixMe[invalid-constructor]
|
||||
project = new gd.ProjectHelper.createNewGDJSProject();
|
||||
testScene = project.insertNewLayout('TestScene', 0);
|
||||
testScene
|
||||
.getObjects()
|
||||
.insertNewObject(project, 'FakeScene3D::Cube3DObject', 'Cube', 0);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
project.delete();
|
||||
});
|
||||
|
||||
const getRawInstances = (scene: gdLayout): Array<gdInitialInstance> => {
|
||||
const instances = [];
|
||||
const functor = new gd.InitialInstanceJSFunctor();
|
||||
// $FlowFixMe[cannot-write]
|
||||
functor.invoke = instancePtr => {
|
||||
const instance: gdInitialInstance = gd.wrapPointer(
|
||||
// $FlowFixMe[incompatible-type]
|
||||
instancePtr,
|
||||
gd.InitialInstance
|
||||
);
|
||||
instances.push(instance);
|
||||
};
|
||||
// $FlowFixMe[incompatible-type]
|
||||
scene.getInitialInstances().iterateOverInstances(functor);
|
||||
functor.delete();
|
||||
return instances;
|
||||
};
|
||||
|
||||
const putInstances = async (args: any) =>
|
||||
editorFunctions.put_3d_instances.launchFunction({
|
||||
...makeFakeLaunchFunctionOptionsWithProject(project),
|
||||
args: {
|
||||
scene_name: 'TestScene',
|
||||
object_name: 'Cube',
|
||||
layer_name: '',
|
||||
...args,
|
||||
},
|
||||
});
|
||||
|
||||
it('creates 3D instances hidden at start', async () => {
|
||||
const result = await putInstances({
|
||||
brush_kind: 'point',
|
||||
brush_position: '10,20,30',
|
||||
new_instances_count: 1,
|
||||
instances_hidden: true,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toEqual(expect.stringContaining('hidden at start'));
|
||||
const [created] = getRawInstances(testScene);
|
||||
expect(created.isHidden()).toBe(true);
|
||||
});
|
||||
|
||||
it('hides an existing 3D instance with the none brush, alone as a change', async () => {
|
||||
await putInstances({
|
||||
brush_kind: 'point',
|
||||
brush_position: '10,20,30',
|
||||
new_instances_count: 1,
|
||||
});
|
||||
const [created] = getRawInstances(testScene);
|
||||
expect(created.isHidden()).toBe(false);
|
||||
|
||||
const result = await putInstances({
|
||||
brush_kind: 'none',
|
||||
existing_instance_ids: created.getPersistentUuid().slice(0, 10),
|
||||
instances_hidden: true,
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toEqual(
|
||||
expect.stringContaining('Marked 1 instance of "Cube" as hidden at start')
|
||||
);
|
||||
expect(created.isHidden()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3386,8 +3386,15 @@ const describeInstances: EditorFunction = {
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
// Expose the per-instance variables (overrides of the object
|
||||
// variables), but only when there are some, to keep the output
|
||||
// compact. Absence means the instance uses the object variables.
|
||||
initialVariables:
|
||||
serializedInstance.initialVariables &&
|
||||
serializedInstance.initialVariables.length > 0
|
||||
? serializedInstance.initialVariables
|
||||
: undefined,
|
||||
// For now, don't expose these:
|
||||
initialVariables: undefined,
|
||||
numberProperties: undefined,
|
||||
stringProperties: undefined,
|
||||
});
|
||||
@@ -3893,6 +3900,7 @@ const put2dInstances: EditorFunction = {
|
||||
originalZOrder: instance.getZOrder(),
|
||||
originalRotation: instance.getAngle(),
|
||||
originalOpacity: instance.getOpacity(),
|
||||
originalHidden: instance.isHidden(),
|
||||
originalCustomWidth: instance.hasCustomSize()
|
||||
? instance.getCustomWidth()
|
||||
: null,
|
||||
@@ -4036,6 +4044,10 @@ const put2dInstances: EditorFunction = {
|
||||
args,
|
||||
'instances_opacity'
|
||||
);
|
||||
const instancesHidden = SafeExtractor.extractBooleanProperty(
|
||||
args,
|
||||
'instances_hidden'
|
||||
);
|
||||
|
||||
modifiedAndCreatedInstances.forEach(instance => {
|
||||
if (instancesSize) {
|
||||
@@ -4052,6 +4064,9 @@ const put2dInstances: EditorFunction = {
|
||||
if (instancesOpacity !== null) {
|
||||
instance.setOpacity(instancesOpacity);
|
||||
}
|
||||
if (instancesHidden !== null) {
|
||||
instance.setHidden(instancesHidden);
|
||||
}
|
||||
});
|
||||
|
||||
// Track specific changes that were made
|
||||
@@ -4063,6 +4078,8 @@ const put2dInstances: EditorFunction = {
|
||||
attrs.push(`rotation ${instancesRotation}°`);
|
||||
if (instancesOpacity !== null)
|
||||
attrs.push(`opacity ${instancesOpacity}/255`);
|
||||
if (instancesHidden !== null)
|
||||
attrs.push(instancesHidden ? 'hidden at start' : 'visible at start');
|
||||
if (instances_z_order !== null)
|
||||
attrs.push(`z-order ${instances_z_order}`);
|
||||
const effectiveSize = instancesSize
|
||||
@@ -4108,6 +4125,7 @@ const put2dInstances: EditorFunction = {
|
||||
let resizedCount = 0;
|
||||
let rotatedCount = 0;
|
||||
let opacityChangedCount = 0;
|
||||
let hiddenChangedCount = 0;
|
||||
let zOrderChangedCount = 0;
|
||||
|
||||
existingInstanceStates.forEach((originalState, instance) => {
|
||||
@@ -4139,6 +4157,12 @@ const put2dInstances: EditorFunction = {
|
||||
) {
|
||||
opacityChangedCount++;
|
||||
}
|
||||
if (
|
||||
instancesHidden !== null &&
|
||||
originalState.originalHidden !== instance.isHidden()
|
||||
) {
|
||||
hiddenChangedCount++;
|
||||
}
|
||||
if (
|
||||
instances_z_order !== null &&
|
||||
originalState.originalZOrder !== instance.getZOrder()
|
||||
@@ -4200,6 +4224,18 @@ const put2dInstances: EditorFunction = {
|
||||
);
|
||||
}
|
||||
|
||||
if (hiddenChangedCount > 0 && instancesHidden !== null) {
|
||||
changes.push(
|
||||
instancesHidden
|
||||
? `Marked ${hiddenChangedCount} instance${
|
||||
hiddenChangedCount > 1 ? 's' : ''
|
||||
}${ofObjectsSuffix} as hidden at start (they can be displayed with the "Show" action).`
|
||||
: `Marked ${hiddenChangedCount} instance${
|
||||
hiddenChangedCount > 1 ? 's' : ''
|
||||
}${ofObjectsSuffix} as visible at start.`
|
||||
);
|
||||
}
|
||||
|
||||
if (zOrderChangedCount > 0 && instances_z_order !== null) {
|
||||
changes.push(
|
||||
`Changed Z-order of ${zOrderChangedCount} instance${
|
||||
@@ -4236,6 +4272,7 @@ const put2dInstances: EditorFunction = {
|
||||
!!instancesSize ||
|
||||
instancesRotation !== null ||
|
||||
instancesOpacity !== null ||
|
||||
instancesHidden !== null ||
|
||||
instances_z_order !== null;
|
||||
const hasPositionBrush =
|
||||
brush_kind === 'point' ||
|
||||
@@ -4749,6 +4786,7 @@ const put3dInstances: EditorFunction = {
|
||||
originalRotationX: instance.getRotationX(),
|
||||
originalRotationY: instance.getRotationY(),
|
||||
originalRotationZ: instance.getAngle(),
|
||||
originalHidden: instance.isHidden(),
|
||||
originalCustomWidth: instance.hasCustomSize()
|
||||
? instance.getCustomWidth()
|
||||
: null,
|
||||
@@ -4854,6 +4892,10 @@ const put3dInstances: EditorFunction = {
|
||||
const instancesRotationArray = instances_rotation
|
||||
? instances_rotation.split(',').map(coord => parseFloat(coord) || 0)
|
||||
: null;
|
||||
const instancesHidden = SafeExtractor.extractBooleanProperty(
|
||||
args,
|
||||
'instances_hidden'
|
||||
);
|
||||
|
||||
modifiedAndCreatedInstances.forEach(instance => {
|
||||
if (instancesSizeArray) {
|
||||
@@ -4868,6 +4910,9 @@ const put3dInstances: EditorFunction = {
|
||||
instance.setRotationY(instancesRotationArray[1]);
|
||||
instance.setAngle(instancesRotationArray[2]);
|
||||
}
|
||||
if (instancesHidden !== null) {
|
||||
instance.setHidden(instancesHidden);
|
||||
}
|
||||
});
|
||||
|
||||
// Track specific changes that were made
|
||||
@@ -4885,6 +4930,8 @@ const put3dInstances: EditorFunction = {
|
||||
instancesRotationArray[1]
|
||||
}°, ${instancesRotationArray[2]}°)`
|
||||
);
|
||||
if (instancesHidden !== null)
|
||||
attrs.push(instancesHidden ? 'hidden at start' : 'visible at start');
|
||||
const effectiveSize = instancesSizeArray
|
||||
? instancesSizeArray
|
||||
: objectSizeInfo &&
|
||||
@@ -4928,6 +4975,7 @@ const put3dInstances: EditorFunction = {
|
||||
let movedPositionCount = 0;
|
||||
let resizedCount = 0;
|
||||
let rotatedCount = 0;
|
||||
let hiddenChangedCount = 0;
|
||||
|
||||
existingInstanceStates.forEach((originalState, instance) => {
|
||||
if (originalState.originalLayer !== instance.getLayer()) {
|
||||
@@ -4957,6 +5005,12 @@ const put3dInstances: EditorFunction = {
|
||||
) {
|
||||
rotatedCount++;
|
||||
}
|
||||
if (
|
||||
instancesHidden !== null &&
|
||||
originalState.originalHidden !== instance.isHidden()
|
||||
) {
|
||||
hiddenChangedCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// Name the modified object(s) in messages so a wrongly targeted call is
|
||||
@@ -5008,6 +5062,18 @@ const put3dInstances: EditorFunction = {
|
||||
);
|
||||
}
|
||||
|
||||
if (hiddenChangedCount > 0 && instancesHidden !== null) {
|
||||
changes.push(
|
||||
instancesHidden
|
||||
? `Marked ${hiddenChangedCount} instance${
|
||||
hiddenChangedCount > 1 ? 's' : ''
|
||||
}${ofObjectsSuffix} as hidden at start (they can be displayed with the "Show" action).`
|
||||
: `Marked ${hiddenChangedCount} instance${
|
||||
hiddenChangedCount > 1 ? 's' : ''
|
||||
}${ofObjectsSuffix} as visible at start.`
|
||||
);
|
||||
}
|
||||
|
||||
if (notFoundExistingInstanceIds.size > 0) {
|
||||
// If NONE of the requested instances were found and nothing new was
|
||||
// created, the call did nothing. Return a failure so the agent gets a
|
||||
@@ -5034,7 +5100,8 @@ const put3dInstances: EditorFunction = {
|
||||
const matchedCount = existingInstanceStates.size;
|
||||
const hasMutationParams =
|
||||
!!instancesSizeArray ||
|
||||
(!!instancesRotationArray && instancesRotationArray.length >= 3);
|
||||
(!!instancesRotationArray && instancesRotationArray.length >= 3) ||
|
||||
instancesHidden !== null;
|
||||
const hasPositionBrush =
|
||||
brush_kind === 'point' ||
|
||||
brush_kind === 'line' ||
|
||||
@@ -7850,11 +7917,13 @@ const resolveVariablesContainers = ({
|
||||
variable_scope,
|
||||
scene_name,
|
||||
object_name,
|
||||
instance_id,
|
||||
}: {|
|
||||
project: gdProject,
|
||||
variable_scope: string,
|
||||
scene_name: ?string,
|
||||
object_name: ?string,
|
||||
instance_id: ?string,
|
||||
|}): VariablesContainersResolution => {
|
||||
const fail = (message: string): VariablesContainersResolution => ({
|
||||
failure: makeGenericFailure(message),
|
||||
@@ -7862,6 +7931,65 @@ const resolveVariablesContainers = ({
|
||||
scopeDescription: '',
|
||||
});
|
||||
|
||||
if (variable_scope === 'instance') {
|
||||
if (!scene_name) {
|
||||
return fail(`Missing "scene_name" (required for instance variables).`);
|
||||
}
|
||||
if (!project.hasLayoutNamed(scene_name)) {
|
||||
return fail(getSceneNotFoundMessage(project, scene_name));
|
||||
}
|
||||
const instanceId = instance_id ? instance_id.trim() : '';
|
||||
if (!instanceId) {
|
||||
return fail(
|
||||
`Missing "instance_id" (required for instance variables) - get it from \`describe_instances\` (the \`id\` field of each instance).`
|
||||
);
|
||||
}
|
||||
|
||||
const layout = project.getLayout(scene_name);
|
||||
let wrongObjectDescription = null;
|
||||
// The id is a prefix of the instance persistent uuid (like everywhere
|
||||
// else, see `describe_instances`) - it normally matches a single
|
||||
// instance.
|
||||
const matchedInstances: Array<gdInitialInstance> = [];
|
||||
iterateOnInstances(layout.getInitialInstances(), instance => {
|
||||
if (!instance.getPersistentUuid().startsWith(instanceId)) return;
|
||||
if (object_name && instance.getObjectName() !== object_name) {
|
||||
wrongObjectDescription = `"${instanceId}" (instance of "${instance.getObjectName()}")`;
|
||||
return;
|
||||
}
|
||||
matchedInstances.push(instance);
|
||||
});
|
||||
|
||||
if (object_name && wrongObjectDescription) {
|
||||
return fail(
|
||||
`This instance id is not an instance of object "${object_name}": ${wrongObjectDescription}. Nothing was changed. Pass the right \`object_name\` (or omit it), or fix the id using \`describe_instances\`.`
|
||||
);
|
||||
}
|
||||
if (matchedInstances.length === 0) {
|
||||
return fail(
|
||||
`No instance with id "${instanceId}" found in scene "${scene_name}". Nothing was changed. Call \`describe_instances\` to get valid ids (the \`id\` field of each instance).`
|
||||
);
|
||||
}
|
||||
|
||||
const instancesLabel = matchedInstances
|
||||
.map(
|
||||
instance =>
|
||||
`"${instance
|
||||
.getPersistentUuid()
|
||||
.slice(0, 10)}" (${instance.getObjectName()})`
|
||||
)
|
||||
.join(', ');
|
||||
return {
|
||||
failure: null,
|
||||
variablesContainers: matchedInstances.map(instance =>
|
||||
instance.getVariables()
|
||||
),
|
||||
scopeDescription: `instance${
|
||||
matchedInstances.length > 1 ? 's' : ''
|
||||
} ${instancesLabel} of scene "${scene_name}"`,
|
||||
};
|
||||
}
|
||||
|
||||
if (variable_scope === 'scene') {
|
||||
if (!scene_name) {
|
||||
return fail(`Missing "scene_name" (required for scene variable).`);
|
||||
@@ -7944,7 +8072,7 @@ const resolveVariablesContainers = ({
|
||||
}
|
||||
|
||||
return fail(
|
||||
`Invalid "variable_scope": "${variable_scope}". Use \`scene\`, \`object\`, \`group\` or \`global\`.`
|
||||
`Invalid "variable_scope": "${variable_scope}". Use \`scene\`, \`object\`, \`group\`, \`instance\` or \`global\`.`
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8011,6 +8139,17 @@ const addOrEditVariable: EditorFunction = {
|
||||
details,
|
||||
hasDetailsToShow: true,
|
||||
};
|
||||
} else if (variable_scope === 'instance') {
|
||||
return {
|
||||
text: (
|
||||
<Trans>
|
||||
Set variable <b>{variable_name_or_path}</b> on an instance of
|
||||
scene {scene_name}.
|
||||
</Trans>
|
||||
),
|
||||
details,
|
||||
hasDetailsToShow: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -8056,6 +8195,17 @@ const addOrEditVariable: EditorFunction = {
|
||||
details,
|
||||
hasDetailsToShow: true,
|
||||
};
|
||||
} else if (variable_scope === 'instance') {
|
||||
return {
|
||||
text: (
|
||||
<Trans>
|
||||
Update variables <b>{variableNames}</b> on an instance of scene{' '}
|
||||
{scene_name}.
|
||||
</Trans>
|
||||
),
|
||||
details,
|
||||
hasDetailsToShow: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -8075,6 +8225,10 @@ const addOrEditVariable: EditorFunction = {
|
||||
'object_name'
|
||||
);
|
||||
const scene_name = SafeExtractor.extractStringProperty(args, 'scene_name');
|
||||
const instance_id = SafeExtractor.extractStringProperty(
|
||||
args,
|
||||
'instance_id'
|
||||
);
|
||||
const operations = extractVariableOperations(args);
|
||||
if (operations.length === 0) {
|
||||
return makeGenericFailure(
|
||||
@@ -8087,6 +8241,7 @@ const addOrEditVariable: EditorFunction = {
|
||||
variable_scope,
|
||||
scene_name,
|
||||
object_name,
|
||||
instance_id,
|
||||
});
|
||||
if (resolved.failure) return resolved.failure;
|
||||
const { variablesContainers, scopeDescription } = resolved;
|
||||
@@ -8236,6 +8391,7 @@ const inspectVariables: EditorFunction = {
|
||||
variable_scope,
|
||||
scene_name,
|
||||
object_name,
|
||||
instance_id: SafeExtractor.extractStringProperty(args, 'instance_id'),
|
||||
});
|
||||
if (resolved.failure) return resolved.failure;
|
||||
const { variablesContainers, scopeDescription } = resolved;
|
||||
|
||||
+29
@@ -28,6 +28,8 @@ import Unlink from '../../UI/CustomSvgIcons/Unlink';
|
||||
import RemoveCircle from '../../UI/CustomSvgIcons/RemoveCircle';
|
||||
import Lock from '../../UI/CustomSvgIcons/Lock';
|
||||
import LockOpen from '../../UI/CustomSvgIcons/LockOpen';
|
||||
import Visibility from '../../UI/CustomSvgIcons/Visibility';
|
||||
import VisibilityOff from '../../UI/CustomSvgIcons/VisibilityOff';
|
||||
import Restore from '../../UI/CustomSvgIcons/Restore';
|
||||
import Object3d from '../../UI/CustomSvgIcons/Object3d';
|
||||
import Object2d from '../../UI/CustomSvgIcons/Object2d';
|
||||
@@ -175,6 +177,33 @@ const getTitleRow = ({ i18n }: {| i18n: I18nType |}): Field => ({
|
||||
nonFieldType: 'title',
|
||||
defaultValue: i18n._(t`Different objects`),
|
||||
},
|
||||
{
|
||||
name: 'Hide instance',
|
||||
getLabel: (instance: gdInitialInstance) =>
|
||||
instance.isHidden()
|
||||
? i18n._(t`Hidden when the scene starts`)
|
||||
: i18n._(t`Visible when the scene starts`),
|
||||
valueType: 'enumIcon',
|
||||
renderIcon: value =>
|
||||
value === 'hidden' ? (
|
||||
<VisibilityOff style={styles.icon} />
|
||||
) : (
|
||||
<Visibility style={styles.icon} />
|
||||
),
|
||||
isHighlighted: value => value === 'hidden',
|
||||
getValue: (instance: gdInitialInstance) =>
|
||||
instance.isHidden() ? 'hidden' : 'visible',
|
||||
setValue: (
|
||||
instance: gdInitialInstance,
|
||||
newValue: 'hidden' | 'visible'
|
||||
) => {
|
||||
instance.setHidden(newValue === 'hidden');
|
||||
},
|
||||
getNextValue: (
|
||||
currentValue: 'hidden' | 'visible'
|
||||
): 'hidden' | 'visible' =>
|
||||
currentValue === 'hidden' ? 'visible' : 'hidden',
|
||||
},
|
||||
{
|
||||
name: 'Lock instance',
|
||||
getLabel: (instance: gdInitialInstance) =>
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
// @flow
|
||||
import * as PIXI from 'pixi.js-legacy';
|
||||
import transformRect from '../Utils/TransformRect';
|
||||
import { type InstanceMeasurer } from './InstancesRenderer';
|
||||
import Rectangle from '../Utils/Rectangle';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
/**
|
||||
* Displays a crossed-eye icon on every instance that is hidden when the
|
||||
* scene starts. Those instances are still shown in the editor, and the icon
|
||||
* makes their state visible at a glance.
|
||||
*/
|
||||
export default class HiddenInstancesDecorations {
|
||||
instances: gdInitialInstancesContainer;
|
||||
layersContainer: gdLayersContainer;
|
||||
instanceMeasurer: InstanceMeasurer;
|
||||
toCanvasCoordinates: (x: number, y: number) => [number, number];
|
||||
// $FlowFixMe[value-as-type]
|
||||
decorations: PIXI.Container;
|
||||
// $FlowFixMe[value-as-type]
|
||||
iconTexture: PIXI.Texture;
|
||||
instancesFunctor: gdInitialInstanceJSFunctor;
|
||||
_temporaryRectangle: Rectangle = new Rectangle();
|
||||
_shownIconsCount = 0;
|
||||
|
||||
constructor({
|
||||
instances,
|
||||
layersContainer,
|
||||
instanceMeasurer,
|
||||
toCanvasCoordinates,
|
||||
}: {
|
||||
instances: gdInitialInstancesContainer,
|
||||
layersContainer: gdLayersContainer,
|
||||
instanceMeasurer: InstanceMeasurer,
|
||||
toCanvasCoordinates: (x: number, y: number) => [number, number],
|
||||
}) {
|
||||
this.instances = instances;
|
||||
this.layersContainer = layersContainer;
|
||||
this.instanceMeasurer = instanceMeasurer;
|
||||
this.toCanvasCoordinates = toCanvasCoordinates;
|
||||
|
||||
this.decorations = new PIXI.Container();
|
||||
// The same icon as the one shown by the in-game editor on hidden
|
||||
// instances (see `HiddenInstanceIcon.svg`), rasterized at 8x its
|
||||
// intrinsic size so it stays crisp at the biggest icon sizes.
|
||||
this.iconTexture = PIXI.Texture.from('res/hidden-instance-icon.svg', {
|
||||
resourceOptions: { scale: 8 },
|
||||
});
|
||||
|
||||
// $FlowFixMe[invalid-constructor] - JSFunctor is a class-like.
|
||||
this.instancesFunctor = new gd.InitialInstanceJSFunctor();
|
||||
// $FlowFixMe[cannot-write]
|
||||
this.instancesFunctor.invoke = instancePtr => {
|
||||
const instance: gdInitialInstance = gd.wrapPointer(
|
||||
// $FlowFixMe[incompatible-type]
|
||||
instancePtr,
|
||||
gd.InitialInstance
|
||||
);
|
||||
if (!instance.isHidden()) return;
|
||||
if (!this._isOnVisibleLayer(instance)) return;
|
||||
|
||||
const canvasRectangle = transformRect(
|
||||
this.toCanvasCoordinates,
|
||||
this.instanceMeasurer.getInstanceAABB(
|
||||
instance,
|
||||
this._temporaryRectangle
|
||||
)
|
||||
);
|
||||
// The icon size follows the on-screen size of the instance (so it
|
||||
// never dwarfs a small or zoomed-out instance, and stays legible on a
|
||||
// big one), within limits - like in the 3D editor.
|
||||
const width = canvasRectangle.width();
|
||||
const height = canvasRectangle.height();
|
||||
const onScreenRadius = Math.sqrt(width * width + height * height) / 2;
|
||||
// The 10px legibility floor never exceeds the size of the instance
|
||||
// itself: when zoomed far out, the icon shrinks with the instance
|
||||
// instead of dwarfing it.
|
||||
const iconSize = Math.min(
|
||||
Math.max(onScreenRadius * 0.4, 10),
|
||||
24,
|
||||
onScreenRadius
|
||||
);
|
||||
// Too small to be readable: don't show a smudge.
|
||||
if (iconSize < 4) return;
|
||||
|
||||
// Centered on the instance, like in the 3D editor.
|
||||
this._showIcon(
|
||||
canvasRectangle.left + width / 2,
|
||||
canvasRectangle.top + height / 2,
|
||||
iconSize
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
_isOnVisibleLayer(instance: gdInitialInstance): boolean {
|
||||
const layerName = instance.getLayer();
|
||||
if (!this.layersContainer.hasLayerNamed(layerName)) return true;
|
||||
return this.layersContainer.getLayer(layerName).getVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an icon centered on the given canvas position, with the given size
|
||||
* (in canvas pixels), reusing the sprites of the previous renders.
|
||||
*/
|
||||
_showIcon(x: number, y: number, size: number) {
|
||||
let sprite = this.decorations.children[this._shownIconsCount];
|
||||
if (!sprite) {
|
||||
sprite = new PIXI.Sprite(this.iconTexture);
|
||||
sprite.anchor.set(0.5, 0.5);
|
||||
this.decorations.addChild(sprite);
|
||||
}
|
||||
sprite.visible = true;
|
||||
sprite.position.set(x, y);
|
||||
// Set on every render: this also fixes the size of the sprites created
|
||||
// before the texture finished loading.
|
||||
sprite.width = size;
|
||||
sprite.height = size;
|
||||
this._shownIconsCount++;
|
||||
}
|
||||
|
||||
// $FlowFixMe[value-as-type]
|
||||
getPixiObject(): PIXI.Container {
|
||||
return this.decorations;
|
||||
}
|
||||
|
||||
render() {
|
||||
this._shownIconsCount = 0;
|
||||
// $FlowFixMe[incompatible-type]
|
||||
this.instances.iterateOverInstances(this.instancesFunctor);
|
||||
|
||||
// Hide the pooled sprites that were not used by this render.
|
||||
for (
|
||||
let i = this._shownIconsCount;
|
||||
i < this.decorations.children.length;
|
||||
i++
|
||||
) {
|
||||
this.decorations.children[i].visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.instancesFunctor.delete();
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import CompactSearchBar from '../../UI/CompactSearchBar';
|
||||
import RemoveCircle from '../../UI/CustomSvgIcons/RemoveCircle';
|
||||
import Lock from '../../UI/CustomSvgIcons/Lock';
|
||||
import LockOpen from '../../UI/CustomSvgIcons/LockOpen';
|
||||
import Visibility from '../../UI/CustomSvgIcons/Visibility';
|
||||
import VisibilityOff from '../../UI/CustomSvgIcons/VisibilityOff';
|
||||
import RotateZ from '../../UI/CustomSvgIcons/RotateZ';
|
||||
import Layers from '../../UI/CustomSvgIcons/Layers';
|
||||
import SortArrowUp from '../../UI/CustomSvgIcons/SortArrowUp';
|
||||
@@ -35,6 +37,7 @@ type RenderedRowInfo = {
|
||||
instance: gdInitialInstance,
|
||||
name: string,
|
||||
locked: boolean,
|
||||
hidden: boolean,
|
||||
x: string,
|
||||
y: string,
|
||||
angle: string,
|
||||
@@ -153,6 +156,7 @@ class InstancesList extends Component<Props, State> {
|
||||
instance,
|
||||
name,
|
||||
locked: instance.isLocked(),
|
||||
hidden: instance.isHidden(),
|
||||
x: toFixedWithoutTrailingZeros(instance.getX(), 2),
|
||||
y: toFixedWithoutTrailingZeros(instance.getY(), 2),
|
||||
angle: toFixedWithoutTrailingZeros(instance.getAngle(), 2),
|
||||
@@ -192,6 +196,29 @@ class InstancesList extends Component<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
_renderVisibilityCell = ({
|
||||
rowData: { instance },
|
||||
}: {
|
||||
rowData: RenderedRowInfo,
|
||||
}): React.Node => {
|
||||
return (
|
||||
<IconButton
|
||||
size="small"
|
||||
tooltip={
|
||||
instance.isHidden()
|
||||
? t`Hidden when the scene starts`
|
||||
: t`Visible when the scene starts`
|
||||
}
|
||||
onClick={() => {
|
||||
instance.setHidden(!instance.isHidden());
|
||||
this.props.onInstancesModified([instance]);
|
||||
}}
|
||||
>
|
||||
{instance.isHidden() ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
_renderLockCell = ({
|
||||
rowData: { instance },
|
||||
}: {
|
||||
@@ -379,6 +406,13 @@ class InstancesList extends Component<Props, State> {
|
||||
className={'tableColumn tableColumnSecondary'}
|
||||
headerRenderer={renderSortableHeader}
|
||||
/>
|
||||
<RVColumn
|
||||
label=""
|
||||
dataKey="hidden"
|
||||
width={Math.max(width * 0.05, minimumWidths.numberProperty)}
|
||||
className={'tableColumn'}
|
||||
cellRenderer={this._renderVisibilityCell}
|
||||
/>
|
||||
<RVColumn
|
||||
label=""
|
||||
dataKey="locked"
|
||||
|
||||
@@ -7,6 +7,7 @@ import InstancesRenderer from './InstancesRenderer';
|
||||
import ViewPosition from './ViewPosition';
|
||||
import SelectedInstances from './SelectedInstances';
|
||||
import HighlightedInstance from './HighlightedInstance';
|
||||
import HiddenInstancesDecorations from './HiddenInstancesDecorations';
|
||||
import SelectionRectangle from './SelectionRectangle';
|
||||
import InstancesResizer, {
|
||||
type ResizeGrabbingLocation,
|
||||
@@ -169,6 +170,7 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
tileMapPaintingPreview: TileMapPaintingPreview;
|
||||
clickInterceptor: ClickInterceptor;
|
||||
highlightedInstance: HighlightedInstance;
|
||||
hiddenInstancesDecorations: HiddenInstancesDecorations;
|
||||
instancesResizer: InstancesResizer;
|
||||
instancesRotator: InstancesRotator;
|
||||
instancesMover: InstancesMover;
|
||||
@@ -493,6 +495,12 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
this.highlightedInstance.getPixiObject()
|
||||
);
|
||||
}
|
||||
if (this.hiddenInstancesDecorations) {
|
||||
this.uiPixiContainer.removeChild(
|
||||
this.hiddenInstancesDecorations.getPixiObject()
|
||||
);
|
||||
this.hiddenInstancesDecorations.delete();
|
||||
}
|
||||
if (this.tileMapPaintingPreview) {
|
||||
this.uiPixiContainer.removeChild(
|
||||
this.tileMapPaintingPreview.getPixiObject()
|
||||
@@ -595,6 +603,12 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
toCanvasCoordinates: this.viewPosition.toCanvasCoordinates,
|
||||
isInstanceOf3DObject: this.props.isInstanceOf3DObject,
|
||||
});
|
||||
this.hiddenInstancesDecorations = new HiddenInstancesDecorations({
|
||||
instances: props.initialInstances,
|
||||
layersContainer: props.layersContainer,
|
||||
instanceMeasurer: this.instancesRenderer.getInstanceMeasurer(),
|
||||
toCanvasCoordinates: this.viewPosition.toCanvasCoordinates,
|
||||
});
|
||||
this.instancesResizer = new InstancesResizer({
|
||||
instanceMeasurer: this.instancesRenderer.getInstanceMeasurer(),
|
||||
instancesEditorSettings: this.props.instancesEditorSettings,
|
||||
@@ -630,6 +644,9 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
this.uiPixiContainer.addChild(this.windowMask.getPixiObject());
|
||||
this.uiPixiContainer.addChild(this.selectedInstances.getPixiContainer());
|
||||
this.uiPixiContainer.addChild(this.highlightedInstance.getPixiObject());
|
||||
this.uiPixiContainer.addChild(
|
||||
this.hiddenInstancesDecorations.getPixiObject()
|
||||
);
|
||||
this.uiPixiContainer.addChild(this.tileMapPaintingPreview.getPixiObject());
|
||||
this.uiPixiContainer.addChild(this.clickInterceptor.getPixiObject());
|
||||
this.uiPixiContainer.addChild(this.statusBar.getPixiObject());
|
||||
@@ -653,6 +670,9 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
if (this.selectionRectangle) {
|
||||
this.selectionRectangle.delete();
|
||||
}
|
||||
if (this.hiddenInstancesDecorations) {
|
||||
this.hiddenInstancesDecorations.delete();
|
||||
}
|
||||
if (this.instancesRenderer) {
|
||||
this.instancesRenderer.delete();
|
||||
}
|
||||
@@ -1805,6 +1825,7 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
this.canvasCursor.render();
|
||||
this.grid.render();
|
||||
this.highlightedInstance.render();
|
||||
this.hiddenInstancesDecorations.render();
|
||||
this.tileMapPaintingPreview.render();
|
||||
this.clickInterceptor.render();
|
||||
this.selectedInstances.render();
|
||||
|
||||
Reference in New Issue
Block a user