diff --git a/src/main/java/cz/jzitnik/events/handlers/DialogEventHandler.java b/src/main/java/cz/jzitnik/events/handlers/DialogEventHandler.java index bb63a87..a3fa46d 100644 --- a/src/main/java/cz/jzitnik/events/handlers/DialogEventHandler.java +++ b/src/main/java/cz/jzitnik/events/handlers/DialogEventHandler.java @@ -7,7 +7,6 @@ import cz.jzitnik.annotations.EventHandler; import cz.jzitnik.annotations.injectors.InjectDependency; import cz.jzitnik.annotations.injectors.InjectState; import cz.jzitnik.events.RerenderScreen; -import cz.jzitnik.game.GameState; import cz.jzitnik.game.dialog.Dialog; import cz.jzitnik.game.dialog.OnEnd; import cz.jzitnik.states.DialogState; @@ -16,7 +15,6 @@ import cz.jzitnik.states.TerminalState; import cz.jzitnik.ui.pixels.AlphaPixel; import cz.jzitnik.ui.pixels.ColoredPixel; import cz.jzitnik.ui.pixels.Empty; -import cz.jzitnik.ui.pixels.Pixel; import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.TextRenderer; import cz.jzitnik.utils.events.AbstractEventHandler; @@ -49,22 +47,34 @@ public class DialogEventHandler extends AbstractEventHandler { @InjectDependency private TextRenderer textRenderer; + private static final int WIDTH = 350; private static final int MARGIN_BOTTOM = 15; - private static final int PADDING = 7; + public static final int PADDING = 7; private static final int BUTTON_TEXT_PADDING = 4; private static final int QUESTION_ACTIONS_GAP = 10; - private static final int BUTTON_HEIGHT = 15; - private static final int BUTTON_PADDING = 5; + public static final int BUTTON_HEIGHT = 15; + public static final int BUTTON_PADDING = 5; private static final float FONT_SIZE = 15f; - public static TerminalSize getSize(TextRenderer textRenderer, Dialog dialog) { - int WIDTH = 355; + public static int calculateButtonHeight(Dialog dialog) { + if (dialog.getOnEnd() instanceof OnEnd.AskQuestion(OnEnd.AskQuestion.Answer[] answers)) { + return answers.length * BUTTON_HEIGHT + (answers.length - 1) * BUTTON_PADDING; + } else { + return 0; + } + } + + public static int getYStartButtons(TextRenderer textRenderer, Dialog dialog) { var textSize = textRenderer.measureText(dialog.getText(), WIDTH - PADDING * 2, FONT_SIZE); - return new TerminalSize(355, PADDING + textSize.height + ( - dialog.getOnEnd() instanceof OnEnd.AskQuestion( - OnEnd.AskQuestion.Answer[] answers - ) ? answers.length * BUTTON_HEIGHT + (answers.length - 1) * BUTTON_PADDING : 0 + return PADDING + textSize.height + BUTTON_PADDING; + } + + public static TerminalSize getSize(TextRenderer textRenderer, Dialog dialog) { + var textSize = textRenderer.measureText(dialog.getText(), WIDTH - PADDING * 2, FONT_SIZE); + + return new TerminalSize(300, PADDING + textSize.height + ( + dialog.getOnEnd() instanceof OnEnd.AskQuestion ? BUTTON_PADDING + calculateButtonHeight(dialog) : 0 ) + PADDING); } @@ -77,6 +87,7 @@ public class DialogEventHandler extends AbstractEventHandler { @Override public void handle(Dialog event) { + boolean onlyLast = dialogState.getCurrentDialog() == event; dialogState.setCurrentDialog(event); TerminalSize terminalSize = terminalState.getTerminalScreen().getTerminalSize(); var overrideBuffer = screenBuffer.getGlobalOverrideBuffer(); @@ -98,13 +109,15 @@ public class DialogEventHandler extends AbstractEventHandler { } } + dialogState.setRenderInProgress(true); try { - for (AlphaPixel[][] buf : animation) { + for (int i = onlyLast ? animation.length : 0; i <= animation.length; i++) { + var buf = animation[Math.min(i, animation.length - 1)]; for (int y = 0; y < size.getRows(); y++) { for (int x = 0; x < size.getColumns(); x++) { var textPixel = buf[Math.min(Math.max(0, y - PADDING), buf.length - 1)][Math.min(Math.max(0, x - PADDING), buf[0].length - 1)]; if (textPixel instanceof Empty || y < PADDING || x < PADDING || x >= size.getColumns() - PADDING || y >= size.getRows() - PADDING) { - if (y - 2 > textSize.height + QUESTION_ACTIONS_GAP && onEnd instanceof OnEnd.AskQuestion( + if (i == animation.length && y - 2 > textSize.height + QUESTION_ACTIONS_GAP && onEnd instanceof OnEnd.AskQuestion( OnEnd.AskQuestion.Answer[] answers )) { int buttonsY = y - textSize.height - QUESTION_ACTIONS_GAP - 2; @@ -118,7 +131,7 @@ public class DialogEventHandler extends AbstractEventHandler { var buttonTextPixel = buttonBuf[Math.min(Math.max(0, localY), buttonBuf.length - 1)][Math.min(Math.max(0, localX), buttonBuf[0].length - 1)]; if (buttonTextPixel instanceof Empty || localY < 0 || localX < 0 || localY >= buttonBuf.length || localX >= buttonBuf[0].length) { - overrideBuffer[start.getRow() + y][start.getColumn() + x] = new ColoredPixel(new TextColor.RGB(255, 255, 255), 1f); + overrideBuffer[start.getRow() + y][start.getColumn() + x] = new ColoredPixel(new TextColor.RGB(255, 255, 255), dialogState.getHoveredButtonIndex() == buttonIndex ? 0.8f : 0.6f); } else { overrideBuffer[start.getRow() + y][start.getColumn() + x] = buttonTextPixel; } @@ -147,11 +160,29 @@ public class DialogEventHandler extends AbstractEventHandler { Thread.sleep(1000 / event.getTypingSpeed()); } + dialogState.setRenderInProgress(false); + if (onEnd instanceof OnEnd.Continue(Dialog nextDialog)) { Thread.sleep(1000); - eventManager.emitEvent(nextDialog); - } else if (onEnd instanceof OnEnd.AskQuestion(OnEnd.AskQuestion.Answer[] answers)) { + for (int y = start.getRow(); y < start.getRow() + size.getRows(); y++) { + for (int x = start.getColumn(); x < start.getColumn() + size.getColumns(); x++) { + screenBuffer.getGlobalOverrideBuffer()[y][x] = new Empty(); + } + } + if (nextDialog == null) { + dialogState.setCurrentDialog(null); + eventManager.emitEvent( + new RerenderScreen( + new RerenderScreen.ScreenPart( + start, + new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows()) + ) + ) + ); + } else { + eventManager.emitEvent(nextDialog); + } } } catch (InterruptedException e) { throw new RuntimeException(e); diff --git a/src/main/java/cz/jzitnik/states/DialogState.java b/src/main/java/cz/jzitnik/states/DialogState.java index b7fa618..60f15e0 100644 --- a/src/main/java/cz/jzitnik/states/DialogState.java +++ b/src/main/java/cz/jzitnik/states/DialogState.java @@ -8,4 +8,6 @@ import lombok.Data; @Data public class DialogState { private Dialog currentDialog; + private boolean renderInProgress = false; + private int hoveredButtonIndex = -1; } diff --git a/src/main/java/cz/jzitnik/ui/DialogUI.java b/src/main/java/cz/jzitnik/ui/DialogUI.java index 7acb450..af1ea7f 100644 --- a/src/main/java/cz/jzitnik/ui/DialogUI.java +++ b/src/main/java/cz/jzitnik/ui/DialogUI.java @@ -11,11 +11,18 @@ import cz.jzitnik.annotations.ui.UI; import cz.jzitnik.events.MouseAction; import cz.jzitnik.events.RerenderScreen; import cz.jzitnik.events.handlers.DialogEventHandler; +import cz.jzitnik.game.dialog.OnEnd; import cz.jzitnik.states.DialogState; +import cz.jzitnik.states.ScreenBuffer; import cz.jzitnik.states.TerminalState; +import cz.jzitnik.ui.pixels.Empty; import cz.jzitnik.utils.TextRenderer; +import cz.jzitnik.utils.events.Event; +import cz.jzitnik.utils.events.EventManager; import lombok.extern.slf4j.Slf4j; +import static cz.jzitnik.events.handlers.DialogEventHandler.*; + @Slf4j @UI @Dependency @@ -29,48 +36,129 @@ public class DialogUI { @InjectDependency private TextRenderer textRenderer; + @InjectDependency + private EventManager eventManager; + + @InjectState + private ScreenBuffer screenBuffer; + @MouseHandler(MouseHandlerType.CLICK) public boolean handleClick(MouseAction mouseAction) { - if (dialogState.getCurrentDialog() == null) { + if (dialogState.getCurrentDialog() == null || dialogState.isRenderInProgress()) { return false; } + TerminalSize size = DialogEventHandler.getSize(textRenderer, dialogState.getCurrentDialog()); TerminalPosition start = DialogEventHandler.getStart(terminalState.getTerminalScreen().getTerminalSize(), size); + + if (!(dialogState.getCurrentDialog().getOnEnd() instanceof OnEnd.AskQuestion( + OnEnd.AskQuestion.Answer[] answers + ))) { + setHoveredButtonIndex(-1); + return false; + } + TerminalPosition mouse = mouseAction.getPosition(); TerminalPosition mouseNormalized = new TerminalPosition(mouse.getColumn(), mouse.getRow() * 2); - ; RerenderScreen.ScreenPart part = new RerenderScreen.ScreenPart( start, new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows()) ); + if (!part.isWithin(mouseNormalized)) { + setHoveredButtonIndex(-1); return false; } - TerminalPosition localPosition = new TerminalPosition(mouseNormalized.getColumn() - start.getColumn(), mouseNormalized.getRow() - start.getRow()); - return false; + int buttonsStartY = DialogEventHandler.getYStartButtons(textRenderer, dialogState.getCurrentDialog()); + TerminalPosition localPosition = new TerminalPosition(mouseNormalized.getColumn() - start.getColumn(), mouseNormalized.getRow() - start.getRow() - buttonsStartY); + int buttonsHeight = DialogEventHandler.calculateButtonHeight(dialogState.getCurrentDialog()); + + if (localPosition.getRow() < 0 || localPosition.getRow() >= buttonsHeight) { + setHoveredButtonIndex(-1); + return true; + } + + int buttonIndex = localPosition.getRow() / (BUTTON_HEIGHT + BUTTON_PADDING); + int rest = localPosition.getRow() % (BUTTON_HEIGHT + BUTTON_PADDING); + + if (buttonIndex < answers.length && rest < BUTTON_HEIGHT && localPosition.getColumn() >= PADDING && localPosition.getColumn() < size.getColumns() - PADDING) { + for (int y = start.getRow(); y < start.getRow() + size.getRows(); y++) { + for (int x = start.getColumn(); x < start.getColumn() + size.getColumns(); x++) { + screenBuffer.getGlobalOverrideBuffer()[y][x] = new Empty(); + } + } + + eventManager.emitEvent( + new Event[]{ + new RerenderScreen( + new RerenderScreen.ScreenPart( + start, + new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows()) + ) + ), + answers[buttonIndex].dialog(), + }); + + return true; + } + + return true; } @MouseHandler(MouseHandlerType.MOVE) public boolean handleMove(MouseAction mouseAction) { - if (dialogState.getCurrentDialog() == null) { + if (dialogState.getCurrentDialog() == null || dialogState.isRenderInProgress()) { return false; } + TerminalSize size = DialogEventHandler.getSize(textRenderer, dialogState.getCurrentDialog()); TerminalPosition start = DialogEventHandler.getStart(terminalState.getTerminalScreen().getTerminalSize(), size); + + if (!(dialogState.getCurrentDialog().getOnEnd() instanceof OnEnd.AskQuestion( + OnEnd.AskQuestion.Answer[] answers + ))) { + setHoveredButtonIndex(-1); + return false; + } + TerminalPosition mouse = mouseAction.getPosition(); TerminalPosition mouseNormalized = new TerminalPosition(mouse.getColumn(), mouse.getRow() * 2); RerenderScreen.ScreenPart part = new RerenderScreen.ScreenPart( start, new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows()) ); + if (!part.isWithin(mouseNormalized)) { + setHoveredButtonIndex(-1); return false; } - TerminalPosition localPosition = new TerminalPosition(mouseNormalized.getColumn() - start.getColumn(), mouseNormalized.getRow() - start.getRow()); - log.debug("Position: {}", localPosition); + int buttonsStartY = DialogEventHandler.getYStartButtons(textRenderer, dialogState.getCurrentDialog()); + TerminalPosition localPosition = new TerminalPosition(mouseNormalized.getColumn() - start.getColumn(), mouseNormalized.getRow() - start.getRow() - buttonsStartY); + int buttonsHeight = DialogEventHandler.calculateButtonHeight(dialogState.getCurrentDialog()); + if (localPosition.getRow() < 0 || localPosition.getRow() >= buttonsHeight) { + setHoveredButtonIndex(-1); + return true; + } + + int buttonIndex = localPosition.getRow() / (BUTTON_HEIGHT + BUTTON_PADDING); + int rest = localPosition.getRow() % (BUTTON_HEIGHT + BUTTON_PADDING); + + if (buttonIndex < answers.length && rest < BUTTON_HEIGHT && localPosition.getColumn() >= PADDING && localPosition.getColumn() < size.getColumns() - PADDING) { + setHoveredButtonIndex(buttonIndex); + return true; + } + + setHoveredButtonIndex(-1); return true; } + + private void setHoveredButtonIndex(int index) { + if (dialogState.getHoveredButtonIndex() != index) { + dialogState.setHoveredButtonIndex(index); + eventManager.emitEvent(dialogState.getCurrentDialog()); + } + } }