diff --git a/README.md b/README.md index 6483e4c..4f846ba 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ Thus I've decided to use a custom event system that is similar to the one used i | Group | Description | | -- | -- | | `entity` | Marks the object as being an entity placed in space | -| `ui_focus` | The element can be focused | +| `ui_focus` | The element can be focused, can be a parent | | `ui_focus_skip` | The focus will not be reset. Useful for keyboard | diff --git a/content/entities/media_player/media_player.tscn b/content/entities/media_player/media_player.tscn index c587e08..dda69ea 100644 --- a/content/entities/media_player/media_player.tscn +++ b/content/entities/media_player/media_player.tscn @@ -16,16 +16,19 @@ shape = SubResource("BoxShape3D_vi3eg") [node name="Previous" parent="." instance=ExtResource("1_8opk3")] transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, -0.07, 0, 0) +focusable = true label = "skip_previous" icon = true [node name="Play" parent="." instance=ExtResource("1_8opk3")] transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, -4.65661e-08, 0, 0) +focusable = true label = "pause" icon = true [node name="Next" parent="." instance=ExtResource("1_8opk3")] transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0.07, 0, 0) +focusable = true label = "skip_next" icon = true diff --git a/content/main.tscn b/content/main.tscn index 1b78e82..581f3a4 100644 --- a/content/main.tscn +++ b/content/main.tscn @@ -52,6 +52,7 @@ mesh = SubResource("BoxMesh_ir3co") [node name="Raycast" parent="XROrigin3D/XRControllerRight" instance=ExtResource("3_67lii")] [node name="Hands" parent="XROrigin3D" node_paths=PackedStringArray("ray_left", "ray_right") instance=ExtResource("4_v8xu6")] +transform = Transform3D(0.999968, -1.39576e-11, 0, 9.52038e-12, 0.999984, -2.59206e-11, -2.91038e-11, 5.22959e-11, 0.999984, 0, 0, 0) ray_left = NodePath("../XRControllerLeft/Raycast") ray_right = NodePath("../XRControllerRight/Raycast") diff --git a/content/system/hands/hands.gd b/content/system/hands/hands.gd index 51b1a08..14286fd 100644 --- a/content/system/hands/hands.gd +++ b/content/system/hands/hands.gd @@ -2,15 +2,19 @@ extends Node3D const Pointer = preload("res://lib/utils/pointer/pointer.gd") const Initiator = preload("res://lib/utils/pointer/initiator.gd") +const Finger = preload("res://lib/utils/touch/finger.gd") +const Touch = preload("res://lib/utils/touch/touch.gd") @onready var hand_right: OpenXRHand = $XRHandRight @onready var hand_left: OpenXRHand = $XRHandLeft @export var ray_left: RayCast3D @export var ray_right: RayCast3D var initiator: Initiator = Initiator.new() +var touch: Touch var pointer: Pointer var press_distance = 0.03 -var grip_distance = 0.05 +var grip_distance = 0.03 +var close_distance = 0.1 var pressed_left = false var pressed_right = false @@ -18,6 +22,11 @@ var grabbed_left = false var grabbed_right = false func _ready(): + touch = Touch.new({ + Finger.Type.INDEX_RIGHT: $XRHandRight/IndexTip/IndexArea + }) + add_child(touch) + _ready_hand(hand_right) func _ready_hand(hand: OpenXRHand): @@ -40,31 +49,55 @@ func _process_hand(hand: OpenXRHand): var distance_trigger = index_tip.global_position.distance_to(thumb_tip.global_position) var distance_grab = middle_tip.global_position.distance_to(thumb_tip.global_position) + var distance_target = _ray.get_collision_point().distance_to(_ray.global_position) + + var trigger_close = distance_trigger <= press_distance + var grab_close = distance_grab <= grip_distance + var distance_close = distance_target <= close_distance + if hand == hand_left: - if distance_trigger <= press_distance && !pressed_left: - initiator.on_press.emit(Initiator.EventType.TRIGGER) - pressed_left = true - elif distance_trigger > press_distance && pressed_left: - initiator.on_release.emit(Initiator.EventType.TRIGGER) - pressed_left = false - - if distance_grab <= grip_distance && !grabbed_left: - initiator.on_press.emit(Initiator.EventType.GRIP) - grabbed_left = true - elif distance_grab > grip_distance && grabbed_left: - initiator.on_release.emit(Initiator.EventType.GRIP) - grabbed_left = false + + if !distance_close: + if trigger_close && !pressed_left: + initiator.on_press.emit(Initiator.EventType.TRIGGER) + pressed_left = true + elif !trigger_close && pressed_left: + initiator.on_release.emit(Initiator.EventType.TRIGGER) + pressed_left = false + + if grab_close && !grabbed_left: + initiator.on_press.emit(Initiator.EventType.GRIP) + grabbed_left = true + elif !grab_close && grabbed_left: + initiator.on_release.emit(Initiator.EventType.GRIP) + grabbed_left = false + else: + if trigger_close && !grabbed_right: + initiator.on_press.emit(Initiator.EventType.GRIP) + grabbed_right = true + elif !trigger_close && grabbed_right: + initiator.on_release.emit(Initiator.EventType.GRIP) + grabbed_right = false else: - if distance_trigger <= press_distance && !pressed_right: - initiator.on_press.emit(Initiator.EventType.TRIGGER) - pressed_right = true - elif distance_trigger > press_distance && pressed_right: - initiator.on_release.emit(Initiator.EventType.TRIGGER) - pressed_right = false + if !distance_close: + if trigger_close && !pressed_right: + initiator.on_press.emit(Initiator.EventType.TRIGGER) + pressed_right = true + elif !trigger_close && pressed_right: + initiator.on_release.emit(Initiator.EventType.TRIGGER) + pressed_right = false - if distance_grab <= grip_distance && !grabbed_right: - initiator.on_press.emit(Initiator.EventType.GRIP) - grabbed_right = true - elif distance_grab > grip_distance && grabbed_right: - initiator.on_release.emit(Initiator.EventType.GRIP) - grabbed_right = false + if grab_close && !grabbed_right: + initiator.on_press.emit(Initiator.EventType.GRIP) + grabbed_right = true + elif !grab_close && grabbed_right: + initiator.on_release.emit(Initiator.EventType.GRIP) + grabbed_right = false + + else: + if trigger_close && !grabbed_right: + initiator.on_press.emit(Initiator.EventType.GRIP) + grabbed_right = true + elif !trigger_close && grabbed_right: + initiator.on_release.emit(Initiator.EventType.GRIP) + grabbed_right = false \ No newline at end of file diff --git a/content/system/hands/hands.tscn b/content/system/hands/hands.tscn index d5f680f..7f37601 100644 --- a/content/system/hands/hands.tscn +++ b/content/system/hands/hands.tscn @@ -1,9 +1,16 @@ -[gd_scene load_steps=4 format=3 uid="uid://bsx12q23v8apy"] +[gd_scene load_steps=6 format=3 uid="uid://bsx12q23v8apy"] [ext_resource type="Script" path="res://content/system/hands/hands.gd" id="1_c4f76"] [ext_resource type="PackedScene" uid="uid://c0kow4g10wolq" path="res://assets/models/hands_steam/right_hand.glb" id="1_uekbj"] [ext_resource type="PackedScene" uid="uid://dt4ksvogfctkr" path="res://assets/models/hands_steam/left_hand.glb" id="2_n73lt"] +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_dopke"] +radius = 0.001 +height = 0.02 + +[sub_resource type="BoxShape3D" id="BoxShape3D_wty44"] +size = Vector3(0.0274034, 0.194213, 0.133443) + [node name="Hands" type="Node3D"] script = ExtResource("1_c4f76") @@ -62,6 +69,16 @@ external_skeleton = NodePath("../right_hand/Armature/Skeleton3D") [node name="Marker3D" type="Marker3D" parent="XRHandRight/IndexTip"] gizmo_extents = 0.02 +[node name="IndexArea" type="Area3D" parent="XRHandRight/IndexTip"] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0.01) +collision_layer = 2 +collision_mask = 2 +monitorable = false + +[node name="CollisionShape3D" type="CollisionShape3D" parent="XRHandRight/IndexTip/IndexArea"] +transform = Transform3D(1, -7.45058e-09, -2.22045e-16, 7.45058e-09, 1, 0, 0, 0, 1, 0, 0, 0) +shape = SubResource("CapsuleShape3D_dopke") + [node name="ThumbTip" type="BoneAttachment3D" parent="XRHandRight"] transform = Transform3D(0.937246, -0.0284254, 0.347508, -0.348179, -0.129158, 0.928488, 0.0184906, -0.991216, -0.130949, -0.0498677, -0.112777, -0.0230909) bone_name = "Thumb_Tip_R" @@ -82,5 +99,14 @@ external_skeleton = NodePath("../right_hand/Armature/Skeleton3D") [node name="Marker3D" type="Marker3D" parent="XRHandRight/MiddleTip"] gizmo_extents = 0.02 +[node name="AnimatableBody3D" type="AnimatableBody3D" parent="XRHandRight"] +transform = Transform3D(1, 0, 4.7579e-13, 0, 1, 0, -3.16048e-12, 1.77636e-15, 1, 5.36442e-07, -9.16771e-10, 0.0208378) +collision_layer = 4 +collision_mask = 4 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="XRHandRight/AnimatableBody3D"] +transform = Transform3D(1, 0, 4.7579e-13, -1.16415e-10, 1, 0, -1.04364e-11, 1.77636e-15, 1, -0.0166854, -0.0757538, 4.16786e-07) +shape = SubResource("BoxShape3D_wty44") + [editable path="XRHandLeft/left_hand"] [editable path="XRHandRight/right_hand"] diff --git a/content/system/keyboard/keyboard.gd b/content/system/keyboard/keyboard.gd index 751f108..36c2a3f 100644 --- a/content/system/keyboard/keyboard.gd +++ b/content/system/keyboard/keyboard.gd @@ -71,8 +71,8 @@ func create_key(key: Key): var key_node = button_scene.instantiate() key_node.label = EventKey.key_to_string(key, caps) - key_node.add_to_group("ui_focus_skip") - key_node.get_node("Label").font_size = 32 + key_node.focusable = false + key_node.font_size = 32 key_node.set_meta("key", key) return key_node diff --git a/content/system/keyboard/keyboard.tscn b/content/system/keyboard/keyboard.tscn index 9450a96..b6f6c71 100644 --- a/content/system/keyboard/keyboard.tscn +++ b/content/system/keyboard/keyboard.tscn @@ -15,20 +15,23 @@ size = Vector3(0.79, 0.01, 0.26) transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0) script = ExtResource("1_maojw") -[node name="Backspace" parent="." groups=["ui_focus_skip"] instance=ExtResource("1_xdpwr")] +[node name="Backspace" parent="." instance=ExtResource("1_xdpwr")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.66, 0, 0.02) +focusable = false label = "backspace" icon = true metadata/key = 4194308 -[node name="Caps" parent="." groups=["ui_focus_skip"] instance=ExtResource("1_xdpwr")] +[node name="Caps" parent="." instance=ExtResource("1_xdpwr")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.06, 0, 0.15) +focusable = false label = "keyboard_capslock" icon = true toggleable = true -[node name="Paste" parent="." groups=["ui_focus_skip"] instance=ExtResource("1_xdpwr")] +[node name="Paste" parent="." instance=ExtResource("1_xdpwr")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.66, 0, 0.18) +focusable = false label = "assignment" icon = true diff --git a/content/ui/components/button/button.gd b/content/ui/components/button/button.gd index e6b9adb..f350859 100644 --- a/content/ui/components/button/button.gd +++ b/content/ui/components/button/button.gd @@ -1,15 +1,32 @@ @tool -extends StaticBody3D +extends Node3D class_name Button3D +const Proxy = preload("res://lib/utils/proxy.gd") + signal on_button_down() signal on_button_up() const IconFont = preload("res://assets/icons/icons.tres") -@onready var label_node: Label3D = $Label +@onready var label_node: Label3D = $Body/Label +@onready var finger_area: Area3D = $FingerArea +@export var focusable: bool = true: + set(value): + focusable = value + if !is_node_ready(): await ready + if value: + $Body.add_to_group("ui_focus_skip") + else: + $Body.remove_from_group("ui_focus_skip") + +@export var font_size: int = 10: + set(value): + font_size = value + if !is_node_ready(): await ready + label_node.font_size = value @export var label: String = "": set(value): label = value @@ -25,30 +42,44 @@ const IconFont = preload("res://assets/icons/icons.tres") label_node.font_size = 48 label_node.width = 1000 label_node.autowrap_mode = TextServer.AUTOWRAP_OFF + else: + label_node.font = null + label_node.font_size = font_size + label_node.width = 50 + label_node.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - @export var toggleable: bool = false @export var disabled: bool = false -@export var external_state: bool = false @export var initial_active: bool = false -var active: bool = false : +var external_value: Proxy = null +var active: bool = false: + get: + if external_value != null: + return external_value.value + return active set(value): - animation_player.stop() - if value == active: - return - - active = value - - if active: - animation_player.play("down") + if external_value != null: + external_value.value = value else: - animation_player.play_backwards("down") + active = value + update_animation() @onready var animation_player: AnimationPlayer = $AnimationPlayer func _ready(): if initial_active: active = true + +func update_animation(): + print(1) + var length = animation_player.get_animation("down").length + + if active && animation_player.current_animation_position != length: + print(2) + animation_player.play("down") + elif !active && animation_player.current_animation_position != 0: + print(3) + animation_player.play_backwards("down") func _on_press_down(event): if disabled: @@ -57,22 +88,17 @@ func _on_press_down(event): AudioPlayer.play_effect("click") - if external_state || toggleable: + if toggleable: return active = true on_button_down.emit() - - func _on_press_up(event): if disabled: event.bubbling = false return - if external_state: - return - if toggleable: active = !active @@ -83,3 +109,53 @@ func _on_press_up(event): else: active = false on_button_up.emit() + +func _on_touch_enter(event: EventTouch): + animation_player.stop() + animation_player.speed_scale = 0 + animation_player.current_animation = "down" + _touch_change(event) + +func _on_touch_move(event: EventTouch): + _touch_change(event) + +func _on_touch_leave(_event: EventTouch): + animation_player.stop() + animation_player.speed_scale = 1 + + if toggleable: + active = !active + if active: + on_button_up.emit() + else: + on_button_down.emit() + + +func _touch_change(event: EventTouch): + if disabled: + event.bubbling = false + return + + var pos = Vector3(0, 1, 0) + for finger in event.fingers: + var finger_pos = to_local(finger.area.global_position) + if pos.y > finger_pos.y: + pos = finger_pos + + var button_height = finger_area.get_node("CollisionShape3D").shape.size.y + var button_center = finger_area.position.y + + var percent = clamp((button_center + button_height / 2 - pos.y) / (button_height / 2), 0, 1) + + if !active && percent < 1: + on_button_down.emit() + elif active && percent >= 1: + on_button_up.emit() + + animation_player.seek(percent * animation_player.current_animation_length, true) + + if toggleable: + return + + active = percent < 1 + diff --git a/content/ui/components/button/button.tscn b/content/ui/components/button/button.tscn index edf5649..a26404d 100644 --- a/content/ui/components/button/button.tscn +++ b/content/ui/components/button/button.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=8 format=3 uid="uid://bsjqdvkt0u87c"] +[gd_scene load_steps=9 format=3 uid="uid://bsjqdvkt0u87c"] [ext_resource type="Script" path="res://content/ui/components/button/button.gd" id="1_74x7g"] [ext_resource type="ArrayMesh" uid="uid://iv4lk77axlk4" path="res://assets/immersive_home/button.obj" id="2_cve3l"] @@ -15,60 +15,50 @@ length = 0.001 tracks/0/type = "bezier" tracks/0/imported = false tracks/0/enabled = true -tracks/0/path = NodePath(".:position:y") +tracks/0/path = NodePath("Body:scale:y") tracks/0/interp = 1 tracks/0/loop_wrap = true tracks/0/keys = { "handle_modes": PackedInt32Array(0), -"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0), +"points": PackedFloat32Array(1, -0.25, 0, 0.25, 0), "times": PackedFloat32Array(0) } tracks/1/type = "bezier" tracks/1/imported = false tracks/1/enabled = true -tracks/1/path = NodePath("MeshInstance3D:position:y") +tracks/1/path = NodePath("Body:position:y") tracks/1/interp = 1 tracks/1/loop_wrap = true tracks/1/keys = { "handle_modes": PackedInt32Array(0), -"points": PackedFloat32Array(0, -0.25, 0, 0.25, 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.2 +step = 0.01 tracks/0/type = "bezier" tracks/0/imported = false tracks/0/enabled = true -tracks/0/path = NodePath(".:position:y") +tracks/0/path = NodePath("Body:scale: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), +"points": PackedFloat32Array(1, -0.25, 0, 0.12, -0.00966179, 0.500747, -0.13, 0.00874269, 0.25, 0), "times": PackedFloat32Array(0, 0.2) } tracks/1/type = "bezier" tracks/1/imported = false tracks/1/enabled = true -tracks/1/path = NodePath("MeshInstance3D:mesh:size:y") +tracks/1/path = NodePath("Body:position:y") tracks/1/interp = 1 tracks/1/loop_wrap = true tracks/1/keys = { "handle_modes": PackedInt32Array(0, 0), -"points": PackedFloat32Array(0.02, -0.25, 0, 0.25, 0, 0.01, -0.25, 0, 0.25, 0), -"times": PackedFloat32Array(0, 0.2) -} -tracks/2/type = "bezier" -tracks/2/imported = false -tracks/2/enabled = true -tracks/2/path = NodePath("MeshInstance3D:position:y") -tracks/2/interp = 1 -tracks/2/loop_wrap = true -tracks/2/keys = { -"handle_modes": PackedInt32Array(0, 0), -"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0.005, -0.25, 0, 0.25, 0), +"points": PackedFloat32Array(0.0101447, -0.25, 0, 0.12, 0.000134136, 0.0051993, -0.12, 0.000145131, 0.25, 0), "times": PackedFloat32Array(0, 0.2) } @@ -78,28 +68,45 @@ _data = { "down": SubResource("Animation_iu2ed") } -[node name="Button" type="StaticBody3D" groups=["ui_focus"]] +[sub_resource type="BoxShape3D" id="BoxShape3D_bqjii"] +size = Vector3(0.0501598, 0.0195937, 0.0501598) + +[node name="Button" type="Node3D" groups=["ui_focus"]] script = ExtResource("1_74x7g") +focusable = null label = "Example Text" -[node name="MeshInstance3D" type="MeshInstance3D" parent="."] -transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0) +[node name="Body" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.01, 0) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="Body"] +transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, -0.005, 0) mesh = ExtResource("2_cve3l") +skeleton = NodePath("../..") surface_material_override/0 = SubResource("StandardMaterial3D_8s8ln") -[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +[node name="CollisionShape3D" type="CollisionShape3D" parent="Body"] shape = SubResource("ConvexPolygonShape3D_o4j7g") -[node name="AnimationPlayer" type="AnimationPlayer" parent="."] -libraries = { -"": SubResource("AnimationLibrary_sbgno") -} - -[node name="Label" type="Label3D" parent="."] -transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.011, 0) +[node name="Label" type="Label3D" parent="Body"] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.005, 0) pixel_size = 0.001 text = "Example Text" font_size = 10 outline_size = 0 autowrap_mode = 3 width = 50.0 + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +libraries = { +"": SubResource("AnimationLibrary_sbgno") +} + +[node name="FingerArea" type="Area3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0101447, 0) +collision_layer = 2 +collision_mask = 2 +monitoring = false + +[node name="CollisionShape3D" type="CollisionShape3D" parent="FingerArea"] +shape = SubResource("BoxShape3D_bqjii") diff --git a/content/ui/components/input/input.gd b/content/ui/components/input/input.gd index 1265a2f..d324872 100644 --- a/content/ui/components/input/input.gd +++ b/content/ui/components/input/input.gd @@ -68,6 +68,10 @@ func _process(_delta): if get_tree().debug_collisions_hint && OS.get_name() != "Android": _draw_debug_text_gaps() +func _on_press_down(event): + var pos_x = label.to_local(event.ray.get_collision_point()).x + text_handler.update_caret_position(pos_x) + func _on_press_move(event): var ray_pos = event.ray.global_position var ray_dir = -event.ray.global_transform.basis.z @@ -86,10 +90,7 @@ func _on_press_move(event): caret.position.x = text_handler.get_caret_position() label.text = text_handler.get_display_text() -func _on_focus_in(event): - var pos_x = label.to_local(event.ray.get_collision_point()).x - text_handler.update_caret_position(pos_x) - +func _on_focus_in(_event): caret.position.x = text_handler.get_caret_position() label.text = text_handler.get_display_text() caret.show() diff --git a/content/ui/components/input/text_handler.gd b/content/ui/components/input/text_handler.gd index 2dffa73..149f7f2 100644 --- a/content/ui/components/input/text_handler.gd +++ b/content/ui/components/input/text_handler.gd @@ -1,4 +1,4 @@ -extends Object +extends RefCounted var label: Label3D diff --git a/content/ui/menu/edit/device/device.tscn b/content/ui/menu/edit/device/device.tscn index 12fdd5c..9c0237a 100644 --- a/content/ui/menu/edit/device/device.tscn +++ b/content/ui/menu/edit/device/device.tscn @@ -8,6 +8,7 @@ script = ExtResource("1_rbo86") [node name="Button" parent="." instance=ExtResource("2_go2es")] +focusable = true [node name="Clickable" type="Node" parent="."] script = ExtResource("3_6wicx") diff --git a/content/ui/menu/edit/edit_menu.gd b/content/ui/menu/edit/edit_menu.gd index 4e7def5..404417b 100644 --- a/content/ui/menu/edit/edit_menu.gd +++ b/content/ui/menu/edit/edit_menu.gd @@ -1,7 +1,6 @@ extends Node3D -const Device = preload("./device/device.tscn") -const Entity = preload("./entity/entity.tscn") +const ButtonScene = preload("res://content/ui/components/button/button.tscn") const EntityCreator = preload("./entity_creator.gd") @onready var devices_node: GridContainer3D = $Devices @@ -101,33 +100,33 @@ func render_devices(): for device in page_devices: var info = device.values()[0] - var device_instance = Device.instantiate() - device_instance.id = device.keys()[0] - device_instance.get_node("Clickable").on_click.connect(func(_event): - _on_device_click(device_instance.id) + var button_instance = ButtonScene.instantiate() + button_instance.label = info["name"] + button_instance.on_button_down.connect(func(): + _on_device_click(device.keys()[0]) ) - devices_node.add_child(device_instance) - device_instance.set_device_name.call_deferred(info["name"]) + devices_node.add_child(button_instance) devices_node._update_container() func render_entities(): var entities = get_page() - var back_button = Entity.instantiate() - back_button.get_node("Clickable").on_click.connect(func(_event): + var back_button = ButtonScene.instantiate() + back_button.label = "arrow_back" + back_button.icon = true + back_button.on_button_down.connect(func(): _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.get_node("Clickable").on_click.connect(func(_event): + var button_instance = ButtonScene.instantiate() + button_instance.label = entity + button_instance.on_button_down.connect(func(): _on_entity_click(entity) ) - devices_node.add_child(entity_instance) - entity_instance.set_entity_name(entity) + devices_node.add_child(button_instance) devices_node._update_container() diff --git a/content/ui/menu/edit/edit_menu.tscn b/content/ui/menu/edit/edit_menu.tscn index b9ca02f..db65995 100644 --- a/content/ui/menu/edit/edit_menu.tscn +++ b/content/ui/menu/edit/edit_menu.tscn @@ -1,9 +1,8 @@ -[gd_scene load_steps=5 format=3 uid="uid://crrb0l3ekuotj"] +[gd_scene load_steps=4 format=3 uid="uid://crrb0l3ekuotj"] [ext_resource type="Script" path="res://content/ui/menu/edit/edit_menu.gd" id="1_34cbn"] [ext_resource type="Script" path="res://content/ui/menu/grid.gd" id="3_0xvyw"] [ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="4_tvimg"] -[ext_resource type="Script" path="res://content/functions/clickable.gd" id="6_pf8jy"] [node name="EditMenu" type="Node3D"] script = ExtResource("1_34cbn") @@ -26,16 +25,12 @@ outline_size = 0 transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.19, 0.01, 0.27) [node name="NextPageButton" parent="Buttons" instance=ExtResource("4_tvimg")] +focusable = true label = "navigate_next" icon = true -[node name="Clickable" type="Node" parent="Buttons/NextPageButton"] -script = ExtResource("6_pf8jy") - [node name="PreviousPageButton" parent="Buttons" instance=ExtResource("4_tvimg")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.08, 0, 0) +focusable = true label = "navigate_before" icon = true - -[node name="Clickable" type="Node" parent="Buttons/PreviousPageButton"] -script = ExtResource("6_pf8jy") diff --git a/content/ui/menu/edit/entity_creator.gd b/content/ui/menu/edit/entity_creator.gd index 61093a6..9d4e5f1 100644 --- a/content/ui/menu/edit/entity_creator.gd +++ b/content/ui/menu/edit/entity_creator.gd @@ -1,4 +1,4 @@ -extends Object +extends RefCounted const Switch = preload("res://content/entities/switch/switch.tscn") const Light = preload("res://content/entities/light/light.tscn") diff --git a/content/ui/menu/menu.gd b/content/ui/menu/menu.gd index 0d4aca1..061bb0d 100644 --- a/content/ui/menu/menu.gd +++ b/content/ui/menu/menu.gd @@ -1,5 +1,7 @@ extends Node3D +const Proxy = preload("res://lib/utils/proxy.gd") + @onready var _controller := XRHelpers.get_xr_controller(self) @onready var nav_view = $AnimationContainer/Navigation/View @@ -34,10 +36,25 @@ func _ready(): show_menu = !show_menu ) - select_menu(nav_edit) + var nav_buttons = [ + nav_view, + nav_edit, + nav_room, + nav_automate, + nav_settings + ] -func _on_click(event): - select_menu(event.target) + for nav_button in nav_buttons: + var getter = func(): + return nav_button == selected_nav + + var setter = func(value): + if value: + select_menu(nav_button) + + nav_button.external_value = Proxy.new(getter, setter) + + select_menu(nav_edit) func select_menu(nav): if _is_valid_nav(nav) == false || selected_nav == nav: @@ -46,13 +63,15 @@ func select_menu(nav): for child in content.get_children(): content.remove_child(child) - if selected_nav != null: - selected_nav.active = false + var old_nav = selected_nav selected_nav = nav + if old_nav != null: + old_nav.update_animation() + if selected_nav != null: - selected_nav.active = true + selected_nav.update_animation() var menu = _nav_to_menu(selected_nav) if menu != null: content.add_child(menu) diff --git a/content/ui/menu/menu.tscn b/content/ui/menu/menu.tscn index 888355e..53c22c8 100644 --- a/content/ui/menu/menu.tscn +++ b/content/ui/menu/menu.tscn @@ -165,38 +165,38 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06, 0, 0) [node name="View" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.03) +focusable = true label = "visibility" icon = true toggleable = true -external_state = true [node name="Edit" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.09) +focusable = true label = "widgets" icon = true toggleable = true -external_state = true [node name="Room" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.15) +focusable = true label = "view_in_ar" icon = true toggleable = true -external_state = true [node name="Automate" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.21) +focusable = true label = "schema" icon = true toggleable = true -external_state = true [node name="Settings" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.27) +focusable = true label = "settings" icon = true toggleable = true -external_state = true [node name="Content" type="Node3D" parent="AnimationContainer"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06, 0, 0) diff --git a/content/ui/menu/room/room_menu.tscn b/content/ui/menu/room/room_menu.tscn index b706edd..7f956bc 100644 --- a/content/ui/menu/room/room_menu.tscn +++ b/content/ui/menu/room/room_menu.tscn @@ -18,6 +18,8 @@ script = ExtResource("1_ch4jb") [node name="TeleportRoot" type="Node3D" parent="."] [node name="Ground" type="StaticBody3D" parent="TeleportRoot"] +collision_layer = 5 +collision_mask = 5 [node name="CollisionShape3D" type="CollisionShape3D" parent="TeleportRoot/Ground"] shape = SubResource("WorldBoundaryShape3D_08sv0") @@ -44,6 +46,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.01, 0) [node name="ToggleEdit" parent="Interface" instance=ExtResource("3_whl7a")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.05, 0, 0.05) +focusable = true label = "room_preferences" icon = true toggleable = true diff --git a/content/ui/menu/settings/ball.tscn b/content/ui/menu/settings/ball.tscn index f549a12..fc99630 100644 --- a/content/ui/menu/settings/ball.tscn +++ b/content/ui/menu/settings/ball.tscn @@ -9,6 +9,8 @@ bounce = 0.7 radius = 0.08 [node name="Ball" type="RigidBody3D"] +collision_layer = 4 +collision_mask = 4 physics_material_override = SubResource("PhysicsMaterial_f6jtg") angular_damp = 4.0 diff --git a/content/ui/menu/settings/settings_menu.gd b/content/ui/menu/settings/settings_menu.gd index 8642a89..f454b0e 100644 --- a/content/ui/menu/settings/settings_menu.gd +++ b/content/ui/menu/settings/settings_menu.gd @@ -3,7 +3,7 @@ extends Node3D const ball_scene = preload("./ball.tscn") const credits_scene = preload("./credits.tscn") -@onready var clickable = $Content/Button/Clickable +@onready var ball_button = $Content/Button @onready var connection_status = $Content/ConnectionStatus @onready var input_url = $Content/InputURL @@ -15,10 +15,12 @@ const credits_scene = preload("./credits.tscn") func _ready(): background.visible = false - clickable.on_click.connect(func(event): + ball_button.on_button_down.connect(func(): var ball = ball_scene.instantiate() - ball.transform = event.controller.transform - ball.linear_velocity = -event.controller.transform.basis.z * 5 + Vector3(0, 5, 0) + var controller = XRHelpers.get_right_controller(self) + + ball.transform = controller.transform + ball.linear_velocity = -controller.transform.basis.z * 5 + Vector3(0, 5, 0) get_tree().root.add_child(ball) ) diff --git a/content/ui/menu/settings/settings_menu.tscn b/content/ui/menu/settings/settings_menu.tscn index e279d0a..61f53eb 100644 --- a/content/ui/menu/settings/settings_menu.tscn +++ b/content/ui/menu/settings/settings_menu.tscn @@ -32,6 +32,7 @@ outline_size = 0 [node name="Button" parent="Content" instance=ExtResource("1_faxng")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0458097, 0, 0.253575) +focusable = true label = "sports_basketball" icon = true @@ -84,6 +85,7 @@ horizontal_alignment = 0 [node name="Connect" parent="Content" instance=ExtResource("1_faxng")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.25, 0, 0.12) +focusable = true label = "wifi" icon = true diff --git a/lib/events/event_focus.gd b/lib/events/event_focus.gd index 7c88f49..24b0449 100644 --- a/lib/events/event_focus.gd +++ b/lib/events/event_focus.gd @@ -1,6 +1,5 @@ extends Event class_name EventFocus -var ray: RayCast3D var target: Node var previous_target: Node \ No newline at end of file diff --git a/lib/events/event_touch.gd b/lib/events/event_touch.gd new file mode 100644 index 0000000..3c38616 --- /dev/null +++ b/lib/events/event_touch.gd @@ -0,0 +1,12 @@ +extends EventBubble +class_name EventTouch + +const Finger = preload("res://lib/utils/touch/finger.gd") + +var fingers: Array[Finger] = [] + +func has_finger(finger: Finger.Type): + for f in fingers: + if f.type == finger: + return true + return false \ No newline at end of file diff --git a/lib/globals/event_system.gd b/lib/globals/event_system.gd index c24f81e..7681812 100644 --- a/lib/globals/event_system.gd +++ b/lib/globals/event_system.gd @@ -20,37 +20,40 @@ signal on_key_up(event: EventKey) signal on_focus_in(event: EventFocus) signal on_focus_out(event: EventFocus) +signal on_touch_enter(event: EventTouch) +signal on_touch_move(event: EventTouch) +signal on_touch_leave(event: EventTouch) + var _active_node: Node = null func emit(type: String, event: Event): if event is EventBubble: _bubble_call(type, event.target, event) - if type == "press_down": - _handle_focus(event) else: _root_call(type, event) func is_focused(node: Node): return _active_node == node -func _handle_focus(event: EventPointer): - if event.target != null && event.target.is_in_group("ui_focus_skip"): +func _handle_focus(event: EventBubble): + var target = event.target + + if target != null && target.is_in_group("ui_focus_skip"): return var event_focus = EventFocus.new() event_focus.previous_target = _active_node - event_focus.target = event.target - event_focus.ray = event.ray + event_focus.target = target if _active_node != null && _active_node.has_method(FN_PREFIX + "focus_out"): _active_node.call(FN_PREFIX + "focus_out", event_focus) on_focus_out.emit(event_focus) - if event.target == null || event.target.is_in_group("ui_focus") == false: + if target == null || target.is_in_group("ui_focus") == false: _active_node = null return - _active_node = event.target + _active_node = target if _active_node != null && _active_node.has_method(FN_PREFIX + "focus_in"): _active_node.call(FN_PREFIX + "focus_in", event_focus) @@ -70,6 +73,9 @@ func _bubble_call(type: String, target: Variant, event: EventBubble): if event.bubbling == false: return false + if type == "press_down" || type == "touch_enter": + _handle_focus(event) + for child in target.get_children(): if child is Function && child.has_method(FN_PREFIX + type): child.call(FN_PREFIX + type, event) diff --git a/lib/utils/pointer/initiator.gd b/lib/utils/pointer/initiator.gd index d81112a..d76af07 100644 --- a/lib/utils/pointer/initiator.gd +++ b/lib/utils/pointer/initiator.gd @@ -1,4 +1,4 @@ -extends Object +extends RefCounted enum Type { CONTROLLER_LEFT, diff --git a/lib/utils/proxy.gd b/lib/utils/proxy.gd new file mode 100644 index 0000000..376db93 --- /dev/null +++ b/lib/utils/proxy.gd @@ -0,0 +1,14 @@ +extends RefCounted + +var gettable: Callable +var settable: Callable + +func _init(gettable: Callable, settable: Callable): + self.gettable = gettable + self.settable = settable + +var value: Variant: + get: + return gettable.call() + set(new_value): + settable.call(new_value) \ No newline at end of file diff --git a/lib/utils/touch/finger.gd b/lib/utils/touch/finger.gd new file mode 100644 index 0000000..4b29b84 --- /dev/null +++ b/lib/utils/touch/finger.gd @@ -0,0 +1,17 @@ +extends RefCounted + +enum Type { + THUMB_RIGHT, + INDEX_RIGHT, + MIDDLE_RIGHT, + RING_RIGHT, + LITTLE_RIGHT, + THUMB_LEFT, + INDEX_LEFT, + MIDDLE_LEFT, + RING_LEFT, + LITTLE_LEFT, +} + +var type: Type +var area: Area3D \ No newline at end of file diff --git a/lib/utils/touch/touch.gd b/lib/utils/touch/touch.gd new file mode 100644 index 0000000..3fc2642 --- /dev/null +++ b/lib/utils/touch/touch.gd @@ -0,0 +1,57 @@ +extends Node + +const Finger = preload("res://lib/utils/touch/finger.gd") + +## Record +var finger_areas: Dictionary + +var areas_entered = {} + +func _init(finger_areas: Dictionary): + self.finger_areas = finger_areas + +func _ready(): + for finger_type in finger_areas.keys(): + finger_areas[finger_type].area_entered.connect(func(area): + _on_area_entered(finger_type, area) + ) + + finger_areas[finger_type].area_exited.connect(func(area): + _on_area_exited(finger_type, area) + ) + +func _physics_process(_delta): + for area in areas_entered.keys(): + _emit_event("touch_move", area) + +func _on_area_entered(finger_type, area): + if areas_entered.has(area): + if !areas_entered[area].has(finger_type): + areas_entered[area].append(finger_type) + _emit_event("touch_enter", area) + else: + areas_entered[area] = [finger_type] + _emit_event("touch_enter", area) + +func _on_area_exited(finger_type, area): + if areas_entered.has(area): + if areas_entered[area].has(finger_type): + areas_entered[area].erase(finger_type) + + if areas_entered[area].size() == 0: + _emit_event("touch_leave", area) + areas_entered.erase(area) + +func _emit_event(type: String, target): + var event = EventTouch.new() + event.target = target + var fingers: Array[Finger] = [] + for finger_type in areas_entered[target]: + var finger = Finger.new() + finger.type = finger_type + finger.area = finger_areas[finger_type] + fingers.append(finger) + + event.fingers = fingers + + EventSystem.emit(type, event)