@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
191 lines (133 loc) • 5 kB
JavaScript
import { array_set_diff_sorting } from "../../../src/core/collection/array/array_set_diff_sorting.js";
import List from "../../../src/core/collection/list/List.js";
import Signal from "../../../src/core/events/signal/Signal.js";
import { number_compare_ascending } from "../../../src/core/primitives/numbers/number_compare_ascending.js";
import Name from "../../../src/engine/ecs/name/Name.js";
import { ParentEntity } from "../../../src/engine/ecs/parent/ParentEntity.js";
import { MouseEvents } from "../../../src/engine/input/devices/events/MouseEvents.js";
import LabelView from "../../../src/view/common/LabelView.js";
import ListView from "../../../src/view/common/ListView.js";
import EmptyView from "../../../src/view/elements/EmptyView.js";
import style from "./HierarchicalEntityListView.module.scss";
export class HierarchicalEntityListView extends EmptyView {
/**
*
* @type {EntityComponentDataset|null}
*/
#dataset = null;
#roots = new List();
#child_map = new Map();
#selected = new List();
#update_roots() {
const new_roots = [];
const ecd = this.#dataset;
ecd.traverseEntityIndices(id => {
const parent = ecd.getComponent(id, ParentEntity);
if (parent !== undefined) {
const childMap = this.#child_map;
let children = childMap.get(parent.entity);
if (children === undefined) {
children = [];
childMap.set(parent.entity, children);
}
children.push(id);
return;
}
new_roots.push(id);
});
// perform patch
const diff = array_set_diff_sorting(new_roots, this.#roots.asArray(), number_compare_ascending);
for (let i = 0; i < diff.uniqueB.length; i++) {
const removal_id = diff.uniqueB[i];
this.#roots.removeOneOf(removal_id);
}
for (let i = 0; i < diff.uniqueA.length; i++) {
const addition_id = diff.uniqueA[i];
this.#roots.add(addition_id);
}
}
set selection(entities) {
this.#selected.patch(entities);
}
set dataset(v) {
this.#roots.reset();
this.#dataset = v;
this.#update_roots();
}
/**
*
* @param {number} entity
*/
#make_entity_view(entity) {
const ecd = this.#dataset;
const children = this.#child_map.get(entity);
const has_children = children !== undefined;
const entity_view = new EmptyView({
classList:[style.entityView],
css: {
position: 'relative',
pointerEvents: "auto",
}
});
let label_text = `${entity}`;
const name = ecd.getComponent(entity, Name);
if (name !== undefined) {
label_text += `: ${name}`
}
entity_view.addChild(new LabelView(label_text, {
css: {
position: 'relative'
}
}));
if (has_children) {
const children_container = new EmptyView({
css: {
paddingLeft: "8px"
}
});
for (let i = 0; i < children.length; i++) {
const child = children[i];
const child_view = this.#make_entity_view(child);
children_container.addChild(child_view);
}
entity_view.addChild(children_container);
}
entity_view.el.addEventListener(MouseEvents.Click, (evt) => {
this.on.interact.send1(entity);
evt.stopPropagation();
});
const update_selection = () => {
const is_selected = this.#selected.contains(entity);
entity_view.setClass("selected", is_selected);
}
entity_view.bindSignal(this.#selected.on.added, update_selection);
entity_view.bindSignal(this.#selected.on.removed, update_selection);
entity_view.on.linked.add(update_selection);
return entity_view;
}
constructor() {
super();
const listView = new ListView(this.#roots, {
classList: [
style.main
],
elementFactory: (entity) => {
return this.#make_entity_view(entity);
},
});
listView.css({
display: 'flex',
flexDirection: 'column'
})
this.size.onChanged.add(listView.size.set, listView.size);
this.on.interact = new Signal();
this.addChild(listView);
}
link() {
super.link();
this.#update_roots();
}
unlink() {
super.unlink();
}
}