fix: make choice options the only clickable area in dialogue choices

This commit is contained in:
vottozi 2026-04-17 02:04:36 +06:00
parent 2494a44e4a
commit be4dcd4cf9
8 changed files with 112 additions and 29 deletions

View File

@ -696,6 +696,13 @@ namespace ZL
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
if (dialogueSystem.blocksGameplayInput()) {
dialogueSystem.handlePointerMoved(
static_cast<float>(mx),
Environment::projectionHeight - static_cast<float>(my)
);
}
if (rightMouseDown) {
int dx = event.motion.x - lastMouseX;
int dy = event.motion.y - lastMouseY;
@ -732,7 +739,7 @@ namespace ZL
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
switch (event.key.keysym.sym) {
case SDLK_f:
dialogueSystem.startDialogue("test_cutscene_pan_dialogue_silent");
dialogueSystem.startDialogue("test_choice_dialogue");
break;
case SDLK_e:

View File

@ -84,6 +84,12 @@ bool DialogueOverlay::init(Renderer& renderer, const std::string& zipFile) {
return ok;
}
void DialogueOverlay::update(const PresentationModel& model, int deltaMs) {
if (model.mode != PresentationMode::Choice) {
hoveredChoiceIndex = -1;
}
}
void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) {
if (model.mode == PresentationMode::Hidden) {
lastChoiceRects.clear();
@ -170,12 +176,9 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
lastChoiceRects.push_back(rect);
choiceQuads[i].rebuild(rect);
const bool isSelected = static_cast<int>(i) == model.selectedChoice;
std::shared_ptr<Texture> choiceTexture = choiceMainTexture;
if (model.choices[i].kind == ChoiceKind::Optional) {
choiceTexture = choiceOptionalTexture;
}
if (isSelected) {
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;
}
drawQuad(renderer, choiceQuads[i], choiceTexture);
@ -187,10 +190,10 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
for (size_t i = 0; i < model.choices.size(); ++i) {
const UiRect& rect = lastChoiceRects[i];
const bool isSelected = static_cast<int>(i) == model.selectedChoice;
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 };
: std::array<float, 4>{ 1.0f, 0.93f, 0.65f, 1.0f };
choiceRenderer->drawText(
wrapText(model.choices[i].text, 52),
@ -198,7 +201,7 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
rect.y + 9.0f,
1.0f,
false,
isSelected ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : color
isHighlighted ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : color
);
}
}
@ -417,21 +420,48 @@ void DialogueOverlay::drawCutscene(Renderer& renderer, const PresentationModel&
glDisable(GL_BLEND);
}
bool DialogueOverlay::handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex) const {
void DialogueOverlay::handlePointerDown(float x, float y, const PresentationModel& model) {
if (model.mode == PresentationMode::Choice) {
handlePointerMoved(x, y, model);
return;
}
}
void DialogueOverlay::handlePointerMoved(float x, float y, const PresentationModel& model) {
if (model.mode == PresentationMode::Choice) {
hoveredChoiceIndex = -1;
for (size_t i = 0; i < lastChoiceRects.size(); ++i) {
if (rectContains(lastChoiceRects[i], x, y)) {
hoveredChoiceIndex = static_cast<int>(i);
break;
}
}
return;
}
hoveredChoiceIndex = -1;
}
bool DialogueOverlay::handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex, bool& outAdvanceDialogue) {
outChoiceIndex = -1;
outAdvanceDialogue = false;
if (model.mode == PresentationMode::Choice) {
for (size_t i = 0; i < lastChoiceRects.size(); ++i) {
if (lastChoiceRects[i].contains(x, y)) {
if (rectContains(lastChoiceRects[i], x, y)) {
outChoiceIndex = static_cast<int>(i);
return true;
}
}
return lastDialogueAdvanceRect.contains(x, y);
return false;
}
if (model.mode == PresentationMode::Dialogue) {
return lastDialogueAdvanceRect.contains(x, y);
if (lastDialogueAdvanceRect.contains(x, y)) {
outAdvanceDialogue = true;
return true;
}
return false;
}
if (model.mode == PresentationMode::Cutscene) {
@ -494,4 +524,8 @@ std::string DialogueOverlay::wrapText(const std::string& input, size_t maxLineLe
return output;
}
bool DialogueOverlay::rectContains(const UiRect& rect, float x, float y) {
return x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h;
}
} // namespace ZL::Dialogue

View File

@ -15,11 +15,12 @@ namespace ZL::Dialogue {
class DialogueOverlay {
public:
bool init(Renderer& renderer, const std::string& zipFile = "");
void update(const PresentationModel& model, int deltaMs);
void draw(Renderer& renderer, const PresentationModel& model);
// Coordinates are expected in the game's UI projection space
// Returns true only when the click should advance/select.
bool handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex) const;
void handlePointerDown(float x, float y, const PresentationModel& model);
void handlePointerMoved(float x, float y, const PresentationModel& model);
bool handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex, bool& outAdvanceDialogue);
private:
struct TexturedQuad {
@ -60,6 +61,8 @@ private:
mutable UiRect lastCutsceneAdvanceRect{};
mutable bool cutsceneAdvanceEnabled = false;
int hoveredChoiceIndex = -1;
std::unique_ptr<TextRenderer> nameRenderer;
std::unique_ptr<TextRenderer> bodyRenderer;
std::unique_ptr<TextRenderer> choiceRenderer;
@ -80,6 +83,7 @@ private:
void drawQuad(Renderer& renderer, const TexturedQuad& quad, const std::shared_ptr<Texture>& texture) const;
static std::string wrapText(const std::string& input, size_t maxLineLength);
static bool rectContains(const UiRect& rect, float x, float y);
static float lerpFloat(float a, float b, float t);
static ResolvedViewport resolveViewportPose(

View File

@ -185,10 +185,26 @@ void DialogueRuntime::moveSelection(int delta) {
}
const int count = static_cast<int>(visibleChoices.size());
selectedChoice = (selectedChoice + delta) % count;
if (selectedChoice < 0) {
selectedChoice += count;
if (selectedChoice < 0 || selectedChoice >= count) {
selectedChoice = (delta >= 0) ? 0 : (count - 1);
}
else {
selectedChoice = (selectedChoice + delta) % count;
if (selectedChoice < 0) {
selectedChoice += count;
}
}
presentation.selectedChoice = selectedChoice;
}
void DialogueRuntime::selectChoice(int index) {
if (mode != Mode::WaitingForChoice || visibleChoices.empty()) {
return;
}
if (index < 0 || index >= static_cast<int>(visibleChoices.size())) {
return;
}
selectedChoice = index;
presentation.selectedChoice = selectedChoice;
}
@ -346,14 +362,14 @@ void DialogueRuntime::presentChoices(const Node& node) {
}
mode = Mode::WaitingForChoice;
selectedChoice = 0;
selectedChoice = -1;
presentation.mode = PresentationMode::Choice;
presentation.speaker = node.speaker;
presentation.fullText = node.text;
presentation.visibleText = node.text;
presentation.portraitPath = node.portrait;
presentation.backgroundPath.clear();
presentation.selectedChoice = 0;
presentation.selectedChoice = -1;
presentation.revealCompleted = true;
presentation.showCutsceneSubtitle = false;
presentation.cutsceneCamera = {};

View File

@ -26,6 +26,7 @@ public:
void confirmAdvance();
void moveSelection(int delta);
void selectChoice(int index);
const PresentationModel& getPresentation() const { return presentation; }

View File

@ -66,24 +66,43 @@ bool DialogueSystem::handleKeyDown(SDL_Keycode key) {
}
}
void DialogueSystem::handlePointerDown(float x, float y) {
if (!runtime.isActive()) {
return;
}
overlay.handlePointerDown(x, y, runtime.getPresentation());
}
void DialogueSystem::handlePointerMoved(float x, float y) {
if (!runtime.isActive()) {
return;
}
overlay.handlePointerMoved(x, y, runtime.getPresentation());
}
bool DialogueSystem::handlePointerReleased(float x, float y) {
if (!runtime.isActive()) {
return false;
}
int choiceIndex = -1;
bool advanceDialogue = false;
const PresentationModel& model = runtime.getPresentation();
if (!overlay.handlePointerReleased(x, y, model, choiceIndex)) {
if (!overlay.handlePointerReleased(x, y, model, choiceIndex, advanceDialogue)) {
return false;
}
if (choiceIndex >= 0) {
while (runtime.getPresentation().selectedChoice != choiceIndex) {
runtime.moveSelection(1);
}
runtime.selectChoice(choiceIndex);
runtime.confirmAdvance();
return true;
}
if (advanceDialogue) {
runtime.confirmAdvance();
return true;
}
runtime.confirmAdvance();
return true;
}

View File

@ -27,6 +27,8 @@ public:
void draw(Renderer& renderer);
bool handleKeyDown(SDL_Keycode key);
void handlePointerDown(float x, float y);
void handlePointerMoved(float x, float y);
bool handlePointerReleased(float x, float y);
bool startDialogue(const std::string& dialogueId);

View File

@ -173,7 +173,7 @@ struct PresentationModel {
std::string portraitPath;
std::string backgroundPath;
std::vector<PresentedChoice> choices;
int selectedChoice = 0;
int selectedChoice = -1;
bool revealCompleted = true;
bool showCutsceneSubtitle = false;