Polish fullscreen CCS dialogue

This commit is contained in:
hz
2026-08-07 17:56:49 -05:00
parent bab73b53ff
commit 41b3092552
5 changed files with 57 additions and 12 deletions
+4 -3
View File
@@ -136,7 +136,7 @@ anchor_bottom = 1.0
offset_left = 96.0
offset_top = 31.0
offset_right = -64.0
offset_bottom = -52.0
offset_bottom = -64.0
grow_horizontal = 2
grow_vertical = 2
theme_override_colors/font_color = Color(0.055, 0.105, 0.145, 1)
@@ -145,6 +145,7 @@ text = "Hello, doctor."
fit_content = false
scroll_active = false
autowrap_mode = 2
visible_characters_behavior = 1
vertical_alignment = 1
[node name="ContinueHint" type="Label" parent="DialogueBubble"]
@@ -155,9 +156,9 @@ anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -330.0
offset_top = -60.0
offset_top = -55.0
offset_right = -90.0
offset_bottom = -32.0
offset_bottom = -27.0
grow_horizontal = 0
grow_vertical = 0
mouse_filter = 2
+8 -3
View File
@@ -12,9 +12,9 @@ const CHOICE_CROP := Rect2(80.0, 200.0, 1610.0, 485.0)
const CHOICES_PER_PAGE := 4
const DIALOGUE_PAGE_MIN_CHARS := 32
const DIALOGUE_PAGE_MAX_CHARS := 82
const DIALOGUE_BUBBLE_MIN_HEIGHT := 110.0
const DIALOGUE_BUBBLE_MIN_HEIGHT := 130.0
const DIALOGUE_BUBBLE_MAX_HEIGHT := 240.0
const DIALOGUE_BUBBLE_VERTICAL_CHROME := 83.0
const DIALOGUE_BUBBLE_VERTICAL_CHROME := 95.0
@onready var hour_hand: Node2D = $ClockMount/HourHand
@onready var minute_hand: Node2D = $ClockMount/MinuteHand
@@ -43,6 +43,7 @@ var dialogue_page_index := 0
var dialogue_characters_per_second := 35.0
var dialogue_reveal_complete := false
var dialogue_layout_generation := 0
var dialogue_input_enabled := true
func _ready() -> void:
@@ -219,7 +220,7 @@ func complete_response_transition() -> void:
func _input(event: InputEvent) -> void:
if not dialogue_bubble.visible:
if not dialogue_input_enabled or not dialogue_bubble.visible:
return
var should_advance := false
if event is InputEventMouseButton:
@@ -236,6 +237,10 @@ func _input(event: InputEvent) -> void:
get_viewport().set_input_as_handled()
func set_dialogue_input_enabled(enabled: bool) -> void:
dialogue_input_enabled = enabled
func _split_dialogue_pages(text: String) -> Array[String]:
var normalized := text.strip_edges()
if normalized.is_empty():
+5
View File
@@ -65,6 +65,11 @@ func on_hide() -> void:
_clear_active_scene()
func set_pause_menu_open(open: bool) -> void:
if active_scene != null and active_scene.has_method("set_dialogue_input_enabled"):
active_scene.set_dialogue_input_enabled(not open)
func compatible_cases(cases: Array) -> Array:
return cases.filter(func(item: Variant) -> bool:
return typeof(item) == TYPE_DICTIONARY \
+22 -6
View File
@@ -126,11 +126,11 @@ func _apply_screen_margins(screen_name: String) -> void:
if screens_margin == null or screen_name.is_empty():
return
var is_fullscreen_study := screen_name == "player"
var margin := 0 if screen_name == "login" else (12 if is_fullscreen_study else (20 if compact_shell else 44))
var margin := 0 if screen_name in ["login", "ccs_cases_2"] else (12 if is_fullscreen_study else (20 if compact_shell else 44))
screens_margin.add_theme_constant_override("margin_left", margin)
screens_margin.add_theme_constant_override("margin_right", margin)
screens_margin.add_theme_constant_override("margin_top", 0 if screen_name == "login" else (12 if is_fullscreen_study else (20 if compact_shell else 34)))
screens_margin.add_theme_constant_override("margin_bottom", 0 if screen_name == "login" else (12 if is_fullscreen_study else 28))
screens_margin.add_theme_constant_override("margin_top", 0 if screen_name in ["login", "ccs_cases_2"] else (12 if is_fullscreen_study else (20 if compact_shell else 34)))
screens_margin.add_theme_constant_override("margin_bottom", 0 if screen_name in ["login", "ccs_cases_2"] else (12 if is_fullscreen_study else 28))
func _build_ui() -> void:
@@ -582,7 +582,7 @@ func _on_drawer_backdrop_input(event: InputEvent) -> void:
func _screen_uses_shell(screen_name: String) -> bool:
return AuthState.is_logged_in and screen_name not in ["login", "legal_consent", "player", "ccs_player"]
return AuthState.is_logged_in and screen_name not in ["login", "legal_consent", "player", "ccs_player", "ccs_cases_2"]
func _update_shell_visibility(screen_name: String) -> void:
@@ -703,11 +703,15 @@ func _input(event: InputEvent) -> void:
_close_drawer()
get_viewport().set_input_as_handled()
return
if current_screen_name in ["player", "ccs_player", "flashcards_study"]:
if _screen_uses_pause_menu(current_screen_name):
_toggle_app_menu()
get_viewport().set_input_as_handled()
func _screen_uses_pause_menu(screen_name: String) -> bool:
return screen_name in ["player", "ccs_player", "ccs_cases_2", "flashcards_study"]
func _refresh_header_controls() -> void:
if feedback_button != null:
feedback_button.visible = AuthState.is_logged_in and size.x >= 560.0
@@ -875,13 +879,15 @@ func _toggle_app_menu() -> void:
_close_app_menu()
else:
UISoundService.play_sound("open")
_set_current_screen_pause_menu_open(true)
app_menu.visible = true
app_menu.size = app_menu.custom_minimum_size
app_menu.position = (get_viewport_rect().size - app_menu.size) * 0.5
menu_exit_button.visible = (current_screen_name == "player" and BlockSessionService.block_started) or current_screen_name in ["ccs_player", "flashcards_study"]
menu_exit_button.visible = (current_screen_name == "player" and BlockSessionService.block_started) or current_screen_name in ["ccs_player", "ccs_cases_2", "flashcards_study"]
if menu_exit_button.visible:
menu_exit_button.text = (
"Abandon CCS Case" if current_screen_name == "ccs_player"
else "Exit CCS Interview" if current_screen_name == "ccs_cases_2"
else "Exit Study Session" if current_screen_name == "flashcards_study"
else "Leave (timer continues)" if BlockSessionService.session_kind == "simulation"
else "Exit Run")
@@ -892,9 +898,16 @@ func _toggle_app_menu() -> void:
func _close_app_menu() -> void:
if app_menu != null:
app_menu.visible = false
_set_current_screen_pause_menu_open(false)
BlockSessionService.resume_current_question_timer()
func _set_current_screen_pause_menu_open(open: bool) -> void:
var screen = screens.get(current_screen_name)
if screen != null and screen.has_method("set_pause_menu_open"):
screen.set_pause_menu_open(open)
func _navigate_from_pause(target_screen: String) -> void:
var return_screen := current_screen_name
show_screen(target_screen, {"return_screen": return_screen})
@@ -911,6 +924,9 @@ func exit_current_run() -> void:
if current_screen_name == "flashcards_study":
show_screen("flashcards_hub")
return
if current_screen_name == "ccs_cases_2":
show_screen("dashboard")
return
if BlockSessionService.block_started and BlockSessionService.session_kind != "simulation":
BlockSessionService.finish_block()
elif BlockSessionService.session_kind == "simulation":
+18
View File
@@ -23,6 +23,16 @@ func _run() -> void:
var nav_button := main_screen.sidebar.find_child("NavCcsCases2", true, false) as Button
_assert(nav_button != null and nav_button.visible, "UAT should expose a CCS Cases 2 navigation item")
_assert(main_screen.screens.has("ccs_cases_2"), "UAT should register the graphical CCS screen")
_assert(not main_screen._screen_uses_shell("ccs_cases_2"), "CCS Cases 2 should hide the application shell")
main_screen._apply_screen_margins("ccs_cases_2")
_assert(main_screen.screens_margin.get_theme_constant("margin_left") == 0, "fullscreen CCS should remove horizontal screen margins")
_assert(main_screen.screens_margin.get_theme_constant("margin_top") == 0, "fullscreen CCS should remove vertical screen margins")
main_screen.current_screen_name = "ccs_cases_2"
_assert(main_screen._screen_uses_pause_menu("ccs_cases_2"), "CCS Cases 2 should route Escape to the pause menu")
main_screen._toggle_app_menu()
_assert(main_screen.app_menu.visible, "the pause menu should open over CCS Cases 2")
_assert(main_screen.menu_exit_button.visible and main_screen.menu_exit_button.text == "Exit CCS Interview", "the CCS pause menu should expose a clear exit action")
main_screen._close_app_menu()
main_screen.queue_free()
await process_frame
ProjectSettings.set_setting("step3/deployment_environment", prior_environment)
@@ -94,12 +104,19 @@ func _run() -> void:
_assert(_bubble_matches_content(screen.active_scene), "the speech bubble should size itself to the wrapped response page")
_assert(screen.active_scene.dialogue_continue_hint.visible, "the continuation prompt should remain visible while text is typing")
_assert(screen.active_scene.dialogue_continue_hint.text == "Click to continue", "every dialogue page should use supported continuation wording")
_assert(screen.active_scene.dialogue_text.visible_characters_behavior == TextServer.VC_CHARS_AFTER_SHAPING, "typewriter text should wrap before glyphs are revealed")
var wrapped_line_count: int = screen.active_scene.dialogue_text.get_line_count()
var outside_click := _left_click(screen.active_scene.get_global_rect().end + Vector2(50.0, 50.0))
screen.active_scene._input(outside_click)
_assert(not screen.active_scene.dialogue_reveal_complete, "clicks outside the doctor scene should not affect dialogue")
var scene_click := _left_click(screen.active_scene.get_global_rect().get_center())
screen.set_pause_menu_open(true)
screen.active_scene._input(scene_click)
_assert(not screen.active_scene.dialogue_reveal_complete, "dialogue should ignore scene clicks while the Escape menu is open")
screen.set_pause_menu_open(false)
screen.active_scene._input(scene_click)
_assert(screen.active_scene.dialogue_page_index == 0, "clicking during typing should complete rather than skip the current page")
_assert(screen.active_scene.dialogue_text.get_line_count() == wrapped_line_count, "revealing typewriter text should not shift words between lines")
_assert(screen.active_scene.dialogue_continue_hint.visible, "completed dialogue pages should invite the learner to continue")
await create_timer(1.35).timeout
_assert(screen.active_scene.is_patient_dialogue_visible(), "patient dialogue should remain until the learner advances it")
@@ -161,6 +178,7 @@ func _run() -> void:
var short_height: float = sizing_scene.dialogue_bubble.size.y
_assert(is_equal_approx(short_height, sizing_scene.DIALOGUE_BUBBLE_MIN_HEIGHT), "short dialogue should use the minimum bubble height")
_assert(sizing_scene.dialogue_bubble.get_global_rect().encloses(sizing_scene.dialogue_continue_hint.get_global_rect()), "the continuation hint should fit inside a minimum-height bubble")
_assert(sizing_scene.dialogue_continue_hint.position.y - sizing_scene.dialogue_text.get_rect().end.y >= 8.0, "the continuation hint should have visible padding above it")
sizing_scene.show_patient_dialogue("Line one explains the symptom.\nLine two adds timing.\nLine three adds context.\nLine four concludes.")
await process_frame
_assert(sizing_scene.dialogue_bubble.size.y > short_height, "multi-line dialogue should increase the bubble height")