From c59f89e99c90c0e756cdf780bcbf04d782eb3e6f Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 1 Jan 2022 04:56:58 +0000 Subject: Change touch scroll detection to pointer-input-sensor approach using signals/events. --- logic/database.gd | 60 ++++++++++++++++++++++- logic/stage.gd | 104 ++++++++++++++++++++++++++++------------ main.tscn | 7 +-- pointer_input_sensor.gd | 84 ++++++++++++++++++++++++++++++++ project.godot | 12 ++--- touch_scroll.gd | 125 ------------------------------------------------ 6 files changed, 224 insertions(+), 168 deletions(-) create mode 100644 pointer_input_sensor.gd delete mode 100644 touch_scroll.gd diff --git a/logic/database.gd b/logic/database.gd index 4f3e613..41ba679 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -31,7 +31,8 @@ onready var stage: Stage = get_node("/root/main/stage") onready var delete_button: Button = get_node("actions/delete") onready var edit_button: Button = get_node("actions/edit") onready var add_button: Button = get_node("actions/add") -onready var v_scroll_bar: ScrollBar = get_v_scroll() +onready var v_scroll_bar: ScrollBar = get_v_scroll() # @DAM Stop scroll inertia when buttons are pressed. +onready var drag_sensor: PointerInputSensor = get_node("drag_sensor") func _init(): @@ -44,15 +45,72 @@ func _init(): func _ready(): self.connect("item_selected", self, "item_selected") self.connect("nothing_selected", self, "clear_selection") + delete_button.connect("pressed", self, "delete_action") edit_button.connect("pressed", self, "edit_action") add_button.connect("pressed", self, "add_action") + stage.connect("save", self, "save") stage.connect("discard", self, "discard") + + drag_sensor.connect("on_press", self, "on_press") + drag_sensor.connect("on_drag", self, "on_drag") + drag_sensor.connect("on_end_drag", self, "on_end_drag") + drag_sensor.connect("on_click", self, "on_click") + for it in db: self.add_item(get_entry_view(it)) +# @DAM Cleanup this code. +export var scroll_velocity_decaying_factor: float = 2.5 +var drag_velocity := 0.0 +var is_dragging := false + +func _process(delta: float): + if is_dragging == false && abs(drag_velocity) > 0.5: + drag_velocity *= clamp((1.0 - scroll_velocity_decaying_factor * delta), 0.0, 1.0) + v_scroll_bar.value -= drag_velocity * delta + +func on_click(pointer: PointerInputSensor.PointerInputData): + var target := self + var position := target.get_global_mouse_position() - target.rect_global_position + + var event_touch := InputEventScreenTouch.new() + event_touch.index = 0 + event_touch.position = position + + var event_mouse := InputEventMouseButton.new() + event_mouse.button_index = BUTTON_LEFT + event_mouse.button_mask = BUTTON_MASK_LEFT + event_mouse.position = position + + event_mouse.pressed = true + event_touch.pressed = true + target._gui_input(event_mouse) + target._gui_input(event_touch) + + target.grab_focus() + + event_mouse.pressed = false + event_touch.pressed = false + target._gui_input(event_mouse) + target._gui_input(event_touch) + +func on_press(pointer: PointerInputSensor.PointerInputData): + is_dragging = true + +func on_end_drag(pointer: PointerInputSensor.PointerInputData): + is_dragging = false + +func on_drag(pointer: PointerInputSensor.PointerInputData): + is_dragging = true + drag_velocity = pointer.velocity.y + v_scroll_bar.value -= pointer.relative_position.y + + + + func _notification(what: int): if visible == false: return diff --git a/logic/stage.gd b/logic/stage.gd index f7cced4..62e0a58 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -37,7 +37,6 @@ onready var save_button: Button = get_node("controls/save") onready var discard_button: Button = get_node("controls/discard") - func _ready(): save_button.connect("pressed", self, "save_action") discard_button.connect("pressed", self, "discard_action") @@ -48,19 +47,82 @@ func _ready(): match it.name: "date_picker", "save", "discard": print("- %s" % it.name) - + _: # "first_assistant": print("+ %s" % it.name) - var touch_scroll = TouchScroll.new() - touch_scroll.name = "touch_scroll" - touch_scroll.click_target_path = "../" - touch_scroll.scroll_target_path = "../../../" - touch_scroll.scroll_bar_get_method = "get_v_scrollbar" - touch_scroll.mouse_default_cursor_shape = Control.CURSOR_IBEAM - (it as Control).add_child(touch_scroll) - touch_scroll.anchor_right = 1.0 - touch_scroll.anchor_bottom = 1.0 + var drag_sensor = PointerInputSensor.new() + drag_sensor.name = "drag_sensor" + drag_sensor.mouse_default_cursor_shape = Control.CURSOR_IBEAM + (it as Control).add_child(drag_sensor) + drag_sensor.anchor_right = 1.0 + drag_sensor.anchor_bottom = 1.0 + + drag_sensor.connect("on_press", self, "on_press") + drag_sensor.connect("on_drag", self, "on_drag") + drag_sensor.connect("on_end_drag", self, "on_end_drag") + drag_sensor.connect("on_click", self, "on_click") + drag_sensor.on_process = funcref(self, "bazinga") + + +# @DAM Cleanup this code. +onready var v_scroll_bar: VScrollBar = get_v_scrollbar() +export var scroll_velocity_decaying_factor: float = 2.5 +var drag_velocity := 0.0 +var is_dragging := false + +func bazinga(delta: float, pointer: PointerInputSensor): + if (pointer.get_parent() as Control).has_focus(): + pointer.mouse_filter = Control.MOUSE_FILTER_IGNORE + else: + pointer.mouse_filter = Control.MOUSE_FILTER_STOP + +func _process(delta: float): + if is_dragging == false && abs(drag_velocity) > 0.5: + drag_velocity *= clamp((1.0 - scroll_velocity_decaying_factor * delta), 0.0, 1.0) + v_scroll_bar.value -= drag_velocity * delta + +func on_click(pointer: PointerInputSensor.PointerInputData): + var target: Control = pointer.target.get_parent() + var position := target.get_global_mouse_position() - target.rect_global_position + + var event_touch := InputEventScreenTouch.new() + event_touch.index = 0 + event_touch.position = position + + var event_mouse := InputEventMouseButton.new() + event_mouse.button_index = BUTTON_LEFT + event_mouse.button_mask = BUTTON_MASK_LEFT + event_mouse.position = position + + event_mouse.pressed = true + event_touch.pressed = true + target._gui_input(event_mouse) + target._gui_input(event_touch) + + target.grab_focus() + + event_mouse.pressed = false + event_touch.pressed = false + target._gui_input(event_mouse) + target._gui_input(event_touch) + +func on_press(pointer: PointerInputSensor.PointerInputData): + is_dragging = true + +func on_end_drag(pointer: PointerInputSensor.PointerInputData): + is_dragging = false + +func on_drag(pointer: PointerInputSensor.PointerInputData): + is_dragging = true + drag_velocity = pointer.velocity.y + v_scroll_bar.value -= pointer.relative_position.y + + + + + + func save_action(): @@ -91,18 +153,6 @@ func set_stage(entry: Dictionary): func get_stage() -> Dictionary: - -# var entry: Dictionary = Database.instance_entry({ -# "process_id": process_id.text, -# "surgery_id": surgery_id.text, -# "date_year": date.get_year(), -# "date_month": date.get_month(), -# "date_day": date.get_day(), -# "place": place.text, -# "anesthetic": anesthetic.text, -# "first_assistant": first_assistant.text, -# "type": type.text, -# }) # @DAM Simplify all this... avoid creating multiple entries/dictionaries. var entry: Dictionary = { "process_id": process_id.text, @@ -124,14 +174,6 @@ func get_stage() -> Dictionary: return entry -# @DAM Testing. Needs all children controllers to have Mouse > Filter : Pass. -#func _gui_input(event): -# accept_event() -# if event is InputEventScreenDrag: -# self.scroll_vertical -= event.relative.y -# return - - func _notification(what: int): if what == MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST: discard_action() diff --git a/main.tscn b/main.tscn index 9cb9220..331d94d 100644 --- a/main.tscn +++ b/main.tscn @@ -9,7 +9,7 @@ [ext_resource path="res://icons/add.png" type="Texture" id=7] [ext_resource path="res://icons/delete.png" type="Texture" id=8] [ext_resource path="res://logic/stage.gd" type="Script" id=9] -[ext_resource path="res://touch_scroll.gd" type="Script" id=10] +[ext_resource path="res://pointer_input_sensor.gd" type="Script" id=10] [node name="main" type="Control"] anchor_right = 1.0 @@ -29,7 +29,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="touch_scroll" type="Control" parent="database"] +[node name="drag_sensor" type="Control" parent="database"] anchor_right = 1.0 anchor_bottom = 1.0 margin_right = -8.0 @@ -37,9 +37,6 @@ script = ExtResource( 10 ) __meta__ = { "_edit_use_anchors_": false } -click_target_path = "../" -scroll_target_path = "../" -scroll_bar_get_method = "get_v_scroll" [node name="actions" type="VBoxContainer" parent="database"] anchor_left = 1.0 diff --git a/pointer_input_sensor.gd b/pointer_input_sensor.gd new file mode 100644 index 0000000..95e1b4b --- /dev/null +++ b/pointer_input_sensor.gd @@ -0,0 +1,84 @@ +extends Control +class_name PointerInputSensor + +export var drag_threshold_cm: float = 0.250 + +signal on_press +signal on_release +#signal on_release_outside +signal on_click +signal on_enter +signal on_exit +#signal on_exit_app_window +signal on_begin_drag +signal on_drag +signal on_end_drag + +class PointerInputData: + var target: PointerInputSensor + var index := -1 + var initial_position := Vector2.ZERO + var current_position := Vector2.ZERO + var relative_position = Vector2.ZERO + var velocity := Vector2.ZERO + var was_dragged := false + var is_pressed := false + +var pointer: PointerInputData +var screen_dpcm: float +var on_process: FuncRef + + +func _ready(): + screen_dpcm = float(OS.get_screen_dpi()) / 2.54 + pointer = PointerInputData.new() + pointer.target = self + connect("mouse_entered", self, "_on_enter_exit", [true]) + connect("mouse_entered", self, "_on_enter_exit", [false]) + + +func _process(delta: float): + if on_process != null && on_process.is_valid(): + on_process.call_func(delta, self) + + +func _on_enter_exit(is_inside: bool): + emit_signal("on_enter" if is_inside else "on_exit", pointer) + + +func _gui_input(event: InputEvent): + + # @DAM A bug on GODOT-3.X makes events from non-mouse-emulated pointers (index > 0) unreliable. + if event is InputEventScreenTouch || event is InputEventScreenDrag: + if event.index != 0: + return + + if event is InputEventScreenTouch && (pointer.is_pressed == false || pointer.index == event.index): + var touch := event as InputEventScreenTouch + pointer.is_pressed = event.pressed + pointer.current_position = get_global_mouse_position() + if pointer.is_pressed: + pointer.index = touch.index + pointer.initial_position = get_global_mouse_position() + emit_signal("on_press", pointer) + else: + emit_signal("on_release", pointer) + if pointer.was_dragged == false: + emit_signal("on_click", pointer) + else: + emit_signal("on_end_drag", pointer) + pointer.index = -1 + pointer.was_dragged = false + + if event is InputEventScreenDrag && event.index == pointer.index: + var drag := event as InputEventScreenDrag + pointer.current_position = get_global_mouse_position() + pointer.velocity = drag.speed + pointer.relative_position = drag.relative + # @DAM Instead of constantly converting the pointer distance to cm... just conver the drag_threshold_cm to pixels + if pointer.was_dragged == false && pointer.current_position.distance_to(pointer.initial_position) / screen_dpcm > drag_threshold_cm: + pointer.was_dragged = true + emit_signal("on_begin_drag", pointer) + emit_signal("on_drag", pointer) + + diff --git a/project.godot b/project.godot index 703d936..f5dbe7a 100644 --- a/project.godot +++ b/project.godot @@ -19,17 +19,17 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://date_picker/date_picker.gd" }, { +"base": "Control", +"class": "PointerInputSensor", +"language": "GDScript", +"path": "res://pointer_input_sensor.gd" +}, { "base": "ScrollContainer", "class": "Stage", "language": "GDScript", "path": "res://logic/stage.gd" }, { "base": "Control", -"class": "TouchScroll", -"language": "GDScript", -"path": "res://touch_scroll.gd" -}, { -"base": "Control", "class": "ValuePicker", "language": "GDScript", "path": "res://date_picker/value_picker.gd" @@ -37,8 +37,8 @@ _global_script_classes=[ { _global_script_class_icons={ "Database": "", "DatePicker": "", +"PointerInputSensor": "", "Stage": "", -"TouchScroll": "", "ValuePicker": "" } diff --git a/touch_scroll.gd b/touch_scroll.gd deleted file mode 100644 index 7b6aefd..0000000 --- a/touch_scroll.gd +++ /dev/null @@ -1,125 +0,0 @@ -extends Control -class_name TouchScroll - -const DRAG_THRESHOLD_CM: float = 0.250 - -export var click_target_path: String -export var scroll_target_path: String -export var scroll_bar_get_method: String -export var scroll_velocity_decaying_factor: float = 2.5 - -var pointer: Dictionary -var anchor: float -var offset: float -var screen_dpcm: float - -var click_target: Control -var scroll_target: Control -var target_scroll_bar: VScrollBar - - - -# @DAM -# Change this into an event-driven approach by changing the touch_scroll into a signal emitter and -# the target scrollable control a signal consumer. This way, the signal consumer will behave as the -# central node and will have the capability to detect when different touch_scroll sensors are -# activated and stop previous inputs (if required). -# Signal emitters should signal: -# - when an input drag starts -# - when an input drag stops -# - when an input drags -# - when an input clicks - - -func _ready(): - click_target = get_node(click_target_path) - scroll_target = get_node(scroll_target_path) - target_scroll_bar = scroll_target.call(scroll_bar_get_method) - pointer = { - index = -1, - initial_position = Vector2.ZERO, - current_position = Vector2.ZERO, - relative_position = Vector2.ZERO, - velocity = Vector2.ZERO, - was_dragged = false, - is_active = false, - } - screen_dpcm = float(OS.get_screen_dpi()) / 2.54 - - -func _process(delta: float): - # @DAM This only works for the stage... for the database this hides the touch_scroll after the first click. - if click_target.has_focus(): - mouse_filter = Control.MOUSE_FILTER_IGNORE - else: - mouse_filter = Control.MOUSE_FILTER_STOP - - if pointer.is_active: - var dragged_distance: float = - (pointer.current_position.y - pointer.initial_position.y) - offset = anchor + dragged_distance -# self.target_scroll_bar.value = offset - self.target_scroll_bar.value -= pointer.relative_position.y - pointer.relative_position = Vector2.ZERO - elif pointer.velocity.length() > 0.5: - pointer.velocity *= clamp((1.0 - scroll_velocity_decaying_factor * delta), 0.0, 1.0) - offset -= pointer.velocity.y * delta -# self.target_scroll_bar.value = offset - self.target_scroll_bar.value -= pointer.velocity.y * delta - - -func _gui_input(event: InputEvent): - - # @DAM A bug on GODOT-3.X makes events from non-mouse-emulated pointers (index > 0) unreliable. - if event is InputEventScreenTouch || event is InputEventScreenDrag: - if event.index != 0: - return - - if event is InputEventScreenTouch && (pointer.is_active == false || pointer.index == event.index): - var touch := event as InputEventScreenTouch - pointer.is_active = event.pressed - pointer.current_position = get_global_mouse_position() # touch.position - if pointer.is_active: - pointer.index = touch.index - pointer.initial_position = get_global_mouse_position() # touch.position - anchor = self.target_scroll_bar.value - else: - if pointer.was_dragged == false: # Click detected. - simulate_gui_click() - pointer.index = -1 - pointer.was_dragged = false - - if event is InputEventScreenDrag && event.index == pointer.index: - var drag := event as InputEventScreenDrag - pointer.current_position = get_global_mouse_position() # drag.position - pointer.velocity = drag.speed - pointer.relative_position = drag.relative - if pointer.current_position.distance_to(pointer.initial_position) / screen_dpcm > DRAG_THRESHOLD_CM: - pointer.was_dragged = true - - -func simulate_gui_click(): - - var position := self.get_global_mouse_position() - click_target.rect_global_position - - var event_touch := InputEventScreenTouch.new() - event_touch.index = 0 - event_touch.position = position - - var event_mouse := InputEventMouseButton.new() - event_mouse.button_index = BUTTON_LEFT - event_mouse.button_mask = BUTTON_MASK_LEFT - event_mouse.position = position - - event_mouse.pressed = true - event_touch.pressed = true - click_target._gui_input(event_mouse) - click_target._gui_input(event_touch) - - click_target.grab_focus() - - event_mouse.pressed = false - event_touch.pressed = false - click_target._gui_input(event_mouse) - click_target._gui_input(event_touch) - - -- cgit v1.2.3