1090 lines
39 KiB
GDScript
1090 lines
39 KiB
GDScript
extends VBoxContainer
|
||
|
||
const Brand := preload("res://scripts/ui/medmind_style.gd")
|
||
const ICON_STETHOSCOPE := preload("res://assets/icons/lucide/stethoscope.svg")
|
||
const ICON_CLIPBOARD := preload("res://assets/icons/lucide/clipboard-plus.svg")
|
||
const ICON_CLOCK := preload("res://assets/icons/lucide/clock-3.svg")
|
||
const ICON_LOCATION := preload("res://assets/icons/lucide/house.svg")
|
||
const ICON_SEARCH := preload("res://assets/icons/lucide/search.svg")
|
||
const RESPONSIVE_BREAKPOINT := 1050.0
|
||
const CHART_TABS := [
|
||
["Order Sheet", "orders"],
|
||
["Progress Notes", "progress"],
|
||
["Vital Signs", "vitals"],
|
||
["Lab Reports", "lab"],
|
||
["Imaging", "imaging"],
|
||
["Other Tests", "other_test"],
|
||
["Treatment Record", "treatment"]
|
||
]
|
||
|
||
var main: Control
|
||
var header_panel: PanelContainer
|
||
var header_layout: BoxContainer
|
||
var heading: Label
|
||
var allotted_label: Label
|
||
var toolbar: HFlowContainer
|
||
var theme_button: Button
|
||
var exit_button: Button
|
||
var abandon_button: Button
|
||
var action_grid: GridContainer
|
||
var action_buttons: Array[Button] = []
|
||
var workspace: PanelContainer
|
||
var chart_panel: PanelContainer
|
||
var clerk_panel: PanelContainer
|
||
var tabs: TabContainer
|
||
var chart_views := {}
|
||
var order_sheet: VBoxContainer
|
||
var active_orders: VBoxContainer
|
||
var search: LineEdit
|
||
var search_button: Button
|
||
var search_results: VBoxContainer
|
||
var staged_orders: VBoxContainer
|
||
var commit_orders_button: Button
|
||
var clear_orders_button: Button
|
||
var final_orders_notice: Label
|
||
var simulated_clock: Label
|
||
var elapsed_clock: Label
|
||
var remaining_clock: Label
|
||
var status: Label
|
||
var timer: Timer
|
||
var report_dialog: Control
|
||
|
||
var intro_dialog: AcceptDialog
|
||
var intro_title: Label
|
||
var intro_body: RichTextLabel
|
||
var opening_step := 0
|
||
var opening_active := false
|
||
|
||
var exam_dialog: ConfirmationDialog
|
||
var interval_history_check: CheckBox
|
||
var exam_checks := {}
|
||
var exam_summary_dialog: ConfirmationDialog
|
||
var exam_summary: RichTextLabel
|
||
var staged_exam_codes: Array[String] = []
|
||
var staged_interval_history := false
|
||
var awaiting_exam_result := false
|
||
var findings_dialog: AcceptDialog
|
||
var findings_body: RichTextLabel
|
||
|
||
var time_dialog: ConfirmationDialog
|
||
var time_preset: OptionButton
|
||
var custom_target: LineEdit
|
||
var time_preview: Label
|
||
var location_dialog: ConfirmationDialog
|
||
var location_option: OptionButton
|
||
var end_dialog: ConfirmationDialog
|
||
var final_instructions: AcceptDialog
|
||
var abandon_dialog: ConfirmationDialog
|
||
|
||
var staged_order_codes: Array[String] = []
|
||
var staged_order_names := {}
|
||
var pending_findings_before := 0
|
||
var leaving := false
|
||
var theme_service
|
||
var toolbar_buttons: Array[Button] = []
|
||
var clinical_labels: Array[RichTextLabel] = []
|
||
|
||
|
||
func _ready() -> void:
|
||
theme_service = get_node("/root/UIThemeService")
|
||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
add_theme_constant_override("separation", 8)
|
||
_build_header()
|
||
_build_actions()
|
||
_build_workspace()
|
||
_build_footer()
|
||
_build_dialogs()
|
||
_build_runtime_services()
|
||
resized.connect(_update_responsive_layout)
|
||
if not theme_service.theme_changed.is_connected(_on_theme_changed):
|
||
theme_service.theme_changed.connect(_on_theme_changed)
|
||
if not CCSService.text_scale_changed.is_connected(_apply_text_scale):
|
||
CCSService.text_scale_changed.connect(_apply_text_scale)
|
||
_apply_theme()
|
||
call_deferred("_update_responsive_layout")
|
||
|
||
|
||
func _build_header() -> void:
|
||
header_panel = PanelContainer.new()
|
||
add_child(header_panel)
|
||
var margin := MarginContainer.new()
|
||
for side in ["left", "right"]:
|
||
margin.add_theme_constant_override("margin_%s" % side, 14)
|
||
margin.add_theme_constant_override("margin_top", 8)
|
||
margin.add_theme_constant_override("margin_bottom", 8)
|
||
header_panel.add_child(margin)
|
||
header_layout = BoxContainer.new()
|
||
header_layout.add_theme_constant_override("separation", 12)
|
||
margin.add_child(header_layout)
|
||
heading = Label.new()
|
||
heading.text = "CCS Case"
|
||
heading.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
heading.add_theme_font_size_override("font_size", 18)
|
||
Brand.apply_font(heading)
|
||
header_layout.add_child(heading)
|
||
allotted_label = Label.new()
|
||
allotted_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
allotted_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
allotted_label.add_theme_font_size_override("font_size", 17)
|
||
Brand.apply_font(allotted_label)
|
||
header_layout.add_child(allotted_label)
|
||
toolbar = HFlowContainer.new()
|
||
toolbar.size_flags_horizontal = Control.SIZE_SHRINK_END
|
||
toolbar.add_theme_constant_override("h_separation", 6)
|
||
header_layout.add_child(toolbar)
|
||
var smaller := _toolbar_button("A−", "Decrease clinical text size")
|
||
smaller.pressed.connect(_zoom.bind(-CCSService.TEXT_SCALE_STEP))
|
||
var larger := _toolbar_button("A+", "Increase clinical text size")
|
||
larger.pressed.connect(_zoom.bind(CCSService.TEXT_SCALE_STEP))
|
||
var report := _toolbar_button("Report", "Report this CCS case")
|
||
report.pressed.connect(_report)
|
||
theme_button = _toolbar_button("", "Switch color theme")
|
||
theme_button.custom_minimum_size.x = 42
|
||
theme_button.pressed.connect(theme_service.toggle_theme)
|
||
exit_button = _toolbar_button("Exit Case", "Enter final orders")
|
||
exit_button.pressed.connect(_request_end_case)
|
||
abandon_button = _toolbar_button("Abandon", "Abandon this attempt without a score")
|
||
abandon_button.pressed.connect(func(): abandon_dialog.popup_centered(Vector2i(520, 220)))
|
||
|
||
|
||
func _toolbar_button(text_value: String, tooltip: String) -> Button:
|
||
var button := Button.new()
|
||
button.text = text_value
|
||
button.tooltip_text = tooltip
|
||
button.custom_minimum_size = Vector2(58, 38)
|
||
toolbar.add_child(button)
|
||
toolbar_buttons.append(button)
|
||
return button
|
||
|
||
|
||
func _build_actions() -> void:
|
||
action_grid = GridContainer.new()
|
||
action_grid.columns = 4
|
||
action_grid.add_theme_constant_override("h_separation", 8)
|
||
action_grid.add_theme_constant_override("v_separation", 8)
|
||
add_child(action_grid)
|
||
_add_action("History & Physical", "Interval history and physical exam", ICON_STETHOSCOPE, _open_exam)
|
||
_add_action("Write Orders / Review Chart", "Place orders and review the patient chart", ICON_CLIPBOARD, _open_order_sheet)
|
||
_add_action("Obtain Results / See Patient Later", "Advance simulated case time", ICON_CLOCK, _open_time_dialog)
|
||
_add_action("Change Location", "Move the patient to another care setting", ICON_LOCATION, _open_location_dialog)
|
||
|
||
|
||
func _add_action(title: String, subtitle: String, icon: Texture2D, callback: Callable) -> void:
|
||
var button := Button.new()
|
||
button.text = "%s\n%s" % [title, subtitle]
|
||
button.icon = icon
|
||
button.expand_icon = true
|
||
button.add_theme_constant_override("icon_max_width", 38)
|
||
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||
button.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
button.custom_minimum_size = Vector2(220, 86)
|
||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
button.pressed.connect(callback)
|
||
action_grid.add_child(button)
|
||
action_buttons.append(button)
|
||
|
||
|
||
func _build_workspace() -> void:
|
||
workspace = PanelContainer.new()
|
||
workspace.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
add_child(workspace)
|
||
chart_panel = workspace
|
||
clerk_panel = workspace
|
||
var margin := MarginContainer.new()
|
||
margin.add_theme_constant_override("margin_left", 14)
|
||
margin.add_theme_constant_override("margin_right", 14)
|
||
margin.add_theme_constant_override("margin_top", 10)
|
||
margin.add_theme_constant_override("margin_bottom", 12)
|
||
workspace.add_child(margin)
|
||
tabs = TabContainer.new()
|
||
tabs.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
tabs.tab_changed.connect(func(_index: int): _render_chart())
|
||
margin.add_child(tabs)
|
||
for definition in CHART_TABS:
|
||
var page := VBoxContainer.new()
|
||
page.name = str(definition[0])
|
||
page.add_theme_constant_override("separation", 8)
|
||
tabs.add_child(page)
|
||
if str(definition[1]) == "orders":
|
||
_build_order_sheet(page)
|
||
else:
|
||
var scroll := ScrollContainer.new()
|
||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
page.add_child(scroll)
|
||
var label := _clinical_text("chart_%s" % definition[1])
|
||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
scroll.add_child(label)
|
||
chart_views[definition[1]] = label
|
||
|
||
|
||
func _build_order_sheet(page: VBoxContainer) -> void:
|
||
order_sheet = page
|
||
final_orders_notice = Label.new()
|
||
final_orders_notice.visible = false
|
||
final_orders_notice.text = "FINAL ORDERS — Add or cancel orders for the patient’s current condition, then submit."
|
||
final_orders_notice.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
page.add_child(final_orders_notice)
|
||
var search_row := HBoxContainer.new()
|
||
search_row.add_theme_constant_override("separation", 6)
|
||
page.add_child(search_row)
|
||
search = LineEdit.new()
|
||
search.placeholder_text = "Search tests, treatments, medications, and procedures"
|
||
search.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
search.text_submitted.connect(func(_value: String): _submit_search())
|
||
search_row.add_child(search)
|
||
search_button = Button.new()
|
||
search_button.icon = ICON_SEARCH
|
||
search_button.tooltip_text = "Search the order catalog"
|
||
search_button.custom_minimum_size = Vector2(44, 42)
|
||
search_button.pressed.connect(_submit_search)
|
||
search_row.add_child(search_button)
|
||
search_results = VBoxContainer.new()
|
||
search_results.add_theme_constant_override("separation", 4)
|
||
page.add_child(search_results)
|
||
var staged_title := Label.new()
|
||
staged_title.text = "Orders awaiting confirmation"
|
||
Brand.apply_font(staged_title)
|
||
page.add_child(staged_title)
|
||
staged_orders = VBoxContainer.new()
|
||
page.add_child(staged_orders)
|
||
var staged_actions := HBoxContainer.new()
|
||
page.add_child(staged_actions)
|
||
commit_orders_button = Button.new()
|
||
commit_orders_button.text = "Confirm Orders"
|
||
commit_orders_button.pressed.connect(_commit_orders)
|
||
staged_actions.add_child(commit_orders_button)
|
||
clear_orders_button = Button.new()
|
||
clear_orders_button.text = "Clear"
|
||
clear_orders_button.pressed.connect(_clear_staged_orders)
|
||
staged_actions.add_child(clear_orders_button)
|
||
page.add_child(HSeparator.new())
|
||
var active_title := Label.new()
|
||
active_title.text = "Order sheet"
|
||
active_title.add_theme_font_size_override("font_size", 18)
|
||
Brand.apply_font(active_title)
|
||
page.add_child(active_title)
|
||
var header := HBoxContainer.new()
|
||
for item in [["Order", 2.4], ["Route", 0.8], ["Frequency", 1.0], ["Order Time", 1.0], ["Report Time", 1.0], ["", 0.8]]:
|
||
var label := Label.new()
|
||
label.text = item[0]
|
||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
label.size_flags_stretch_ratio = item[1]
|
||
Brand.apply_font(label)
|
||
header.add_child(label)
|
||
page.add_child(header)
|
||
var active_scroll := ScrollContainer.new()
|
||
active_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
page.add_child(active_scroll)
|
||
active_orders = VBoxContainer.new()
|
||
active_orders.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
active_scroll.add_child(active_orders)
|
||
|
||
|
||
func _build_footer() -> void:
|
||
var footer := HBoxContainer.new()
|
||
footer.add_theme_constant_override("separation", 12)
|
||
add_child(footer)
|
||
simulated_clock = _footer_label(HORIZONTAL_ALIGNMENT_LEFT)
|
||
footer.add_child(simulated_clock)
|
||
elapsed_clock = _footer_label(HORIZONTAL_ALIGNMENT_CENTER)
|
||
footer.add_child(elapsed_clock)
|
||
remaining_clock = _footer_label(HORIZONTAL_ALIGNMENT_RIGHT)
|
||
footer.add_child(remaining_clock)
|
||
status = Label.new()
|
||
status.visible = false
|
||
status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
add_child(status)
|
||
|
||
|
||
func _footer_label(h_align: HorizontalAlignment) -> Label:
|
||
var label := Label.new()
|
||
label.horizontal_alignment = h_align
|
||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
label.add_theme_font_size_override("font_size", 14)
|
||
Brand.apply_font(label)
|
||
return label
|
||
|
||
|
||
func _build_dialogs() -> void:
|
||
intro_dialog = AcceptDialog.new()
|
||
intro_dialog.close_requested.connect(_accept_opening_step)
|
||
intro_dialog.confirmed.connect(_accept_opening_step)
|
||
add_child(intro_dialog)
|
||
var intro_box := VBoxContainer.new()
|
||
intro_box.custom_minimum_size = Vector2(760, 480)
|
||
intro_dialog.add_child(intro_box)
|
||
intro_title = Label.new()
|
||
intro_title.add_theme_font_size_override("font_size", 22)
|
||
Brand.apply_font(intro_title)
|
||
intro_box.add_child(intro_title)
|
||
intro_body = _clinical_text("opening")
|
||
intro_body.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
intro_body.fit_content = false
|
||
intro_body.scroll_active = true
|
||
intro_box.add_child(intro_body)
|
||
|
||
exam_dialog = ConfirmationDialog.new()
|
||
exam_dialog.title = "History & Physical"
|
||
exam_dialog.ok_button_text = "Review selection"
|
||
exam_dialog.confirmed.connect(_review_exam_selection)
|
||
add_child(exam_dialog)
|
||
var exam_scroll := ScrollContainer.new()
|
||
exam_scroll.custom_minimum_size = Vector2(680, 480)
|
||
exam_dialog.add_child(exam_scroll)
|
||
var exam_box := VBoxContainer.new()
|
||
exam_box.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
exam_scroll.add_child(exam_box)
|
||
var history_help := Label.new()
|
||
history_help.text = "Select physical examination components. Findings become available after the confirmed time advance."
|
||
history_help.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
exam_box.add_child(history_help)
|
||
exam_box.set_meta("exam_container", true)
|
||
|
||
exam_summary_dialog = ConfirmationDialog.new()
|
||
exam_summary_dialog.title = "Confirm History & Physical"
|
||
exam_summary_dialog.confirmed.connect(_commit_exams)
|
||
add_child(exam_summary_dialog)
|
||
exam_summary = _clinical_text("exam_confirmation")
|
||
exam_summary.custom_minimum_size = Vector2(620, 300)
|
||
exam_summary_dialog.add_child(exam_summary)
|
||
findings_dialog = AcceptDialog.new()
|
||
findings_dialog.title = "History and Physical"
|
||
add_child(findings_dialog)
|
||
findings_body = _clinical_text("exam_findings")
|
||
findings_body.custom_minimum_size = Vector2(720, 440)
|
||
findings_body.fit_content = false
|
||
findings_body.scroll_active = true
|
||
findings_dialog.add_child(findings_body)
|
||
|
||
time_dialog = ConfirmationDialog.new()
|
||
time_dialog.title = "Obtain Results / See Patient Later"
|
||
time_dialog.confirmed.connect(_commit_time)
|
||
add_child(time_dialog)
|
||
var time_box := VBoxContainer.new()
|
||
time_box.custom_minimum_size = Vector2(540, 260)
|
||
time_dialog.add_child(time_box)
|
||
time_preset = OptionButton.new()
|
||
for item in [["10 minutes", 10], ["1 hour", 60], ["4 hours", 240], ["12 hours", 720], ["1 day", 1440], ["Custom target", -1]]:
|
||
time_preset.add_item(item[0])
|
||
time_preset.set_item_metadata(time_preset.item_count - 1, item[1])
|
||
time_preset.item_selected.connect(_update_time_preview)
|
||
time_box.add_child(time_preset)
|
||
custom_target = LineEdit.new()
|
||
custom_target.placeholder_text = "YYYY-MM-DD HH:MM (case time)"
|
||
custom_target.visible = false
|
||
custom_target.text_changed.connect(func(_value: String): _update_time_preview(time_preset.selected))
|
||
time_box.add_child(custom_target)
|
||
time_preview = Label.new()
|
||
time_preview.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
time_box.add_child(time_preview)
|
||
|
||
location_dialog = ConfirmationDialog.new()
|
||
location_dialog.title = "Change Location"
|
||
location_dialog.confirmed.connect(_commit_location)
|
||
add_child(location_dialog)
|
||
var location_box := VBoxContainer.new()
|
||
location_box.custom_minimum_size = Vector2(480, 180)
|
||
location_dialog.add_child(location_box)
|
||
var location_help := Label.new()
|
||
location_help.text = "Select the patient’s destination:"
|
||
location_box.add_child(location_help)
|
||
location_option = OptionButton.new()
|
||
location_box.add_child(location_option)
|
||
|
||
end_dialog = ConfirmationDialog.new()
|
||
end_dialog.title = "Enter Final Orders?"
|
||
end_dialog.dialog_text = "This ends active case management. You will have up to two minutes to finalize or cancel patient-care orders."
|
||
end_dialog.ok_button_text = "Enter Final Orders"
|
||
end_dialog.confirmed.connect(func(): CCSService.send_command("end_case"))
|
||
add_child(end_dialog)
|
||
final_instructions = AcceptDialog.new()
|
||
final_instructions.title = "Case-end Instructions"
|
||
final_instructions.dialog_text = "Finalize care of the patient’s current condition.\n\n• Cancel orders that should stop now.\n• Add orders that should be done now.\n• Add relevant future-care orders.\n\nThen submit final orders before time expires."
|
||
final_instructions.confirmed.connect(_open_order_sheet)
|
||
add_child(final_instructions)
|
||
abandon_dialog = ConfirmationDialog.new()
|
||
abandon_dialog.title = "Abandon CCS case?"
|
||
abandon_dialog.dialog_text = "This attempt will end without a score and cannot be resumed."
|
||
abandon_dialog.ok_button_text = "Abandon Case"
|
||
abandon_dialog.confirmed.connect(_confirm_abandon)
|
||
add_child(abandon_dialog)
|
||
|
||
|
||
func _build_runtime_services() -> void:
|
||
timer = Timer.new()
|
||
timer.wait_time = 1
|
||
timer.timeout.connect(_tick)
|
||
add_child(timer)
|
||
CCSService.attempt_changed.connect(_render)
|
||
CCSService.attempt_abandoned.connect(_on_abandoned)
|
||
CCSService.orders_changed.connect(_render_search)
|
||
CCSService.request_failed.connect(_request_failed)
|
||
CCSService.report_submitted.connect(_report_submitted)
|
||
report_dialog = preload("res://scripts/ui/ccs_report_dialog.gd").new()
|
||
add_child(report_dialog)
|
||
|
||
|
||
func on_show(payload: Variant = null) -> void:
|
||
_set_status("")
|
||
leaving = false
|
||
if typeof(payload) == TYPE_STRING and not str(payload).is_empty():
|
||
CCSService.load_attempt(str(payload))
|
||
_render()
|
||
timer.start()
|
||
|
||
|
||
func on_hide() -> void:
|
||
timer.stop()
|
||
|
||
|
||
func _render() -> void:
|
||
var attempt: Dictionary = CCSService.attempt
|
||
if attempt.is_empty():
|
||
return
|
||
if str(attempt.get("state", "")) == "completed":
|
||
main.show_screen("ccs_review", attempt)
|
||
return
|
||
var was_final := exit_button.text == "Submit Final Orders"
|
||
var final_orders := str(attempt.get("state", "")) == "final_orders"
|
||
for code in staged_order_codes.duplicate():
|
||
if code in attempt.get("activeOrderCodes", []):
|
||
staged_order_codes.erase(code)
|
||
staged_order_names.erase(code)
|
||
heading.text = "CCS Case | %s" % _humanize_code(str(attempt.get("location", "")))
|
||
var total := int(attempt.get("realTimeMinutes", 20))
|
||
allotted_label.text = (
|
||
"Legacy untimed CCS case"
|
||
if str(attempt.get("mode", "timed")) == "practice"
|
||
else "Maximum allotted real time: %d minutes + 2 minutes for final orders" % maxi(1, total - 2)
|
||
)
|
||
exit_button.text = "Submit Final Orders" if final_orders else "Exit Case"
|
||
exit_button.tooltip_text = "Submit final orders and complete the case" if final_orders else "Enter final orders"
|
||
final_orders_notice.visible = final_orders
|
||
for button in action_buttons:
|
||
button.disabled = final_orders
|
||
action_buttons[1].disabled = false
|
||
_render_active_orders()
|
||
_render_staged_orders()
|
||
_render_chart()
|
||
_tick()
|
||
_apply_text_scale()
|
||
if not bool(attempt.get("openingCompleted", true)) and not opening_active:
|
||
_start_opening()
|
||
if final_orders and not was_final:
|
||
final_instructions.popup_centered(Vector2i(820, 500))
|
||
_open_order_sheet()
|
||
if awaiting_exam_result and not CCSService.is_loading:
|
||
awaiting_exam_result = false
|
||
_show_new_findings()
|
||
|
||
|
||
func _start_opening() -> void:
|
||
opening_active = true
|
||
opening_step = 0
|
||
_show_opening_step()
|
||
|
||
|
||
func _show_opening_step() -> void:
|
||
var attempt: Dictionary = CCSService.attempt
|
||
match opening_step:
|
||
0:
|
||
intro_dialog.title = "Case Introduction"
|
||
intro_title.text = _simulated_time_text(0)
|
||
intro_body.text = "[center][b]%s[/b]\n\n%s[/center]" % [
|
||
_escape_bbcode(_humanize_code(str(attempt.get("location", "")))),
|
||
_escape_bbcode(str(attempt.get("reasonForEncounter", "")))
|
||
]
|
||
1:
|
||
intro_dialog.title = "Initial Vital Signs"
|
||
intro_title.text = _simulated_time_text(0)
|
||
intro_body.text = _vitals_bbcode(attempt.get("vitals", {}))
|
||
2:
|
||
intro_dialog.title = "Initial History"
|
||
intro_title.text = str(attempt.get("patientSummary", ""))
|
||
intro_body.text = _history_bbcode()
|
||
intro_dialog.popup_centered(Vector2i(820, 560))
|
||
|
||
|
||
func _accept_opening_step() -> void:
|
||
if not opening_active:
|
||
return
|
||
opening_step += 1
|
||
if opening_step < 3:
|
||
call_deferred("_show_opening_step")
|
||
else:
|
||
opening_active = false
|
||
CCSService.send_command("acknowledge_opening")
|
||
|
||
|
||
func _open_exam() -> void:
|
||
if _is_final_orders():
|
||
return
|
||
var container: VBoxContainer
|
||
for node in exam_dialog.find_children("*", "VBoxContainer", true, false):
|
||
if node.has_meta("exam_container"):
|
||
container = node
|
||
break
|
||
if container == null:
|
||
return
|
||
for child in container.get_children():
|
||
if child is CheckBox:
|
||
child.queue_free()
|
||
exam_checks.clear()
|
||
interval_history_check = CheckBox.new()
|
||
interval_history_check.text = "Interval / follow-up history (1 min)"
|
||
container.add_child(interval_history_check)
|
||
for raw_exam in CCSService.attempt.get("availableExams", []):
|
||
var exam: Dictionary = raw_exam
|
||
var check := CheckBox.new()
|
||
var minutes := maxi(1, int(exam.get("durationMinutes", 1)))
|
||
check.text = "%s (%d min)" % [str(exam.get("name", _humanize_code(str(exam.get("code", ""))))), minutes]
|
||
check.set_meta("exam_code", str(exam.get("code", "")))
|
||
check.set_meta("exam_minutes", minutes)
|
||
container.add_child(check)
|
||
exam_checks[str(exam.get("code", ""))] = check
|
||
exam_dialog.popup_centered(Vector2i(740, 600))
|
||
|
||
|
||
func _review_exam_selection() -> void:
|
||
staged_exam_codes.clear()
|
||
staged_interval_history = interval_history_check != null and interval_history_check.button_pressed
|
||
var total := 0
|
||
var lines: Array[String] = []
|
||
if staged_interval_history:
|
||
total += 1
|
||
lines.append("• Interval / follow-up history — 1 min")
|
||
for code in exam_checks:
|
||
var check: CheckBox = exam_checks[code]
|
||
if check.button_pressed:
|
||
staged_exam_codes.append(code)
|
||
var minutes := int(check.get_meta("exam_minutes", 1))
|
||
total += minutes
|
||
lines.append("• %s — %d min" % [check.text.get_slice(" (", 0), minutes])
|
||
if staged_exam_codes.is_empty() and not staged_interval_history:
|
||
_set_status("Select interval history or at least one examination component.")
|
||
return
|
||
exam_summary.text = "%s\n\n[b]Total simulated time: %d minutes[/b]\nThis will advance the clock to [b]%s[/b]." % [
|
||
"\n".join(lines), total, _simulated_time_text(int(CCSService.attempt.get("simulatedMinute", 0)) + total)
|
||
]
|
||
exam_summary_dialog.popup_centered(Vector2i(680, 420))
|
||
|
||
|
||
func _commit_exams() -> void:
|
||
var commands: Array = []
|
||
if staged_interval_history:
|
||
commands.append({"type": "interval_history", "code": "", "minutes": 0})
|
||
for code in staged_exam_codes:
|
||
commands.append({"type": "exam", "code": code, "minutes": 0})
|
||
pending_findings_before = _chart_entries("progress").size()
|
||
awaiting_exam_result = true
|
||
CCSService.send_batch(commands)
|
||
|
||
|
||
func _show_new_findings() -> void:
|
||
var entries := _chart_entries("progress")
|
||
var start := mini(pending_findings_before, entries.size())
|
||
var text := "[center][b]%s[/b][/center]\n\n" % _simulated_time_text(int(CCSService.attempt.get("simulatedMinute", 0)))
|
||
for index in range(start, entries.size()):
|
||
var entry: Dictionary = entries[index]
|
||
if str(entry.get("code", "")).is_empty():
|
||
continue
|
||
text += "[b]%s[/b]\n%s\n\n" % [_escape_bbcode(str(entry.get("title", ""))), _escape_bbcode(str(entry.get("text", "")))]
|
||
findings_body.text = text
|
||
findings_dialog.popup_centered(Vector2i(780, 540))
|
||
|
||
|
||
func _open_order_sheet() -> void:
|
||
tabs.current_tab = 0
|
||
search.grab_focus()
|
||
|
||
|
||
func _submit_search() -> void:
|
||
if search.text.strip_edges().length() < 2:
|
||
_set_status("Enter at least two characters to search.")
|
||
return
|
||
_set_status("")
|
||
CCSService.search_orders(search.text)
|
||
|
||
|
||
func _render_search() -> void:
|
||
_clear(search_results)
|
||
if CCSService.orders.is_empty():
|
||
_set_status(
|
||
"No matching orders found. Try a clinical name or category."
|
||
if search.text.strip_edges().length() >= 2
|
||
else "")
|
||
return
|
||
_set_status("")
|
||
for raw_order in CCSService.orders:
|
||
var order: Dictionary = raw_order
|
||
var code := str(order.get("code", ""))
|
||
var button := Button.new()
|
||
button.text = "%s · %s" % [str(order.get("canonicalName", code)), _humanize_code(str(order.get("category", "")))]
|
||
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||
button.disabled = code in staged_order_codes or code in CCSService.attempt.get("activeOrderCodes", [])
|
||
button.pressed.connect(_stage_order.bind(code))
|
||
search_results.add_child(button)
|
||
|
||
|
||
func _stage_order(code: String) -> void:
|
||
if code.is_empty() or code in staged_order_codes:
|
||
return
|
||
staged_order_names[code] = _catalog_name(code)
|
||
staged_order_codes.append(code)
|
||
search.text = ""
|
||
CCSService.orders = []
|
||
_render_search()
|
||
_render_staged_orders()
|
||
|
||
|
||
func _render_staged_orders() -> void:
|
||
_clear(staged_orders)
|
||
for code in staged_order_codes:
|
||
var row := HBoxContainer.new()
|
||
staged_orders.add_child(row)
|
||
var label := Label.new()
|
||
label.text = _catalog_name(code)
|
||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
row.add_child(label)
|
||
var remove := Button.new()
|
||
remove.text = "Remove"
|
||
remove.pressed.connect(_unstage_order.bind(code))
|
||
row.add_child(remove)
|
||
commit_orders_button.disabled = staged_order_codes.is_empty()
|
||
clear_orders_button.disabled = staged_order_codes.is_empty()
|
||
|
||
|
||
func _unstage_order(code: String) -> void:
|
||
staged_order_codes.erase(code)
|
||
staged_order_names.erase(code)
|
||
_render_staged_orders()
|
||
|
||
|
||
func _clear_staged_orders() -> void:
|
||
staged_order_codes.clear()
|
||
staged_order_names.clear()
|
||
_render_staged_orders()
|
||
|
||
|
||
func _commit_orders() -> void:
|
||
if staged_order_codes.is_empty():
|
||
return
|
||
var commands: Array = []
|
||
for code in staged_order_codes:
|
||
commands.append({"type": "order", "code": code, "minutes": 0})
|
||
CCSService.orders = []
|
||
_render_search()
|
||
_render_staged_orders()
|
||
CCSService.send_batch(commands)
|
||
|
||
|
||
func _render_active_orders() -> void:
|
||
_clear(active_orders)
|
||
var rows: Array = CCSService.attempt.get("activeOrders", [])
|
||
if rows.is_empty():
|
||
var empty := Label.new()
|
||
empty.text = "No active orders."
|
||
active_orders.add_child(empty)
|
||
return
|
||
for raw_order in rows:
|
||
var order: Dictionary = raw_order
|
||
var row := HBoxContainer.new()
|
||
row.custom_minimum_size.y = 44
|
||
active_orders.add_child(row)
|
||
_add_order_cell(row, str(order.get("name", _humanize_code(str(order.get("code", ""))))), 2.4)
|
||
_add_order_cell(row, str(order.get("route", "—")) if not str(order.get("route", "")).is_empty() else "—", 0.8)
|
||
_add_order_cell(row, str(order.get("frequency", "—")) if not str(order.get("frequency", "")).is_empty() else "—", 1.0)
|
||
_add_order_cell(row, _simulated_time_text(int(order.get("orderedMinute", 0)), false), 1.0)
|
||
var report_value: Variant = order.get("reportMinute", null)
|
||
var report_text := "—"
|
||
if bool(order.get("isPending", false)):
|
||
report_text = "Pending"
|
||
elif bool(order.get("hasResult", report_value != null)) and report_value != null:
|
||
report_text = _simulated_time_text(int(report_value), false)
|
||
_add_order_cell(row, report_text, 1.0)
|
||
var cancel := Button.new()
|
||
cancel.text = "Cancel"
|
||
cancel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
cancel.size_flags_stretch_ratio = 0.8
|
||
cancel.pressed.connect(_cancel_order.bind(str(order.get("code", ""))))
|
||
row.add_child(cancel)
|
||
|
||
|
||
func _add_order_cell(row: HBoxContainer, value: String, ratio: float) -> void:
|
||
var label := Label.new()
|
||
label.text = value
|
||
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
label.size_flags_stretch_ratio = ratio
|
||
row.add_child(label)
|
||
|
||
|
||
func _cancel_order(code: String) -> void:
|
||
CCSService.send_batch([{"type": "cancel_order", "code": code, "minutes": 0}])
|
||
|
||
|
||
func _open_time_dialog() -> void:
|
||
if _is_final_orders():
|
||
return
|
||
time_preset.select(0)
|
||
custom_target.visible = false
|
||
_update_time_preview(0)
|
||
time_dialog.popup_centered(Vector2i(600, 350))
|
||
|
||
|
||
func _update_time_preview(index: int) -> void:
|
||
var minutes := int(time_preset.get_item_metadata(index))
|
||
custom_target.visible = minutes < 0
|
||
if minutes < 0:
|
||
minutes = _custom_target_minutes()
|
||
time_preview.text = "Clock will advance to %s." % _simulated_time_text(int(CCSService.attempt.get("simulatedMinute", 0)) + maxi(0, minutes))
|
||
time_dialog.get_ok_button().disabled = minutes <= 0
|
||
|
||
|
||
func _custom_target_minutes() -> int:
|
||
var value := custom_target.text.strip_edges().replace(" ", "T")
|
||
if value.length() == 16:
|
||
value += ":00"
|
||
var target := Time.get_unix_time_from_datetime_string(value)
|
||
if target <= 0:
|
||
return 0
|
||
var start_text := str(CCSService.attempt.get("simulatedStartUtc", "")).trim_suffix("Z")
|
||
var start_unix := Time.get_unix_time_from_datetime_string(start_text)
|
||
var current_unix := start_unix + int(CCSService.attempt.get("simulatedMinute", 0)) * 60
|
||
return maxi(0, int((target - current_unix) / 60.0))
|
||
|
||
|
||
func _commit_time() -> void:
|
||
var minutes := int(time_preset.get_item_metadata(time_preset.selected))
|
||
if minutes < 0:
|
||
minutes = _custom_target_minutes()
|
||
if minutes > 0:
|
||
CCSService.send_command("advance", "", minutes)
|
||
|
||
|
||
func _open_location_dialog() -> void:
|
||
if _is_final_orders():
|
||
return
|
||
location_option.clear()
|
||
for raw_location in CCSService.attempt.get("allowedLocations", []):
|
||
var location := str(raw_location)
|
||
location_option.add_item(_humanize_code(location))
|
||
location_option.set_item_metadata(location_option.item_count - 1, location)
|
||
location_dialog.get_ok_button().disabled = location_option.item_count == 0
|
||
location_dialog.popup_centered(Vector2i(540, 280))
|
||
|
||
|
||
func _commit_location() -> void:
|
||
if location_option.item_count > 0:
|
||
CCSService.send_command("location", str(location_option.get_item_metadata(location_option.selected)))
|
||
|
||
|
||
func _request_end_case() -> void:
|
||
if _is_final_orders():
|
||
CCSService.send_command("exit_case")
|
||
else:
|
||
end_dialog.popup_centered(Vector2i(560, 260))
|
||
|
||
|
||
func leave_case() -> void:
|
||
abandon_dialog.popup_centered(Vector2i(520, 220))
|
||
|
||
|
||
func _confirm_abandon() -> void:
|
||
if leaving:
|
||
return
|
||
leaving = true
|
||
CCSService.abandon()
|
||
|
||
|
||
func _on_abandoned() -> void:
|
||
if leaving:
|
||
leaving = false
|
||
main.show_screen("ccs_hub")
|
||
|
||
|
||
func _render_chart() -> void:
|
||
for definition in CHART_TABS:
|
||
var category := str(definition[1])
|
||
if category == "orders":
|
||
continue
|
||
var label: RichTextLabel = chart_views.get(category)
|
||
if label == null:
|
||
continue
|
||
label.text = _chart_category_bbcode(category)
|
||
|
||
|
||
func _chart_category_bbcode(category: String) -> String:
|
||
if category == "vitals":
|
||
return _vital_history_bbcode()
|
||
var entries := _chart_entries(category)
|
||
if entries.is_empty():
|
||
return "[center][color=#8490a5]No %s available.[/color][/center]" % str(_tab_title(category)).to_lower()
|
||
var text := ""
|
||
for raw_entry in entries:
|
||
var entry: Dictionary = raw_entry
|
||
text += "[b]%s[/b] [color=#8490a5]%s[/color]\n%s\n\n" % [
|
||
_escape_bbcode(str(entry.get("title", _tab_title(category)))),
|
||
_escape_bbcode(_simulated_time_text(int(entry.get("simulatedMinute", 0)), false)),
|
||
_escape_bbcode(str(entry.get("text", "")))
|
||
]
|
||
return text
|
||
|
||
|
||
func _chart_entries(category: String) -> Array:
|
||
var result: Array = []
|
||
for raw_entry in CCSService.attempt.get("chart", []):
|
||
var entry: Dictionary = raw_entry
|
||
var actual := str(entry.get("category", "progress"))
|
||
if actual == "exam":
|
||
actual = "progress"
|
||
elif actual == "results":
|
||
actual = "other_test"
|
||
if actual == category:
|
||
result.append(entry)
|
||
return result
|
||
|
||
|
||
func _vital_history_bbcode() -> String:
|
||
var snapshots: Array = CCSService.attempt.get("vitalHistory", [])
|
||
if snapshots.is_empty():
|
||
return _vitals_bbcode(CCSService.attempt.get("vitals", {}))
|
||
var text := ""
|
||
for raw_snapshot in snapshots:
|
||
var snapshot: Dictionary = raw_snapshot
|
||
text += "[b]%s[/b]\n%s\n\n" % [
|
||
_escape_bbcode(_simulated_time_text(int(snapshot.get("simulatedMinute", 0)), false)),
|
||
_vitals_bbcode(snapshot.get("values", {}))
|
||
]
|
||
return text
|
||
|
||
|
||
func _vitals_bbcode(vitals: Dictionary) -> String:
|
||
var lines: Array[String] = []
|
||
for key in _ordered_vital_keys(vitals):
|
||
lines.append("[b]%s:[/b] %s" % [_escape_bbcode(_vital_label(key)), _escape_bbcode(str(vitals[key]))])
|
||
return "\n".join(lines)
|
||
|
||
|
||
func _history_bbcode() -> String:
|
||
var sections: Array = CCSService.attempt.get("historySections", [])
|
||
if sections.is_empty():
|
||
return "[b]History of present illness[/b]\n%s\n\n[b]Initial appearance[/b]\n%s" % [
|
||
_escape_bbcode(str(CCSService.attempt.get("history", ""))),
|
||
_escape_bbcode(str(CCSService.attempt.get("appearance", "")))
|
||
]
|
||
var text := ""
|
||
for raw_section in sections:
|
||
var section: Dictionary = raw_section
|
||
text += "[b]%s[/b]\n%s\n\n" % [_escape_bbcode(str(section.get("title", ""))), _escape_bbcode(str(section.get("text", "")))]
|
||
return text
|
||
|
||
|
||
func _tick() -> void:
|
||
if CCSService.attempt.is_empty():
|
||
return
|
||
var now := Time.get_unix_time_from_system()
|
||
var created_text := str(CCSService.attempt.get("createdUtc", "")).trim_suffix("Z")
|
||
var created := Time.get_unix_time_from_datetime_string(created_text)
|
||
var elapsed := maxi(0, int(now - created))
|
||
var phase_deadline := CCSService.deadline_unix()
|
||
var hard_deadline := CCSService.hard_deadline_unix()
|
||
var display_deadline := phase_deadline if _is_final_orders() else hard_deadline
|
||
var remaining := maxi(0, int(ceil(display_deadline - now)))
|
||
var phase_remaining := maxi(0, int(ceil(phase_deadline - now)))
|
||
simulated_clock.text = "Elapsed SIMULATED case time: %s" % _elapsed_simulated_text(int(CCSService.attempt.get("simulatedMinute", 0)))
|
||
elapsed_clock.text = "Elapsed REAL time: %s" % _duration_text(elapsed)
|
||
remaining_clock.text = (
|
||
"Legacy untimed case"
|
||
if hard_deadline <= 0
|
||
else "%s REAL time: %s" % ["Final-order" if _is_final_orders() else "Remaining", _duration_text(remaining)]
|
||
)
|
||
if phase_remaining == 0 and phase_deadline > 0 and not CCSService.is_loading:
|
||
CCSService.load_attempt(str(CCSService.attempt.get("id", "")))
|
||
|
||
|
||
func _simulated_time_text(minute: int, include_day: bool = true) -> String:
|
||
var start_text := str(CCSService.attempt.get("simulatedStartUtc", "")).trim_suffix("Z")
|
||
var unix := Time.get_unix_time_from_datetime_string(start_text)
|
||
if unix <= 0:
|
||
return "Minute %d" % minute
|
||
var value := Time.get_datetime_dict_from_unix_time(unix + minute * 60)
|
||
var hour := int(value.hour)
|
||
var suffix := "AM" if hour < 12 else "PM"
|
||
var display_hour := hour % 12
|
||
if display_hour == 0:
|
||
display_hour = 12
|
||
var day_number := 1 + int(floor(float(minute) / 1440.0))
|
||
return "%s%d:%02d %s" % ["Day %d @ " % day_number if include_day else "", display_hour, int(value.minute), suffix]
|
||
|
||
|
||
func _elapsed_simulated_text(minutes: int) -> String:
|
||
@warning_ignore("integer_division")
|
||
return "%d Days %02d Hrs %02d Mins" % [minutes / 1440, (minutes % 1440) / 60, minutes % 60]
|
||
|
||
|
||
func _duration_text(seconds: int) -> String:
|
||
@warning_ignore("integer_division")
|
||
return "%02d:%02d" % [seconds / 60, seconds % 60]
|
||
|
||
|
||
func _ordered_vital_keys(vitals: Dictionary) -> Array[String]:
|
||
var ordered: Array[String] = []
|
||
for key in ["temperature", "bloodPressure", "pulse", "respirations", "oxygenSaturation", "height", "weight", "bmi"]:
|
||
if vitals.has(key):
|
||
ordered.append(key)
|
||
var remaining: Array[String] = []
|
||
for raw_key in vitals.keys():
|
||
var key := str(raw_key)
|
||
if not key in ordered:
|
||
remaining.append(key)
|
||
remaining.sort()
|
||
ordered.append_array(remaining)
|
||
return ordered
|
||
|
||
|
||
func _vital_label(key: String) -> String:
|
||
match key:
|
||
"bloodPressure": return "Blood Pressure"
|
||
"oxygenSaturation": return "Oxygen Saturation"
|
||
"bmi": return "Body Mass Index"
|
||
_: return _humanize_code(key)
|
||
|
||
|
||
func _clinical_text(region: String) -> RichTextLabel:
|
||
var label := RichTextLabel.new()
|
||
label.bbcode_enabled = true
|
||
label.selection_enabled = true
|
||
label.fit_content = true
|
||
label.scroll_active = false
|
||
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
label.add_theme_font_override("normal_font", Brand.font)
|
||
label.add_theme_font_override("bold_font", Brand.font)
|
||
label.set_meta("ccs_base_font_size", 16)
|
||
label.set_meta("ccs_region", region)
|
||
label.gui_input.connect(_text_input.bind(label))
|
||
clinical_labels.append(label)
|
||
return label
|
||
|
||
|
||
func _text_input(event: InputEvent, label: RichTextLabel) -> void:
|
||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and not event.pressed:
|
||
call_deferred("_commit_selection", label)
|
||
|
||
|
||
func _commit_selection(label: RichTextLabel) -> void:
|
||
var text := label.get_selected_text()
|
||
if text.is_empty():
|
||
return
|
||
var source := label.get_parsed_text()
|
||
var start := source.find(text)
|
||
if start >= 0:
|
||
CCSService.toggle_highlight(str(CCSService.attempt.get("id", "")), str(label.get_meta("ccs_region", "")), start, start + text.length())
|
||
|
||
|
||
func _zoom(delta: float) -> void:
|
||
CCSService.adjust_text_scale(delta)
|
||
_apply_text_scale()
|
||
|
||
|
||
func _apply_text_scale() -> void:
|
||
for label in clinical_labels:
|
||
var font_size := maxi(12, int(round(float(label.get_meta("ccs_base_font_size", 16)) * CCSService.text_scale)))
|
||
label.add_theme_font_size_override("normal_font_size", font_size)
|
||
label.add_theme_font_size_override("bold_font_size", font_size)
|
||
|
||
|
||
func _report() -> void:
|
||
if report_dialog != null:
|
||
report_dialog.open(CCSService.attempt)
|
||
|
||
|
||
func _report_submitted(_id: String) -> void:
|
||
_set_status("Case report submitted.", false)
|
||
|
||
|
||
func _request_failed(message: String) -> void:
|
||
if visible:
|
||
awaiting_exam_result = false
|
||
_set_status(message)
|
||
|
||
|
||
func _set_status(message: String, is_error: bool = true) -> void:
|
||
status.text = message
|
||
status.visible = not message.is_empty()
|
||
status.add_theme_color_override("font_color", Color("ff6b7f") if is_error else Brand.SUCCESS)
|
||
|
||
|
||
func _catalog_name(code: String) -> String:
|
||
if staged_order_names.has(code):
|
||
return str(staged_order_names[code])
|
||
for raw_order in CCSService.orders:
|
||
var order: Dictionary = raw_order
|
||
if str(order.get("code", "")) == code:
|
||
return str(order.get("canonicalName", _humanize_code(code)))
|
||
return _humanize_code(code)
|
||
|
||
|
||
func _tab_title(category: String) -> String:
|
||
for definition in CHART_TABS:
|
||
if str(definition[1]) == category:
|
||
return str(definition[0])
|
||
return _humanize_code(category)
|
||
|
||
|
||
func _humanize_code(value: String) -> String:
|
||
return value.replace("_", " ").capitalize()
|
||
|
||
|
||
func _escape_bbcode(value: String) -> String:
|
||
return value.replace("[", "[lb]").replace("]", "[rb]")
|
||
|
||
|
||
func _is_final_orders() -> bool:
|
||
return str(CCSService.attempt.get("state", "")) == "final_orders"
|
||
|
||
|
||
func _clear(parent: Node) -> void:
|
||
for child in parent.get_children():
|
||
child.queue_free()
|
||
|
||
|
||
func _update_responsive_layout() -> void:
|
||
if action_grid == null:
|
||
return
|
||
var narrow := size.x > 0 and size.x < RESPONSIVE_BREAKPOINT
|
||
header_layout.vertical = narrow
|
||
action_grid.columns = 2 if narrow else 4
|
||
toolbar.size_flags_horizontal = Control.SIZE_EXPAND_FILL if narrow else Control.SIZE_SHRINK_END
|
||
for button in action_buttons:
|
||
button.custom_minimum_size.x = 0 if narrow else 220
|
||
queue_sort()
|
||
|
||
|
||
func _on_theme_changed(_dark: bool) -> void:
|
||
call_deferred("_apply_theme")
|
||
|
||
|
||
func _apply_theme() -> void:
|
||
if header_panel == null:
|
||
return
|
||
var dark: bool = theme_service != null and theme_service.is_dark_mode()
|
||
header_panel.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
||
workspace.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
||
heading.add_theme_color_override("font_color", Brand.text_color(dark))
|
||
allotted_label.add_theme_color_override("font_color", Brand.ROYAL_BLUE if not dark else Brand.SKY_BLUE)
|
||
for label in [simulated_clock, elapsed_clock, remaining_clock]:
|
||
label.add_theme_color_override("font_color", Brand.text_color(dark))
|
||
final_orders_notice.add_theme_color_override("font_color", Brand.AMBER)
|
||
for button in toolbar_buttons + action_buttons:
|
||
button.add_theme_font_override("font", Brand.font)
|
||
button.add_theme_color_override("font_color", Brand.text_color(dark))
|
||
button.add_theme_color_override("font_hover_color", Brand.text_color(dark))
|
||
button.add_theme_stylebox_override("normal", Brand.content_card_box(dark, false))
|
||
button.add_theme_stylebox_override("hover", Brand.content_card_box(dark, true))
|
||
for label in clinical_labels:
|
||
label.add_theme_color_override("default_color", Brand.text_color(dark))
|
||
Brand.set_theme_toggle_icon(theme_button, dark)
|
||
theme_button.tooltip_text = "Switch to light mode" if dark else "Switch to dark mode"
|
||
_apply_text_scale()
|