Redesign exam and CCS experiences
This commit is contained in:
+679
-106
@@ -1,215 +1,788 @@
|
||||
extends VBoxContainer
|
||||
|
||||
const Style := preload("res://scripts/ui/ui_style.gd")
|
||||
const Brand := preload("res://scripts/ui/medmind_style.gd")
|
||||
const ICON_ACTIVITY := preload("res://assets/icons/lucide/activity.svg")
|
||||
const ICON_HEART := preload("res://assets/icons/lucide/heart-pulse.svg")
|
||||
const ICON_LUNGS := preload("res://assets/icons/lucide/lungs.svg")
|
||||
const ICON_SEARCH := preload("res://assets/icons/lucide/search.svg")
|
||||
const ICON_STETHOSCOPE := preload("res://assets/icons/lucide/stethoscope.svg")
|
||||
const ICON_THERMOMETER := preload("res://assets/icons/lucide/thermometer.svg")
|
||||
const RESPONSIVE_BREAKPOINT := 1050.0
|
||||
|
||||
var main: Control
|
||||
var header_panel: PanelContainer
|
||||
var header_layout: BoxContainer
|
||||
var header_icon_panel: PanelContainer
|
||||
var heading: Label
|
||||
var clock: Label
|
||||
var toolbar: HFlowContainer
|
||||
var workspace: BoxContainer
|
||||
var chart_panel: PanelContainer
|
||||
var clerk_panel: PanelContainer
|
||||
var summary: RichTextLabel
|
||||
var history: RichTextLabel
|
||||
var appearance: RichTextLabel
|
||||
var chart: RichTextLabel
|
||||
var vitals_flow: HFlowContainer
|
||||
var orders: VBoxContainer
|
||||
var search: LineEdit
|
||||
var search_button: Button
|
||||
var search_results_panel: PanelContainer
|
||||
var search_results: VBoxContainer
|
||||
var actions: VBoxContainer
|
||||
var locations: HFlowContainer
|
||||
var time_row: GridContainer
|
||||
var status: Label
|
||||
var final_orders_notice: Label
|
||||
var clock: Label
|
||||
var timer: Timer
|
||||
var end_button: Button
|
||||
var time_row: HBoxContainer
|
||||
var report_dialog: Control
|
||||
var report_button: Button
|
||||
var app_feedback_button: Button
|
||||
var workspace: HSplitContainer
|
||||
var leave_button: Button
|
||||
var leaving := false
|
||||
var theme_service
|
||||
var theme_button: Button
|
||||
var section_labels: Array[Label] = []
|
||||
var toolbar_buttons: Array[Button] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
theme_service = get_node("/root/UIThemeService")
|
||||
set_anchors_preset(Control.PRESET_FULL_RECT); add_theme_constant_override("separation", 8)
|
||||
var top := HBoxContainer.new(); top.add_theme_constant_override("separation", 8); add_child(top)
|
||||
heading = Label.new(); heading.size_flags_horizontal = Control.SIZE_EXPAND_FILL; Style.set_label(heading, 25, Style.TEXT); top.add_child(heading)
|
||||
clock = Label.new(); Style.set_label(clock, 18, Style.AMBER); top.add_child(clock)
|
||||
var smaller := Button.new(); smaller.text = "A-"; smaller.tooltip_text = "Decrease text size"; smaller.pressed.connect(_zoom.bind(-CCSService.TEXT_SCALE_STEP)); top.add_child(smaller)
|
||||
var larger := Button.new(); larger.text = "A+"; larger.tooltip_text = "Increase text size"; larger.pressed.connect(_zoom.bind(CCSService.TEXT_SCALE_STEP)); top.add_child(larger)
|
||||
var clear_highlights := Button.new(); clear_highlights.text = "Clear"; clear_highlights.tooltip_text = "Clear highlights"; clear_highlights.pressed.connect(_clear_highlights); top.add_child(clear_highlights)
|
||||
report_button = Button.new(); report_button.text = "Report"; report_button.tooltip_text = "Report CCS case"; report_button.pressed.connect(_report); top.add_child(report_button)
|
||||
app_feedback_button = Button.new(); app_feedback_button.text = "Feedback"; app_feedback_button.tooltip_text = "Send general feedback or report a bug"; app_feedback_button.pressed.connect(func(): main.open_feedback_dialog()); top.add_child(app_feedback_button)
|
||||
theme_button = Button.new(); theme_button.custom_minimum_size = Vector2(44, 44); theme_button.add_theme_font_size_override("font_size", 22); UISoundService.set_button_sound(theme_button, "toggle"); theme_button.pressed.connect(theme_service.toggle_theme); top.add_child(theme_button); _refresh_theme_button()
|
||||
workspace = HSplitContainer.new(); workspace.size_flags_vertical = Control.SIZE_EXPAND_FILL; workspace.split_offset = 850; add_child(workspace)
|
||||
var chart_scroll := ScrollContainer.new(); chart_scroll.custom_minimum_size.x = 500; chart_scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL; workspace.add_child(chart_scroll)
|
||||
var chart_panel := VBoxContainer.new(); chart_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL; chart_panel.add_theme_constant_override("separation", 18); chart_scroll.add_child(chart_panel)
|
||||
summary = RichTextLabel.new(); summary.bbcode_enabled = true; summary.selection_enabled = true; summary.fit_content = true; summary.scroll_active = false; summary.set_meta("ccs_region", "summary"); summary.gui_input.connect(_text_input.bind(summary)); chart_panel.add_child(summary)
|
||||
chart = RichTextLabel.new(); chart.bbcode_enabled = true; chart.selection_enabled = true; chart.fit_content = true; chart.scroll_active = false; chart.set_meta("ccs_region", "chart"); chart.gui_input.connect(_text_input.bind(chart)); chart_panel.add_child(chart)
|
||||
var clerk_scroll := ScrollContainer.new(); clerk_scroll.custom_minimum_size.x = 430; workspace.add_child(clerk_scroll)
|
||||
var clerk := VBoxContainer.new(); clerk.size_flags_horizontal = Control.SIZE_EXPAND_FILL; clerk.add_theme_constant_override("separation", 8); clerk_scroll.add_child(clerk)
|
||||
var order_title := Label.new(); order_title.text = "Order Clerk"; Style.set_label(order_title, 21, Style.CYAN); clerk.add_child(order_title)
|
||||
search = LineEdit.new(); search.placeholder_text = "Search tests, treatments, and procedures"; search.text_submitted.connect(func(_value): CCSService.search_orders(search.text)); clerk.add_child(search)
|
||||
var find := Button.new(); find.text = "Search"; find.pressed.connect(func(): CCSService.search_orders(search.text)); clerk.add_child(find)
|
||||
search_results = VBoxContainer.new(); clerk.add_child(search_results)
|
||||
var active_title := Label.new(); active_title.text = "Active orders"; Style.set_label(active_title, 18, Style.TEXT); clerk.add_child(active_title)
|
||||
orders = VBoxContainer.new(); clerk.add_child(orders)
|
||||
var exam_title := Label.new(); exam_title.text = "Physical examination"; Style.set_label(exam_title, 18, Style.TEXT); clerk.add_child(exam_title)
|
||||
actions = VBoxContainer.new(); clerk.add_child(actions)
|
||||
time_row = HBoxContainer.new(); clerk.add_child(time_row)
|
||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
add_theme_constant_override("separation", 10)
|
||||
_build_header()
|
||||
_build_workspace()
|
||||
_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)
|
||||
_apply_theme()
|
||||
call_deferred("_update_responsive_layout")
|
||||
|
||||
|
||||
func _build_header() -> void:
|
||||
header_panel = PanelContainer.new()
|
||||
add_child(header_panel)
|
||||
var margin := MarginContainer.new()
|
||||
margin.add_theme_constant_override("margin_left", 12)
|
||||
margin.add_theme_constant_override("margin_right", 12)
|
||||
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.vertical = false
|
||||
header_layout.add_theme_constant_override("separation", 12)
|
||||
margin.add_child(header_layout)
|
||||
|
||||
var title_row := HBoxContainer.new()
|
||||
title_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
title_row.add_theme_constant_override("separation", 10)
|
||||
header_layout.add_child(title_row)
|
||||
header_icon_panel = PanelContainer.new()
|
||||
header_icon_panel.custom_minimum_size = Vector2(38, 38)
|
||||
title_row.add_child(header_icon_panel)
|
||||
var header_icon := TextureRect.new()
|
||||
header_icon.texture = ICON_STETHOSCOPE
|
||||
header_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
header_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
header_icon.custom_minimum_size = Vector2(22, 22)
|
||||
header_icon.modulate = Brand.SKY_BLUE
|
||||
header_icon_panel.add_child(header_icon)
|
||||
heading = Label.new()
|
||||
heading.text = "CCS Case"
|
||||
heading.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_set_label(heading, 18, Brand.text_color(true))
|
||||
title_row.add_child(heading)
|
||||
|
||||
toolbar = HFlowContainer.new()
|
||||
toolbar.size_flags_horizontal = Control.SIZE_SHRINK_END
|
||||
toolbar.custom_minimum_size.x = 440
|
||||
toolbar.add_theme_constant_override("h_separation", 8)
|
||||
toolbar.add_theme_constant_override("v_separation", 8)
|
||||
header_layout.add_child(toolbar)
|
||||
var smaller := _toolbar_button("A-", "Decrease case text size")
|
||||
smaller.pressed.connect(_zoom.bind(-CCSService.TEXT_SCALE_STEP))
|
||||
var larger := _toolbar_button("A+", "Increase case text size")
|
||||
larger.pressed.connect(_zoom.bind(CCSService.TEXT_SCALE_STEP))
|
||||
var clear_highlights := _toolbar_button("Clear", "Clear case highlights")
|
||||
clear_highlights.pressed.connect(_clear_highlights)
|
||||
report_button = _toolbar_button("Report", "Report this CCS case")
|
||||
report_button.pressed.connect(_report)
|
||||
app_feedback_button = _toolbar_button("Feedback", "Send general feedback or report a bug")
|
||||
app_feedback_button.pressed.connect(func(): main.open_feedback_dialog())
|
||||
theme_button = _toolbar_button("", "Switch color theme")
|
||||
theme_button.custom_minimum_size = Vector2(44, 38)
|
||||
UISoundService.set_button_sound(theme_button, "toggle")
|
||||
theme_button.pressed.connect(theme_service.toggle_theme)
|
||||
|
||||
|
||||
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(54, 38)
|
||||
button.add_theme_font_override("font", Brand.font)
|
||||
button.add_theme_font_size_override("font_size", 14)
|
||||
toolbar.add_child(button)
|
||||
toolbar_buttons.append(button)
|
||||
return button
|
||||
|
||||
|
||||
func _build_workspace() -> void:
|
||||
var page_scroll := ScrollContainer.new()
|
||||
page_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
page_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
add_child(page_scroll)
|
||||
workspace = BoxContainer.new()
|
||||
workspace.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
workspace.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
workspace.add_theme_constant_override("separation", 10)
|
||||
page_scroll.add_child(workspace)
|
||||
_build_chart_panel()
|
||||
_build_order_clerk()
|
||||
|
||||
|
||||
func _build_chart_panel() -> void:
|
||||
chart_panel = PanelContainer.new()
|
||||
chart_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
chart_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
workspace.add_child(chart_panel)
|
||||
var margin := MarginContainer.new()
|
||||
margin.add_theme_constant_override("margin_left", 18)
|
||||
margin.add_theme_constant_override("margin_right", 18)
|
||||
margin.add_theme_constant_override("margin_top", 16)
|
||||
margin.add_theme_constant_override("margin_bottom", 18)
|
||||
chart_panel.add_child(margin)
|
||||
var content := VBoxContainer.new()
|
||||
content.add_theme_constant_override("separation", 14)
|
||||
margin.add_child(content)
|
||||
|
||||
summary = _clinical_text("summary", 17)
|
||||
content.add_child(summary)
|
||||
vitals_flow = HFlowContainer.new()
|
||||
vitals_flow.add_theme_constant_override("h_separation", 10)
|
||||
vitals_flow.add_theme_constant_override("v_separation", 8)
|
||||
content.add_child(vitals_flow)
|
||||
content.add_child(HSeparator.new())
|
||||
_add_chart_section(content, "History", "history")
|
||||
history = _clinical_text("history", 16)
|
||||
content.add_child(history)
|
||||
_add_chart_section(content, "Appearance", "appearance")
|
||||
appearance = _clinical_text("appearance", 16)
|
||||
content.add_child(appearance)
|
||||
_add_chart_section(content, "Chart", "chart")
|
||||
chart = _clinical_text("chart", 16)
|
||||
content.add_child(chart)
|
||||
|
||||
|
||||
func _add_chart_section(parent: VBoxContainer, text_value: String, region: String) -> void:
|
||||
var label := Label.new()
|
||||
label.name = "%sHeading" % region.capitalize()
|
||||
label.text = text_value
|
||||
_set_label(label, 15, Brand.PURPLE)
|
||||
parent.add_child(label)
|
||||
section_labels.append(label)
|
||||
|
||||
|
||||
func _clinical_text(region: String, font_size: int) -> 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.add_theme_font_size_override("normal_font_size", font_size)
|
||||
label.add_theme_font_size_override("bold_font_size", font_size)
|
||||
label.set_meta("ccs_base_font_size", font_size)
|
||||
label.set_meta("ccs_region", region)
|
||||
label.gui_input.connect(_text_input.bind(label))
|
||||
return label
|
||||
|
||||
|
||||
func _build_order_clerk() -> void:
|
||||
clerk_panel = PanelContainer.new()
|
||||
clerk_panel.custom_minimum_size = Vector2(370, 0)
|
||||
clerk_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
workspace.add_child(clerk_panel)
|
||||
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", 12)
|
||||
margin.add_theme_constant_override("margin_bottom", 14)
|
||||
clerk_panel.add_child(margin)
|
||||
var clerk := VBoxContainer.new()
|
||||
clerk.add_theme_constant_override("separation", 8)
|
||||
margin.add_child(clerk)
|
||||
var order_title := Label.new()
|
||||
order_title.text = "Order Clerk"
|
||||
_set_label(order_title, 18, Brand.PURPLE)
|
||||
clerk.add_child(order_title)
|
||||
section_labels.append(order_title)
|
||||
var search_help := Label.new()
|
||||
search_help.text = "Search tests, treatments, and procedures"
|
||||
_set_label(search_help, 12, Brand.muted_color(true))
|
||||
search_help.set_meta("ccs_muted_label", true)
|
||||
clerk.add_child(search_help)
|
||||
|
||||
var search_row := HBoxContainer.new()
|
||||
search_row.add_theme_constant_override("separation", 6)
|
||||
clerk.add_child(search_row)
|
||||
search = LineEdit.new()
|
||||
search.placeholder_text = "Search..."
|
||||
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 order catalog"
|
||||
search_button.custom_minimum_size = Vector2(42, 42)
|
||||
search_button.pressed.connect(_submit_search)
|
||||
search_row.add_child(search_button)
|
||||
|
||||
search_results_panel = PanelContainer.new()
|
||||
search_results_panel.visible = false
|
||||
clerk.add_child(search_results_panel)
|
||||
var results_scroll := ScrollContainer.new()
|
||||
results_scroll.custom_minimum_size.y = 150
|
||||
search_results_panel.add_child(results_scroll)
|
||||
search_results = VBoxContainer.new()
|
||||
search_results.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
search_results.add_theme_constant_override("separation", 5)
|
||||
results_scroll.add_child(search_results)
|
||||
|
||||
var active_title := _clerk_heading("Active orders")
|
||||
clerk.add_child(active_title)
|
||||
orders = VBoxContainer.new()
|
||||
orders.add_theme_constant_override("separation", 5)
|
||||
clerk.add_child(orders)
|
||||
var exam_title := _clerk_heading("Physical examination")
|
||||
clerk.add_child(exam_title)
|
||||
actions = VBoxContainer.new()
|
||||
actions.add_theme_constant_override("separation", 5)
|
||||
clerk.add_child(actions)
|
||||
locations = HFlowContainer.new()
|
||||
locations.add_theme_constant_override("h_separation", 5)
|
||||
locations.add_theme_constant_override("v_separation", 5)
|
||||
clerk.add_child(locations)
|
||||
time_row = GridContainer.new()
|
||||
time_row.columns = 4
|
||||
time_row.add_theme_constant_override("h_separation", 5)
|
||||
time_row.add_theme_constant_override("v_separation", 5)
|
||||
clerk.add_child(time_row)
|
||||
for minutes in [1, 10, 60, 1440]:
|
||||
var button := Button.new(); button.text = "+%s" % ("1 day" if minutes == 1440 else "%d min" % minutes); button.pressed.connect(CCSService.send_command.bind("advance", "", minutes)); time_row.add_child(button)
|
||||
var reassess := Button.new(); reassess.text = "Interval history"; reassess.pressed.connect(CCSService.send_command.bind("interval_history", "", 0)); clerk.add_child(reassess)
|
||||
end_button = Button.new(); end_button.text = "End case"; Style.style_chip(end_button, Style.AMBER); end_button.pressed.connect(_end_case); clerk.add_child(end_button)
|
||||
leave_button = Button.new(); leave_button.text = "Leave case"; Style.style_chip(leave_button, Style.RED); leave_button.pressed.connect(_leave); clerk.add_child(leave_button)
|
||||
status = Label.new(); Style.set_label(status, 14, Style.RED); add_child(status)
|
||||
timer = Timer.new(); timer.wait_time = 1; timer.timeout.connect(_tick); add_child(timer)
|
||||
var button := Button.new()
|
||||
button.text = "+%s" % ("1 day" if minutes == 1440 else "%d min" % minutes)
|
||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
button.pressed.connect(CCSService.send_command.bind("advance", "", minutes))
|
||||
time_row.add_child(button)
|
||||
var reassess := Button.new()
|
||||
reassess.text = "Interval history"
|
||||
reassess.pressed.connect(CCSService.send_command.bind("interval_history", "", 0))
|
||||
clerk.add_child(reassess)
|
||||
end_button = Button.new()
|
||||
end_button.text = "End case"
|
||||
end_button.pressed.connect(_end_case)
|
||||
clerk.add_child(end_button)
|
||||
leave_button = Button.new()
|
||||
leave_button.text = "Leave case"
|
||||
leave_button.pressed.connect(_leave)
|
||||
clerk.add_child(leave_button)
|
||||
final_orders_notice = Label.new()
|
||||
final_orders_notice.visible = false
|
||||
final_orders_notice.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
clerk.add_child(final_orders_notice)
|
||||
clock = Label.new()
|
||||
clock.visible = false
|
||||
clerk.add_child(clock)
|
||||
status = Label.new()
|
||||
status.visible = false
|
||||
status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
clerk.add_child(status)
|
||||
|
||||
|
||||
func _clerk_heading(text_value: String) -> Label:
|
||||
var label := Label.new()
|
||||
label.text = text_value
|
||||
_set_label(label, 13, Brand.PURPLE)
|
||||
section_labels.append(label)
|
||||
return label
|
||||
|
||||
|
||||
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)
|
||||
if not theme_service.theme_changed.is_connected(_on_theme_changed): theme_service.theme_changed.connect(_on_theme_changed)
|
||||
report_dialog = preload("res://scripts/ui/ccs_report_dialog.gd").new(); add_child(report_dialog)
|
||||
report_dialog = preload("res://scripts/ui/ccs_report_dialog.gd").new()
|
||||
add_child(report_dialog)
|
||||
|
||||
|
||||
func on_show(payload: Variant = null) -> void:
|
||||
status.text = ""
|
||||
_set_status("")
|
||||
leaving = false
|
||||
leave_button.disabled = false
|
||||
if typeof(payload) == TYPE_STRING and not str(payload).is_empty(): CCSService.load_attempt(str(payload))
|
||||
_render(); timer.start()
|
||||
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 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
|
||||
heading.text = "CCS Case | %s" % str(attempt.get("location", "")).capitalize()
|
||||
if attempt.is_empty():
|
||||
return
|
||||
if str(attempt.get("state", "")) == "completed":
|
||||
main.show_screen("ccs_review", attempt)
|
||||
return
|
||||
heading.text = "CCS Case | %s" % _humanize_code(str(attempt.get("location", "")))
|
||||
_render_study_text()
|
||||
_clear(orders)
|
||||
for code in attempt.get("activeOrderCodes", []):
|
||||
var row := HBoxContainer.new(); orders.add_child(row)
|
||||
var label := Label.new(); label.text = str(code); label.size_flags_horizontal = Control.SIZE_EXPAND_FILL; row.add_child(label)
|
||||
var cancel := Button.new(); cancel.text = "Cancel"; cancel.pressed.connect(CCSService.send_command.bind("cancel_order", str(code), 0)); row.add_child(cancel)
|
||||
_clear(actions)
|
||||
for exam in attempt.get("availableExams", []):
|
||||
var button := Button.new(); button.text = str(exam.get("name", exam.get("code", "Exam"))); button.pressed.connect(CCSService.send_command.bind("exam", str(exam.get("code", "")), 0)); actions.add_child(button)
|
||||
for location in attempt.get("allowedLocations", []):
|
||||
var button := Button.new(); button.text = "Move to %s" % str(location).capitalize(); button.pressed.connect(CCSService.send_command.bind("location", str(location), 0)); actions.add_child(button)
|
||||
_render_vitals()
|
||||
_render_orders()
|
||||
_render_commands()
|
||||
var final_orders := str(attempt.get("state", "")) == "final_orders"
|
||||
end_button.text = "Submit final orders" if final_orders else "End case"
|
||||
time_row.visible = not final_orders
|
||||
final_orders_notice.visible = final_orders
|
||||
final_orders_notice.text = "Finalize or cancel patient-care orders, then submit."
|
||||
_tick()
|
||||
end_button.text = "Submit final orders" if str(attempt.get("state", "")) == "final_orders" else "End case"
|
||||
time_row.visible = str(attempt.get("state", "")) == "active"
|
||||
if str(attempt.get("state", "")) == "final_orders": status.text = "Case-end instructions: finalize or cancel orders, then submit."
|
||||
_apply_text_scale()
|
||||
|
||||
|
||||
func _render_vitals() -> void:
|
||||
_clear(vitals_flow)
|
||||
var vitals: Dictionary = CCSService.attempt.get("vitals", {})
|
||||
for key in _ordered_vital_keys(vitals):
|
||||
var chip := PanelContainer.new()
|
||||
chip.add_theme_stylebox_override("panel", _vital_chip_box())
|
||||
vitals_flow.add_child(chip)
|
||||
var row := HBoxContainer.new()
|
||||
row.add_theme_constant_override("separation", 7)
|
||||
chip.add_child(row)
|
||||
var icon := TextureRect.new()
|
||||
icon.texture = _vital_icon(key)
|
||||
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
icon.custom_minimum_size = Vector2(18, 18)
|
||||
icon.modulate = _vital_color(key)
|
||||
row.add_child(icon)
|
||||
var label := Label.new()
|
||||
label.text = "%s: %s" % [_vital_label(key), str(vitals[key])]
|
||||
_set_label(label, 13, Brand.text_color(_is_dark()))
|
||||
row.add_child(label)
|
||||
|
||||
|
||||
func _ordered_vital_keys(vitals: Dictionary) -> Array[String]:
|
||||
var ordered: Array[String] = []
|
||||
for key in ["temperature", "bloodPressure", "pulse", "respirations", "oxygenSaturation"]:
|
||||
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"
|
||||
_: return _humanize_code(key)
|
||||
|
||||
|
||||
func _vital_icon(key: String) -> Texture2D:
|
||||
match key:
|
||||
"temperature": return ICON_THERMOMETER
|
||||
"bloodPressure", "pulse": return ICON_HEART
|
||||
"respirations": return ICON_LUNGS
|
||||
_: return ICON_ACTIVITY
|
||||
|
||||
|
||||
func _vital_color(key: String) -> Color:
|
||||
match key:
|
||||
"temperature", "oxygenSaturation": return Brand.SKY_BLUE
|
||||
"bloodPressure", "pulse": return Color("fb7185")
|
||||
_: return Brand.PURPLE
|
||||
|
||||
|
||||
func _render_orders() -> void:
|
||||
_clear(orders)
|
||||
var active_codes: Array = CCSService.attempt.get("activeOrderCodes", [])
|
||||
if active_codes.is_empty():
|
||||
var empty := Label.new()
|
||||
empty.text = "No active orders"
|
||||
_set_label(empty, 12, Brand.muted_color(_is_dark()))
|
||||
orders.add_child(empty)
|
||||
return
|
||||
for raw_code in active_codes:
|
||||
var code := str(raw_code)
|
||||
var row := HBoxContainer.new()
|
||||
row.add_theme_constant_override("separation", 6)
|
||||
orders.add_child(row)
|
||||
var label := Label.new()
|
||||
label.text = _humanize_code(code)
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_set_label(label, 13, Brand.text_color(_is_dark()))
|
||||
row.add_child(label)
|
||||
var cancel := Button.new()
|
||||
cancel.text = "Cancel"
|
||||
cancel.pressed.connect(CCSService.send_command.bind("cancel_order", code, 0))
|
||||
_style_secondary_button(cancel)
|
||||
row.add_child(cancel)
|
||||
|
||||
|
||||
func _render_commands() -> void:
|
||||
_clear(actions)
|
||||
for exam in CCSService.attempt.get("availableExams", []):
|
||||
if typeof(exam) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var button := Button.new()
|
||||
button.text = str(exam.get("name", _humanize_code(str(exam.get("code", "Exam")))))
|
||||
button.pressed.connect(CCSService.send_command.bind("exam", str(exam.get("code", "")), 0))
|
||||
_style_secondary_button(button)
|
||||
actions.add_child(button)
|
||||
_clear(locations)
|
||||
for raw_location in CCSService.attempt.get("allowedLocations", []):
|
||||
var location := str(raw_location)
|
||||
var button := Button.new()
|
||||
button.text = "Move to %s" % _humanize_code(location)
|
||||
button.custom_minimum_size.x = 150
|
||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
button.pressed.connect(CCSService.send_command.bind("location", location, 0))
|
||||
_style_secondary_button(button)
|
||||
locations.add_child(button)
|
||||
_style_command_buttons()
|
||||
|
||||
|
||||
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)
|
||||
search_results_panel.visible = not CCSService.orders.is_empty()
|
||||
for order in CCSService.orders:
|
||||
var button := Button.new(); button.text = "%s (%s)" % [order.get("canonicalName", ""), order.get("category", "")]; button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
button.pressed.connect(_place_order.bind(str(order.get("code", "")))); search_results.add_child(button)
|
||||
_scale_controls(search_results)
|
||||
if typeof(order) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var button := Button.new()
|
||||
button.text = "%s (%s)" % [order.get("canonicalName", ""), _humanize_code(str(order.get("category", "")))]
|
||||
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
button.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
button.pressed.connect(_place_order.bind(str(order.get("code", ""))))
|
||||
_style_secondary_button(button)
|
||||
search_results.add_child(button)
|
||||
|
||||
|
||||
func _place_order(code: String) -> void:
|
||||
CCSService.send_command("order", code); search.text = ""; CCSService.orders = []; _render_search()
|
||||
CCSService.send_command("order", code)
|
||||
search.text = ""
|
||||
CCSService.orders = []
|
||||
_render_search()
|
||||
|
||||
|
||||
func _tick() -> void:
|
||||
var deadline := CCSService.deadline_unix()
|
||||
var final_orders := str(CCSService.attempt.get("state", "")) == "final_orders"
|
||||
clock.visible = final_orders and deadline > 0
|
||||
if deadline <= 0:
|
||||
clock.visible = false
|
||||
clock.text = ""
|
||||
return
|
||||
clock.visible = true
|
||||
var remaining := maxi(0, int(ceil(deadline - Time.get_unix_time_from_system())))
|
||||
clock.text = "%02d:%02d" % [floori(float(remaining) / 60.0), remaining % 60]
|
||||
if remaining == 0 and not CCSService.is_loading: CCSService.load_attempt(str(CCSService.attempt.get("id", "")))
|
||||
if final_orders:
|
||||
clock.text = "Final orders: %02d:%02d remaining" % [floori(float(remaining) / 60.0), remaining % 60]
|
||||
if remaining == 0 and not CCSService.is_loading:
|
||||
CCSService.load_attempt(str(CCSService.attempt.get("id", "")))
|
||||
|
||||
|
||||
func leave_case() -> void:
|
||||
_leave()
|
||||
|
||||
|
||||
func _leave() -> void:
|
||||
if leaving or CCSService.attempt.is_empty(): return
|
||||
if leaving or CCSService.attempt.is_empty():
|
||||
return
|
||||
leaving = true
|
||||
leave_button.disabled = true
|
||||
status.text = "Leaving case..."
|
||||
_set_status("Leaving case...", false)
|
||||
CCSService.abandon()
|
||||
|
||||
|
||||
func _on_abandoned() -> void:
|
||||
if not leaving: return
|
||||
if not leaving:
|
||||
return
|
||||
leaving = false
|
||||
main.show_screen("selection", "step3")
|
||||
|
||||
|
||||
func _request_failed(message: String) -> void:
|
||||
if not visible: return
|
||||
status.text = message
|
||||
if not visible:
|
||||
return
|
||||
_set_status(message)
|
||||
if leaving:
|
||||
leaving = false
|
||||
leave_button.disabled = false
|
||||
|
||||
|
||||
func _end_case() -> void:
|
||||
CCSService.send_command("exit_case" if str(CCSService.attempt.get("state", "")) == "final_orders" else "end_case")
|
||||
|
||||
func _clear(parent: Node) -> void:
|
||||
for child in parent.get_children(): child.queue_free()
|
||||
|
||||
func _vitals_text(vitals: Dictionary) -> String:
|
||||
var parts: Array[String] = []
|
||||
for key in vitals: parts.append("%s: %s" % [str(key), str(vitals[key])])
|
||||
return " | ".join(parts)
|
||||
func _clear(parent: Node) -> void:
|
||||
for child in parent.get_children():
|
||||
parent.remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
|
||||
func _chart_text(entries: Array) -> String:
|
||||
var lines: Array[String] = []
|
||||
for entry in entries: lines.append("T+%d [%s] %s" % [entry.get("simulatedMinute", 0), entry.get("category", "note"), entry.get("text", "")])
|
||||
for entry in entries:
|
||||
if typeof(entry) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var category := str(entry.get("category", "note"))
|
||||
var category_label := "In Progress" if category == "progress" else _humanize_code(category)
|
||||
lines.append("T+%d (%s): %s" % [entry.get("simulatedMinute", 0), category_label, entry.get("text", "")])
|
||||
return "\n\n".join(lines)
|
||||
|
||||
|
||||
func _summary_plain() -> String:
|
||||
var attempt := CCSService.attempt
|
||||
return "%s\n%s\n\nVitals %s" % [attempt.get("patientSummary", ""), attempt.get("reasonForEncounter", ""), _vitals_text(attempt.get("vitals", {}))]
|
||||
return "%s\n%s" % [attempt.get("patientSummary", ""), attempt.get("reasonForEncounter", "")]
|
||||
|
||||
func _chart_plain() -> String:
|
||||
var attempt := CCSService.attempt
|
||||
return "History\n%s\n\nAppearance\n%s\n\nChart\n%s" % [attempt.get("history", ""), attempt.get("appearance", ""), _chart_text(attempt.get("chart", []))]
|
||||
|
||||
func _render_study_text() -> void:
|
||||
var id := str(CCSService.attempt.get("id", ""))
|
||||
summary.text = CCSService.highlighted_text(id, "summary", _summary_plain())
|
||||
chart.text = CCSService.highlighted_text(id, "chart", _chart_plain())
|
||||
var attempt_id := str(CCSService.attempt.get("id", ""))
|
||||
summary.text = CCSService.highlighted_text(attempt_id, "summary", _summary_plain())
|
||||
history.text = CCSService.highlighted_text(attempt_id, "history", str(CCSService.attempt.get("history", "")))
|
||||
appearance.text = CCSService.highlighted_text(attempt_id, "appearance", str(CCSService.attempt.get("appearance", "")))
|
||||
chart.text = CCSService.highlighted_text(attempt_id, "chart", _chart_text(CCSService.attempt.get("chart", [])))
|
||||
|
||||
|
||||
func _text_input(event: InputEvent, label: RichTextLabel) -> void:
|
||||
var mouse := event as InputEventMouseButton
|
||||
if mouse != null and mouse.button_index == MOUSE_BUTTON_LEFT and not mouse.pressed:
|
||||
call_deferred("_commit_selection", label)
|
||||
|
||||
|
||||
func _commit_selection(label: RichTextLabel) -> void:
|
||||
var selected := label.get_selected_text().strip_edges()
|
||||
if selected.is_empty(): return
|
||||
var start := label.get_selection_from(); var end := label.get_selection_to()
|
||||
CCSService.toggle_highlight(str(CCSService.attempt.get("id", "")), str(label.get_meta("ccs_region", "")), mini(start, end), maxi(start, end))
|
||||
label.deselect(); _render_study_text()
|
||||
if selected.is_empty():
|
||||
return
|
||||
var start := label.get_selection_from()
|
||||
var end := label.get_selection_to()
|
||||
CCSService.toggle_highlight(
|
||||
str(CCSService.attempt.get("id", "")),
|
||||
str(label.get_meta("ccs_region", "")),
|
||||
mini(start, end),
|
||||
maxi(start, end)
|
||||
)
|
||||
label.deselect()
|
||||
_render_study_text()
|
||||
|
||||
|
||||
func _zoom(delta: float) -> void:
|
||||
CCSService.adjust_text_scale(delta); _apply_text_scale()
|
||||
CCSService.adjust_text_scale(delta)
|
||||
_apply_text_scale()
|
||||
|
||||
|
||||
func _apply_text_scale() -> void:
|
||||
var font_size := int(round(18.0 * CCSService.text_scale))
|
||||
for label in [summary, chart]:
|
||||
label.add_theme_font_size_override("normal_font_size", font_size); label.add_theme_font_size_override("bold_font_size", font_size)
|
||||
_scale_controls(workspace)
|
||||
for label in [summary, history, appearance, chart]:
|
||||
var base_size := int(label.get_meta("ccs_base_font_size", 16))
|
||||
var font_size := int(round(float(base_size) * 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 _scale_controls(node: Node) -> void:
|
||||
if node is Label or node is Button or node is LineEdit:
|
||||
if not node.has_meta("ccs_base_font_size"): node.set_meta("ccs_base_font_size", node.get_theme_font_size("font_size"))
|
||||
node.add_theme_font_size_override("font_size", int(round(float(node.get_meta("ccs_base_font_size")) * CCSService.text_scale)))
|
||||
for child in node.get_children(): _scale_controls(child)
|
||||
|
||||
func _clear_highlights() -> void:
|
||||
CCSService.clear_highlights(str(CCSService.attempt.get("id", ""))); _render_study_text()
|
||||
CCSService.clear_highlights(str(CCSService.attempt.get("id", "")))
|
||||
_render_study_text()
|
||||
|
||||
|
||||
func _report() -> void:
|
||||
report_dialog.show_for(str(CCSService.attempt.get("id", "")))
|
||||
|
||||
|
||||
func _report_submitted(_id: String) -> void:
|
||||
Style.style_chip(report_button, Style.RED); report_button.tooltip_text = "Report submitted; report another issue"
|
||||
report_button.tooltip_text = "Report submitted; report another issue"
|
||||
_style_outline_button(report_button, Brand.SUCCESS)
|
||||
|
||||
|
||||
func _set_status(message: String, is_error: bool = true) -> void:
|
||||
if status == null:
|
||||
return
|
||||
status.text = message
|
||||
status.visible = not message.is_empty()
|
||||
status.add_theme_color_override("font_color", Color("fb7185") if is_error else Brand.SKY_BLUE)
|
||||
|
||||
|
||||
func _humanize_code(value: String) -> String:
|
||||
return value.replace("_", " ").replace("-", " ").capitalize()
|
||||
|
||||
|
||||
func _update_responsive_layout() -> void:
|
||||
if workspace == null or header_layout == null:
|
||||
return
|
||||
var narrow := size.x > 0 and size.x < RESPONSIVE_BREAKPOINT
|
||||
workspace.vertical = narrow
|
||||
header_layout.vertical = narrow
|
||||
toolbar.custom_minimum_size.x = 0 if narrow else 440
|
||||
toolbar.size_flags_horizontal = Control.SIZE_EXPAND_FILL if narrow else Control.SIZE_SHRINK_END
|
||||
chart_panel.custom_minimum_size.x = 0 if narrow else 600
|
||||
clerk_panel.custom_minimum_size.x = 0 if narrow else 370
|
||||
clerk_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL if narrow else Control.SIZE_SHRINK_END
|
||||
queue_sort()
|
||||
|
||||
|
||||
func _on_theme_changed(_is_dark_mode: bool) -> void:
|
||||
_refresh_theme_button()
|
||||
call_deferred("_finish_theme_change")
|
||||
|
||||
func _refresh_theme_button() -> void:
|
||||
if theme_button == null: return
|
||||
theme_button.text = "☀" if theme_service.is_dark_mode() else "☾"
|
||||
theme_button.tooltip_text = "Switch to light mode" if theme_service.is_dark_mode() else "Switch to dark mode"
|
||||
Style.style_chip(theme_button, Style.CYAN)
|
||||
|
||||
func _finish_theme_change() -> void:
|
||||
_apply_theme()
|
||||
if visible and not CCSService.attempt.is_empty():
|
||||
_render()
|
||||
|
||||
|
||||
func _apply_theme() -> void:
|
||||
var dark := _is_dark()
|
||||
if header_panel == null:
|
||||
return
|
||||
header_panel.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
||||
chart_panel.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
||||
clerk_panel.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
||||
header_icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, Brand.PURPLE, 6, true))
|
||||
heading.add_theme_color_override("font_color", Brand.text_color(dark))
|
||||
for label in section_labels:
|
||||
label.add_theme_color_override("font_color", Brand.PURPLE if dark else Brand.ROYAL_BLUE)
|
||||
for node in find_children("*", "Label", true, false):
|
||||
var label := node as Label
|
||||
if label.has_meta("ccs_muted_label"):
|
||||
label.add_theme_color_override("font_color", Brand.muted_color(dark))
|
||||
for label in [summary, history, appearance, chart]:
|
||||
label.add_theme_color_override("default_color", Brand.text_color(dark))
|
||||
search.add_theme_font_override("font", Brand.font)
|
||||
search.add_theme_color_override("font_color", Brand.text_color(dark))
|
||||
search.add_theme_color_override("font_placeholder_color", Brand.muted_color(dark))
|
||||
search.add_theme_stylebox_override("normal", _compact_field_box(dark))
|
||||
search.add_theme_stylebox_override("focus", _compact_field_box(dark, true))
|
||||
search_results_panel.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
|
||||
for button in toolbar_buttons:
|
||||
_style_secondary_button(button)
|
||||
_style_icon_button(search_button)
|
||||
_style_outline_button(end_button, Brand.AMBER)
|
||||
_style_outline_button(leave_button, Color("fb7185"))
|
||||
for button in time_row.get_children():
|
||||
if button is Button:
|
||||
_style_secondary_button(button)
|
||||
Brand.set_theme_toggle_icon(theme_button, dark)
|
||||
theme_button.tooltip_text = "Switch to light mode" if dark else "Switch to dark mode"
|
||||
final_orders_notice.add_theme_color_override("font_color", Brand.AMBER)
|
||||
clock.add_theme_color_override("font_color", Brand.AMBER)
|
||||
_render_vitals()
|
||||
|
||||
|
||||
func _style_command_buttons() -> void:
|
||||
for container in [actions, locations]:
|
||||
for child in container.get_children():
|
||||
if child is Button:
|
||||
_style_secondary_button(child)
|
||||
var reassess := time_row.get_parent().get_child(time_row.get_index() + 1) as Button
|
||||
if reassess != null:
|
||||
_style_secondary_button(reassess)
|
||||
|
||||
|
||||
func _style_secondary_button(button: Button) -> void:
|
||||
var dark := _is_dark()
|
||||
button.add_theme_font_override("font", Brand.font)
|
||||
button.add_theme_font_size_override("font_size", 13)
|
||||
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", _compact_button_box(dark))
|
||||
button.add_theme_stylebox_override("hover", _compact_button_box(dark, true))
|
||||
button.add_theme_stylebox_override("pressed", _compact_button_box(dark, true))
|
||||
|
||||
|
||||
func _style_icon_button(button: Button) -> void:
|
||||
_style_secondary_button(button)
|
||||
button.add_theme_constant_override("icon_max_width", 20)
|
||||
var color := Brand.SKY_BLUE if _is_dark() else Brand.ROYAL_BLUE
|
||||
for color_name in ["icon_normal_color", "icon_hover_color", "icon_pressed_color", "icon_focus_color"]:
|
||||
button.add_theme_color_override(color_name, color)
|
||||
|
||||
|
||||
func _style_outline_button(button: Button, accent: Color) -> void:
|
||||
var dark := _is_dark()
|
||||
var normal := _compact_button_box(dark)
|
||||
normal.border_color = accent
|
||||
var hover := normal.duplicate() as StyleBoxFlat
|
||||
hover.bg_color = Color(accent, 0.1)
|
||||
button.add_theme_font_override("font", Brand.font)
|
||||
button.add_theme_font_size_override("font_size", 13)
|
||||
button.add_theme_color_override("font_color", accent)
|
||||
button.add_theme_color_override("font_hover_color", accent)
|
||||
button.add_theme_stylebox_override("normal", normal)
|
||||
button.add_theme_stylebox_override("hover", hover)
|
||||
button.add_theme_stylebox_override("pressed", hover)
|
||||
|
||||
|
||||
func _compact_field_box(dark: bool, focused: bool = false) -> StyleBoxFlat:
|
||||
var box := StyleBoxFlat.new()
|
||||
box.bg_color = Color("0d172a") if dark else Brand.LIGHT_SURFACE
|
||||
box.border_color = Brand.ROYAL_BLUE if focused else (Color("33415d") if dark else Brand.LIGHT_BORDER)
|
||||
box.set_border_width_all(1)
|
||||
box.set_corner_radius_all(6)
|
||||
box.content_margin_left = 12
|
||||
box.content_margin_right = 12
|
||||
box.content_margin_top = 8
|
||||
box.content_margin_bottom = 8
|
||||
return box
|
||||
|
||||
|
||||
func _compact_button_box(dark: bool, hover: bool = false) -> StyleBoxFlat:
|
||||
var box := _compact_field_box(dark, hover)
|
||||
box.bg_color = Color("111b30") if dark else Brand.LIGHT_SURFACE
|
||||
return box
|
||||
|
||||
|
||||
func _vital_chip_box() -> StyleBoxFlat:
|
||||
var dark := _is_dark()
|
||||
var box := StyleBoxFlat.new()
|
||||
box.bg_color = Color("111b30") if dark else Color("f8faff")
|
||||
box.border_color = Color("33415d") if dark else Brand.LIGHT_BORDER
|
||||
box.set_border_width_all(1)
|
||||
box.set_corner_radius_all(6)
|
||||
box.content_margin_left = 10
|
||||
box.content_margin_right = 10
|
||||
box.content_margin_top = 8
|
||||
box.content_margin_bottom = 8
|
||||
return box
|
||||
|
||||
|
||||
func _set_label(label: Label, font_size: int, color: Color) -> void:
|
||||
label.add_theme_font_override("font", Brand.font)
|
||||
label.add_theme_font_size_override("font_size", font_size)
|
||||
label.add_theme_color_override("font_color", color)
|
||||
|
||||
|
||||
func _is_dark() -> bool:
|
||||
return theme_service != null and theme_service.is_dark_mode()
|
||||
|
||||
Reference in New Issue
Block a user