From 6b39cfca3b45f2a3858a0dbc9401804367db49e0 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Thu, 25 Jun 2026 12:13:02 +0300 Subject: [PATCH] Working on portrait mode --- src/Game.cpp | 10 +- src/Game.h | 2 +- src/dialogue/DialogueOverlay.cpp | 153 +++++++++++++++++++++++++++++-- src/dialogue/DialogueOverlay.h | 7 ++ src/main.cpp | 17 +--- 5 files changed, 162 insertions(+), 27 deletions(-) diff --git a/src/Game.cpp b/src/Game.cpp index 7369c33..a358838 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -694,9 +694,9 @@ namespace ZL if (!loadingCompleted) { drawLoading(); } - else if (Environment::width < Environment::height) { + /*else if (Environment::width < Environment::height) { drawMobilePortraitOverlay(); - } + }*/ else { Location* loc = currentLocation(); @@ -733,7 +733,7 @@ namespace ZL CheckGlError(__FILE__, __LINE__); } - void Game::drawMobilePortraitOverlay() + /*void Game::drawMobilePortraitOverlay() { if (!mobileRotateTexture) return; @@ -762,7 +762,7 @@ namespace ZL renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); CheckGlError(__FILE__, __LINE__); - } + }*/ void Game::drawLoading() { @@ -944,7 +944,7 @@ namespace ZL } // Suppress all input while portrait overlay is shown - if (mobileIsPortrait) continue; + //if (mobileIsPortrait) continue; #ifdef __ANDROID__ if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) { diff --git a/src/Game.h b/src/Game.h index ece5782..947b2ca 100644 --- a/src/Game.h +++ b/src/Game.h @@ -153,7 +153,7 @@ namespace ZL { float mobileRotateMeshLastW = -1.0f; float mobileRotateMeshLastH = -1.0f; - void drawMobilePortraitOverlay(); + //void drawMobilePortraitOverlay(); int64_t newTickCount; int64_t lastTickCount; diff --git a/src/dialogue/DialogueOverlay.cpp b/src/dialogue/DialogueOverlay.cpp index 88dca81..505c8d9 100644 --- a/src/dialogue/DialogueOverlay.cpp +++ b/src/dialogue/DialogueOverlay.cpp @@ -56,6 +56,13 @@ void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) { return; } + if (Environment::projectionWidth < Environment::projectionHeight) + drawPortrait(renderer, model); + else + drawLandscape(renderer, model); +} + +void DialogueOverlay::drawLandscape(Renderer& renderer, const PresentationModel& model) { const float W = Environment::projectionWidth; const float nameX = 312 + 8; @@ -64,7 +71,7 @@ void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) { const float bodyY = 232 - 78.0f; const float bodyMaxWidthPx = 1280.0 - nameX - 48.f - 60.f; - + float centeredShiftX = ((W - nameX - 48.f - 60.f) - bodyMaxWidthPx)*0.5; @@ -147,16 +154,13 @@ void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) { const std::string wrappedBody = wrapTextToWidth(model.visibleText, *bodyRenderer, bodyMaxWidthPx, 1.0f); bodyRenderer->drawText(wrappedBody, bodyX + centeredShiftX, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f }); - + if (model.mode == PresentationMode::Choice) { const float choiceStartY = 270.f-35; const float choiceHeight = 121.f*0.9; const float choiceSpacing = -30.f; const float choiceWidth = 913.f * 0.9; - //UiRect textboxRect{ 30.f, -48.f, 1222.f, 340.0f }; - - if (choiceQuads.size() < model.choices.size()) { choiceQuads.resize(model.choices.size()); } @@ -204,6 +208,141 @@ void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) { glDisable(GL_BLEND); } +void DialogueOverlay::drawPortrait(Renderer& renderer, const PresentationModel& model) { + const float W = Environment::projectionWidth; + const float H = Environment::projectionHeight; + + // Panel: DialogChoice001.png scaled to full screen width, aspect ratio preserved. + const float texAspect = static_cast(textboxChoiceTexture->getWidth()) + / static_cast(textboxChoiceTexture->getHeight()); + const float panelW = W; + const float panelH = panelW / texAspect; + const float clipAtBottom = 18.f; + const float panelTopY = panelH - clipAtBottom; + + // Character portrait sits above the panel, shifted right and slightly overlapping. + const float portraitSize = 200.f; + const float portraitX = 56.f; + const float portraitY = panelTopY + 6.f - 40.f; + + // Text sits inside the panel near the top. + const float leftPad = 60.f; + const float rightPad = 60.f; + const float textMaxWidth = W - leftPad - rightPad; + const float nameY = panelTopY - 32.f - 50.f; + const float bodyY = nameY - 38.f; + + // --- Rebuild quads only when geometry changes --- + { + UiRect panelRect{ 0.f, -clipAtBottom, panelW, panelH }; + if (!portraitPanelQuad.initialized + || portraitPanelQuad.rect.x != panelRect.x || portraitPanelQuad.rect.y != panelRect.y + || portraitPanelQuad.rect.w != panelRect.w || portraitPanelQuad.rect.h != panelRect.h) { + portraitPanelQuad.rebuild(panelRect); + } + } + { + UiRect portraitRect{ portraitX, portraitY, portraitSize, portraitSize }; + if (!portraitAbovePortraitQuad.initialized + || portraitAbovePortraitQuad.rect.x != portraitRect.x || portraitAbovePortraitQuad.rect.y != portraitRect.y + || portraitAbovePortraitQuad.rect.w != portraitRect.w || portraitAbovePortraitQuad.rect.h != portraitRect.h) { + portraitAbovePortraitQuad.rebuild(portraitRect); + } + } + + // --- Draw panel and portrait image --- + glEnable(GL_BLEND); + renderer.shaderManager.PushShader(defaultShaderName); + renderer.RenderUniform1i(textureUniformName, 0); + renderer.PushProjectionMatrix(0.f, W, 0.f, H, -10.f, 10.f); + renderer.PushMatrix(); + renderer.LoadIdentity(); + renderer.RenderUniform1f("uAlpha", 1.0f); + + glBindTexture(GL_TEXTURE_2D, textboxChoiceTexture->getTexID()); + renderer.DrawVertexRenderStruct(portraitPanelQuad.mesh); + + auto portrait = loadTextureCached(model.portraitPath); + if (portrait) { + glBindTexture(GL_TEXTURE_2D, portrait->getTexID()); + renderer.DrawVertexRenderStruct(portraitAbovePortraitQuad.mesh); + } + + renderer.PopMatrix(); + renderer.PopProjectionMatrix(); + renderer.shaderManager.PopShader(); + + // --- Name and body text --- + lastChoiceRects.clear(); + + if (!model.speaker.empty()) { + nameRenderer->drawText(model.speaker, leftPad, nameY, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f }); + } + + const std::string wrappedBody = wrapTextToWidth(model.visibleText, *bodyRenderer, textMaxWidth, 1.0f); + bodyRenderer->drawText(wrappedBody, leftPad, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f }); + + // Hit rect for tap-to-advance. + lastDialogueAdvanceRect = { 0.f, 0.f, W, panelTopY }; + + // --- Choice mode: stack choices inside the panel --- + if (model.mode == PresentationMode::Choice) { + const float choiceTexAspect = static_cast(choiceMainTexture->getWidth()) + / static_cast(choiceMainTexture->getHeight()); + const float choiceW = W - leftPad - rightPad; + const float choiceH = choiceW / choiceTexAspect; + const float choiceSpacing = 8.f; + + const float totalChoicesH = (choiceH + choiceSpacing) * static_cast(model.choices.size()) - choiceSpacing; + const float choiceStartY = panelTopY - 34.f - totalChoicesH; // below name row + + if (choiceQuads.size() < model.choices.size()) { + choiceQuads.resize(model.choices.size()); + } + + glEnable(GL_BLEND); + renderer.shaderManager.PushShader(defaultShaderName); + renderer.RenderUniform1i(textureUniformName, 0); + renderer.PushProjectionMatrix(0.f, W, 0.f, H, -10.f, 10.f); + renderer.PushMatrix(); + renderer.LoadIdentity(); + + for (size_t i = 0; i < model.choices.size(); ++i) { + const float cy = choiceStartY + (choiceH + choiceSpacing) * static_cast(model.choices.size() - 1 - i); + UiRect rect{ leftPad, cy, choiceW, choiceH }; + lastChoiceRects.push_back(rect); + choiceQuads[i].rebuild(rect); + + const bool isHighlighted = static_cast(i) == hoveredChoiceIndex || static_cast(i) == model.selectedChoice; + std::shared_ptr choiceTexture = + (model.choices[i].kind == ChoiceKind::Optional) ? choiceOptionalTexture : choiceMainTexture; + if (isHighlighted) choiceTexture = choiceSelectedTexture; + glBindTexture(GL_TEXTURE_2D, choiceTexture->getTexID()); + renderer.DrawVertexRenderStruct(choiceQuads[i].mesh); + } + + renderer.PopMatrix(); + renderer.PopProjectionMatrix(); + renderer.shaderManager.PopShader(); + + for (size_t i = 0; i < model.choices.size(); ++i) { + const UiRect& rect = lastChoiceRects[i]; + const bool isHighlighted = static_cast(i) == hoveredChoiceIndex || static_cast(i) == model.selectedChoice; + const std::array color = (model.choices[i].kind == ChoiceKind::Optional) + ? std::array{0.82f, 0.82f, 0.82f, 1.0f} + : std::array{ 1.0f, 0.93f, 0.65f, 1.0f }; + + const std::string wrappedChoice = wrapTextToWidth(model.choices[i].text, *choiceRenderer, rect.w - 20.0f, 1.0f); + choiceRenderer->drawText( + wrappedChoice, rect.x + 20.f, rect.y + choiceH * 0.35f, 1.5f, false, + isHighlighted ? std::array{1.0f, 1.0f, 1.0f, 1.0f} : color + ); + } + } + + glDisable(GL_BLEND); +} + void DialogueOverlay::handlePointerDown(float x, float y, const PresentationModel& model) { if (model.mode == PresentationMode::Choice) { handlePointerMoved(x, y, model); @@ -241,8 +380,8 @@ bool DialogueOverlay::handlePointerReleased(float x, float y, const Presentation } if (model.mode == PresentationMode::Dialogue) { - outAdvanceDialogue = rectContains(lastDialogueAdvanceRect, x, y); - return outAdvanceDialogue; + outAdvanceDialogue = true; + return true; } return false; diff --git a/src/dialogue/DialogueOverlay.h b/src/dialogue/DialogueOverlay.h index e31910a..c21b279 100644 --- a/src/dialogue/DialogueOverlay.h +++ b/src/dialogue/DialogueOverlay.h @@ -47,11 +47,18 @@ private: UiQuad textboxChoiceQuad; mutable std::vector choiceQuads; + // Portrait-mode quads (rebuilt when geometry changes) + UiQuad portraitPanelQuad; + UiQuad portraitAbovePortraitQuad; + std::shared_ptr loadTextureCached(const std::string& path); static std::string wrapTextToWidth(const std::string& input, const TextRenderer& textRenderer, float maxWidthPx, float scale); static bool rectContains(const UiRect& rect, float x, float y); + + void drawLandscape(Renderer& renderer, const PresentationModel& model); + void drawPortrait(Renderer& renderer, const PresentationModel& model); }; } // namespace ZL::Dialogue diff --git a/src/main.cpp b/src/main.cpp index d147002..01761a8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,14 +38,10 @@ void MainLoop() { #ifdef EMSCRIPTEN EM_BOOL onWebGLContextLost(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) { - //delete g_game; - //g_game = nullptr; return EM_TRUE; } EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) { - //g_game = new ZL::Game(); - //g_game->setup(); return EM_TRUE; } @@ -235,8 +231,8 @@ int main(int argc, char *argv[]) { { - constexpr int CONST_WIDTH = 1280; - constexpr int CONST_HEIGHT = 720; + constexpr int CONST_WIDTH = ZL::Environment::CONST_DEFAULT_WIDTH; + constexpr int CONST_HEIGHT = ZL::Environment::CONST_DEFAULT_HEIGHT; ZL::Environment::width = CONST_WIDTH; ZL::Environment::height = CONST_HEIGHT; @@ -250,16 +246,9 @@ int main(int argc, char *argv[]) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); #ifdef FULLSCREEN - // Match the desktop's current resolution so the framebuffer matches - // the screen and we don't force a video mode change. - /*SDL_DisplayMode dm; - if (SDL_GetCurrentDisplayMode(0, &dm) == 0) { - ZL::Environment::width = dm.w; - ZL::Environment::height = dm.h; - }*/ const Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN_DESKTOP; #else - const Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI; + const Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE; #endif ZL::Environment::window = SDL_CreateWindow(