mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 07:18:29 +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];
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (options && options.call_now) {
|
||||
this.call_list_observer(observer, {
|
||||
type: ListChangeType.ListChange,
|
||||
index: 0,
|
||||
removed: [],
|
||||
inserted: this.val,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
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 {
|
||||
const super_disposable = super.observe_list(observer);
|
||||
observe_list(
|
||||
observer: (change: ListPropertyChangeEvent<T>) => void,
|
||||
options?: { call_now?: boolean },
|
||||
): Disposable {
|
||||
const super_disposable = super.observe_list(observer, options);
|
||||
|
||||
this.init_dependency_disposable();
|
||||
|
||||
|
@ -29,7 +29,10 @@ export interface ListProperty<T> extends Property<readonly 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>;
|
||||
}
|
||||
|
@ -30,6 +30,10 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.quest_editor_EventsView_edge_container {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.quest_editor_EventsView_edge {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { QuestEventDagModel } from "../model/QuestEventDagModel";
|
||||
import { Disposer } from "../../core/observable/Disposer";
|
||||
@ -7,9 +7,25 @@ import { NumberInput } from "../../core/gui/NumberInput";
|
||||
import "./EventsView.css";
|
||||
import { Disposable } from "../../core/observable/Disposable";
|
||||
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 {
|
||||
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" });
|
||||
|
||||
@ -24,29 +40,99 @@ export class EventsView extends ResizableWidget {
|
||||
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 => {
|
||||
this.quest_disposer.dispose_all();
|
||||
if (this.event_dags_observer) {
|
||||
this.event_dags_observer.dispose();
|
||||
}
|
||||
|
||||
const quest = quest_editor_store.current_quest.val;
|
||||
const area = quest_editor_store.current_area.val;
|
||||
|
||||
if (quest && area) {
|
||||
this.quest_disposer.add(
|
||||
bind_children_to(
|
||||
this.element,
|
||||
quest.event_dags.filtered(dag => dag.area_id === area.id),
|
||||
this.create_dag_element,
|
||||
),
|
||||
);
|
||||
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 create_dag_element = (dag: QuestEventDagModel): [HTMLElement, Disposable] => {
|
||||
const disposer = new Disposer();
|
||||
const element = el.div({ class: "quest_editor_EventsView_dag" });
|
||||
const event_elements = new Map<number, { element: HTMLDivElement; position: number }>();
|
||||
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();
|
||||
}
|
||||
|
||||
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) => {
|
||||
const event_element = el.div(
|
||||
{ class: "quest_editor_EventsView_event" },
|
||||
@ -70,27 +156,38 @@ export class EventsView extends ResizableWidget {
|
||||
);
|
||||
|
||||
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.
|
||||
defer(() => {
|
||||
const SPACING = 8;
|
||||
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 = "";
|
||||
|
||||
const used_depths: boolean[][] = Array(dag.events.length - 1);
|
||||
|
||||
for (let i = 0; i < used_depths.length; i++) {
|
||||
used_depths[i] = [];
|
||||
}
|
||||
|
||||
let max_depth = 0;
|
||||
|
||||
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;
|
||||
|
||||
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,
|
||||
)!;
|
||||
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.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`;
|
||||
});
|
||||
|
||||
return [element, disposer];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user