2019-10-11 05:11:52 +08:00
|
|
|
import { ResizableWidget } from "../../core/gui/ResizableWidget";
|
2019-11-05 00:19:57 +08:00
|
|
|
import { el } from "../../core/gui/dom";
|
2019-10-11 05:11:52 +08:00
|
|
|
import { quest_editor_store } from "../stores/QuestEditorStore";
|
2019-10-29 22:22:46 +08:00
|
|
|
import { QuestEventDagModel } from "../model/QuestEventDagModel";
|
2019-10-11 05:11:52 +08:00
|
|
|
import { Disposer } from "../../core/observable/Disposer";
|
|
|
|
import { NumberInput } from "../../core/gui/NumberInput";
|
|
|
|
import "./EventsView.css";
|
|
|
|
import { Disposable } from "../../core/observable/Disposable";
|
2019-11-01 02:11:14 +08:00
|
|
|
import { defer } from "lodash";
|
2019-11-05 00:19:57 +08:00
|
|
|
import {
|
|
|
|
ListChangeType,
|
|
|
|
ListPropertyChangeEvent,
|
|
|
|
} from "../../core/observable/property/list/ListProperty";
|
|
|
|
|
|
|
|
type DagGuiData = {
|
|
|
|
dag: QuestEventDagModel;
|
|
|
|
element: HTMLElement;
|
|
|
|
edge_container_element: HTMLElement;
|
|
|
|
disposer: Disposer;
|
|
|
|
/**
|
|
|
|
* Maps event IDs to GUI data.
|
|
|
|
*/
|
|
|
|
event_gui_data: Map<number, { element: HTMLDivElement; position: number }>;
|
|
|
|
};
|
2019-10-11 05:11:52 +08:00
|
|
|
|
|
|
|
export class EventsView extends ResizableWidget {
|
2019-11-05 00:19:57 +08:00
|
|
|
private readonly dag_gui_data: DagGuiData[] = [];
|
|
|
|
private event_dags_observer?: Disposable;
|
2019-10-11 05:11:52 +08:00
|
|
|
|
|
|
|
readonly element = el.div({ class: "quest_editor_EventsView" });
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
|
|
|
|
this.disposables(
|
2019-10-28 05:40:26 +08:00
|
|
|
quest_editor_store.current_quest.observe(this.update),
|
|
|
|
quest_editor_store.current_area.observe(this.update),
|
2019-12-19 07:11:42 +08:00
|
|
|
this.enabled.bind_to(quest_editor_store.quest_runner.running.map(r => !r)),
|
2019-10-11 05:11:52 +08:00
|
|
|
);
|
|
|
|
|
2019-12-20 01:54:01 +08:00
|
|
|
this.finalize_construction();
|
2019-10-11 05:11:52 +08:00
|
|
|
}
|
|
|
|
|
2019-11-05 00:19:57 +08:00
|
|
|
resize(width: number, height: number): this {
|
|
|
|
super.resize(width, height);
|
|
|
|
this.update_edges();
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
focus(): void {
|
|
|
|
super.focus();
|
|
|
|
this.update_edges();
|
|
|
|
}
|
|
|
|
|
|
|
|
dispose(): void {
|
|
|
|
super.dispose();
|
|
|
|
|
|
|
|
if (this.event_dags_observer) {
|
|
|
|
this.event_dags_observer.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const { disposer } of this.dag_gui_data) {
|
|
|
|
disposer.dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-28 05:40:26 +08:00
|
|
|
private update = (): void => {
|
2019-11-05 00:19:57 +08:00
|
|
|
if (this.event_dags_observer) {
|
|
|
|
this.event_dags_observer.dispose();
|
|
|
|
}
|
2019-10-28 05:40:26 +08:00
|
|
|
|
|
|
|
const quest = quest_editor_store.current_quest.val;
|
|
|
|
const area = quest_editor_store.current_area.val;
|
|
|
|
|
|
|
|
if (quest && area) {
|
2019-11-05 00:19:57 +08:00
|
|
|
const event_dags = quest.event_dags.filtered(dag => dag.area_id === area.id);
|
|
|
|
this.event_dags_observer = event_dags.observe_list(this.observe_event_dags);
|
|
|
|
this.redraw_event_dags(event_dags.val);
|
|
|
|
} else {
|
|
|
|
this.event_dags_observer = undefined;
|
|
|
|
this.redraw_event_dags([]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private redraw_event_dags = (event_dags: readonly QuestEventDagModel[]): void => {
|
|
|
|
this.element.innerHTML = "";
|
|
|
|
|
|
|
|
for (const removed of this.dag_gui_data.splice(0, this.dag_gui_data.length)) {
|
|
|
|
removed.disposer.dispose();
|
2019-10-28 05:40:26 +08:00
|
|
|
}
|
2019-11-05 00:19:57 +08:00
|
|
|
|
|
|
|
let index = 0;
|
|
|
|
|
|
|
|
for (const dag of event_dags) {
|
|
|
|
const data = this.create_dag_ui_data(dag);
|
|
|
|
this.dag_gui_data.splice(index, 0, data);
|
|
|
|
this.element.append(data.element);
|
|
|
|
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
defer(this.update_edges);
|
2019-10-28 05:40:26 +08:00
|
|
|
};
|
|
|
|
|
2019-11-05 00:19:57 +08:00
|
|
|
private observe_event_dags = (change: ListPropertyChangeEvent<QuestEventDagModel>): void => {
|
|
|
|
if (change.type === ListChangeType.ListChange) {
|
|
|
|
for (const removed of this.dag_gui_data.splice(change.index, change.removed.length)) {
|
|
|
|
removed.element.remove();
|
|
|
|
removed.disposer.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
let index = change.index;
|
|
|
|
|
|
|
|
for (const dag of change.inserted) {
|
|
|
|
const data = this.create_dag_ui_data(dag);
|
|
|
|
this.dag_gui_data.splice(index, 0, data);
|
|
|
|
this.element.insertBefore(data.element, this.element.children.item(index));
|
|
|
|
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
defer(this.update_edges);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private create_dag_ui_data = (dag: QuestEventDagModel): DagGuiData => {
|
2019-10-11 05:11:52 +08:00
|
|
|
const disposer = new Disposer();
|
2019-11-05 00:19:57 +08:00
|
|
|
const event_gui_data = new Map<number, { element: HTMLDivElement; position: number }>();
|
|
|
|
|
2019-11-01 02:11:14 +08:00
|
|
|
const element = el.div({ class: "quest_editor_EventsView_dag" });
|
|
|
|
|
2019-11-05 00:19:57 +08:00
|
|
|
const edge_container_element = el.div({
|
|
|
|
class: "quest_editor_EventsView_edge_container",
|
|
|
|
});
|
|
|
|
element.append(edge_container_element);
|
|
|
|
|
2019-11-01 02:11:14 +08:00
|
|
|
dag.events.forEach((event, i) => {
|
|
|
|
const event_element = el.div(
|
|
|
|
{ class: "quest_editor_EventsView_event" },
|
|
|
|
el.table(
|
|
|
|
el.tr(el.th({ text: "ID:" }), el.td({ text: event.id.toString() })),
|
|
|
|
el.tr(
|
|
|
|
el.th({ text: "Section:" }),
|
|
|
|
el.td(
|
|
|
|
disposer.add(new NumberInput(event.section_id, { enabled: false }))
|
|
|
|
.element,
|
2019-10-11 05:11:52 +08:00
|
|
|
),
|
2019-11-01 02:11:14 +08:00
|
|
|
),
|
|
|
|
el.tr(el.th({ text: "Wave:" }), el.td({ text: event.wave.toString() })),
|
|
|
|
el.tr(
|
|
|
|
el.th({ text: "Delay:" }),
|
|
|
|
el.td(
|
|
|
|
disposer.add(new NumberInput(event.delay, { enabled: false })).element,
|
2019-10-11 05:11:52 +08:00
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
2019-11-01 02:11:14 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
element.append(event_element);
|
2019-11-05 00:19:57 +08:00
|
|
|
event_gui_data.set(event.id, { element: event_element, position: i });
|
2019-11-01 02:11:14 +08:00
|
|
|
});
|
|
|
|
|
2019-11-05 00:19:57 +08:00
|
|
|
return {
|
|
|
|
dag,
|
|
|
|
element,
|
|
|
|
edge_container_element,
|
|
|
|
disposer,
|
|
|
|
event_gui_data,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
private update_edges = (): void => {
|
|
|
|
const SPACING = 8;
|
|
|
|
let max_depth = 0;
|
|
|
|
|
|
|
|
for (const { dag, edge_container_element, event_gui_data } of this.dag_gui_data) {
|
|
|
|
edge_container_element.innerHTML = "";
|
|
|
|
|
2019-11-01 02:11:14 +08:00
|
|
|
const used_depths: boolean[][] = Array(dag.events.length - 1);
|
|
|
|
|
|
|
|
for (let i = 0; i < used_depths.length; i++) {
|
|
|
|
used_depths[i] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const event of dag.events) {
|
2019-11-05 00:19:57 +08:00
|
|
|
const { element: event_element, position } = event_gui_data.get(event.id)!;
|
2019-11-01 02:11:14 +08:00
|
|
|
|
|
|
|
const y_offset = event_element.offsetTop + event_element.offsetHeight;
|
|
|
|
|
|
|
|
for (const child of dag.get_children(event)) {
|
2019-11-05 00:19:57 +08:00
|
|
|
const { element: child_element, position: child_position } = event_gui_data.get(
|
2019-11-01 02:11:14 +08:00
|
|
|
child.id,
|
|
|
|
)!;
|
|
|
|
const child_y_offset = child_element.offsetTop;
|
|
|
|
|
|
|
|
const edge_element = el.div({ class: "quest_editor_EventsView_edge" });
|
|
|
|
|
|
|
|
const top = Math.min(y_offset, child_y_offset) - 20;
|
|
|
|
const height = Math.max(y_offset, child_y_offset) - top + 20;
|
|
|
|
|
|
|
|
let depth = 1;
|
|
|
|
const low_pos = Math.min(position, child_position);
|
|
|
|
const high_pos = Math.max(position, child_position);
|
|
|
|
|
|
|
|
outer: while (true) {
|
|
|
|
for (let i = low_pos; i < high_pos; i++) {
|
|
|
|
if (used_depths[i][depth]) {
|
|
|
|
depth++;
|
|
|
|
continue outer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = low_pos; i < high_pos; i++) {
|
|
|
|
used_depths[i][depth] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
max_depth = Math.max(depth, max_depth);
|
|
|
|
|
|
|
|
const width = SPACING * depth;
|
|
|
|
|
|
|
|
edge_element.style.left = `${4 - width}px`;
|
|
|
|
edge_element.style.top = `${top}px`;
|
|
|
|
edge_element.style.width = `${width}px`;
|
|
|
|
edge_element.style.height = `${height}px`;
|
|
|
|
|
2019-11-05 00:19:57 +08:00
|
|
|
edge_container_element.append(edge_element);
|
2019-11-01 02:11:14 +08:00
|
|
|
}
|
|
|
|
}
|
2019-11-05 00:19:57 +08:00
|
|
|
}
|
2019-11-01 02:11:14 +08:00
|
|
|
|
2019-11-05 00:19:57 +08:00
|
|
|
for (const { element } of this.dag_gui_data) {
|
2019-11-01 02:11:14 +08:00
|
|
|
element.style.marginLeft = `${SPACING * max_depth}px`;
|
2019-11-05 00:19:57 +08:00
|
|
|
}
|
2019-10-11 05:11:52 +08:00
|
|
|
};
|
|
|
|
}
|