Add CCS computer vitals app
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user