diff --git a/README.md b/README.md index a3dabfb..d4364d7 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ For example, the entity of name `lights.smart_lamp_1` would control the kitchen ├── assets (Files like logos or assets that are shared across scenes) ├── content/ (Main files of the project) │ ├── entities (Entities that can be placed into the room) +│ ├── functions (Generic functions that can be used in scenes) │ └── ui (User Interface Scenes and related files) └── lib/ (Code that is global or shared across scenes) ├── globals (Globally running scripts) @@ -80,11 +81,14 @@ func watch_state(entity: String, callback: Callable[entity: Entity]) -> Callable ### Interaction Events Each time a button is pressed on the primary controller, a ray-cast is done to be able to interact with devices or the UI. +Additionally, each event will bubble up until the root node is reached, allowing to handle events on parents. +In case that an event of a specific node has to be reacted on, use the `Clickable` function node. ```python InteractionEvent { "controller": XRController3D, # The controller that triggered the event "ray": RayCast3D, # The ray-cast that triggered the event + "target": Node3D, # The node that was hit by the ray-cast } ``` @@ -100,6 +104,18 @@ InteractionEvent { | `_on_ray_enter` | `[event: InteractionEvent]` | The ray-cast enters the the collision body | | `_on_ray_leave` | `[event: InteractionEvent]` | The ray-cast leaves the the collision body | +### Functions + +In order to implement generic features, a set of functions is available to be used in the project. + +#### Movable + +The `Movable` node allows to move a node around in the room. It uses the grab events in order to transform the parent node. + +#### Clickable + +The `Clickable` allows to access events of the parent using signals this node emits. + ### Testing without a VR Headset In order to test without a headset, press the run project (F5) button in Godot and ignore the prompt that OpenXR failed to start. diff --git a/assets/design.afdesign b/assets/design.afdesign index 5e102a7..88cc857 100644 --- a/assets/design.afdesign +++ b/assets/design.afdesign @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:034fb07ad84872c283141a5f2ae34989b349cdf77328cab5458a9263f58e4967 -size 2984138 +oid sha256:d9423ef5121871fe9f60a673f76728265d2618f318a14e73affb4257cb42f73d +size 3906893 diff --git a/assets/materials/arrow_right.png b/assets/materials/arrow_right.png new file mode 100644 index 0000000..7671ab8 --- /dev/null +++ b/assets/materials/arrow_right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a00d470585d14a17ca080c46003908207238e9595ea99adc4c40e759d9c13c58 +size 10494 diff --git a/assets/materials/arrow_right.png.import b/assets/materials/arrow_right.png.import new file mode 100644 index 0000000..02e0977 --- /dev/null +++ b/assets/materials/arrow_right.png.import @@ -0,0 +1,36 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tr8yrx470k03" +path.s3tc="res://.godot/imported/arrow_right.png-5fd8dcaad0caa5233d1b8c443dbe1e1d.s3tc.ctex" +path.etc2="res://.godot/imported/arrow_right.png-5fd8dcaad0caa5233d1b8c443dbe1e1d.etc2.ctex" +metadata={ +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/materials/arrow_right.png" +dest_files=["res://.godot/imported/arrow_right.png-5fd8dcaad0caa5233d1b8c443dbe1e1d.s3tc.ctex", "res://.godot/imported/arrow_right.png-5fd8dcaad0caa5233d1b8c443dbe1e1d.etc2.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +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/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 diff --git a/assets/materials/pointer.png b/assets/materials/pointer.png new file mode 100644 index 0000000..00aa14b --- /dev/null +++ b/assets/materials/pointer.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d62537d8aa4fee28ed5484c8c7ba96c2c9a7fa7bc0740e79420f01105602ca5 +size 43035 diff --git a/assets/materials/pointer.png.import b/assets/materials/pointer.png.import new file mode 100644 index 0000000..9e97001 --- /dev/null +++ b/assets/materials/pointer.png.import @@ -0,0 +1,36 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bo55nohs0wsgf" +path.s3tc="res://.godot/imported/pointer.png-bc1b217fc800145e13fa1a1689c1f1ee.s3tc.ctex" +path.etc2="res://.godot/imported/pointer.png-bc1b217fc800145e13fa1a1689c1f1ee.etc2.ctex" +metadata={ +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/materials/pointer.png" +dest_files=["res://.godot/imported/pointer.png-bc1b217fc800145e13fa1a1689c1f1ee.s3tc.ctex", "res://.godot/imported/pointer.png-bc1b217fc800145e13fa1a1689c1f1ee.etc2.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +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/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 diff --git a/content/functions/clickable.gd b/content/functions/clickable.gd new file mode 100644 index 0000000..bbf1366 --- /dev/null +++ b/content/functions/clickable.gd @@ -0,0 +1,39 @@ +extends Function +class_name Clickable + +signal on_click(event: Dictionary) +signal on_press_down(event: Dictionary) +signal on_press_move(event: Dictionary) +signal on_press_up(event: Dictionary) +signal on_grab_down(event: Dictionary) +signal on_grab_move(event: Dictionary) +signal on_grab_up(event: Dictionary) +signal on_ray_enter(event: Dictionary) +signal on_ray_leave(event: Dictionary) + +func _on_click(event: Dictionary): + on_click.emit(event) + +func _on_press_down(event: Dictionary): + on_press_down.emit(event) + +func _on_press_move(event: Dictionary): + on_press_move.emit(event) + +func _on_press_up(event: Dictionary): + on_press_up.emit(event) + +func _on_grab_down(event: Dictionary): + on_grab_down.emit(event) + +func _on_grab_move(event: Dictionary): + on_grab_move.emit(event) + +func _on_grab_up(event: Dictionary): + on_grab_up.emit(event) + +func _on_ray_enter(event: Dictionary): + on_ray_enter.emit(event) + +func _on_ray_leave(event: Dictionary): + on_ray_leave.emit(event) \ No newline at end of file diff --git a/content/main.tscn b/content/main.tscn index 1da5ecb..3276d32 100644 --- a/content/main.tscn +++ b/content/main.tscn @@ -1,9 +1,10 @@ -[gd_scene load_steps=11 format=3 uid="uid://eecv28y6jxk4"] +[gd_scene load_steps=12 format=3 uid="uid://eecv28y6jxk4"] [ext_resource type="PackedScene" uid="uid://clc5dre31iskm" path="res://addons/godot-xr-tools/xr/start_xr.tscn" id="1_i4c04"] [ext_resource type="Script" path="res://content/raycast.gd" id="1_tsqxc"] [ext_resource type="Script" path="res://content/main.gd" id="1_uvrd4"] [ext_resource type="PackedScene" uid="uid://c3kdssrmv84kv" path="res://content/ui/menu/menu.tscn" id="3_1tbp3"] +[ext_resource type="Texture2D" uid="uid://bo55nohs0wsgf" path="res://assets/materials/pointer.png" id="4_wcfej"] [ext_resource type="PackedScene" uid="uid://ctltchlf2j2r4" path="res://addons/xr-simulator/XRSimulator.tscn" id="5_3qc8g"] [ext_resource type="Material" uid="uid://bf5ina366dwm6" path="res://assets/materials/sky.material" id="5_wgwf8"] @@ -42,7 +43,7 @@ pose = &"aim" mesh = SubResource("BoxMesh_ir3co") [node name="Menu" parent="XROrigin3D/XRControllerLeft" instance=ExtResource("3_1tbp3")] -transform = Transform3D(-4.37114e-08, 0, -1, -0.707107, 0.707107, 3.09086e-08, 0.707107, 0.707107, -3.09086e-08, 0.183517, 0, -0.0534939) +transform = Transform3D(1, -0.000382732, -0.000120985, 8.65898e-05, 0.5, -0.866025, 0.000391948, 0.866025, 0.5, 0.0669508, 0.0876772, -0.101157) [node name="XRControllerRight" type="XRController3D" parent="XROrigin3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.488349, 0.559219, -0.2988) @@ -60,6 +61,11 @@ ray = NodePath("RayCast3D") transform = Transform3D(-2.58078e-11, 4.3714e-08, 1, 1, -4.37117e-08, 9.27469e-12, 4.37112e-08, 1, -4.3714e-08, 0, 0, 0) target_position = Vector3(0, -5, 0) +[node name="Decal" type="Decal" parent="XROrigin3D/XRControllerRight/Raycast"] +transform = Transform3D(0.999999, -0.000567105, -2.5179e-05, -2.51789e-05, 4.39886e-08, -0.999999, 0.000567105, 1, 2.97064e-08, -0.000775784, -1.09076e-05, -2.46767) +size = Vector3(0.01, 5, 0.01) +texture_albedo = ExtResource("4_wcfej") + [node name="StartXR" parent="." instance=ExtResource("1_i4c04")] enable_passthrough = true diff --git a/content/main_golf.tscn b/content/main_golf.tscn new file mode 100644 index 0000000..89d8588 --- /dev/null +++ b/content/main_golf.tscn @@ -0,0 +1,129 @@ +[gd_scene load_steps=18 format=3 uid="uid://fap7m74qctpl"] + +[ext_resource type="Script" path="res://content/main.gd" id="1_d7nko"] +[ext_resource type="PackedScene" uid="uid://c3kdssrmv84kv" path="res://content/ui/menu/menu.tscn" id="2_1ns4p"] +[ext_resource type="Script" path="res://content/raycast.gd" id="3_raorn"] +[ext_resource type="PackedScene" uid="uid://clc5dre31iskm" path="res://addons/godot-xr-tools/xr/start_xr.tscn" id="4_6x466"] +[ext_resource type="Material" uid="uid://bf5ina366dwm6" path="res://assets/materials/sky.material" id="5_o7oeh"] +[ext_resource type="PackedScene" uid="uid://ctltchlf2j2r4" path="res://addons/xr-simulator/XRSimulator.tscn" id="6_yj6uv"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_m58yb"] +ao_enabled = true + +[sub_resource type="BoxMesh" id="BoxMesh_ir3co"] +material = SubResource("StandardMaterial3D_m58yb") +size = Vector3(0.01, 0.01, 0.01) + +[sub_resource type="Sky" id="Sky_vhymk"] +sky_material = ExtResource("5_o7oeh") + +[sub_resource type="Environment" id="Environment_7ghp0"] +background_mode = 2 +background_color = Color(0.466667, 0.47451, 0.462745, 0) +sky = SubResource("Sky_vhymk") +ambient_light_color = Color(1, 1, 1, 1) +ambient_light_sky_contribution = 0.72 + +[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_5qi0p"] +rough = true + +[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_i18hv"] + +[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_t7p2m"] +rough = true + +[sub_resource type="SphereShape3D" id="SphereShape3D_wckr8"] +radius = 0.1 + +[sub_resource type="SphereMesh" id="SphereMesh_5b0e3"] +radius = 0.1 +height = 0.2 + +[sub_resource type="BoxMesh" id="BoxMesh_4w3j6"] +size = Vector3(0.02, 1, 0.02) + +[sub_resource type="BoxShape3D" id="BoxShape3D_huggw"] +size = Vector3(0.02, 1, 0.02) + +[node name="Main" type="Node3D"] +transform = Transform3D(1, -0.000296142, 0.000270963, 0.000296143, 1, -4.61078e-06, -0.000270962, 4.67014e-06, 1, 0, 0, 0) +script = ExtResource("1_d7nko") + +[node name="XROrigin3D" type="XROrigin3D" parent="."] + +[node name="XRCamera3D" type="XRCamera3D" parent="XROrigin3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.798091, 0.311748) + +[node name="XRControllerLeft" type="XRController3D" parent="XROrigin3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.469893, 0.597213, -0.251112) +tracker = &"left_hand" +pose = &"aim" + +[node name="MeshInstance3D" type="MeshInstance3D" parent="XROrigin3D/XRControllerLeft"] +mesh = SubResource("BoxMesh_ir3co") + +[node name="Menu" parent="XROrigin3D/XRControllerLeft" instance=ExtResource("2_1ns4p")] +transform = Transform3D(-4.37114e-08, 0, -1, -0.707107, 0.707107, 3.09086e-08, 0.707107, 0.707107, -3.09086e-08, 0.183517, 0, -0.0534939) + +[node name="XRControllerRight" type="XRController3D" parent="XROrigin3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.488349, 0.559219, -0.2988) +tracker = &"right_hand" +pose = &"aim" + +[node name="MeshInstance3D" type="MeshInstance3D" parent="XROrigin3D/XRControllerRight"] +mesh = SubResource("BoxMesh_ir3co") + +[node name="Raycast" type="Node3D" parent="XROrigin3D/XRControllerRight" node_paths=PackedStringArray("ray")] +script = ExtResource("3_raorn") +ray = NodePath("RayCast3D") + +[node name="RayCast3D" type="RayCast3D" parent="XROrigin3D/XRControllerRight/Raycast"] +transform = Transform3D(-2.58078e-11, 4.3714e-08, 1, 1, -4.37117e-08, 9.27469e-12, 4.37112e-08, 1, -4.3714e-08, 0, 0, 0) +target_position = Vector3(0, -5, 0) + +[node name="RemoteTransform3D" type="RemoteTransform3D" parent="XROrigin3D/XRControllerRight"] +remote_path = NodePath("../../../Club (AnimatableBody3D)") + +[node name="StartXR" parent="." instance=ExtResource("4_6x466")] +enable_passthrough = true + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_7ghp0") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(0.834925, -0.386727, -0.39159, 0.550364, 0.586681, 0.594058, 0, -0.711511, 0.702675, 0, 7.21041, 2.06458) +shadow_enabled = true + +[node name="XRSimulator" parent="." instance=ExtResource("6_yj6uv")] +xr_origin = NodePath("../XROrigin3D") + +[node name="Ground (StaticBody3D)" type="StaticBody3D" parent="."] +transform = Transform3D(1, -1.39636e-11, 0, 9.47997e-12, 1, 0, 0, 0, 1, 0, 0, 0) +physics_material_override = SubResource("PhysicsMaterial_5qi0p") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Ground (StaticBody3D)"] +transform = Transform3D(0.999999, -1.39631e-11, 0, 9.48108e-12, 0.999999, -4.54747e-13, 0, -4.54747e-13, 0.999999, 0, 0, 0) +shape = SubResource("WorldBoundaryShape3D_i18hv") + +[node name="Ball (RigidBody3D)" type="RigidBody3D" parent="."] +transform = Transform3D(0.999997, -1.39633e-11, 0, 9.48364e-12, 0.999998, -1.81899e-12, 0, 5.91172e-12, 0.999998, 0.487249, 1.15211, -0.679336) +physics_material_override = SubResource("PhysicsMaterial_t7p2m") +angular_damp = 4.0 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Ball (RigidBody3D)"] +shape = SubResource("SphereShape3D_wckr8") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="Ball (RigidBody3D)"] +transform = Transform3D(1, -1.39641e-11, 0, 9.47986e-12, 1, 0, 2.91038e-11, 0, 1, 0, 0, 0) +mesh = SubResource("SphereMesh_5b0e3") + +[node name="Club (AnimatableBody3D)" type="AnimatableBody3D" parent="."] +transform = Transform3D(1, -1.39637e-11, 0, 9.47975e-12, 1, 0, 0, 0, 1, 0.488349, 0.559219, -0.2988) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="Club (AnimatableBody3D)"] +transform = Transform3D(1, -0.000567105, -2.51786e-05, -2.51789e-05, 4.39913e-08, -0.999999, 0.000567105, 1, 2.97096e-08, 0.000972658, -0.00257713, -0.524774) +mesh = SubResource("BoxMesh_4w3j6") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Club (AnimatableBody3D)"] +transform = Transform3D(1, -0.000567105, -2.51788e-05, -2.51788e-05, 4.39918e-08, -1, 0.000567105, 1, 2.97127e-08, 0.000972658, -0.00257713, -0.524774) +shape = SubResource("BoxShape3D_huggw") diff --git a/content/raycast.gd b/content/raycast.gd index 03a3c32..1e8aabb 100644 --- a/content/raycast.gd +++ b/content/raycast.gd @@ -18,12 +18,6 @@ func _physics_process(delta): _handle_enter_leave() _handle_move() -func _get_event_data(): - return { - "controller": _controller, - "ray": ray, - } - func _handle_move(): if _is_pressed == false && _is_grabbed == false: return @@ -86,11 +80,27 @@ func _on_button_released(button): _is_grabbed = false _moved = false -func _call_fn(collider: Object, fn_name: String): - if collider != null: - if collider.has_method(fn_name): - collider.call(fn_name, _get_event_data()) +func _call_fn(collider: Object, fn_name: String, node: Node3D = null): + if collider == null: + return - for child in collider.get_children(): - if child is Function: - _call_fn(child, fn_name) + if node == null: + node = collider + + var event = { + "controller": _controller, + "ray": ray, + "target": collider, + } + + for child in node.get_children(): + if child is Function && child.has_method(fn_name): + child.call(fn_name, event) + + if node.has_method(fn_name): + node.call(fn_name, event) + + var parent = node.get_parent() + + if parent != null && parent is Node3D: + _call_fn(collider, fn_name, parent) diff --git a/content/ui/button/button.gd b/content/ui/button/button.gd new file mode 100644 index 0000000..8e6c8be --- /dev/null +++ b/content/ui/button/button.gd @@ -0,0 +1,9 @@ +extends StaticBody3D + +@onready var animation_player: AnimationPlayer = $AnimationPlayer + +func _on_press_down(_event): + animation_player.play("down") + +func _on_press_up(_event): + animation_player.play("up") diff --git a/content/ui/button/button.tscn b/content/ui/button/button.tscn new file mode 100644 index 0000000..5821dbb --- /dev/null +++ b/content/ui/button/button.tscn @@ -0,0 +1,79 @@ +[gd_scene load_steps=9 format=3 uid="uid://bsjqdvkt0u87c"] + +[ext_resource type="Script" path="res://content/ui/button/button.gd" id="1_g5u54"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_peqek"] +albedo_color = Color(0.151534, 0.211909, 0.619523, 1) + +[sub_resource type="BoxMesh" id="BoxMesh_jwpm5"] +material = SubResource("StandardMaterial3D_peqek") +size = Vector3(0.05, 0.02, 0.05) + +[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_o4j7g"] +points = PackedVector3Array(-0.025, -0.01, -0.025, -0.025, 0.01, -0.025, 0.025, -0.01, -0.025, -0.025, -0.01, 0.025, -0.025, 0.01, 0.025, 0.025, 0.01, -0.025, 0.025, -0.01, 0.025, 0.025, 0.01, 0.025) + +[sub_resource type="Animation" id="Animation_04k6i"] +length = 0.001 +tracks/0/type = "bezier" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position:y") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"handle_modes": PackedInt32Array(0), +"points": PackedFloat32Array(0.01, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0) +} + +[sub_resource type="Animation" id="Animation_iu2ed"] +resource_name = "down" +length = 0.4 +tracks/0/type = "bezier" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position:y") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"handle_modes": PackedInt32Array(0, 0), +"points": PackedFloat32Array(0.01, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0, 0.4) +} + +[sub_resource type="Animation" id="Animation_y7xum"] +resource_name = "up" +length = 0.4 +tracks/0/type = "bezier" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position:y") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"handle_modes": PackedInt32Array(0, 0), +"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0.01, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0, 0.4) +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_sbgno"] +_data = { +"RESET": SubResource("Animation_04k6i"), +"down": SubResource("Animation_iu2ed"), +"up": SubResource("Animation_y7xum") +} + +[node name="Button" type="StaticBody3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.01, 0) +script = ExtResource("1_g5u54") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +mesh = SubResource("BoxMesh_jwpm5") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +shape = SubResource("ConvexPolygonShape3D_o4j7g") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +libraries = { +"": SubResource("AnimationLibrary_sbgno") +} diff --git a/content/ui/device/device.gd b/content/ui/device/device.gd index dba05ab..1ec8175 100644 --- a/content/ui/device/device.gd +++ b/content/ui/device/device.gd @@ -1,13 +1,8 @@ -extends StaticBody3D +extends Node3D -@onready var label: Label3D = $Label +@onready var label: Label3D = $Button/Label @export var id: String = "0" -signal click(id: String) - -func _on_click(event): - click.emit(id) - func set_device_name(text): assert(label != null, "Device has to be added to the scene tree") - label.text = text \ No newline at end of file + label.text = text diff --git a/content/ui/device/device.tscn b/content/ui/device/device.tscn index 3c8ea3a..ee30614 100644 --- a/content/ui/device/device.tscn +++ b/content/ui/device/device.tscn @@ -1,23 +1,19 @@ [gd_scene load_steps=4 format=3 uid="uid://dbe8slnyhro2n"] [ext_resource type="Script" path="res://content/ui/device/device.gd" id="1_rbo86"] +[ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/button/button.tscn" id="2_go2es"] +[ext_resource type="Script" path="res://content/functions/clickable.gd" id="3_6wicx"] -[sub_resource type="BoxMesh" id="BoxMesh_aa3i4"] -size = Vector3(0.05, 0.01, 0.05) - -[sub_resource type="BoxShape3D" id="BoxShape3D_28fjq"] -size = Vector3(0.05, 0.01, 0.05) - -[node name="Device" type="StaticBody3D"] +[node name="Device" type="Node3D"] script = ExtResource("1_rbo86") -[node name="MeshInstance3D" type="MeshInstance3D" parent="."] -mesh = SubResource("BoxMesh_aa3i4") +[node name="Button" parent="." instance=ExtResource("2_go2es")] -[node name="CollisionShape3D" type="CollisionShape3D" parent="."] -shape = SubResource("BoxShape3D_28fjq") - -[node name="Label" type="Label3D" parent="."] -transform = Transform3D(-2.18557e-09, -0.05, -2.18557e-09, 0, -2.18557e-09, 0.05, -0.05, 2.18557e-09, 9.55343e-17, 0, 0.00918245, 0) -text = "Text" +[node name="Label" type="Label3D" parent="Button"] +transform = Transform3D(0.05, 0, 0, 0, -2.18557e-09, 0.05, 0, -0.05, -2.18557e-09, 0, 0.0142873, 0) +text = "Texttexttexttexttexttextext" autowrap_mode = 3 +width = 200.0 + +[node name="Clickable" type="Node" parent="."] +script = ExtResource("3_6wicx") diff --git a/content/ui/entity/entity.gd b/content/ui/entity/entity.gd index aa7a3ad..a4341ef 100644 --- a/content/ui/entity/entity.gd +++ b/content/ui/entity/entity.gd @@ -3,11 +3,6 @@ extends StaticBody3D @onready var label: Label3D = $Label @export var text = "Default" -signal click(name: String) - -func _on_click(event): - click.emit(text) - func set_entity_name(text): assert(label != null, "Entity has to be added to the scene tree") label.text = text.replace(".", "\n") diff --git a/content/ui/entity/entity.tscn b/content/ui/entity/entity.tscn index a80a77d..7c5e872 100644 --- a/content/ui/entity/entity.tscn +++ b/content/ui/entity/entity.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=4 format=3 uid="uid://xo0o5nrfjl23"] +[gd_scene load_steps=5 format=3 uid="uid://xo0o5nrfjl23"] [ext_resource type="Script" path="res://content/ui/entity/entity.gd" id="1_825oj"] +[ext_resource type="Script" path="res://content/functions/clickable.gd" id="2_i054q"] [sub_resource type="BoxMesh" id="BoxMesh_aa3i4"] size = Vector3(0.05, 0.01, 0.05) @@ -18,6 +19,10 @@ mesh = SubResource("BoxMesh_aa3i4") shape = SubResource("BoxShape3D_28fjq") [node name="Label" type="Label3D" parent="."] -transform = Transform3D(-2.18557e-09, -0.05, -2.18557e-09, 0, -2.18557e-09, 0.05, -0.05, 2.18557e-09, 9.55343e-17, 0, 0.00918245, 0) +transform = Transform3D(0.05, 0, 0, 0, -2.18557e-09, 0.05, 0, -0.05, -2.18557e-09, 0, 0.00918245, 0) text = "Text" autowrap_mode = 3 +width = 200.0 + +[node name="Clickable" type="Node" parent="."] +script = ExtResource("2_i054q") diff --git a/content/ui/menu/container3d.gd b/content/ui/menu/container3d.gd new file mode 100644 index 0000000..4818f29 --- /dev/null +++ b/content/ui/menu/container3d.gd @@ -0,0 +1,15 @@ +extends Node3D +class_name Container3D + +@export var size := Vector3(1.0, 1.0, 1.0) : + set(value): + size = value + _update_container() + +@export var padding: Vector4 = Vector4(0, 0, 0, 0) : + set(value): + padding = value + _update_container() + +func _update_container(): + pass \ No newline at end of file diff --git a/content/ui/menu/grid.gd b/content/ui/menu/grid.gd new file mode 100644 index 0000000..3bff0b0 --- /dev/null +++ b/content/ui/menu/grid.gd @@ -0,0 +1,42 @@ +@tool +extends Container3D +class_name GridContainer3D + + +@export var columns := 5 : + set(value): + columns = value + _update_container() + +@export var rows := 1 : + set(value): + rows = value + _update_container() + +@export var depth_gap := 1.0 : + set(value): + depth_gap = value + _update_container() + +func _ready(): + _update_container() + +func get_gaps() -> Vector3: + return Vector3( + (float(size.x) / (columns - 1 )) if columns != 1 else 0.0, + (float(size.y) / (rows - 1)) if rows != 1 else 0.0, + depth_gap + ) + + +func _update_container(): + var i := 0 + var gaps := get_gaps() + + for child in get_children(): + var x := (i % columns) * gaps.x + var y := ((i / columns) % rows) * gaps.y + var z := (i / (columns * rows)) * gaps.z + + child.set_position(Vector3(x, -y, z)) + i += 1 \ No newline at end of file diff --git a/content/ui/menu/menu.gd b/content/ui/menu/menu.gd index 0c16034..66ddad8 100644 --- a/content/ui/menu/menu.gd +++ b/content/ui/menu/menu.gd @@ -6,77 +6,123 @@ const Switch = preload("res://content/entities/switch/switch.tscn") const Light = preload("res://content/entities/light/light.tscn") const Sensor = preload("res://content/entities/sensor/sensor.tscn") -@onready var devices_node = $Devices +@onready var devices_node: GridContainer3D = $Devices +@onready var next_page_button = $NextPageButton +@onready var previous_page_button = $PreviousPageButton +@onready var page_number_label = $PageNumberLabel var devices +var page = 0 +var last_device_page = 0 +var page_size = 20 +var pages = 0 var selected_device = null # Called when the node enters the scene tree for the first time. func _ready(): devices = await HomeAdapters.adapter.get_devices() - render_devices() + + next_page_button.get_node("Clickable").on_click.connect(func(_event): + print("next page") + next_page() + ) + + previous_page_button.get_node("Clickable").on_click.connect(func(_event): + previous_page() + ) + + render() + +func update_pages(): + if selected_device == null: + pages = ceil(float(devices.size()) / page_size) + else: + for device in devices: + if device.keys()[0] == selected_device: + pages = ceil(float(device.values()[0]["entities"].size()) / page_size) + +func get_page(): + if selected_device == null: + return devices.slice(page * page_size, page * page_size + page_size) + else: + for device in devices: + if device.keys()[0] == selected_device: + return device.values()[0]["entities"].slice(page * page_size, page * page_size + page_size) + +func next_page(): + if page >= pages - 1: + return + page += 1 + render() + +func previous_page(): + if page <= 0: + return + + page -= 1 + render() + +func render(): + update_pages() + page_number_label.set_text(str(page + 1) + " / " + str(pages)) + + previous_page_button.visible = page > 0 + next_page_button.visible = page < pages - 1 + + clear_menu() + if selected_device == null: + render_devices() + else: + render_entities() func render_devices(): - var x = 0 - var y = 0 - - for device in devices: + var page_devices = get_page() + + for device in page_devices: var info = device.values()[0] var device_instance = Device.instantiate() - device_instance.set_position(Vector3(y * 0.08, 0, -x * 0.08)) - device_instance.click.connect(_on_device_click) device_instance.id = device.keys()[0] - + device_instance.get_node("Clickable").on_click.connect(func(_event): + _on_device_click(device_instance.id) + ) devices_node.add_child(device_instance) - device_instance.set_device_name(info["name"]) - - x += 1 - if x % 5 == 0: - x = 0 - y += 1 + + devices_node._update_container() func render_entities(): - var x = 0 - var y = 0 - - var info + var entities = get_page() - for device in devices: - if device.keys()[0] == selected_device: - info = device.values()[0] - break - - if info == null: - return - - var entities = info["entities"] + var back_button = Entity.instantiate() + back_button.get_node("Clickable").on_click.connect(func(_event): + _on_entity_click("#back") + ) + devices_node.add_child(back_button) + back_button.set_entity_name("#back") for entity in entities: var entity_instance = Entity.instantiate() - entity_instance.set_position(Vector3(y * 0.08, 0, -x * 0.08)) - entity_instance.click.connect(_on_entity_click) - + entity_instance.get_node("Clickable").on_click.connect(func(_event): + _on_entity_click(entity) + ) devices_node.add_child(entity_instance) - entity_instance.set_entity_name(entity) - - x += 1 - if x % 5 == 0: - x = 0 - y += 1 + + devices_node._update_container() func _on_device_click(device_id): selected_device = device_id - print(selected_device) - clear_menu() - render_entities() + last_device_page = page + page = 0 + + render() func _on_entity_click(entity_name): - print(entity_name) - selected_device = null - clear_menu() - render_devices() + if entity_name == "#back": + selected_device = null + page = last_device_page + render() + return var type = entity_name.split(".")[0] print(type) diff --git a/content/ui/menu/menu.tscn b/content/ui/menu/menu.tscn index 928c938..5c2ac19 100644 --- a/content/ui/menu/menu.tscn +++ b/content/ui/menu/menu.tscn @@ -1,7 +1,11 @@ -[gd_scene load_steps=4 format=3 uid="uid://c3kdssrmv84kv"] +[gd_scene load_steps=8 format=3 uid="uid://c3kdssrmv84kv"] [ext_resource type="Script" path="res://content/ui/menu/menu.gd" id="1_ng4u3"] [ext_resource type="Material" uid="uid://bertj8bp8b5l1" path="res://assets/materials/interface.tres" id="2_nsukb"] +[ext_resource type="Script" path="res://content/ui/menu/grid.gd" id="3_35a5r"] +[ext_resource type="Texture2D" uid="uid://tr8yrx470k03" path="res://assets/materials/arrow_right.png" id="4_7frvc"] +[ext_resource type="Script" path="res://content/functions/clickable.gd" id="4_f385t"] +[ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/button/button.tscn" id="5_w4i01"] [sub_resource type="PlaneMesh" id="PlaneMesh_6t3dn"] material = ExtResource("2_nsukb") @@ -11,7 +15,38 @@ size = Vector2(0.3, 0.3) script = ExtResource("1_ng4u3") [node name="Background" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.15, 0, 0.15) mesh = SubResource("PlaneMesh_6t3dn") [node name="Devices" type="Node3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.149223, 0, 0.150667) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.03, 0, 0.03) +script = ExtResource("3_35a5r") +depth_gap = 0.06 +size = Vector3(0.24, 0.1, 0.1) + +[node name="NextPageButton" parent="." instance=ExtResource("5_w4i01")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.19, 0.01, 0.27) + +[node name="Decal" type="Decal" parent="NextPageButton"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.02, 0) +size = Vector3(0.04, 0.04, 0.04) +texture_albedo = ExtResource("4_7frvc") + +[node name="Clickable" type="Node" parent="NextPageButton"] +script = ExtResource("4_f385t") + +[node name="PreviousPageButton" parent="." instance=ExtResource("5_w4i01")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.11, 0.01, 0.27) + +[node name="Decal" type="Decal" parent="PreviousPageButton"] +transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0.02, 0) +size = Vector3(0.04, 0.04, 0.04) +texture_albedo = ExtResource("4_7frvc") + +[node name="Clickable" type="Node" parent="PreviousPageButton"] +script = ExtResource("4_f385t") + +[node name="PageNumberLabel" type="Label3D" parent="."] +transform = Transform3D(0.1, 0, 0, 0, -4.37114e-09, 0.1, 0, -0.1, -4.37114e-09, 0.26, 0.01, 0.27) +text = "1 / 3" +font_size = 36