Polish fullscreen CCS dialogue
This commit is contained in:
@@ -136,7 +136,7 @@ anchor_bottom = 1.0
|
|||||||
offset_left = 96.0
|
offset_left = 96.0
|
||||||
offset_top = 31.0
|
offset_top = 31.0
|
||||||
offset_right = -64.0
|
offset_right = -64.0
|
||||||
offset_bottom = -52.0
|
offset_bottom = -64.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme_override_colors/font_color = Color(0.055, 0.105, 0.145, 1)
|
theme_override_colors/font_color = Color(0.055, 0.105, 0.145, 1)
|
||||||
@@ -145,6 +145,7 @@ text = "Hello, doctor."
|
|||||||
fit_content = false
|
fit_content = false
|
||||||
scroll_active = false
|
scroll_active = false
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
|
visible_characters_behavior = 1
|
||||||
vertical_alignment = 1
|
vertical_alignment = 1
|
||||||
|
|
||||||
[node name="ContinueHint" type="Label" parent="DialogueBubble"]
|
[node name="ContinueHint" type="Label" parent="DialogueBubble"]
|
||||||
@@ -155,9 +156,9 @@ anchor_top = 1.0
|
|||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
offset_left = -330.0
|
offset_left = -330.0
|
||||||
offset_top = -60.0
|
offset_top = -55.0
|
||||||
offset_right = -90.0
|
offset_right = -90.0
|
||||||
offset_bottom = -32.0
|
offset_bottom = -27.0
|
||||||
grow_horizontal = 0
|
grow_horizontal = 0
|
||||||
grow_vertical = 0
|
grow_vertical = 0
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ const CHOICE_CROP := Rect2(80.0, 200.0, 1610.0, 485.0)
|
|||||||
const CHOICES_PER_PAGE := 4
|
const CHOICES_PER_PAGE := 4
|
||||||
const DIALOGUE_PAGE_MIN_CHARS := 32
|
const DIALOGUE_PAGE_MIN_CHARS := 32
|
||||||
const DIALOGUE_PAGE_MAX_CHARS := 82
|
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_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 hour_hand: Node2D = $ClockMount/HourHand
|
||||||
@onready var minute_hand: Node2D = $ClockMount/MinuteHand
|
@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_characters_per_second := 35.0
|
||||||
var dialogue_reveal_complete := false
|
var dialogue_reveal_complete := false
|
||||||
var dialogue_layout_generation := 0
|
var dialogue_layout_generation := 0
|
||||||
|
var dialogue_input_enabled := true
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -219,7 +220,7 @@ func complete_response_transition() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
if not dialogue_bubble.visible:
|
if not dialogue_input_enabled or not dialogue_bubble.visible:
|
||||||
return
|
return
|
||||||
var should_advance := false
|
var should_advance := false
|
||||||
if event is InputEventMouseButton:
|
if event is InputEventMouseButton:
|
||||||
@@ -236,6 +237,10 @@ func _input(event: InputEvent) -> void:
|
|||||||
get_viewport().set_input_as_handled()
|
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]:
|
func _split_dialogue_pages(text: String) -> Array[String]:
|
||||||
var normalized := text.strip_edges()
|
var normalized := text.strip_edges()
|
||||||
if normalized.is_empty():
|
if normalized.is_empty():
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ func on_hide() -> void:
|
|||||||
_clear_active_scene()
|
_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:
|
func compatible_cases(cases: Array) -> Array:
|
||||||
return cases.filter(func(item: Variant) -> bool:
|
return cases.filter(func(item: Variant) -> bool:
|
||||||
return typeof(item) == TYPE_DICTIONARY \
|
return typeof(item) == TYPE_DICTIONARY \
|
||||||
|
|||||||
+22
-6
@@ -126,11 +126,11 @@ func _apply_screen_margins(screen_name: String) -> void:
|
|||||||
if screens_margin == null or screen_name.is_empty():
|
if screens_margin == null or screen_name.is_empty():
|
||||||
return
|
return
|
||||||
var is_fullscreen_study := screen_name == "player"
|
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_left", margin)
|
||||||
screens_margin.add_theme_constant_override("margin_right", 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_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 == "login" else (12 if is_fullscreen_study else 28))
|
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:
|
func _build_ui() -> void:
|
||||||
@@ -582,7 +582,7 @@ func _on_drawer_backdrop_input(event: InputEvent) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _screen_uses_shell(screen_name: String) -> bool:
|
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:
|
func _update_shell_visibility(screen_name: String) -> void:
|
||||||
@@ -703,11 +703,15 @@ func _input(event: InputEvent) -> void:
|
|||||||
_close_drawer()
|
_close_drawer()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
return
|
return
|
||||||
if current_screen_name in ["player", "ccs_player", "flashcards_study"]:
|
if _screen_uses_pause_menu(current_screen_name):
|
||||||
_toggle_app_menu()
|
_toggle_app_menu()
|
||||||
get_viewport().set_input_as_handled()
|
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:
|
func _refresh_header_controls() -> void:
|
||||||
if feedback_button != null:
|
if feedback_button != null:
|
||||||
feedback_button.visible = AuthState.is_logged_in and size.x >= 560.0
|
feedback_button.visible = AuthState.is_logged_in and size.x >= 560.0
|
||||||
@@ -875,13 +879,15 @@ func _toggle_app_menu() -> void:
|
|||||||
_close_app_menu()
|
_close_app_menu()
|
||||||
else:
|
else:
|
||||||
UISoundService.play_sound("open")
|
UISoundService.play_sound("open")
|
||||||
|
_set_current_screen_pause_menu_open(true)
|
||||||
app_menu.visible = true
|
app_menu.visible = true
|
||||||
app_menu.size = app_menu.custom_minimum_size
|
app_menu.size = app_menu.custom_minimum_size
|
||||||
app_menu.position = (get_viewport_rect().size - app_menu.size) * 0.5
|
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:
|
if menu_exit_button.visible:
|
||||||
menu_exit_button.text = (
|
menu_exit_button.text = (
|
||||||
"Abandon CCS Case" if current_screen_name == "ccs_player"
|
"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 "Exit Study Session" if current_screen_name == "flashcards_study"
|
||||||
else "Leave (timer continues)" if BlockSessionService.session_kind == "simulation"
|
else "Leave (timer continues)" if BlockSessionService.session_kind == "simulation"
|
||||||
else "Exit Run")
|
else "Exit Run")
|
||||||
@@ -892,9 +898,16 @@ func _toggle_app_menu() -> void:
|
|||||||
func _close_app_menu() -> void:
|
func _close_app_menu() -> void:
|
||||||
if app_menu != null:
|
if app_menu != null:
|
||||||
app_menu.visible = false
|
app_menu.visible = false
|
||||||
|
_set_current_screen_pause_menu_open(false)
|
||||||
BlockSessionService.resume_current_question_timer()
|
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:
|
func _navigate_from_pause(target_screen: String) -> void:
|
||||||
var return_screen := current_screen_name
|
var return_screen := current_screen_name
|
||||||
show_screen(target_screen, {"return_screen": return_screen})
|
show_screen(target_screen, {"return_screen": return_screen})
|
||||||
@@ -911,6 +924,9 @@ func exit_current_run() -> void:
|
|||||||
if current_screen_name == "flashcards_study":
|
if current_screen_name == "flashcards_study":
|
||||||
show_screen("flashcards_hub")
|
show_screen("flashcards_hub")
|
||||||
return
|
return
|
||||||
|
if current_screen_name == "ccs_cases_2":
|
||||||
|
show_screen("dashboard")
|
||||||
|
return
|
||||||
if BlockSessionService.block_started and BlockSessionService.session_kind != "simulation":
|
if BlockSessionService.block_started and BlockSessionService.session_kind != "simulation":
|
||||||
BlockSessionService.finish_block()
|
BlockSessionService.finish_block()
|
||||||
elif BlockSessionService.session_kind == "simulation":
|
elif BlockSessionService.session_kind == "simulation":
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ func _run() -> void:
|
|||||||
var nav_button := main_screen.sidebar.find_child("NavCcsCases2", true, false) as Button
|
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(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(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()
|
main_screen.queue_free()
|
||||||
await process_frame
|
await process_frame
|
||||||
ProjectSettings.set_setting("step3/deployment_environment", prior_environment)
|
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(_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.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_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))
|
var outside_click := _left_click(screen.active_scene.get_global_rect().end + Vector2(50.0, 50.0))
|
||||||
screen.active_scene._input(outside_click)
|
screen.active_scene._input(outside_click)
|
||||||
_assert(not screen.active_scene.dialogue_reveal_complete, "clicks outside the doctor scene should not affect dialogue")
|
_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())
|
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)
|
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_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")
|
_assert(screen.active_scene.dialogue_continue_hint.visible, "completed dialogue pages should invite the learner to continue")
|
||||||
await create_timer(1.35).timeout
|
await create_timer(1.35).timeout
|
||||||
_assert(screen.active_scene.is_patient_dialogue_visible(), "patient dialogue should remain until the learner advances it")
|
_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
|
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(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_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.")
|
sizing_scene.show_patient_dialogue("Line one explains the symptom.\nLine two adds timing.\nLine three adds context.\nLine four concludes.")
|
||||||
await process_frame
|
await process_frame
|
||||||
_assert(sizing_scene.dialogue_bubble.size.y > short_height, "multi-line dialogue should increase the bubble height")
|
_assert(sizing_scene.dialogue_bubble.size.y > short_height, "multi-line dialogue should increase the bubble height")
|
||||||
|
|||||||
Reference in New Issue
Block a user