extends Control signal choice_selected(choice_id: String) signal physical_exam_selected(exam_code: String) signal physical_exam_finish_requested signal order_search_requested(query: String) signal orders_submit_requested(codes: Array) signal order_cancel_requested(code: String) signal time_advance_requested(minutes: int) signal final_orders_submit_requested signal debrief_return_requested signal debrief_repeat_requested const CHOICE_NORMAL := preload("res://assets/ccs_cases_2/images/doctor/dialogue-choice-normal.png") const CHOICE_HOVER := preload("res://assets/ccs_cases_2/images/doctor/dialogue-choice-hover.png") const CHOICE_PRESSED := preload("res://assets/ccs_cases_2/images/doctor/dialogue-choice-pressed.png") 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 := 130.0 const DIALOGUE_BUBBLE_MAX_HEIGHT := 240.0 const DIALOGUE_BUBBLE_VERTICAL_CHROME := 95.0 @onready var hour_hand: Node2D = $ClockMount/HourHand @onready var minute_hand: Node2D = $ClockMount/MinuteHand @onready var second_hand: Node2D = $ClockMount/SecondHand @onready var patient: TextureRect = $Patient @onready var dialogue_bubble: Control = $DialogueBubble @onready var dialogue_text: RichTextLabel = $DialogueBubble/Text @onready var dialogue_continue_hint: Label = $DialogueBubble/ContinueHint @onready var choice_area: Control = $DialogueChoiceArea @onready var choice_container: HBoxContainer = $DialogueChoiceArea/DialogueChoices @onready var choice_pager: HBoxContainer = $DialogueChoiceArea/ChoicePager @onready var previous_page_button: Button = $DialogueChoiceArea/ChoicePager/Previous @onready var page_label: Label = $DialogueChoiceArea/ChoicePager/Page @onready var next_page_button: Button = $DialogueChoiceArea/ChoicePager/Next @onready var computer_launcher: Button = $ComputerLauncher @onready var examination_launcher: Button = $ExaminationLauncher @onready var computer_overlay: Control = $ComputerOverlay @onready var computer_close_button: Button = $ComputerOverlay/Close @onready var vitals_launcher: Button = $ComputerOverlay/VitalsLauncher @onready var vitals_window: PanelContainer = $ComputerOverlay/VitalsWindow @onready var vitals_close_button: Button = $ComputerOverlay/VitalsWindow/Margin/Content/Header/Close @onready var vitals_header: HBoxContainer = $ComputerOverlay/VitalsWindow/Margin/Content/Header @onready var vitals_rows: GridContainer = $ComputerOverlay/VitalsWindow/Margin/Content/Rows @onready var vitals_empty: Label = $ComputerOverlay/VitalsWindow/Margin/Content/Empty @onready var medical_history_launcher: Button = $ComputerOverlay/MedicalHistoryLauncher @onready var medical_history_window: PanelContainer = $ComputerOverlay/MedicalHistoryWindow @onready var medical_history_header: HBoxContainer = $ComputerOverlay/MedicalHistoryWindow/Margin/Content/Header @onready var medical_history_close_button: Button = $ComputerOverlay/MedicalHistoryWindow/Margin/Content/Header/Close @onready var medical_history_text: Label = $ComputerOverlay/MedicalHistoryWindow/Margin/Content/Text @onready var orders_launcher: Button = $ComputerOverlay/OrdersLauncher var blink_tween: Tween var dialogue_tween: Tween var clock_seconds := 9.0 * 60.0 * 60.0 var debrief_panel: PanelContainer var physical_exam_panel: PanelContainer var physical_exam_grid: GridContainer var physical_exam_finding_title: Label var physical_exam_finding_text: Label var physical_exam_status: Label var physical_exam_finish_button: Button var physical_exam_buttons := {} var physical_exam_close_button: Button var physical_exam_result_button: Button var physical_exam_terminal := false var available_exams: Array = [] var attempt_chart: Array = [] var medical_history_section_title: Label var medical_history_exam_text: RichTextLabel var medical_history_exam_scroll: ScrollContainer var medical_history_tab_button: Button var physical_exam_tab_button: Button var orders_window: PanelContainer var orders_search: LineEdit var orders_search_results: VBoxContainer var orders_staged: VBoxContainer var orders_active: VBoxContainer var orders_results: RichTextLabel var orders_tabs: TabContainer var orders_confirm_button: Button var orders_advance_button: Button var orders_advance_option: OptionButton var orders_submit_final_button: Button var orders_close_button: Button var orders_status: Label var order_search_timer: Timer var staged_order_codes: Array[String] = [] var order_names := {} var order_options := {} var final_orders_mode := false var active_order_codes: Array = [] var current_order_location := "" var current_order_minute := 0 var known_order_result_count := -1 var known_visit_completions: Array[String] = [] var visit_completion_tracking_ready := false var patient_update_panel: PanelContainer var patient_update_text: Label var experience_mode := "management" var pending_choices: Array = [] var available_choices: Array = [] var choice_page := 0 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 var dialogue_input_enabled := true var computer_available := true var computer_drag_window: Control var computer_drag_offset := Vector2.ZERO var computer_window_z := 2 func _ready() -> void: previous_page_button.pressed.connect(_change_choice_page.bind(-1)) next_page_button.pressed.connect(_change_choice_page.bind(1)) computer_launcher.pressed.connect(open_computer) examination_launcher.pressed.connect(show_exam_selector) computer_close_button.pressed.connect(close_computer) vitals_launcher.pressed.connect(open_vitals) vitals_close_button.pressed.connect(close_vitals) medical_history_launcher.pressed.connect(open_medical_history) medical_history_close_button.pressed.connect(close_medical_history) orders_launcher.pressed.connect(open_orders) vitals_header.gui_input.connect(_on_computer_window_header_input.bind(vitals_window, vitals_close_button)) medical_history_header.gui_input.connect(_on_computer_window_header_input.bind( medical_history_window, medical_history_close_button)) vitals_window.gui_input.connect(_on_computer_window_input.bind(vitals_window)) medical_history_window.gui_input.connect(_on_computer_window_input.bind(medical_history_window)) resized.connect(_resize_computer_windows) _style_vitals_window() _style_computer_window(medical_history_window) _style_desktop_shortcut(vitals_launcher) _style_desktop_shortcut(medical_history_launcher) _style_desktop_shortcut(orders_launcher) _build_medical_history_tabs() _build_orders_window() _build_patient_update() _resize_computer_windows() _update_clock() _schedule_patient_blink() hide_patient_dialogue() hide_dialogue_choices() func _process(_delta: float) -> void: pass func _update_clock() -> void: var hour_value := floori(clock_seconds / 3600.0) var minute_value := floori(fmod(clock_seconds, 3600.0) / 60.0) var second_value := fmod(clock_seconds, 60.0) var second := float(second_value) var minute := float(minute_value) + second / 60.0 var hour := float(hour_value % 12) + minute / 60.0 hour_hand.rotation = TAU * hour / 12.0 minute_hand.rotation = TAU * minute / 60.0 second_hand.rotation = TAU * second / 60.0 func configure_presentation( presentation: Dictionary, simulated_start: String, simulated_minute: int, vitals: Dictionary = {}, medical_history: String = "", chart: Array = []) -> void: if str(presentation.get("patientAssetKey", "")) != "patient_44_white_male": push_warning("Unsupported CCS patient asset key; using the office default.") var date_time := Time.get_datetime_dict_from_datetime_string(simulated_start, false) if not date_time.is_empty(): clock_seconds = float(int(date_time.get("hour", 9)) * 3600 + int(date_time.get("minute", 0)) * 60) clock_seconds += float(simulated_minute * 60) _update_clock() _render_vitals(vitals) medical_history_text.text = medical_history.strip_edges() \ if not medical_history.strip_edges().is_empty() \ else "No past medical history is available." attempt_chart = chart.duplicate(true) _render_physical_exam_history() func configure_experience_mode(value: String) -> void: experience_mode = value func _schedule_patient_blink() -> void: blink_tween = create_tween() blink_tween.tween_interval(randf_range(2.8, 5.2)) blink_tween.tween_method(_set_patient_blink, 0.0, 1.0, 0.075).set_trans( Tween.TRANS_SINE ).set_ease(Tween.EASE_IN) blink_tween.tween_interval(0.045) blink_tween.tween_method(_set_patient_blink, 1.0, 0.0, 0.10).set_trans( Tween.TRANS_SINE ).set_ease(Tween.EASE_OUT) blink_tween.tween_callback(_schedule_patient_blink) func _set_patient_blink(amount: float) -> void: patient.material.set_shader_parameter("blink_amount", amount) func show_patient_dialogue(text: String, characters_per_second: float = 35.0) -> void: if dialogue_tween and dialogue_tween.is_valid(): dialogue_tween.kill() dialogue_pages = _split_dialogue_pages(text) dialogue_page_index = 0 dialogue_characters_per_second = characters_per_second _show_dialogue_page() func _show_dialogue_page() -> void: if dialogue_tween and dialogue_tween.is_valid(): dialogue_tween.kill() if dialogue_pages.is_empty(): complete_response_transition() return dialogue_text.text = dialogue_pages[dialogue_page_index] dialogue_text.visible_ratio = 0.0 dialogue_reveal_complete = false dialogue_continue_hint.text = "Click to continue" dialogue_continue_hint.visible = true 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) dialogue_tween.finished.connect(_on_dialogue_reveal_finished) 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 = [] dialogue_page_index = 0 dialogue_reveal_complete = false pending_choices = [] func is_patient_dialogue_visible() -> bool: return dialogue_bubble.visible func configure_interview(node: Dictionary) -> void: computer_available = true computer_launcher.visible = not computer_overlay.visible if debrief_panel != null and is_instance_valid(debrief_panel): debrief_panel.queue_free() debrief_panel = null var next_choices: Array if bool(node.get("isTerminal", false)): next_choices = [{ "id": "__continue", "text": "Review Examinations" if experience_mode == "graphical_interview" else "Continue to Patient Management" }] else: next_choices = node.get("choices", []) hide_dialogue_choices() if bool(node.get("suppressPatientText", false)): hide_patient_dialogue() show_dialogue_choices(next_choices) return pending_choices = next_choices show_patient_dialogue(str(node.get("patientText", ""))) func complete_dialogue_reveal() -> void: if dialogue_reveal_complete: 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() func _on_dialogue_reveal_finished() -> void: dialogue_tween = null dialogue_reveal_complete = true dialogue_continue_hint.text = "Click to continue" dialogue_continue_hint.visible = true func advance_dialogue() -> void: if not dialogue_bubble.visible: return if not dialogue_reveal_complete: complete_dialogue_reveal() return if dialogue_page_index < dialogue_pages.size() - 1: dialogue_page_index += 1 _show_dialogue_page() return complete_response_transition() func complete_response_transition() -> void: var choices := pending_choices pending_choices = [] dialogue_bubble.visible = false dialogue_continue_hint.visible = false dialogue_pages = [] dialogue_page_index = 0 dialogue_reveal_complete = false show_dialogue_choices(choices) _update_examination_launcher() func _input(event: InputEvent) -> void: if patient_update_panel != null and patient_update_panel.visible: return if computer_overlay.visible or (physical_exam_panel != null and physical_exam_panel.visible): return if _event_targets_computer_launcher(event): return if not dialogue_input_enabled or not dialogue_bubble.visible: return var should_advance := false if event is InputEventMouseButton: should_advance = event.button_index == MOUSE_BUTTON_LEFT and event.pressed \ and get_global_rect().has_point(event.position) elif event is InputEventScreenTouch: should_advance = event.pressed and get_global_rect().has_point(event.position) elif event is InputEventKey: should_advance = event.pressed and not event.echo \ and event.keycode in [KEY_ENTER, KEY_KP_ENTER, KEY_SPACE] if not should_advance: return advance_dialogue() get_viewport().set_input_as_handled() func set_dialogue_input_enabled(enabled: bool) -> void: dialogue_input_enabled = enabled func open_computer() -> void: if not computer_available or computer_overlay.visible: return computer_overlay.visible = true computer_launcher.visible = false if dialogue_tween and dialogue_tween.is_valid(): dialogue_tween.pause() computer_close_button.grab_focus() func close_computer() -> bool: if not computer_overlay.visible: return false close_vitals() close_medical_history() close_orders() computer_overlay.visible = false computer_launcher.visible = computer_available if dialogue_tween and dialogue_tween.is_valid(): dialogue_tween.play() return true func is_computer_open() -> bool: return computer_overlay.visible func close_transient_overlay() -> bool: if patient_update_panel != null and patient_update_panel.visible: patient_update_panel.visible = false return true if physical_exam_panel != null and physical_exam_panel.visible: if physical_exam_result_button.visible: _close_exam_result() elif not physical_exam_terminal: close_exam_overlay() else: return false return true var top_window := _topmost_computer_window() if top_window == vitals_window: return close_vitals() if top_window == medical_history_window: return close_medical_history() if top_window == orders_window: return close_orders() return close_computer() func open_vitals() -> void: if not computer_overlay.visible: return vitals_window.visible = true _bring_computer_window_to_front(vitals_window) vitals_close_button.grab_focus() func close_vitals() -> bool: if not vitals_window.visible: return false if computer_drag_window == vitals_window: computer_drag_window = null vitals_window.visible = false return true func is_vitals_open() -> bool: return vitals_window.visible func open_medical_history() -> void: if not computer_overlay.visible: return medical_history_window.visible = true _bring_computer_window_to_front(medical_history_window) medical_history_close_button.grab_focus() func close_medical_history() -> bool: if not medical_history_window.visible: return false if computer_drag_window == medical_history_window: computer_drag_window = null medical_history_window.visible = false return true func is_medical_history_open() -> bool: return medical_history_window.visible func _build_orders_window() -> void: orders_window = PanelContainer.new() orders_window.name = "OrdersWindow" orders_window.visible = false orders_window.z_index = 2 orders_window.size = Vector2(1040.0, 700.0) orders_window.position = Vector2(280.0, 100.0) _style_computer_window(orders_window) computer_overlay.add_child(orders_window) var margin := MarginContainer.new() margin.add_theme_constant_override("margin_left", 28) margin.add_theme_constant_override("margin_top", 22) margin.add_theme_constant_override("margin_right", 28) margin.add_theme_constant_override("margin_bottom", 24) orders_window.add_child(margin) var content := VBoxContainer.new() content.add_theme_constant_override("separation", 12) margin.add_child(content) var header := HBoxContainer.new() header.mouse_default_cursor_shape = Control.CURSOR_MOVE content.add_child(header) var title := Label.new() title.text = "Orders" title.size_flags_horizontal = Control.SIZE_EXPAND_FILL title.add_theme_font_size_override("font_size", 30) header.add_child(title) orders_close_button = Button.new() orders_close_button.text = "Close" orders_close_button.pressed.connect(close_orders) header.add_child(orders_close_button) header.gui_input.connect(_on_computer_window_header_input.bind(orders_window, orders_close_button)) orders_window.gui_input.connect(_on_computer_window_input.bind(orders_window)) orders_status = Label.new() orders_status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART content.add_child(orders_status) orders_tabs = TabContainer.new() orders_tabs.size_flags_vertical = Control.SIZE_EXPAND_FILL content.add_child(orders_tabs) _build_order_search_tab() _build_active_orders_tab() _build_order_results_tab() var actions := HBoxContainer.new() actions.alignment = BoxContainer.ALIGNMENT_END content.add_child(actions) orders_advance_option = OptionButton.new() for item in [["10 minutes", 10], ["1 hour", 60], ["4 hours", 240], ["1 day", 1440]]: orders_advance_option.add_item(item[0]) orders_advance_option.set_item_metadata(orders_advance_option.item_count - 1, item[1]) actions.add_child(orders_advance_option) orders_advance_button = Button.new() orders_advance_button.text = "Obtain Results" orders_advance_button.pressed.connect(func(): time_advance_requested.emit( int(orders_advance_option.get_item_metadata(orders_advance_option.selected)))) actions.add_child(orders_advance_button) orders_submit_final_button = Button.new() orders_submit_final_button.text = "Submit Final Orders" orders_submit_final_button.visible = false orders_submit_final_button.pressed.connect(func(): final_orders_submit_requested.emit()) actions.add_child(orders_submit_final_button) func _build_order_search_tab() -> void: var page := VBoxContainer.new() page.name = "Search" page.add_theme_constant_override("separation", 8) orders_tabs.add_child(page) var row := HBoxContainer.new() page.add_child(row) orders_search = LineEdit.new() orders_search.placeholder_text = "Search tests, medications, treatments, and procedures" orders_search.size_flags_horizontal = Control.SIZE_EXPAND_FILL orders_search.text_submitted.connect(func(_value): _request_order_search()) orders_search.text_changed.connect(_queue_live_order_search) row.add_child(orders_search) order_search_timer = Timer.new() order_search_timer.one_shot = true order_search_timer.wait_time = 0.35 order_search_timer.timeout.connect(_request_order_search) add_child(order_search_timer) var search_button := Button.new() search_button.text = "Search" search_button.pressed.connect(_request_order_search) row.add_child(search_button) var search_scroll := ScrollContainer.new() search_scroll.custom_minimum_size.y = 170.0 search_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL page.add_child(search_scroll) orders_search_results = VBoxContainer.new() orders_search_results.size_flags_horizontal = Control.SIZE_EXPAND_FILL search_scroll.add_child(orders_search_results) page.add_child(Label.new()) page.get_child(page.get_child_count() - 1).text = "Orders awaiting confirmation" orders_staged = VBoxContainer.new() page.add_child(orders_staged) orders_confirm_button = Button.new() orders_confirm_button.text = "Confirm Orders" orders_confirm_button.disabled = true orders_confirm_button.pressed.connect(_confirm_staged_orders) page.add_child(orders_confirm_button) func _build_active_orders_tab() -> void: var page := VBoxContainer.new() page.name = "Active Orders" orders_tabs.add_child(page) var scroll := ScrollContainer.new() scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL page.add_child(scroll) orders_active = VBoxContainer.new() orders_active.size_flags_horizontal = Control.SIZE_EXPAND_FILL scroll.add_child(orders_active) func _build_order_results_tab() -> void: var page := VBoxContainer.new() page.name = "Results" orders_tabs.add_child(page) orders_results = RichTextLabel.new() orders_results.bbcode_enabled = true orders_results.size_flags_vertical = Control.SIZE_EXPAND_FILL page.add_child(orders_results) func open_orders() -> void: if not computer_overlay.visible: return orders_window.visible = true orders_launcher.get_node("Label").text = "Orders" _bring_computer_window_to_front(orders_window) orders_search.grab_focus() func close_orders() -> bool: if orders_window == null or not orders_window.visible: return false orders_window.visible = false return true func _request_order_search() -> void: var query := orders_search.text.strip_edges() if query.length() < 2: orders_status.text = "Enter at least two characters to search." return orders_status.text = "Searching..." order_search_requested.emit(query) func _queue_live_order_search(query: String) -> void: order_search_timer.stop() if query.strip_edges().length() < 2: _clear_dynamic_children(orders_search_results) orders_status.text = "" return order_search_timer.start() func render_order_search(rows: Array) -> void: _clear_dynamic_children(orders_search_results) var result_count := 0 for raw_order in rows: var order: Dictionary = raw_order result_count += 1 var code := str(order.get("code", "")) order_names[code] = str(order.get("canonicalName", code.capitalize())) var option: Dictionary = order_options.get(code, {}) var configured := not option.is_empty() var button := Button.new() var detail := str(order.get("category", "")).capitalize() if configured: var location := _humanize_order_location(str(option.get("performanceLocation", current_order_location))) var delay := int(option.get("turnaroundMinutes", 0)) detail += " - %s%s" % [location, " - %d min" % delay if delay > 0 else ""] else: detail += " - Unavailable in this case" button.text = "%s\n%s" % [order_names[code], detail] button.alignment = HORIZONTAL_ALIGNMENT_LEFT button.disabled = not configured or code in staged_order_codes or code in active_order_codes button.tooltip_text = "This order is not configured for this case." if not configured else "" button.pressed.connect(_stage_order.bind(code)) orders_search_results.add_child(button) orders_status.text = "" if result_count > 0 else "No matching orders found." func _stage_order(code: String) -> void: if code.is_empty() or code in staged_order_codes: return staged_order_codes.append(code) _render_staged_orders() func _render_staged_orders() -> void: _clear_dynamic_children(orders_staged) for code in staged_order_codes: var row := HBoxContainer.new() orders_staged.add_child(row) var label := Label.new() var option: Dictionary = order_options.get(code, {}) var location := _humanize_order_location(str(option.get("performanceLocation", current_order_location))) label.text = "%s - %s" % [str(order_names.get(code, code.capitalize())), location] label.size_flags_horizontal = Control.SIZE_EXPAND_FILL row.add_child(label) var remove := Button.new() remove.text = "Remove" remove.pressed.connect(_remove_staged_order.bind(code)) row.add_child(remove) orders_confirm_button.disabled = staged_order_codes.is_empty() func _remove_staged_order(code: String) -> void: staged_order_codes.erase(code) _render_staged_orders() func _confirm_staged_orders() -> void: if staged_order_codes.is_empty(): return orders_submit_requested.emit(staged_order_codes.duplicate()) func configure_orders(attempt: Dictionary) -> void: if orders_window == null: return current_order_location = str(attempt.get("location", "")) current_order_minute = int(attempt.get("simulatedMinute", 0)) order_options.clear() for raw_option in attempt.get("orderOptions", []): var option: Dictionary = raw_option order_options[str(option.get("code", ""))] = option active_order_codes = attempt.get("activeOrderCodes", []).duplicate() for code in attempt.get("activeOrderCodes", []): staged_order_codes.erase(str(code)) _render_staged_orders() _render_active_orders(attempt.get("activeOrders", [])) _render_order_results(attempt.get("chart", [])) _track_patient_visit_completions(attempt.get("chart", [])) var result_count := _order_result_count(attempt.get("chart", [])) if known_order_result_count >= 0 and result_count > known_order_result_count and not orders_window.visible: orders_launcher.get_node("Label").text = "Orders •" known_order_result_count = result_count final_orders_mode = str(attempt.get("state", "")) == "final_orders" orders_advance_button.disabled = false orders_submit_final_button.disabled = false orders_advance_button.visible = not final_orders_mode orders_advance_option.visible = not final_orders_mode orders_submit_final_button.visible = final_orders_mode orders_close_button.visible = true if final_orders_mode: orders_status.text = "FINAL STEP — Review your active orders. Add or cancel anything necessary, then select Submit Final Orders. Pending results do not need to return first." open_computer() open_orders() elif orders_status.text.begins_with("Submitting") or orders_status.text.begins_with("Cancelling") \ or orders_status.text.begins_with("Obtaining"): orders_status.text = "" func show_order_action_complete(action: String) -> void: match action: "orders": orders_tabs.current_tab = 1 orders_status.text = "Orders confirmed and automatically scheduled. Use Obtain Results to advance time." "cancel_order": orders_tabs.current_tab = 1 orders_status.text = "Order cancelled." "advance": orders_tabs.current_tab = 2 orders_status.text = "Simulated time advanced. Newly available results are shown here." func _render_active_orders(rows: Array) -> void: _clear_dynamic_children(orders_active) if rows.is_empty(): var empty := Label.new() empty.text = "No active orders." orders_active.add_child(empty) return for raw_order in rows: var order: Dictionary = raw_order var row := HBoxContainer.new() orders_active.add_child(row) var label := Label.new() var status := str(order.get("status", "active")).capitalize() var location := _humanize_order_location(str(order.get("performanceLocation", current_order_location))) var due_text := "" if order.get("dueMinute", null) != null: due_text = " - due in %d min" % maxi(0, int(order.get("dueMinute", 0)) - current_order_minute) label.text = "%s - %s at %s%s" % [str(order.get("name", "Order")), status, location, due_text] label.size_flags_horizontal = Control.SIZE_EXPAND_FILL row.add_child(label) var cancel := Button.new() cancel.text = "Cancel" cancel.pressed.connect(_emit_cancel_order.bind(str(order.get("code", "")))) row.add_child(cancel) func _render_order_results(chart: Array) -> void: var parts: Array[String] = [] for raw_entry in chart: var entry: Dictionary = raw_entry if str(entry.get("category", "")) in ["lab", "imaging", "other_test", "treatment"]: parts.append("[b]%s[/b]\n%s" % [str(entry.get("title", "Result")), str(entry.get("text", ""))]) orders_results.text = "\n\n".join(parts) if not parts.is_empty() else "No results are available." func _order_result_count(chart: Array) -> int: var count := 0 for raw_entry in chart: if str(raw_entry.get("category", "")) in ["lab", "imaging", "other_test", "treatment"]: count += 1 return count func _humanize_order_location(value: String) -> String: if value.is_empty(): return "Current location" return value.replace("_", " ").capitalize() func _build_patient_update() -> void: patient_update_panel = PanelContainer.new() patient_update_panel.name = "PatientProcedureUpdate" patient_update_panel.visible = false patient_update_panel.z_index = 100 patient_update_panel.position = Vector2(400, 300) patient_update_panel.custom_minimum_size = Vector2(800, 0) add_child(patient_update_panel) var margin := MarginContainer.new() margin.add_theme_constant_override("margin_left", 24) margin.add_theme_constant_override("margin_right", 24) margin.add_theme_constant_override("margin_top", 18) margin.add_theme_constant_override("margin_bottom", 18) patient_update_panel.add_child(margin) var content := VBoxContainer.new() content.add_theme_constant_override("separation", 12) margin.add_child(content) var heading := Label.new() heading.text = "Patient Update" heading.add_theme_font_size_override("font_size", 24) content.add_child(heading) patient_update_text = Label.new() patient_update_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART patient_update_text.add_theme_font_size_override("font_size", 18) content.add_child(patient_update_text) var dismiss := Button.new() dismiss.text = "Continue" dismiss.custom_minimum_size = Vector2(140, 44) dismiss.size_flags_horizontal = Control.SIZE_SHRINK_END dismiss.pressed.connect(func() -> void: patient_update_panel.visible = false) content.add_child(dismiss) func _track_patient_visit_completions(chart: Array) -> void: var current: Array[String] = [] var new_messages: Array[String] = [] for raw_entry in chart: var entry: Dictionary = raw_entry var code := str(entry.get("code", "")) if str(entry.get("category", "")) != "progress" or not code.ends_with("_completed"): continue var key := "%s:%d" % [code, int(entry.get("simulatedMinute", 0))] current.append(key) if visit_completion_tracking_ready and key not in known_visit_completions: new_messages.append(str(entry.get("text", "The patient completed a scheduled procedure and returned."))) known_visit_completions = current if not visit_completion_tracking_ready: visit_completion_tracking_ready = true return if not new_messages.is_empty(): patient_update_text.text = "\n".join(new_messages) + "\nNew results are available in the computer." patient_update_panel.visible = true func set_orders_pending(pending: bool, message: String = "") -> void: orders_confirm_button.disabled = pending or staged_order_codes.is_empty() orders_advance_button.disabled = pending orders_submit_final_button.disabled = pending orders_status.text = message func _emit_cancel_order(code: String) -> void: order_cancel_requested.emit(code) func _clear_dynamic_children(parent: Node) -> void: for child in parent.get_children(): child.queue_free() func _build_medical_history_tabs() -> void: var content := medical_history_text.get_parent() as VBoxContainer medical_history_section_title = content.get_node("SectionTitle") as Label var tabs := HBoxContainer.new() tabs.add_theme_constant_override("separation", 8) content.add_child(tabs) content.move_child(tabs, medical_history_section_title.get_index()) medical_history_tab_button = Button.new() medical_history_tab_button.text = "Medical History" medical_history_tab_button.pressed.connect(_show_chart_tab.bind(false)) tabs.add_child(medical_history_tab_button) physical_exam_tab_button = Button.new() physical_exam_tab_button.text = "Physical Exam" physical_exam_tab_button.pressed.connect(_show_chart_tab.bind(true)) tabs.add_child(physical_exam_tab_button) medical_history_exam_scroll = ScrollContainer.new() medical_history_exam_scroll.custom_minimum_size = Vector2(0.0, 120.0) medical_history_exam_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL medical_history_exam_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED content.add_child(medical_history_exam_scroll) medical_history_exam_text = RichTextLabel.new() medical_history_exam_text.bbcode_enabled = true medical_history_exam_text.fit_content = true medical_history_exam_text.custom_minimum_size = Vector2(560.0, 120.0) medical_history_exam_text.size_flags_horizontal = Control.SIZE_EXPAND_FILL medical_history_exam_text.add_theme_font_size_override("normal_font_size", 18) medical_history_exam_scroll.add_child(medical_history_exam_text) _show_chart_tab(false) func _show_chart_tab(show_exams: bool) -> void: medical_history_section_title.visible = not show_exams medical_history_text.visible = not show_exams medical_history_exam_scroll.visible = show_exams medical_history_tab_button.disabled = not show_exams physical_exam_tab_button.disabled = show_exams _resize_computer_windows() func _render_physical_exam_history() -> void: if medical_history_exam_text == null: return var exam_codes := {} for raw_exam in available_exams: exam_codes[str(raw_exam.get("code", ""))] = true var findings: Array[String] = [] for raw_entry in attempt_chart: var entry: Dictionary = raw_entry if exam_codes.has(str(entry.get("code", ""))): findings.append("[b]%s[/b]\n%s" % [str(entry.get("title", "Examination")), str(entry.get("text", ""))]) medical_history_exam_text.text = "\n\n".join(findings) if not findings.is_empty() \ else "No physical examinations have been completed." func _update_examination_launcher() -> void: if examination_launcher == null: return examination_launcher.visible = choice_container.visible and not computer_overlay.visible \ and (physical_exam_panel == null or not physical_exam_panel.visible) examination_launcher.disabled = available_exams.is_empty() func _render_vitals(vitals: Dictionary) -> void: for child in vitals_rows.get_children(): vitals_rows.remove_child(child) child.queue_free() var rows := _build_vital_rows(vitals) for row in rows: var name_label := Label.new() name_label.custom_minimum_size.x = 230.0 name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL name_label.add_theme_font_size_override("font_size", 19) name_label.add_theme_color_override("font_color", Color(0.18, 0.25, 0.31)) name_label.text = "%s:" % str(row.get("label", "Vital sign")) vitals_rows.add_child(name_label) var value_label := Label.new() value_label.custom_minimum_size.x = 230.0 value_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL value_label.add_theme_font_size_override("font_size", 19) value_label.add_theme_color_override("font_color", Color(0.04, 0.09, 0.13)) value_label.text = str(row.get("value", "")) value_label.autowrap_mode = TextServer.AUTOWRAP_OFF value_label.clip_text = true vitals_rows.add_child(value_label) vitals_rows.visible = not rows.is_empty() vitals_empty.visible = rows.is_empty() _resize_computer_windows() func _build_vital_rows(vitals: Dictionary) -> Array[Dictionary]: var rows: Array[Dictionary] = [] var displayed := {} _append_vital(rows, displayed, vitals, "temperature", "Temperature") _append_vital(rows, displayed, vitals, "pulse", "Pulse") _append_vital(rows, displayed, vitals, "respirations", "Respiratory rate") var blood_pressure := str(vitals.get("bloodPressure", "")).strip_edges() if not blood_pressure.is_empty(): displayed["bloodPressure"] = true var pattern := RegEx.create_from_string("^\\s*([^/]+)\\s*/\\s*([0-9.]+)(.*)$") var matched := pattern.search(blood_pressure) if matched: var suffix := matched.get_string(3).strip_edges() var unit_suffix := " %s" % suffix if not suffix.is_empty() else "" rows.append({"label": "Blood pressure, systolic", "value": matched.get_string(1).strip_edges() + unit_suffix}) rows.append({"label": "Blood pressure, diastolic", "value": matched.get_string(2).strip_edges() + unit_suffix}) else: rows.append({"label": "Blood pressure", "value": blood_pressure}) _append_vital(rows, displayed, vitals, "oxygenSaturation", "Oxygen saturation") _append_vital(rows, displayed, vitals, "height", "Height") _append_vital(rows, displayed, vitals, "weight", "Weight") _append_vital(rows, displayed, vitals, "bmi", "Body Mass Index") var remaining_keys: Array = vitals.keys() remaining_keys.sort() for raw_key in remaining_keys: var key := str(raw_key) if not displayed.has(key): _append_vital(rows, displayed, vitals, key, _friendly_vital_name(key)) return rows func _append_vital( rows: Array[Dictionary], displayed: Dictionary, vitals: Dictionary, key: String, label: String) -> void: displayed[key] = true var value := str(vitals.get(key, "")).strip_edges() if not value.is_empty(): rows.append({"label": label, "value": value}) func _friendly_vital_name(key: String) -> String: var result := "" for index in key.length(): var character := key[index] if index > 0 and character == character.to_upper() and character != character.to_lower(): result += " " result += character return result.capitalize() func _style_vitals_window() -> void: _style_computer_window(vitals_window) func _style_computer_window(window: PanelContainer) -> void: var panel_style := StyleBoxFlat.new() panel_style.bg_color = Color(0.97, 0.98, 0.99, 0.98) panel_style.border_color = Color(0.12, 0.24, 0.34, 0.9) panel_style.set_border_width_all(3) panel_style.set_corner_radius_all(14) panel_style.shadow_color = Color(0.0, 0.0, 0.0, 0.35) panel_style.shadow_size = 18 window.add_theme_stylebox_override("panel", panel_style) func _style_desktop_shortcut(button: Button) -> void: var normal := StyleBoxEmpty.new() var hover := StyleBoxFlat.new() hover.bg_color = Color(0.82, 0.91, 0.97, 0.18) hover.border_color = Color(0.9, 0.96, 1.0, 0.5) hover.set_border_width_all(1) hover.set_corner_radius_all(8) var pressed := hover.duplicate() as StyleBoxFlat pressed.bg_color = Color(0.72, 0.86, 0.96, 0.28) button.add_theme_stylebox_override("normal", normal) button.add_theme_stylebox_override("hover", hover) button.add_theme_stylebox_override("pressed", pressed) button.add_theme_stylebox_override("focus", hover) func _resize_computer_windows() -> void: if vitals_window == null or medical_history_window == null: return var vital_row_count := vitals_rows.get_child_count() / 2 var vitals_content_height := clampf(180.0 + float(vital_row_count) * 36.0, 300.0, 540.0) var vitals_size := Vector2( minf(720.0, maxf(320.0, size.x - 48.0)), minf(vitals_content_height, maxf(300.0, size.y - 48.0))) _set_centered_window_rect(vitals_window, vitals_size, Vector2(-55.0, -25.0)) var exam_count := medical_history_exam_text.text.count("[b]") if medical_history_exam_text != null else 0 var showing_exams := medical_history_exam_scroll != null and medical_history_exam_scroll.visible var history_height := 340.0 if not showing_exams else clampf(250.0 + exam_count * 105.0, 340.0, 650.0) var history_size := Vector2( minf(680.0, maxf(320.0, size.x - 48.0)), minf(history_height, maxf(300.0, size.y - 48.0))) _set_centered_window_rect(medical_history_window, history_size, Vector2(55.0, 25.0)) func _set_centered_window_rect(window: Control, window_size: Vector2, offset: Vector2) -> void: window.offset_left = -window_size.x * 0.5 + offset.x window.offset_top = -window_size.y * 0.5 + offset.y window.offset_right = window_size.x * 0.5 + offset.x window.offset_bottom = window_size.y * 0.5 + offset.y _set_computer_window_position(window, window.position) func _on_computer_window_header_input( event: InputEvent, window: Control, close_button: Button) -> void: if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: if event.pressed: if close_button.get_global_rect().has_point(event.global_position): return computer_drag_window = window computer_drag_offset = event.global_position - window.global_position _bring_computer_window_to_front(window) else: computer_drag_window = null window.accept_event() elif event is InputEventMouseMotion and computer_drag_window == window: var target_global: Vector2 = event.global_position - computer_drag_offset _set_computer_window_position(window, target_global - global_position) window.accept_event() func _on_computer_window_input(event: InputEvent, window: Control) -> void: if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed: _bring_computer_window_to_front(window) func _set_vitals_window_position(target: Vector2) -> void: _set_computer_window_position(vitals_window, target) func _set_medical_history_window_position(target: Vector2) -> void: _set_computer_window_position(medical_history_window, target) func _set_computer_window_position(window: Control, target: Vector2) -> void: var maximum := Vector2( maxf(0.0, size.x - window.size.x), maxf(0.0, size.y - window.size.y)) window.position = Vector2( clampf(target.x, 0.0, maximum.x), clampf(target.y, 0.0, maximum.y)) func _bring_computer_window_to_front(window: Control) -> void: computer_window_z += 1 window.z_index = computer_window_z func _topmost_computer_window() -> Control: var top_window: Control for window in [vitals_window, medical_history_window, orders_window]: if window.visible and (top_window == null or window.z_index > top_window.z_index): top_window = window return top_window func _event_targets_computer_launcher(event: InputEvent) -> bool: if not computer_launcher.visible: return false if event is InputEventMouseButton: return event.button_index == MOUSE_BUTTON_LEFT \ and computer_launcher.get_global_rect().has_point(event.position) if event is InputEventScreenTouch: return computer_launcher.get_global_rect().has_point(event.position) return false func _split_dialogue_pages(text: String) -> Array[String]: var normalized := text.strip_edges() if normalized.is_empty(): return [] var pages: Array[String] = [] var current := "" for word in normalized.split(" ", false): var candidate := word if current.is_empty() else "%s %s" % [current, word] var has_natural_break := current.length() >= DIALOGUE_PAGE_MIN_CHARS \ and _ends_dialogue_phrase(word) if candidate.length() > DIALOGUE_PAGE_MAX_CHARS and not current.is_empty(): pages.append(current) current = word elif has_natural_break: pages.append(candidate) current = "" else: current = candidate if not current.is_empty(): pages.append(current) return pages func _ends_dialogue_phrase(word: String) -> bool: return word.ends_with(",") or word.ends_with(";") or word.ends_with(":") \ or word.ends_with(".") or word.ends_with("?") or word.ends_with("!") func set_choices_pending(pending: bool) -> void: for child in choice_container.get_children(): if child is BaseButton: child.disabled = pending previous_page_button.disabled = pending or choice_page <= 0 next_page_button.disabled = pending or choice_page >= _choice_page_count() - 1 func show_dialogue_choices(options: Array) -> void: hide_dialogue_choices() available_choices = options.duplicate(true) choice_page = 0 _render_choice_page() func _render_choice_page() -> void: _clear_choice_buttons() var page_count := _choice_page_count() if page_count == 0: choice_area.visible = false return choice_page = clampi(choice_page, 0, page_count - 1) var start_index := choice_page * CHOICES_PER_PAGE var end_index := mini(start_index + CHOICES_PER_PAGE, available_choices.size()) var visible_choice_count := end_index - start_index for index in range(start_index, end_index): var option: Dictionary = available_choices[index] var choice_id := str(option.get("id", "")) var option_text := str(option.get("text", "")) var button := TextureButton.new() button.set_meta("choice_id", choice_id) button.custom_minimum_size = Vector2( 0.0 if visible_choice_count == CHOICES_PER_PAGE else 350.0, 136.0) button.size_flags_horizontal = ( Control.SIZE_EXPAND_FILL if visible_choice_count == CHOICES_PER_PAGE else Control.SIZE_SHRINK_CENTER) button.ignore_texture_size = true button.stretch_mode = TextureButton.STRETCH_SCALE button.texture_normal = _crop_choice_texture(CHOICE_NORMAL) button.texture_hover = _crop_choice_texture(CHOICE_HOVER) button.texture_pressed = _crop_choice_texture(CHOICE_PRESSED) button.self_modulate = Color(1.0, 1.0, 1.0, 0.86) button.focus_mode = Control.FOCUS_ALL button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND button.mouse_entered.connect(_on_choice_hover.bind(button, true)) button.mouse_exited.connect(_on_choice_hover.bind(button, false)) button.button_down.connect(_on_choice_pressed_visual.bind(button, true)) button.button_up.connect(_on_choice_pressed_visual.bind(button, false)) button.pressed.connect(_on_dialogue_choice_pressed.bind(choice_id)) var label := Label.new() label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) label.offset_left = 28.0 label.offset_top = 12.0 label.offset_right = -28.0 label.offset_bottom = -12.0 label.mouse_filter = Control.MOUSE_FILTER_IGNORE label.add_theme_color_override("font_color", Color(0.055, 0.105, 0.145)) label.add_theme_font_size_override("font_size", _choice_font_size(option_text)) label.text = option_text label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART label.clip_text = true label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER button.add_child(label) choice_container.add_child(button) choice_pager.visible = page_count > 1 page_label.text = "Page %d of %d" % [choice_page + 1, page_count] previous_page_button.disabled = choice_page <= 0 next_page_button.disabled = choice_page >= page_count - 1 choice_area.visible = true choice_container.visible = true _update_examination_launcher() func hide_dialogue_choices() -> void: _clear_choice_buttons() available_choices = [] choice_page = 0 choice_container.visible = false choice_pager.visible = false choice_area.visible = false _update_examination_launcher() func _clear_choice_buttons() -> void: for child in choice_container.get_children(): choice_container.remove_child(child) child.queue_free() func _choice_page_count() -> int: return ceili(float(available_choices.size()) / float(CHOICES_PER_PAGE)) func _change_choice_page(direction: int) -> void: choice_page = clampi(choice_page + direction, 0, maxi(_choice_page_count() - 1, 0)) _render_choice_page() func _choice_font_size(text: String) -> int: if text.length() > 105: return 15 if text.length() > 75: return 16 return 17 func _crop_choice_texture(source: Texture2D) -> AtlasTexture: var cropped := AtlasTexture.new() cropped.atlas = source cropped.region = CHOICE_CROP cropped.filter_clip = true return cropped func _on_choice_hover(button: TextureButton, hovered: bool) -> void: button.self_modulate = ( Color(1.08, 1.05, 0.96, 0.94) if hovered else Color(1.0, 1.0, 1.0, 0.86) ) func _on_choice_pressed_visual(button: TextureButton, pressed: bool) -> void: button.position.y += 2.0 if pressed else -2.0 func _on_dialogue_choice_pressed(choice_id: String) -> void: if choice_id == "__continue": show_exam_selector(true) else: choice_selected.emit(choice_id) func show_physical_exam(exams: Array, chart: Array, pending_code: String = "") -> void: available_exams = exams.duplicate(true) attempt_chart = chart.duplicate(true) _ensure_physical_exam_panel() physical_exam_panel.visible = true physical_exam_finding_title.visible = true physical_exam_finding_text.visible = true physical_exam_result_button.visible = false for child in physical_exam_grid.get_children(): physical_exam_grid.remove_child(child) child.queue_free() physical_exam_buttons.clear() var completed := {} for raw_entry in chart: var entry: Dictionary = raw_entry completed[str(entry.get("code", ""))] = entry var exam_codes := {} for raw_exam in exams: var exam: Dictionary = raw_exam var code := str(exam.get("code", "")) if code.is_empty(): continue exam_codes[code] = true var button := Button.new() var is_complete := completed.has(code) button.custom_minimum_size = Vector2(0.0, 48.0) button.size_flags_horizontal = Control.SIZE_EXPAND_FILL button.text = "%s%s" % ["[Done] " if is_complete else "", str(exam.get("name", code.capitalize()))] button.alignment = HORIZONTAL_ALIGNMENT_LEFT button.disabled = is_complete or not pending_code.is_empty() button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND button.pressed.connect(_on_physical_exam_pressed.bind(code)) _style_physical_exam_button(button, is_complete) physical_exam_grid.add_child(button) physical_exam_buttons[code] = button var latest_finding: Dictionary = {} for index in range(chart.size() - 1, -1, -1): var entry: Dictionary = chart[index] if exam_codes.has(str(entry.get("code", ""))): latest_finding = entry break if latest_finding.is_empty(): physical_exam_finding_title.text = "Examination Findings" physical_exam_finding_text.text = "Select an examination component to perform it and review the finding." else: physical_exam_finding_title.text = str(latest_finding.get("title", "Examination Finding")) physical_exam_finding_text.text = str(latest_finding.get("text", "No finding was reported.")) set_physical_exam_pending(pending_code) func configure_examinations(exams: Array, chart: Array) -> void: available_exams = exams.duplicate(true) attempt_chart = chart.duplicate(true) _render_physical_exam_history() _update_examination_launcher() func show_exam_selector(terminal: bool = false) -> void: if not choice_container.visible or (physical_exam_panel != null and physical_exam_panel.visible): return physical_exam_terminal = terminal show_physical_exam(available_exams, attempt_chart) physical_exam_finish_button.visible = terminal physical_exam_close_button.visible = not terminal func show_exam_result(chart: Array, exam_code: String) -> void: attempt_chart = chart.duplicate(true) _render_physical_exam_history() _ensure_physical_exam_panel() physical_exam_panel.visible = true for child in physical_exam_grid.get_children(): child.visible = false var finding := _chart_exam_entry(chart, exam_code) physical_exam_finding_title.text = str(finding.get("title", "Examination Finding")) physical_exam_finding_text.text = str(finding.get("text", "No finding was reported.")) physical_exam_result_button.visible = true physical_exam_close_button.visible = false physical_exam_finish_button.visible = false func close_exam_overlay() -> void: if physical_exam_panel != null: physical_exam_panel.visible = false _update_examination_launcher() func _close_exam_result() -> void: if physical_exam_terminal: show_physical_exam(available_exams, attempt_chart) physical_exam_finish_button.visible = true physical_exam_close_button.visible = false else: close_exam_overlay() func _chart_exam_entry(chart: Array, code: String) -> Dictionary: for index in range(chart.size() - 1, -1, -1): var entry: Dictionary = chart[index] if str(entry.get("code", "")) == code: return entry return {} func set_physical_exam_pending(exam_code: String) -> void: var pending := not exam_code.is_empty() for code in physical_exam_buttons: var button: Button = physical_exam_buttons[code] button.disabled = pending or button.text.begins_with("[Done]") physical_exam_finish_button.disabled = pending physical_exam_status.text = "Performing %s..." % _physical_exam_name(exam_code) if pending else "" func is_physical_exam_visible() -> bool: return physical_exam_panel != null and physical_exam_panel.visible func show_physical_exam_error(message: String) -> void: set_physical_exam_pending("") physical_exam_status.text = message func _ensure_physical_exam_panel() -> void: if physical_exam_panel != null and is_instance_valid(physical_exam_panel): return physical_exam_panel = PanelContainer.new() physical_exam_panel.name = "PhysicalExamination" physical_exam_panel.z_index = 50 physical_exam_panel.custom_minimum_size = Vector2(980.0, 650.0) add_child(physical_exam_panel) physical_exam_panel.set_anchors_preset(Control.PRESET_CENTER) physical_exam_panel.offset_left = -490.0 physical_exam_panel.offset_top = -325.0 physical_exam_panel.offset_right = 490.0 physical_exam_panel.offset_bottom = 325.0 var panel_style := StyleBoxFlat.new() panel_style.bg_color = Color(0.96, 0.97, 0.98, 0.97) panel_style.border_color = Color(0.12, 0.22, 0.28, 0.9) panel_style.set_border_width_all(2) panel_style.set_corner_radius_all(14) physical_exam_panel.add_theme_stylebox_override("panel", panel_style) var margin := MarginContainer.new() margin.add_theme_constant_override("margin_left", 26) margin.add_theme_constant_override("margin_top", 22) margin.add_theme_constant_override("margin_right", 26) margin.add_theme_constant_override("margin_bottom", 22) physical_exam_panel.add_child(margin) var content := VBoxContainer.new() content.add_theme_constant_override("separation", 10) margin.add_child(content) var heading := Label.new() heading.text = "Physical Examination" heading.add_theme_font_size_override("font_size", 30) heading.add_theme_color_override("font_color", Color(0.055, 0.105, 0.145)) content.add_child(heading) var instructions := Label.new() instructions.text = "Select one component at a time. Completed examinations are marked below." instructions.add_theme_font_size_override("font_size", 17) instructions.add_theme_color_override("font_color", Color(0.18, 0.25, 0.31)) content.add_child(instructions) var scroll := ScrollContainer.new() scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED content.add_child(scroll) physical_exam_grid = GridContainer.new() physical_exam_grid.name = "ExamComponents" physical_exam_grid.columns = 2 physical_exam_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL physical_exam_grid.add_theme_constant_override("h_separation", 12) physical_exam_grid.add_theme_constant_override("v_separation", 8) scroll.add_child(physical_exam_grid) var divider := HSeparator.new() content.add_child(divider) physical_exam_finding_title = Label.new() physical_exam_finding_title.add_theme_font_size_override("font_size", 21) physical_exam_finding_title.add_theme_color_override("font_color", Color(0.08, 0.19, 0.27)) content.add_child(physical_exam_finding_title) physical_exam_finding_text = Label.new() physical_exam_finding_text.custom_minimum_size.y = 64.0 physical_exam_finding_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART physical_exam_finding_text.add_theme_font_size_override("font_size", 17) physical_exam_finding_text.add_theme_color_override("font_color", Color(0.06, 0.1, 0.14)) content.add_child(physical_exam_finding_text) var actions := HBoxContainer.new() actions.alignment = BoxContainer.ALIGNMENT_END actions.add_theme_constant_override("separation", 12) content.add_child(actions) physical_exam_status = Label.new() physical_exam_status.size_flags_horizontal = Control.SIZE_EXPAND_FILL physical_exam_status.add_theme_font_size_override("font_size", 16) physical_exam_status.vertical_alignment = VERTICAL_ALIGNMENT_CENTER actions.add_child(physical_exam_status) physical_exam_finish_button = Button.new() physical_exam_finish_button.text = "Review Final Orders" physical_exam_finish_button.custom_minimum_size = Vector2(170.0, 48.0) physical_exam_finish_button.pressed.connect(func(): physical_exam_finish_requested.emit()) actions.add_child(physical_exam_finish_button) physical_exam_close_button = Button.new() physical_exam_close_button.text = "Return to Interview" physical_exam_close_button.custom_minimum_size = Vector2(190.0, 48.0) physical_exam_close_button.pressed.connect(close_exam_overlay) actions.add_child(physical_exam_close_button) physical_exam_result_button = Button.new() physical_exam_result_button.text = "Continue" physical_exam_result_button.custom_minimum_size = Vector2(150.0, 48.0) physical_exam_result_button.visible = false physical_exam_result_button.pressed.connect(_close_exam_result) actions.add_child(physical_exam_result_button) func _style_physical_exam_button(button: Button, complete: bool) -> void: var style := StyleBoxFlat.new() style.bg_color = Color(0.88, 0.94, 0.97, 0.96) if complete else Color(1.0, 1.0, 1.0, 0.96) style.border_color = Color(0.19, 0.52, 0.43) if complete else Color(0.45, 0.54, 0.61) style.set_border_width_all(1) style.set_corner_radius_all(8) style.set_content_margin_all(12.0) var hover := style.duplicate() as StyleBoxFlat hover.border_color = Color(0.16, 0.45, 0.68) button.add_theme_stylebox_override("normal", style) button.add_theme_stylebox_override("disabled", style) button.add_theme_stylebox_override("hover", hover) button.add_theme_stylebox_override("pressed", hover) button.add_theme_font_size_override("font_size", 17) button.add_theme_color_override("font_color", Color(0.06, 0.12, 0.17)) button.add_theme_color_override("font_disabled_color", Color(0.12, 0.31, 0.26) if complete else Color(0.42, 0.47, 0.51)) func _physical_exam_name(code: String) -> String: if physical_exam_buttons.has(code): return str(physical_exam_buttons[code].text).trim_prefix("[Done] ") return "examination" func _on_physical_exam_pressed(code: String) -> void: physical_exam_selected.emit(code) func show_debrief(debrief: Dictionary, score: Dictionary) -> void: if physical_exam_panel != null and is_instance_valid(physical_exam_panel): physical_exam_panel.visible = false computer_available = false close_computer() computer_launcher.visible = false hide_patient_dialogue() hide_dialogue_choices() if debrief_panel != null and is_instance_valid(debrief_panel): debrief_panel.queue_free() debrief_panel = PanelContainer.new() debrief_panel.name = "InterviewDebrief" debrief_panel.z_index = 50 debrief_panel.size = Vector2(870.0, 600.0) debrief_panel.position = (size - debrief_panel.size) * 0.5 var panel_style := StyleBoxFlat.new() panel_style.bg_color = Color(0.96, 0.95, 0.91, 0.97) panel_style.border_color = Color(0.12, 0.22, 0.28, 0.8) panel_style.set_border_width_all(2) panel_style.set_corner_radius_all(12) panel_style.set_content_margin_all(28.0) debrief_panel.add_theme_stylebox_override("panel", panel_style) add_child(debrief_panel) var content := VBoxContainer.new() content.add_theme_constant_override("separation", 16) debrief_panel.add_child(content) var heading := Label.new() heading.text = "Interview Debrief" heading.add_theme_font_size_override("font_size", 30) heading.add_theme_color_override("font_color", Color(0.055, 0.105, 0.145)) content.add_child(heading) var diagnosis := Label.new() diagnosis.text = str(debrief.get("likelyDiagnosis", "Interview complete")) diagnosis.add_theme_font_size_override("font_size", 23) diagnosis.add_theme_color_override("font_color", Color(0.12, 0.36, 0.27)) diagnosis.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART content.add_child(diagnosis) var summary := Label.new() summary.text = str(debrief.get("summary", "")) summary.add_theme_font_size_override("font_size", 18) summary.add_theme_color_override("font_color", Color(0.08, 0.12, 0.15)) summary.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART content.add_child(summary) var score_label := Label.new() score_label.text = "History coverage: %d%%" % int(round(float(score.get("percent", 0.0)))) score_label.add_theme_font_size_override("font_size", 20) content.add_child(score_label) var covered: Array[String] = [] var missed: Array[String] = [] for raw_criterion in score.get("criteria", []): var criterion: Dictionary = raw_criterion var title := str(criterion.get("title", "History topic")) if str(criterion.get("status", "")) == "credited": covered.append(title) elif str(criterion.get("status", "")) == "missed": missed.append(title) var coverage := Label.new() coverage.text = "Covered: %s\nMissed optional topics: %s" % [ ", ".join(covered), "None" if missed.is_empty() else ", ".join(missed)] coverage.add_theme_font_size_override("font_size", 16) coverage.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART content.add_child(coverage) var points := Label.new() var teaching_points: Array = debrief.get("teachingPoints", []) points.text = "Key points\n- " + "\n- ".join(teaching_points) if not teaching_points.is_empty() else "" points.add_theme_font_size_override("font_size", 17) points.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART points.size_flags_vertical = Control.SIZE_EXPAND_FILL content.add_child(points) var actions := HBoxContainer.new() actions.alignment = BoxContainer.ALIGNMENT_END actions.add_theme_constant_override("separation", 12) content.add_child(actions) var return_button := Button.new() return_button.text = "Return to Cases" return_button.custom_minimum_size = Vector2(190.0, 48.0) return_button.pressed.connect(func(): debrief_return_requested.emit()) actions.add_child(return_button) var repeat_button := Button.new() repeat_button.text = "Repeat Interview" repeat_button.custom_minimum_size = Vector2(190.0, 48.0) repeat_button.pressed.connect(func(): debrief_repeat_requested.emit()) actions.add_child(repeat_button)