UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

191 lines (133 loc) • 5 kB
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(); } }