mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 07:18:29 +08:00
418 lines
16 KiB
TypeScript
418 lines
16 KiB
TypeScript
import { ResizableWidget } from "../../core/gui/ResizableWidget";
|
|
import { create_element, el } from "../../core/gui/dom";
|
|
import { QuestEditorToolBar } from "./QuestEditorToolBar";
|
|
import GoldenLayout, { Container, ContentItem, ItemConfigType } from "golden-layout";
|
|
import { QuestInfoView } from "./QuestInfoView";
|
|
import "golden-layout/src/css/goldenlayout-base.css";
|
|
import "../../core/gui/golden_layout_theme.css";
|
|
import { NpcCountsView } from "./NpcCountsView";
|
|
import { QuestEditorRendererView } from "./QuestEditorRendererView";
|
|
import { AsmEditorView } from "./AsmEditorView";
|
|
import { EntityInfoView } from "./EntityInfoView";
|
|
import { GuiStore, GuiTool } from "../../core/stores/GuiStore";
|
|
import { NpcListView } from "./NpcListView";
|
|
import { ObjectListView } from "./ObjectListView";
|
|
import { EventsView } from "./EventsView";
|
|
import { RegistersView } from "./RegistersView";
|
|
import { LogView } from "./LogView";
|
|
import { QuestRunnerRendererView } from "./QuestRunnerRendererView";
|
|
import { QuestEditorStore } from "../stores/QuestEditorStore";
|
|
import { AsmEditorStore } from "../stores/AsmEditorStore";
|
|
import { AreaStore } from "../stores/AreaStore";
|
|
import { EntityImageRenderer } from "../rendering/EntityImageRenderer";
|
|
import { AreaAssetLoader } from "../loading/AreaAssetLoader";
|
|
import { EntityAssetLoader } from "../loading/EntityAssetLoader";
|
|
import { DisposableThreeRenderer } from "../../core/rendering/Renderer";
|
|
import { QuestEditorUiPersister } from "../persistence/QuestEditorUiPersister";
|
|
import Logger = require("js-logger");
|
|
|
|
const logger = Logger.get("quest_editor/gui/QuestEditorView");
|
|
|
|
const DEFAULT_LAYOUT_CONFIG = {
|
|
settings: {
|
|
showPopoutIcon: false,
|
|
showMaximiseIcon: true,
|
|
showCloseIcon: true,
|
|
},
|
|
dimensions: {
|
|
headerHeight: 24,
|
|
},
|
|
labels: {
|
|
close: "Close",
|
|
maximise: "Maximise",
|
|
minimise: "Minimise",
|
|
popout: "Open in new window",
|
|
},
|
|
};
|
|
|
|
export class QuestEditorView extends ResizableWidget {
|
|
readonly element = el.div({ class: "quest_editor_QuestEditorView" });
|
|
|
|
/**
|
|
* Maps views to names and creation functions.
|
|
*/
|
|
private readonly view_map: Map<
|
|
new (...args: never) => ResizableWidget,
|
|
{ name: string; create(): ResizableWidget }
|
|
>;
|
|
|
|
private readonly tool_bar: QuestEditorToolBar;
|
|
|
|
private readonly layout_element = create_element("div", { class: "quest_editor_gl_container" });
|
|
private readonly layout: Promise<GoldenLayout>;
|
|
private loaded_layout: GoldenLayout | undefined;
|
|
|
|
private readonly sub_views = new Map<string, ResizableWidget>();
|
|
|
|
constructor(
|
|
private readonly gui_store: GuiStore,
|
|
area_store: AreaStore,
|
|
quest_editor_store: QuestEditorStore,
|
|
asm_editor_store: AsmEditorStore,
|
|
area_asset_loader: AreaAssetLoader,
|
|
entity_asset_loader: EntityAssetLoader,
|
|
entity_image_renderer: EntityImageRenderer,
|
|
private readonly quest_editor_ui_persister: QuestEditorUiPersister,
|
|
create_three_renderer: () => DisposableThreeRenderer,
|
|
) {
|
|
super();
|
|
|
|
// Don't change the values of this map, as they are persisted in the user's browser.
|
|
this.view_map = new Map<
|
|
new (...args: never) => ResizableWidget,
|
|
{ name: string; create(): ResizableWidget }
|
|
>([
|
|
[
|
|
QuestInfoView,
|
|
{ name: "quest_info", create: () => new QuestInfoView(quest_editor_store) },
|
|
],
|
|
[
|
|
NpcCountsView,
|
|
{ name: "npc_counts", create: () => new NpcCountsView(quest_editor_store) },
|
|
],
|
|
[
|
|
QuestEditorRendererView,
|
|
{
|
|
name: "quest_renderer",
|
|
create: () =>
|
|
new QuestEditorRendererView(
|
|
gui_store,
|
|
quest_editor_store,
|
|
area_asset_loader,
|
|
entity_asset_loader,
|
|
create_three_renderer(),
|
|
),
|
|
},
|
|
],
|
|
[
|
|
AsmEditorView,
|
|
{
|
|
name: "asm_editor",
|
|
create: () =>
|
|
new AsmEditorView(
|
|
gui_store,
|
|
quest_editor_store.quest_runner,
|
|
asm_editor_store,
|
|
),
|
|
},
|
|
],
|
|
[
|
|
EntityInfoView,
|
|
{ name: "entity_info", create: () => new EntityInfoView(quest_editor_store) },
|
|
],
|
|
[
|
|
NpcListView,
|
|
{
|
|
name: "npc_list_view",
|
|
create: () => new NpcListView(quest_editor_store, entity_image_renderer),
|
|
},
|
|
],
|
|
[
|
|
ObjectListView,
|
|
{
|
|
name: "object_list_view",
|
|
create: () => new ObjectListView(quest_editor_store, entity_image_renderer),
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (gui_store.feature_active("events")) {
|
|
this.view_map.set(EventsView, {
|
|
name: "events_view",
|
|
create: () => new EventsView(quest_editor_store),
|
|
});
|
|
}
|
|
|
|
if (gui_store.feature_active("vm")) {
|
|
this.view_map.set(QuestRunnerRendererView, {
|
|
name: "quest_runner",
|
|
create: () =>
|
|
new QuestRunnerRendererView(
|
|
gui_store,
|
|
quest_editor_store,
|
|
area_asset_loader,
|
|
entity_asset_loader,
|
|
create_three_renderer(),
|
|
),
|
|
});
|
|
this.view_map.set(LogView, { name: "log_view", create: () => new LogView() });
|
|
this.view_map.set(RegistersView, {
|
|
name: "registers_view",
|
|
create: () => new RegistersView(quest_editor_store.quest_runner),
|
|
});
|
|
}
|
|
|
|
this.tool_bar = this.disposable(
|
|
new QuestEditorToolBar(gui_store, area_store, quest_editor_store),
|
|
);
|
|
|
|
this.element.append(this.tool_bar.element, this.layout_element);
|
|
|
|
this.layout = this.init_golden_layout();
|
|
|
|
this.layout.then(layout => (this.loaded_layout = layout));
|
|
|
|
this.disposables(
|
|
gui_store.on_global_keydown(
|
|
GuiTool.QuestEditor,
|
|
"Ctrl-Alt-D",
|
|
() => (quest_editor_store.debug.val = !quest_editor_store.debug.val),
|
|
),
|
|
|
|
quest_editor_store.quest_runner.running.observe(async ({ value: running }) => {
|
|
const layout = await this.layout;
|
|
|
|
if (quest_editor_store.quest_runner.running.val === running) {
|
|
const runner_items = layout.root.getItemsById(
|
|
this.view_map.get(QuestRunnerRendererView)!.name,
|
|
);
|
|
|
|
if (running) {
|
|
if (runner_items.length === 0) {
|
|
const renderer_item = layout.root.getItemsById(
|
|
this.view_map.get(QuestEditorRendererView)!.name,
|
|
)[0];
|
|
|
|
renderer_item.parent.addChild({
|
|
id: this.view_map.get(QuestRunnerRendererView)!.name,
|
|
title: "Quest",
|
|
type: "component",
|
|
componentName: this.view_map.get(QuestRunnerRendererView)!.name,
|
|
isClosable: false,
|
|
});
|
|
}
|
|
} else {
|
|
for (const item of runner_items) {
|
|
item.remove();
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
);
|
|
|
|
this.finalize_construction();
|
|
}
|
|
|
|
resize(width: number, height: number): this {
|
|
super.resize(width, height);
|
|
|
|
const layout_height = Math.max(0, height - this.tool_bar.height);
|
|
this.layout_element.style.width = `${width}px`;
|
|
this.layout_element.style.height = `${layout_height}px`;
|
|
this.layout.then(layout => layout.updateSize(width, layout_height));
|
|
|
|
return this;
|
|
}
|
|
|
|
dispose(): void {
|
|
super.dispose();
|
|
this.layout.then(layout => layout.destroy());
|
|
|
|
for (const view of this.sub_views.values()) {
|
|
view.dispose();
|
|
}
|
|
|
|
this.sub_views.clear();
|
|
}
|
|
|
|
private async init_golden_layout(): Promise<GoldenLayout> {
|
|
const default_layout_content = this.get_default_layout_content();
|
|
|
|
try {
|
|
const content = await this.quest_editor_ui_persister.load_layout_config(
|
|
default_layout_content,
|
|
);
|
|
|
|
if (content) {
|
|
const gl = this.attempt_gl_init({
|
|
...DEFAULT_LAYOUT_CONFIG,
|
|
content,
|
|
});
|
|
|
|
logger.info("Instantiated golden layout with persisted layout.");
|
|
|
|
return gl;
|
|
}
|
|
} catch (e) {
|
|
logger.warn("Couldn't instantiate golden layout with persisted layout.", e);
|
|
}
|
|
|
|
logger.info("Instantiating golden layout with default layout.");
|
|
|
|
return this.attempt_gl_init({
|
|
...DEFAULT_LAYOUT_CONFIG,
|
|
content: default_layout_content,
|
|
});
|
|
}
|
|
|
|
private attempt_gl_init(config: GoldenLayout.Config): GoldenLayout {
|
|
const layout = new GoldenLayout(config, this.layout_element);
|
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
const self = this;
|
|
|
|
try {
|
|
for (const { name, create } of this.view_map.values()) {
|
|
// registerComponent expects a regular function and not an arrow function. This
|
|
// function will be called with new.
|
|
layout.registerComponent(name, function(container: Container) {
|
|
const view = create();
|
|
|
|
container.on("close", () => view.dispose());
|
|
container.on("resize", () =>
|
|
// Subtract 4 from height to work around bug in Golden Layout related to
|
|
// headerHeight.
|
|
view.resize(container.width, container.height - 4),
|
|
);
|
|
|
|
view.resize(container.width, container.height);
|
|
|
|
self.sub_views.set(name, view);
|
|
container.getElement().append(view.element);
|
|
});
|
|
}
|
|
|
|
layout.on("stateChanged", () => {
|
|
this.quest_editor_ui_persister.persist_layout_config(layout.toConfig().content);
|
|
});
|
|
|
|
layout.on("stackCreated", (stack: ContentItem) => {
|
|
stack.on("activeContentItemChanged", (item: ContentItem) => {
|
|
if ("componentName" in item.config) {
|
|
const view = this.sub_views.get(item.config.componentName);
|
|
if (view) view.focus();
|
|
}
|
|
});
|
|
});
|
|
|
|
layout.init();
|
|
|
|
return layout;
|
|
} catch (e) {
|
|
layout.destroy();
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
private get_default_layout_content(): ItemConfigType[] {
|
|
return [
|
|
{
|
|
type: "row",
|
|
content: [
|
|
{
|
|
type: "column",
|
|
width: 2,
|
|
content: [
|
|
{
|
|
type: "stack",
|
|
content: [
|
|
{
|
|
title: "Info",
|
|
type: "component",
|
|
componentName: this.view_map.get(QuestInfoView)!.name,
|
|
isClosable: false,
|
|
},
|
|
{
|
|
title: "NPC Counts",
|
|
type: "component",
|
|
componentName: this.view_map.get(NpcCountsView)!.name,
|
|
isClosable: false,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: "Entity",
|
|
type: "component",
|
|
componentName: this.view_map.get(EntityInfoView)!.name,
|
|
isClosable: false,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "stack",
|
|
width: 9,
|
|
content: [
|
|
{
|
|
id: this.view_map.get(QuestEditorRendererView)!.name,
|
|
title: "3D View",
|
|
type: "component",
|
|
componentName: this.view_map.get(QuestEditorRendererView)!.name,
|
|
isClosable: false,
|
|
},
|
|
{
|
|
title: "Script",
|
|
type: "component",
|
|
componentName: this.view_map.get(AsmEditorView)!.name,
|
|
isClosable: false,
|
|
},
|
|
...(this.gui_store.feature_active("vm")
|
|
? [
|
|
{
|
|
title: "Debug Log",
|
|
type: "component",
|
|
componentName: this.view_map.get(LogView)!.name,
|
|
isClosable: false,
|
|
},
|
|
{
|
|
title: "Registers",
|
|
type: "component",
|
|
componentName: this.view_map.get(RegistersView)!.name,
|
|
isClosable: false,
|
|
},
|
|
]
|
|
: []),
|
|
],
|
|
},
|
|
{
|
|
type: "stack",
|
|
width: 2,
|
|
content: [
|
|
{
|
|
title: "NPCs",
|
|
type: "component",
|
|
componentName: this.view_map.get(NpcListView)!.name,
|
|
isClosable: false,
|
|
},
|
|
{
|
|
title: "Objects",
|
|
type: "component",
|
|
componentName: this.view_map.get(ObjectListView)!.name,
|
|
isClosable: false,
|
|
},
|
|
...(this.gui_store.feature_active("events")
|
|
? [
|
|
{
|
|
title: "Events",
|
|
type: "component",
|
|
componentName: this.view_map.get(EventsView)!.name,
|
|
isClosable: false,
|
|
},
|
|
]
|
|
: []),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
}
|
|
}
|