From 860398a34053801a14e7815ba220b2263dc15587 Mon Sep 17 00:00:00 2001 From: aNNiMON Date: Sat, 9 Mar 2024 22:30:10 +0200 Subject: [PATCH] Add more main view implementations --- src/main.ts | 54 +++++-- src/parser/Parser.ts | 5 +- src/utils/TextUtils.ts | 14 +- src/view/MainView.ts | 311 ++++++++++++++++++++++++++++++++++++ src/view/model/ViewModel.ts | 6 +- 5 files changed, 372 insertions(+), 18 deletions(-) create mode 100644 src/view/MainView.ts diff --git a/src/main.ts b/src/main.ts index 4d8e752..edbe858 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,47 @@ import { Lexer } from './parser/Lexer' -import { BinaryExpression } from './parser/ast/BinaryExpression' -import { Expression } from './parser/ast/Expression' -import { Operator } from './parser/ast/Operator' -import { ValueExpression } from './parser/ast/ValueExpression' +import { Parser } from './parser/Parser' +import { MainView } from './view/MainView' -let expr1: Expression = new ValueExpression(10) -let expr2: Expression = new ValueExpression(20) -let exprAdd: Expression = new BinaryExpression(Operator.ADD, expr1, expr2) -console.log(exprAdd.eval()); +function run(data: string): void { + const tokens = new Lexer(data).process().getTokens(); + const parser = new Parser(tokens); + const view = new MainView(parser, false); + parser.setView(view); + view.init(); + parser.next(); +} -const tokens = new Lexer("10 / 2").process().getTokens(); -console.log(tokens) \ No newline at end of file +document.addEventListener('DOMContentLoaded', function() { + run(`scene bg black + "Основные теги:\n{b}жирный{/b} {i}курсив{/i} {u}подчёркнутый{/u}" + "{big}большой{/big} нормальный {small}маленький{/small}" + "{center}текст по центру" + sl "{center}текст по центру" + "{html}{center}

HTML

" + "{html}Большой big" + "{html}Маленький small" + "{html}Жирный bold strong" + "{html}Курсив i em cite dfn" + "{html}Подчёркнутый" + "{html}

Длинная цитата

blockquote
" + "{html}

Переносы

строк



br
" + "{html}Моноширинный tt" + "{html}Подстрочный sub Нормальный Надстрочный sup" + '{html}Ссылка a href' + "{html}

Заголовок h1

+

Заголовок h2

+

Заголовок h3

+

Заголовок h4

+
Заголовок h5
+
Заголовок h6
" + '{html}font color="red" + font color="purple" + font color="#336699" + font color="#ffffff"' + '{html}font face="serif" + font face="sans-serif" + font face="monospace"' + '{html}font face="monospace" color="#7f7f7f"' + "{html}

Параграф

p

" + "{html}
Блок div
"`) +}); \ No newline at end of file diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts index 1ead9a6..6e7c7df 100644 --- a/src/parser/Parser.ts +++ b/src/parser/Parser.ts @@ -34,10 +34,7 @@ export class Parser implements Navigable { /** Optimization, if there no endmenu/endif => don't search them */ this.hasEndMenu = false; this.hasEndIf = false; - } - - public getInstance(): Parser { - return this; + this.preScan(); } public setView(view: ViewModel): void { diff --git a/src/utils/TextUtils.ts b/src/utils/TextUtils.ts index e5fb9a9..25ddbb0 100644 --- a/src/utils/TextUtils.ts +++ b/src/utils/TextUtils.ts @@ -1,7 +1,19 @@ export class TextUtils { - public static isEmpty(str: string|null) { + public static isEmpty(str: string|null): boolean { if (typeof str === 'undefined') return true; if (str === undefined) return true; return str.length === 0; } + + public static equalsIgnoreCase(str: string, text: string): boolean { + return str.toLowerCase() == text.toLowerCase(); + } + + public static contains(str: string, text: string): boolean { + return str.indexOf(text) >= 0; + } + + public static replaceAll(str: string, find: string, replace: string): string { + return str.replace(new RegExp(find, 'g'), replace); + } } \ No newline at end of file diff --git a/src/view/MainView.ts b/src/view/MainView.ts new file mode 100644 index 0000000..39b0d12 --- /dev/null +++ b/src/view/MainView.ts @@ -0,0 +1,311 @@ +import { TextUtils } from "../utils/TextUtils"; +import { Characters } from "./model/Characters"; +import { FadeInfo } from "./model/FadeInfo"; +import { Menu } from "./model/Menu"; +import { Navigable } from "./model/Navigable"; +import { Transition } from "./model/Transition"; +import { TransitionType } from "./model/TransitionType"; +import { ViewModel } from "./model/ViewModel"; + +export class MainView implements ViewModel { + private static readonly NO_FADE = new FadeInfo(); + + private windowTag: HTMLElement; + private textAuthorTag: HTMLElement; + private textContentTag: HTMLElement; + + private musicPlayerAudio: HTMLAudioElement; + private soundPlayerAudio: HTMLAudioElement; + private soundLoopPlayerAudio: HTMLAudioElement; + private ambiencePlayerAudio: HTMLAudioElement; + private musicQueue: Array; + private soundQueue: Array; + + private backgroundName: string; + private backgroundType: string; + private characters: Characters; + + private useSpriteTransitions: boolean; + private spriteInContainer: {}; + + private blockTap: boolean; + private cancelNextStep: boolean; + private nextCommandRunnable: () => void; + + constructor(private navigable: Navigable, private debug: boolean = false) { + this.windowTag = document.getElementById('window'); + this.textAuthorTag = document.getElementById('textAuthor'); + this.textContentTag = document.getElementById('textContent'); + + this.musicPlayerAudio = new Audio(); + this.soundPlayerAudio = new Audio(); + this.soundLoopPlayerAudio = new Audio(); + this.ambiencePlayerAudio = new Audio(); + this.musicQueue = new Array(); + this.soundQueue = new Array(); + + this.backgroundName = ""; + this.backgroundType = ""; + this.characters = new Characters(); + this.characters.makeNamesUnknown(); + this.characters.makeNamesKnown(); + + // this.places = new MapPlaces(); // TODO + + this.useSpriteTransitions = true; + this.spriteInContainer = {}; + + this.blockTap = false; + this.cancelNextStep = false; + this.nextCommandRunnable = () => { + this.blockTap = false; + if (!this.cancelNextStep) navigable.next(); + }; + } + + public init(): void { + document.getElementById('menu').style.display = 'none'; + document.getElementById('mainMenuButton').onclick = (e) => this.showMainMenu(); + document.getElementById('background').onclick = (e) => this.onTouch(e); + } + + private onTouch(e: MouseEvent): void { + if (this.blockTap) return; + if (document.getElementById('menu').style.display !== 'none') return; + if ((e.target as HTMLElement).tagName.toLowerCase() === 'li') return; + if ((e.target as HTMLElement).id === 'mainMenuButton') return; + + this.cancelNextStep = true; + this.navigable.next(); + } + + private showMainMenu(): void { + } + + public text(text: string): void { + this.textAuthorTag.innerText = ''; + if (TextUtils.isEmpty(text)) this.windowHide(""); + else { + this.windowShow(""); + this.formatString(this.textContentTag, text); + } + } + + public textW(whoid: string, text: string): void { + if (TextUtils.equalsIgnoreCase(whoid, 'th')) this.text("~ " + text + " ~"); + else if (!this.characters.contains(whoid)) this.text(text); + else { + this.windowShow(""); + const person = this.characters.get(whoid); + this.textAuthorTag.textContent = person.name; + this.textAuthorTag.style.color = this.toColor(person.color); + this.formatString(this.textContentTag, text); + } + } + + private formatString(tag: HTMLElement, text: string) { + let edited = TextUtils.replaceAll(text, "\\{w.*?\\}", ""); + if (TextUtils.contains(edited, "{center}")) { + edited = TextUtils.replaceAll(edited, "\\{center\\}", ""); + this.textContentTag.style.textAlign = "center"; + } else { + this.textContentTag.style.textAlign = "left"; + } + if (TextUtils.contains(edited, "{html}")) { + edited = TextUtils.replaceAll(edited, "\\{html\\}", ""); + tag.innerHTML = edited; + return; + } + const codes = ["b", "i", "s", "u", "big", "small"]; + let html = false; + for (let i = 0; i < codes.length; i++) { + const ch = codes[i]; + if (TextUtils.contains(edited, "{" + ch + "}")) { + edited = TextUtils.replaceAll(edited, "{" + ch + "}", "<" + ch + ">"); + edited = TextUtils.replaceAll(edited, "{/" + ch + "}", ""); + html = true; + } + } + if (html) { + edited = edited.replace("\n", "
"); + tag.innerHTML = edited; + } else { + tag.textContent = edited; + // TODO + // const tmp = $(tag).text(edited); + // tmp.html(tmp.html().replace(/\n/g, '
')); + } + } + + public background(type: string, name: string, effect: string): void { + throw new Error("Method not implemented."); + } + + public sprite(whoid: string, params: string, position: string, alias: string, effect: string): void { + throw new Error("Method not implemented."); + } + + public hideSprite(whoid: string, effect: string): void { + if (!(whoid in this.spriteInContainer)) return; + this.hide(whoid, effect); + } + + private hide(whoid: string, effect: string): void { + const img = this.spriteInContainer[whoid]; + if (img == null) return; + if (effect in Transition.DEFAULT) { + const transition = Transition.DEFAULT[effect]; + if (transition["type"] === TransitionType.TYPE_FADE) { + // TODO + //img.fadeOut(transition["outTime"], function () { + // $(this).remove(); + //}); + img.remove(); + } else { + img.remove(); + } + } else { + img.remove(); + } + delete this.spriteInContainer[whoid]; + } + + private spritesClear() : void { + document.getElementById('container').innerHTML = ''; + this.spriteInContainer = {}; + } + + public menu(menu: Menu): void { + throw new Error("Method not implemented."); + } + + public showMap(): void { + throw new Error("Method not implemented."); + } + + public setZone(zone: string, label: string): void { + // this.places.setZone(name, label); + } + + public resetZone(zone: string): void { + // this.places.resetZone(zone); + } + + public disableCurrentZone(): void { + // this.places.disableCurrentZone(); + } + + public disableAllZones(): void { + // this.places.disableAllZones(); + } + + public makeNamesKnown(): void { + this.characters.makeNamesKnown(); + } + + public makeNamesUnknown(): void { + this.characters.makeNamesUnknown(); + } + + public meet(whoid: string, name: string): void { + this.characters.setName(whoid, name); + } + + public music(name: string, fade: FadeInfo): void { + throw new Error("Method not implemented."); + } + + public ambience(name: string, fade: FadeInfo): void { + throw new Error("Method not implemented."); + } + + public sound(name: string, fade: FadeInfo): void { + throw new Error("Method not implemented."); + } + + public soundLoop(name: string, fade: FadeInfo): void { + throw new Error("Method not implemented."); + } + + public addSoundToQueue(name: string): void { + throw new Error("Method not implemented."); + } + + public addMusicToQueue(name: string): void { + throw new Error("Method not implemented."); + } + + public stopAmbience(fade: FadeInfo): void { + throw new Error("Method not implemented."); + } + + public stopSoundLoop(fade: FadeInfo): void { + throw new Error("Method not implemented."); + } + + public stopSound(fade: FadeInfo): void { + throw new Error("Method not implemented."); + } + + public stopMusic(fade: FadeInfo): void { + throw new Error("Method not implemented."); + } + + public windowShow(effect: string): boolean { + if (this.windowTag.style.visibility !== "visible") { + this.windowTag.style.visibility = "visible"; + } + if (!TextUtils.isEmpty(effect)) { + this.background(this.backgroundType, this.backgroundName, effect); + return true; + } + return false; + } + + public windowHide(effect: string): boolean { + if (this.windowTag.style.visibility !== "hidden") { + this.windowTag.style.visibility = "hidden"; + } + if (!TextUtils.isEmpty(effect)) { + this.background(this.backgroundType, this.backgroundName, effect); + return true; + } + return false; + } + + private windowSwitchVisibility(): void { + if (this.windowTag.style.visibility === "hidden") { + this.windowTag.style.visibility = "visible"; + } else { + this.windowTag.style.visibility = "hidden"; + } + } + + public pause(duration: number, isHard: boolean): void { + this.blockTap = isHard; + this.cancelNextStep = false; + setTimeout(this.nextCommandRunnable, duration); + } + + public finish(): void { + if (this.debug) { + this.stopMusic(MainView.NO_FADE); + this.stopSound(MainView.NO_FADE); + this.stopSoundLoop(MainView.NO_FADE); + this.stopAmbience(MainView.NO_FADE); + this.spritesClear(); + this.text('{center}{html}Сценарий завершён'); + } else { + this.text('{center}{html}Вернуться на главную'); + } + } + + private toColor(num: number): string { + num >>>= 0; + const b = num & 0xFF, + g = (num & 0xFF00) >>> 8, + r = (num & 0xFF0000) >>> 16, + a = ( (num & 0xFF000000) >>> 24 ) / 255 ; + return "rgba(" + [r, g, b, a].join(",") + ")"; + } +} \ No newline at end of file diff --git a/src/view/model/ViewModel.ts b/src/view/model/ViewModel.ts index ec97625..5bc36dc 100644 --- a/src/view/model/ViewModel.ts +++ b/src/view/model/ViewModel.ts @@ -4,7 +4,6 @@ import { Menu } from "./Menu" export interface ViewModel { text(text: string): void textW(whoid: string, text: string): void - meet(whoid: string, name: string): void background(type: string, name: string, effect: string): void sprite(whoid: string, params: string, position: string, alias: string, effect: string): void hideSprite(whoid: string, effect: string): void @@ -12,12 +11,13 @@ export interface ViewModel { menu(menu: Menu): void showMap(): void setZone(zone: string, label: string): void + resetZone(zone: string): void disableCurrentZone(): void disableAllZones(): void - resetZone(zone: string): void - makeNamesUnknown(): void + meet(whoid: string, name: string): void makeNamesKnown(): void + makeNamesUnknown(): void music(name: string, fade: FadeInfo): void ambience(name: string, fade: FadeInfo): void