Working on portrait mode

This commit is contained in:
Vladislav Khorev 2026-06-25 12:13:02 +03:00
parent b8d79c7b7b
commit 6b39cfca3b
5 changed files with 162 additions and 27 deletions

View File

@ -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) {

View File

@ -153,7 +153,7 @@ namespace ZL {
float mobileRotateMeshLastW = -1.0f;
float mobileRotateMeshLastH = -1.0f;
void drawMobilePortraitOverlay();
//void drawMobilePortraitOverlay();
int64_t newTickCount;
int64_t lastTickCount;

View File

@ -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<float>(textboxChoiceTexture->getWidth())
/ static_cast<float>(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<float>(choiceMainTexture->getWidth())
/ static_cast<float>(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<float>(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<float>(model.choices.size() - 1 - i);
UiRect rect{ leftPad, cy, choiceW, choiceH };
lastChoiceRects.push_back(rect);
choiceQuads[i].rebuild(rect);
const bool isHighlighted = static_cast<int>(i) == hoveredChoiceIndex || static_cast<int>(i) == model.selectedChoice;
std::shared_ptr<Texture> 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<int>(i) == hoveredChoiceIndex || static_cast<int>(i) == model.selectedChoice;
const std::array<float, 4> color = (model.choices[i].kind == ChoiceKind::Optional)
? std::array<float, 4>{0.82f, 0.82f, 0.82f, 1.0f}
: std::array<float, 4>{ 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<float, 4>{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;

View File

@ -47,11 +47,18 @@ private:
UiQuad textboxChoiceQuad;
mutable std::vector<UiQuad> choiceQuads;
// Portrait-mode quads (rebuilt when geometry changes)
UiQuad portraitPanelQuad;
UiQuad portraitAbovePortraitQuad;
std::shared_ptr<Texture> 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

View File

@ -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(