Added environment panel for depth, pressure and outside temperature.
This commit is contained in:
1 parent
2d91487c6a
commit
92006372da
21 files changed
+2825
-1616
No files matched your search
@@ -0,0 +1,136 @@
|
||||
[gd_scene format=3 uid="uid://b75nt1kn2sobp"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dnafkcowopwod" path="res://scripts/environment_panel.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://cbobdfddbbddo" path="res://scripts/depth_scale.gd" id="2"]
|
||||
|
||||
[node name="EnvironmentPanel" type="PanelContainer" unique_id=1120688022]
|
||||
custom_minimum_size = Vector2(360, 240)
|
||||
offset_right = 360.0
|
||||
offset_bottom = 240.0
|
||||
theme_type_variation = &"VehicleBackground"
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="." unique_id=1435834688]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_right = 16
|
||||
theme_override_constants/margin_bottom = 10
|
||||
|
||||
[node name="Body" type="VBoxContainer" parent="Margin" unique_id=826880208]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="Header" type="HBoxContainer" parent="Margin/Body" unique_id=1188782081]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Title" type="Label" parent="Margin/Body/Header" unique_id=1435423541]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "ENVIRONMENT"
|
||||
|
||||
[node name="State" type="Label" parent="Margin/Body/Header" unique_id=1387248588]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"InstrumentMuted"
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "NO TELEMETRY"
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="Measurements" type="HBoxContainer" parent="Margin/Body" unique_id=354890970]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="Readouts" type="VBoxContainer" parent="Margin/Body/Measurements" unique_id=766009790]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="DepthTitle" type="Label" parent="Margin/Body/Measurements/Readouts" unique_id=1777992876]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"Label"
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "DEPTH"
|
||||
|
||||
[node name="DepthValue" type="Label" parent="Margin/Body/Measurements/Readouts" unique_id=1937430512]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 40
|
||||
text = "— m"
|
||||
|
||||
[node name="DepthReference" type="Label" parent="Margin/Body/Measurements/Readouts" unique_id=1946013161]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"InstrumentMuted"
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "/ 1000 m"
|
||||
|
||||
[node name="Spacer" type="Control" parent="Margin/Body/Measurements/Readouts" unique_id=1016695424]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Supporting" type="HBoxContainer" parent="Margin/Body/Measurements/Readouts" unique_id=1473880932]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="Pressure" type="VBoxContainer" parent="Margin/Body/Measurements/Readouts/Supporting" unique_id=1964987657]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="PressureTitle" type="Label" parent="Margin/Body/Measurements/Readouts/Supporting/Pressure" unique_id=1727076495]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"Label"
|
||||
theme_override_font_sizes/font_size = 13
|
||||
text = "PRESSURE"
|
||||
|
||||
[node name="PressureValue" type="Label" parent="Margin/Body/Measurements/Readouts/Supporting/Pressure" unique_id=1479127186]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 22
|
||||
text = "— MPa"
|
||||
|
||||
[node name="PressureReference" type="Label" parent="Margin/Body/Measurements/Readouts/Supporting/Pressure" unique_id=1835332085]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"InstrumentMuted"
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "/ 10 MPa"
|
||||
|
||||
[node name="Temperature" type="VBoxContainer" parent="Margin/Body/Measurements/Readouts/Supporting" unique_id=1827823179]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="Title" type="Label" parent="Margin/Body/Measurements/Readouts/Supporting/Temperature" unique_id=1829138035]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 13
|
||||
text = "WATER TEMP"
|
||||
|
||||
[node name="TemperatureValue" type="Label" parent="Margin/Body/Measurements/Readouts/Supporting/Temperature" unique_id=1337672100]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 22
|
||||
text = "— °C"
|
||||
|
||||
[node name="Scale" type="VBoxContainer" parent="Margin/Body/Measurements" unique_id=1235543623]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="DepthScale" type="Control" parent="Margin/Body/Measurements/Scale" unique_id=223719936]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(74, 140)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
tooltip_text = "Shallow depths expanded: log(1 + depth / knee). Includes zero."
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="Caption" type="Label" parent="Margin/Body/Measurements/Scale" unique_id=1491620694]
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"InstrumentMuted"
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "NONLINEAR"
|
||||
horizontal_alignment = 1
|
||||
@@ -1,20 +1,13 @@
|
||||
To do:
|
||||
|
||||
- Pitch/yaw/tilt relative to magnetic north
|
||||
- Motor information
|
||||
- RPM
|
||||
- Torque
|
||||
- Power Consumption
|
||||
- Overall power consumption
|
||||
- Command line
|
||||
- Inputs
|
||||
- Camera Status
|
||||
- VR Peripherals and ROV connection status
|
||||
- Uplink/Downlink Data Rate
|
||||
- Manipulator Pos/Rot
|
||||
- 3D model of arm rotation
|
||||
- Status
|
||||
- Logging
|
||||
- Enclosure Temperature
|
||||
- Depth (Pressure)
|
||||
- Tether Latency
|
||||
@@ -41,7 +41,7 @@ The elements array is the complete active layout; omitted default panels stay de
|
||||
|
||||
`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. Environment, VR/video, and operator tools are placeholders. The Link panel has a separate [telemetry demo](link_panel.md). Camera and telemetry feeds are not connected.
|
||||
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.
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Environment panel
|
||||
|
||||
Run `scenes/environment_demo.tscn` with F6. It previews surface/zero, shallow operation, deeper readings, independent depth/pressure warnings, stale data and both themes.
|
||||
|
||||
```gdscript
|
||||
$EnvironmentPanel.set_telemetry(248.6, 2.54, 7.4)
|
||||
# depth: metres; outside pressure: MPa; water temperature: Celsius
|
||||
```
|
||||
|
||||
The depth indicator uses `log(1 + depth / knee)`, normalized to the maximum depth. This includes zero and expands shallow readings; the default knee is 1 m. Tick labels show the non-linear spacing. Numeric readings are untransformed; negative depth remains visible numerically while the marker stays at zero. Above-limit readings remain visible while the marker clamps to the bottom.
|
||||
|
||||
Limits default to 1000 m and 10 MPa. Warning/saturation colors start at 80%/95% of each actual measurement's limit, independently of the scale transform. Temperature has no limit or gauge. Pressure and depth are supplied independently; no conversion is performed, and the pressure datum should match the configured pressure limit. Demo samples are illustrative.
|
||||
|
||||
Missing data shows dashes. After two seconds without a sample, values are marked STALE / HELD. Non-finite values and negative pressure are rejected. `clear_telemetry()` restores the missing-data state.
|
||||
|
||||
Labels and containers remain editable scene nodes; `depth_scale.gd` draws the indicator. There is no background icon. Colors use shared theme roles. The panel replaces the Environment placeholder in the library and both layouts without changing saved JSON type IDs.
|
||||
+2
-1
@@ -11,6 +11,7 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="SurfaceSide-UI"
|
||||
run/main_scene="res://scenes/layout_landscape.tscn"
|
||||
config/features=PackedStringArray("4.7", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
@@ -31,4 +32,4 @@ theme/custom="res://themes/theme.tres"
|
||||
[rendering]
|
||||
|
||||
rendering_device/driver.windows="d3d12"
|
||||
environment/defaults/default_clear_color=Color(0.067, 0.055, 0.067, 1)
|
||||
environment/defaults/default_clear_color=Color(0.069019616, 0.058235295, 0.06686275, 1)
|
||||
+2
-9
@@ -1,19 +1,12 @@
|
||||
[gd_scene format=3 uid="uid://j2vmxn1p5hej"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cgcgn8lwigumx" path="res://scripts/angle_display_3d.gd" id="1_8a3he"]
|
||||
[ext_resource type="Texture2D" uid="uid://cjw3n47kehnfj" path="res://assets/vector/dial.svg" id="1_vggt3"]
|
||||
[ext_resource type="Material" path="res://themes/compass_material.tres" id="1_material"]
|
||||
[ext_resource type="PackedScene" uid="uid://0qb2f8igltax" path="res://assets/models/grey-seal-halichoerus-grypus/grey_seal_clean.glb" id="2_8a3he"]
|
||||
[ext_resource type="Script" uid="uid://cv5cmdlub5gtx" path="res://scenes/node_3d.gd" id="3_mut2o"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_mut2o"]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
shading_mode = 0
|
||||
albedo_color = Color(0.44361365, 0.72118443, 0, 1)
|
||||
albedo_texture = ExtResource("1_vggt3")
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_wivqi"]
|
||||
material = SubResource("StandardMaterial3D_mut2o")
|
||||
material = ExtResource("1_material")
|
||||
|
||||
[node name="Compass" type="Node3D" unique_id=1678590387]
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://components/layout_canvas.tscn" id="canvas"]
|
||||
[ext_resource type="PackedScene" path="res://components/layout_item.tscn" id="item"]
|
||||
[ext_resource type="PackedScene" path="res://components/camera_layout_slot.tscn" id="camera"]
|
||||
[ext_resource type="PackedScene" path="res://components/vehicle_status_panel.tscn" id="vehicle"]
|
||||
[ext_resource type="PackedScene" path="res://components/motion_panel.tscn" id="motion"]
|
||||
[ext_resource type="PackedScene" path="res://components/environment_panel.tscn" id="environment"]
|
||||
[ext_resource type="PackedScene" path="res://components/link_panel.tscn" id="link"]
|
||||
[ext_resource type="PackedScene" path="res://components/layout_slot.tscn" id="slot"]
|
||||
|
||||
[node name="DisplayLeft" instance=ExtResource("canvas")]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
grid_size = Vector2i(18, 32)
|
||||
config_path = "user://layouts/portrait_left.json"
|
||||
legacy_config_path = "user://layout_portrait_left.cfg"
|
||||
|
||||
[node name="Camera" parent="." instance=ExtResource("item")]
|
||||
layout_mode = 0
|
||||
panel_id = "camera"
|
||||
cell_rect = Rect2i(0, 0, 18, 11)
|
||||
minimum_cells = Vector2i(8, 5)
|
||||
reference_size = Vector2(960, 600)
|
||||
|
||||
[node name="Content" parent="Camera" instance=ExtResource("camera")]
|
||||
layout_mode = 0
|
||||
|
||||
[node name="Vehicle" parent="." instance=ExtResource("item")]
|
||||
layout_mode = 0
|
||||
panel_id = "vehicle"
|
||||
cell_rect = Rect2i(0, 11, 10, 8)
|
||||
minimum_cells = Vector2i(6, 4)
|
||||
reference_size = Vector2(600, 440)
|
||||
|
||||
[node name="Content" parent="Vehicle" instance=ExtResource("vehicle")]
|
||||
layout_mode = 0
|
||||
|
||||
[node name="Motion" parent="." instance=ExtResource("item")]
|
||||
layout_mode = 0
|
||||
panel_id = "motion"
|
||||
cell_rect = Rect2i(0, 19, 10, 7)
|
||||
minimum_cells = Vector2i(6, 4)
|
||||
reference_size = Vector2(600, 410)
|
||||
background_path = NodePath("Content/Background")
|
||||
|
||||
[node name="Content" parent="Motion" instance=ExtResource("motion")]
|
||||
layout_mode = 0
|
||||
|
||||
[node name="Environment" parent="." instance=ExtResource("item")]
|
||||
layout_mode = 0
|
||||
panel_id = "environment"
|
||||
cell_rect = Rect2i(10, 11, 8, 8)
|
||||
minimum_cells = Vector2i(4, 3)
|
||||
reference_size = Vector2(360, 240)
|
||||
|
||||
[node name="Content" parent="Environment" instance=ExtResource("environment")]
|
||||
layout_mode = 0
|
||||
@@ -0,0 +1,57 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://components/layout_canvas.tscn" id="canvas"]
|
||||
[ext_resource type="PackedScene" path="res://components/layout_item.tscn" id="item"]
|
||||
[ext_resource type="PackedScene" path="res://components/camera_layout_slot.tscn" id="camera"]
|
||||
[ext_resource type="PackedScene" path="res://components/vehicle_status_panel.tscn" id="vehicle"]
|
||||
[ext_resource type="PackedScene" path="res://components/motion_panel.tscn" id="motion"]
|
||||
[ext_resource type="PackedScene" path="res://components/environment_panel.tscn" id="environment"]
|
||||
[ext_resource type="PackedScene" path="res://components/link_panel.tscn" id="link"]
|
||||
[ext_resource type="PackedScene" path="res://components/layout_slot.tscn" id="slot"]
|
||||
|
||||
[node name="DisplayRight" instance=ExtResource("canvas")]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
grid_size = Vector2i(18, 32)
|
||||
config_path = "user://layouts/portrait_right.json"
|
||||
legacy_config_path = "user://layout_portrait_right.cfg"
|
||||
|
||||
[node name="VideoVR" parent="." instance=ExtResource("item")]
|
||||
layout_mode = 0
|
||||
panel_id = "videovr"
|
||||
cell_rect = Rect2i(0, 0, 18, 17)
|
||||
minimum_cells = Vector2i(6, 4)
|
||||
reference_size = Vector2(600, 400)
|
||||
fill_allocation = true
|
||||
|
||||
[node name="Content" parent="VideoVR" instance=ExtResource("slot")]
|
||||
layout_mode = 0
|
||||
title = "VIDEO / VR"
|
||||
description = "Latency history and breakdown · headset connection · device · resolution"
|
||||
|
||||
[node name="Link" parent="." instance=ExtResource("item")]
|
||||
layout_mode = 0
|
||||
panel_id = "link"
|
||||
cell_rect = Rect2i(0, 17, 18, 5)
|
||||
minimum_cells = Vector2i(4, 3)
|
||||
reference_size = Vector2(360, 240)
|
||||
|
||||
[node name="Content" parent="Link" instance=ExtResource("link")]
|
||||
layout_mode = 0
|
||||
|
||||
[node name="OperatorTools" parent="." instance=ExtResource("item")]
|
||||
layout_mode = 0
|
||||
panel_id = "operatortools"
|
||||
cell_rect = Rect2i(0, 22, 18, 10)
|
||||
minimum_cells = Vector2i(6, 4)
|
||||
reference_size = Vector2(600, 400)
|
||||
fill_allocation = true
|
||||
|
||||
[node name="Content" parent="OperatorTools" instance=ExtResource("slot")]
|
||||
layout_mode = 0
|
||||
title = "OPERATOR TOOLS / RESERVED"
|
||||
description = "Recording · manipulator · terminal / events"
|
||||
@@ -0,0 +1,52 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/environment_demo.gd" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://components/environment_panel.tscn" id="2"]
|
||||
|
||||
[node name="EnvironmentDemo" type="VBoxContainer"]
|
||||
offset_left = 24.0
|
||||
offset_top = 24.0
|
||||
offset_right = 624.0
|
||||
offset_bottom = 420.0
|
||||
theme_override_constants/separation = 16
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Notice" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
text = "SIMULATED / ENVIRONMENT INSTRUMENT"
|
||||
|
||||
[node name="EnvironmentPanel" parent="." instance=ExtResource("2")]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
|
||||
[node name="Controls" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="State" type="OptionButton" parent="Controls"]
|
||||
layout_mode = 2
|
||||
selected = 1
|
||||
item_count = 8
|
||||
popup/item_0/text = "Surface / zero"
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = "Shallow operation"
|
||||
popup/item_1/id = 1
|
||||
popup/item_2/text = "Operating depth"
|
||||
popup/item_2/id = 2
|
||||
popup/item_3/text = "Near depth limit"
|
||||
popup/item_3/id = 3
|
||||
popup/item_4/text = "Pressure near limit"
|
||||
popup/item_4/id = 4
|
||||
popup/item_5/text = "Over limits"
|
||||
popup/item_5/id = 5
|
||||
popup/item_6/text = "Stop feed / stale"
|
||||
popup/item_6/id = 6
|
||||
popup/item_7/text = "No telemetry"
|
||||
popup/item_7/id = 7
|
||||
|
||||
[node name="Contrast" type="CheckButton" parent="Controls"]
|
||||
layout_mode = 2
|
||||
text = "High contrast"
|
||||
|
||||
[node name="Note" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
text = "Zero-inclusive log scale · shallow depths expanded"
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
[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
|
||||
@@ -76,12 +78,9 @@ panel_id = "environment"
|
||||
cell_rect = Rect2i(26, 0, 6, 8)
|
||||
minimum_cells = Vector2i(4, 3)
|
||||
reference_size = Vector2(360, 240)
|
||||
fill_allocation = true
|
||||
|
||||
[node name="Content" parent="Overview/Dashboard/Canvas/Environment" instance=ExtResource("slot")]
|
||||
[node name="Content" parent="Overview/Dashboard/Canvas/Environment" instance=ExtResource("environment")]
|
||||
layout_mode = 0
|
||||
title = "ENVIRONMENT"
|
||||
description = "Depth · pressure · water temperature"
|
||||
|
||||
[node name="VideoVR" parent="Overview/Dashboard/Canvas" instance=ExtResource("item")]
|
||||
layout_mode = 0
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
var depth_m := 0.0
|
||||
var maximum_depth_m := 1000.0
|
||||
var knee_m := 1.0
|
||||
var warning_fraction := 0.8
|
||||
var fault_fraction := 0.95
|
||||
var available := false
|
||||
|
||||
func _ready() -> void:
|
||||
resized.connect(queue_redraw)
|
||||
theme_changed.connect(queue_redraw)
|
||||
|
||||
# log(1 + depth / knee) includes zero and is nearly linear close to the surface.
|
||||
func normalized_depth(value: float) -> float:
|
||||
var knee := maxf(knee_m, 0.001)
|
||||
var maximum := maxf(maximum_depth_m, 0.001)
|
||||
return log(1 + clampf(value, 0, maximum) / knee) / log(1 + maximum / knee)
|
||||
|
||||
func severity() -> int:
|
||||
var ratio := depth_m / maxf(maximum_depth_m, 0.001)
|
||||
return 2 if ratio >= fault_fraction else (1 if ratio >= warning_fraction else 0)
|
||||
|
||||
func _draw() -> void:
|
||||
var top := 9.0
|
||||
var bottom := maxf(top + 1, size.y - 10)
|
||||
var track := get_theme_color("instrument_grid", "SurfaceSide")
|
||||
var ink := get_theme_color("font_color", "Label")
|
||||
var font := get_theme_default_font()
|
||||
draw_line(Vector2(17, top), Vector2(17, bottom), track, 2, true)
|
||||
var decade := maxf(knee_m, 0.001)
|
||||
while decade < maximum_depth_m:
|
||||
for multiplier in [1.0, 2.0, 5.0]:
|
||||
var tick: float = decade * multiplier
|
||||
if tick > 0 and tick < maximum_depth_m:
|
||||
var y: float = lerpf(top, bottom, normalized_depth(tick))
|
||||
draw_line(Vector2(14, y), Vector2(21, y), track, 1, true)
|
||||
decade *= 10
|
||||
var last_label_y := -100.0
|
||||
var last_label_value := -1.0
|
||||
var ticks: Array[float] = [0, maxf(knee_m, 0.001) * 2]
|
||||
decade = maxf(knee_m, 0.001) * 10
|
||||
while decade < maximum_depth_m:
|
||||
ticks.append(decade)
|
||||
decade *= 10
|
||||
ticks.append(maximum_depth_m)
|
||||
for tick in ticks:
|
||||
if tick < 0 or tick > maximum_depth_m or is_equal_approx(tick, last_label_value):
|
||||
continue
|
||||
var y := lerpf(top, bottom, normalized_depth(tick))
|
||||
if tick != maximum_depth_m and (y - last_label_y < 20 or (bottom - y < 20 and tick != 0)):
|
||||
continue
|
||||
draw_line(Vector2(12, y), Vector2(23, y), ink, 1, true)
|
||||
draw_string(font, Vector2(29, y + 5), String.num(tick, 3).trim_suffix(".0"), HORIZONTAL_ALIGNMENT_LEFT, -1, 14, ink)
|
||||
last_label_y = y
|
||||
last_label_value = tick
|
||||
if available:
|
||||
var y := lerpf(top, bottom, normalized_depth(depth_m))
|
||||
var role: String = ["instrument_accent", "warning", "fault"][severity()]
|
||||
var accent := get_theme_color(role, "SurfaceSide")
|
||||
draw_line(Vector2(17, top), Vector2(17, y), accent, 3, true)
|
||||
draw_colored_polygon(PackedVector2Array([Vector2(1, y - 5), Vector2(1, y + 5), Vector2(12, y)]), accent)
|
||||
draw_line(Vector2(12, y), Vector2(23, y), accent, 3, true)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cbobdfddbbddo
|
||||
@@ -0,0 +1,23 @@
|
||||
extends VBoxContainer
|
||||
|
||||
var elapsed := 0.0
|
||||
@onready var panel = $EnvironmentPanel
|
||||
|
||||
func _ready() -> void:
|
||||
$Controls/Contrast.toggled.connect(func(high: bool) -> void:
|
||||
theme = load("res://themes/theme_highcontrast.tres" if high else "res://themes/theme.tres")
|
||||
)
|
||||
$Controls/State.item_selected.connect(func(index: int) -> void:
|
||||
if index == 7: panel.clear_telemetry()
|
||||
)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
elapsed += delta
|
||||
match $Controls/State.selected:
|
||||
0: panel.set_telemetry(0, 0.10, 12.4)
|
||||
1: panel.set_telemetry(0.75 + sin(elapsed) * 0.25, 0.11, 12.4)
|
||||
2: panel.set_telemetry(248.6, 2.54, 7.4)
|
||||
3: panel.set_telemetry(850, 8.6, 4.2)
|
||||
4: panel.set_telemetry(400, 9.6, -1.5)
|
||||
5: panel.set_telemetry(1010, 10.1, 3.2)
|
||||
6, 7: pass
|
||||
@@ -0,0 +1 @@
|
||||
uid://5qywekq0w4ne
|
||||
@@ -0,0 +1,67 @@
|
||||
@tool
|
||||
extends PanelContainer
|
||||
|
||||
@export var maximum_depth_m := 1000.0
|
||||
@export var maximum_pressure_mpa := 10.0
|
||||
@export var shallow_scale_knee_m := 1.0
|
||||
@export_range(0, 1) var warning_fraction := 0.8
|
||||
@export_range(0, 1) var fault_fraction := 0.95
|
||||
@export var stale_after_seconds := 2.0
|
||||
|
||||
var _depth := 0.0
|
||||
var _pressure := 0.0
|
||||
var _temperature := 0.0
|
||||
var _received_ms := -1
|
||||
var _stale := false
|
||||
|
||||
func _ready() -> void:
|
||||
_refresh()
|
||||
|
||||
## Depth in metres, pressure in MPa, water temperature in Celsius; supplied independently.
|
||||
func set_telemetry(depth_m: float, pressure_mpa: float, water_temperature_c: float) -> bool:
|
||||
if not is_finite(depth_m) or not is_finite(pressure_mpa) or not is_finite(water_temperature_c) or pressure_mpa < 0:
|
||||
return false
|
||||
_depth = depth_m
|
||||
_pressure = pressure_mpa
|
||||
_temperature = water_temperature_c
|
||||
_received_ms = Time.get_ticks_msec()
|
||||
_refresh()
|
||||
return true
|
||||
|
||||
func clear_telemetry() -> void:
|
||||
_received_ms = -1
|
||||
_refresh()
|
||||
|
||||
func _severity(ratio: float) -> int:
|
||||
return 2 if ratio >= maxf(warning_fraction, fault_fraction) else (1 if ratio >= warning_fraction else 0)
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
var stale := _received_ms >= 0 and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000
|
||||
if stale != _stale:
|
||||
_refresh()
|
||||
|
||||
func _refresh() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
var measured := _received_ms >= 0
|
||||
_stale = measured and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000
|
||||
var depth_ratio := _depth / maxf(maximum_depth_m, 0.001)
|
||||
var pressure_ratio := _pressure / maxf(maximum_pressure_mpa, 0.001)
|
||||
var worst_ratio := maxf(depth_ratio, pressure_ratio)
|
||||
var variants := ["Label", "InstrumentWarning", "InstrumentFault"]
|
||||
%DepthValue.text = "%.2f m" % _depth if measured else "— m"
|
||||
%PressureValue.text = "%.2f MPa" % _pressure if measured else "— MPa"
|
||||
%TemperatureValue.text = "%.1f °C" % _temperature if measured else "— °C"
|
||||
%DepthReference.text = "/ %s m" % String.num(maximum_depth_m, 2).trim_suffix(".0")
|
||||
%PressureReference.text = "/ %s MPa" % String.num(maximum_pressure_mpa, 2).trim_suffix(".0")
|
||||
%DepthTitle.theme_type_variation = variants[_severity(depth_ratio)] if measured else "Label"
|
||||
%PressureTitle.theme_type_variation = variants[_severity(pressure_ratio)] if measured else "Label"
|
||||
%State.text = "NO TELEMETRY" if not measured else ("STALE" if _stale else ("OVER LIMIT" if worst_ratio > 1 else ("NEAR LIMIT" if _severity(worst_ratio) > 0 else "")))
|
||||
%State.theme_type_variation = "InstrumentMuted" if not measured else ("InstrumentWarning" if _stale else variants[_severity(worst_ratio)])
|
||||
%DepthScale.depth_m = _depth
|
||||
%DepthScale.maximum_depth_m = maxf(maximum_depth_m, 0.001)
|
||||
%DepthScale.knee_m = maxf(shallow_scale_knee_m, 0.001)
|
||||
%DepthScale.warning_fraction = warning_fraction
|
||||
%DepthScale.fault_fraction = maxf(warning_fraction, fault_fraction)
|
||||
%DepthScale.available = measured
|
||||
%DepthScale.queue_redraw()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dnafkcowopwod
|
||||
@@ -5,7 +5,7 @@ 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": "Reserved for depth, pressure and water temperature.", "scene": preload("res://components/layout_slot.tscn"), "size": Vector2i(6, 5), "minimum": Vector2i(4, 3), "reference": Vector2(360, 240), "fill": true},
|
||||
"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},
|
||||
|
||||
+86
-34
@@ -1,39 +1,36 @@
|
||||
extends SceneTree
|
||||
|
||||
const ACCENT = Color("ff7b00")
|
||||
const SECONDARY = Color("402e32")
|
||||
const CLEAR = Color.TRANSPARENT
|
||||
|
||||
const HIGH_CONTRAST = {
|
||||
"text": Color("f5f1ed"), "muted_text": Color("b9b0b3"),
|
||||
"active_text": Color("141114"), "surface": Color("201b1f"),
|
||||
"panel": Color("201b1f"), "input": Color("141114"),
|
||||
"hover": SECONDARY, "pressed": ACCENT, "fill": ACCENT,
|
||||
"disabled": Color("191619"), "border": Color("817278"),
|
||||
"disabled_border": Color("51464c"), "focus": ACCENT,
|
||||
"separator": SECONDARY, "thumb": Color("b9b0b3"),
|
||||
"fill_text": Color("141114"),
|
||||
}
|
||||
const SOFT = {
|
||||
"text": Color("e7e0dd"), "muted_text": Color("ada2a8"),
|
||||
"active_text": Color("f5f1ed"), "surface": Color("292126"),
|
||||
"panel": Color("201b1f"), "input": Color("141114"),
|
||||
"hover": SECONDARY, "pressed": SECONDARY,
|
||||
"fill": ACCENT, "disabled": Color("211c20"),
|
||||
"border": CLEAR, "disabled_border": CLEAR,
|
||||
"focus": ACCENT, "separator": SECONDARY,
|
||||
"thumb": Color("d2c6c9"), "fill_text": Color("141114"),
|
||||
}
|
||||
const COLORS = preload("res://themes/palette.gd")
|
||||
|
||||
func _initialize() -> void:
|
||||
for high_contrast in [false, true]:
|
||||
var palette: Dictionary = HIGH_CONTRAST if high_contrast else SOFT
|
||||
var palette: Dictionary = COLORS.HIGH_CONTRAST if high_contrast else COLORS.SOFT
|
||||
var path := "res://themes/theme_highcontrast.tres" if high_contrast else "res://themes/theme.tres"
|
||||
var error := ResourceSaver.save(_build_theme(palette, high_contrast), path)
|
||||
if error != OK:
|
||||
push_error("Could not save %s: %s" % [path, error_string(error)])
|
||||
quit(error)
|
||||
return
|
||||
var material := StandardMaterial3D.new()
|
||||
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
material.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
material.albedo_color = COLORS.COMPASS_TEST
|
||||
material.albedo_texture = load("res://assets/vector/dial.svg")
|
||||
var material_error := ResourceSaver.save(material, "res://themes/compass_material.tres")
|
||||
if material_error != OK:
|
||||
push_error("Could not save compass material: " + error_string(material_error))
|
||||
quit(material_error)
|
||||
return
|
||||
var config := ConfigFile.new()
|
||||
var config_error := config.load("res://project.godot")
|
||||
if config_error == OK and config.get_value("rendering", "environment/defaults/default_clear_color") != COLORS.VIEWPORT_BACKGROUND:
|
||||
config.set_value("rendering", "environment/defaults/default_clear_color", COLORS.VIEWPORT_BACKGROUND)
|
||||
config_error = config.save("res://project.godot")
|
||||
if config_error != OK:
|
||||
push_error("Could not update viewport background: " + error_string(config_error))
|
||||
quit(config_error)
|
||||
return
|
||||
quit()
|
||||
|
||||
func _build_theme(palette: Dictionary, high_contrast: bool) -> Theme:
|
||||
@@ -48,7 +45,7 @@ func _build_theme(palette: Dictionary, high_contrast: bool) -> Theme:
|
||||
if "disabled" in name or "placeholder" in name or "readonly" in name:
|
||||
color = palette.muted_text
|
||||
elif "selection" in name:
|
||||
color = SECONDARY
|
||||
color = COLORS.SECONDARY
|
||||
elif name in ["font_pressed_color", "font_hover_pressed_color"] and type in ["Button", "OptionButton", "MenuButton"]:
|
||||
color = palette.active_text
|
||||
elif name == "font_selected_color" and type == "TabContainer":
|
||||
@@ -56,11 +53,11 @@ func _build_theme(palette: Dictionary, high_contrast: bool) -> Theme:
|
||||
elif name == "font_color" and type == "ProgressBar":
|
||||
color = palette.fill_text
|
||||
elif "background" in name:
|
||||
color = CLEAR
|
||||
color = COLORS.CLEAR
|
||||
elif "caret" in name or "accent" in name:
|
||||
color = ACCENT
|
||||
color = COLORS.ACCENT
|
||||
elif "shadow" in name or "outline" in name:
|
||||
color = CLEAR
|
||||
color = COLORS.CLEAR
|
||||
theme.set_color(name, type, color)
|
||||
for name in theme.get_stylebox_list(type):
|
||||
var original := theme.get_stylebox(name, type)
|
||||
@@ -99,13 +96,11 @@ func _build_theme(palette: Dictionary, high_contrast: bool) -> Theme:
|
||||
style.bg_color = palette.separator
|
||||
style.border_color = palette.separator
|
||||
theme.set_stylebox(name, type, style)
|
||||
# Slider thumbs use square textures instead of the engine's round icons.
|
||||
for type in ["HSlider", "VSlider"]:
|
||||
for name in ["grabber", "grabber_highlight", "grabber_disabled"]:
|
||||
var bitmap := Image.create(14, 18, false, Image.FORMAT_RGBA8)
|
||||
bitmap.fill(ACCENT if name == "grabber_highlight" else palette.thumb)
|
||||
bitmap.fill(COLORS.ACCENT if name == "grabber_highlight" else palette.thumb)
|
||||
theme.set_icon(name, type, ImageTexture.create_from_image(bitmap))
|
||||
# Dedicated panel states used by the keyboard focus containers.
|
||||
var panel := theme.get_stylebox("panel", "PanelContainer").duplicate() as StyleBoxFlat
|
||||
theme.set_stylebox("panel_inactive", "SurfaceSide", panel)
|
||||
var active := panel.duplicate() as StyleBoxFlat
|
||||
@@ -114,6 +109,63 @@ func _build_theme(palette: Dictionary, high_contrast: bool) -> Theme:
|
||||
if not high_contrast:
|
||||
active.border_width_top = 2
|
||||
theme.set_stylebox("panel_active", "SurfaceSide", active)
|
||||
theme.set_color("accent", "SurfaceSide", ACCENT)
|
||||
theme.set_color("secondary", "SurfaceSide", SECONDARY)
|
||||
theme.set_color("accent", "SurfaceSide", COLORS.ACCENT)
|
||||
theme.set_color("secondary", "SurfaceSide", COLORS.SECONDARY)
|
||||
_add_instrument_theme(theme, palette)
|
||||
for role in COLORS.STATUS:
|
||||
theme.set_color(role, "SurfaceSide", COLORS.STATUS[role])
|
||||
theme.set_type_variation("VehicleWarningText", "Label")
|
||||
theme.set_color("font_color", "VehicleWarningText", COLORS.STATUS.status_ink)
|
||||
theme.set_type_variation("VehicleFaultText", "Label")
|
||||
theme.set_color("font_color", "VehicleFaultText", COLORS.WHITE)
|
||||
theme.set_type_variation("VehicleBackground", "PanelContainer")
|
||||
var vehicle_background := StyleBoxFlat.new()
|
||||
vehicle_background.bg_color = palette.panel
|
||||
vehicle_background.border_color = palette.border
|
||||
vehicle_background.set_border_width_all(1 if high_contrast else 0)
|
||||
theme.set_stylebox("panel", "VehicleBackground", vehicle_background)
|
||||
theme.set_type_variation("LayoutItemBackground", "Panel")
|
||||
theme.set_stylebox("panel", "LayoutItemBackground", vehicle_background)
|
||||
theme.set_type_variation("LayoutLibraryList", "ItemList")
|
||||
theme.set_color("guide_color", "LayoutLibraryList", COLORS.SECONDARY if high_contrast else COLORS.CLEAR)
|
||||
theme.set_constant("v_separation", "LayoutLibraryList", 8)
|
||||
for variant in ["LinkRateNormal", "LinkRateWarning", "LinkRateFault", "LinkMuted"]:
|
||||
theme.set_type_variation(variant, "Label")
|
||||
theme.set_color("font_color", "LinkRateNormal", COLORS.INSTRUMENT.instrument_accent)
|
||||
theme.set_color("font_color", "LinkRateWarning", COLORS.WARNING)
|
||||
theme.set_color("font_color", "LinkRateFault", COLORS.FAULT)
|
||||
theme.set_color("font_color", "LinkMuted", palette.muted_text)
|
||||
for variant in ["InstrumentWarning", "InstrumentFault", "InstrumentMuted"]:
|
||||
theme.set_type_variation(variant, "Label")
|
||||
theme.set_color("font_color", "InstrumentWarning", COLORS.WARNING)
|
||||
theme.set_color("font_color", "InstrumentFault", COLORS.FAULT)
|
||||
theme.set_color("font_color", "InstrumentMuted", palette.muted_text)
|
||||
return theme
|
||||
|
||||
func _add_instrument_theme(theme: Theme, palette: Dictionary) -> void:
|
||||
for role in COLORS.INSTRUMENT:
|
||||
theme.set_color(role, "SurfaceSide", COLORS.INSTRUMENT[role])
|
||||
theme.set_color("instrument_empty", "SurfaceSide", palette.input)
|
||||
theme.set_color("instrument_axis", "SurfaceSide", palette.muted_text)
|
||||
theme.set_type_variation("MotionBackground", "Panel")
|
||||
var background := StyleBoxFlat.new()
|
||||
background.bg_color = palette.panel
|
||||
theme.set_stylebox("panel", "MotionBackground", background)
|
||||
for vertical in [false, true]:
|
||||
var type := "MotionVSeparator" if vertical else "MotionHSeparator"
|
||||
theme.set_type_variation(type, "VSeparator" if vertical else "HSeparator")
|
||||
var separator := StyleBoxLine.new()
|
||||
separator.color = COLORS.INSTRUMENT.instrument_grid
|
||||
separator.thickness = 1
|
||||
separator.vertical = vertical
|
||||
theme.set_stylebox("separator", type, separator)
|
||||
theme.set_type_variation("MotionAlertPanel", "PanelContainer")
|
||||
var alert := StyleBoxFlat.new()
|
||||
alert.bg_color = COLORS.INSTRUMENT.alert_background
|
||||
alert.content_margin_top = 7
|
||||
alert.content_margin_bottom = 7
|
||||
theme.set_stylebox("panel", "MotionAlertPanel", alert)
|
||||
theme.set_type_variation("MotionAlertText", "Label")
|
||||
theme.set_color("font_color", "MotionAlertText", COLORS.INSTRUMENT.alert_text)
|
||||
theme.set_type_variation("MotionWarning", "Label")
|
||||
theme.set_color("font_color", "MotionWarning", COLORS.INSTRUMENT.instrument_accent)
|
||||
@@ -8,6 +8,8 @@ const PANEL = Color("201b1f")
|
||||
const SKY = Color("172f40")
|
||||
const GROUND = Color("4b3930")
|
||||
const COMPASS_TEST = Color(0.44361365, 0.72118443, 0, 1)
|
||||
const WARNING = Color("f5c542")
|
||||
const FAULT = Color("dc414b")
|
||||
const WHITE = Color.WHITE
|
||||
const CLEAR = Color.TRANSPARENT
|
||||
|
||||
@@ -55,3 +57,12 @@ static var INSTRUMENT: Dictionary = {
|
||||
"alert_background": ACCENT_DARK,
|
||||
"alert_text": WHITE,
|
||||
}
|
||||
|
||||
static var STATUS: Dictionary = {
|
||||
"warning": WARNING,
|
||||
"warning_alternate": WARNING.darkened(0.36),
|
||||
"fault": FAULT,
|
||||
"fault_alternate": FAULT.darkened(0.50),
|
||||
"status_ink": INPUT,
|
||||
"status_icon": ACCENT_LIGHT,
|
||||
}
|
||||
+1409
-803
File diff suppressed because it is too large.
Load diff
+830
-756
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user