Compare commits
7
Commits
948b177eee
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b98ed82619 | ||
|
|
6038b816b7 | ||
|
|
92006372da | ||
|
|
2d91487c6a | ||
|
|
26c29df4ad | ||
|
|
ea6ba54666 | ||
|
|
f05a6148fb |
No files matched your search
Binary file not shown.
@@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://1sjxuy7kgxy7"
|
||||||
|
path="res://.godot/imported/link.svg-f8724c7cad57bde98ee0cda284f3ebbb.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/vector/status/link.svg"
|
||||||
|
dest_files=["res://.godot/imported/link.svg-f8724c7cad57bde98ee0cda284f3ebbb.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c3kgbws2ejds6"
|
||||||
|
path="res://.godot/imported/rov.svg-85cd68e18cc64f6002858c870af4fd81.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/vector/status/rov.svg"
|
||||||
|
dest_files=["res://.godot/imported/rov.svg-85cd68e18cc64f6002858c870af4fd81.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bc2m1gknloa5i"
|
||||||
|
path="res://.godot/imported/stationkeep.svg-ba46234e5cd35725d5ce1d66d82e2fa0.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/vector/status/stationkeep.svg"
|
||||||
|
dest_files=["res://.godot/imported/stationkeep.svg-ba46234e5cd35725d5ce1d66d82e2fa0.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/camera_layout_frame.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="CameraSlot" type="VBoxContainer"]
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="Header" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "CAMERA / PRIMARY"
|
||||||
|
theme_override_font_sizes/font_size = 20
|
||||||
|
|
||||||
|
[node name="State" type="Label" parent="Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "NO STREAM"
|
||||||
|
|
||||||
|
[node name="Frame" type="AspectRatioContainer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
ratio = 1.7777778
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Placeholder" type="PanelContainer" parent="Frame"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Frame/Placeholder"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "16:9 CAMERA\nReserved video surface"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" path="res://components/selectable_container.tscn" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/console_panel.gd" id="2"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/console_session.tscn" id="3"]
|
||||||
|
|
||||||
|
[node name="ConsolePanel" instance=ExtResource("1")]
|
||||||
|
custom_minimum_size = Vector2(720, 420)
|
||||||
|
script = ExtResource("2")
|
||||||
|
title = "CONSOLE"
|
||||||
|
focus_key = 0
|
||||||
|
initial_focus = NodePath("Tabs/ROV/Input/Command")
|
||||||
|
|
||||||
|
[node name="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||||
|
label_settings = null
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "CONSOLE"
|
||||||
|
|
||||||
|
[node name="Tabs" type="TabContainer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
current_tab = 0
|
||||||
|
|
||||||
|
[node name="ROV" parent="Tabs" instance=ExtResource("3")]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Host" parent="Tabs" instance=ExtResource("3")]
|
||||||
|
layout_mode = 2
|
||||||
|
session_id = "host"
|
||||||
|
tab_label = "Host"
|
||||||
|
shortcut = 4194337
|
||||||
|
prompt = "host $"
|
||||||
|
introduction = "Host shell disconnected."
|
||||||
|
|
||||||
|
[node name="Logs" parent="Tabs" instance=ExtResource("3")]
|
||||||
|
layout_mode = 2
|
||||||
|
session_id = "logs"
|
||||||
|
tab_label = "Logs"
|
||||||
|
shortcut = 4194338
|
||||||
|
read_only = true
|
||||||
|
introduction = "ROV journal stream disconnected."
|
||||||
|
|
||||||
|
[node name="Server" parent="Tabs" instance=ExtResource("3")]
|
||||||
|
layout_mode = 2
|
||||||
|
session_id = "server"
|
||||||
|
tab_label = "Server"
|
||||||
|
shortcut = 4194339
|
||||||
|
prompt = "server >"
|
||||||
|
introduction = "Server shell disconnected."
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/console_session.gd" id="1"]
|
||||||
|
|
||||||
|
[ext_resource type="Font" path="res://themes/console_mono.tres" id="2_mono"]
|
||||||
|
|
||||||
|
[node name="ConsoleSession" type="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 6
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Status" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Connection" type="Label" parent="Status"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "Not connected"
|
||||||
|
|
||||||
|
[node name="Follow" type="CheckButton" parent="Status"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "Follow"
|
||||||
|
button_pressed = true
|
||||||
|
|
||||||
|
[node name="Clear" type="Button" parent="Status"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "Clear"
|
||||||
|
|
||||||
|
[node name="Output" type="RichTextLabel" parent="."]
|
||||||
|
custom_minimum_size = Vector2(0, 160)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
focus_mode = 2
|
||||||
|
theme_override_fonts/normal_font = ExtResource("2_mono")
|
||||||
|
theme_override_font_sizes/normal_font_size = 18
|
||||||
|
bbcode_enabled = false
|
||||||
|
scroll_following = true
|
||||||
|
selection_enabled = true
|
||||||
|
context_menu_enabled = true
|
||||||
|
|
||||||
|
[node name="Filter" type="HBoxContainer" parent="."]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Filter"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Filter"
|
||||||
|
|
||||||
|
[node name="Text" type="LineEdit" parent="Filter"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_fonts/font = ExtResource("2_mono")
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
placeholder_text = "Filter log text"
|
||||||
|
|
||||||
|
[node name="Input" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Prompt" type="Label" parent="Input"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_fonts/font = ExtResource("2_mono")
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "rov $"
|
||||||
|
|
||||||
|
[node name="Command" type="LineEdit" parent="Input"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_fonts/font = ExtResource("2_mono")
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
placeholder_text = "Enter command…"
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
[gd_scene format=3 uid="uid://b75nt1kn2sobp"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://dnafkcowopwod" path="res://scripts/environment_panel.gd" id="1"]
|
||||||
|
[ext_resource type="Script" uid="uid://cbobdfddbbddo" path="res://scripts/depth_scale.gd" id="2"]
|
||||||
|
|
||||||
|
[node name="EnvironmentPanel" type="PanelContainer" unique_id=1120688022]
|
||||||
|
custom_minimum_size = Vector2(360, 240)
|
||||||
|
offset_right = 360.0
|
||||||
|
offset_bottom = 240.0
|
||||||
|
theme_type_variation = &"VehicleBackground"
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Margin" type="MarginContainer" parent="." unique_id=1435834688]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_left = 16
|
||||||
|
theme_override_constants/margin_top = 10
|
||||||
|
theme_override_constants/margin_right = 16
|
||||||
|
theme_override_constants/margin_bottom = 10
|
||||||
|
|
||||||
|
[node name="Body" type="VBoxContainer" parent="Margin" unique_id=826880208]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 6
|
||||||
|
|
||||||
|
[node name="Header" type="HBoxContainer" parent="Margin/Body" unique_id=1188782081]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Margin/Body/Header" unique_id=1435423541]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "ENVIRONMENT"
|
||||||
|
|
||||||
|
[node name="State" type="Label" parent="Margin/Body/Header" unique_id=1387248588]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"InstrumentMuted"
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "NO TELEMETRY"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="Measurements" type="HBoxContainer" parent="Margin/Body" unique_id=354890970]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/separation = 16
|
||||||
|
|
||||||
|
[node name="Readouts" type="VBoxContainer" parent="Margin/Body/Measurements" unique_id=766009790]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="DepthTitle" type="Label" parent="Margin/Body/Measurements/Readouts" unique_id=1777992876]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"Label"
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "DEPTH"
|
||||||
|
|
||||||
|
[node name="DepthValue" type="Label" parent="Margin/Body/Measurements/Readouts" unique_id=1937430512]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 40
|
||||||
|
text = "— m"
|
||||||
|
|
||||||
|
[node name="DepthReference" type="Label" parent="Margin/Body/Measurements/Readouts" unique_id=1946013161]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"InstrumentMuted"
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "/ 1000 m"
|
||||||
|
|
||||||
|
[node name="Spacer" type="Control" parent="Margin/Body/Measurements/Readouts" unique_id=1016695424]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Supporting" type="HBoxContainer" parent="Margin/Body/Measurements/Readouts" unique_id=1473880932]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="Pressure" type="VBoxContainer" parent="Margin/Body/Measurements/Readouts/Supporting" unique_id=1964987657]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="PressureTitle" type="Label" parent="Margin/Body/Measurements/Readouts/Supporting/Pressure" unique_id=1727076495]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"Label"
|
||||||
|
theme_override_font_sizes/font_size = 13
|
||||||
|
text = "PRESSURE"
|
||||||
|
|
||||||
|
[node name="PressureValue" type="Label" parent="Margin/Body/Measurements/Readouts/Supporting/Pressure" unique_id=1479127186]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 22
|
||||||
|
text = "— MPa"
|
||||||
|
|
||||||
|
[node name="PressureReference" type="Label" parent="Margin/Body/Measurements/Readouts/Supporting/Pressure" unique_id=1835332085]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"InstrumentMuted"
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "/ 10 MPa"
|
||||||
|
|
||||||
|
[node name="Temperature" type="VBoxContainer" parent="Margin/Body/Measurements/Readouts/Supporting" unique_id=1827823179]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Margin/Body/Measurements/Readouts/Supporting/Temperature" unique_id=1829138035]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 13
|
||||||
|
text = "WATER TEMP"
|
||||||
|
|
||||||
|
[node name="TemperatureValue" type="Label" parent="Margin/Body/Measurements/Readouts/Supporting/Temperature" unique_id=1337672100]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 22
|
||||||
|
text = "— °C"
|
||||||
|
|
||||||
|
[node name="Scale" type="VBoxContainer" parent="Margin/Body/Measurements" unique_id=1235543623]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="DepthScale" type="Control" parent="Margin/Body/Measurements/Scale" unique_id=223719936]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(74, 140)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
tooltip_text = "Shallow depths expanded: log(1 + depth / knee). Includes zero."
|
||||||
|
script = ExtResource("2")
|
||||||
|
|
||||||
|
[node name="Caption" type="Label" parent="Margin/Body/Measurements/Scale" unique_id=1491620694]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"InstrumentMuted"
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "LOG(n) -1"
|
||||||
|
horizontal_alignment = 1
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
[gd_scene format=3 uid="uid://dscdhba74otou"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://bqrapdh6yddl8" path="res://scripts/layout_canvas.gd" id="1"]
|
||||||
|
[ext_resource type="Script" uid="uid://cwbk5g4yhdfd6" path="res://scripts/layout_edit_overlay.gd" id="2"]
|
||||||
|
|
||||||
|
[node name="LayoutCanvas" type="Control" unique_id=325333537]
|
||||||
|
clip_contents = true
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
script = ExtResource("1")
|
||||||
|
legacy_config_path = null
|
||||||
|
|
||||||
|
[node name="EditOverlay" type="Control" parent="." unique_id=162710218]
|
||||||
|
visible = false
|
||||||
|
z_index = 120
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
mouse_filter = 2
|
||||||
|
script = ExtResource("2")
|
||||||
|
|
||||||
|
[node name="Actions" type="HBoxContainer" parent="EditOverlay" unique_id=846269020]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 0
|
||||||
|
offset_right = 112.0
|
||||||
|
offset_bottom = 36.0
|
||||||
|
theme_override_constants/separation = 2
|
||||||
|
|
||||||
|
[node name="Settings" type="Button" parent="EditOverlay/Actions" unique_id=1387368158]
|
||||||
|
custom_minimum_size = Vector2(36, 36)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Settings (not available yet)"
|
||||||
|
disabled = true
|
||||||
|
text = "⚙"
|
||||||
|
|
||||||
|
[node name="Resize" type="Button" parent="EditOverlay/Actions" unique_id=1301115821]
|
||||||
|
custom_minimum_size = Vector2(36, 36)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Drag to resize"
|
||||||
|
text = "↘"
|
||||||
|
|
||||||
|
[node name="Delete" type="Button" parent="EditOverlay/Actions" unique_id=1936216385]
|
||||||
|
custom_minimum_size = Vector2(36, 36)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Remove panel from layout"
|
||||||
|
text = "×"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/layout_item.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="LayoutItem" type="Control"]
|
||||||
|
layout_mode = 0
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Background" type="Panel" parent="."]
|
||||||
|
layout_mode = 0
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_type_variation = &"LayoutItemBackground"
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/layout_library.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="LayoutLibrary" type="PanelContainer"]
|
||||||
|
visible = false
|
||||||
|
z_index = 300
|
||||||
|
layout_mode = 1
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = -424.0
|
||||||
|
offset_top = 80.0
|
||||||
|
offset_right = -24.0
|
||||||
|
offset_bottom = 720.0
|
||||||
|
theme_type_variation = &"VehicleBackground"
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Margin" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_left = 16
|
||||||
|
theme_override_constants/margin_right = 16
|
||||||
|
theme_override_constants/margin_top = 12
|
||||||
|
theme_override_constants/margin_bottom = 12
|
||||||
|
|
||||||
|
[node name="Body" type="VBoxContainer" parent="Margin"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="Header" type="HBoxContainer" parent="Margin/Body"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Margin/Body/Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "ELEMENT LIBRARY"
|
||||||
|
theme_override_font_sizes/font_size = 20
|
||||||
|
|
||||||
|
[node name="Close" type="Button" parent="Margin/Body/Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "×"
|
||||||
|
tooltip_text = "Close library"
|
||||||
|
|
||||||
|
[node name="List" type="ItemList" parent="Margin/Body"]
|
||||||
|
custom_minimum_size = Vector2(0, 180)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_type_variation = &"LayoutLibraryList"
|
||||||
|
same_column_width = true
|
||||||
|
fixed_icon_size = Vector2i(24, 24)
|
||||||
|
|
||||||
|
[node name="Description" type="Label" parent="Margin/Body"]
|
||||||
|
custom_minimum_size = Vector2(0, 78)
|
||||||
|
layout_mode = 2
|
||||||
|
autowrap_mode = 3
|
||||||
|
|
||||||
|
[node name="Size" type="Label" parent="Margin/Body"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Status" type="Label" parent="Margin/Body"]
|
||||||
|
layout_mode = 2
|
||||||
|
autowrap_mode = 3
|
||||||
|
|
||||||
|
[node name="Add" type="Button" parent="Margin/Body"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Add element"
|
||||||
|
tooltip_text = "Place in the first available space, using the minimum size if needed"
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/layout_overview.gd" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/layout_grid.gd" id="2"]
|
||||||
|
|
||||||
|
[ext_resource type="Shader" path="res://shaders/layout_grid.gdshader" id="3"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_library.tscn" id="library"]
|
||||||
|
|
||||||
|
[sub_resource type="ShaderMaterial" id="GridMaterial"]
|
||||||
|
resource_local_to_scene = true
|
||||||
|
shader = ExtResource("3")
|
||||||
|
|
||||||
|
[node name="LayoutOverview" type="Control"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Dashboard" type="Control" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="Grid" type="ColorRect" parent="Dashboard"]
|
||||||
|
visible = false
|
||||||
|
material = SubResource("GridMaterial")
|
||||||
|
z_index = 100
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 0
|
||||||
|
script = ExtResource("2")
|
||||||
|
|
||||||
|
[node name="Toolbar" type="HBoxContainer" parent="."]
|
||||||
|
visible = false
|
||||||
|
z_index = 200
|
||||||
|
layout_mode = 1
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = 24.0
|
||||||
|
offset_top = 16.0
|
||||||
|
offset_right = -24.0
|
||||||
|
offset_bottom = 60.0
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 20
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Toolbar"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_font_sizes/font_size = 20
|
||||||
|
text = "LAYOUT OVERVIEW"
|
||||||
|
|
||||||
|
[node name="Hint" type="Label" parent="Toolbar"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
text = "Esc to return"
|
||||||
|
|
||||||
|
[node name="Add" type="Button" parent="Toolbar"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Add element"
|
||||||
|
|
||||||
|
[node name="Reset" type="Button" parent="Toolbar"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Reset layout"
|
||||||
|
|
||||||
|
[node name="Return" type="Button" parent="Toolbar"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Return to dashboard"
|
||||||
|
|
||||||
|
[node name="Library" parent="." instance=ExtResource("library")]
|
||||||
|
layout_mode = 1
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
[gd_scene format=3 uid="uid://u014ihs685mq"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://btaf1ke22k5ij" path="res://scripts/layout_slot.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="LayoutSlot" type="PanelContainer" unique_id=241401501]
|
||||||
|
custom_minimum_size = Vector2(0, 100)
|
||||||
|
theme_type_variation = &"VehicleBackground"
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Margin" type="MarginContainer" parent="." unique_id=1948608899]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_left = 16
|
||||||
|
theme_override_constants/margin_top = 12
|
||||||
|
theme_override_constants/margin_right = 16
|
||||||
|
theme_override_constants/margin_bottom = 12
|
||||||
|
|
||||||
|
[node name="Text" type="VBoxContainer" parent="Margin" unique_id=764454736]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Margin/Text" unique_id=806114057]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 20
|
||||||
|
text = "RESERVED"
|
||||||
|
|
||||||
|
[node name="Description" type="Label" parent="Margin/Text" unique_id=592619421]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
autowrap_mode = 3
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/link_panel.gd" id="1"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/link_rate.tscn" id="2"]
|
||||||
|
[ext_resource type="Texture2D" path="res://assets/vector/status/link.svg" id="3"]
|
||||||
|
|
||||||
|
[node name="LinkPanel" type="PanelContainer"]
|
||||||
|
custom_minimum_size = Vector2(360, 240)
|
||||||
|
offset_right = 360.0
|
||||||
|
offset_bottom = 240.0
|
||||||
|
theme_type_variation = &"VehicleBackground"
|
||||||
|
script = ExtResource("1")
|
||||||
|
link_icon = ExtResource("3")
|
||||||
|
|
||||||
|
[node name="Backdrop" type="Control" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
clip_contents = true
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="LinkIcon" type="TextureRect" parent="Backdrop"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchor_left = 0.65
|
||||||
|
anchor_right = 0.65
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = -90.0
|
||||||
|
offset_right = 90.0
|
||||||
|
offset_top = -90.0
|
||||||
|
offset_bottom = 90.0
|
||||||
|
rotation = -0.20943952
|
||||||
|
pivot_offset = Vector2(90, 90)
|
||||||
|
mouse_filter = 2
|
||||||
|
texture = ExtResource("3")
|
||||||
|
expand_mode = 1
|
||||||
|
stretch_mode = 5
|
||||||
|
|
||||||
|
[node name="Margin" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_left = 16
|
||||||
|
theme_override_constants/margin_right = 16
|
||||||
|
theme_override_constants/margin_top = 10
|
||||||
|
theme_override_constants/margin_bottom = 10
|
||||||
|
|
||||||
|
[node name="Body" type="VBoxContainer" parent="Margin"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="Header" type="HBoxContainer" parent="Margin/Body"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Margin/Body/Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "LINK"
|
||||||
|
|
||||||
|
[node name="State" type="Label" parent="Margin/Body/Header"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "NO TELEMETRY"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="Rates" type="HBoxContainer" parent="Margin/Body"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/separation = 16
|
||||||
|
|
||||||
|
[node name="Downlink" parent="Margin/Body/Rates" instance=ExtResource("2")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="Uplink" parent="Margin/Body/Rates" instance=ExtResource("2")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
direction = "↑ UPLINK"
|
||||||
|
|
||||||
|
[node name="Loss" type="HBoxContainer" parent="Margin/Body"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Margin/Body/Loss"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "PACKET LOSS"
|
||||||
|
|
||||||
|
[node name="LossValue" type="Label" parent="Margin/Body/Loss"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 20
|
||||||
|
text = "—"
|
||||||
|
|
||||||
|
[node name="LossWindow" type="Label" parent="Margin/Body/Loss"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"LinkMuted"
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "10 s window"
|
||||||
|
horizontal_alignment = 2
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/link_rate.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="LinkRate" type="Control"]
|
||||||
|
custom_minimum_size = Vector2(140, 148)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Readouts" type="VBoxContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = -70.0
|
||||||
|
offset_right = 70.0
|
||||||
|
offset_top = -44.0
|
||||||
|
offset_bottom = 44.0
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="Direction" type="Label" parent="Readouts"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"LinkRateNormal"
|
||||||
|
theme_override_font_sizes/font_size = 15
|
||||||
|
text = "↓ DOWNLINK"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Value" type="Label" parent="Readouts"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 32
|
||||||
|
text = "—"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Units" type="Label" parent="Readouts"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "Gbps"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Capacity" type="Label" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = -62.0
|
||||||
|
offset_right = 62.0
|
||||||
|
offset_top = -23.0
|
||||||
|
offset_bottom = -3.0
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "10 Gbps max"
|
||||||
|
horizontal_alignment = 1
|
||||||
@@ -0,0 +1,332 @@
|
|||||||
|
[gd_scene format=3 uid="uid://dvljqi38sjimi"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://dh2vnk35potq3" path="res://scripts/motion_panel.gd" id="1"]
|
||||||
|
[ext_resource type="Script" uid="uid://1dn362pyibkx" path="res://scripts/motion_instrument.gd" id="2"]
|
||||||
|
|
||||||
|
[node name="MotionPanel" type="Control" unique_id=660733185]
|
||||||
|
custom_minimum_size = Vector2(600, 410)
|
||||||
|
custom_maximum_size = Vector2(600, 410)
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 0
|
||||||
|
offset_right = 600.0
|
||||||
|
offset_bottom = 440.0
|
||||||
|
tooltip_text = "Orientation: roll right side down, pitch nose up, heading clockwise from north.
|
||||||
|
Body axes: X forward, Y right, Z down. Acceleration excludes gravity."
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Background" type="Panel" parent="." unique_id=144412302]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_type_variation = &"MotionBackground"
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=246886683]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 16.0
|
||||||
|
offset_top = 8.0
|
||||||
|
offset_right = -16.0
|
||||||
|
offset_bottom = -8.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="GridContainer" type="GridContainer" parent="VBoxContainer" unique_id=682224599]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/h_separation = 8
|
||||||
|
columns = 3
|
||||||
|
|
||||||
|
[node name="Attitude" type="VBoxContainer" parent="VBoxContainer/GridContainer" unique_id=200553262]
|
||||||
|
custom_minimum_size = Vector2(244, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/separation = 2
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="HBoxContainer" parent="VBoxContainer/GridContainer/Attitude" unique_id=1592037194]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer/GridContainer/Attitude/MarginContainer" unique_id=678832939]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "ATTITUDE deg"
|
||||||
|
|
||||||
|
[node name="DialAspect" type="MarginContainer" parent="VBoxContainer/GridContainer/Attitude" unique_id=1404917279]
|
||||||
|
custom_minimum_size = Vector2(0, 264)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
|
||||||
|
[node name="Horizon" type="Control" parent="VBoxContainer/GridContainer/Attitude/DialAspect" unique_id=1698568859]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
script = ExtResource("2")
|
||||||
|
|
||||||
|
[node name="OrientationReadouts" type="HBoxContainer" parent="VBoxContainer/GridContainer/Attitude" unique_id=185739847]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="Roll" type="Label" parent="VBoxContainer/GridContainer/Attitude/OrientationReadouts" unique_id=818199825]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "R —°"
|
||||||
|
|
||||||
|
[node name="Pitch" type="Label" parent="VBoxContainer/GridContainer/Attitude/OrientationReadouts" unique_id=25151996]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "P —°"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="HeadingTape" type="Control" parent="VBoxContainer/GridContainer/Attitude" unique_id=736154972]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 40)
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
script = ExtResource("2")
|
||||||
|
instrument = 1
|
||||||
|
|
||||||
|
[node name="Heading" type="Label" parent="VBoxContainer/GridContainer/Attitude" unique_id=1630510362]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "H —"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="CategorySeparator" type="VSeparator" parent="VBoxContainer/GridContainer" unique_id=1838523344]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/separation = 1
|
||||||
|
theme_type_variation = &"MotionVSeparator"
|
||||||
|
|
||||||
|
[node name="Acceleration" type="VBoxContainer" parent="VBoxContainer/GridContainer" unique_id=1201786358]
|
||||||
|
custom_minimum_size = Vector2(307, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/separation = 2
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="HBoxContainer" parent="VBoxContainer/GridContainer/Acceleration" unique_id=1329156619]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer/GridContainer/Acceleration/MarginContainer" unique_id=2085073917]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "ACCEL m/s²"
|
||||||
|
|
||||||
|
[node name="FullScale" type="Label" parent="VBoxContainer/GridContainer/Acceleration/MarginContainer" unique_id=248494772]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "±5.0"
|
||||||
|
|
||||||
|
[node name="Limit" type="Label" parent="VBoxContainer/GridContainer/Acceleration/MarginContainer" unique_id=1467570655]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"MotionWarning"
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "LIMIT"
|
||||||
|
|
||||||
|
[node name="Plots" type="HBoxContainer" parent="VBoxContainer/GridContainer/Acceleration" unique_id=127821710]
|
||||||
|
custom_minimum_size = Vector2(0, 264)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="AccelerationPlot" type="Control" parent="VBoxContainer/GridContainer/Acceleration/Plots" unique_id=383346065 node_paths=PackedStringArray("radius_source")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
script = ExtResource("2")
|
||||||
|
instrument = 2
|
||||||
|
radius_source = NodePath("../../../Attitude/DialAspect/Horizon")
|
||||||
|
|
||||||
|
[node name="VerticalAxis" type="Control" parent="VBoxContainer/GridContainer/Acceleration/Plots" unique_id=1631611633]
|
||||||
|
custom_minimum_size = Vector2(24, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Axis" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Plots/VerticalAxis" unique_id=1652971232]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_right = 24.0
|
||||||
|
offset_bottom = 26.0
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "−Z"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="VerticalAcceleration" type="Control" parent="VBoxContainer/GridContainer/Acceleration/Plots/VerticalAxis" unique_id=786328417 node_paths=PackedStringArray("radius_source")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
mouse_filter = 2
|
||||||
|
script = ExtResource("2")
|
||||||
|
instrument = 3
|
||||||
|
radius_source = NodePath("../../AccelerationPlot")
|
||||||
|
|
||||||
|
[node name="Readouts" type="HBoxContainer" parent="VBoxContainer/GridContainer/Acceleration" unique_id=1041397004]
|
||||||
|
custom_minimum_size = Vector2(260, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="X" type="HBoxContainer" parent="VBoxContainer/GridContainer/Acceleration/Readouts" unique_id=1850756267]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="AxisX" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Readouts/X" unique_id=1279485247]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "X"
|
||||||
|
|
||||||
|
[node name="AccelX" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Readouts/X" unique_id=528426129]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "—"
|
||||||
|
|
||||||
|
[node name="Y" type="HBoxContainer" parent="VBoxContainer/GridContainer/Acceleration/Readouts" unique_id=1540446279]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="AxisY" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Readouts/Y" unique_id=1789229948]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "Y"
|
||||||
|
|
||||||
|
[node name="AccelY" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Readouts/Y" unique_id=1811878137]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "—"
|
||||||
|
|
||||||
|
[node name="Z" type="HBoxContainer" parent="VBoxContainer/GridContainer/Acceleration/Readouts" unique_id=449449369]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="AxisZ" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Readouts/Z" unique_id=1812289041]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "Z"
|
||||||
|
|
||||||
|
[node name="AccelZ" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Readouts/Z" unique_id=648512841]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "—"
|
||||||
|
|
||||||
|
[node name="MomentumSeparator" type="HSeparator" parent="VBoxContainer/GridContainer/Acceleration" unique_id=498814282]
|
||||||
|
custom_minimum_size = Vector2(0, 12)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
theme_type_variation = &"MotionHSeparator"
|
||||||
|
|
||||||
|
[node name="MomentumTitle" type="HBoxContainer" parent="VBoxContainer/GridContainer/Acceleration" unique_id=1176164218]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="VBoxContainer/GridContainer/Acceleration/MomentumTitle" unique_id=1226461986]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "ANG. MOMENTUM"
|
||||||
|
|
||||||
|
[node name="Units" type="Label" parent="VBoxContainer/GridContainer/Acceleration/MomentumTitle" unique_id=1830199918]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "kg·m²/s"
|
||||||
|
|
||||||
|
[node name="Momentum" type="HBoxContainer" parent="VBoxContainer/GridContainer/Acceleration" unique_id=276201290]
|
||||||
|
custom_minimum_size = Vector2(260, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="X" type="HBoxContainer" parent="VBoxContainer/GridContainer/Acceleration/Momentum" unique_id=166270987]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="Axis" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Momentum/X" unique_id=243294341]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "X"
|
||||||
|
|
||||||
|
[node name="MomentumX" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Momentum/X" unique_id=210690849]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "—"
|
||||||
|
|
||||||
|
[node name="Y" type="HBoxContainer" parent="VBoxContainer/GridContainer/Acceleration/Momentum" unique_id=722649907]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="Axis" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Momentum/Y" unique_id=1590278938]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "Y"
|
||||||
|
|
||||||
|
[node name="MomentumY" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Momentum/Y" unique_id=349989084]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "—"
|
||||||
|
|
||||||
|
[node name="Z" type="HBoxContainer" parent="VBoxContainer/GridContainer/Acceleration/Momentum" unique_id=517849282]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="Axis" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Momentum/Z" unique_id=1648789922]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "Z"
|
||||||
|
|
||||||
|
[node name="MomentumZ" type="Label" parent="VBoxContainer/GridContainer/Acceleration/Momentum/Z" unique_id=1970978687]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 19
|
||||||
|
text = "—"
|
||||||
|
|
||||||
|
[node name="StatusBanner" type="PanelContainer" parent="." unique_id=1814858071]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 10
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_bottom = 44.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_type_variation = &"MotionAlertPanel"
|
||||||
|
|
||||||
|
[node name="Status" type="Label" parent="StatusBanner" unique_id=1561328163]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_type_variation = &"MotionAlertText"
|
||||||
|
theme_override_font_sizes/font_size = 22
|
||||||
|
text = "NO DATA"
|
||||||
|
horizontal_alignment = 1
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
[gd_scene format=3 uid="uid://c6xqxnk0pqhg1"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://d3jrypgr15n4x" path="res://scripts/vehicle_status_panel.gd" id="1"]
|
||||||
|
[ext_resource type="Script" uid="uid://rh8gwu8mfqx2" path="res://scripts/vehicle_status_art.gd" id="2"]
|
||||||
|
[ext_resource type="Script" uid="uid://bqvhrignal143" path="res://scripts/status_gauge.gd" id="3"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://bc2m1gknloa5i" path="res://assets/vector/status/stationkeep.svg" id="4"]
|
||||||
|
|
||||||
|
[node name="VehicleStatusPanel" type="PanelContainer" unique_id=489606862]
|
||||||
|
custom_minimum_size = Vector2(600, 440)
|
||||||
|
offset_right = 600.0
|
||||||
|
offset_bottom = 440.0
|
||||||
|
theme_type_variation = &"VehicleBackground"
|
||||||
|
script = ExtResource("1")
|
||||||
|
mode_icon = ExtResource("4")
|
||||||
|
|
||||||
|
[node name="Margin" type="MarginContainer" parent="." unique_id=1101194281]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_left = 16
|
||||||
|
theme_override_constants/margin_top = 12
|
||||||
|
theme_override_constants/margin_right = 16
|
||||||
|
theme_override_constants/margin_bottom = 12
|
||||||
|
|
||||||
|
[node name="Layout" type="VBoxContainer" parent="Margin" unique_id=221437014]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="StatusArea" type="MarginContainer" parent="Margin/Layout" unique_id=1107401965]
|
||||||
|
custom_minimum_size = Vector2(0, 164)
|
||||||
|
clip_contents = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="StatusArt" type="Control" parent="Margin/Layout/StatusArea" unique_id=649910411]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
script = ExtResource("2")
|
||||||
|
|
||||||
|
[node name="ModeIcon" type="TextureRect" parent="Margin/Layout/StatusArea/StatusArt" unique_id=728690011]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
self_modulate = Color(1, 0.61176467, 0.25, 0.09)
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = -1
|
||||||
|
anchor_left = 0.68
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.68
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = -105.0
|
||||||
|
offset_top = -105.0
|
||||||
|
offset_right = 105.0
|
||||||
|
offset_bottom = 105.0
|
||||||
|
rotation = -0.20943952
|
||||||
|
pivot_offset = Vector2(105, 105)
|
||||||
|
mouse_filter = 2
|
||||||
|
texture = ExtResource("4")
|
||||||
|
expand_mode = 1
|
||||||
|
stretch_mode = 5
|
||||||
|
|
||||||
|
[node name="Inset" type="MarginContainer" parent="Margin/Layout/StatusArea" unique_id=2050290664]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_left = 12
|
||||||
|
theme_override_constants/margin_top = 8
|
||||||
|
theme_override_constants/margin_right = 12
|
||||||
|
theme_override_constants/margin_bottom = 8
|
||||||
|
|
||||||
|
[node name="Text" type="VBoxContainer" parent="Margin/Layout/StatusArea/Inset" unique_id=905192152]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="Top" type="HBoxContainer" parent="Margin/Layout/StatusArea/Inset/Text" unique_id=424687654]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Code" type="Label" parent="Margin/Layout/StatusArea/Inset/Text/Top" unique_id=1099636858]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_type_variation = &"Label"
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
|
||||||
|
[node name="Severity" type="Label" parent="Margin/Layout/StatusArea/Inset/Text/Top" unique_id=1356897068]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"Label"
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "NO DATA"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="StatusName" type="Label" parent="Margin/Layout/StatusArea/Inset/Text" unique_id=1189663888]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"Label"
|
||||||
|
theme_override_font_sizes/font_size = 30
|
||||||
|
text = "Waiting for telemetry"
|
||||||
|
autowrap_mode = 3
|
||||||
|
|
||||||
|
[node name="Detail" type="Label" parent="Margin/Layout/StatusArea/Inset/Text" unique_id=703466821]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"Label"
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
autowrap_mode = 3
|
||||||
|
|
||||||
|
[node name="Action" type="Label" parent="Margin/Layout/StatusArea/Inset/Text" unique_id=514634651]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_type_variation = &"Label"
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
autowrap_mode = 3
|
||||||
|
|
||||||
|
[node name="Readouts" type="VBoxContainer" parent="Margin/Layout" unique_id=1999758637]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="Voltage" type="VBoxContainer" parent="Margin/Layout/Readouts" unique_id=1002742281]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 2
|
||||||
|
|
||||||
|
[node name="Numbers" type="HBoxContainer" parent="Margin/Layout/Readouts/Voltage" unique_id=1312021033]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Margin/Layout/Readouts/Voltage/Numbers" unique_id=264983620]
|
||||||
|
custom_minimum_size = Vector2(124, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "VOLTAGE"
|
||||||
|
|
||||||
|
[node name="VoltageValue" type="Label" parent="Margin/Layout/Readouts/Voltage/Numbers" unique_id=1230347145]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "—"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="VoltageReference" type="Label" parent="Margin/Layout/Readouts/Voltage/Numbers" unique_id=264381298]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(100, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "/ 48 V"
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="VoltageGauge" type="Control" parent="Margin/Layout/Readouts/Voltage" unique_id=446082505]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 16)
|
||||||
|
layout_mode = 2
|
||||||
|
script = ExtResource("3")
|
||||||
|
minimum = 42.0
|
||||||
|
maximum = 54.0
|
||||||
|
warning_low = 47.0
|
||||||
|
warning_high = 49.0
|
||||||
|
fault_low = 45.0
|
||||||
|
fault_high = 51.0
|
||||||
|
nominal = 48.0
|
||||||
|
|
||||||
|
[node name="Current" type="VBoxContainer" parent="Margin/Layout/Readouts" unique_id=1982270337]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 2
|
||||||
|
|
||||||
|
[node name="Numbers" type="HBoxContainer" parent="Margin/Layout/Readouts/Current" unique_id=619271939]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Margin/Layout/Readouts/Current/Numbers" unique_id=137700803]
|
||||||
|
custom_minimum_size = Vector2(124, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "CURRENT"
|
||||||
|
|
||||||
|
[node name="CurrentValue" type="Label" parent="Margin/Layout/Readouts/Current/Numbers" unique_id=2147417420]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "—"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="CurrentReference" type="Label" parent="Margin/Layout/Readouts/Current/Numbers" unique_id=1995980746]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(100, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "/ 30 A"
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="CurrentGauge" type="Control" parent="Margin/Layout/Readouts/Current" unique_id=484645650]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 16)
|
||||||
|
layout_mode = 2
|
||||||
|
script = ExtResource("3")
|
||||||
|
nominal = nan
|
||||||
|
|
||||||
|
[node name="Power" type="VBoxContainer" parent="Margin/Layout/Readouts" unique_id=1074490654]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 2
|
||||||
|
|
||||||
|
[node name="Numbers" type="HBoxContainer" parent="Margin/Layout/Readouts/Power" unique_id=111236353]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Margin/Layout/Readouts/Power/Numbers" unique_id=213594380]
|
||||||
|
custom_minimum_size = Vector2(124, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "POWER"
|
||||||
|
|
||||||
|
[node name="PowerValue" type="Label" parent="Margin/Layout/Readouts/Power/Numbers" unique_id=1921756191]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "—"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="PowerReference" type="Label" parent="Margin/Layout/Readouts/Power/Numbers" unique_id=1441937096]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(100, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "/ 1440 W"
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="PowerGauge" type="Control" parent="Margin/Layout/Readouts/Power" unique_id=82393019]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 16)
|
||||||
|
layout_mode = 2
|
||||||
|
script = ExtResource("3")
|
||||||
|
maximum = 1728.0
|
||||||
|
warning_high = 1152.0
|
||||||
|
fault_high = 1440.0
|
||||||
|
nominal = nan
|
||||||
|
|
||||||
|
[node name="Temperature" type="VBoxContainer" parent="Margin/Layout/Readouts" unique_id=658026844]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 2
|
||||||
|
|
||||||
|
[node name="Numbers" type="HBoxContainer" parent="Margin/Layout/Readouts/Temperature" unique_id=567006533]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Margin/Layout/Readouts/Temperature/Numbers" unique_id=1987975109]
|
||||||
|
custom_minimum_size = Vector2(124, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "INTERNAL"
|
||||||
|
|
||||||
|
[node name="TemperatureValue" type="Label" parent="Margin/Layout/Readouts/Temperature/Numbers" unique_id=1041616611]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "—"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="TemperatureReference" type="Label" parent="Margin/Layout/Readouts/Temperature/Numbers" unique_id=1032238631]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(100, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "/ 65 °C"
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="TemperatureGauge" type="Control" parent="Margin/Layout/Readouts/Temperature" unique_id=214013672]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 16)
|
||||||
|
layout_mode = 2
|
||||||
|
script = ExtResource("3")
|
||||||
|
maximum = 90.0
|
||||||
|
warning_high = 55.0
|
||||||
|
fault_high = 65.0
|
||||||
|
nominal = nan
|
||||||
@@ -1,20 +1,13 @@
|
|||||||
To do:
|
To do:
|
||||||
|
|
||||||
- Pitch/yaw/tilt relative to magnetic north
|
|
||||||
- Motor information
|
- Motor information
|
||||||
- RPM
|
- RPM
|
||||||
- Torque
|
- Torque
|
||||||
- Power Consumption
|
- Power Consumption
|
||||||
- Overall power consumption
|
|
||||||
- Command line
|
- Command line
|
||||||
- Inputs
|
- Inputs
|
||||||
- Camera Status
|
- Camera Status
|
||||||
- VR Peripherals and ROV connection status
|
- VR Peripherals and ROV connection status
|
||||||
- Uplink/Downlink Data Rate
|
|
||||||
- Manipulator Pos/Rot
|
- Manipulator Pos/Rot
|
||||||
- 3D model of arm rotation
|
- 3D model of arm rotation
|
||||||
- Status
|
|
||||||
- Logging
|
- Logging
|
||||||
- Enclosure Temperature
|
|
||||||
- Depth (Pressure)
|
|
||||||
- Tether Latency
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Colors
|
||||||
|
|
||||||
|
Edit `themes/palette.gd`: base swatches define the hues; derived shades use `lightened()`, `darkened()`, and `lerp()`. `SOFT`, `HIGH_CONTRAST`, and `INSTRUMENT` map them to theme roles. Treat derived static values as read-only.
|
||||||
|
|
||||||
|
Regenerate after palette edits:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
godot --headless --path . --script themes/build_theme.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit the generated themes and compass material alongside palette changes. The generator also updates the viewport background when needed.
|
||||||
|
|
||||||
|
Controls inherit the active theme. Custom graphics use `get_theme_color(role, "SurfaceSide")` for instrument roles and `get_theme_color("font_color", "Label")` for text. Prefer existing roles before adding new ones.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Console
|
||||||
|
|
||||||
|
Run `scenes/console_demo.tscn` for simulated commands and a journal feed, or add **Console** from the layout library.
|
||||||
|
|
||||||
|
| Tab | Shortcut | Input |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| ROV | F5 | ROV shell command |
|
||||||
|
| Host | F6 | Host shell command |
|
||||||
|
| Logs | F7 | Case-insensitive log filter |
|
||||||
|
| Server | F8 | Server command |
|
||||||
|
|
||||||
|
A shortcut selects its tab and focuses its input across either dashboard window. Escape releases focus. Ctrl+Tab / Ctrl+Shift+Tab cycle tabs, Ctrl+L clears the current output, and Up/Down recall commands. Each tab retains its draft, history and output. Follow controls automatic scrolling; Clear keeps command history. Output supports selection and copying.
|
||||||
|
|
||||||
|
Shortcuts are exported on the session nodes in `components/console_panel.tscn`. Conflicting shortcuts show “in use”; the first visible panel owns them. Layout editing suspends console input and shortcuts. Placement persists with the layout; console contents and command history do not.
|
||||||
|
|
||||||
|
## Transport hooks
|
||||||
|
|
||||||
|
`components/console_panel.tscn` owns four instances of `console_session.tscn`. Controls and tabs live in the scene tree and inherit the shared theme. Output, prompts and inputs share `themes/console_mono.tres`, with full hinting, 2× oversampling and slightly strengthened strokes for legibility under scaling.
|
||||||
|
|
||||||
|
- Connect `command_requested(session_id, command)` to a transport.
|
||||||
|
- Call `set_connection(session_id, connected, message)` to update availability and status.
|
||||||
|
- Call `append_output(session_id, text)` to deliver output, including trailing newlines where appropriate.
|
||||||
|
- Session IDs are `rov`, `host`, `logs`, and `server`.
|
||||||
|
|
||||||
|
The component starts disconnected and rejects command dispatch until connected with a handler attached. Logs are read-only. Scrollback is bounded by each session's `max_scrollback_characters`.
|
||||||
|
|
||||||
|
This is a line-oriented frontend. SSH, local processes, journal streaming, ANSI/VT terminal emulation and interactive full-screen programs are not connected. The demo only simulates responses and never executes system commands.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Display layouts
|
||||||
|
|
||||||
|
Run either scene with F6:
|
||||||
|
|
||||||
|
- `scenes/layout_landscape.tscn`: 1920×1080, 32×18 grid.
|
||||||
|
- `scenes/layout_dual_portrait.tscn`: two independent 1080×1920 windows, each 18×32. With two portrait monitors detected, each window is maximized on its own screen; otherwise, smaller windows open side by side. Disable `use_portrait_monitors` to force the windowed preview.
|
||||||
|
|
||||||
|
The reference cell size is 60px. Window scaling is uniform and independent of the project's stretch scale. Physical monitor placement still needs checking on the target hardware.
|
||||||
|
|
||||||
|
## Editing
|
||||||
|
|
||||||
|
Hover briefly in the top-left corner to enter overview. The trigger uses a 20-pixel window area, including letterboxing, independently of UI scale. In dual portrait, only the left window owns the hot corner; both windows enter overview together.
|
||||||
|
|
||||||
|
**Add element** opens a scrollable library. Select an entry and click Add element (or double-click it). Panels use the first free space, falling back to their minimum size if needed. Multiple instances are supported; a full grid leaves the library open with a no-space message.
|
||||||
|
|
||||||
|
Select and drag a panel to move it. Selected panels show three buttons:
|
||||||
|
|
||||||
|
- **↘**: drag to resize from the bottom-right edge.
|
||||||
|
- **×**: remove the panel from this layout.
|
||||||
|
- **⚙**: settings, currently disabled.
|
||||||
|
|
||||||
|
Moves and sizes snap to cells. Occupied space, minimum sizes, and window boundaries block invalid placements; invalid drops return to their starting position. Escape or leaving overview cancels an unfinished drag. Panels stay within their own window.
|
||||||
|
|
||||||
|
Changes save automatically as JSON under `user://layouts/`: `landscape.json`, `portrait_left.json`, and `portrait_right.json`. Godot resolves `user://` to the application's user-data directory (the XDG data location on Linux), available from `OS.get_user_data_dir()`. To store files beside a writable application instead, set each canvas's `config_path` to an absolute path in that folder. Existing `.cfg` layouts migrate on first load when no JSON file exists; the originals are retained. **Reset layout** restores default positions, sizes, and deleted panels. Exit with Escape, Return to dashboard, or a click outside the preview.
|
||||||
|
|
||||||
|
## JSON format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"grid": [32, 18],
|
||||||
|
"elements": [
|
||||||
|
{"id": "camera", "type": "camera", "rect": [0, 0, 16, 10]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The elements array is the complete active layout; omitted default panels stay deleted. IDs distinguish repeated instances. Writes use a temporary file followed by rename. Invalid types, duplicate IDs, overlaps, and out-of-bounds rectangles are rejected before changing the displayed layout.
|
||||||
|
|
||||||
|
## Scene structure
|
||||||
|
|
||||||
|
`layout_overview.tscn` owns the shader grid and toolbar. Each `layout_canvas.tscn` places its panel children using `layout_item.gd`; set their IDs, cell rectangles, and minimum sizes in the Inspector. Existing UI scenes remain under each item's `Content` node. Each allocation has a full-size panel background with a shared `panel_gap` (16 logical pixels by default). Instruments scale uniformly inside that frame; reserved slots fill it. Embedded component backgrounds are transparent so their scaling does not change the visible gutters. The camera retains its 16:9 picture inside the camera/header allocation.
|
||||||
|
|
||||||
|
The initial layouts deliberately leave free cells for rearranging. VR/video and operator tools are placeholders. The Link and Environment panels have separate [Link](link_panel.md) and [Environment](environment_panel.md) telemetry demos. The [Console](console_panel.md) is available in the library with four tabs and direct focus shortcuts. Camera and telemetry feeds are not connected.
|
||||||
|
|
||||||
|
The library registry is `scripts/layout_catalog.gd`. Each entry supplies a scene, display text, preferred/minimum cells and sizing behavior. JSON stores registered type IDs rather than resource paths. `components/layout_library.tscn` owns the drawer; selection button order is unchanged.
|
||||||
|
|
||||||
|
`LayoutItemBackground` supplies the shared outer frame. Components with a separate background node can specify `background_path` on their item (or `background` in the catalog entry); root PanelContainers are embedded automatically. Standalone component scenes retain their own styling.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Environment panel
|
||||||
|
|
||||||
|
Run `scenes/environment_demo.tscn` with F6. It previews surface/zero, shallow operation, deeper readings, independent depth/pressure warnings, stale data and both themes.
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
$EnvironmentPanel.set_telemetry(248.6, 2.54, 7.4)
|
||||||
|
# depth: metres; outside pressure: MPa; water temperature: Celsius
|
||||||
|
```
|
||||||
|
|
||||||
|
The depth indicator uses `log(1 + depth / knee)`, normalized to the maximum depth. This includes zero and expands shallow readings; the default knee is 1 m. Tick labels show the non-linear spacing. Numeric readings are untransformed; negative depth remains visible numerically while the marker stays at zero. Above-limit readings remain visible while the marker clamps to the bottom.
|
||||||
|
|
||||||
|
Limits default to 1000 m and 10 MPa. Warning/saturation colors start at 80%/95% of each actual measurement's limit, independently of the scale transform. Temperature has no limit or gauge. Pressure and depth are supplied independently; no conversion is performed, and the pressure datum should match the configured pressure limit. Demo samples are illustrative.
|
||||||
|
|
||||||
|
Missing data shows dashes. After two seconds without a sample, values are marked STALE / HELD. Non-finite values and negative pressure are rejected. `clear_telemetry()` restores the missing-data state.
|
||||||
|
|
||||||
|
Labels and containers remain editable scene nodes; `depth_scale.gd` draws the indicator. There is no background icon. Colors use shared theme roles. The panel replaces the Environment placeholder in the library and both layouts without changing saved JSON type IDs.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Link panel
|
||||||
|
|
||||||
|
Run `scenes/link_demo.tscn` with F6. The controls preview connected, near-capacity, saturated, degraded, disconnected, stale and missing telemetry, plus both themes.
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
$LinkPanel.set_telemetry(6.42, 1.08, 0.02, 0)
|
||||||
|
# down/up: Gbps; packet loss: percent; state: 0 connected, 1 degraded, 2 disconnected
|
||||||
|
```
|
||||||
|
|
||||||
|
Capacity defaults to 10 Gbps per direction. Arc and direction colors change at 80% and 95%; these thresholds and capacities are configurable. Numeric readings stay in the normal text color and remain unclamped above capacity. Utilization does not change the reported connection state.
|
||||||
|
|
||||||
|
`loss_window_seconds` describes the supplied loss measurement (default 10 s); compute that measurement upstream. Disconnected and missing telemetry show dashes. After `stale_after_seconds` (default 2), values are held with a STALE label. Invalid or non-finite samples are rejected. `clear_telemetry()` restores the missing-data state.
|
||||||
|
|
||||||
|
Layout and text are scene nodes; `link_rate.gd` only draws the utilization arcs. `link_icon` selects the subdued SVG watermark. The current local chain-link drawing is a draft. Colors come from the shared theme.
|
||||||
|
|
||||||
|
The panel replaces the Link placeholder in both layouts and the element library. Existing JSON layouts keep their `link` type IDs. Camera and telemetry transports are not connected by this prototype.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Motion instrument
|
||||||
|
|
||||||
|
Instance `components/motion_panel.tscn`. Run `scenes/motion_demo.tscn` with F6 for simulated telemetry; pause the feed to preview stale status.
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
$MotionPanel.set_telemetry(
|
||||||
|
Vector3(roll_degrees, pitch_degrees, heading_degrees),
|
||||||
|
Vector3(momentum_x, momentum_y, momentum_z),
|
||||||
|
Vector3(accel_x, accel_y, accel_z)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Orientation: positive roll right side down, positive pitch nose up, heading clockwise from north (0–360°).
|
||||||
|
- Body axes: X forward, Y right, Z down.
|
||||||
|
- Angular momentum: kg·m²/s, distinct from gyroscope angular velocity.
|
||||||
|
- Acceleration: m/s², with gravity removed upstream.
|
||||||
|
|
||||||
|
Missing values show dashes; non-finite samples are rejected. After `stale_after_seconds` (default 2), the banner marks held values STALE. `acceleration_full_scale` sets plot limits; numeric values remain unclamped.
|
||||||
|
|
||||||
|
Edit layout and Labels in the scene tree. Preserve scene-unique names used by `motion_panel.gd`. The four graphics use `motion_instrument.gd`; `radius_source` keeps the circles and Z scale aligned. Colors inherit the active theme; see [colors.md](colors.md).
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Vehicle status prototype
|
||||||
|
|
||||||
|
Run `scenes/vehicle_status_demo.tscn` with F6. The controls switch between normal, warning, fault, and a stopped feed, plus high contrast and alert pulsing.
|
||||||
|
|
||||||
|
Instance `components/vehicle_status_panel.tscn` for integration:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
$VehicleStatusPanel.set_status("STATIONKEEP", 0, "", "Position hold active", "Thrusters enabled")
|
||||||
|
$VehicleStatusPanel.set_telemetry(48.0, 18.2, 874.0, 34.0)
|
||||||
|
# severity: 0 normal, 1 warning, 2 fault
|
||||||
|
$VehicleStatusPanel.set_status("Enclosure overtemperature", 2, "TMP-003",
|
||||||
|
"68 °C · trip 65 °C · thrusters inhibited", "Inspect cooling before reset")
|
||||||
|
```
|
||||||
|
|
||||||
|
All sample fault codes, responses, and thresholds are illustrative. The UI displays reported vehicle status; gauge zones do not control the ROV or generate its faults.
|
||||||
|
|
||||||
|
Configure each gauge in the scene Inspector. The numeric references use the voltage nominal or the other gauges' upper fault thresholds. Voltage marks 48 V at center; any deviation enters a yellow region.
|
||||||
|
|
||||||
|
| Gauge | Display range | Warning zone | Fault zone |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Voltage | 42–54 V | Either side of 48 V | Below 45 / above 51 V |
|
||||||
|
| Current | 0–36 A | 24–30 A | Above 30 A |
|
||||||
|
| Power | 0–1728 W | 1152–1440 W | Above 1440 W |
|
||||||
|
| Internal temperature | 0–90 °C | 55–65 °C | Above 65 °C |
|
||||||
|
|
||||||
|
Only 48 V nominal and 30 A were supplied. The other thresholds need confirmation; 1440 W is the provisional 48 × 30 reference. Markers clamp to the track, but numeric readings remain unclamped.
|
||||||
|
|
||||||
|
Labels and containers own layout. `status_gauge.gd` draws tracks and markers; `vehicle_status_art.gd` draws the watermark and status background. Warning/fault backgrounds alternate shades once per second; `pulse_alerts` disables that behavior. After two seconds without telemetry, the status is marked STALE and readings are held. Colors come from the shared palette and generated themes.
|
||||||
|
|
||||||
|
`mode_icon` selects the SVG watermark (stationkeep and ROV samples are in `assets/vector/status/`). Edit `ModeIcon` anchors, rotation, and size in the scene. Readings are right-aligned beside a fixed-width, left-aligned limits column. Off-phase warning/fault shades are derived in the shared palette.
|
||||||
+2
-1
@@ -11,6 +11,7 @@ config_version=5
|
|||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="SurfaceSide-UI"
|
config/name="SurfaceSide-UI"
|
||||||
|
run/main_scene="res://scenes/layout_landscape.tscn"
|
||||||
config/features=PackedStringArray("4.7", "Forward Plus")
|
config/features=PackedStringArray("4.7", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
@@ -31,4 +32,4 @@ theme/custom="res://themes/theme.tres"
|
|||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
rendering_device/driver.windows="d3d12"
|
rendering_device/driver.windows="d3d12"
|
||||||
environment/defaults/default_clear_color=Color(0.067, 0.055, 0.067, 1)
|
environment/defaults/default_clear_color=Color(0.069019616, 0.058235295, 0.06686275, 1)
|
||||||
+2
-9
@@ -1,19 +1,12 @@
|
|||||||
[gd_scene format=3 uid="uid://j2vmxn1p5hej"]
|
[gd_scene format=3 uid="uid://j2vmxn1p5hej"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://cgcgn8lwigumx" path="res://scripts/angle_display_3d.gd" id="1_8a3he"]
|
[ext_resource type="Script" uid="uid://cgcgn8lwigumx" path="res://scripts/angle_display_3d.gd" id="1_8a3he"]
|
||||||
[ext_resource type="Texture2D" uid="uid://cjw3n47kehnfj" path="res://assets/vector/dial.svg" id="1_vggt3"]
|
[ext_resource type="Material" path="res://themes/compass_material.tres" id="1_material"]
|
||||||
[ext_resource type="PackedScene" uid="uid://0qb2f8igltax" path="res://assets/models/grey-seal-halichoerus-grypus/grey_seal_clean.glb" id="2_8a3he"]
|
[ext_resource type="PackedScene" uid="uid://0qb2f8igltax" path="res://assets/models/grey-seal-halichoerus-grypus/grey_seal_clean.glb" id="2_8a3he"]
|
||||||
[ext_resource type="Script" uid="uid://cv5cmdlub5gtx" path="res://scenes/node_3d.gd" id="3_mut2o"]
|
[ext_resource type="Script" uid="uid://cv5cmdlub5gtx" path="res://scenes/node_3d.gd" id="3_mut2o"]
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_mut2o"]
|
|
||||||
transparency = 1
|
|
||||||
cull_mode = 2
|
|
||||||
shading_mode = 0
|
|
||||||
albedo_color = Color(0.44361365, 0.72118443, 0, 1)
|
|
||||||
albedo_texture = ExtResource("1_vggt3")
|
|
||||||
|
|
||||||
[sub_resource type="PlaneMesh" id="PlaneMesh_wivqi"]
|
[sub_resource type="PlaneMesh" id="PlaneMesh_wivqi"]
|
||||||
material = SubResource("StandardMaterial3D_mut2o")
|
material = ExtResource("1_material")
|
||||||
|
|
||||||
[node name="Compass" type="Node3D" unique_id=1678590387]
|
[node name="Compass" type="Node3D" unique_id=1678590387]
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/console_demo.gd" id="1"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/console_panel.tscn" id="2"]
|
||||||
|
|
||||||
|
[node name="ConsoleDemo" type="VBoxContainer"]
|
||||||
|
offset_left = 24.0
|
||||||
|
offset_top = 24.0
|
||||||
|
offset_right = 924.0
|
||||||
|
offset_bottom = 700.0
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Notice" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "SIMULATED / CONSOLE · F5–F8 choose a tab · Esc releases focus"
|
||||||
|
|
||||||
|
[node name="ConsolePanel" parent="." instance=ExtResource("2")]
|
||||||
|
layout_mode = 2
|
||||||
|
custom_minimum_size = Vector2(900, 520)
|
||||||
|
|
||||||
|
[node name="Controls" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Contrast" type="CheckButton" parent="Controls"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "High contrast"
|
||||||
|
|
||||||
|
[node name="Feed" type="CheckButton" parent="Controls"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Simulated journal feed"
|
||||||
|
button_pressed = true
|
||||||
|
|
||||||
|
[node name="LogTimer" type="Timer" parent="."]
|
||||||
|
wait_time = 1.0
|
||||||
|
autostart = true
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_canvas.tscn" id="canvas"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_item.tscn" id="item"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/camera_layout_slot.tscn" id="camera"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/vehicle_status_panel.tscn" id="vehicle"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/motion_panel.tscn" id="motion"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/environment_panel.tscn" id="environment"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/link_panel.tscn" id="link"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_slot.tscn" id="slot"]
|
||||||
|
|
||||||
|
[node name="DisplayLeft" instance=ExtResource("canvas")]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
grid_size = Vector2i(18, 32)
|
||||||
|
config_path = "user://layouts/portrait_left.json"
|
||||||
|
legacy_config_path = "user://layout_portrait_left.cfg"
|
||||||
|
|
||||||
|
[node name="Camera" parent="." instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "camera"
|
||||||
|
cell_rect = Rect2i(0, 0, 18, 11)
|
||||||
|
minimum_cells = Vector2i(8, 5)
|
||||||
|
reference_size = Vector2(960, 600)
|
||||||
|
|
||||||
|
[node name="Content" parent="Camera" instance=ExtResource("camera")]
|
||||||
|
layout_mode = 0
|
||||||
|
|
||||||
|
[node name="Vehicle" parent="." instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "vehicle"
|
||||||
|
cell_rect = Rect2i(0, 11, 10, 8)
|
||||||
|
minimum_cells = Vector2i(6, 4)
|
||||||
|
reference_size = Vector2(600, 440)
|
||||||
|
|
||||||
|
[node name="Content" parent="Vehicle" instance=ExtResource("vehicle")]
|
||||||
|
layout_mode = 0
|
||||||
|
|
||||||
|
[node name="Motion" parent="." instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "motion"
|
||||||
|
cell_rect = Rect2i(0, 19, 10, 7)
|
||||||
|
minimum_cells = Vector2i(6, 4)
|
||||||
|
reference_size = Vector2(600, 410)
|
||||||
|
background_path = NodePath("Content/Background")
|
||||||
|
|
||||||
|
[node name="Content" parent="Motion" instance=ExtResource("motion")]
|
||||||
|
layout_mode = 0
|
||||||
|
|
||||||
|
[node name="Environment" parent="." instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "environment"
|
||||||
|
cell_rect = Rect2i(10, 11, 8, 8)
|
||||||
|
minimum_cells = Vector2i(4, 3)
|
||||||
|
reference_size = Vector2(360, 240)
|
||||||
|
|
||||||
|
[node name="Content" parent="Environment" instance=ExtResource("environment")]
|
||||||
|
layout_mode = 0
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_canvas.tscn" id="canvas"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_item.tscn" id="item"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/camera_layout_slot.tscn" id="camera"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/vehicle_status_panel.tscn" id="vehicle"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/motion_panel.tscn" id="motion"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/environment_panel.tscn" id="environment"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/link_panel.tscn" id="link"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_slot.tscn" id="slot"]
|
||||||
|
|
||||||
|
[node name="DisplayRight" instance=ExtResource("canvas")]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
grid_size = Vector2i(18, 32)
|
||||||
|
config_path = "user://layouts/portrait_right.json"
|
||||||
|
legacy_config_path = "user://layout_portrait_right.cfg"
|
||||||
|
|
||||||
|
[node name="VideoVR" parent="." instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "videovr"
|
||||||
|
cell_rect = Rect2i(0, 0, 18, 17)
|
||||||
|
minimum_cells = Vector2i(6, 4)
|
||||||
|
reference_size = Vector2(600, 400)
|
||||||
|
fill_allocation = true
|
||||||
|
|
||||||
|
[node name="Content" parent="VideoVR" instance=ExtResource("slot")]
|
||||||
|
layout_mode = 0
|
||||||
|
title = "VIDEO / VR"
|
||||||
|
description = "Latency history and breakdown · headset connection · device · resolution"
|
||||||
|
|
||||||
|
[node name="Link" parent="." instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "link"
|
||||||
|
cell_rect = Rect2i(0, 17, 18, 5)
|
||||||
|
minimum_cells = Vector2i(4, 3)
|
||||||
|
reference_size = Vector2(360, 240)
|
||||||
|
|
||||||
|
[node name="Content" parent="Link" instance=ExtResource("link")]
|
||||||
|
layout_mode = 0
|
||||||
|
|
||||||
|
[node name="OperatorTools" parent="." instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "operatortools"
|
||||||
|
cell_rect = Rect2i(0, 22, 18, 10)
|
||||||
|
minimum_cells = Vector2i(6, 4)
|
||||||
|
reference_size = Vector2(600, 400)
|
||||||
|
fill_allocation = true
|
||||||
|
|
||||||
|
[node name="Content" parent="OperatorTools" instance=ExtResource("slot")]
|
||||||
|
layout_mode = 0
|
||||||
|
title = "OPERATOR TOOLS / RESERVED"
|
||||||
|
description = "Recording · manipulator · terminal / events"
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/environment_demo.gd" id="1"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/environment_panel.tscn" id="2"]
|
||||||
|
|
||||||
|
[node name="EnvironmentDemo" type="VBoxContainer"]
|
||||||
|
offset_left = 24.0
|
||||||
|
offset_top = 24.0
|
||||||
|
offset_right = 624.0
|
||||||
|
offset_bottom = 420.0
|
||||||
|
theme_override_constants/separation = 16
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Notice" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "SIMULATED / ENVIRONMENT INSTRUMENT"
|
||||||
|
|
||||||
|
[node name="EnvironmentPanel" parent="." instance=ExtResource("2")]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
|
||||||
|
[node name="Controls" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="State" type="OptionButton" parent="Controls"]
|
||||||
|
layout_mode = 2
|
||||||
|
selected = 1
|
||||||
|
item_count = 8
|
||||||
|
popup/item_0/text = "Surface / zero"
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "Shallow operation"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
popup/item_2/text = "Operating depth"
|
||||||
|
popup/item_2/id = 2
|
||||||
|
popup/item_3/text = "Near depth limit"
|
||||||
|
popup/item_3/id = 3
|
||||||
|
popup/item_4/text = "Pressure near limit"
|
||||||
|
popup/item_4/id = 4
|
||||||
|
popup/item_5/text = "Over limits"
|
||||||
|
popup/item_5/id = 5
|
||||||
|
popup/item_6/text = "Stop feed / stale"
|
||||||
|
popup/item_6/id = 6
|
||||||
|
popup/item_7/text = "No telemetry"
|
||||||
|
popup/item_7/id = 7
|
||||||
|
|
||||||
|
[node name="Contrast" type="CheckButton" parent="Controls"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "High contrast"
|
||||||
|
|
||||||
|
[node name="Note" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Zero-inclusive log scale · shallow depths expanded"
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" path="res://scenes/display_left.tscn" id="left"]
|
||||||
|
[ext_resource type="PackedScene" path="res://scenes/display_right.tscn" id="right"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/layout_preview.gd" id="preview"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_overview.tscn" id="overview"]
|
||||||
|
|
||||||
|
[node name="DualPortraitLayout" type="Control"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("preview")
|
||||||
|
configuration = 1
|
||||||
|
|
||||||
|
[node name="Overview" parent="." instance=ExtResource("overview")]
|
||||||
|
layout_mode = 1
|
||||||
|
grid_size = Vector2i(18, 32)
|
||||||
|
|
||||||
|
[node name="Canvas" parent="Overview/Dashboard" instance=ExtResource("left")]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="RightWindow" type="Window" parent="."]
|
||||||
|
title = "ROV / right portrait"
|
||||||
|
visible = false
|
||||||
|
size = Vector2i(540, 960)
|
||||||
|
content_scale_size = Vector2i(1080, 1920)
|
||||||
|
content_scale_mode = 1
|
||||||
|
content_scale_aspect = 1
|
||||||
|
|
||||||
|
[node name="RightOverview" parent="RightWindow" instance=ExtResource("overview")]
|
||||||
|
layout_mode = 1
|
||||||
|
hot_corner_enabled = false
|
||||||
|
grid_size = Vector2i(18, 32)
|
||||||
|
|
||||||
|
[node name="Canvas" parent="RightWindow/RightOverview/Dashboard" instance=ExtResource("right")]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_canvas.tscn" id="canvas"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_item.tscn" id="item"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/camera_layout_slot.tscn" id="camera"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/vehicle_status_panel.tscn" id="vehicle"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/motion_panel.tscn" id="motion"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_slot.tscn" id="slot"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/layout_preview.gd" id="preview"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/layout_overview.tscn" id="overview"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" path="res://components/link_panel.tscn" id="link"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" path="res://components/environment_panel.tscn" id="environment"]
|
||||||
|
|
||||||
|
[node name="LandscapeLayout" type="Control"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("preview")
|
||||||
|
|
||||||
|
[node name="Overview" parent="." instance=ExtResource("overview")]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="Canvas" parent="Overview/Dashboard" instance=ExtResource("canvas")]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
config_path = "user://layouts/landscape.json"
|
||||||
|
legacy_config_path = "user://layout_landscape.cfg"
|
||||||
|
|
||||||
|
[node name="Camera" parent="Overview/Dashboard/Canvas" instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "camera"
|
||||||
|
cell_rect = Rect2i(0, 0, 16, 10)
|
||||||
|
minimum_cells = Vector2i(8, 5)
|
||||||
|
reference_size = Vector2(960, 600)
|
||||||
|
|
||||||
|
[node name="Content" parent="Overview/Dashboard/Canvas/Camera" instance=ExtResource("camera")]
|
||||||
|
layout_mode = 0
|
||||||
|
|
||||||
|
[node name="Vehicle" parent="Overview/Dashboard/Canvas" instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "vehicle"
|
||||||
|
cell_rect = Rect2i(16, 0, 10, 8)
|
||||||
|
minimum_cells = Vector2i(6, 4)
|
||||||
|
reference_size = Vector2(600, 440)
|
||||||
|
|
||||||
|
[node name="Content" parent="Overview/Dashboard/Canvas/Vehicle" instance=ExtResource("vehicle")]
|
||||||
|
layout_mode = 0
|
||||||
|
|
||||||
|
[node name="Motion" parent="Overview/Dashboard/Canvas" instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "motion"
|
||||||
|
cell_rect = Rect2i(16, 8, 10, 7)
|
||||||
|
minimum_cells = Vector2i(6, 4)
|
||||||
|
reference_size = Vector2(600, 410)
|
||||||
|
|
||||||
|
background_path = NodePath("Content/Background")
|
||||||
|
|
||||||
|
[node name="Content" parent="Overview/Dashboard/Canvas/Motion" instance=ExtResource("motion")]
|
||||||
|
layout_mode = 0
|
||||||
|
|
||||||
|
[node name="Environment" parent="Overview/Dashboard/Canvas" instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "environment"
|
||||||
|
cell_rect = Rect2i(26, 0, 6, 8)
|
||||||
|
minimum_cells = Vector2i(4, 3)
|
||||||
|
reference_size = Vector2(360, 240)
|
||||||
|
|
||||||
|
[node name="Content" parent="Overview/Dashboard/Canvas/Environment" instance=ExtResource("environment")]
|
||||||
|
layout_mode = 0
|
||||||
|
|
||||||
|
[node name="VideoVR" parent="Overview/Dashboard/Canvas" instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "videovr"
|
||||||
|
cell_rect = Rect2i(0, 10, 16, 8)
|
||||||
|
minimum_cells = Vector2i(6, 4)
|
||||||
|
reference_size = Vector2(600, 400)
|
||||||
|
fill_allocation = true
|
||||||
|
|
||||||
|
[node name="Content" parent="Overview/Dashboard/Canvas/VideoVR" instance=ExtResource("slot")]
|
||||||
|
layout_mode = 0
|
||||||
|
title = "VIDEO / VR"
|
||||||
|
description = "Latency breakdown · headset · resolution"
|
||||||
|
|
||||||
|
[node name="Link" parent="Overview/Dashboard/Canvas" instance=ExtResource("item")]
|
||||||
|
layout_mode = 0
|
||||||
|
panel_id = "link"
|
||||||
|
cell_rect = Rect2i(26, 8, 6, 4)
|
||||||
|
minimum_cells = Vector2i(4, 3)
|
||||||
|
reference_size = Vector2(360, 240)
|
||||||
|
|
||||||
|
[node name="Content" parent="Overview/Dashboard/Canvas/Link" instance=ExtResource("link")]
|
||||||
|
layout_mode = 0
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/link_demo.gd" id="1"]
|
||||||
|
[ext_resource type="PackedScene" path="res://components/link_panel.tscn" id="2"]
|
||||||
|
|
||||||
|
[node name="LinkDemo" type="VBoxContainer"]
|
||||||
|
offset_left = 24.0
|
||||||
|
offset_top = 24.0
|
||||||
|
offset_right = 624.0
|
||||||
|
offset_bottom = 420.0
|
||||||
|
theme_override_constants/separation = 16
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="Notice" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "SIMULATED / LINK INSTRUMENT"
|
||||||
|
|
||||||
|
[node name="LinkPanel" parent="." instance=ExtResource("2")]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
|
||||||
|
[node name="Controls" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="State" type="OptionButton" parent="Controls"]
|
||||||
|
layout_mode = 2
|
||||||
|
selected = 0
|
||||||
|
item_count = 7
|
||||||
|
popup/item_0/text = "Connected"
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "Near capacity"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
popup/item_2/text = "Saturated"
|
||||||
|
popup/item_2/id = 2
|
||||||
|
popup/item_3/text = "Degraded"
|
||||||
|
popup/item_3/id = 3
|
||||||
|
popup/item_4/text = "Disconnected"
|
||||||
|
popup/item_4/id = 4
|
||||||
|
popup/item_5/text = "Stop feed / stale"
|
||||||
|
popup/item_5/id = 5
|
||||||
|
popup/item_6/text = "No telemetry"
|
||||||
|
popup/item_6/id = 6
|
||||||
|
|
||||||
|
[node name="Contrast" type="CheckButton" parent="Controls"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "High contrast"
|
||||||
|
|
||||||
|
[node name="Thresholds" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Arc thresholds: 80% warning · 95% saturation"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[gd_scene format=3 uid="uid://b8u5uwdoxxvb1"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dvljqi38sjimi" path="res://components/motion_panel.tscn" id="1"]
|
||||||
|
[ext_resource type="Script" uid="uid://c5mncovlnarve" path="res://scripts/motion_demo.gd" id="2"]
|
||||||
|
|
||||||
|
[node name="MotionDemo" type="VBoxContainer" unique_id=1608361665]
|
||||||
|
offset_left = 24.0
|
||||||
|
offset_top = 24.0
|
||||||
|
offset_right = 624.0
|
||||||
|
offset_bottom = 530.0
|
||||||
|
script = ExtResource("2")
|
||||||
|
|
||||||
|
[node name="DemoNotice" type="Label" parent="." unique_id=937059766]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "SIMULATED TELEMETRY / motion instrument preview"
|
||||||
|
|
||||||
|
[node name="MotionPanel" parent="." unique_id=843107870 instance=ExtResource("1")]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Pause" type="CheckButton" parent="." unique_id=103027158]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Pause feed (shows stale state after 2 seconds)"
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" path="res://components/vehicle_status_panel.tscn" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/vehicle_status_demo.gd" id="2"]
|
||||||
|
|
||||||
|
[node name="VehicleStatusDemo" type="VBoxContainer"]
|
||||||
|
offset_left = 24.0
|
||||||
|
offset_top = 24.0
|
||||||
|
offset_right = 624.0
|
||||||
|
offset_bottom = 560.0
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
script = ExtResource("2")
|
||||||
|
|
||||||
|
[node name="Notice" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "SIMULATED / gauge thresholds are provisional"
|
||||||
|
|
||||||
|
[node name="VehicleStatusPanel" parent="." instance=ExtResource("1")]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Controls" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="State" type="OptionButton" parent="Controls"]
|
||||||
|
layout_mode = 2
|
||||||
|
item_count = 4
|
||||||
|
selected = 0
|
||||||
|
popup/item_0/text = "Normal"
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "Warning"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
popup/item_2/text = "Fault"
|
||||||
|
popup/item_2/id = 2
|
||||||
|
popup/item_3/text = "Stop feed / stale"
|
||||||
|
popup/item_3/id = 3
|
||||||
|
|
||||||
|
[node name="Contrast" type="CheckButton" parent="Controls"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "High contrast"
|
||||||
|
|
||||||
|
[node name="Pulse" type="CheckButton" parent="Controls"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Pulse"
|
||||||
|
button_pressed = true
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
@tool
|
||||||
|
extends AspectRatioContainer
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
resized.connect(_update_height)
|
||||||
|
_update_height()
|
||||||
|
|
||||||
|
func _update_height() -> void:
|
||||||
|
custom_minimum_size.y = ceilf(size.x / ratio)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://k6buaitag78o
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
extends VBoxContainer
|
||||||
|
|
||||||
|
@onready var console = $ConsolePanel
|
||||||
|
var _log_index := 0
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
console.command_requested.connect(_demo_command)
|
||||||
|
for id in ["rov", "host", "logs", "server"]:
|
||||||
|
console.set_connection(id, true, "SIMULATED · " + id)
|
||||||
|
console.append_output("rov", "[demo] ROV shell ready. Try help, pwd or status.\n")
|
||||||
|
console.append_output("host", "[demo] Host shell ready. Try help or pwd.\n")
|
||||||
|
console.append_output("server", "[demo] Server shell ready. Try help or status.\n")
|
||||||
|
$Controls/Contrast.toggled.connect(func(high: bool) -> void:
|
||||||
|
theme = load("res://themes/theme_highcontrast.tres" if high else "res://themes/theme.tres")
|
||||||
|
)
|
||||||
|
$Controls/Feed.toggled.connect(func(enabled: bool) -> void: $LogTimer.paused = not enabled)
|
||||||
|
$LogTimer.timeout.connect(_append_log)
|
||||||
|
_append_log()
|
||||||
|
|
||||||
|
func _demo_command(id: String, command: String) -> void:
|
||||||
|
match command.strip_edges():
|
||||||
|
"help": console.append_output(id, "Demo commands: help, status, pwd, clear\n")
|
||||||
|
"status": console.append_output(id, "[demo] Vehicle ready · stationkeep · no live transport\n")
|
||||||
|
"pwd": console.append_output(id, "/" + id + " (simulated)\n")
|
||||||
|
"clear": console.session(id).clear_output()
|
||||||
|
_: console.append_output(id, "[demo] Received: " + command + "\n")
|
||||||
|
|
||||||
|
func _append_log() -> void:
|
||||||
|
_log_index += 1
|
||||||
|
var level := "WARN" if _log_index % 5 == 0 else "INFO"
|
||||||
|
var message := "sample telemetry delayed" if level == "WARN" else "sample telemetry received"
|
||||||
|
console.append_output("logs", "%04d %s rov-server: %s\n" % [_log_index, level, message])
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://blo11s7dqe2b
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
@tool
|
||||||
|
extends SelectableContainer
|
||||||
|
|
||||||
|
signal command_requested(session_id: String, command: String)
|
||||||
|
|
||||||
|
@onready var tabs: TabContainer = %Tabs
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
super._ready()
|
||||||
|
_refresh_header()
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return
|
||||||
|
tabs.get_tab_bar().focus_mode = Control.FOCUS_NONE
|
||||||
|
tabs.get_tab_bar().gui_input.connect(func(event: InputEvent) -> void:
|
||||||
|
if event is InputEventMouseButton and event.pressed: activate()
|
||||||
|
)
|
||||||
|
tabs.tab_changed.connect(func(_index: int) -> void:
|
||||||
|
_last_focus = null
|
||||||
|
activate()
|
||||||
|
)
|
||||||
|
for page in _pages():
|
||||||
|
page.command_submitted.connect(_route_command.bind(page.session_id))
|
||||||
|
page.focus_requested.connect(activate)
|
||||||
|
|
||||||
|
func _pages() -> Array[Control]:
|
||||||
|
var result: Array[Control] = []
|
||||||
|
var node := get_node_or_null("%Tabs")
|
||||||
|
if node != null:
|
||||||
|
for child in node.get_children():
|
||||||
|
if child.has_method("submit_command"): result.append(child)
|
||||||
|
return result
|
||||||
|
|
||||||
|
func get_focus_shortcuts() -> Array[Key]:
|
||||||
|
var keys: Array[Key] = []
|
||||||
|
for page in _pages():
|
||||||
|
if page.shortcut != KEY_NONE: keys.append(page.shortcut)
|
||||||
|
return keys
|
||||||
|
|
||||||
|
func _refresh_header() -> void:
|
||||||
|
super._refresh_header()
|
||||||
|
var node := get_node_or_null("%Tabs") as TabContainer
|
||||||
|
if node == null:
|
||||||
|
return
|
||||||
|
var seen: Dictionary = {}
|
||||||
|
for index in range(node.get_tab_count()):
|
||||||
|
var page := node.get_tab_control(index)
|
||||||
|
if not page.has_method("submit_command"): continue
|
||||||
|
var key: Key = page.shortcut
|
||||||
|
var available := is_shortcut_available(key) and not seen.has(key)
|
||||||
|
var caption: String = page.tab_label
|
||||||
|
if key != KEY_NONE:
|
||||||
|
caption += " · " + OS.get_keycode_string(key) + (" (in use)" if not available else "")
|
||||||
|
seen[key] = true
|
||||||
|
node.set_tab_title(index, caption)
|
||||||
|
node.set_tab_tooltip(index, "Focus " + page.tab_label if available else "Shortcut already used by another panel or tab")
|
||||||
|
|
||||||
|
func activate() -> void:
|
||||||
|
super.activate()
|
||||||
|
if is_active:
|
||||||
|
var page := tabs.get_current_tab_control()
|
||||||
|
if page != null:
|
||||||
|
var target: Control = page.preferred_input()
|
||||||
|
if target.is_visible_in_tree(): target.grab_focus()
|
||||||
|
|
||||||
|
func activate_shortcut(key: Key) -> void:
|
||||||
|
for index in range(tabs.get_tab_count()):
|
||||||
|
if tabs.get_tab_control(index).shortcut == key:
|
||||||
|
tabs.current_tab = index
|
||||||
|
_last_focus = null
|
||||||
|
activate()
|
||||||
|
return
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if not Engine.is_editor_hint() and is_active and event is InputEventKey:
|
||||||
|
if event.ctrl_pressed and not event.alt_pressed and not event.meta_pressed and event.keycode in [KEY_TAB, KEY_L]:
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
if event.pressed and not event.echo:
|
||||||
|
if event.keycode == KEY_TAB:
|
||||||
|
tabs.current_tab = wrapi(tabs.current_tab + (-1 if event.shift_pressed else 1), 0, tabs.get_tab_count())
|
||||||
|
else:
|
||||||
|
tabs.get_current_tab_control().clear_output()
|
||||||
|
return
|
||||||
|
super._input(event)
|
||||||
|
|
||||||
|
func session(id: String) -> Control:
|
||||||
|
for page in _pages():
|
||||||
|
if page.session_id == id: return page
|
||||||
|
return null
|
||||||
|
|
||||||
|
func append_output(id: String, text: String) -> void:
|
||||||
|
var page := session(id)
|
||||||
|
if page != null: page.append_output(text)
|
||||||
|
|
||||||
|
func set_connection(id: String, connected: bool, message: String = "") -> void:
|
||||||
|
var page := session(id)
|
||||||
|
if page != null: page.set_connection(connected, message)
|
||||||
|
|
||||||
|
func _route_command(command: String, id: String) -> void:
|
||||||
|
var page := session(id)
|
||||||
|
if page == null: return
|
||||||
|
if not page.connected or command_requested.get_connections().is_empty():
|
||||||
|
page.append_output("Not connected; command was not sent.\n")
|
||||||
|
else:
|
||||||
|
command_requested.emit(id, command)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dcxkla5mcrxmx
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
@tool
|
||||||
|
extends VBoxContainer
|
||||||
|
|
||||||
|
signal command_submitted(command: String)
|
||||||
|
signal focus_requested
|
||||||
|
|
||||||
|
@export var session_id := "rov"
|
||||||
|
@export var tab_label := "ROV"
|
||||||
|
@export var shortcut: Key = KEY_F5
|
||||||
|
@export var prompt := "rov $"
|
||||||
|
@export var read_only := false
|
||||||
|
@export var introduction := "ROV shell disconnected."
|
||||||
|
@export var max_scrollback_characters := 128000
|
||||||
|
var connected := false
|
||||||
|
var _buffer := ""
|
||||||
|
var _history: Array[String] = []
|
||||||
|
var _history_index := 0
|
||||||
|
var _draft := ""
|
||||||
|
|
||||||
|
@onready var output: RichTextLabel = $Output
|
||||||
|
@onready var command_input: LineEdit = $Input/Command
|
||||||
|
@onready var filter_input: LineEdit = $Filter/Text
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
$Input.visible = not read_only
|
||||||
|
$Filter.visible = read_only
|
||||||
|
$Input/Prompt.text = prompt
|
||||||
|
_buffer = introduction + "\n"
|
||||||
|
output.text = _buffer
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return
|
||||||
|
command_input.text_submitted.connect(submit_command)
|
||||||
|
command_input.gui_input.connect(_history_input)
|
||||||
|
filter_input.text_changed.connect(func(_text: String) -> void: _render_output())
|
||||||
|
$Status/Clear.pressed.connect(clear_output)
|
||||||
|
$Status/Follow.toggled.connect(func(enabled: bool) -> void:
|
||||||
|
output.scroll_following = enabled
|
||||||
|
if enabled: output.scroll_to_line(maxi(output.get_line_count() - 1, 0))
|
||||||
|
)
|
||||||
|
for control in [output, command_input, filter_input, $Status/Clear, $Status/Follow]:
|
||||||
|
control.gui_input.connect(func(event: InputEvent) -> void:
|
||||||
|
if event is InputEventMouseButton and event.pressed:
|
||||||
|
focus_requested.emit()
|
||||||
|
)
|
||||||
|
|
||||||
|
func preferred_input() -> Control:
|
||||||
|
return filter_input if read_only else command_input
|
||||||
|
|
||||||
|
func set_connection(value: bool, message: String = "") -> void:
|
||||||
|
connected = value
|
||||||
|
$Status/Connection.text = message if not message.is_empty() else ("Connected" if value else "Not connected")
|
||||||
|
|
||||||
|
func submit_command(command: String) -> bool:
|
||||||
|
if read_only or command.strip_edges().is_empty():
|
||||||
|
return false
|
||||||
|
if _history.is_empty() or _history.back() != command:
|
||||||
|
_history.append(command)
|
||||||
|
if _history.size() > 200: _history.pop_front()
|
||||||
|
_history_index = _history.size()
|
||||||
|
_draft = ""
|
||||||
|
append_output(prompt + " " + command + "\n")
|
||||||
|
command_input.clear()
|
||||||
|
command_submitted.emit(command)
|
||||||
|
return true
|
||||||
|
|
||||||
|
func append_output(text: String) -> void:
|
||||||
|
_buffer += text
|
||||||
|
var truncated := _buffer.length() > max_scrollback_characters
|
||||||
|
if truncated:
|
||||||
|
_buffer = _buffer.right(max_scrollback_characters)
|
||||||
|
var newline := _buffer.find("\n")
|
||||||
|
if newline >= 0 and newline < _buffer.length() - 1:
|
||||||
|
_buffer = _buffer.substr(newline + 1)
|
||||||
|
if truncated or (read_only and not filter_input.text.is_empty()):
|
||||||
|
_render_output()
|
||||||
|
else:
|
||||||
|
output.add_text(text)
|
||||||
|
|
||||||
|
func clear_output() -> void:
|
||||||
|
_buffer = ""
|
||||||
|
output.clear()
|
||||||
|
|
||||||
|
func _render_output() -> void:
|
||||||
|
var scroll := output.get_v_scroll_bar().value
|
||||||
|
if read_only and not filter_input.text.is_empty():
|
||||||
|
var matches := PackedStringArray()
|
||||||
|
for line in _buffer.split("\n"):
|
||||||
|
if line.to_lower().contains(filter_input.text.to_lower()):
|
||||||
|
matches.append(line)
|
||||||
|
output.text = "\n".join(matches)
|
||||||
|
else:
|
||||||
|
output.text = _buffer
|
||||||
|
if not $Status/Follow.button_pressed:
|
||||||
|
output.get_v_scroll_bar().set_deferred("value", scroll)
|
||||||
|
|
||||||
|
func _history_input(event: InputEvent) -> void:
|
||||||
|
if not event is InputEventKey or not event.pressed or event.ctrl_pressed or event.alt_pressed or event.meta_pressed:
|
||||||
|
return
|
||||||
|
if event.keycode not in [KEY_UP, KEY_DOWN] or _history.is_empty():
|
||||||
|
return
|
||||||
|
if _history_index == _history.size():
|
||||||
|
_draft = command_input.text
|
||||||
|
_history_index = clampi(_history_index + (-1 if event.keycode == KEY_UP else 1), 0, _history.size())
|
||||||
|
command_input.text = _draft if _history_index == _history.size() else _history[_history_index]
|
||||||
|
command_input.caret_column = command_input.text.length()
|
||||||
|
command_input.accept_event()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bvo8xa0w47jy6
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
var depth_m := 0.0
|
||||||
|
var maximum_depth_m := 1000.0
|
||||||
|
var knee_m := 1.0
|
||||||
|
var warning_fraction := 0.8
|
||||||
|
var fault_fraction := 0.95
|
||||||
|
var available := false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
resized.connect(queue_redraw)
|
||||||
|
theme_changed.connect(queue_redraw)
|
||||||
|
|
||||||
|
# log(1 + depth / knee) includes zero and is nearly linear close to the surface.
|
||||||
|
func normalized_depth(value: float) -> float:
|
||||||
|
var knee := maxf(knee_m, 0.001)
|
||||||
|
var maximum := maxf(maximum_depth_m, 0.001)
|
||||||
|
return log(1 + clampf(value, 0, maximum) / knee) / log(1 + maximum / knee)
|
||||||
|
|
||||||
|
func severity() -> int:
|
||||||
|
var ratio := depth_m / maxf(maximum_depth_m, 0.001)
|
||||||
|
return 2 if ratio >= fault_fraction else (1 if ratio >= warning_fraction else 0)
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
var top := 9.0
|
||||||
|
var bottom := maxf(top + 1, size.y - 10)
|
||||||
|
var track := get_theme_color("instrument_grid", "SurfaceSide")
|
||||||
|
var ink := get_theme_color("font_color", "Label")
|
||||||
|
var font := get_theme_default_font()
|
||||||
|
draw_line(Vector2(17, top), Vector2(17, bottom), track, 2, true)
|
||||||
|
var decade := maxf(knee_m, 0.001)
|
||||||
|
while decade < maximum_depth_m:
|
||||||
|
for multiplier in [1.0, 2.0, 5.0]:
|
||||||
|
var tick: float = decade * multiplier
|
||||||
|
if tick > 0 and tick < maximum_depth_m:
|
||||||
|
var y: float = lerpf(top, bottom, normalized_depth(tick))
|
||||||
|
draw_line(Vector2(14, y), Vector2(21, y), track, 1, true)
|
||||||
|
decade *= 10
|
||||||
|
var last_label_y := -100.0
|
||||||
|
var last_label_value := -1.0
|
||||||
|
var ticks: Array[float] = [0, maxf(knee_m, 0.001) * 2]
|
||||||
|
decade = maxf(knee_m, 0.001) * 10
|
||||||
|
while decade < maximum_depth_m:
|
||||||
|
ticks.append(decade)
|
||||||
|
decade *= 10
|
||||||
|
ticks.append(maximum_depth_m)
|
||||||
|
for tick in ticks:
|
||||||
|
if tick < 0 or tick > maximum_depth_m or is_equal_approx(tick, last_label_value):
|
||||||
|
continue
|
||||||
|
var y := lerpf(top, bottom, normalized_depth(tick))
|
||||||
|
if tick != maximum_depth_m and (y - last_label_y < 20 or (bottom - y < 20 and tick != 0)):
|
||||||
|
continue
|
||||||
|
draw_line(Vector2(12, y), Vector2(23, y), ink, 1, true)
|
||||||
|
draw_string(font, Vector2(29, y + 5), String.num(tick, 3).trim_suffix(".0"), HORIZONTAL_ALIGNMENT_LEFT, -1, 14, ink)
|
||||||
|
last_label_y = y
|
||||||
|
last_label_value = tick
|
||||||
|
if available:
|
||||||
|
var y := lerpf(top, bottom, normalized_depth(depth_m))
|
||||||
|
var role: String = ["instrument_accent", "warning", "fault"][severity()]
|
||||||
|
var accent := get_theme_color(role, "SurfaceSide")
|
||||||
|
draw_line(Vector2(17, top), Vector2(17, y), accent, 3, true)
|
||||||
|
draw_colored_polygon(PackedVector2Array([Vector2(1, y - 5), Vector2(1, y + 5), Vector2(12, y)]), accent)
|
||||||
|
draw_line(Vector2(12, y), Vector2(23, y), accent, 3, true)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cbobdfddbbddo
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
extends VBoxContainer
|
||||||
|
|
||||||
|
var elapsed := 0.0
|
||||||
|
@onready var panel = $EnvironmentPanel
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
$Controls/Contrast.toggled.connect(func(high: bool) -> void:
|
||||||
|
theme = load("res://themes/theme_highcontrast.tres" if high else "res://themes/theme.tres")
|
||||||
|
)
|
||||||
|
$Controls/State.item_selected.connect(func(index: int) -> void:
|
||||||
|
if index == 7: panel.clear_telemetry()
|
||||||
|
)
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
elapsed += delta
|
||||||
|
match $Controls/State.selected:
|
||||||
|
0: panel.set_telemetry(0, 0.10, 12.4)
|
||||||
|
1: panel.set_telemetry(0.75 + sin(elapsed) * 0.25, 0.11, 12.4)
|
||||||
|
2: panel.set_telemetry(248.6, 2.54, 7.4)
|
||||||
|
3: panel.set_telemetry(850, 8.6, 4.2)
|
||||||
|
4: panel.set_telemetry(400, 9.6, -1.5)
|
||||||
|
5: panel.set_telemetry(1010, 10.1, 3.2)
|
||||||
|
6, 7: pass
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://5qywekq0w4ne
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
@tool
|
||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
@export var maximum_depth_m := 1000.0
|
||||||
|
@export var maximum_pressure_mpa := 10.0
|
||||||
|
@export var shallow_scale_knee_m := 1.0
|
||||||
|
@export_range(0, 1) var warning_fraction := 0.8
|
||||||
|
@export_range(0, 1) var fault_fraction := 0.95
|
||||||
|
@export var stale_after_seconds := 2.0
|
||||||
|
|
||||||
|
var _depth := 0.0
|
||||||
|
var _pressure := 0.0
|
||||||
|
var _temperature := 0.0
|
||||||
|
var _received_ms := -1
|
||||||
|
var _stale := false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
## Depth in metres, pressure in MPa, water temperature in Celsius; supplied independently.
|
||||||
|
func set_telemetry(depth_m: float, pressure_mpa: float, water_temperature_c: float) -> bool:
|
||||||
|
if not is_finite(depth_m) or not is_finite(pressure_mpa) or not is_finite(water_temperature_c) or pressure_mpa < 0:
|
||||||
|
return false
|
||||||
|
_depth = depth_m
|
||||||
|
_pressure = pressure_mpa
|
||||||
|
_temperature = water_temperature_c
|
||||||
|
_received_ms = Time.get_ticks_msec()
|
||||||
|
_refresh()
|
||||||
|
return true
|
||||||
|
|
||||||
|
func clear_telemetry() -> void:
|
||||||
|
_received_ms = -1
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
func _severity(ratio: float) -> int:
|
||||||
|
return 2 if ratio >= maxf(warning_fraction, fault_fraction) else (1 if ratio >= warning_fraction else 0)
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
var stale := _received_ms >= 0 and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000
|
||||||
|
if stale != _stale:
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
func _refresh() -> void:
|
||||||
|
if not is_node_ready():
|
||||||
|
return
|
||||||
|
var measured := _received_ms >= 0
|
||||||
|
_stale = measured and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000
|
||||||
|
var depth_ratio := _depth / maxf(maximum_depth_m, 0.001)
|
||||||
|
var pressure_ratio := _pressure / maxf(maximum_pressure_mpa, 0.001)
|
||||||
|
var worst_ratio := maxf(depth_ratio, pressure_ratio)
|
||||||
|
var variants := ["Label", "InstrumentWarning", "InstrumentFault"]
|
||||||
|
%DepthValue.text = "%.2f m" % _depth if measured else "— m"
|
||||||
|
%PressureValue.text = "%.2f MPa" % _pressure if measured else "— MPa"
|
||||||
|
%TemperatureValue.text = "%.1f °C" % _temperature if measured else "— °C"
|
||||||
|
%DepthReference.text = "/ %s m" % String.num(maximum_depth_m, 2).trim_suffix(".0")
|
||||||
|
%PressureReference.text = "/ %s MPa" % String.num(maximum_pressure_mpa, 2).trim_suffix(".0")
|
||||||
|
%DepthTitle.theme_type_variation = variants[_severity(depth_ratio)] if measured else "Label"
|
||||||
|
%PressureTitle.theme_type_variation = variants[_severity(pressure_ratio)] if measured else "Label"
|
||||||
|
%State.text = "NO TELEMETRY" if not measured else ("STALE" if _stale else ("OVER LIMIT" if worst_ratio > 1 else ("NEAR LIMIT" if _severity(worst_ratio) > 0 else "")))
|
||||||
|
%State.theme_type_variation = "InstrumentMuted" if not measured else ("InstrumentWarning" if _stale else variants[_severity(worst_ratio)])
|
||||||
|
%DepthScale.depth_m = _depth
|
||||||
|
%DepthScale.maximum_depth_m = maxf(maximum_depth_m, 0.001)
|
||||||
|
%DepthScale.knee_m = maxf(shallow_scale_knee_m, 0.001)
|
||||||
|
%DepthScale.warning_fraction = warning_fraction
|
||||||
|
%DepthScale.fault_fraction = maxf(warning_fraction, fault_fraction)
|
||||||
|
%DepthScale.available = measured
|
||||||
|
%DepthScale.queue_redraw()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dnafkcowopwod
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
signal layout_saved
|
||||||
|
signal save_failed
|
||||||
|
signal load_failed
|
||||||
|
|
||||||
|
const CATALOG = preload("res://scripts/layout_catalog.gd")
|
||||||
|
const STORE = preload("res://scripts/layout_store.gd")
|
||||||
|
|
||||||
|
@export var grid_size := Vector2i(32, 18)
|
||||||
|
@export var panel_gap := 16.0:
|
||||||
|
set(value):
|
||||||
|
panel_gap = maxf(value, 0.0)
|
||||||
|
if is_node_ready():
|
||||||
|
arrange_items()
|
||||||
|
@export var config_path := ""
|
||||||
|
@export var legacy_config_path := ""
|
||||||
|
var edit_mode := false
|
||||||
|
var selected: Control
|
||||||
|
var _items: Array[Control] = []
|
||||||
|
var _defaults: Dictionary = {}
|
||||||
|
var _operation := ""
|
||||||
|
var _origin := Rect2i()
|
||||||
|
var _candidate := Rect2i()
|
||||||
|
var _offset := Vector2.ZERO
|
||||||
|
|
||||||
|
@onready var overlay: Control = $EditOverlay
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
for child in get_children():
|
||||||
|
if child.has_method("arrange_content"):
|
||||||
|
if child.element_type.is_empty():
|
||||||
|
child.element_type = child.panel_id
|
||||||
|
_items.append(child)
|
||||||
|
_defaults[child.panel_id] = child.cell_rect
|
||||||
|
resized.connect(arrange_items)
|
||||||
|
get_window().focus_exited.connect(cancel_interaction)
|
||||||
|
$EditOverlay/Actions/Resize.gui_input.connect(_resize_input)
|
||||||
|
$EditOverlay/Actions/Delete.pressed.connect(delete_selected)
|
||||||
|
arrange_items()
|
||||||
|
|
||||||
|
func cell_size() -> Vector2:
|
||||||
|
return size / Vector2(grid_size)
|
||||||
|
|
||||||
|
func arrange_items() -> void:
|
||||||
|
for item in _items:
|
||||||
|
item.padding = panel_gap / 2
|
||||||
|
item.position = Vector2(item.cell_rect.position) * cell_size()
|
||||||
|
item.size = Vector2(item.cell_rect.size) * cell_size()
|
||||||
|
item.arrange_content()
|
||||||
|
_update_overlay()
|
||||||
|
|
||||||
|
func set_edit_mode(active: bool) -> void:
|
||||||
|
cancel_interaction()
|
||||||
|
edit_mode = active
|
||||||
|
for item in _items:
|
||||||
|
_set_item_interaction(item)
|
||||||
|
if not active:
|
||||||
|
selected = null
|
||||||
|
overlay.visible = active
|
||||||
|
_update_overlay()
|
||||||
|
|
||||||
|
func handle_grid_input(event: InputEvent) -> void:
|
||||||
|
if not edit_mode:
|
||||||
|
return
|
||||||
|
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||||
|
var point: Vector2 = event.position
|
||||||
|
selected = null
|
||||||
|
for item in _items:
|
||||||
|
if item.visible and Rect2(item.position, item.size).has_point(point):
|
||||||
|
selected = item
|
||||||
|
break
|
||||||
|
if selected != null:
|
||||||
|
_begin_interaction("move", point)
|
||||||
|
_update_overlay()
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if not edit_mode or _operation.is_empty():
|
||||||
|
return
|
||||||
|
if event is InputEventMouseMotion:
|
||||||
|
update_interaction(get_global_transform_with_canvas().affine_inverse() * event.position)
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and not event.pressed:
|
||||||
|
finish_interaction()
|
||||||
|
|
||||||
|
func _resize_input(event: InputEvent) -> void:
|
||||||
|
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed and selected != null:
|
||||||
|
var button: Control = $EditOverlay/Actions/Resize
|
||||||
|
var point: Vector2 = get_global_transform_with_canvas().affine_inverse() * button.get_global_transform_with_canvas() * event.position
|
||||||
|
_begin_interaction("resize", point)
|
||||||
|
$EditOverlay/Actions/Resize.accept_event()
|
||||||
|
|
||||||
|
func _begin_interaction(operation: String, point: Vector2) -> void:
|
||||||
|
_operation = operation
|
||||||
|
_origin = selected.cell_rect
|
||||||
|
_candidate = _origin
|
||||||
|
_offset = point / cell_size()
|
||||||
|
_update_overlay()
|
||||||
|
|
||||||
|
func update_interaction(point: Vector2) -> void:
|
||||||
|
if selected == null or _operation.is_empty():
|
||||||
|
return
|
||||||
|
var delta := Vector2i((point / cell_size() - _offset).round())
|
||||||
|
_candidate = Rect2i(_origin.position + delta, _origin.size) if _operation == "move" else Rect2i(_origin.position, _origin.size + delta)
|
||||||
|
if can_place(selected, _candidate):
|
||||||
|
selected.cell_rect = _candidate
|
||||||
|
arrange_items()
|
||||||
|
_update_overlay()
|
||||||
|
|
||||||
|
func can_place(item: Control, rect: Rect2i) -> bool:
|
||||||
|
if rect.position.x < 0 or rect.position.y < 0 or rect.end.x > grid_size.x or rect.end.y > grid_size.y:
|
||||||
|
return false
|
||||||
|
if rect.size.x < item.minimum_cells.x or rect.size.y < item.minimum_cells.y:
|
||||||
|
return false
|
||||||
|
for other in _items:
|
||||||
|
if other != item and other.visible and rect.intersects(other.cell_rect):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
func finish_interaction() -> void:
|
||||||
|
if selected == null or _operation.is_empty():
|
||||||
|
return
|
||||||
|
selected.cell_rect = _candidate if can_place(selected, _candidate) else _origin
|
||||||
|
var changed: bool = selected.cell_rect != _origin
|
||||||
|
_operation = ""
|
||||||
|
arrange_items()
|
||||||
|
if changed:
|
||||||
|
save_layout()
|
||||||
|
|
||||||
|
func cancel_interaction() -> void:
|
||||||
|
if selected != null and not _operation.is_empty():
|
||||||
|
selected.cell_rect = _origin
|
||||||
|
_operation = ""
|
||||||
|
if is_node_ready():
|
||||||
|
arrange_items()
|
||||||
|
|
||||||
|
func delete_selected() -> void:
|
||||||
|
if selected == null:
|
||||||
|
return
|
||||||
|
cancel_interaction()
|
||||||
|
selected.hide()
|
||||||
|
selected = null
|
||||||
|
_update_overlay()
|
||||||
|
save_layout()
|
||||||
|
|
||||||
|
func reset_layout() -> void:
|
||||||
|
cancel_interaction()
|
||||||
|
selected = null
|
||||||
|
_remove_dynamic_items()
|
||||||
|
for item in _items:
|
||||||
|
item.cell_rect = _defaults[item.panel_id]
|
||||||
|
item.show()
|
||||||
|
arrange_items()
|
||||||
|
save_layout()
|
||||||
|
|
||||||
|
func add_element(element_type: String) -> Control:
|
||||||
|
if not edit_mode or not CATALOG.ENTRIES.has(element_type):
|
||||||
|
return null
|
||||||
|
cancel_interaction()
|
||||||
|
var entry: Dictionary = CATALOG.ENTRIES[element_type]
|
||||||
|
var item: Control
|
||||||
|
for candidate in _items:
|
||||||
|
if not candidate.visible and candidate.element_type == element_type:
|
||||||
|
item = candidate
|
||||||
|
break
|
||||||
|
var minimum: Vector2i = item.minimum_cells if item != null else entry.minimum
|
||||||
|
var preferred: Vector2i = Vector2i(entry.size).max(minimum)
|
||||||
|
var rect := _first_free(preferred)
|
||||||
|
if rect.size == Vector2i.ZERO:
|
||||||
|
rect = _first_free(minimum)
|
||||||
|
if rect.size == Vector2i.ZERO:
|
||||||
|
return null
|
||||||
|
if item == null:
|
||||||
|
var id := element_type
|
||||||
|
var suffix := 2
|
||||||
|
while _find_item(id) != null:
|
||||||
|
id = "%s_%d" % [element_type, suffix]
|
||||||
|
suffix += 1
|
||||||
|
item = CATALOG.create_item(element_type, id, rect)
|
||||||
|
add_child(item)
|
||||||
|
_items.append(item)
|
||||||
|
item.cell_rect = rect
|
||||||
|
item.show()
|
||||||
|
_set_item_interaction(item)
|
||||||
|
selected = item
|
||||||
|
arrange_items()
|
||||||
|
save_layout()
|
||||||
|
return item
|
||||||
|
|
||||||
|
func _first_free(dimensions: Vector2i) -> Rect2i:
|
||||||
|
for y in range(grid_size.y - dimensions.y + 1):
|
||||||
|
for x in range(grid_size.x - dimensions.x + 1):
|
||||||
|
var rect := Rect2i(Vector2i(x, y), dimensions)
|
||||||
|
var free := true
|
||||||
|
for item in _items:
|
||||||
|
if item.visible and rect.intersects(item.cell_rect):
|
||||||
|
free = false
|
||||||
|
break
|
||||||
|
if free:
|
||||||
|
return rect
|
||||||
|
return Rect2i()
|
||||||
|
|
||||||
|
func _find_item(id: String) -> Control:
|
||||||
|
for item in _items:
|
||||||
|
if item.panel_id == id:
|
||||||
|
return item
|
||||||
|
return null
|
||||||
|
|
||||||
|
func _remove_dynamic_items() -> void:
|
||||||
|
for item in _items.duplicate():
|
||||||
|
if not _defaults.has(item.panel_id):
|
||||||
|
_items.erase(item)
|
||||||
|
remove_child(item)
|
||||||
|
item.queue_free()
|
||||||
|
|
||||||
|
func _record(item: Control, rect: Rect2i) -> Dictionary:
|
||||||
|
return {"id": item.panel_id, "type": item.element_type,
|
||||||
|
"rect": [rect.position.x, rect.position.y, rect.size.x, rect.size.y]}
|
||||||
|
|
||||||
|
func save_layout() -> void:
|
||||||
|
if config_path.is_empty():
|
||||||
|
return
|
||||||
|
var elements: Array = []
|
||||||
|
for item in _items:
|
||||||
|
if item.visible:
|
||||||
|
elements.append(_record(item, item.cell_rect))
|
||||||
|
var document := {"version": 1, "grid": [grid_size.x, grid_size.y], "elements": elements}
|
||||||
|
if STORE.write_json(config_path, document) != OK:
|
||||||
|
save_failed.emit()
|
||||||
|
else:
|
||||||
|
layout_saved.emit()
|
||||||
|
|
||||||
|
func load_layout() -> void:
|
||||||
|
if config_path.is_empty():
|
||||||
|
return
|
||||||
|
if not FileAccess.file_exists(config_path):
|
||||||
|
_migrate_legacy()
|
||||||
|
return
|
||||||
|
var decoded := _decode_document(STORE.read_json(config_path))
|
||||||
|
if decoded.is_empty():
|
||||||
|
load_failed.emit()
|
||||||
|
return
|
||||||
|
_apply_records(decoded.records)
|
||||||
|
|
||||||
|
func _decode_document(document: Variant) -> Dictionary:
|
||||||
|
if not document is Dictionary or document.get("version") != 1:
|
||||||
|
return {}
|
||||||
|
var dimensions = document.get("grid")
|
||||||
|
if not STORE.integer_array(dimensions, 2) or (dimensions[0] != grid_size.x or dimensions[1] != grid_size.y):
|
||||||
|
return {}
|
||||||
|
var elements = document.get("elements")
|
||||||
|
if not elements is Array or elements.size() > grid_size.x * grid_size.y:
|
||||||
|
return {}
|
||||||
|
var ids: Dictionary = {}
|
||||||
|
var occupied: Array[Rect2i] = []
|
||||||
|
var records: Array = []
|
||||||
|
for record in elements:
|
||||||
|
if not record is Dictionary:
|
||||||
|
return {}
|
||||||
|
var id = record.get("id")
|
||||||
|
var element_type = record.get("type")
|
||||||
|
var coordinates = record.get("rect")
|
||||||
|
if not id is String or id.length() > 64 or not id.is_valid_identifier() or ids.has(id):
|
||||||
|
return {}
|
||||||
|
if not element_type is String or not CATALOG.ENTRIES.has(element_type) or not STORE.integer_array(coordinates, 4):
|
||||||
|
return {}
|
||||||
|
for coordinate in coordinates:
|
||||||
|
if absf(float(coordinate)) > maxi(grid_size.x, grid_size.y):
|
||||||
|
return {}
|
||||||
|
var rect := Rect2i(coordinates[0], coordinates[1], coordinates[2], coordinates[3])
|
||||||
|
var existing := _find_item(id)
|
||||||
|
if existing != null and existing.element_type != element_type:
|
||||||
|
return {}
|
||||||
|
var minimum: Vector2i = existing.minimum_cells if existing != null else CATALOG.ENTRIES[element_type].minimum
|
||||||
|
if rect.position.x < 0 or rect.position.y < 0 or rect.end.x > grid_size.x or rect.end.y > grid_size.y or rect.size.x < minimum.x or rect.size.y < minimum.y:
|
||||||
|
return {}
|
||||||
|
for other in occupied:
|
||||||
|
if rect.intersects(other):
|
||||||
|
return {}
|
||||||
|
occupied.append(rect)
|
||||||
|
ids[id] = true
|
||||||
|
records.append({"id": id, "type": element_type, "rect": rect})
|
||||||
|
return {"records": records}
|
||||||
|
|
||||||
|
func _apply_records(records: Array) -> void:
|
||||||
|
cancel_interaction()
|
||||||
|
selected = null
|
||||||
|
_remove_dynamic_items()
|
||||||
|
for item in _items:
|
||||||
|
item.hide()
|
||||||
|
for record in records:
|
||||||
|
var item := _find_item(record.id)
|
||||||
|
if item == null:
|
||||||
|
item = CATALOG.create_item(record.type, record.id, record.rect)
|
||||||
|
add_child(item)
|
||||||
|
_items.append(item)
|
||||||
|
item.cell_rect = record.rect
|
||||||
|
item.show()
|
||||||
|
_set_item_interaction(item)
|
||||||
|
arrange_items()
|
||||||
|
|
||||||
|
func _migrate_legacy() -> void:
|
||||||
|
if legacy_config_path.is_empty() or not FileAccess.file_exists(legacy_config_path):
|
||||||
|
return
|
||||||
|
var config := ConfigFile.new()
|
||||||
|
if config.load(legacy_config_path) != OK or config.get_value("layout", "grid") != grid_size:
|
||||||
|
load_failed.emit()
|
||||||
|
return
|
||||||
|
var elements: Array = []
|
||||||
|
for item in _items:
|
||||||
|
if not _defaults.has(item.panel_id):
|
||||||
|
continue
|
||||||
|
var rect = config.get_value(item.panel_id, "rect", _defaults[item.panel_id])
|
||||||
|
var enabled = config.get_value(item.panel_id, "visible", true)
|
||||||
|
if not rect is Rect2i or not enabled is bool:
|
||||||
|
load_failed.emit()
|
||||||
|
return
|
||||||
|
if enabled:
|
||||||
|
elements.append(_record(item, rect))
|
||||||
|
var decoded := _decode_document({"version": 1, "grid": [grid_size.x, grid_size.y], "elements": elements})
|
||||||
|
if decoded.is_empty():
|
||||||
|
load_failed.emit()
|
||||||
|
return
|
||||||
|
_apply_records(decoded.records)
|
||||||
|
save_layout()
|
||||||
|
|
||||||
|
func _update_overlay() -> void:
|
||||||
|
if not is_instance_valid(overlay):
|
||||||
|
return
|
||||||
|
var actions: Control = $EditOverlay/Actions
|
||||||
|
actions.visible = edit_mode and selected != null
|
||||||
|
if selected != null:
|
||||||
|
overlay.selected_rect = Rect2(selected.position, selected.size)
|
||||||
|
overlay.candidate_rect = Rect2(Vector2(_candidate.position) * cell_size(), Vector2(_candidate.size) * cell_size())
|
||||||
|
overlay.candidate_valid = can_place(selected, _candidate)
|
||||||
|
actions.position = selected.position + Vector2(selected.size.x - 116, 4)
|
||||||
|
overlay.show_candidate = not _operation.is_empty()
|
||||||
|
overlay.queue_redraw()
|
||||||
|
|
||||||
|
func _set_item_interaction(item: Control) -> void:
|
||||||
|
item.mouse_behavior_recursive = Control.MOUSE_BEHAVIOR_DISABLED if edit_mode else Control.MOUSE_BEHAVIOR_INHERITED
|
||||||
|
var content := item.get_node_or_null("Content")
|
||||||
|
if content != null and content.has_method("set_layout_editing"):
|
||||||
|
content.set_layout_editing(edit_mode)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bqrapdh6yddl8
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
const ITEM = preload("res://components/layout_item.tscn")
|
||||||
|
const ENTRIES = {
|
||||||
|
"camera": {"title": "Camera", "description": "16:9 video surface with a stream header.", "scene": preload("res://components/camera_layout_slot.tscn"), "size": Vector2i(16, 10), "minimum": Vector2i(8, 5), "reference": Vector2(960, 600)},
|
||||||
|
"vehicle": {"title": "Vehicle status", "description": "Mode, faults, electrical readings and internal temperature.", "scene": preload("res://components/vehicle_status_panel.tscn"), "size": Vector2i(10, 8), "minimum": Vector2i(6, 4), "reference": Vector2(600, 440)},
|
||||||
|
"motion": {"title": "Motion", "description": "Attitude, heading, acceleration and angular momentum.", "scene": preload("res://components/motion_panel.tscn"), "size": Vector2i(10, 7), "minimum": Vector2i(6, 4), "reference": Vector2(600, 410), "background": NodePath("Content/Background")},
|
||||||
|
"environment": {"title": "Environment", "description": "Depth with a shallow-expanded scale, outside pressure and water temperature.", "scene": preload("res://components/environment_panel.tscn"), "size": Vector2i(6, 5), "minimum": Vector2i(4, 3), "reference": Vector2(360, 240)},
|
||||||
|
"link": {"title": "Link", "description": "Connection state, traffic utilization and packet loss.", "scene": preload("res://components/link_panel.tscn"), "size": Vector2i(6, 4), "minimum": Vector2i(4, 3), "reference": Vector2(360, 240)},
|
||||||
|
"videovr": {"title": "Video / VR", "description": "Reserved for latency, headset connection and resolution.", "scene": preload("res://components/layout_slot.tscn"), "size": Vector2i(12, 8), "minimum": Vector2i(6, 4), "reference": Vector2(600, 400), "fill": true},
|
||||||
|
"console": {"title": "Console", "description": "Tabbed ROV shell, host shell, journal logs and server shell. F5–F8 select tabs.", "scene": preload("res://components/console_panel.tscn"), "size": Vector2i(16, 9), "minimum": Vector2i(10, 6), "reference": Vector2(900, 520)},
|
||||||
|
"operatortools": {"title": "Operator tools", "description": "Reserved for recording, manipulator controls and terminal.", "scene": preload("res://components/layout_slot.tscn"), "size": Vector2i(10, 6), "minimum": Vector2i(6, 4), "reference": Vector2(600, 400), "fill": true},
|
||||||
|
}
|
||||||
|
|
||||||
|
static func create_item(element_type: String, id: String, rect: Rect2i) -> Control:
|
||||||
|
var entry: Dictionary = ENTRIES[element_type]
|
||||||
|
var item = ITEM.instantiate()
|
||||||
|
item.name = id
|
||||||
|
item.panel_id = id
|
||||||
|
item.element_type = element_type
|
||||||
|
item.cell_rect = rect
|
||||||
|
item.minimum_cells = entry.minimum
|
||||||
|
item.reference_size = entry.reference
|
||||||
|
item.fill_allocation = entry.get("fill", false)
|
||||||
|
item.background_path = entry.get("background", NodePath(""))
|
||||||
|
var content = entry.scene.instantiate()
|
||||||
|
content.name = "Content"
|
||||||
|
if item.fill_allocation:
|
||||||
|
content.title = entry.title.to_upper()
|
||||||
|
content.description = entry.description
|
||||||
|
item.add_child(content)
|
||||||
|
return item
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://0hiij8bwyn7w
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
var selected_rect := Rect2()
|
||||||
|
var candidate_rect := Rect2()
|
||||||
|
var show_candidate := false
|
||||||
|
var candidate_valid := true
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
if not $Actions.visible:
|
||||||
|
return
|
||||||
|
var accent := get_theme_color("accent", "SurfaceSide")
|
||||||
|
draw_rect(selected_rect.grow(-1), accent, false, 2)
|
||||||
|
if show_candidate:
|
||||||
|
var ink := accent if candidate_valid else get_theme_color("fault", "SurfaceSide")
|
||||||
|
var fill := ink
|
||||||
|
fill.a = 0.12
|
||||||
|
draw_rect(candidate_rect.abs(), fill)
|
||||||
|
draw_rect(candidate_rect.abs(), ink, false, 3)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cwbk5g4yhdfd6
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
@tool
|
||||||
|
extends ColorRect
|
||||||
|
|
||||||
|
@export var grid_size := Vector2i(32, 18):
|
||||||
|
set(value):
|
||||||
|
grid_size = value
|
||||||
|
if is_node_ready():
|
||||||
|
_update_material()
|
||||||
|
@export var monitor_boundary := 0:
|
||||||
|
set(value):
|
||||||
|
monitor_boundary = value
|
||||||
|
if is_node_ready():
|
||||||
|
_update_material()
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
theme_changed.connect(_update_material)
|
||||||
|
_update_material()
|
||||||
|
|
||||||
|
func _gui_input(_event: InputEvent) -> void:
|
||||||
|
accept_event()
|
||||||
|
|
||||||
|
func _update_material() -> void:
|
||||||
|
var shader_material := material as ShaderMaterial
|
||||||
|
shader_material.set_shader_parameter("grid_size", Vector2(grid_size))
|
||||||
|
shader_material.set_shader_parameter("monitor_boundary", float(monitor_boundary))
|
||||||
|
shader_material.set_shader_parameter("line_color", get_theme_color("instrument_grid", "SurfaceSide"))
|
||||||
|
shader_material.set_shader_parameter("boundary_color", get_theme_color("accent", "SurfaceSide"))
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://n4eb1a6j5j88
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
const EMBEDDED_PANEL = preload("res://themes/layout_embedded_panel.tres")
|
||||||
|
|
||||||
|
@export var panel_id := "panel"
|
||||||
|
@export var element_type := ""
|
||||||
|
@export var cell_rect := Rect2i(0, 0, 6, 4)
|
||||||
|
@export var minimum_cells := Vector2i(4, 3)
|
||||||
|
@export var reference_size := Vector2(600, 440)
|
||||||
|
@export var fill_allocation := false
|
||||||
|
@export var padding := 8.0
|
||||||
|
@export_node_path("Control") var background_path := NodePath("")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
clip_contents = true
|
||||||
|
resized.connect(arrange_content)
|
||||||
|
if has_node("Content") and $Content is PanelContainer:
|
||||||
|
$Content.add_theme_stylebox_override("panel", EMBEDDED_PANEL)
|
||||||
|
if not background_path.is_empty():
|
||||||
|
var background := get_node_or_null(background_path) as Control
|
||||||
|
if background != null:
|
||||||
|
background.add_theme_stylebox_override("panel", EMBEDDED_PANEL)
|
||||||
|
if has_node("Content") and $Content.has_method("set_frame_host"):
|
||||||
|
$Content.set_frame_host($Background)
|
||||||
|
arrange_content.call_deferred()
|
||||||
|
|
||||||
|
func arrange_content() -> void:
|
||||||
|
if not has_node("Content"):
|
||||||
|
return
|
||||||
|
var content: Control = $Content
|
||||||
|
var available := (size - Vector2.ONE * padding * 2).max(Vector2.ZERO)
|
||||||
|
$Background.position = Vector2.ONE * padding
|
||||||
|
$Background.size = available
|
||||||
|
content.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
if fill_allocation:
|
||||||
|
content.scale = Vector2.ONE
|
||||||
|
content.position = Vector2.ONE * padding
|
||||||
|
content.size = available
|
||||||
|
else:
|
||||||
|
content.size = reference_size
|
||||||
|
var factor := maxf(0.01, minf(available.x / reference_size.x, available.y / reference_size.y))
|
||||||
|
content.scale = Vector2.ONE * factor
|
||||||
|
content.position = (size - reference_size * factor) / 2
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://byjnf0ae0jr2y
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
signal element_requested(element_type: String)
|
||||||
|
const CATALOG = preload("res://scripts/layout_catalog.gd")
|
||||||
|
var _types: Array = []
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_types = CATALOG.ENTRIES.keys()
|
||||||
|
for element_type in _types:
|
||||||
|
$Margin/Body/List.add_item(CATALOG.ENTRIES[element_type].title)
|
||||||
|
$Margin/Body/List.item_selected.connect(_select)
|
||||||
|
$Margin/Body/List.item_activated.connect(func(_index: int) -> void: _request())
|
||||||
|
$Margin/Body/Add.pressed.connect(_request)
|
||||||
|
$Margin/Body/Header/Close.pressed.connect(hide)
|
||||||
|
$Margin/Body/List.select(0)
|
||||||
|
_select(0)
|
||||||
|
|
||||||
|
func _select(index: int) -> void:
|
||||||
|
var entry: Dictionary = CATALOG.ENTRIES[_types[index]]
|
||||||
|
$Margin/Body/Description.text = entry.description
|
||||||
|
$Margin/Body/Size.text = "%d × %d cells · minimum %d × %d" % [entry.size.x, entry.size.y, entry.minimum.x, entry.minimum.y]
|
||||||
|
$Margin/Body/Status.text = ""
|
||||||
|
|
||||||
|
func _request() -> void:
|
||||||
|
var indices: PackedInt32Array = $Margin/Body/List.get_selected_items()
|
||||||
|
if not indices.is_empty():
|
||||||
|
element_requested.emit(_types[indices[0]])
|
||||||
|
|
||||||
|
func show_no_space() -> void:
|
||||||
|
$Margin/Body/Status.text = "No space available. Move, resize or remove a panel first."
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cwdwpabj62mhl
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
signal overview_changed(active: bool)
|
||||||
|
|
||||||
|
const FOCUS_SCOPES = preload("res://scripts/selectable_container.gd")
|
||||||
|
|
||||||
|
@export var grid_size := Vector2i(32, 18):
|
||||||
|
set(value):
|
||||||
|
grid_size = Vector2i(maxi(1, value.x), maxi(1, value.y))
|
||||||
|
if is_node_ready():
|
||||||
|
_update_grid()
|
||||||
|
@export var monitor_boundary := 0
|
||||||
|
@export_range(0.5, 0.95) var overview_scale := 0.82
|
||||||
|
@export var transition_seconds := 0.22
|
||||||
|
@export var hot_corner_enabled := true
|
||||||
|
@export var corner_size := 20.0
|
||||||
|
@export var corner_dwell := 0.18
|
||||||
|
|
||||||
|
var overview_active := false
|
||||||
|
var _pointer_in_window := false
|
||||||
|
var _corner_hover := false
|
||||||
|
var _corner_armed := true
|
||||||
|
var _dwell := 0.0
|
||||||
|
var _tween: Tween
|
||||||
|
|
||||||
|
@onready var dashboard: Control = $Dashboard
|
||||||
|
@onready var grid: Control = $Dashboard/Grid
|
||||||
|
@onready var canvas: Control = $Dashboard/Canvas
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
grid.gui_input.connect(canvas.handle_grid_input)
|
||||||
|
$Toolbar/Add.pressed.connect(func() -> void: $Library.visible = not $Library.visible)
|
||||||
|
$Library.element_requested.connect(_add_element)
|
||||||
|
canvas.layout_saved.connect(func() -> void: $Toolbar/Hint.text = "Esc to return")
|
||||||
|
canvas.load_failed.connect(func() -> void: $Toolbar/Hint.text = "Could not load saved layout")
|
||||||
|
$Toolbar/Reset.pressed.connect(canvas.reset_layout)
|
||||||
|
canvas.save_failed.connect(func() -> void: $Toolbar/Hint.text = "Could not save layout")
|
||||||
|
$Toolbar/Return.pressed.connect(func() -> void: set_overview(false))
|
||||||
|
resized.connect(_resize)
|
||||||
|
get_window().mouse_entered.connect(func() -> void: _pointer_in_window = true)
|
||||||
|
get_window().mouse_exited.connect(_leave_corner)
|
||||||
|
theme_changed.connect(queue_redraw)
|
||||||
|
_update_grid()
|
||||||
|
canvas.load_layout()
|
||||||
|
_resize()
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if FOCUS_SCOPES.dispatch_focus_shortcut(event, get_tree()):
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
|
if event is InputEventMouseMotion and hot_corner_enabled:
|
||||||
|
_pointer_in_window = true
|
||||||
|
_update_corner(get_window().get_final_transform() * event.position)
|
||||||
|
elif overview_active and event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
||||||
|
if $Library.visible:
|
||||||
|
$Library.hide()
|
||||||
|
else:
|
||||||
|
set_overview(false)
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
elif overview_active and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
|
var pointer: Vector2 = get_global_transform_with_canvas().affine_inverse() * event.position
|
||||||
|
var in_library: bool = $Library.visible and $Library.get_global_rect().has_point(event.position)
|
||||||
|
if not in_library and not preview_rect().has_point(pointer) and not $Toolbar.get_global_rect().has_point(event.position):
|
||||||
|
set_overview(false)
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
var window := get_window()
|
||||||
|
# Letterbox regions may not send canvas mouse-motion events.
|
||||||
|
if hot_corner_enabled and window.visible and (_pointer_in_window or window.has_focus()):
|
||||||
|
_update_corner(window.get_final_transform() * window.get_mouse_position())
|
||||||
|
else:
|
||||||
|
_leave_corner()
|
||||||
|
if hot_corner_enabled and _corner_hover and _corner_armed and not overview_active:
|
||||||
|
_dwell += delta
|
||||||
|
if _dwell >= corner_dwell:
|
||||||
|
_corner_armed = false
|
||||||
|
set_overview(true)
|
||||||
|
|
||||||
|
func _update_corner(window_position: Vector2) -> void:
|
||||||
|
_corner_hover = Rect2(Vector2.ZERO, Vector2.ONE * corner_size).has_point(window_position)
|
||||||
|
if not _corner_hover:
|
||||||
|
_corner_armed = true
|
||||||
|
_dwell = 0
|
||||||
|
|
||||||
|
func _leave_corner() -> void:
|
||||||
|
_pointer_in_window = false
|
||||||
|
_corner_hover = false
|
||||||
|
_dwell = 0
|
||||||
|
|
||||||
|
func set_overview(active: bool) -> void:
|
||||||
|
if overview_active == active:
|
||||||
|
return
|
||||||
|
if not active:
|
||||||
|
$Library.hide()
|
||||||
|
canvas.set_edit_mode(active)
|
||||||
|
overview_active = active
|
||||||
|
_corner_armed = false
|
||||||
|
_dwell = 0
|
||||||
|
if is_instance_valid(_tween):
|
||||||
|
_tween.kill()
|
||||||
|
grid.visible = active
|
||||||
|
$Toolbar.visible = active
|
||||||
|
queue_redraw()
|
||||||
|
var target := Vector2.ONE * overview_scale if active else Vector2.ONE
|
||||||
|
if transition_seconds <= 0:
|
||||||
|
dashboard.scale = target
|
||||||
|
else:
|
||||||
|
_tween = create_tween().set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
|
||||||
|
_tween.tween_property(dashboard, "scale", target, transition_seconds)
|
||||||
|
overview_changed.emit(active)
|
||||||
|
|
||||||
|
func _add_element(element_type: String) -> void:
|
||||||
|
if canvas.add_element(element_type) == null:
|
||||||
|
$Library.show_no_space()
|
||||||
|
else:
|
||||||
|
$Library.hide()
|
||||||
|
|
||||||
|
func preview_rect() -> Rect2:
|
||||||
|
return Rect2((size - size * dashboard.scale) / 2, size * dashboard.scale)
|
||||||
|
|
||||||
|
func _resize() -> void:
|
||||||
|
_dwell = 0
|
||||||
|
if is_instance_valid(_tween):
|
||||||
|
_tween.kill()
|
||||||
|
dashboard.pivot_offset = size / 2
|
||||||
|
dashboard.scale = Vector2.ONE * overview_scale if overview_active else Vector2.ONE
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
func _update_grid() -> void:
|
||||||
|
canvas.grid_size = grid_size
|
||||||
|
canvas.arrange_items()
|
||||||
|
grid.grid_size = grid_size
|
||||||
|
grid.monitor_boundary = monitor_boundary
|
||||||
|
grid.queue_redraw()
|
||||||
|
$Toolbar/Title.text = "LAYOUT OVERVIEW %d × %d" % [grid_size.x, grid_size.y]
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
if overview_active:
|
||||||
|
draw_rect(Rect2(Vector2.ZERO, size), get_theme_color("instrument_empty", "SurfaceSide"))
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b4g15anvgpot8
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
@export_enum("Landscape", "Dual portrait") var configuration := 0
|
||||||
|
@export var use_portrait_monitors := true
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if configuration == 1:
|
||||||
|
$Overview.overview_changed.connect($RightWindow/RightOverview.set_overview)
|
||||||
|
$RightWindow/RightOverview.overview_changed.connect($Overview.set_overview)
|
||||||
|
$RightWindow.close_requested.connect(get_tree().quit)
|
||||||
|
if get_parent() == get_tree().root:
|
||||||
|
_configure_windows.call_deferred()
|
||||||
|
|
||||||
|
func _configure_windows() -> void:
|
||||||
|
var window := get_window()
|
||||||
|
var reference := Vector2i(1920, 1080) if configuration == 0 else Vector2i(1080, 1920)
|
||||||
|
_prepare_window(window, reference)
|
||||||
|
if configuration == 0:
|
||||||
|
var usable := DisplayServer.screen_get_usable_rect()
|
||||||
|
window.size = _preview_size(reference, usable.size, 1)
|
||||||
|
window.title = "Layout / landscape"
|
||||||
|
return
|
||||||
|
var right: Window = $RightWindow
|
||||||
|
_prepare_window(right, reference)
|
||||||
|
window.title = "ROV / left portrait"
|
||||||
|
var portrait_screens: Array[int] = []
|
||||||
|
for screen in range(DisplayServer.get_screen_count()):
|
||||||
|
var dimensions := DisplayServer.screen_get_size(screen)
|
||||||
|
if dimensions.y > dimensions.x:
|
||||||
|
portrait_screens.append(screen)
|
||||||
|
if use_portrait_monitors and portrait_screens.size() >= 2:
|
||||||
|
window.current_screen = portrait_screens[0]
|
||||||
|
window.mode = Window.MODE_MAXIMIZED
|
||||||
|
right.current_screen = portrait_screens[1]
|
||||||
|
right.show()
|
||||||
|
right.mode = Window.MODE_MAXIMIZED
|
||||||
|
else:
|
||||||
|
var usable := DisplayServer.screen_get_usable_rect()
|
||||||
|
var preview := _preview_size(reference, usable.size, 2)
|
||||||
|
window.size = preview
|
||||||
|
right.size = preview
|
||||||
|
var origin := usable.position + (usable.size - Vector2i(preview.x * 2 + 16, preview.y)) / 2
|
||||||
|
window.position = origin
|
||||||
|
right.position = origin + Vector2i(preview.x + 16, 0)
|
||||||
|
right.show()
|
||||||
|
|
||||||
|
func _prepare_window(window: Window, reference: Vector2i) -> void:
|
||||||
|
window.content_scale_factor = 1.0
|
||||||
|
window.content_scale_size = reference
|
||||||
|
window.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
|
||||||
|
window.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_KEEP
|
||||||
|
window.mode = Window.MODE_WINDOWED
|
||||||
|
|
||||||
|
func _preview_size(reference: Vector2i, usable: Vector2i, count: int) -> Vector2i:
|
||||||
|
var factor := minf(float(usable.x) * 0.85 / (reference.x * count + 16 * (count - 1)), float(usable.y) * 0.85 / reference.y)
|
||||||
|
return Vector2i(Vector2(reference) * clampf(factor, 0.1, 1.0))
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bxj3puinklg62
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
@tool
|
||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
@export var title := "RESERVED":
|
||||||
|
set(value):
|
||||||
|
title = value
|
||||||
|
if is_node_ready():
|
||||||
|
%Title.text = value
|
||||||
|
@export_multiline var description := "":
|
||||||
|
set(value):
|
||||||
|
description = value
|
||||||
|
if is_node_ready():
|
||||||
|
%Description.text = value
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
%Title.text = title
|
||||||
|
%Description.text = description
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://btaf1ke22k5ij
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
static func read_json(path: String) -> Variant:
|
||||||
|
var file := FileAccess.open(path, FileAccess.READ)
|
||||||
|
if file == null or file.get_length() > 1048576:
|
||||||
|
return null
|
||||||
|
var parser := JSON.new()
|
||||||
|
if parser.parse(file.get_as_text()) != OK:
|
||||||
|
return null
|
||||||
|
return parser.data
|
||||||
|
|
||||||
|
static func write_json(path: String, document: Dictionary) -> Error:
|
||||||
|
var absolute := ProjectSettings.globalize_path(path)
|
||||||
|
var error := DirAccess.make_dir_recursive_absolute(absolute.get_base_dir())
|
||||||
|
if error != OK:
|
||||||
|
return error
|
||||||
|
var temporary := absolute + ".tmp"
|
||||||
|
var file := FileAccess.open(temporary, FileAccess.WRITE)
|
||||||
|
if file == null:
|
||||||
|
return FileAccess.get_open_error()
|
||||||
|
file.store_string(JSON.stringify(document, "\t") + "\n")
|
||||||
|
file.flush()
|
||||||
|
error = file.get_error()
|
||||||
|
file.close()
|
||||||
|
if error != OK:
|
||||||
|
return error
|
||||||
|
return DirAccess.rename_absolute(temporary, absolute)
|
||||||
|
|
||||||
|
static func integer_array(value: Variant, length: int) -> bool:
|
||||||
|
if not value is Array or value.size() != length:
|
||||||
|
return false
|
||||||
|
for number in value:
|
||||||
|
if not (number is int or number is float) or not is_finite(float(number)) or float(number) != floorf(float(number)):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://ceg5mij8na3qr
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
extends VBoxContainer
|
||||||
|
|
||||||
|
var elapsed := 0.0
|
||||||
|
@onready var panel = $LinkPanel
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
$Controls/Contrast.toggled.connect(func(high: bool) -> void:
|
||||||
|
theme = load("res://themes/theme_highcontrast.tres" if high else "res://themes/theme.tres")
|
||||||
|
)
|
||||||
|
$Controls/State.item_selected.connect(func(index: int) -> void:
|
||||||
|
if index == 6: panel.clear_telemetry()
|
||||||
|
)
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
elapsed += delta
|
||||||
|
match $Controls/State.selected:
|
||||||
|
0: panel.set_telemetry(6.42 + sin(elapsed) * 0.12, 1.08, 0.02)
|
||||||
|
1: panel.set_telemetry(8.65, 7.9, 0.02)
|
||||||
|
2: panel.set_telemetry(9.72, 10.2, 0.02)
|
||||||
|
3: panel.set_telemetry(3.2, 0.4, 2.3, 1)
|
||||||
|
4: panel.set_telemetry(0, 0, 0, 2)
|
||||||
|
5, 6: pass
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://i5dbrwr8d2gr
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
@tool
|
||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
enum Connection { CONNECTED, DEGRADED, DISCONNECTED }
|
||||||
|
|
||||||
|
@export var downlink_capacity_gbps := 10.0
|
||||||
|
@export var uplink_capacity_gbps := 10.0
|
||||||
|
@export_range(0, 1) var warning_fraction := 0.8
|
||||||
|
@export_range(0, 1) var fault_fraction := 0.95
|
||||||
|
@export var loss_window_seconds := 10.0
|
||||||
|
@export var stale_after_seconds := 2.0
|
||||||
|
@export var link_icon: Texture2D:
|
||||||
|
set(value):
|
||||||
|
link_icon = value
|
||||||
|
if is_node_ready():
|
||||||
|
%LinkIcon.texture = value
|
||||||
|
|
||||||
|
var _received_ms := -1
|
||||||
|
var _state := Connection.CONNECTED
|
||||||
|
var _down := 0.0
|
||||||
|
var _up := 0.0
|
||||||
|
var _loss := 0.0
|
||||||
|
var _stale := false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
%LinkIcon.texture = link_icon
|
||||||
|
theme_changed.connect(_update_icon)
|
||||||
|
_update_icon()
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
## Rates in Gbps; loss in percent over loss_window_seconds.
|
||||||
|
func set_telemetry(down: float, up: float, loss_percent: float, state: int = Connection.CONNECTED) -> bool:
|
||||||
|
if not is_finite(down) or not is_finite(up) or not is_finite(loss_percent) or down < 0 or up < 0 or loss_percent < 0 or loss_percent > 100 or state < 0 or state > 2:
|
||||||
|
return false
|
||||||
|
_down = down
|
||||||
|
_up = up
|
||||||
|
_loss = loss_percent
|
||||||
|
_state = state
|
||||||
|
_received_ms = Time.get_ticks_msec()
|
||||||
|
_refresh()
|
||||||
|
return true
|
||||||
|
|
||||||
|
func clear_telemetry() -> void:
|
||||||
|
_received_ms = -1
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
var stale := _received_ms >= 0 and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000
|
||||||
|
if stale != _stale:
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
func _refresh() -> void:
|
||||||
|
if not is_node_ready():
|
||||||
|
return
|
||||||
|
var missing := _received_ms < 0
|
||||||
|
_stale = not missing and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000
|
||||||
|
var measured := not missing and _state != Connection.DISCONNECTED
|
||||||
|
%State.text = "NO TELEMETRY" if missing else ("STALE" if _stale else ["CONNECTED", "DEGRADED", "DISCONNECTED"][_state])
|
||||||
|
%State.theme_type_variation = "LinkMuted" if missing else ("LinkRateWarning" if _stale or _state == Connection.DEGRADED else ("LinkRateFault" if _state == Connection.DISCONNECTED else "Label"))
|
||||||
|
var gauges := [%Downlink, %Uplink]
|
||||||
|
for index in range(2):
|
||||||
|
gauges[index].capacity_gbps = maxf([downlink_capacity_gbps, uplink_capacity_gbps][index], 0.001)
|
||||||
|
gauges[index].warning_fraction = warning_fraction
|
||||||
|
gauges[index].fault_fraction = maxf(warning_fraction, fault_fraction)
|
||||||
|
gauges[index].rate_gbps = [_down, _up][index]
|
||||||
|
gauges[index].available = measured
|
||||||
|
gauges[index].refresh()
|
||||||
|
%LossValue.text = "%.2f%%" % _loss if measured else "—"
|
||||||
|
%LossWindow.text = "%.0f s window" % loss_window_seconds + (" · held" if _stale else "")
|
||||||
|
|
||||||
|
func _update_icon() -> void:
|
||||||
|
var tint := get_theme_color("status_icon", "SurfaceSide")
|
||||||
|
tint.a = 0.06
|
||||||
|
%LinkIcon.self_modulate = tint
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://84jt1vgxm2ei
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
@export var direction := "↓ DOWNLINK"
|
||||||
|
var capacity_gbps := 10.0
|
||||||
|
var warning_fraction := 0.8
|
||||||
|
var fault_fraction := 0.95
|
||||||
|
var rate_gbps := 0.0
|
||||||
|
var available := false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
resized.connect(queue_redraw)
|
||||||
|
theme_changed.connect(queue_redraw)
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
func utilization() -> float:
|
||||||
|
return rate_gbps / maxf(capacity_gbps, 0.001)
|
||||||
|
|
||||||
|
func severity() -> int:
|
||||||
|
if not available:
|
||||||
|
return 0
|
||||||
|
return 2 if utilization() >= fault_fraction else (1 if utilization() >= warning_fraction else 0)
|
||||||
|
|
||||||
|
func refresh() -> void:
|
||||||
|
if not is_node_ready():
|
||||||
|
return
|
||||||
|
%Direction.text = direction
|
||||||
|
%Direction.theme_type_variation = "LinkMuted" if not available else ["LinkRateNormal", "LinkRateWarning", "LinkRateFault"][severity()]
|
||||||
|
%Value.text = "%.2f" % rate_gbps if available else "—"
|
||||||
|
%Capacity.text = "%s Gbps max" % String.num(capacity_gbps, 2)
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
var radius := maxf(1.0, minf(size.x, size.y) / 2 - 6)
|
||||||
|
var center := size / 2
|
||||||
|
var start := deg_to_rad(140)
|
||||||
|
var sweep := deg_to_rad(260)
|
||||||
|
draw_arc(center, radius, start, start + sweep, 96, get_theme_color("instrument_grid", "SurfaceSide"), 3, true)
|
||||||
|
for fraction in [warning_fraction, fault_fraction]:
|
||||||
|
var radial := Vector2.from_angle(start + sweep * fraction)
|
||||||
|
draw_line(center + radial * (radius - 4), center + radial * (radius + 4), get_theme_color("instrument_axis", "SurfaceSide"), 1, true)
|
||||||
|
if available and rate_gbps > 0:
|
||||||
|
var role: String = ["instrument_accent", "warning", "fault"][severity()]
|
||||||
|
draw_arc(center, radius, start, start + sweep * clampf(utilization(), 0, 1), 96, get_theme_color(role, "SurfaceSide"), 4, true)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b1pcqygbaipjh
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
extends VBoxContainer
|
||||||
|
|
||||||
|
var elapsed := 0.0
|
||||||
|
var running := true
|
||||||
|
@onready var instrument = $MotionPanel
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
$Pause.toggled.connect(func(paused: bool) -> void: running = not paused)
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
if not running:
|
||||||
|
return
|
||||||
|
elapsed += delta
|
||||||
|
instrument.set_telemetry(
|
||||||
|
Vector3(24 * sin(elapsed * 0.5), 18 * sin(elapsed * 0.3), fposmod(350 + elapsed * 4, 360)),
|
||||||
|
Vector3(1.8 * cos(elapsed * 0.5), 0.9 * cos(elapsed * 0.3), 0.6),
|
||||||
|
Vector3(2.8 * sin(elapsed * 0.7), 1.9 * cos(elapsed * 0.5), 1.2 * sin(elapsed * 0.4))
|
||||||
|
)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c5mncovlnarve
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
@export_enum("Attitude", "Heading", "Acceleration", "Vertical acceleration") var instrument: int = 0
|
||||||
|
## Shares the circle radius with another instrument.
|
||||||
|
@export var radius_source: Control
|
||||||
|
|
||||||
|
var attitude := Vector3.ZERO
|
||||||
|
var acceleration := Vector3.ZERO
|
||||||
|
var available := false
|
||||||
|
var full_scale := 5.0
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
resized.connect(queue_redraw)
|
||||||
|
theme_changed.connect(queue_redraw)
|
||||||
|
if is_instance_valid(radius_source):
|
||||||
|
radius_source.resized.connect(queue_redraw)
|
||||||
|
|
||||||
|
func _text(at: Vector2, value: String, font_size: int = 16, color: Variant = null) -> void:
|
||||||
|
draw_string(get_theme_default_font(), at, value, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, _ink() if color == null else color)
|
||||||
|
|
||||||
|
func _line(a: Vector2, b: Vector2, color: Variant = null, width: float = 1.0) -> void:
|
||||||
|
draw_line(a, b, _role("instrument_grid") if color == null else color, width, true)
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
match instrument:
|
||||||
|
0: _draw_attitude(available)
|
||||||
|
1: _draw_heading()
|
||||||
|
2: _draw_acceleration()
|
||||||
|
3: _draw_vertical()
|
||||||
|
|
||||||
|
func _horizon_half(center: Vector2, radius: float, normal: Vector2, displacement: float) -> PackedVector2Array:
|
||||||
|
var polygon := PackedVector2Array()
|
||||||
|
var previous := Vector2(radius, 0)
|
||||||
|
var previous_distance := previous.dot(normal) - displacement
|
||||||
|
for step in range(1, 129):
|
||||||
|
var point := Vector2.from_angle(step * TAU / 128.0) * radius
|
||||||
|
var distance := point.dot(normal) - displacement
|
||||||
|
if (distance >= 0) != (previous_distance >= 0):
|
||||||
|
polygon.append(center + previous.lerp(point, previous_distance / (previous_distance - distance)))
|
||||||
|
if distance >= 0:
|
||||||
|
polygon.append(center + point)
|
||||||
|
previous = point
|
||||||
|
previous_distance = distance
|
||||||
|
return polygon
|
||||||
|
|
||||||
|
func _draw_attitude(available: bool) -> void:
|
||||||
|
var center := circle_center()
|
||||||
|
var radius := circle_radius()
|
||||||
|
draw_circle(center, radius, _role("horizon_sky") if available else _role("instrument_empty"))
|
||||||
|
if available:
|
||||||
|
var rotation := deg_to_rad(-attitude.x)
|
||||||
|
var normal := Vector2.DOWN.rotated(rotation)
|
||||||
|
var direction := Vector2.RIGHT.rotated(rotation)
|
||||||
|
var displacement := attitude.y * radius / 60.0
|
||||||
|
var ground := _horizon_half(center, radius, normal, displacement)
|
||||||
|
if ground.size() >= 3:
|
||||||
|
draw_colored_polygon(ground, _role("horizon_ground"))
|
||||||
|
if absf(displacement) < radius:
|
||||||
|
var half_chord := sqrt(radius * radius - displacement * displacement)
|
||||||
|
_line(center + normal * displacement - direction * half_chord, center + normal * displacement + direction * half_chord, _ink(), 3)
|
||||||
|
for pitch_mark in range(-90, 91, 10):
|
||||||
|
var y: float = (attitude.y - pitch_mark) * radius / 60.0
|
||||||
|
if pitch_mark != 0 and absf(y) < radius - 25:
|
||||||
|
var p := center + normal * y
|
||||||
|
var width := 26.0 if pitch_mark % 20 == 0 else 15.0
|
||||||
|
_line(p - direction * width, p + direction * width, _ink(), 2)
|
||||||
|
if pitch_mark % 20 == 0:
|
||||||
|
_text(p + direction * (width + 7) + Vector2(0, 6), str(absi(pitch_mark)), 18)
|
||||||
|
for bank in [-60, -30, 0, 30, 60]:
|
||||||
|
var radial := Vector2.UP.rotated(deg_to_rad(bank))
|
||||||
|
_line(center + radial * (radius - 10), center + radial * (radius - 1), _ink(), 2)
|
||||||
|
var pointer := Vector2.UP.rotated(rotation)
|
||||||
|
draw_colored_polygon(PackedVector2Array([center + pointer * (radius - 17), center + pointer.rotated(0.07) * (radius - 29), center + pointer.rotated(-0.07) * (radius - 29)]), _role("instrument_accent"))
|
||||||
|
draw_arc(center, radius, 0, TAU, 128, _role("instrument_grid"), 2, true)
|
||||||
|
_line(center + Vector2(-47, 0), center + Vector2(-14, 0), _role("instrument_accent"), 4)
|
||||||
|
_line(center + Vector2(14, 0), center + Vector2(47, 0), _role("instrument_accent"), 4)
|
||||||
|
_line(center + Vector2(-14, 0), center + Vector2(-14, 8), _role("instrument_accent"), 3)
|
||||||
|
_line(center + Vector2(14, 0), center + Vector2(14, 8), _role("instrument_accent"), 3)
|
||||||
|
draw_circle(center, 4, _role("instrument_accent"))
|
||||||
|
func _heading_tick_x(degrees: float) -> float:
|
||||||
|
return size.x / 2 + (degrees - attitude.z) * size.x / 100.0
|
||||||
|
|
||||||
|
func _draw_heading() -> void:
|
||||||
|
if available:
|
||||||
|
var first_tick := int(floor((attitude.z - 50.0) / 10.0)) * 10
|
||||||
|
for degrees in range(first_tick, first_tick + 120, 10):
|
||||||
|
var x := _heading_tick_x(degrees)
|
||||||
|
if x < 18 or x > size.x - 18:
|
||||||
|
continue
|
||||||
|
_line(Vector2(x, 7), Vector2(x, 16), _ink(), 2)
|
||||||
|
if degrees % 20 == 0:
|
||||||
|
_text(Vector2(x - 17, 36), "%03d" % int(fposmod(degrees, 360)), 18, _ink())
|
||||||
|
_line(Vector2(size.x / 2, 0), Vector2(size.x / 2, 20), _role("instrument_accent"), 3)
|
||||||
|
|
||||||
|
func _draw_acceleration() -> void:
|
||||||
|
var radius := circle_radius()
|
||||||
|
var center := circle_center()
|
||||||
|
for fraction in [0.5, 1.0]:
|
||||||
|
draw_arc(center, radius * fraction, 0, TAU, 96, _role("instrument_grid"), 2, true)
|
||||||
|
_line(center - Vector2(radius, 0), center + Vector2(radius, 0), _role("instrument_axis"))
|
||||||
|
_line(center - Vector2(0, radius), center + Vector2(0, radius), _role("instrument_axis"))
|
||||||
|
_text(center + Vector2(-14, -radius - 8), "+X", 18)
|
||||||
|
_text(center + Vector2(radius + 2, 6), "+Y", 18)
|
||||||
|
if available:
|
||||||
|
var planar := Vector2(acceleration.y, -acceleration.x) / maxf(full_scale, 0.1)
|
||||||
|
var endpoint := center + planar.limit_length(1.0) * radius
|
||||||
|
_line(center, endpoint, _role("instrument_accent"), 4)
|
||||||
|
draw_circle(endpoint, 7, _role("instrument_accent"))
|
||||||
|
|
||||||
|
func _draw_vertical() -> void:
|
||||||
|
var center := circle_center()
|
||||||
|
var radius := circle_radius()
|
||||||
|
_line(center - Vector2(0, radius), center + Vector2(0, radius), _role("instrument_axis"), 2)
|
||||||
|
for fraction in [-1.0, -0.5, 0.0, 0.5, 1.0]:
|
||||||
|
var y: float = center.y + fraction * radius
|
||||||
|
_line(Vector2(center.x - 6, y), Vector2(center.x + 6, y), _ink(), 2)
|
||||||
|
if available:
|
||||||
|
var y := center.y + clampf(acceleration.z / maxf(full_scale, 0.1), -1, 1) * radius
|
||||||
|
_line(Vector2(center.x - 10, y), Vector2(center.x + 10, y), _role("instrument_accent"), 5)
|
||||||
|
|
||||||
|
func circle_radius() -> float:
|
||||||
|
if is_instance_valid(radius_source):
|
||||||
|
return radius_source.circle_radius()
|
||||||
|
return maxf(1.0, minf(size.x - 24, size.y - 44) / 2)
|
||||||
|
|
||||||
|
func circle_center() -> Vector2:
|
||||||
|
return size / 2 + Vector2(0, 10)
|
||||||
|
|
||||||
|
func _ink() -> Color:
|
||||||
|
return get_theme_color("font_color", "Label")
|
||||||
|
|
||||||
|
func _role(name: StringName) -> Color:
|
||||||
|
return get_theme_color(name, "SurfaceSide")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://1dn362pyibkx
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
## Telemetry conventions: docs/motion_panel.md.
|
||||||
|
|
||||||
|
@export var stale_after_seconds: float = 2.0
|
||||||
|
@export_range(0.1, 100.0) var acceleration_full_scale: float = 5.0:
|
||||||
|
set(value):
|
||||||
|
acceleration_full_scale = maxf(value, 0.1)
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
var attitude := Vector3.ZERO # roll, pitch, heading in degrees
|
||||||
|
var momentum := Vector3.ZERO
|
||||||
|
var acceleration := Vector3.ZERO
|
||||||
|
var _received_ms: int = -1
|
||||||
|
|
||||||
|
var _last_stale := false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
func set_telemetry(orientation_degrees: Vector3, angular_momentum: Vector3, linear_acceleration: Vector3) -> void:
|
||||||
|
if not orientation_degrees.is_finite() or not angular_momentum.is_finite() or not linear_acceleration.is_finite():
|
||||||
|
return
|
||||||
|
attitude = Vector3(wrapf(orientation_degrees.x, -180, 180), wrapf(orientation_degrees.y, -180, 180), fposmod(orientation_degrees.z, 360))
|
||||||
|
momentum = angular_momentum
|
||||||
|
acceleration = linear_acceleration
|
||||||
|
_received_ms = Time.get_ticks_msec()
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
var stale := _is_stale()
|
||||||
|
if stale != _last_stale:
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
func _is_stale() -> bool:
|
||||||
|
return _received_ms >= 0 and (Time.get_ticks_msec() - _received_ms) / 1000.0 > stale_after_seconds
|
||||||
|
|
||||||
|
func _number(value: float, precision: int = 2) -> String:
|
||||||
|
return "—" if _received_ms < 0 else ("%+.*f" % [precision, value])
|
||||||
|
|
||||||
|
func _refresh() -> void:
|
||||||
|
if not is_node_ready():
|
||||||
|
return
|
||||||
|
_last_stale = _is_stale()
|
||||||
|
%Status.text = "STALE" if _last_stale else "NO DATA"
|
||||||
|
%StatusBanner.visible = _last_stale or _received_ms < 0
|
||||||
|
%Status.visible = true
|
||||||
|
%Roll.text = "R " + _number(attitude.x, 1) + "°"
|
||||||
|
%Pitch.text = "P " + _number(attitude.y, 1) + "°"
|
||||||
|
%Heading.text = "H " + ("%05.1f°" % attitude.z if _received_ms >= 0 else "—")
|
||||||
|
var accel_labels := [%AccelX, %AccelY, %AccelZ]
|
||||||
|
var momentum_labels := [%MomentumX, %MomentumY, %MomentumZ]
|
||||||
|
for axis in range(3):
|
||||||
|
accel_labels[axis].text = _number(acceleration[axis])
|
||||||
|
momentum_labels[axis].text = _number(momentum[axis])
|
||||||
|
%FullScale.text = "±%.1f" % acceleration_full_scale
|
||||||
|
%Limit.visible = _received_ms >= 0 and (Vector2(acceleration.x, acceleration.y).length() > acceleration_full_scale or absf(acceleration.z) > acceleration_full_scale)
|
||||||
|
for graphic in [%Horizon, %HeadingTape, %AccelerationPlot, %VerticalAcceleration]:
|
||||||
|
graphic.attitude = attitude
|
||||||
|
graphic.acceleration = acceleration
|
||||||
|
graphic.available = _received_ms >= 0
|
||||||
|
graphic.full_scale = acceleration_full_scale
|
||||||
|
graphic.queue_redraw()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dh2vnk35potq3
|
||||||
@@ -16,8 +16,12 @@ signal deactivated
|
|||||||
@export_node_path("Control") var initial_focus: NodePath
|
@export_node_path("Control") var initial_focus: NodePath
|
||||||
|
|
||||||
var is_active: bool = false
|
var is_active: bool = false
|
||||||
|
var _layout_editing := false
|
||||||
|
var _registered := false
|
||||||
|
var _blocked_shortcuts: Dictionary = {}
|
||||||
|
var _frame_host: Control
|
||||||
|
const EMBEDDED_PANEL = preload("res://themes/layout_embedded_panel.tres")
|
||||||
var _last_focus: Control
|
var _last_focus: Control
|
||||||
var _shortcut_available: bool = true
|
|
||||||
var _initial_focus_control: Control
|
var _initial_focus_control: Control
|
||||||
@onready var content: VBoxContainer = $MarginContainer/VBoxContainer/Content
|
@onready var content: VBoxContainer = $MarginContainer/VBoxContainer/Content
|
||||||
@onready var _panel: PanelContainer = $PanelContainer
|
@onready var _panel: PanelContainer = $PanelContainer
|
||||||
@@ -33,16 +37,13 @@ func _ready() -> void:
|
|||||||
_route_content_child(child)
|
_route_content_child(child)
|
||||||
child_entered_tree.connect(_on_child_entered_tree)
|
child_entered_tree.connect(_on_child_entered_tree)
|
||||||
add_to_group("keyboard_focus_containers")
|
add_to_group("keyboard_focus_containers")
|
||||||
|
_registered = true
|
||||||
focus_mode = Control.FOCUS_NONE
|
focus_mode = Control.FOCUS_NONE
|
||||||
_shortcut.focus_mode = Control.FOCUS_NONE
|
_shortcut.focus_mode = Control.FOCUS_NONE
|
||||||
_shortcut.pressed.connect(activate)
|
_shortcut.pressed.connect(activate)
|
||||||
for other in get_tree().get_nodes_in_group("keyboard_focus_containers"):
|
refresh_shortcuts(get_tree())
|
||||||
if other != self and other.get_viewport() == get_viewport() and other.focus_key == focus_key and other._shortcut_available:
|
get_window().focus_exited.connect(deactivate)
|
||||||
_shortcut_available = false
|
theme_changed.connect(_update_style)
|
||||||
_shortcut.disabled = true
|
|
||||||
_shortcut.tooltip_text = "Shortcut already assigned to " + other.title
|
|
||||||
push_warning("Duplicate panel shortcut: " + OS.get_keycode_string(focus_key))
|
|
||||||
break
|
|
||||||
get_viewport().gui_focus_changed.connect(_on_focus_changed)
|
get_viewport().gui_focus_changed.connect(_on_focus_changed)
|
||||||
visibility_changed.connect(_on_visibility_changed)
|
visibility_changed.connect(_on_visibility_changed)
|
||||||
_update_style()
|
_update_style()
|
||||||
@@ -54,7 +55,10 @@ func _refresh_header() -> void:
|
|||||||
if label != null:
|
if label != null:
|
||||||
label.text = title
|
label.text = title
|
||||||
if badge != null:
|
if badge != null:
|
||||||
|
badge.visible = focus_key != KEY_NONE
|
||||||
|
badge.disabled = not is_shortcut_available(focus_key)
|
||||||
badge.text = OS.get_keycode_string(focus_key) + (" · ACTIVE" if is_active else "")
|
badge.text = OS.get_keycode_string(focus_key) + (" · ACTIVE" if is_active else "")
|
||||||
|
badge.tooltip_text = "Shortcut already used by " + str(_blocked_shortcuts.get(focus_key, "")) if _blocked_shortcuts.has(focus_key) else "Focus this panel"
|
||||||
|
|
||||||
func _on_child_entered_tree(child: Node) -> void:
|
func _on_child_entered_tree(child: Node) -> void:
|
||||||
# SceneTree may still be adding the node when this signal fires.
|
# SceneTree may still be adding the node when this signal fires.
|
||||||
@@ -68,11 +72,12 @@ func _route_content_child(child: Node) -> void:
|
|||||||
child.reparent(content, false)
|
child.reparent(content, false)
|
||||||
|
|
||||||
func activate() -> void:
|
func activate() -> void:
|
||||||
if Engine.is_editor_hint() or not is_visible_in_tree() or not _shortcut_available:
|
if Engine.is_editor_hint() or not focus_available():
|
||||||
return
|
return
|
||||||
for other in get_tree().get_nodes_in_group("keyboard_focus_containers"):
|
for other in get_tree().get_nodes_in_group("keyboard_focus_containers"):
|
||||||
if other != self and other.get_viewport() == get_viewport():
|
if other != self:
|
||||||
other.deactivate()
|
other.deactivate()
|
||||||
|
get_window().grab_focus()
|
||||||
var was_active := is_active
|
var was_active := is_active
|
||||||
is_active = true
|
is_active = true
|
||||||
focus_mode = Control.FOCUS_ALL
|
focus_mode = Control.FOCUS_ALL
|
||||||
@@ -105,15 +110,14 @@ func deactivate() -> void:
|
|||||||
deactivated.emit()
|
deactivated.emit()
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
if Engine.is_editor_hint() or not event is InputEventKey or not is_visible_in_tree():
|
if Engine.is_editor_hint() or not is_visible_in_tree() or _layout_editing:
|
||||||
|
return
|
||||||
|
if dispatch_focus_shortcut(event, get_tree()):
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
|
if not event is InputEventKey:
|
||||||
return
|
return
|
||||||
var key := event as InputEventKey
|
var key := event as InputEventKey
|
||||||
if key.keycode == focus_key and not key.alt_pressed and not key.ctrl_pressed and not key.meta_pressed and not key.shift_pressed and _shortcut_available:
|
|
||||||
# Consume releases and repeats as well, so shortcuts never become input.
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
if key.pressed and not key.echo:
|
|
||||||
activate()
|
|
||||||
return
|
|
||||||
if not is_active or not key.pressed:
|
if not is_active or not key.pressed:
|
||||||
return
|
return
|
||||||
if key.keycode == KEY_ESCAPE:
|
if key.keycode == KEY_ESCAPE:
|
||||||
@@ -145,8 +149,77 @@ func _on_focus_changed(control: Control) -> void:
|
|||||||
func _on_visibility_changed() -> void:
|
func _on_visibility_changed() -> void:
|
||||||
if not is_visible_in_tree():
|
if not is_visible_in_tree():
|
||||||
deactivate()
|
deactivate()
|
||||||
|
if is_inside_tree():
|
||||||
|
refresh_shortcuts(get_tree())
|
||||||
|
|
||||||
func _update_style() -> void:
|
func _update_style() -> void:
|
||||||
var style_name := "panel_active" if is_active else "panel_inactive"
|
var style_name := "panel_active" if is_active else "panel_inactive"
|
||||||
|
if is_instance_valid(_frame_host):
|
||||||
|
_panel.add_theme_stylebox_override("panel", EMBEDDED_PANEL)
|
||||||
|
_frame_host.add_theme_stylebox_override("panel", get_theme_stylebox(style_name, "SurfaceSide"))
|
||||||
|
else:
|
||||||
_panel.add_theme_stylebox_override("panel", get_theme_stylebox(style_name, "SurfaceSide"))
|
_panel.add_theme_stylebox_override("panel", get_theme_stylebox(style_name, "SurfaceSide"))
|
||||||
_refresh_header()
|
_refresh_header()
|
||||||
|
|
||||||
|
func get_focus_shortcuts() -> Array[Key]:
|
||||||
|
return [focus_key] if focus_key != KEY_NONE else []
|
||||||
|
|
||||||
|
func is_shortcut_available(key: Key) -> bool:
|
||||||
|
return key != KEY_NONE and not _blocked_shortcuts.has(key)
|
||||||
|
|
||||||
|
func focus_available() -> bool:
|
||||||
|
return _registered and is_inside_tree() and is_visible_in_tree() and not _layout_editing
|
||||||
|
|
||||||
|
func activate_shortcut(_key: Key) -> void:
|
||||||
|
activate()
|
||||||
|
|
||||||
|
func set_layout_editing(editing: bool) -> void:
|
||||||
|
_layout_editing = editing
|
||||||
|
if editing:
|
||||||
|
deactivate()
|
||||||
|
if is_inside_tree():
|
||||||
|
refresh_shortcuts(get_tree())
|
||||||
|
|
||||||
|
func set_frame_host(host: Control) -> void:
|
||||||
|
_frame_host = host
|
||||||
|
if is_node_ready():
|
||||||
|
_update_style()
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
_registered = false
|
||||||
|
if not Engine.is_editor_hint() and get_tree() != null:
|
||||||
|
refresh_shortcuts(get_tree())
|
||||||
|
|
||||||
|
static func refresh_shortcuts(tree: SceneTree) -> void:
|
||||||
|
var scopes := tree.get_nodes_in_group("keyboard_focus_containers")
|
||||||
|
scopes.sort_custom(func(a: Node, b: Node) -> bool: return a.get_instance_id() < b.get_instance_id())
|
||||||
|
var claimed: Dictionary = {}
|
||||||
|
for scope in scopes:
|
||||||
|
scope._blocked_shortcuts.clear()
|
||||||
|
if scope.focus_available():
|
||||||
|
for key in scope.get_focus_shortcuts():
|
||||||
|
if key == KEY_NONE:
|
||||||
|
continue
|
||||||
|
if claimed.has(key) and claimed[key] != scope:
|
||||||
|
scope._blocked_shortcuts[key] = claimed[key].title
|
||||||
|
else:
|
||||||
|
claimed[key] = scope
|
||||||
|
scope._refresh_header()
|
||||||
|
|
||||||
|
static func dispatch_focus_shortcut(event: InputEvent, tree: SceneTree) -> bool:
|
||||||
|
if not event is InputEventKey or event.alt_pressed or event.ctrl_pressed or event.meta_pressed or event.shift_pressed:
|
||||||
|
return false
|
||||||
|
var matches := false
|
||||||
|
for scope in tree.get_nodes_in_group("keyboard_focus_containers"):
|
||||||
|
if scope.focus_available() and event.keycode in scope.get_focus_shortcuts():
|
||||||
|
matches = true
|
||||||
|
break
|
||||||
|
if not matches:
|
||||||
|
return false
|
||||||
|
refresh_shortcuts(tree)
|
||||||
|
for scope in tree.get_nodes_in_group("keyboard_focus_containers"):
|
||||||
|
if scope.focus_available() and event.keycode in scope.get_focus_shortcuts() and scope.is_shortcut_available(event.keycode):
|
||||||
|
if event.pressed and not event.echo:
|
||||||
|
scope.activate_shortcut(event.keycode)
|
||||||
|
return true
|
||||||
|
return false
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
@export var minimum := 0.0
|
||||||
|
@export var maximum := 36.0
|
||||||
|
@export var warning_low := 0.0
|
||||||
|
@export var warning_high := 24.0
|
||||||
|
@export var fault_low := 0.0
|
||||||
|
@export var fault_high := 30.0
|
||||||
|
@export var nominal := NAN
|
||||||
|
var value := 0.0
|
||||||
|
var available := false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
resized.connect(queue_redraw)
|
||||||
|
theme_changed.connect(queue_redraw)
|
||||||
|
|
||||||
|
func _x(number: float) -> float:
|
||||||
|
return 4 + clampf((number - minimum) / maxf(maximum - minimum, 0.001), 0, 1) * (size.x - 8)
|
||||||
|
|
||||||
|
func _zone(low: float, high: float, role: String) -> void:
|
||||||
|
if high <= low:
|
||||||
|
return
|
||||||
|
var color := get_theme_color(role, "SurfaceSide")
|
||||||
|
color.a = 0.65
|
||||||
|
draw_rect(Rect2(_x(low), 4, _x(high) - _x(low), 6), color)
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
draw_rect(Rect2(4, 4, maxf(size.x - 8, 0), 6), get_theme_color("instrument_grid", "SurfaceSide"))
|
||||||
|
_zone(minimum, fault_low, "fault")
|
||||||
|
_zone(fault_low, warning_low, "warning")
|
||||||
|
_zone(warning_high, fault_high, "warning")
|
||||||
|
_zone(fault_high, maximum, "fault")
|
||||||
|
if is_finite(nominal):
|
||||||
|
draw_line(Vector2(_x(nominal), 0), Vector2(_x(nominal), 16), get_theme_color("instrument_accent", "SurfaceSide"), 2)
|
||||||
|
if available:
|
||||||
|
var x := _x(value)
|
||||||
|
var ink := get_theme_color("font_color", "Label")
|
||||||
|
draw_line(Vector2(x, 0), Vector2(x, 14), ink, 3)
|
||||||
|
draw_colored_polygon(PackedVector2Array([Vector2(x - 5, 0), Vector2(x + 5, 0), Vector2(x, 4)]), ink)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bqvhrignal143
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
@tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
@export_enum("Normal", "Warning", "Fault", "No data") var severity := 3
|
||||||
|
@export var pulse_alerts := true
|
||||||
|
var _phase := -1
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
resized.connect(queue_redraw)
|
||||||
|
theme_changed.connect(_update_icon)
|
||||||
|
_update_icon()
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
var phase := int(Time.get_ticks_msec() / 1000.0) % 2 if pulse_alerts else 0
|
||||||
|
if phase != _phase:
|
||||||
|
_phase = phase
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
if severity in [1, 2]:
|
||||||
|
var role := "warning" if severity == 1 else "fault"
|
||||||
|
if _phase == 1:
|
||||||
|
role += "_alternate"
|
||||||
|
draw_rect(Rect2(Vector2.ZERO, size), get_theme_color(role, "SurfaceSide"))
|
||||||
|
func _update_icon() -> void:
|
||||||
|
var tint := get_theme_color("status_icon", "SurfaceSide")
|
||||||
|
tint.a = 0.09
|
||||||
|
$ModeIcon.self_modulate = tint
|
||||||
|
queue_redraw()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://rh8gwu8mfqx2
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
extends VBoxContainer
|
||||||
|
|
||||||
|
@onready var panel = $VehicleStatusPanel
|
||||||
|
var elapsed := 0.0
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
$Controls/State.item_selected.connect(_select_state)
|
||||||
|
$Controls/Contrast.toggled.connect(func(high: bool) -> void:
|
||||||
|
theme = load("res://themes/theme_highcontrast.tres" if high else "res://themes/theme.tres")
|
||||||
|
)
|
||||||
|
$Controls/Pulse.toggled.connect(func(enabled: bool) -> void: panel.pulse_alerts = enabled)
|
||||||
|
_select_state(0)
|
||||||
|
|
||||||
|
func _select_state(index: int) -> void:
|
||||||
|
match index:
|
||||||
|
0: panel.set_status("STATIONKEEP", 0, "", "Position hold active", "Thrusters enabled")
|
||||||
|
1: panel.set_status("Supply voltage low", 1, "PWR-012", "46.5 V · below 48 V nominal", "Check topside supply and tether load")
|
||||||
|
2: panel.set_status("Enclosure overtemperature", 2, "TMP-003", "68 °C · trip 65 °C · thrusters inhibited", "Inspect cooling before reset")
|
||||||
|
3: panel.set_status("Enclosure overtemperature", 2, "TMP-003", "68 °C · trip 65 °C · thrusters inhibited", "Inspect cooling before reset")
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
elapsed += delta
|
||||||
|
match $Controls/State.selected:
|
||||||
|
0: panel.set_telemetry(48.0, 18.2 + sin(elapsed) * 0.6, 874 + sin(elapsed) * 29, 34)
|
||||||
|
1: panel.set_telemetry(46.5, 25.5, 1186, 42)
|
||||||
|
2: panel.set_telemetry(48.0, 2.1, 101, 68)
|
||||||
|
3: pass
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://qipln371l5is
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
@tool
|
||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
@export var mode_icon: Texture2D:
|
||||||
|
set(value):
|
||||||
|
mode_icon = value
|
||||||
|
if is_node_ready():
|
||||||
|
%ModeIcon.texture = value
|
||||||
|
|
||||||
|
@export var stale_after_seconds := 2.0
|
||||||
|
@export var pulse_alerts := true:
|
||||||
|
set(value):
|
||||||
|
pulse_alerts = value
|
||||||
|
if is_node_ready():
|
||||||
|
%StatusArt.pulse_alerts = value
|
||||||
|
var _received_ms := -1
|
||||||
|
var _severity := 0
|
||||||
|
var _name := ""
|
||||||
|
var _code := ""
|
||||||
|
var _detail := ""
|
||||||
|
var _action := ""
|
||||||
|
var _stale := false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
%ModeIcon.texture = mode_icon
|
||||||
|
_refresh_status()
|
||||||
|
_refresh_references()
|
||||||
|
|
||||||
|
## Severity and fault explanations come from vehicle telemetry, not gauge zones.
|
||||||
|
func set_status(name_text: String, severity: int = 0, code: String = "", detail: String = "", action: String = "") -> void:
|
||||||
|
_name = name_text
|
||||||
|
_severity = clampi(severity, 0, 2)
|
||||||
|
_code = code
|
||||||
|
_detail = detail
|
||||||
|
_action = action
|
||||||
|
_refresh_status()
|
||||||
|
|
||||||
|
func set_telemetry(voltage: float, current: float, power: float, internal_temperature: float) -> void:
|
||||||
|
for number in [voltage, current, power, internal_temperature]:
|
||||||
|
if not is_finite(number):
|
||||||
|
return
|
||||||
|
_refresh_references()
|
||||||
|
_received_ms = Time.get_ticks_msec()
|
||||||
|
var values := [voltage, current, power, internal_temperature]
|
||||||
|
var labels := [%VoltageValue, %CurrentValue, %PowerValue, %TemperatureValue]
|
||||||
|
var gauges := [%VoltageGauge, %CurrentGauge, %PowerGauge, %TemperatureGauge]
|
||||||
|
for index in range(4):
|
||||||
|
labels[index].text = (["%.1f V", "%.1f A", "%.0f W", "%.1f °C"][index]) % values[index]
|
||||||
|
gauges[index].value = values[index]
|
||||||
|
gauges[index].available = true
|
||||||
|
gauges[index].queue_redraw()
|
||||||
|
_refresh_status()
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
var stale := _received_ms >= 0 and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000
|
||||||
|
if stale != _stale:
|
||||||
|
_refresh_status()
|
||||||
|
|
||||||
|
func _refresh_status() -> void:
|
||||||
|
if not is_node_ready():
|
||||||
|
return
|
||||||
|
_stale = _received_ms >= 0 and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000
|
||||||
|
var missing := _received_ms < 0
|
||||||
|
var unavailable := missing or _stale
|
||||||
|
%StatusArt.severity = 3 if unavailable else _severity
|
||||||
|
%StatusArt.pulse_alerts = pulse_alerts
|
||||||
|
%StatusArt.queue_redraw()
|
||||||
|
%ModeIcon.visible = not unavailable and _severity == 0
|
||||||
|
%Severity.text = "NO DATA" if missing else ("STALE" if _stale else ["", "WARNING", "FAULT"][_severity])
|
||||||
|
%StatusName.text = "Waiting for telemetry" if missing else (_name if not _name.is_empty() else "Mode unavailable")
|
||||||
|
%Code.text = _code
|
||||||
|
%Code.visible = not _code.is_empty() and not missing
|
||||||
|
%Detail.text = "" if missing else _detail
|
||||||
|
%Action.text = "Values held · connection overdue" if _stale else ("" if missing else _action)
|
||||||
|
var variant := ("VehicleWarningText" if _severity == 1 else "VehicleFaultText") if _severity in [1, 2] and not unavailable else "Label"
|
||||||
|
for label in [%Severity, %StatusName, %Code, %Detail, %Action]:
|
||||||
|
label.theme_type_variation = variant
|
||||||
|
|
||||||
|
func _refresh_references() -> void:
|
||||||
|
%VoltageReference.text = "/ %.0f V" % %VoltageGauge.nominal
|
||||||
|
%CurrentReference.text = "/ %.0f A" % %CurrentGauge.fault_high
|
||||||
|
%PowerReference.text = "/ %.0f W" % %PowerGauge.fault_high
|
||||||
|
%TemperatureReference.text = "/ %.0f °C" % %TemperatureGauge.fault_high
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://d3jrypgr15n4x
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
shader_type canvas_item;
|
||||||
|
render_mode unshaded;
|
||||||
|
|
||||||
|
uniform vec2 grid_size = vec2(32.0, 18.0);
|
||||||
|
uniform vec4 line_color : source_color;
|
||||||
|
uniform vec4 boundary_color : source_color;
|
||||||
|
uniform float monitor_boundary = 0.0;
|
||||||
|
|
||||||
|
float grid_coverage(vec2 coordinate, float width) {
|
||||||
|
vec2 distance_to_line = abs(fract(coordinate + 0.5) - 0.5);
|
||||||
|
vec2 pixels = distance_to_line / max(fwidth(coordinate), vec2(0.00001));
|
||||||
|
vec2 coverage = 1.0 - smoothstep(vec2(width - 0.5), vec2(width + 0.5), pixels);
|
||||||
|
return max(coverage.x, coverage.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
vec2 cells = UV * grid_size;
|
||||||
|
float minor = grid_coverage(cells, 0.6) * 0.4;
|
||||||
|
float major = grid_coverage(cells / 4.0, 0.8) * 0.85;
|
||||||
|
vec2 edge_pixels = min(UV, 1.0 - UV) / max(fwidth(UV), vec2(0.00001));
|
||||||
|
float border = 1.0 - smoothstep(0.5, 1.5, min(edge_pixels.x, edge_pixels.y));
|
||||||
|
float alpha = max(max(minor, major), border);
|
||||||
|
vec4 result = vec4(line_color.rgb, line_color.a * alpha);
|
||||||
|
if (monitor_boundary > 0.0 && monitor_boundary < grid_size.x) {
|
||||||
|
float distance_pixels = abs(cells.x - monitor_boundary) / max(fwidth(cells.x), 0.00001);
|
||||||
|
float boundary = 1.0 - smoothstep(1.0, 2.0, distance_pixels);
|
||||||
|
result = mix(result, boundary_color, boundary);
|
||||||
|
}
|
||||||
|
COLOR = result;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://beaucnaf4ol72
|
||||||
+86
-34
@@ -1,39 +1,36 @@
|
|||||||
extends SceneTree
|
extends SceneTree
|
||||||
|
|
||||||
const ACCENT = Color("ff7b00")
|
const COLORS = preload("res://themes/palette.gd")
|
||||||
const SECONDARY = Color("402e32")
|
|
||||||
const CLEAR = Color.TRANSPARENT
|
|
||||||
|
|
||||||
const HIGH_CONTRAST = {
|
|
||||||
"text": Color("f5f1ed"), "muted_text": Color("b9b0b3"),
|
|
||||||
"active_text": Color("141114"), "surface": Color("201b1f"),
|
|
||||||
"panel": Color("201b1f"), "input": Color("141114"),
|
|
||||||
"hover": SECONDARY, "pressed": ACCENT, "fill": ACCENT,
|
|
||||||
"disabled": Color("191619"), "border": Color("817278"),
|
|
||||||
"disabled_border": Color("51464c"), "focus": ACCENT,
|
|
||||||
"separator": SECONDARY, "thumb": Color("b9b0b3"),
|
|
||||||
"fill_text": Color("141114"),
|
|
||||||
}
|
|
||||||
const SOFT = {
|
|
||||||
"text": Color("e7e0dd"), "muted_text": Color("ada2a8"),
|
|
||||||
"active_text": Color("f5f1ed"), "surface": Color("292126"),
|
|
||||||
"panel": Color("201b1f"), "input": Color("141114"),
|
|
||||||
"hover": SECONDARY, "pressed": SECONDARY,
|
|
||||||
"fill": ACCENT, "disabled": Color("211c20"),
|
|
||||||
"border": CLEAR, "disabled_border": CLEAR,
|
|
||||||
"focus": ACCENT, "separator": SECONDARY,
|
|
||||||
"thumb": Color("d2c6c9"), "fill_text": Color("141114"),
|
|
||||||
}
|
|
||||||
|
|
||||||
func _initialize() -> void:
|
func _initialize() -> void:
|
||||||
for high_contrast in [false, true]:
|
for high_contrast in [false, true]:
|
||||||
var palette: Dictionary = HIGH_CONTRAST if high_contrast else SOFT
|
var palette: Dictionary = COLORS.HIGH_CONTRAST if high_contrast else COLORS.SOFT
|
||||||
var path := "res://themes/theme_highcontrast.tres" if high_contrast else "res://themes/theme.tres"
|
var path := "res://themes/theme_highcontrast.tres" if high_contrast else "res://themes/theme.tres"
|
||||||
var error := ResourceSaver.save(_build_theme(palette, high_contrast), path)
|
var error := ResourceSaver.save(_build_theme(palette, high_contrast), path)
|
||||||
if error != OK:
|
if error != OK:
|
||||||
push_error("Could not save %s: %s" % [path, error_string(error)])
|
push_error("Could not save %s: %s" % [path, error_string(error)])
|
||||||
quit(error)
|
quit(error)
|
||||||
return
|
return
|
||||||
|
var material := StandardMaterial3D.new()
|
||||||
|
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
material.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||||
|
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
material.albedo_color = COLORS.COMPASS_TEST
|
||||||
|
material.albedo_texture = load("res://assets/vector/dial.svg")
|
||||||
|
var material_error := ResourceSaver.save(material, "res://themes/compass_material.tres")
|
||||||
|
if material_error != OK:
|
||||||
|
push_error("Could not save compass material: " + error_string(material_error))
|
||||||
|
quit(material_error)
|
||||||
|
return
|
||||||
|
var config := ConfigFile.new()
|
||||||
|
var config_error := config.load("res://project.godot")
|
||||||
|
if config_error == OK and config.get_value("rendering", "environment/defaults/default_clear_color") != COLORS.VIEWPORT_BACKGROUND:
|
||||||
|
config.set_value("rendering", "environment/defaults/default_clear_color", COLORS.VIEWPORT_BACKGROUND)
|
||||||
|
config_error = config.save("res://project.godot")
|
||||||
|
if config_error != OK:
|
||||||
|
push_error("Could not update viewport background: " + error_string(config_error))
|
||||||
|
quit(config_error)
|
||||||
|
return
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
func _build_theme(palette: Dictionary, high_contrast: bool) -> Theme:
|
func _build_theme(palette: Dictionary, high_contrast: bool) -> Theme:
|
||||||
@@ -48,7 +45,7 @@ func _build_theme(palette: Dictionary, high_contrast: bool) -> Theme:
|
|||||||
if "disabled" in name or "placeholder" in name or "readonly" in name:
|
if "disabled" in name or "placeholder" in name or "readonly" in name:
|
||||||
color = palette.muted_text
|
color = palette.muted_text
|
||||||
elif "selection" in name:
|
elif "selection" in name:
|
||||||
color = SECONDARY
|
color = COLORS.SECONDARY
|
||||||
elif name in ["font_pressed_color", "font_hover_pressed_color"] and type in ["Button", "OptionButton", "MenuButton"]:
|
elif name in ["font_pressed_color", "font_hover_pressed_color"] and type in ["Button", "OptionButton", "MenuButton"]:
|
||||||
color = palette.active_text
|
color = palette.active_text
|
||||||
elif name == "font_selected_color" and type == "TabContainer":
|
elif name == "font_selected_color" and type == "TabContainer":
|
||||||
@@ -56,11 +53,11 @@ func _build_theme(palette: Dictionary, high_contrast: bool) -> Theme:
|
|||||||
elif name == "font_color" and type == "ProgressBar":
|
elif name == "font_color" and type == "ProgressBar":
|
||||||
color = palette.fill_text
|
color = palette.fill_text
|
||||||
elif "background" in name:
|
elif "background" in name:
|
||||||
color = CLEAR
|
color = COLORS.CLEAR
|
||||||
elif "caret" in name or "accent" in name:
|
elif "caret" in name or "accent" in name:
|
||||||
color = ACCENT
|
color = COLORS.ACCENT
|
||||||
elif "shadow" in name or "outline" in name:
|
elif "shadow" in name or "outline" in name:
|
||||||
color = CLEAR
|
color = COLORS.CLEAR
|
||||||
theme.set_color(name, type, color)
|
theme.set_color(name, type, color)
|
||||||
for name in theme.get_stylebox_list(type):
|
for name in theme.get_stylebox_list(type):
|
||||||
var original := theme.get_stylebox(name, type)
|
var original := theme.get_stylebox(name, type)
|
||||||
@@ -99,13 +96,11 @@ func _build_theme(palette: Dictionary, high_contrast: bool) -> Theme:
|
|||||||
style.bg_color = palette.separator
|
style.bg_color = palette.separator
|
||||||
style.border_color = palette.separator
|
style.border_color = palette.separator
|
||||||
theme.set_stylebox(name, type, style)
|
theme.set_stylebox(name, type, style)
|
||||||
# Slider thumbs use square textures instead of the engine's round icons.
|
|
||||||
for type in ["HSlider", "VSlider"]:
|
for type in ["HSlider", "VSlider"]:
|
||||||
for name in ["grabber", "grabber_highlight", "grabber_disabled"]:
|
for name in ["grabber", "grabber_highlight", "grabber_disabled"]:
|
||||||
var bitmap := Image.create(14, 18, false, Image.FORMAT_RGBA8)
|
var bitmap := Image.create(14, 18, false, Image.FORMAT_RGBA8)
|
||||||
bitmap.fill(ACCENT if name == "grabber_highlight" else palette.thumb)
|
bitmap.fill(COLORS.ACCENT if name == "grabber_highlight" else palette.thumb)
|
||||||
theme.set_icon(name, type, ImageTexture.create_from_image(bitmap))
|
theme.set_icon(name, type, ImageTexture.create_from_image(bitmap))
|
||||||
# Dedicated panel states used by the keyboard focus containers.
|
|
||||||
var panel := theme.get_stylebox("panel", "PanelContainer").duplicate() as StyleBoxFlat
|
var panel := theme.get_stylebox("panel", "PanelContainer").duplicate() as StyleBoxFlat
|
||||||
theme.set_stylebox("panel_inactive", "SurfaceSide", panel)
|
theme.set_stylebox("panel_inactive", "SurfaceSide", panel)
|
||||||
var active := panel.duplicate() as StyleBoxFlat
|
var active := panel.duplicate() as StyleBoxFlat
|
||||||
@@ -114,6 +109,63 @@ func _build_theme(palette: Dictionary, high_contrast: bool) -> Theme:
|
|||||||
if not high_contrast:
|
if not high_contrast:
|
||||||
active.border_width_top = 2
|
active.border_width_top = 2
|
||||||
theme.set_stylebox("panel_active", "SurfaceSide", active)
|
theme.set_stylebox("panel_active", "SurfaceSide", active)
|
||||||
theme.set_color("accent", "SurfaceSide", ACCENT)
|
theme.set_color("accent", "SurfaceSide", COLORS.ACCENT)
|
||||||
theme.set_color("secondary", "SurfaceSide", SECONDARY)
|
theme.set_color("secondary", "SurfaceSide", COLORS.SECONDARY)
|
||||||
|
_add_instrument_theme(theme, palette)
|
||||||
|
for role in COLORS.STATUS:
|
||||||
|
theme.set_color(role, "SurfaceSide", COLORS.STATUS[role])
|
||||||
|
theme.set_type_variation("VehicleWarningText", "Label")
|
||||||
|
theme.set_color("font_color", "VehicleWarningText", COLORS.STATUS.status_ink)
|
||||||
|
theme.set_type_variation("VehicleFaultText", "Label")
|
||||||
|
theme.set_color("font_color", "VehicleFaultText", COLORS.WHITE)
|
||||||
|
theme.set_type_variation("VehicleBackground", "PanelContainer")
|
||||||
|
var vehicle_background := StyleBoxFlat.new()
|
||||||
|
vehicle_background.bg_color = palette.panel
|
||||||
|
vehicle_background.border_color = palette.border
|
||||||
|
vehicle_background.set_border_width_all(1 if high_contrast else 0)
|
||||||
|
theme.set_stylebox("panel", "VehicleBackground", vehicle_background)
|
||||||
|
theme.set_type_variation("LayoutItemBackground", "Panel")
|
||||||
|
theme.set_stylebox("panel", "LayoutItemBackground", vehicle_background)
|
||||||
|
theme.set_type_variation("LayoutLibraryList", "ItemList")
|
||||||
|
theme.set_color("guide_color", "LayoutLibraryList", COLORS.SECONDARY if high_contrast else COLORS.CLEAR)
|
||||||
|
theme.set_constant("v_separation", "LayoutLibraryList", 8)
|
||||||
|
for variant in ["LinkRateNormal", "LinkRateWarning", "LinkRateFault", "LinkMuted"]:
|
||||||
|
theme.set_type_variation(variant, "Label")
|
||||||
|
theme.set_color("font_color", "LinkRateNormal", COLORS.INSTRUMENT.instrument_accent)
|
||||||
|
theme.set_color("font_color", "LinkRateWarning", COLORS.WARNING)
|
||||||
|
theme.set_color("font_color", "LinkRateFault", COLORS.FAULT)
|
||||||
|
theme.set_color("font_color", "LinkMuted", palette.muted_text)
|
||||||
|
for variant in ["InstrumentWarning", "InstrumentFault", "InstrumentMuted"]:
|
||||||
|
theme.set_type_variation(variant, "Label")
|
||||||
|
theme.set_color("font_color", "InstrumentWarning", COLORS.WARNING)
|
||||||
|
theme.set_color("font_color", "InstrumentFault", COLORS.FAULT)
|
||||||
|
theme.set_color("font_color", "InstrumentMuted", palette.muted_text)
|
||||||
return theme
|
return theme
|
||||||
|
|
||||||
|
func _add_instrument_theme(theme: Theme, palette: Dictionary) -> void:
|
||||||
|
for role in COLORS.INSTRUMENT:
|
||||||
|
theme.set_color(role, "SurfaceSide", COLORS.INSTRUMENT[role])
|
||||||
|
theme.set_color("instrument_empty", "SurfaceSide", palette.input)
|
||||||
|
theme.set_color("instrument_axis", "SurfaceSide", palette.muted_text)
|
||||||
|
theme.set_type_variation("MotionBackground", "Panel")
|
||||||
|
var background := StyleBoxFlat.new()
|
||||||
|
background.bg_color = palette.panel
|
||||||
|
theme.set_stylebox("panel", "MotionBackground", background)
|
||||||
|
for vertical in [false, true]:
|
||||||
|
var type := "MotionVSeparator" if vertical else "MotionHSeparator"
|
||||||
|
theme.set_type_variation(type, "VSeparator" if vertical else "HSeparator")
|
||||||
|
var separator := StyleBoxLine.new()
|
||||||
|
separator.color = COLORS.INSTRUMENT.instrument_grid
|
||||||
|
separator.thickness = 1
|
||||||
|
separator.vertical = vertical
|
||||||
|
theme.set_stylebox("separator", type, separator)
|
||||||
|
theme.set_type_variation("MotionAlertPanel", "PanelContainer")
|
||||||
|
var alert := StyleBoxFlat.new()
|
||||||
|
alert.bg_color = COLORS.INSTRUMENT.alert_background
|
||||||
|
alert.content_margin_top = 7
|
||||||
|
alert.content_margin_bottom = 7
|
||||||
|
theme.set_stylebox("panel", "MotionAlertPanel", alert)
|
||||||
|
theme.set_type_variation("MotionAlertText", "Label")
|
||||||
|
theme.set_color("font_color", "MotionAlertText", COLORS.INSTRUMENT.alert_text)
|
||||||
|
theme.set_type_variation("MotionWarning", "Label")
|
||||||
|
theme.set_color("font_color", "MotionWarning", COLORS.INSTRUMENT.instrument_accent)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[gd_resource type="StandardMaterial3D" format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Texture2D" path="res://assets/vector/dial.svg" id="1_7hb6v"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
transparency = 1
|
||||||
|
cull_mode = 2
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.44361365, 0.72118443, 0, 1)
|
||||||
|
albedo_texture = ExtResource("1_7hb6v")
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[gd_resource type="FontVariation" format=3]
|
||||||
|
|
||||||
|
[sub_resource type="SystemFont" id="SystemFont_mono"]
|
||||||
|
font_names = PackedStringArray("DejaVu Sans Mono", "Liberation Mono", "monospace")
|
||||||
|
hinting = 2
|
||||||
|
oversampling = 2.0
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
base_font = SubResource("SystemFont_mono")
|
||||||
|
variation_embolden = 0.35
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[gd_resource type="StyleBoxEmpty" format=3]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
content_margin_left = 0.0
|
||||||
|
content_margin_top = 0.0
|
||||||
|
content_margin_right = 0.0
|
||||||
|
content_margin_bottom = 0.0
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
extends RefCounted
|
||||||
|
## Rebuild: godot --headless --path . --script themes/build_theme.gd
|
||||||
|
|
||||||
|
const ACCENT = Color("ff7b00")
|
||||||
|
const SECONDARY = Color("402e32")
|
||||||
|
const TEXT = Color("f5f1ed")
|
||||||
|
const PANEL = Color("201b1f")
|
||||||
|
const SKY = Color("172f40")
|
||||||
|
const GROUND = Color("4b3930")
|
||||||
|
const COMPASS_TEST = Color(0.44361365, 0.72118443, 0, 1)
|
||||||
|
const WARNING = Color("f5c542")
|
||||||
|
const FAULT = Color("dc414b")
|
||||||
|
const WHITE = Color.WHITE
|
||||||
|
const CLEAR = Color.TRANSPARENT
|
||||||
|
|
||||||
|
# Color methods cannot initialize constants; treat these static values as read-only.
|
||||||
|
static var ACCENT_LIGHT: Color = ACCENT.lightened(0.25)
|
||||||
|
static var ACCENT_DARK: Color = ACCENT.darkened(0.25)
|
||||||
|
static var INPUT: Color = PANEL.darkened(0.40)
|
||||||
|
static var SURFACE: Color = PANEL.lightened(0.04)
|
||||||
|
static var PANEL_DIM: Color = PANEL.darkened(0.20)
|
||||||
|
static var TEXT_SOFT: Color = TEXT.darkened(0.055)
|
||||||
|
static var TEXT_MUTED: Color = TEXT.lerp(SECONDARY, 0.34)
|
||||||
|
static var TEXT_MUTED_SOFT: Color = TEXT.lerp(SECONDARY, 0.40)
|
||||||
|
static var BORDER: Color = TEXT.lerp(SECONDARY, 0.64)
|
||||||
|
static var BORDER_DISABLED: Color = SECONDARY.lightened(0.08)
|
||||||
|
static var GRID: Color = SECONDARY.lightened(0.18)
|
||||||
|
static var THUMB_SOFT: Color = TEXT.lerp(SECONDARY, 0.18)
|
||||||
|
static var VIEWPORT_BACKGROUND: Color = PANEL.darkened(0.45)
|
||||||
|
|
||||||
|
static var HIGH_CONTRAST: Dictionary = {
|
||||||
|
"text": TEXT, "muted_text": TEXT_MUTED,
|
||||||
|
"active_text": INPUT, "surface": PANEL,
|
||||||
|
"panel": PANEL, "input": INPUT,
|
||||||
|
"hover": SECONDARY, "pressed": ACCENT, "fill": ACCENT,
|
||||||
|
"disabled": PANEL_DIM, "border": BORDER,
|
||||||
|
"disabled_border": BORDER_DISABLED, "focus": ACCENT,
|
||||||
|
"separator": SECONDARY, "thumb": TEXT_MUTED,
|
||||||
|
"fill_text": INPUT,
|
||||||
|
}
|
||||||
|
static var SOFT: Dictionary = {
|
||||||
|
"text": TEXT_SOFT, "muted_text": TEXT_MUTED_SOFT,
|
||||||
|
"active_text": TEXT, "surface": SURFACE,
|
||||||
|
"panel": PANEL, "input": INPUT,
|
||||||
|
"hover": SECONDARY, "pressed": SECONDARY,
|
||||||
|
"fill": ACCENT, "disabled": PANEL,
|
||||||
|
"border": CLEAR, "disabled_border": CLEAR,
|
||||||
|
"focus": ACCENT, "separator": SECONDARY,
|
||||||
|
"thumb": THUMB_SOFT, "fill_text": INPUT,
|
||||||
|
}
|
||||||
|
|
||||||
|
static var INSTRUMENT: Dictionary = {
|
||||||
|
"instrument_accent": ACCENT_LIGHT,
|
||||||
|
"instrument_grid": GRID,
|
||||||
|
"horizon_sky": SKY,
|
||||||
|
"horizon_ground": GROUND,
|
||||||
|
"alert_background": ACCENT_DARK,
|
||||||
|
"alert_text": WHITE,
|
||||||
|
}
|
||||||
|
|
||||||
|
static var STATUS: Dictionary = {
|
||||||
|
"warning": WARNING,
|
||||||
|
"warning_alternate": WARNING.darkened(0.36),
|
||||||
|
"fault": FAULT,
|
||||||
|
"fault_alternate": FAULT.darkened(0.50),
|
||||||
|
"status_ink": INPUT,
|
||||||
|
"status_icon": ACCENT_LIGHT,
|
||||||
|
}
|
||||||
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user