Compare commits

..
Author SHA1 Message Date
joel 4d4280a905 Added dual windows for VR and surface side display 2026-04-28 17:45:36 +01:00
joel 1413d12ac5 Added git LFS 2026-04-28 17:22:10 +01:00
Joel Tumelty d794846968 Initial commit :) 2026-04-28 17:07:40 +01:00
114 changed files with 2 additions and 11050 deletions

No files matched your search

Vendored
BIN
View File
Binary file not shown.
-4
View File
@@ -1,7 +1,3 @@
# Godot 4+ specific ignores # Godot 4+ specific ignores
.godot/ .godot/
/android/ /android/
# Apple ignores
.DS_Store
@@ -1,42 +0,0 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://0qb2f8igltax"
path="res://.godot/imported/grey_seal_clean.glb-5cfb1e2a37e293df0fceb791bda0d0bf.scn"
[deps]
source_file="res://assets/models/grey-seal-halichoerus-grypus/grey_seal_clean.glb"
dest_files=["res://.godot/imported/grey_seal_clean.glb-5cfb1e2a37e293df0fceb791bda0d0bf.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
@@ -1,44 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dmab2r4r8h6kg"
path.s3tc="res://.godot/imported/grey_seal_clean_Image_0.png-79b10cc69366c1643b8eaf10ac139de0.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "b028693a4b1518fec263a2aaf78a7691"
}
[deps]
source_file="res://assets/models/grey-seal-halichoerus-grypus/grey_seal_clean_Image_0.png"
dest_files=["res://.godot/imported/grey_seal_clean_Image_0.png-79b10cc69366c1643b8eaf10ac139de0.s3tc.ctex"]
[params]
compress/mode=2
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=true
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=0
BIN
View File
Binary file not shown.
-44
View File
@@ -1,44 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cjw3n47kehnfj"
path.s3tc="res://.godot/imported/dial.svg-109dd48e66db5803776f539728051e27.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/vector/dial.svg"
dest_files=["res://.godot/imported/dial.svg-109dd48e66db5803776f539728051e27.s3tc.ctex"]
[params]
compress/mode=2
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=true
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=0
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
Binary file not shown.
-43
View File
@@ -1,43 +0,0 @@
[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.
-43
View File
@@ -1,43 +0,0 @@
[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.
@@ -1,43 +0,0 @@
[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
-35
View File
@@ -1,35 +0,0 @@
[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
-136
View File
@@ -1,136 +0,0 @@
[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 = "NONLINEAR"
horizontal_alignment = 1
-52
View File
@@ -1,52 +0,0 @@
[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 = "×"
-12
View File
@@ -1,12 +0,0 @@
[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"
-66
View File
@@ -1,66 +0,0 @@
[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"
-81
View File
@@ -1,81 +0,0 @@
[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
-30
View File
@@ -1,30 +0,0 @@
[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
-103
View File
@@ -1,103 +0,0 @@
[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
-57
View File
@@ -1,57 +0,0 @@
[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
-332
View File
@@ -1,332 +0,0 @@
[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
-54
View File
@@ -1,54 +0,0 @@
[gd_scene format=3 uid="uid://s7f003dyd60i"]
[ext_resource type="Script" uid="uid://cxerh1gbkrop2" path="res://scripts/selectable_container.gd" id="1_focus"]
[sub_resource type="LabelSettings" id="LabelSettings_0fpm5"]
font_size = 24
[node name="SelectableContainer" type="MarginContainer" unique_id=903061831]
anchors_preset = -1
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 4.0
offset_top = 4.0
offset_right = -4.0
offset_bottom = -4.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_focus")
[node name="PanelContainer" type="PanelContainer" parent="." unique_id=555833581]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="." unique_id=701103853]
layout_mode = 2
theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 16
theme_override_constants/margin_right = 16
theme_override_constants/margin_bottom = 16
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer" unique_id=1200250605]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer" unique_id=157590431]
layout_mode = 2
size_flags_vertical = 0
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=1765103253]
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 1
text = "Panel"
label_settings = SubResource("LabelSettings_0fpm5")
[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=519793654]
layout_mode = 2
size_flags_horizontal = 10
text = "F1"
[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer" unique_id=1046926303]
layout_mode = 2
[node name="Content" type="VBoxContainer" parent="MarginContainer/VBoxContainer" unique_id=1706387279]
layout_mode = 2
size_flags_vertical = 3
-26
View File
@@ -1,26 +0,0 @@
[gd_scene format=3 uid="uid://chflnxm5486kc"]
[ext_resource type="PackedScene" uid="uid://s7f003dyd60i" path="res://components/selectable_container.tscn" id="1_panel"]
[ext_resource type="Script" uid="uid://cjghwf3b858xu" path="res://scripts/terminal_panel.gd" id="2_terminal"]
[node name="TerminalPanel" unique_id=690518475 instance=ExtResource("1_panel")]
script = ExtResource("2_terminal")
title = "ROV terminal"
initial_focus = NodePath("Command")
[node name="Label" parent="MarginContainer/VBoxContainer/HBoxContainer" parent_id_path=PackedInt32Array(157590431) index="0" unique_id=1765103253]
text = "ROV terminal"
[node name="Output" type="RichTextLabel" parent="." index="2" unique_id=1341181181]
custom_minimum_size = Vector2(260, 100)
layout_mode = 2
size_flags_vertical = 3
focus_mode = 2
text = "SSH transport not connected.
Press the panel shortcut, then type a command.
"
scroll_following = true
[node name="Command" type="LineEdit" parent="." index="3" unique_id=701067863]
layout_mode = 2
placeholder_text = "Enter command…"
-274
View File
@@ -1,274 +0,0 @@
[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
-13
View File
@@ -1,13 +0,0 @@
To do:
- Motor information
- RPM
- Torque
- Power Consumption
- Command line
- Inputs
- Camera Status
- VR Peripherals and ROV connection status
- Manipulator Pos/Rot
- 3D model of arm rotation
- Logging
-13
View File
@@ -1,13 +0,0 @@
# 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.
-48
View File
@@ -1,48 +0,0 @@
# 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. 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.
-16
View File
@@ -1,16 +0,0 @@
# 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.
-16
View File
@@ -1,16 +0,0 @@
# 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.
-20
View File
@@ -1,20 +0,0 @@
# 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).
-30
View File
@@ -1,30 +0,0 @@
# 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 -7
View File
@@ -11,8 +11,8 @@ config_version=5
[application] [application]
config/name="SurfaceSide-UI" config/name="SurfaceSide-UI"
run/main_scene="res://scenes/layout_landscape.tscn" run/main_scene="uid://63m7uewe6luk"
config/features=PackedStringArray("4.7", "Forward Plus") config/features=PackedStringArray("4.6", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[display] [display]
@@ -21,10 +21,6 @@ window/size/mode=2
window/subwindows/embed_subwindows=false window/subwindows/embed_subwindows=false
window/vsync/vsync_mode=3 window/vsync/vsync_mode=3
[gui]
theme/custom="res://themes/theme.tres"
[physics] [physics]
3d/physics_engine="Jolt Physics" 3d/physics_engine="Jolt Physics"
@@ -32,4 +28,3 @@ 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.069019616, 0.058235295, 0.06686275, 1)
-18
View File
@@ -1,7 +1,5 @@
[gd_scene format=3 uid="uid://bx8vjd7wodbv"] [gd_scene format=3 uid="uid://bx8vjd7wodbv"]
[ext_resource type="PackedScene" uid="uid://j2vmxn1p5hej" path="res://scenes/compass.tscn" id="1_5j21v"]
[node name="MainDisplay" type="Control" unique_id=735638234] [node name="MainDisplay" type="Control" unique_id=735638234]
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
@@ -9,19 +7,3 @@ anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
[node name="SubViewportContainer" type="SubViewportContainer" parent="." unique_id=1834501597]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
stretch = true
[node name="SubViewport" type="SubViewport" parent="SubViewportContainer" unique_id=697927821]
handle_input_locally = false
size = Vector2i(1152, 648)
render_target_update_mode = 4
[node name="Compass" parent="SubViewportContainer/SubViewport" unique_id=1678590387 instance=ExtResource("1_5j21v")]
-28
View File
@@ -1,28 +0,0 @@
[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="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="Script" uid="uid://cv5cmdlub5gtx" path="res://scenes/node_3d.gd" id="3_mut2o"]
[sub_resource type="PlaneMesh" id="PlaneMesh_wivqi"]
material = ExtResource("1_material")
[node name="Compass" type="Node3D" unique_id=1678590387]
[node name="AngleDisplay3D" type="Node3D" parent="." unique_id=741461034]
script = ExtResource("1_8a3he")
[node name="MeshInstance3D" type="MeshInstance3D" parent="AngleDisplay3D" unique_id=1205234959]
mesh = SubResource("PlaneMesh_wivqi")
[node name="Node3D" type="Node3D" parent="." unique_id=1404656477]
script = ExtResource("3_mut2o")
[node name="grey_seal_clean" parent="Node3D" unique_id=476466866 instance=ExtResource("2_8a3he")]
transform = Transform3D(2, 0, 0, 0, 1.5, 0, 0, 0, 1.5, 0, 0, 0)
[node name="Camera3D" type="Camera3D" parent="Node3D" unique_id=269443754]
transform = Transform3D(0.7071068, -0.43045935, 0.5609855, 0, 0.7933534, 0.6087614, -0.7071068, -0.43045935, 0.5609855, 2, 2, 2)
fov = 28.4
size = 1.5
-62
View File
@@ -1,62 +0,0 @@
[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
-57
View File
@@ -1,57 +0,0 @@
[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"
-52
View File
@@ -1,52 +0,0 @@
[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"
-49
View File
@@ -1,49 +0,0 @@
[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
-106
View File
@@ -1,106 +0,0 @@
[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
-50
View File
@@ -1,50 +0,0 @@
[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"
-22
View File
@@ -1,22 +0,0 @@
[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)"
-11
View File
@@ -1,11 +0,0 @@
extends Node3D
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
self.rotate_y(delta * 0.1)
-1
View File
@@ -1 +0,0 @@
uid://cv5cmdlub5gtx
-11
View File
@@ -1,11 +0,0 @@
[gd_scene format=3 uid="uid://b3gfs3dear3an"]
[ext_resource type="Script" uid="uid://cublhdarwsfbk" path="res://scripts/theme_gallery.gd" id="1"]
[node name="ThemeGallery" type="MarginContainer" unique_id=1432651951]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
-44
View File
@@ -1,44 +0,0 @@
[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
-16
View File
@@ -1,16 +0,0 @@
extends Node3D
func _ready() -> void:
for i in range(0, 360, 30):
var label := Label3D.new()
label.text = str(i)
label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
label.font_size = 64
label.pixel_size = 0.0025
var base_position = Vector2.from_angle(deg_to_rad(i)) * 1.15
label.position = Vector3(base_position.x, 0.0, base_position.y)
add_child(label)
func _process(delta: float) -> void:
pass
-1
View File
@@ -1 +0,0 @@
uid://cgcgn8lwigumx
-9
View File
@@ -1,9 +0,0 @@
@tool
extends AspectRatioContainer
func _ready() -> void:
resized.connect(_update_height)
_update_height()
func _update_height() -> void:
custom_minimum_size.y = ceilf(size.x / ratio)
-1
View File
@@ -1 +0,0 @@
uid://k6buaitag78o
-64
View File
@@ -1,64 +0,0 @@
@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)
-1
View File
@@ -1 +0,0 @@
uid://cbobdfddbbddo
-23
View File
@@ -1,23 +0,0 @@
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
-1
View File
@@ -1 +0,0 @@
uid://5qywekq0w4ne
-67
View File
@@ -1,67 +0,0 @@
@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()
-1
View File
@@ -1 +0,0 @@
uid://dnafkcowopwod
-339
View File
@@ -1,339 +0,0 @@
@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:
item.mouse_behavior_recursive = Control.MOUSE_BEHAVIOR_DISABLED if active else Control.MOUSE_BEHAVIOR_INHERITED
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()
item.mouse_behavior_recursive = Control.MOUSE_BEHAVIOR_DISABLED
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()
item.mouse_behavior_recursive = Control.MOUSE_BEHAVIOR_DISABLED if edit_mode else Control.MOUSE_BEHAVIOR_INHERITED
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()
-1
View File
@@ -1 +0,0 @@
uid://bqrapdh6yddl8
-31
View File
@@ -1,31 +0,0 @@
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},
"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
-1
View File
@@ -1 +0,0 @@
uid://0hiij8bwyn7w
-19
View File
@@ -1,19 +0,0 @@
@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)
-1
View File
@@ -1 +0,0 @@
uid://cwbk5g4yhdfd6
-27
View File
@@ -1,27 +0,0 @@
@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"))
-1
View File
@@ -1 +0,0 @@
uid://n4eb1a6j5j88
-42
View File
@@ -1,42 +0,0 @@
@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)
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
-1
View File
@@ -1 +0,0 @@
uid://byjnf0ae0jr2y
-30
View File
@@ -1,30 +0,0 @@
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."
-1
View File
@@ -1 +0,0 @@
uid://cwdwpabj62mhl
-135
View File
@@ -1,135 +0,0 @@
extends Control
signal overview_changed(active: bool)
@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 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"))
-1
View File
@@ -1 +0,0 @@
uid://b4g15anvgpot8
-56
View File
@@ -1,56 +0,0 @@
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))
-1
View File
@@ -1 +0,0 @@
uid://bxj3puinklg62
-17
View File
@@ -1,17 +0,0 @@
@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
-1
View File
@@ -1 +0,0 @@
uid://btaf1ke22k5ij
-35
View File
@@ -1,35 +0,0 @@
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
-1
View File
@@ -1 +0,0 @@
uid://ceg5mij8na3qr
-22
View File
@@ -1,22 +0,0 @@
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
-1
View File
@@ -1 +0,0 @@
uid://i5dbrwr8d2gr
-74
View File
@@ -1,74 +0,0 @@
@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
-1
View File
@@ -1 +0,0 @@
uid://84jt1vgxm2ei
-44
View File
@@ -1,44 +0,0 @@
@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)
-1
View File
@@ -1 +0,0 @@
uid://b1pcqygbaipjh
-18
View File
@@ -1,18 +0,0 @@
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))
)
-1
View File
@@ -1 +0,0 @@
uid://c5mncovlnarve
-133
View File
@@ -1,133 +0,0 @@
@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")
-1
View File
@@ -1 +0,0 @@
uid://1dn362pyibkx
-63
View File
@@ -1,63 +0,0 @@
@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()
-1
View File
@@ -1 +0,0 @@
uid://dh2vnk35potq3
-152
View File
@@ -1,152 +0,0 @@
@tool
class_name SelectableContainer
extends MarginContainer
signal activated
signal deactivated
@export var title: String = "Panel":
set(value):
title = value
_refresh_header()
@export var focus_key: Key = KEY_F1:
set(value):
focus_key = value
_refresh_header()
@export_node_path("Control") var initial_focus: NodePath
var is_active: bool = false
var _last_focus: Control
var _shortcut_available: bool = true
var _initial_focus_control: Control
@onready var content: VBoxContainer = $MarginContainer/VBoxContainer/Content
@onready var _panel: PanelContainer = $PanelContainer
@onready var _shortcut: Button = $MarginContainer/VBoxContainer/HBoxContainer/Button
func _ready() -> void:
_refresh_header()
if Engine.is_editor_hint():
return
# Resolve authored paths before reparenting changes the runtime hierarchy.
_initial_focus_control = get_node_or_null(initial_focus) as Control
for child in get_children():
_route_content_child(child)
child_entered_tree.connect(_on_child_entered_tree)
add_to_group("keyboard_focus_containers")
focus_mode = Control.FOCUS_NONE
_shortcut.focus_mode = Control.FOCUS_NONE
_shortcut.pressed.connect(activate)
for other in get_tree().get_nodes_in_group("keyboard_focus_containers"):
if other != self and other.get_viewport() == get_viewport() and other.focus_key == focus_key and other._shortcut_available:
_shortcut_available = false
_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)
visibility_changed.connect(_on_visibility_changed)
_update_style()
content.focus_behavior_recursive = Control.FOCUS_BEHAVIOR_DISABLED
func _refresh_header() -> void:
var label := get_node_or_null("MarginContainer/VBoxContainer/HBoxContainer/Label") as Label
var badge := get_node_or_null("MarginContainer/VBoxContainer/HBoxContainer/Button") as Button
if label != null:
label.text = title
if badge != null:
badge.text = OS.get_keycode_string(focus_key) + (" · ACTIVE" if is_active else "")
func _on_child_entered_tree(child: Node) -> void:
# SceneTree may still be adding the node when this signal fires.
_route_content_child.call_deferred(child)
func _route_content_child(child: Node) -> void:
if not is_instance_valid(child) or child.get_parent() != self:
return
if child == _panel or child == $MarginContainer:
return
child.reparent(content, false)
func activate() -> void:
if Engine.is_editor_hint() or not is_visible_in_tree() or not _shortcut_available:
return
for other in get_tree().get_nodes_in_group("keyboard_focus_containers"):
if other != self and other.get_viewport() == get_viewport():
other.deactivate()
var was_active := is_active
is_active = true
focus_mode = Control.FOCUS_ALL
content.focus_behavior_recursive = Control.FOCUS_BEHAVIOR_ENABLED
_update_style()
var target: Control = _initial_focus_control if is_instance_valid(_initial_focus_control) else null
if is_instance_valid(_last_focus) and _last_focus.is_visible_in_tree():
target = _last_focus
if target != null and target.is_visible_in_tree() and target.get_focus_mode_with_override() != Control.FOCUS_NONE:
target.grab_focus()
else:
var controls := _focusable_controls(content)
if controls.is_empty():
grab_focus()
else:
controls[0].grab_focus()
if not was_active:
activated.emit()
func deactivate() -> void:
if not is_active:
return
is_active = false
focus_mode = Control.FOCUS_NONE
var focus_owner := get_viewport().gui_get_focus_owner()
if focus_owner == self or (focus_owner != null and is_ancestor_of(focus_owner)):
focus_owner.release_focus()
content.focus_behavior_recursive = Control.FOCUS_BEHAVIOR_DISABLED
_update_style()
deactivated.emit()
func _input(event: InputEvent) -> void:
if Engine.is_editor_hint() or not event is InputEventKey or not is_visible_in_tree():
return
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:
return
if key.keycode == KEY_ESCAPE:
deactivate()
get_viewport().set_input_as_handled()
elif key.keycode == KEY_TAB and not key.ctrl_pressed and not key.alt_pressed and not key.meta_pressed:
var controls := _focusable_controls(content)
if not controls.is_empty():
var index := controls.find(get_viewport().gui_get_focus_owner())
var step := -1 if key.shift_pressed else 1
controls[posmod(index + step, controls.size())].grab_focus()
get_viewport().set_input_as_handled()
func _focusable_controls(node: Node) -> Array[Control]:
var result: Array[Control] = []
for child in node.get_children():
if child is Control and child.is_visible_in_tree() and child.get_focus_mode_with_override() == Control.FOCUS_ALL:
if not (child is BaseButton and child.disabled):
result.append(child)
result.append_array(_focusable_controls(child))
return result
func _on_focus_changed(control: Control) -> void:
if is_active and control != null and is_ancestor_of(control):
_last_focus = control
elif is_active and control != self:
deactivate()
func _on_visibility_changed() -> void:
if not is_visible_in_tree():
deactivate()
func _update_style() -> void:
var style_name := "panel_active" if is_active else "panel_inactive"
_panel.add_theme_stylebox_override("panel", get_theme_stylebox(style_name, "SurfaceSide"))
_refresh_header()
-1
View File
@@ -1 +0,0 @@
uid://cxerh1gbkrop2
-40
View File
@@ -1,40 +0,0 @@
@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)
-1
View File
@@ -1 +0,0 @@
uid://bqvhrignal143
-25
View File
@@ -1,25 +0,0 @@
@tool
extends SelectableContainer
signal command_requested(command: String)
@onready var output: RichTextLabel = $Output
@onready var command_input: LineEdit = $Command
func _ready() -> void:
super._ready()
if Engine.is_editor_hint():
return
command_input.text_submitted.connect(_submit)
func _submit(command: String) -> void:
if command.strip_edges().is_empty():
return
append_output("$ " + command + "\n")
command_input.clear()
if command_requested.get_connections().is_empty():
append_output("No SSH transport connected. Command was not sent.\n")
else:
command_requested.emit(command)
func append_output(text: String) -> void:
output.add_text(text)
-1
View File
@@ -1 +0,0 @@
uid://cjghwf3b858xu
-75
View File
@@ -1,75 +0,0 @@
extends MarginContainer
func _ready() -> void:
for side in ["left", "top", "right", "bottom"]:
add_theme_constant_override("margin_" + side, 24)
var scroll := ScrollContainer.new()
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
add_child(scroll)
var layout := VBoxContainer.new()
layout.size_flags_horizontal = Control.SIZE_EXPAND_FILL
layout.add_theme_constant_override("separation", 18)
scroll.add_child(layout)
var heading := Label.new()
heading.text = "SURFACESIDE / THEME GALLERY"
heading.add_theme_font_size_override("font_size", 28)
layout.add_child(heading)
var subtitle := Label.new()
subtitle.text = "Square corners · orange focus · plum accents · choose a theme below"
subtitle.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
layout.add_child(subtitle)
var picker := OptionButton.new()
picker.add_item("Soft / minimal borders")
picker.add_item("Original / high contrast")
picker.item_selected.connect(func(index: int) -> void:
theme = load("res://themes/theme.tres" if index == 0 else "res://themes/theme_highcontrast.tres")
for scope in get_tree().get_nodes_in_group("keyboard_focus_containers"):
if is_ancestor_of(scope):
scope._update_style()
)
layout.add_child(picker)
var panel := PanelContainer.new()
layout.add_child(panel)
var controls := VBoxContainer.new()
controls.add_theme_constant_override("separation", 16)
panel.add_child(controls)
var buttons := HFlowContainer.new()
buttons.add_theme_constant_override("h_separation", 12)
controls.add_child(buttons)
for state in ["Normal", "Toggle / pressed", "Disabled"]:
var button := Button.new()
button.text = state
button.toggle_mode = state == "Toggle / pressed"
button.button_pressed = button.toggle_mode
button.disabled = state == "Disabled"
buttons.add_child(button)
var option := OptionButton.new()
option.add_item("Primary display")
option.add_item("Secondary display")
buttons.add_child(option)
var check := CheckBox.new()
check.text = "Telemetry enabled"
check.button_pressed = true
controls.add_child(check)
var input := LineEdit.new()
input.placeholder_text = "Command input — Tab to inspect focus"
controls.add_child(input)
var text := TextEdit.new()
text.custom_minimum_size.y = 110
text.text = "Operator notes\nSelect text to inspect the selection color."
controls.add_child(text)
var slider := HSlider.new()
slider.value = 65
controls.add_child(slider)
var progress := ProgressBar.new()
progress.value = 65
controls.add_child(progress)
slider.value_changed.connect(func(value: float) -> void: progress.value = value)
var tabs := TabContainer.new()
tabs.custom_minimum_size.y = 100
controls.add_child(tabs)
for title in ["Status", "Diagnostics"]:
var label := Label.new()
label.name = title
label.text = " " + title + " / All systems ready"
tabs.add_child(label)
-1
View File
@@ -1 +0,0 @@
uid://cublhdarwsfbk
-29
View File
@@ -1,29 +0,0 @@
@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()
-1
View File
@@ -1 +0,0 @@
uid://rh8gwu8mfqx2
Loaded 100 of 114 files, more files were not shown because too many files have changed in this diff. Show more