mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
feat(ui): Refactor dialogue and controls layout for improved interaction and visibility
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user