Size CCS speech bubbles to content
This commit is contained in:
@@ -133,9 +133,9 @@ layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 76.0
|
||||
offset_left = 96.0
|
||||
offset_top = 31.0
|
||||
offset_right = -48.0
|
||||
offset_right = -64.0
|
||||
offset_bottom = -52.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
@@ -12,6 +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 := 150.0
|
||||
const DIALOGUE_BUBBLE_MAX_HEIGHT := 240.0
|
||||
const DIALOGUE_BUBBLE_VERTICAL_CHROME := 83.0
|
||||
|
||||
@onready var hour_hand: Node2D = $ClockMount/HourHand
|
||||
@onready var minute_hand: Node2D = $ClockMount/MinuteHand
|
||||
@@ -39,6 +42,7 @@ var dialogue_pages: Array[String] = []
|
||||
var dialogue_page_index := 0
|
||||
var dialogue_characters_per_second := 35.0
|
||||
var dialogue_reveal_complete := false
|
||||
var dialogue_layout_generation := 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -119,6 +123,17 @@ func _show_dialogue_page() -> void:
|
||||
dialogue_continue_hint.visible = false
|
||||
dialogue_bubble.visible = true
|
||||
dialogue_bubble.grab_focus()
|
||||
dialogue_layout_generation += 1
|
||||
call_deferred("_prepare_dialogue_page", dialogue_layout_generation)
|
||||
|
||||
|
||||
func _prepare_dialogue_page(generation: int) -> void:
|
||||
if generation != dialogue_layout_generation or not dialogue_bubble.visible \
|
||||
or dialogue_reveal_complete:
|
||||
return
|
||||
var measured_height := dialogue_text.get_content_height() + DIALOGUE_BUBBLE_VERTICAL_CHROME
|
||||
dialogue_bubble.size.y = clampf(
|
||||
measured_height, DIALOGUE_BUBBLE_MIN_HEIGHT, DIALOGUE_BUBBLE_MAX_HEIGHT)
|
||||
var reveal_duration := float(dialogue_text.text.length()) / maxf(dialogue_characters_per_second, 1.0)
|
||||
dialogue_tween = create_tween()
|
||||
dialogue_tween.tween_property(dialogue_text, "visible_ratio", 1.0, reveal_duration)
|
||||
@@ -128,6 +143,7 @@ func _show_dialogue_page() -> void:
|
||||
func hide_patient_dialogue() -> void:
|
||||
if dialogue_tween and dialogue_tween.is_valid():
|
||||
dialogue_tween.kill()
|
||||
dialogue_layout_generation += 1
|
||||
dialogue_bubble.visible = false
|
||||
dialogue_continue_hint.visible = false
|
||||
dialogue_pages = []
|
||||
@@ -166,6 +182,7 @@ func complete_dialogue_reveal() -> void:
|
||||
return
|
||||
if dialogue_tween and dialogue_tween.is_valid():
|
||||
dialogue_tween.kill()
|
||||
dialogue_layout_generation += 1
|
||||
dialogue_text.visible_ratio = 1.0
|
||||
_on_dialogue_reveal_finished()
|
||||
|
||||
|
||||
@@ -90,6 +90,8 @@ func _run() -> void:
|
||||
_assert(not screen.active_scene.choice_container.visible, "the next choices should wait until the patient finishes speaking")
|
||||
_assert(screen.active_scene.dialogue_pages.size() > 1, "long patient responses should split into readable dialogue pages")
|
||||
_assert(_label_content_fits(screen.active_scene.dialogue_text), "the first patient response page should fit inside its speech bubble")
|
||||
_assert(is_equal_approx(screen.active_scene.dialogue_text.offset_left, 96.0), "patient dialogue should retain the increased left inset")
|
||||
_assert(_bubble_matches_content(screen.active_scene), "the speech bubble should size itself to the wrapped response page")
|
||||
screen.active_scene.advance_dialogue()
|
||||
_assert(screen.active_scene.dialogue_page_index == 0, "clicking during typing should complete rather than skip the current page")
|
||||
_assert(screen.active_scene.dialogue_continue_hint.visible, "completed dialogue pages should invite the learner to continue")
|
||||
@@ -143,6 +145,22 @@ func _run() -> void:
|
||||
_assert(screen.current_view == "interview_debrief", "a completed graphical interview should remain in CCS Cases 2")
|
||||
_assert(screen.active_scene.find_child("InterviewDebrief", true, false) != null, "the graphical case should render its debrief")
|
||||
|
||||
var sizing_scene = load("res://scenes/ccs_cases_2/doctor_office.tscn").instantiate()
|
||||
host.add_child(sizing_scene)
|
||||
await process_frame
|
||||
sizing_scene.show_patient_dialogue("Short response.")
|
||||
await process_frame
|
||||
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")
|
||||
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")
|
||||
_assert(sizing_scene.dialogue_bubble.size.y <= sizing_scene.DIALOGUE_BUBBLE_MAX_HEIGHT, "dialogue bubble growth should remain bounded")
|
||||
_assert(_bubble_matches_content(sizing_scene), "the expanded bubble should contain all wrapped text")
|
||||
sizing_scene.complete_dialogue_reveal()
|
||||
_assert(sizing_scene.dialogue_bubble.get_global_rect().encloses(sizing_scene.dialogue_continue_hint.get_global_rect()), "the continuation hint should remain inside the resized bubble")
|
||||
sizing_scene.queue_free()
|
||||
|
||||
screen.on_hide()
|
||||
await process_frame
|
||||
_assert(screen.active_scene == null, "hiding CCS Cases 2 should release the doctor scene")
|
||||
@@ -245,6 +263,14 @@ func _label_content_fits(label: Control) -> bool:
|
||||
return content_height <= plain_label.size.y + 0.5
|
||||
|
||||
|
||||
func _bubble_matches_content(scene: Control) -> bool:
|
||||
var required_height: float = scene.dialogue_text.get_content_height() + scene.DIALOGUE_BUBBLE_VERTICAL_CHROME
|
||||
var expected_height := clampf(
|
||||
required_height, scene.DIALOGUE_BUBBLE_MIN_HEIGHT, scene.DIALOGUE_BUBBLE_MAX_HEIGHT)
|
||||
return is_equal_approx(scene.dialogue_bubble.size.y, expected_height) \
|
||||
and _label_content_fits(scene.dialogue_text)
|
||||
|
||||
|
||||
func _assert(condition: bool, message: String) -> void:
|
||||
if condition:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user