implement touch system

This commit is contained in:
Nitwel 2023-11-27 23:46:05 +01:00
parent fdbd3f9d96
commit 9a4b493d72
28 changed files with 416 additions and 138 deletions

View File

@ -140,7 +140,7 @@ Thus I've decided to use a custom event system that is similar to the one used i
| Group | Description | | Group | Description |
| -- | -- | | -- | -- |
| `entity` | Marks the object as being an entity placed in space | | `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 | | `ui_focus_skip` | The focus will not be reset. Useful for keyboard |

View File

@ -16,16 +16,19 @@ shape = SubResource("BoxShape3D_vi3eg")
[node name="Previous" parent="." instance=ExtResource("1_8opk3")] [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) transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, -0.07, 0, 0)
focusable = true
label = "skip_previous" label = "skip_previous"
icon = true icon = true
[node name="Play" parent="." instance=ExtResource("1_8opk3")] [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) transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, -4.65661e-08, 0, 0)
focusable = true
label = "pause" label = "pause"
icon = true icon = true
[node name="Next" parent="." instance=ExtResource("1_8opk3")] [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) transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0.07, 0, 0)
focusable = true
label = "skip_next" label = "skip_next"
icon = true icon = true

View File

@ -52,6 +52,7 @@ mesh = SubResource("BoxMesh_ir3co")
[node name="Raycast" parent="XROrigin3D/XRControllerRight" instance=ExtResource("3_67lii")] [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")] [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_left = NodePath("../XRControllerLeft/Raycast")
ray_right = NodePath("../XRControllerRight/Raycast") ray_right = NodePath("../XRControllerRight/Raycast")

View File

@ -2,15 +2,19 @@ extends Node3D
const Pointer = preload("res://lib/utils/pointer/pointer.gd") const Pointer = preload("res://lib/utils/pointer/pointer.gd")
const Initiator = preload("res://lib/utils/pointer/initiator.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_right: OpenXRHand = $XRHandRight
@onready var hand_left: OpenXRHand = $XRHandLeft @onready var hand_left: OpenXRHand = $XRHandLeft
@export var ray_left: RayCast3D @export var ray_left: RayCast3D
@export var ray_right: RayCast3D @export var ray_right: RayCast3D
var initiator: Initiator = Initiator.new() var initiator: Initiator = Initiator.new()
var touch: Touch
var pointer: Pointer var pointer: Pointer
var press_distance = 0.03 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_left = false
var pressed_right = false var pressed_right = false
@ -18,6 +22,11 @@ var grabbed_left = false
var grabbed_right = false var grabbed_right = false
func _ready(): func _ready():
touch = Touch.new({
Finger.Type.INDEX_RIGHT: $XRHandRight/IndexTip/IndexArea
})
add_child(touch)
_ready_hand(hand_right) _ready_hand(hand_right)
func _ready_hand(hand: OpenXRHand): 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_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_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 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: if !distance_close:
initiator.on_press.emit(Initiator.EventType.GRIP) if trigger_close && !pressed_left:
grabbed_left = true initiator.on_press.emit(Initiator.EventType.TRIGGER)
elif distance_grab > grip_distance && grabbed_left: pressed_left = true
initiator.on_release.emit(Initiator.EventType.GRIP) elif !trigger_close && pressed_left:
grabbed_left = false 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: else:
if distance_trigger <= press_distance && !pressed_right: if !distance_close:
initiator.on_press.emit(Initiator.EventType.TRIGGER) if trigger_close && !pressed_right:
pressed_right = true initiator.on_press.emit(Initiator.EventType.TRIGGER)
elif distance_trigger > press_distance && pressed_right: pressed_right = true
initiator.on_release.emit(Initiator.EventType.TRIGGER) elif !trigger_close && pressed_right:
pressed_right = false initiator.on_release.emit(Initiator.EventType.TRIGGER)
pressed_right = false
if distance_grab <= grip_distance && !grabbed_right: if grab_close && !grabbed_right:
initiator.on_press.emit(Initiator.EventType.GRIP) initiator.on_press.emit(Initiator.EventType.GRIP)
grabbed_right = true grabbed_right = true
elif distance_grab > grip_distance && grabbed_right: elif !grab_close && grabbed_right:
initiator.on_release.emit(Initiator.EventType.GRIP) initiator.on_release.emit(Initiator.EventType.GRIP)
grabbed_right = false 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

View File

@ -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="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://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"] [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"] [node name="Hands" type="Node3D"]
script = ExtResource("1_c4f76") script = ExtResource("1_c4f76")
@ -62,6 +69,16 @@ external_skeleton = NodePath("../right_hand/Armature/Skeleton3D")
[node name="Marker3D" type="Marker3D" parent="XRHandRight/IndexTip"] [node name="Marker3D" type="Marker3D" parent="XRHandRight/IndexTip"]
gizmo_extents = 0.02 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"] [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) 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" bone_name = "Thumb_Tip_R"
@ -82,5 +99,14 @@ external_skeleton = NodePath("../right_hand/Armature/Skeleton3D")
[node name="Marker3D" type="Marker3D" parent="XRHandRight/MiddleTip"] [node name="Marker3D" type="Marker3D" parent="XRHandRight/MiddleTip"]
gizmo_extents = 0.02 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="XRHandLeft/left_hand"]
[editable path="XRHandRight/right_hand"] [editable path="XRHandRight/right_hand"]

View File

@ -71,8 +71,8 @@ func create_key(key: Key):
var key_node = button_scene.instantiate() var key_node = button_scene.instantiate()
key_node.label = EventKey.key_to_string(key, caps) key_node.label = EventKey.key_to_string(key, caps)
key_node.add_to_group("ui_focus_skip") key_node.focusable = false
key_node.get_node("Label").font_size = 32 key_node.font_size = 32
key_node.set_meta("key", key) key_node.set_meta("key", key)
return key_node return key_node

View File

@ -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) transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0)
script = ExtResource("1_maojw") 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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.66, 0, 0.02)
focusable = false
label = "backspace" label = "backspace"
icon = true icon = true
metadata/key = 4194308 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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.06, 0, 0.15)
focusable = false
label = "keyboard_capslock" label = "keyboard_capslock"
icon = true icon = true
toggleable = 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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.66, 0, 0.18)
focusable = false
label = "assignment" label = "assignment"
icon = true icon = true

View File

@ -1,15 +1,32 @@
@tool @tool
extends StaticBody3D extends Node3D
class_name Button3D class_name Button3D
const Proxy = preload("res://lib/utils/proxy.gd")
signal on_button_down() signal on_button_down()
signal on_button_up() signal on_button_up()
const IconFont = preload("res://assets/icons/icons.tres") 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 = "": @export var label: String = "":
set(value): set(value):
label = value label = value
@ -25,24 +42,27 @@ const IconFont = preload("res://assets/icons/icons.tres")
label_node.font_size = 48 label_node.font_size = 48
label_node.width = 1000 label_node.width = 1000
label_node.autowrap_mode = TextServer.AUTOWRAP_OFF 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 toggleable: bool = false
@export var disabled: bool = false @export var disabled: bool = false
@export var external_state: bool = false
@export var initial_active: 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): set(value):
animation_player.stop() if external_value != null:
if value == active: external_value.value = value
return
active = value
if active:
animation_player.play("down")
else: else:
animation_player.play_backwards("down") active = value
update_animation()
@onready var animation_player: AnimationPlayer = $AnimationPlayer @onready var animation_player: AnimationPlayer = $AnimationPlayer
@ -50,6 +70,17 @@ func _ready():
if initial_active: if initial_active:
active = true 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): func _on_press_down(event):
if disabled: if disabled:
event.bubbling = false event.bubbling = false
@ -57,22 +88,17 @@ func _on_press_down(event):
AudioPlayer.play_effect("click") AudioPlayer.play_effect("click")
if external_state || toggleable: if toggleable:
return return
active = true active = true
on_button_down.emit() on_button_down.emit()
func _on_press_up(event): func _on_press_up(event):
if disabled: if disabled:
event.bubbling = false event.bubbling = false
return return
if external_state:
return
if toggleable: if toggleable:
active = !active active = !active
@ -83,3 +109,53 @@ func _on_press_up(event):
else: else:
active = false active = false
on_button_up.emit() 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

View File

@ -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="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"] [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/type = "bezier"
tracks/0/imported = false tracks/0/imported = false
tracks/0/enabled = true tracks/0/enabled = true
tracks/0/path = NodePath(".:position:y") tracks/0/path = NodePath("Body:scale:y")
tracks/0/interp = 1 tracks/0/interp = 1
tracks/0/loop_wrap = true tracks/0/loop_wrap = true
tracks/0/keys = { tracks/0/keys = {
"handle_modes": PackedInt32Array(0), "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) "times": PackedFloat32Array(0)
} }
tracks/1/type = "bezier" tracks/1/type = "bezier"
tracks/1/imported = false tracks/1/imported = false
tracks/1/enabled = true tracks/1/enabled = true
tracks/1/path = NodePath("MeshInstance3D:position:y") tracks/1/path = NodePath("Body:position:y")
tracks/1/interp = 1 tracks/1/interp = 1
tracks/1/loop_wrap = true tracks/1/loop_wrap = true
tracks/1/keys = { tracks/1/keys = {
"handle_modes": PackedInt32Array(0), "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) "times": PackedFloat32Array(0)
} }
[sub_resource type="Animation" id="Animation_iu2ed"] [sub_resource type="Animation" id="Animation_iu2ed"]
resource_name = "down" resource_name = "down"
length = 0.2 length = 0.2
step = 0.01
tracks/0/type = "bezier" tracks/0/type = "bezier"
tracks/0/imported = false tracks/0/imported = false
tracks/0/enabled = true tracks/0/enabled = true
tracks/0/path = NodePath(".:position:y") tracks/0/path = NodePath("Body:scale:y")
tracks/0/interp = 1 tracks/0/interp = 1
tracks/0/loop_wrap = true tracks/0/loop_wrap = true
tracks/0/keys = { tracks/0/keys = {
"handle_modes": PackedInt32Array(0, 0), "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) "times": PackedFloat32Array(0, 0.2)
} }
tracks/1/type = "bezier" tracks/1/type = "bezier"
tracks/1/imported = false tracks/1/imported = false
tracks/1/enabled = true 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/interp = 1
tracks/1/loop_wrap = true tracks/1/loop_wrap = true
tracks/1/keys = { tracks/1/keys = {
"handle_modes": PackedInt32Array(0, 0), "handle_modes": PackedInt32Array(0, 0),
"points": PackedFloat32Array(0.02, -0.25, 0, 0.25, 0, 0.01, -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)
}
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),
"times": PackedFloat32Array(0, 0.2) "times": PackedFloat32Array(0, 0.2)
} }
@ -78,28 +68,45 @@ _data = {
"down": SubResource("Animation_iu2ed") "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") script = ExtResource("1_74x7g")
focusable = null
label = "Example Text" label = "Example Text"
[node name="MeshInstance3D" type="MeshInstance3D" parent="."] [node name="Body" type="StaticBody3D" parent="."]
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0) 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") mesh = ExtResource("2_cve3l")
skeleton = NodePath("../..")
surface_material_override/0 = SubResource("StandardMaterial3D_8s8ln") surface_material_override/0 = SubResource("StandardMaterial3D_8s8ln")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."] [node name="CollisionShape3D" type="CollisionShape3D" parent="Body"]
shape = SubResource("ConvexPolygonShape3D_o4j7g") shape = SubResource("ConvexPolygonShape3D_o4j7g")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."] [node name="Label" type="Label3D" parent="Body"]
libraries = { transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.005, 0)
"": 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)
pixel_size = 0.001 pixel_size = 0.001
text = "Example Text" text = "Example Text"
font_size = 10 font_size = 10
outline_size = 0 outline_size = 0
autowrap_mode = 3 autowrap_mode = 3
width = 50.0 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")

View File

@ -68,6 +68,10 @@ func _process(_delta):
if get_tree().debug_collisions_hint && OS.get_name() != "Android": if get_tree().debug_collisions_hint && OS.get_name() != "Android":
_draw_debug_text_gaps() _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): func _on_press_move(event):
var ray_pos = event.ray.global_position var ray_pos = event.ray.global_position
var ray_dir = -event.ray.global_transform.basis.z 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() caret.position.x = text_handler.get_caret_position()
label.text = text_handler.get_display_text() label.text = text_handler.get_display_text()
func _on_focus_in(event): func _on_focus_in(_event):
var pos_x = label.to_local(event.ray.get_collision_point()).x
text_handler.update_caret_position(pos_x)
caret.position.x = text_handler.get_caret_position() caret.position.x = text_handler.get_caret_position()
label.text = text_handler.get_display_text() label.text = text_handler.get_display_text()
caret.show() caret.show()

View File

@ -1,4 +1,4 @@
extends Object extends RefCounted
var label: Label3D var label: Label3D

View File

@ -8,6 +8,7 @@
script = ExtResource("1_rbo86") script = ExtResource("1_rbo86")
[node name="Button" parent="." instance=ExtResource("2_go2es")] [node name="Button" parent="." instance=ExtResource("2_go2es")]
focusable = true
[node name="Clickable" type="Node" parent="."] [node name="Clickable" type="Node" parent="."]
script = ExtResource("3_6wicx") script = ExtResource("3_6wicx")

View File

@ -1,7 +1,6 @@
extends Node3D extends Node3D
const Device = preload("./device/device.tscn") const ButtonScene = preload("res://content/ui/components/button/button.tscn")
const Entity = preload("./entity/entity.tscn")
const EntityCreator = preload("./entity_creator.gd") const EntityCreator = preload("./entity_creator.gd")
@onready var devices_node: GridContainer3D = $Devices @onready var devices_node: GridContainer3D = $Devices
@ -101,33 +100,33 @@ func render_devices():
for device in page_devices: for device in page_devices:
var info = device.values()[0] var info = device.values()[0]
var device_instance = Device.instantiate() var button_instance = ButtonScene.instantiate()
device_instance.id = device.keys()[0] button_instance.label = info["name"]
device_instance.get_node("Clickable").on_click.connect(func(_event): button_instance.on_button_down.connect(func():
_on_device_click(device_instance.id) _on_device_click(device.keys()[0])
) )
devices_node.add_child(device_instance) devices_node.add_child(button_instance)
device_instance.set_device_name.call_deferred(info["name"])
devices_node._update_container() devices_node._update_container()
func render_entities(): func render_entities():
var entities = get_page() var entities = get_page()
var back_button = Entity.instantiate() var back_button = ButtonScene.instantiate()
back_button.get_node("Clickable").on_click.connect(func(_event): back_button.label = "arrow_back"
back_button.icon = true
back_button.on_button_down.connect(func():
_on_entity_click("#back") _on_entity_click("#back")
) )
devices_node.add_child(back_button) devices_node.add_child(back_button)
back_button.set_entity_name("#back")
for entity in entities: for entity in entities:
var entity_instance = Entity.instantiate() var button_instance = ButtonScene.instantiate()
entity_instance.get_node("Clickable").on_click.connect(func(_event): button_instance.label = entity
button_instance.on_button_down.connect(func():
_on_entity_click(entity) _on_entity_click(entity)
) )
devices_node.add_child(entity_instance) devices_node.add_child(button_instance)
entity_instance.set_entity_name(entity)
devices_node._update_container() devices_node._update_container()

View File

@ -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/edit/edit_menu.gd" id="1_34cbn"]
[ext_resource type="Script" path="res://content/ui/menu/grid.gd" id="3_0xvyw"] [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="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"] [node name="EditMenu" type="Node3D"]
script = ExtResource("1_34cbn") 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) 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")] [node name="NextPageButton" parent="Buttons" instance=ExtResource("4_tvimg")]
focusable = true
label = "navigate_next" label = "navigate_next"
icon = true icon = true
[node name="Clickable" type="Node" parent="Buttons/NextPageButton"]
script = ExtResource("6_pf8jy")
[node name="PreviousPageButton" parent="Buttons" instance=ExtResource("4_tvimg")] [node name="PreviousPageButton" parent="Buttons" instance=ExtResource("4_tvimg")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.08, 0, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.08, 0, 0)
focusable = true
label = "navigate_before" label = "navigate_before"
icon = true icon = true
[node name="Clickable" type="Node" parent="Buttons/PreviousPageButton"]
script = ExtResource("6_pf8jy")

View File

@ -1,4 +1,4 @@
extends Object extends RefCounted
const Switch = preload("res://content/entities/switch/switch.tscn") const Switch = preload("res://content/entities/switch/switch.tscn")
const Light = preload("res://content/entities/light/light.tscn") const Light = preload("res://content/entities/light/light.tscn")

View File

@ -1,5 +1,7 @@
extends Node3D extends Node3D
const Proxy = preload("res://lib/utils/proxy.gd")
@onready var _controller := XRHelpers.get_xr_controller(self) @onready var _controller := XRHelpers.get_xr_controller(self)
@onready var nav_view = $AnimationContainer/Navigation/View @onready var nav_view = $AnimationContainer/Navigation/View
@ -34,10 +36,25 @@ func _ready():
show_menu = !show_menu 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): for nav_button in nav_buttons:
select_menu(event.target) 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): func select_menu(nav):
if _is_valid_nav(nav) == false || selected_nav == nav: if _is_valid_nav(nav) == false || selected_nav == nav:
@ -46,13 +63,15 @@ func select_menu(nav):
for child in content.get_children(): for child in content.get_children():
content.remove_child(child) content.remove_child(child)
if selected_nav != null: var old_nav = selected_nav
selected_nav.active = false
selected_nav = nav selected_nav = nav
if old_nav != null:
old_nav.update_animation()
if selected_nav != null: if selected_nav != null:
selected_nav.active = true selected_nav.update_animation()
var menu = _nav_to_menu(selected_nav) var menu = _nav_to_menu(selected_nav)
if menu != null: if menu != null:
content.add_child(menu) content.add_child(menu)

View File

@ -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")] [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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.03)
focusable = true
label = "visibility" label = "visibility"
icon = true icon = true
toggleable = true toggleable = true
external_state = true
[node name="Edit" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] [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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.09)
focusable = true
label = "widgets" label = "widgets"
icon = true icon = true
toggleable = true toggleable = true
external_state = true
[node name="Room" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] [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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.15)
focusable = true
label = "view_in_ar" label = "view_in_ar"
icon = true icon = true
toggleable = true toggleable = true
external_state = true
[node name="Automate" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] [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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.21)
focusable = true
label = "schema" label = "schema"
icon = true icon = true
toggleable = true toggleable = true
external_state = true
[node name="Settings" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] [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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.27)
focusable = true
label = "settings" label = "settings"
icon = true icon = true
toggleable = true toggleable = true
external_state = true
[node name="Content" type="Node3D" parent="AnimationContainer"] [node name="Content" type="Node3D" parent="AnimationContainer"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06, 0, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06, 0, 0)

View File

@ -18,6 +18,8 @@ script = ExtResource("1_ch4jb")
[node name="TeleportRoot" type="Node3D" parent="."] [node name="TeleportRoot" type="Node3D" parent="."]
[node name="Ground" type="StaticBody3D" parent="TeleportRoot"] [node name="Ground" type="StaticBody3D" parent="TeleportRoot"]
collision_layer = 5
collision_mask = 5
[node name="CollisionShape3D" type="CollisionShape3D" parent="TeleportRoot/Ground"] [node name="CollisionShape3D" type="CollisionShape3D" parent="TeleportRoot/Ground"]
shape = SubResource("WorldBoundaryShape3D_08sv0") 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")] [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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.05, 0, 0.05)
focusable = true
label = "room_preferences" label = "room_preferences"
icon = true icon = true
toggleable = true toggleable = true

View File

@ -9,6 +9,8 @@ bounce = 0.7
radius = 0.08 radius = 0.08
[node name="Ball" type="RigidBody3D"] [node name="Ball" type="RigidBody3D"]
collision_layer = 4
collision_mask = 4
physics_material_override = SubResource("PhysicsMaterial_f6jtg") physics_material_override = SubResource("PhysicsMaterial_f6jtg")
angular_damp = 4.0 angular_damp = 4.0

View File

@ -3,7 +3,7 @@ extends Node3D
const ball_scene = preload("./ball.tscn") const ball_scene = preload("./ball.tscn")
const credits_scene = preload("./credits.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 connection_status = $Content/ConnectionStatus
@onready var input_url = $Content/InputURL @onready var input_url = $Content/InputURL
@ -15,10 +15,12 @@ const credits_scene = preload("./credits.tscn")
func _ready(): func _ready():
background.visible = false background.visible = false
clickable.on_click.connect(func(event): ball_button.on_button_down.connect(func():
var ball = ball_scene.instantiate() var ball = ball_scene.instantiate()
ball.transform = event.controller.transform var controller = XRHelpers.get_right_controller(self)
ball.linear_velocity = -event.controller.transform.basis.z * 5 + Vector3(0, 5, 0)
ball.transform = controller.transform
ball.linear_velocity = -controller.transform.basis.z * 5 + Vector3(0, 5, 0)
get_tree().root.add_child(ball) get_tree().root.add_child(ball)
) )

View File

@ -32,6 +32,7 @@ outline_size = 0
[node name="Button" parent="Content" instance=ExtResource("1_faxng")] [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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0458097, 0, 0.253575)
focusable = true
label = "sports_basketball" label = "sports_basketball"
icon = true icon = true
@ -84,6 +85,7 @@ horizontal_alignment = 0
[node name="Connect" parent="Content" instance=ExtResource("1_faxng")] [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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.25, 0, 0.12)
focusable = true
label = "wifi" label = "wifi"
icon = true icon = true

View File

@ -1,6 +1,5 @@
extends Event extends Event
class_name EventFocus class_name EventFocus
var ray: RayCast3D
var target: Node var target: Node
var previous_target: Node var previous_target: Node

12
lib/events/event_touch.gd Normal file
View File

@ -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

View File

@ -20,37 +20,40 @@ signal on_key_up(event: EventKey)
signal on_focus_in(event: EventFocus) signal on_focus_in(event: EventFocus)
signal on_focus_out(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 var _active_node: Node = null
func emit(type: String, event: Event): func emit(type: String, event: Event):
if event is EventBubble: if event is EventBubble:
_bubble_call(type, event.target, event) _bubble_call(type, event.target, event)
if type == "press_down":
_handle_focus(event)
else: else:
_root_call(type, event) _root_call(type, event)
func is_focused(node: Node): func is_focused(node: Node):
return _active_node == node return _active_node == node
func _handle_focus(event: EventPointer): func _handle_focus(event: EventBubble):
if event.target != null && event.target.is_in_group("ui_focus_skip"): var target = event.target
if target != null && target.is_in_group("ui_focus_skip"):
return return
var event_focus = EventFocus.new() var event_focus = EventFocus.new()
event_focus.previous_target = _active_node event_focus.previous_target = _active_node
event_focus.target = event.target event_focus.target = target
event_focus.ray = event.ray
if _active_node != null && _active_node.has_method(FN_PREFIX + "focus_out"): if _active_node != null && _active_node.has_method(FN_PREFIX + "focus_out"):
_active_node.call(FN_PREFIX + "focus_out", event_focus) _active_node.call(FN_PREFIX + "focus_out", event_focus)
on_focus_out.emit(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 _active_node = null
return return
_active_node = event.target _active_node = target
if _active_node != null && _active_node.has_method(FN_PREFIX + "focus_in"): if _active_node != null && _active_node.has_method(FN_PREFIX + "focus_in"):
_active_node.call(FN_PREFIX + "focus_in", event_focus) _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: if event.bubbling == false:
return false return false
if type == "press_down" || type == "touch_enter":
_handle_focus(event)
for child in target.get_children(): for child in target.get_children():
if child is Function && child.has_method(FN_PREFIX + type): if child is Function && child.has_method(FN_PREFIX + type):
child.call(FN_PREFIX + type, event) child.call(FN_PREFIX + type, event)

View File

@ -1,4 +1,4 @@
extends Object extends RefCounted
enum Type { enum Type {
CONTROLLER_LEFT, CONTROLLER_LEFT,

14
lib/utils/proxy.gd Normal file
View File

@ -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)

17
lib/utils/touch/finger.gd Normal file
View File

@ -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

57
lib/utils/touch/touch.gd Normal file
View File

@ -0,0 +1,57 @@
extends Node
const Finger = preload("res://lib/utils/touch/finger.gd")
## Record<Finger.Type, Area3D>
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)