Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions src/core/mixins/ContainerMixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ interface PrivateContainer extends Omit<Container, '_didLocalTransformChangeId'>

const visibility = Object.getOwnPropertyDescriptor(Container.prototype, 'visible')!;

// The visible override is shared by every container, so it is installed once and never removed.
// Its setter does no layout work for containers without a layout.
let visibilityOverridden = false;

const mixin: Partial<PrivateContainer> = {
// Internal reference to the layout object
_layout: null,
Expand All @@ -38,7 +42,6 @@ const mixin: Partial<PrivateContainer> = {
this._layout.destroy();
this._layout = null;
this.updateLocalTransform = Container.prototype.updateLocalTransform;
Object.defineProperty(Container.prototype, 'visible', visibility);
}

return;
Expand All @@ -47,20 +50,23 @@ const mixin: Partial<PrivateContainer> = {
if (!this._layout) {
this._layout = new Layout({ target: this as unknown as Container });

// eslint-disable-next-line accessor-pairs
Object.defineProperty(Container.prototype, 'visible', {
...visibility,
set(visibleValue: boolean) {
visibility.set!.call(this, visibleValue);
if (this.layout) {
if (visibleValue && this.parent) {
this.layout._onChildAdded(this.parent);
} else {
this.layout._onChildRemoved();
if (!visibilityOverridden) {
// eslint-disable-next-line accessor-pairs
Object.defineProperty(Container.prototype, 'visible', {
...visibility,
set(visibleValue: boolean) {
visibility.set!.call(this, visibleValue);
if (this.layout) {
if (visibleValue && this.parent) {
this.layout._onChildAdded(this.parent);
} else {
this.layout._onChildRemoved();
}
}
}
},
});
},
});
visibilityOverridden = true;
}

// update the children's layout if they have one to be this container
for (const child of this.children) {
Expand Down
59 changes: 58 additions & 1 deletion tests/__tests__/visibility.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Application, Sprite, Texture } from 'pixi.js';
import { Application, Container, Sprite, Texture } from 'pixi.js';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import '../../src/index';

Expand Down Expand Up @@ -76,4 +76,61 @@ describe('visibility', async () => {
stage.addChildAt(sprite, 0);
}).not.toThrow();
});

it('should install the visible override once', async () => {
const getVisibleSetter = () => Object.getOwnPropertyDescriptor(Container.prototype, 'visible')!.set;
const container = new Container();
const container2 = new Container();
const container3 = new Container();

container.layout = {};
const visibleSetter = getVisibleSetter();

container2.layout = {};
container3.layout = {};
expect(getVisibleSetter()).toBe(visibleSetter);

// removing one layout must not remove the override used by the others
container.layout = null;
expect(getVisibleSetter()).toBe(visibleSetter);

container.destroy();
container2.destroy();
container3.destroy();
});

it('should update the layout on visibility change after another layout is removed', async () => {
const stage = app.stage;
const sprite = new Sprite(Texture.WHITE);
const sprite2 = new Sprite(Texture.WHITE);
const other = new Container();

stage.layout = {};
stage.addChild(sprite);
stage.addChild(sprite2);

sprite.layout = {};
sprite2.layout = {};
other.layout = {};

// remove the layout from a container that is unrelated to the stage
other.layout = null;

const stageYoga = stage.layout!.yoga;
const spriteYoga = sprite.layout!.yoga;
const sprite2Yoga = sprite2.layout!.yoga;

// Set visibility to hidden
sprite.visible = false;
expect(stageYoga.getChildCount()).toBe(1);
expect(stageYoga.getChild(0)).toStrictEqual(sprite2Yoga);

// add back the sprite
sprite.visible = true;
expect(stageYoga.getChildCount()).toBe(2);
expect(stageYoga.getChild(0)).toStrictEqual(spriteYoga);
expect(stageYoga.getChild(1)).toStrictEqual(sprite2Yoga);

other.destroy();
});
});