mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 23:38:30 +08:00
Made event rendering more robust. Added call_now option to ListProperty.observe_list.
This commit is contained in:
parent
e7a39168ae
commit
8212348a81
@ -63,7 +63,10 @@ export abstract class AbstractListProperty<T> extends AbstractProperty<readonly
|
|||||||
return this.val[index];
|
return this.val[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
observe_list(observer: (change: ListPropertyChangeEvent<T>) => void): Disposable {
|
observe_list(
|
||||||
|
observer: (change: ListPropertyChangeEvent<T>) => void,
|
||||||
|
options?: { call_now?: boolean },
|
||||||
|
): Disposable {
|
||||||
if (this.value_observers.length === 0 && this.extract_observables) {
|
if (this.value_observers.length === 0 && this.extract_observables) {
|
||||||
this.replace_element_observers(0, Infinity, this.val);
|
this.replace_element_observers(0, Infinity, this.val);
|
||||||
}
|
}
|
||||||
@ -72,6 +75,15 @@ export abstract class AbstractListProperty<T> extends AbstractProperty<readonly
|
|||||||
this.list_observers.push(observer);
|
this.list_observers.push(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options && options.call_now) {
|
||||||
|
this.call_list_observer(observer, {
|
||||||
|
type: ListChangeType.ListChange,
|
||||||
|
index: 0,
|
||||||
|
removed: [],
|
||||||
|
inserted: this.val,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dispose: () => {
|
dispose: () => {
|
||||||
const index = this.list_observers.indexOf(observer);
|
const index = this.list_observers.indexOf(observer);
|
||||||
|
@ -44,8 +44,11 @@ export class DependentListProperty<T> extends AbstractListProperty<T> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
observe_list(observer: (change: ListPropertyChangeEvent<T>) => void): Disposable {
|
observe_list(
|
||||||
const super_disposable = super.observe_list(observer);
|
observer: (change: ListPropertyChangeEvent<T>) => void,
|
||||||
|
options?: { call_now?: boolean },
|
||||||
|
): Disposable {
|
||||||
|
const super_disposable = super.observe_list(observer, options);
|
||||||
|
|
||||||
this.init_dependency_disposable();
|
this.init_dependency_disposable();
|
||||||
|
|
||||||
|
@ -29,7 +29,10 @@ export interface ListProperty<T> extends Property<readonly T[]> {
|
|||||||
|
|
||||||
get(index: number): T;
|
get(index: number): T;
|
||||||
|
|
||||||
observe_list(observer: (change: ListPropertyChangeEvent<T>) => void): Disposable;
|
observe_list(
|
||||||
|
observer: (change: ListPropertyChangeEvent<T>) => void,
|
||||||
|
options?: { call_now?: boolean },
|
||||||
|
): Disposable;
|
||||||
|
|
||||||
filtered(predicate: (value: T) => boolean): ListProperty<T>;
|
filtered(predicate: (value: T) => boolean): ListProperty<T>;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,10 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quest_editor_EventsView_edge_container {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
.quest_editor_EventsView_edge {
|
.quest_editor_EventsView_edge {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ResizableWidget } from "../../core/gui/ResizableWidget";
|
import { ResizableWidget } from "../../core/gui/ResizableWidget";
|
||||||
import { bind_children_to, el } from "../../core/gui/dom";
|
import { el } from "../../core/gui/dom";
|
||||||
import { quest_editor_store } from "../stores/QuestEditorStore";
|
import { quest_editor_store } from "../stores/QuestEditorStore";
|
||||||
import { QuestEventDagModel } from "../model/QuestEventDagModel";
|
import { QuestEventDagModel } from "../model/QuestEventDagModel";
|
||||||
import { Disposer } from "../../core/observable/Disposer";
|
import { Disposer } from "../../core/observable/Disposer";
|
||||||
@ -7,9 +7,25 @@ import { NumberInput } from "../../core/gui/NumberInput";
|
|||||||
import "./EventsView.css";
|
import "./EventsView.css";
|
||||||
import { Disposable } from "../../core/observable/Disposable";
|
import { Disposable } from "../../core/observable/Disposable";
|
||||||
import { defer } from "lodash";
|
import { defer } from "lodash";
|
||||||
|
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 }>;
|
||||||
|
};
|
||||||
|
|
||||||
export class EventsView extends ResizableWidget {
|
export class EventsView extends ResizableWidget {
|
||||||
private readonly quest_disposer = this.disposable(new Disposer());
|
private readonly dag_gui_data: DagGuiData[] = [];
|
||||||
|
private event_dags_observer?: Disposable;
|
||||||
|
|
||||||
readonly element = el.div({ class: "quest_editor_EventsView" });
|
readonly element = el.div({ class: "quest_editor_EventsView" });
|
||||||
|
|
||||||
@ -24,29 +40,99 @@ export class EventsView extends ResizableWidget {
|
|||||||
this.finalize_construction(EventsView.prototype);
|
this.finalize_construction(EventsView.prototype);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private update = (): void => {
|
private update = (): void => {
|
||||||
this.quest_disposer.dispose_all();
|
if (this.event_dags_observer) {
|
||||||
|
this.event_dags_observer.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
const quest = quest_editor_store.current_quest.val;
|
const quest = quest_editor_store.current_quest.val;
|
||||||
const area = quest_editor_store.current_area.val;
|
const area = quest_editor_store.current_area.val;
|
||||||
|
|
||||||
if (quest && area) {
|
if (quest && area) {
|
||||||
this.quest_disposer.add(
|
const event_dags = quest.event_dags.filtered(dag => dag.area_id === area.id);
|
||||||
bind_children_to(
|
this.event_dags_observer = event_dags.observe_list(this.observe_event_dags);
|
||||||
this.element,
|
this.redraw_event_dags(event_dags.val);
|
||||||
quest.event_dags.filtered(dag => dag.area_id === area.id),
|
} else {
|
||||||
this.create_dag_element,
|
this.event_dags_observer = undefined;
|
||||||
),
|
this.redraw_event_dags([]);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private create_dag_element = (dag: QuestEventDagModel): [HTMLElement, Disposable] => {
|
private redraw_event_dags = (event_dags: readonly QuestEventDagModel[]): void => {
|
||||||
const disposer = new Disposer();
|
this.element.innerHTML = "";
|
||||||
const element = el.div({ class: "quest_editor_EventsView_dag" });
|
|
||||||
const event_elements = new Map<number, { element: HTMLDivElement; position: number }>();
|
for (const removed of this.dag_gui_data.splice(0, this.dag_gui_data.length)) {
|
||||||
|
removed.disposer.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 => {
|
||||||
|
const disposer = new Disposer();
|
||||||
|
const event_gui_data = new Map<number, { element: HTMLDivElement; position: number }>();
|
||||||
|
|
||||||
|
const element = el.div({ class: "quest_editor_EventsView_dag" });
|
||||||
|
|
||||||
|
const edge_container_element = el.div({
|
||||||
|
class: "quest_editor_EventsView_edge_container",
|
||||||
|
});
|
||||||
|
element.append(edge_container_element);
|
||||||
|
|
||||||
// Render events.
|
|
||||||
dag.events.forEach((event, i) => {
|
dag.events.forEach((event, i) => {
|
||||||
const event_element = el.div(
|
const event_element = el.div(
|
||||||
{ class: "quest_editor_EventsView_event" },
|
{ class: "quest_editor_EventsView_event" },
|
||||||
@ -70,27 +156,38 @@ export class EventsView extends ResizableWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
element.append(event_element);
|
element.append(event_element);
|
||||||
event_elements.set(event.id, { element: event_element, position: i });
|
event_gui_data.set(event.id, { element: event_element, position: i });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render edges.
|
return {
|
||||||
defer(() => {
|
dag,
|
||||||
const SPACING = 8;
|
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 = "";
|
||||||
|
|
||||||
const used_depths: boolean[][] = Array(dag.events.length - 1);
|
const used_depths: boolean[][] = Array(dag.events.length - 1);
|
||||||
|
|
||||||
for (let i = 0; i < used_depths.length; i++) {
|
for (let i = 0; i < used_depths.length; i++) {
|
||||||
used_depths[i] = [];
|
used_depths[i] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_depth = 0;
|
|
||||||
|
|
||||||
for (const event of dag.events) {
|
for (const event of dag.events) {
|
||||||
const { element: event_element, position } = event_elements.get(event.id)!;
|
const { element: event_element, position } = event_gui_data.get(event.id)!;
|
||||||
|
|
||||||
const y_offset = event_element.offsetTop + event_element.offsetHeight;
|
const y_offset = event_element.offsetTop + event_element.offsetHeight;
|
||||||
|
|
||||||
for (const child of dag.get_children(event)) {
|
for (const child of dag.get_children(event)) {
|
||||||
const { element: child_element, position: child_position } = event_elements.get(
|
const { element: child_element, position: child_position } = event_gui_data.get(
|
||||||
child.id,
|
child.id,
|
||||||
)!;
|
)!;
|
||||||
const child_y_offset = child_element.offsetTop;
|
const child_y_offset = child_element.offsetTop;
|
||||||
@ -128,13 +225,13 @@ export class EventsView extends ResizableWidget {
|
|||||||
edge_element.style.width = `${width}px`;
|
edge_element.style.width = `${width}px`;
|
||||||
edge_element.style.height = `${height}px`;
|
edge_element.style.height = `${height}px`;
|
||||||
|
|
||||||
element.append(edge_element);
|
edge_container_element.append(edge_element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { element } of this.dag_gui_data) {
|
||||||
element.style.marginLeft = `${SPACING * max_depth}px`;
|
element.style.marginLeft = `${SPACING * max_depth}px`;
|
||||||
});
|
}
|
||||||
|
|
||||||
return [element, disposer];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user