feat(ui): Refactor dialogue and controls layout for improved interaction and visibility

This commit is contained in:
Z. Cliffe Schreuders
2025-11-06 10:43:43 +00:00
parent b1686fc5eb
commit 5a752d0aca
3 changed files with 132 additions and 49 deletions

View File

@@ -92,22 +92,41 @@
width: 100%;
background: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.95));
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
flex-direction: row;
justify-content: flex-start;
align-items: flex-end;
padding: 20px;
gap: 15px;
gap: 20px;
z-index: 10;
box-sizing: border-box;
}
/* Talk right area - speaker name and dialogue (left side, takes more space) */
.person-chat-talk-right {
display: flex;
flex-direction: column;
gap: 10px;
flex: 1 1 auto;
min-width: 0;
}
/* Header row: speaker name on left, controls on right */
.person-chat-header-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
width: 100%;
padding-bottom: 8px;
border-bottom: 2px solid #333;
}
/* Speaker name */
.person-chat-speaker-name {
font-size: 20px;
font-weight: bold;
padding-bottom: 8px;
border-bottom: 2px solid #333;
min-height: 20px;
flex: 0 0 auto;
}
.person-chat-speaker-name.npc-speaker {
@@ -126,9 +145,8 @@
min-height: auto;
max-height: none;
overflow: visible;
display: flex;
align-items: flex-start;
flex: 0 0 auto;
display: block;
width: 100%;
}
.person-chat-dialogue-text {
@@ -141,15 +159,17 @@
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
}
/* Choices and continue button area */
/* Choices and continue button area (right side, fixed width) */
.person-chat-controls-area {
display: flex;
flex-direction: column;
gap: 10px;
flex: 0 0 280px;
gap: 8px;
flex: 0 0 auto;
align-items: stretch;
min-width: 200px;
}
/* Choices container - displayed below dialogue in caption area */
/* Choices container - displayed in controls area */
.person-chat-choices-container {
display: flex;
flex-direction: column;
@@ -168,6 +188,10 @@
cursor: pointer;
text-align: left;
transition: all 0.1s ease;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 180px;
}
.person-chat-choice-button:hover {

View File

@@ -72,7 +72,7 @@ export class PersonChatMinigame extends MinigameScene {
this.isConversationActive = false;
this.currentSpeaker = null; // Track current speaker ID ('player' or NPC id)
this.lastResult = null; // Store last continue() result for choice handling
this.isClickThroughMode = false; // If true, player must click to advance between dialogue lines
this.isClickThroughMode = false; // If true, player must click to advance between dialogue lines (starts in AUTO mode)
this.pendingContinueCallback = null; // Callback waiting for player click in click-through mode
console.log(`🎭 PersonChatMinigame created for NPC: ${this.npcId}`);
@@ -134,6 +134,9 @@ export class PersonChatMinigame extends MinigameScene {
}
super.init();
// Initialize timer for auto-advance
this.autoAdvanceTimer = null;
// Customize header
this.headerElement.innerHTML = `
<h3>🎭 ${this.title}</h3>
@@ -170,17 +173,37 @@ export class PersonChatMinigame extends MinigameScene {
}
});
// Continue button to toggle click-through mode
// Continue button click handler
if (this.ui.elements.continueButton) {
this.addEventListener(this.ui.elements.continueButton, 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.toggleClickThroughMode();
this.handleContinueButtonClick();
});
}
}
/**
* Handle continue button click - behavior depends on mode
*/
handleContinueButtonClick() {
console.log(`🖱️ Button clicked! isClickThroughMode: ${this.isClickThroughMode}, pendingContinueCallback exists: ${!!this.pendingContinueCallback}`);
if (this.isClickThroughMode) {
// In click-through mode: execute the pending advancement callback
if (this.pendingContinueCallback && typeof this.pendingContinueCallback === 'function') {
console.log(`🖱️ Executing pending callback`);
const callback = this.pendingContinueCallback;
this.pendingContinueCallback = null;
callback();
} else {
console.log(`🖱️ No pending callback to execute!`);
}
} else {
// In auto mode: toggle to click-through mode
console.log(`🖱️ In auto mode, toggling to click-through`);
this.toggleClickThroughMode();
}
} /**
* Toggle between automatic timing and click-through mode
*/
toggleClickThroughMode() {
@@ -188,15 +211,17 @@ export class PersonChatMinigame extends MinigameScene {
if (this.isClickThroughMode) {
console.log('📋 Switched to CLICK-THROUGH mode');
this.ui.elements.continueButton.textContent = '■ Auto';
// Cancel any pending automatic advances
if (this.pendingContinueCallback) {
clearTimeout(this.pendingContinueCallback);
this.pendingContinueCallback = null;
this.ui.elements.continueButton.textContent = 'Continue';
// Cancel any pending automatic advances (timer only, keep the callback!)
if (this.autoAdvanceTimer) {
clearTimeout(this.autoAdvanceTimer);
this.autoAdvanceTimer = null;
}
} else {
console.log('📋 Switched to AUTOMATIC mode');
this.ui.elements.continueButton.textContent = '▼ Continue';
this.ui.elements.continueButton.textContent = 'Auto';
// Resume automatic advancement
this.showCurrentDialogue();
}
}
@@ -206,20 +231,25 @@ export class PersonChatMinigame extends MinigameScene {
* @param {number} delay - Delay in milliseconds (ignored in click-through mode)
*/
scheduleDialogueAdvance(callback, delay = 2000) {
// Always store the callback function itself
this.pendingContinueCallback = callback;
if (this.isClickThroughMode) {
// In click-through mode, show continue button and wait for user click
this.pendingContinueCallback = callback;
this.ui.showContinueButton(() => {
if (this.pendingContinueCallback) {
const pending = this.pendingContinueCallback;
this.pendingContinueCallback = null;
pending();
}
});
// In click-through mode, wait for button click to execute callback
console.log(`⏱️ scheduleDialogueAdvance: Stored callback for click-through mode`);
} else {
// In automatic mode, use the delay
this.ui.hideContinueButton();
this.pendingContinueCallback = setTimeout(callback, delay);
// In automatic mode, schedule execution after delay
console.log(`⏱️ scheduleDialogueAdvance: Will auto-advance after ${delay}ms`);
// Clear any existing timeout
if (this.autoAdvanceTimer) {
clearTimeout(this.autoAdvanceTimer);
}
// Set new timeout that will call handleContinueButtonClick
this.autoAdvanceTimer = setTimeout(() => {
if (this.pendingContinueCallback && typeof this.pendingContinueCallback === 'function') {
this.pendingContinueCallback();
}
}, delay);
}
}
@@ -325,11 +355,12 @@ export class PersonChatMinigame extends MinigameScene {
// At a choice point - display choices
this.ui.showChoices(result.choices);
console.log(`📋 ${result.choices.length} choices available`);
console.log(`📋 pendingContinueCallback NOT set - waiting for choice selection`);
// Also display any accompanying text if present
if (result.text && result.text.trim()) {
console.log(`🗣️ Calling showDialogue with speaker: ${speaker}`);
this.ui.showDialogue(result.text, speaker);
this.ui.showDialogue(result.text, speaker, true); // preserveChoices=true
}
} else if (result.text && result.text.trim()) {
// Have text but no choices - display and continue
@@ -338,6 +369,7 @@ export class PersonChatMinigame extends MinigameScene {
if (result.canContinue) {
// Can continue - schedule next advance
console.log(`📋 Setting pendingContinueCallback - canContinue: true, no choices`);
this.scheduleDialogueAdvance(() => this.showCurrentDialogue(), 2000);
} else {
// Can't continue but have text - story will end
@@ -463,8 +495,10 @@ export class PersonChatMinigame extends MinigameScene {
}
// Normal dialogue flow: display the result (dialogue blocks) after a small delay
console.log(`🎯 After choice: scheduling displayAccumulatedDialogue with result.text length: ${result?.text?.length || 0}`);
this.scheduleDialogueAdvance(() => {
// Process accumulated dialogue by splitting into individual speaker blocks
console.log(`🎯 Inside scheduled callback: calling displayAccumulatedDialogue`);
this.displayAccumulatedDialogue(result);
}, 1500);

View File

@@ -107,9 +107,14 @@ export default class PersonChatUI {
const captionArea = document.createElement('div');
captionArea.className = 'person-chat-caption-area';
// Talk right area - speaker name + dialogue
const talkRightArea = document.createElement('div');
talkRightArea.className = 'person-chat-talk-right';
const speakerName = document.createElement('div');
speakerName.className = 'person-chat-speaker-name';
// Dialogue box (spans full width below header)
const dialogueBox = document.createElement('div');
dialogueBox.className = 'person-chat-dialogue-box';
@@ -119,25 +124,34 @@ export default class PersonChatUI {
dialogueBox.appendChild(dialogueText);
// Continue button - placed next to dialogue
// Assemble talk-right area
talkRightArea.appendChild(speakerName);
talkRightArea.appendChild(dialogueBox);
// Controls area - continue button + choices
const controlsArea = document.createElement('div');
controlsArea.className = 'person-chat-controls-area';
// Continue button
const continueButton = document.createElement('button');
continueButton.className = 'person-chat-continue-button';
continueButton.textContent = '▼ Continue';
continueButton.textContent = 'Auto';
continueButton.id = 'continue-button';
continueButton.style.display = 'none'; // Hidden by default, shown when there's more dialogue
continueButton.style.display = 'inline-block'; // Always visible (hidden only when choices shown)
dialogueBox.appendChild(continueButton);
controlsArea.appendChild(continueButton);
// Choices container (in caption area, below dialogue)
// Choices container (in controls area, below continue button)
const choicesContainer = document.createElement('div');
choicesContainer.className = 'person-chat-choices-container';
choicesContainer.id = 'choices-container';
choicesContainer.style.display = 'none';
// Assemble caption area: speaker name, dialogue, choices
captionArea.appendChild(speakerName);
captionArea.appendChild(dialogueBox);
captionArea.appendChild(choicesContainer);
controlsArea.appendChild(choicesContainer);
// Assemble caption area: talk-right, controls
captionArea.appendChild(talkRightArea);
captionArea.appendChild(controlsArea);
// Assemble main content
mainContent.appendChild(portraitSection);
@@ -148,9 +162,11 @@ export default class PersonChatUI {
this.elements.portraitContainer = portraitContainer;
this.elements.portraitLabel = portraitLabel;
this.elements.captionArea = captionArea;
this.elements.talkRightArea = talkRightArea;
this.elements.speakerName = speakerName;
this.elements.dialogueBox = dialogueBox;
this.elements.dialogueText = dialogueText;
this.elements.controlsArea = controlsArea;
this.elements.continueButton = continueButton;
this.elements.choicesContainer = choicesContainer;
@@ -185,8 +201,9 @@ export default class PersonChatUI {
* Display dialogue text with speaker
* @param {string} text - Dialogue text to display
* @param {string} characterId - Character ID ('player', 'npc', or specific NPC ID)
* @param {boolean} preserveChoices - If true, don't hide existing choices
*/
showDialogue(text, characterId = 'npc') {
showDialogue(text, characterId = 'npc', preserveChoices = false) {
this.currentSpeaker = characterId;
console.log(`📝 showDialogue called with character: ${characterId}, text length: ${text?.length || 0}`);
@@ -226,6 +243,11 @@ export default class PersonChatUI {
// Reset portrait for new speaker
this.updatePortraitForSpeaker(characterId, character);
// Hide choices only if not preserving them (i.e., when transitioning from choices back to text)
if (!preserveChoices) {
this.hideChoices();
}
// Reset continue button state
this.hasContinued = false;
}
@@ -268,7 +290,7 @@ export default class PersonChatUI {
* @param {Array} choices - Array of choice objects {text, index}
*/
showChoices(choices) {
if (!this.elements.choicesContainer) {
if (!this.elements.choicesContainer || !this.elements.continueButton) {
return;
}
@@ -277,10 +299,12 @@ export default class PersonChatUI {
if (!choices || choices.length === 0) {
this.elements.choicesContainer.style.display = 'none';
this.elements.continueButton.style.display = 'inline-block';
return;
}
// Show choices container
// Hide continue button and show choices
this.elements.continueButton.style.display = 'none';
this.elements.choicesContainer.style.display = 'flex';
// Create button for each choice
@@ -297,12 +321,13 @@ export default class PersonChatUI {
}
/**
* Hide choices
* Hide choices and restore continue button
*/
hideChoices() {
if (this.elements.choicesContainer) {
if (this.elements.choicesContainer && this.elements.continueButton) {
this.elements.choicesContainer.innerHTML = '';
this.elements.choicesContainer.style.display = 'none';
this.elements.continueButton.style.display = 'inline-block';
}
}