From e1c93f7be08e9f0e91c4f76a5d18211886123b52 Mon Sep 17 00:00:00 2001 From: betalars Date: Sat, 22 Apr 2023 15:11:10 +0200 Subject: [PATCH] Changed Focus Handling to que-based pattern, WIP --- .../youth_room/Collectable.gd | 21 +++ .../youth_room/room_handle.gd | 9 +- src/dev-util/click-trough-area.gd | 17 +- src/dev-util/collapsing_button.gd | 27 ++++ src/dev-util/ui-testing.tscn | 26 +++ .../collectable/collectable_ui.gd | 105 ++++++++++++ .../collectable/collectable_ui.tscn | 150 ++++++++++++++++-- .../player_controller/player_controller.gd | 81 +++++++--- .../player_controller/player_controller.tscn | 6 + src/singletons/global_state.gd | 55 ++++--- 10 files changed, 434 insertions(+), 63 deletions(-) create mode 100644 src/base-environments/youth_room/Collectable.gd create mode 100644 src/dev-util/collapsing_button.gd create mode 100644 src/dev-util/ui-testing.tscn create mode 100644 src/logic-scenes/collectable/collectable_ui.gd diff --git a/src/base-environments/youth_room/Collectable.gd b/src/base-environments/youth_room/Collectable.gd new file mode 100644 index 0000000..22c3b70 --- /dev/null +++ b/src/base-environments/youth_room/Collectable.gd @@ -0,0 +1,21 @@ +extends Area3D + +var has_mouse: bool = false +var has_focus: bool = false: + set(focus): + _on_mouse_entered() + $UiWrapper/UiSprite/SubViewport/Collectable_ui.has_focus = focus + has_focus = focus + +# Called when the node enters the scene tree for the first time. +func _ready(): + connect("mouse_entered", Callable(self, "_on_mouse_entered")) + +func _on_mouse_entered(): + input_ray_pickable = false + $UiWrapper.show() + $UiWrapper/UiSprite/SubViewport/Collectable_ui.show() + has_mouse = true + +func _on_mouse_exited(): + assert(false) diff --git a/src/base-environments/youth_room/room_handle.gd b/src/base-environments/youth_room/room_handle.gd index 5ae4678..342f16d 100644 --- a/src/base-environments/youth_room/room_handle.gd +++ b/src/base-environments/youth_room/room_handle.gd @@ -36,7 +36,12 @@ func _update_scene(new_mode) -> int: emit_signal("freeze") elif new_mode == Modes.FREEZE: emit_signal("freeze") - - + return new_mode + +func _unhandled_input(event): + if event is InputEventMouseButton: + if event.pressed: + print("passed") + #State.pass_focus_to($PlayerController) diff --git a/src/dev-util/click-trough-area.gd b/src/dev-util/click-trough-area.gd index 5587791..a923193 100644 --- a/src/dev-util/click-trough-area.gd +++ b/src/dev-util/click-trough-area.gd @@ -1,8 +1,7 @@ extends Area3D - -@onready var s: Sprite3D = $Sprite3D -@onready var v: SubViewport = $Sprite3D/SubViewport +@onready var sprite: Sprite3D = $UiSprite +@onready var viewport: SubViewport = $UiSprite/SubViewport func _process(delta): var camera = get_viewport().get_camera_3d() @@ -16,24 +15,26 @@ func _process(delta): func _unhandled_input(event): if event is InputEventMouse: - # Handled via _on_input_event. + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_RIGHT: + assert(false) return - v.push_input(event) + viewport.push_input(event) func _on_input_event(_camera: Camera3D, event: InputEvent, pos: Vector3, _normal: Vector3, _shape_idx: int): # Position of the event in Sprite3D local coordinates. - var texture_3d_position = s.get_global_transform().affine_inverse() * pos + var texture_3d_position = sprite.get_global_transform().affine_inverse() * pos #if !is_zero_approx(texture_3d_position.z): # # Discard event because event didn't happen on the side of the Sprite3D. # return # Position of the event relative to the texture. - var texture_position: Vector2 = Vector2(texture_3d_position.x, -texture_3d_position.y) / s.pixel_size - s.get_item_rect().position + var texture_position: Vector2 = Vector2(texture_3d_position.x, -texture_3d_position.y) / sprite.pixel_size - sprite.get_item_rect().position # Send mouse event. var e: InputEvent = event.duplicate() if e is InputEventMouse: e.set_position(texture_position) e.set_global_position(texture_position) - v.push_input(e) + viewport.push_input(e) func _on_button_pressed(): print("Button pressed") diff --git a/src/dev-util/collapsing_button.gd b/src/dev-util/collapsing_button.gd new file mode 100644 index 0000000..c2d79a8 --- /dev/null +++ b/src/dev-util/collapsing_button.gd @@ -0,0 +1,27 @@ +@tool +extends Button + +func hide(): + if visible == true: + var tween:Tween = create_tween() + custom_minimum_size = get_minimum_size() + var tmp = text + text = "" + tween.tween_property(self, "custom_minimum_size", Vector2(size.x, 0), 0.2) + update_minimum_size() + await tween.finished + visible = false + text = tmp + update_minimum_size() + +func show(): + if visible == false: + var tmp = text + var tween:Tween = create_tween() + tween.tween_property(self, "custom_minimum_size", get_minimum_size(), 0.2) + text = "" + update_minimum_size() + visible = true + await tween.finished + text = tmp + diff --git a/src/dev-util/ui-testing.tscn b/src/dev-util/ui-testing.tscn new file mode 100644 index 0000000..1c55a1f --- /dev/null +++ b/src/dev-util/ui-testing.tscn @@ -0,0 +1,26 @@ +[gd_scene format=3 uid="uid://52mr50b01ibd"] + +[node name="Control" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Button" type="Button" parent="."] +layout_mode = 0 +offset_left = 495.0 +offset_top = 163.0 +offset_right = 634.0 +offset_bottom = 235.0 +text = "small boi" + +[node name="Button2" type="Button" parent="."] +visible = false +layout_mode = 0 +offset_left = 441.0 +offset_top = 119.0 +offset_right = 694.0 +offset_bottom = 320.0 +text = "big boi" diff --git a/src/logic-scenes/collectable/collectable_ui.gd b/src/logic-scenes/collectable/collectable_ui.gd new file mode 100644 index 0000000..29666d6 --- /dev/null +++ b/src/logic-scenes/collectable/collectable_ui.gd @@ -0,0 +1,105 @@ +@tool +extends CenterContainer +class_name Collectable_Ui + +@export var collapsed = true: + set(collapse): + if is_inside_tree(): + if State.reduce_motion: + collapsed = false + return + if collapse and not collapsed: + if is_inside_tree(): + _hide_buttons() + collapsed = collapse + elif not collapse and collapsed: + if is_inside_tree(): + _show_buttons() + collapsed = collapse + +@export var is_story: bool = false +@export var has_focus: bool = false: + set(focused): + if has_focus == focused: return + + if focused: + has_focus = State.request_focus(self) + if has_focus: + collapsed = false + if collected: + $Panel/Content/Buttons/VBoxContainer/put_back.grab_focus() + else: + $Panel/Content/Buttons/VBoxContainer/collect_or_listen.grab_focus() + elif has_focus: + has_focus = false + State.drop_own_focus(self) + get_viewport().gui_release_focus() + hide() + + if not visible: + show() + + +@export var collected: bool = false: + set(set_collected): + collected = set_collected + if set_collected: + $Panel/Content/Buttons/VBoxContainer/put_back.show() + if is_story: + $Content/Buttons/VBoxContainer/put_back.disabled = true + $Content/Buttons/VBoxContainer/collect_or_listen.text = "listen again" + if State.allow_skipping: + $Content/Buttons/VBoxContainer/skip.text = "discard cards (skip)" + else: + $Content/Buttons/VBoxContainer/collect_or_listen.disabled = true + $Content/Buttons/VBoxContainer/put_back.show() + else: + $Content/Buttons/VBoxContainer/collect_or_listen.disabled = false + +@export var skipped: bool = false + +@export var item_name: String = "": + set(new_name): + item_name = new_name + $Content/Name.text = new_name + +@export var content_notes: String = "": + set(new_notes): + content_notes = new_notes + $Content/Name.text = new_notes + +# Called when the node enters the scene tree for the first time. +func _ready(): + #$Panel/Content/ContentNotes.visible = State.show_content_notes + #$Panel/Content/Buttons/VBoxContainer/Summary.visible = State.provide_summaries + #$Panel/Content/Buttons/VBoxContainer/skip.visible = State.allow_skipping + if visible and not collapsed: _show_buttons() + +func _hide_buttons(): + if not State.reduce_motion: $AnimationPlayer.play_backwards("show_buttons") + +func _show_buttons(): + if State.reduce_motion: + $AnimationPlayer.play("show_buttons") + else: + $AnimationPlayer.play("RESET") + +func hide(): + if visible: + _hide_buttons() + var tween = create_tween() + tween.tween_property(self, "modulate", 0, 0.4) + _hide_buttons() + await tween.finished + visible = false + +func show(): + if not collapsed: + _show_buttons() + modulate = Color() + visible = true + var tween = create_tween() + tween.tween_property(self, "modulate", Color(1, 1, 1), 0.4) + +func _yoink_focus(): + State.request_focus(self, true) diff --git a/src/logic-scenes/collectable/collectable_ui.tscn b/src/logic-scenes/collectable/collectable_ui.tscn index b43582a..9d012ac 100644 --- a/src/logic-scenes/collectable/collectable_ui.tscn +++ b/src/logic-scenes/collectable/collectable_ui.tscn @@ -1,22 +1,120 @@ -[gd_scene format=3 uid="uid://cceyp2yd6o3sq"] +[gd_scene load_steps=9 format=3 uid="uid://cceyp2yd6o3sq"] -[node name="CenterContainer" type="CenterContainer"] +[ext_resource type="Theme" uid="uid://b056fn288p8ha" path="res://logic-scenes/themes/messy.theme" id="1_2apkb"] +[ext_resource type="Script" path="res://logic-scenes/collectable/collectable_ui.gd" id="1_tgjc2"] +[ext_resource type="Texture2D" uid="uid://d0ucjqi8tx6vt" path="res://import/interface-elements/frame.png" id="3_63j61"] + +[sub_resource type="GDScript" id="GDScript_g0qhf"] +script/source = "@tool +extends Button + +func hide(): + if visible == true and not State.reduce_motion: + var tween:Tween = create_tween() + custom_minimum_size = get_minimum_size() + var tmp = text + text = \"\" + tween.tween_property(self, \"custom_minimum_size\", Vector2(size.x, 0), 0.2) + update_minimum_size() + await tween.finished + visible = false + text = tmp + update_minimum_size() + else: + visible = false + +func show(): + if visible == false and not State.reduce_motion: + var tmp = text + var tween:Tween = create_tween() + tween.tween_property(self, \"custom_minimum_size\", get_minimum_size(), 0.2) + text = \"\" + update_minimum_size() + visible = true + await tween.finished + text = tmp + else: + visible = true + +" + +[sub_resource type="Animation" id="Animation_rhsmi"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Panel/Content/Buttons:custom_minimum_size") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(0, 256)] +} + +[sub_resource type="Animation" id="Animation_bq4rh"] +resource_name = "invisible" +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Panel/Content/Buttons:custom_minimum_size") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(0, 0)] +} + +[sub_resource type="Animation" id="Animation_rx43a"] +resource_name = "show_buttons" +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Panel/Content/Buttons:custom_minimum_size") +tracks/0/interp = 2 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.4, 0.6), +"transitions": PackedFloat32Array(1, 1, 1), +"update": 0, +"values": [Vector2(0, 0), Vector2(0, 135), Vector2(0, 130)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_jad23"] +_data = { +"RESET": SubResource("Animation_rhsmi"), +"invisible": SubResource("Animation_bq4rh"), +"show_buttons": SubResource("Animation_rx43a") +} + +[node name="CollectableUi" type="CenterContainer"] anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +theme = ExtResource("1_2apkb") +script = ExtResource("1_tgjc2") +collapsed = null -[node name="VBoxContainer" type="VBoxContainer" parent="."] +[node name="Panel" type="PanelContainer" parent="."] layout_mode = 2 -[node name="Name" type="Label" parent="VBoxContainer"] +[node name="Content" type="VBoxContainer" parent="Panel"] +layout_mode = 2 + +[node name="Name" type="Label" parent="Panel/Content"] layout_mode = 2 theme_type_variation = &"HeaderLarge" text = "old Mask" horizontal_alignment = 1 -[node name="Content Notes" type="RichTextLabel" parent="VBoxContainer"] +[node name="ContentNotes" type="RichTextLabel" parent="Panel/Content"] +visible = false custom_minimum_size = Vector2(256, 0) layout_mode = 2 bbcode_enabled = true @@ -25,21 +123,55 @@ Food, Blood, Gore, Whatever, I need a second line. [/center]" fit_content = true -[node name="collect_or_listen" type="Button" parent="VBoxContainer"] +[node name="Buttons" type="ScrollContainer" parent="Panel/Content"] +custom_minimum_size = Vector2(0, 256) +layout_mode = 2 +horizontal_scroll_mode = 0 +vertical_scroll_mode = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="Panel/Content/Buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="collect_or_listen" type="Button" parent="Panel/Content/Buttons/VBoxContainer"] layout_mode = 2 tooltip_text = "Take this with you, and listen to it's story." text = "Collect" +script = SubResource("GDScript_g0qhf") -[node name="Summary" type="Button" parent="VBoxContainer"] +[node name="Summary" type="Button" parent="Panel/Content/Buttons/VBoxContainer"] layout_mode = 2 tooltip_text = "Take this with you, but get a neutral description of it's story." text = "get neutral summary" +script = SubResource("GDScript_g0qhf") -[node name="skip" type="Button" parent="VBoxContainer"] +[node name="skip" type="Button" parent="Panel/Content/Buttons/VBoxContainer"] layout_mode = 2 tooltip_text = "Choose this to entirely skip this Item without being unable to progress in the story. Skipped Segments can still be interacted with via the Pause Screen, if you decide to change your mind." text = "skip" +script = SubResource("GDScript_g0qhf") -[node name="put_back" type="Button" parent="VBoxContainer"] +[node name="put_back" type="Button" parent="Panel/Content/Buttons/VBoxContainer"] layout_mode = 2 text = "put back" +script = SubResource("GDScript_g0qhf") + +[node name="StartFrame" type="TextureRect" parent="Panel"] +layout_mode = 2 +mouse_filter = 2 +texture = ExtResource("3_63j61") +expand_mode = 2 +stretch_mode = 4 + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +autoplay = "invisible" +libraries = { +"": SubResource("AnimationLibrary_jad23") +} + +[connection signal="resized" from="Panel/Content/Buttons/VBoxContainer" to="Panel/Content/Buttons" method="_on_v_box_container_resized"] +[connection signal="focus_entered" from="Panel/Content/Buttons/VBoxContainer/collect_or_listen" to="." method="_yoink_focus"] +[connection signal="focus_entered" from="Panel/Content/Buttons/VBoxContainer/Summary" to="." method="_yoink_focus"] +[connection signal="focus_entered" from="Panel/Content/Buttons/VBoxContainer/skip" to="." method="_yoink_focus"] +[connection signal="focus_entered" from="Panel/Content/Buttons/VBoxContainer/put_back" to="." method="_yoink_focus"] diff --git a/src/logic-scenes/player_controller/player_controller.gd b/src/logic-scenes/player_controller/player_controller.gd index 4e7f383..464c600 100644 --- a/src/logic-scenes/player_controller/player_controller.gd +++ b/src/logic-scenes/player_controller/player_controller.gd @@ -1,6 +1,33 @@ extends RigidBody3D -@export var active: bool = true : set = set_active +@export var has_focus: bool = true : + set(focused): + if has_focus != focused: + if focused: + has_focus = State.request_focus(self) + if is_inside_tree() and has_focus: + camera.make_current() + get_viewport().gui_release_focus() + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + var jitter_tween: Tween = create_tween() + jitter_tween.tween_property(self, "jitter_strength", 1, 1) + if has_entered: emit_signal("ui_entered") + elif has_focus: + camera.current = true + jitter_strength = 1 + else: + if is_inside_tree() and has_focus: + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + var jitter_tween: Tween = create_tween() + jitter_tween.tween_property(self, "jitter_strength", 0, 0.5) + if has_entered: emit_signal("ui_exited") + else: + jitter_strength = 0 + has_focus = false + State.drop_own_focus(self) + + sleeping = has_focus + @export_range (0, 10) var max_speed: float = 3 @export_range (0, 10) var max_acceleration: float = 5 @export_range (0, 20) var damp: float = 10 @@ -30,36 +57,32 @@ var on_crouch_cooldown:bool = false @onready var pitch:Node3D = $Yaw/Pitch @onready var mount:Node3D = $Yaw/Pitch/Mount @onready var camera:Camera3D = $Yaw/Pitch/Mount/Camera3D +@onready var focus_ray: RayCast3D = $Yaw/Pitch/Mount/Camera3D/RayCast3D -func set_active(activate): - active = activate - if !is_inside_tree(): return - if activate: - camera.make_current() - Input.mouse_mode = Input.MOUSE_MODE_CAPTURED - var jitter_tween: Tween = create_tween() - jitter_tween.tween_property(self, "jitter_strength", 1, 1) - else: - Input.mouse_mode = Input.MOUSE_MODE_VISIBLE - var jitter_tween: Tween = create_tween() - jitter_tween.tween_property(self, "jitter_strength", 0, 0.5) - sleeping = active +signal ui_entered +var has_entered:bool = false +signal ui_exited func _ready(): - - if active: - set_active(active) - jitter_strength = 1 - _handle_jitter(0) func _process(delta): - if Input.is_action_just_pressed("ui_cancel"): - set_active(!active) + + if focus_ray.get_collider() != null: + emit_signal("ui_entered") + has_entered = true + + if has_entered: + if focus_ray.get_collider() == null: + emit_signal("ui_exited") + has_entered = false + if Input.is_action_just_pressed("ui_accept"): + State.pass_focus_to(focus_ray.get_collider()) func _physics_process(delta:float): - _handle_movement(delta) - _handle_rotation(delta) + if has_focus: + _handle_movement(delta) + _handle_rotation(delta) if jitter_strength > 0: _handle_jitter(delta) func _handle_movement(delta:float): @@ -122,9 +145,19 @@ func _handle_mouse_input(event:InputEventMouseMotion): current_mouse_rotation = event.relative func _unhandled_input(event:InputEvent): - if active: + if has_focus: if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: _handle_mouse_input(event) + get_viewport().set_input_as_handled() + if event is InputEventMouseButton and has_entered: + has_focus = false + get_viewport().set_input_as_handled() + if event.is_action("ui_accept"): + State.pass_focus_to(focus_ray.get_collider()) + get_viewport().set_input_as_handled() + +func _on_empty_click(): + State.pass_focus_to(self) func _on_bed_enter(_body): if not (crouched or on_crouch_cooldown): diff --git a/src/logic-scenes/player_controller/player_controller.tscn b/src/logic-scenes/player_controller/player_controller.tscn index 16af0f0..1832b4e 100644 --- a/src/logic-scenes/player_controller/player_controller.tscn +++ b/src/logic-scenes/player_controller/player_controller.tscn @@ -120,6 +120,12 @@ transform = Transform3D(1, 0, 0, 0, 0.5, -0.866025, 0, 0.866025, 0.5, 0, 0.25649 transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.202, 0.157) current = true +[node name="RayCast3D" type="RayCast3D" parent="Yaw/Pitch/Mount/Camera3D"] +target_position = Vector3(0, 0, -1.3) +collision_mask = 17 +collide_with_areas = true +collide_with_bodies = false + [node name="PlayerCollision" type="CollisionShape3D" parent="."] transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0.322255, 0) shape = SubResource("8") diff --git a/src/singletons/global_state.gd b/src/singletons/global_state.gd index f0645ae..33c271b 100644 --- a/src/singletons/global_state.gd +++ b/src/singletons/global_state.gd @@ -9,33 +9,48 @@ var show_content_notes: bool = false var provide_summaries: bool = false var allow_skipping: bool = false -var current_focus: Node +var focus_list:Array = [] +var lock_focus: bool = false -func request_focus(new_focus: Node, yoink:bool = false) -> bool: - if (current_focus == null or yoink) and new_focus != null: - current_focus = new_focus +func request_focus(new_focus: Node, reclaim_focus: bool = false) -> bool: + assert(is_instance_valid(new_focus)) + if not focus_list.size() == 0: _pass_focus_of(focus_list[0]) + if not lock_focus or get_tree().paused: + push_warning(new_focus.name, " attempted to get focus while tree was paused or fokus had been locked.") + return false + elif reclaim_focus: + if focus_list.has(new_focus): + while not focus_list[0] == new_focus: focus_list.pop_front() + return true + else: + focus_list.push_front(new_focus) + push_warning(new_focus.name, " attempted to reclaim focus it did not previousely have.") + return true + else: + focus_list.append(new_focus) return true - else: return false -func pass_focus_to(to: Node) -> bool: - if "has_focus" in to: - if not current_focus == null: - drop_focus_of(current_focus) - to.has_focus = true + +func assign_focus_to(focusable: Node) -> bool: + if "has_focus" in focusable: + if not focus_list.size() == 0: + _pass_focus_of(focus_list[0]) + focusable.has_focus = true return true else: return false func drop_own_focus(node: Node): - if current_focus == node: - current_focus = null + if focus_list[0] == node: + focus_list.pop_front().has_focus = false + assert(focus_list.size() > 0) + focus_list[0].has_focus = true else: - push_error(node.name + " attempted to drop focus while not owning it!") + push_warning(node.name + " attempted to drop focus while not owning it.") -func drop_focus_of(node: Node): - if current_focus == node: - current_focus = null - if node.has_focus: node.has_focus = false - else: - node.has_focus = false - push_warning("Attempted to drop Focus for " + node.name + " while it wasn't focused!") +func clear_focus_with(focus: Node): + assert(is_instance_valid(focus)) + if focus_list.size() > 0: focus_list[0].has_focus = false + focus_list = [focus] +func _pass_focus_of(previous_focus: Node): + previous_focus.has_focus = false