This commit is contained in:
Adrian 2024-10-21 16:05:47 -04:00
parent f4c0b53369
commit d6e08aff5c
3 changed files with 150 additions and 104 deletions

View File

@ -153,6 +153,22 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
super.clear();
}
clearText(): void {
super.clearText();
if (this.message.getData("originalMaxLines")) {
this.message.setMaxLines(this.message.getData("originalMaxLines"));
this.message.data.remove("originalMaxLines");
}
if (this.message.getData("originalMaxWidth")) {
this.message.setWordWrapWidth(this.message.getData("originalMaxWidth"));
this.message.data.remove("originalMaxWidth");
}
if (this.message.getData("originalFontSize")) {
this.message.setFontSize(this.message.getData("originalFontSize"));
this.message.data.remove("originalFontSize");
}
}
showText(text: string, delay?: integer | null, callback?: Function | null, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) {
this.hideNameText();
super.showText(text, delay, callback, callbackDelay, prompt, promptDelay);

View File

@ -71,8 +71,13 @@ export default class CommandUiHandler extends UiHandler {
const commandMessage = i18next.t("commandUiHandler:actionMessage", { pokemonName: getPokemonNameWithAffix(commandPhase.getPokemon()) });
messageHandler.tryAdjustText(commandMessage, {
maxWidth: messageMaxWidth,
guideHeight: messageHandler.bg
guideHeight: messageHandler.movesWindowContainer,
padding: {
right: 0,
bottom: 8
}
});
console.log(messageHandler.message);
messageHandler.showText(commandMessage, 0);

View File

@ -11,7 +11,6 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
public message: Phaser.GameObjects.Text;
public prompt: Phaser.GameObjects.Sprite;
protected promptOut: { x: number, y: number } | null;
constructor(scene: BattleScene, mode: Mode | null = null) {
super(scene, mode);
@ -75,6 +74,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
}
if (text) {
// text = this.runWrapSpecialCase(text); // TODO
// Predetermine overflow line breaks to avoid words breaking while displaying
const textWords = text.split(" ");
let lastLineCount = 1;
@ -86,7 +86,8 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
newText = nextWordText;
lastLineCount++;
} else {
const lineCount = this.message.runWordWrap(nextWordText).split(/\n/g).length;
const runWrap = this.message.runWordWrap(nextWordText);
const lineCount = runWrap.split(/\n/g).length;
if (lineCount > lastLineCount) {
lastLineCount = lineCount;
newText = `${newText}\n${textWords[w]}`;
@ -96,9 +97,20 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
}
}
text = newText;
const wasTextAdjusted = this.message.getData("originalMaxWidth") || this.message.getData("originalFontSize") || this.message.getData("originalMaxLines");
if (!!wasTextAdjusted) {
const isWidthOverflow = (this.message.style.wordWrapWidth && this.textSize(newText).width > this.message.style.wordWrapWidth);
const isHeightOverflow = (this.textHeight(newText) + (this.message.y / this.message.scale) > this.textHeight(text));
const isOverflow = isWidthOverflow || isHeightOverflow;
if (!isOverflow) {
text = newText;
}
} else {
text = newText;
}
}
if (this.textTimer) {
this.textTimer.remove();
if (this.textCallbackTimer) {
@ -192,16 +204,22 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
showPrompt(callback?: Function | null, callbackDelay?: integer | null) {
if (this.prompt) {
this.prompt.setScale(parseInt(this.message.style.fontSize.toString()) / 100 + 0.04);
const textSize = Phaser.GameObjects.GetTextSize(this.message, this.message.style.getTextMetrics(), this.message.getWrappedText(this.message.text));
const fontScale = parseInt(this.message.style.fontSize.toString()) / 100 + 0.04;
const scale = this.message.scale;
this.prompt.setScale(fontScale);
const textSize = this.textSize(this.message.text);
const lastLineWidth = textSize.lineWidths[textSize.lineWidths.length - 1];
let x = lastLineWidth * this.message.scale + this.message.x + 2;
let y = this.message.y + (textSize.height * this.message.scale / (20 - textSize.lines) - 0.5) * 18;
if (this.promptOut) {
x = this.promptOut.x * this.message.scale - (this.message.x * 2) + 2;
y = (this.promptOut.y - (this.message.y * 2)) * this.message.scale / 1.3;
this.promptOut = null;
let x = lastLineWidth * scale + this.message.x + 2;
let y = this.message.y + (textSize.lineHeight * scale * (textSize.lines - 1)) + (textSize.lineSpacing * scale * (textSize.lines - 1)) + 2;
// wrap prompt
if (this.message.style.wordWrapWidth) {
if (lastLineWidth + this.prompt.getBounds().width >= this.message.style.wordWrapWidth) {
x = this.message.x;
y = y * 1.5;
}
}
this.prompt.setPosition(x, y);
this.prompt.play("prompt");
}
@ -211,6 +229,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
if (this.prompt) {
this.prompt.anims.stop();
this.prompt.setVisible(false);
this.prompt.setScale(1);
}
if (callback) {
if (callbackDelay) {
@ -246,7 +265,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
}
/**
* Use before showText(), ex:
* @example Use before showText():
* ``` ts
* // Handler extends MessageUiHandler.ts...
* const ui = this.getUi();
@ -259,19 +278,27 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
* super.showText(...);
* }
* ```
* @description It tests a text before showing it to adjust it to a graphic element
* @param text
* @param opts options additional
* @argument ignoreLanguages ignore adjust for some languages or for all.
* @argument maxWidth default this.message.style.wordWrapWidth or this.message.parentContainer.getBounds().width.
* @argument guideHeight default this.message.parentContainer, If the container has many elements or `this.message` does not have a clear guide, use the parent container as a reference guide by default.
* @argument padding default { right: this.message.x, bottom: this.message.y }.
*/
tryAdjustText(text: string, opts?: argsAdjustText): void {
const currentLanguage = i18next.resolvedLanguage!;
if (opts?.ignoreLanguages && opts.ignoreLanguages[0] && (opts.ignoreLanguages === "all" || !opts.ignoreLanguages.some(localKey => localKey === currentLanguage))) {
if (opts?.ignoreLanguages && opts.ignoreLanguages[0] && (opts.ignoreLanguages === "all" || opts.ignoreLanguages.some(localKey => localKey === currentLanguage)) || !text) {
return;
}
const textPaddingScale = 1.2;
const posX = (opts?.padding?.right ?? this.message.x) * textPaddingScale || 1;
const posY = (opts?.padding?.bottom ?? this.message.y) * textPaddingScale || 1;
// text = this.runWrapSpecialCase(text); // TODO
const referenceGuide = opts?.guideHeight ?? this.message.parentContainer;
// If any style changes were made in previous tryAdjustText() calls, revert to the original data.
@ -280,7 +307,12 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
this.message.style.setMaxLines(this.message.getData("originalMaxLines"));
this.message.data.remove("originalMaxLines");
}
const maxWidth = this.message.getData("originalMaxWidth") ?? Math.floor(opts?.maxWidth ?? this.message.style.wordWrapWidth ?? referenceGuide.getBounds().width);
const paddingX = posX / this.message.scale;
const paddingY = posY / this.message.scale - this.message.lineSpacing;
const maxHeight = referenceGuide.getBounds().height - (paddingY * 2);
const maxWidth = this.message.getData("originalMaxWidth") ?? Math.floor(opts?.maxWidth ?? this.message.style.wordWrapWidth ?? referenceGuide.getBounds().width) - paddingX;
this.message.setData("originalMaxWidth", this.message.getData("originalMaxWidth") ?? maxWidth);
this.message.setWordWrapWidth(maxWidth);
@ -288,114 +320,107 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
this.message.setData("originalFontSize", fontSize);
this.message.setFontSize(fontSize);
const scale = this.message.scale;
const getFontSize = () => parseInt(this.message.style.fontSize.toString());
const textWrapped = () => this.message.getWrappedText(text);
const textSize = () => Phaser.GameObjects.GetTextSize(this.message, this.message.style.getTextMetrics(), textWrapped());
const xToPaddingX = ((this.message.x ** 2) - this.message.x / 2) * 2; // Approximate equivalent to what the padding.x (padding.left + padding.right) should be
const paddingX = (xToPaddingX * 1.5) / (this.message.x * scale) - this.message.x * 8 || 0; // If it's too large, scale it down to maintain aspect ratio with x
const yToPaddingY = ((this.message.y ** 2) - this.message.y / 2) * 2; // Approximate equivalent to what the padding.y (padding.top + padding.bottom) should be
const paddingY = (yToPaddingY * 1.5) / (this.message.y * scale) - this.message.y * 2 || 0; // If it's too large, scale it down to maintain aspect ratio with y
// FontSize adjust
let fontDecrement = fontSize;
const adjustFontSize = (condition: () => boolean = () => (textWrapped().length > this.message.style.maxLines)): void => {
if (Utils.isNullOrUndefined(text) || text === "") {
return;
}
const minFontSize = 40;
while (condition() && fontDecrement > minFontSize) {
fontDecrement--;
this.message.setFontSize(fontDecrement);
// If the text has been shrunk so much that another line can fit in the text, add it
// This is to preserve the maximum possible font size.
if ((textSize().height + textSize().lineHeight + paddingY) < referenceGuide.getBounds().height) {
const linesNeed = Math.round((textSize().height + paddingY) / textSize().lineHeight);
if (linesNeed > this.message.style.maxLines) {
if (!this.message.getData("originalMaxLines")) {
// We save the current value as it will be modified, so we can return it in the next showText()
this.message.setData("originalMaxLines", this.message.style.maxLines);
}
this.message.style.setMaxLines(linesNeed);
}
}
if (textSize().height + paddingY - 15 > referenceGuide.getBounds().height) {
if (!this.message.getData("originalMaxLines")) {
// We save the current value as it will be modified, so we can return it in the next showText()
this.message.setData("originalMaxLines", this.message.style.maxLines);
}
this.message.style.setMaxLines(--this.message.style.maxLines);
adjustFontSize();
break;
}
// checking the maximum width
this.message.setWordWrapWidth(maxWidth);
adjustWordWrap();
}
};
// wordWrapWidth adjust
let widthDecrement = maxWidth;
const adjustWordWrap = (): void => {
if (Utils.isNullOrUndefined(text) || text === "") {
return;
}
if (textSize().width + paddingX + Math.round((widthDecrement - textSize().width) / 1.15) >= maxWidth) {
while (textSize().width + paddingX + Math.round((widthDecrement - textSize().width) / 1.15) >= maxWidth && widthDecrement > 100) {
widthDecrement--;
this.message.setWordWrapWidth(widthDecrement);
const wrapWidthAdjust = () => {
if (this.textSize(text).width
+ Math.ceil(this.message.style.wordWrapWidth! + (paddingX / (posX / 2)) - this.textSize(text).width)
>= maxWidth) {
while (
this.textSize(text).width
+ Math.ceil(this.message.style.wordWrapWidth! + (paddingX / (posX / 2)) - this.textSize(text).width)
>= maxWidth
&& this.message.style.wordWrapWidth! > 10) {
this.message.setWordWrapWidth(--this.message.style.wordWrapWidth!);
}
}
// If after trying to adjust the wordWrapWidth it remains the same, it means that..
//.. there is no space between words, so the fontSize is adjusted to fit.
if (textSize().width + (paddingX * 1.2) >= maxWidth) {
this.message.setWordWrapWidth(maxWidth);
adjustFontSize(() => textSize().width + (paddingX * 1.2) >= maxWidth);
if (this.textSize(text).width + (posX * 2) >= maxWidth) {
if (this.textSize(text).width >= maxWidth) {
this.message.setWordWrapWidth(maxWidth);
}
while (this.textSize(text).width + (posX * 2) >= maxWidth && getFontSize() > 30) {
this.message.setFontSize(getFontSize() - 1);
}
}
};
adjustWordWrap();
wrapWidthAdjust();
if (textWrapped().length > this.message.style.maxLines) {
if (
this.textHeight(text) > maxHeight
+ Math.floor(this.textHeight(text) + (paddingY / posY) - this.textSize(text).height)
|| this.textWrapped(text).length > this.message.style.maxLines
) {
adjustFontSize();
while (
this.textHeight(text) > maxHeight
+ Math.floor(this.textHeight(text) + (paddingY / posY) - this.textSize(text).height)
|| this.textWrapped(text).length > this.message.style.maxLines
&& getFontSize() > 10
) {
this.message.setFontSize(getFontSize() - 1);
adjustWordWrap(); // after adjustFontSize, respect "padding"
if (!this.message.getData("originalMaxLines")) {
this.message.setData("originalMaxLines", this.message.style.maxLines);
}
this.message.setMaxLines(Math.ceil(this.textHeight(text) / (this.textSize(text).lineHeight + posY + this.textWrapped(text).length)));
// Some line breaks (\n) may also prevent the text from being displayed..
//.. so if the text is still too large due to the previous issue, adjust it.
if (textSize().height + paddingY - 15 > referenceGuide.getBounds().height) {
adjustFontSize(() => (textSize().height + paddingY - 15 > referenceGuide.getBounds().height));
if (this.textSize(text).width >= maxWidth) {
this.message.setWordWrapWidth(maxWidth);
}
wrapWidthAdjust();
}
}
const lastLine = textSize().lineWidths[textSize().lineWidths.length - 1];
// If when adjusting the text the prompt goes outside..
// .. save the data to put the prompt in the bottom right corner
if (!this.promptOut && lastLine + (paddingX * 1.5) >= maxWidth) {
this.promptOut = {
x: maxWidth,
y: referenceGuide.getBounds().height
};
} else {
this.promptOut = null;
}
}
protected textWrapped(text: string): string[] {
return this.message.getWrappedText(text);
}
protected textSize(text: string): Phaser.Types.GameObjects.Text.GetTextSizeObject {
return Phaser.GameObjects.GetTextSize(this.message, this.message.style.getTextMetrics(), this.textWrapped(text));
}
protected textHeight(text: string): number {
return this.textSize(text).lineHeight * this.textWrapped(text).length + (this.textSize(text).lineSpacing * (this.textWrapped(text).length - 1));
}
// // TODO: Line breaking rules about the beginning/end of a line, for more information see https://en.wikipedia.org/wiki/Line_breaking_rules_in_East_Asian_languages#Line_breaking_rules_in_Japanese_text_(Kinsoku_Shori)
// private runWrapSpecialCase(text: string): string {
// let newText = "";
// if (text) {
// const textWords = text.split(/[  。?!!.,-]/g);
// const space = text.match(/[  。?!!.,-]/g);
// for (let w = 0; w < textWords.length; w++) {
// let spaceType: string = " ";
// if (space?.[0]) {
// spaceType = space[w - 1];
// }
// let nextWordText: string = newText ? `${newText}${spaceType}${textWords[w]}` : textWords[w];
// const sizeNewText = this.textSize(nextWordText);
// const isWidthOverflow = this.message.style.wordWrapWidth && sizeNewText.width > this.message.style.wordWrapWidth;
// if (isWidthOverflow && textWords[w]) {
// nextWordText = newText !== "" ? `${newText}${spaceType}\n${textWords[w]}` : textWords[w];
// }
// newText = nextWordText;
// }
// }
// return newText;
// }
}
interface argsAdjustText {
ignoreLanguages?: Array<string> | "all" | null;
maxWidth?: number;
guideHeight?: Phaser.GameObjects.Container | Phaser.GameObjects.Sprite | Phaser.GameObjects.NineSlice
guideHeight?: Phaser.GameObjects.Container | Phaser.GameObjects.Sprite | Phaser.GameObjects.NineSlice;
padding?: { right?: number, bottom?: number };
}