Add CCS computer vitals app

This commit is contained in:
hz
2026-08-07 22:44:30 -05:00
parent 2b25e25424
commit 065db96211
4 changed files with 249 additions and 10 deletions
+141 -3
View File
@@ -32,6 +32,11 @@ const DIALOGUE_BUBBLE_VERTICAL_CHROME := 95.0
@onready var computer_launcher: Button = $ComputerLauncher
@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_rows: GridContainer = $ComputerOverlay/VitalsWindow/Margin/Content/Rows
@onready var vitals_empty: Label = $ComputerOverlay/VitalsWindow/Margin/Content/Empty
var blink_tween: Tween
var dialogue_tween: Tween
@@ -55,6 +60,11 @@ func _ready() -> void:
next_page_button.pressed.connect(_change_choice_page.bind(1))
computer_launcher.pressed.connect(open_computer)
computer_close_button.pressed.connect(close_computer)
vitals_launcher.pressed.connect(open_vitals)
vitals_close_button.pressed.connect(close_vitals)
resized.connect(_resize_vitals_window)
_style_vitals_window()
_resize_vitals_window()
_update_clock()
_schedule_patient_blink()
hide_patient_dialogue()
@@ -77,7 +87,9 @@ func _update_clock() -> void:
second_hand.rotation = TAU * second / 60.0
func configure_presentation(presentation: Dictionary, simulated_start: String, simulated_minute: int) -> void:
func configure_presentation(
presentation: Dictionary, simulated_start: String, simulated_minute: int,
vitals: Dictionary = {}) -> 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)
@@ -85,6 +97,7 @@ func configure_presentation(presentation: Dictionary, simulated_start: String, s
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)
func configure_experience_mode(value: String) -> void:
@@ -266,6 +279,7 @@ func open_computer() -> void:
func close_computer() -> bool:
if not computer_overlay.visible:
return false
close_vitals()
computer_overlay.visible = false
computer_launcher.visible = computer_available
if dialogue_tween and dialogue_tween.is_valid():
@@ -277,6 +291,126 @@ func is_computer_open() -> bool:
return computer_overlay.visible
func close_transient_overlay() -> bool:
if close_vitals():
return true
return close_computer()
func open_vitals() -> void:
if not computer_overlay.visible or vitals_window.visible:
return
vitals_window.visible = true
vitals_launcher.visible = false
vitals_close_button.grab_focus()
func close_vitals() -> bool:
if not vitals_window.visible:
return false
vitals_window.visible = false
vitals_launcher.visible = true
return true
func is_vitals_open() -> bool:
return vitals_window.visible
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.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.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_WORD_SMART
vitals_rows.add_child(value_label)
vitals_rows.visible = not rows.is_empty()
vitals_empty.visible = rows.is_empty()
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:
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
vitals_window.add_theme_stylebox_override("panel", panel_style)
func _resize_vitals_window() -> void:
if vitals_window == null:
return
var window_size := Vector2(
minf(720.0, maxf(320.0, size.x - 48.0)),
minf(540.0, maxf(360.0, size.y - 48.0)))
vitals_window.offset_left = -window_size.x * 0.5
vitals_window.offset_top = -window_size.y * 0.5
vitals_window.offset_right = window_size.x * 0.5
vitals_window.offset_bottom = window_size.y * 0.5
func _event_targets_computer_launcher(event: InputEvent) -> bool:
if not computer_launcher.visible:
return false
@@ -340,14 +474,18 @@ func _render_choice_page() -> void:
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, 136.0)
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
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)