|
| 1 | +class_name ScrollInputHandler |
| 2 | +extends RefCounted |
| 3 | +## Handles all input events for SmoothScrollContainer. |
| 4 | +## |
| 5 | +## Processes mouse wheel, dragging (mouse/touch), gestures, and scrollbar interactions. |
| 6 | + |
| 7 | + |
| 8 | +#region Variables |
| 9 | +## Reference to the parent scroll container |
| 10 | +var _container: SmoothScrollContainer = null |
| 11 | +## Mouse wheel scroll speed multiplier |
| 12 | +var speed: float = 1000.0 |
| 13 | +## Allow dragging with mouse |
| 14 | +var drag_with_mouse: bool = true |
| 15 | +## Allow dragging with touch |
| 16 | +var drag_with_touch: bool = true |
| 17 | +## Handle input events |
| 18 | +var handle_input: bool = true |
| 19 | +## Whether content is currently being dragged |
| 20 | +var content_dragging: bool = false |
| 21 | +## Whether content has moved during current drag |
| 22 | +var content_dragging_moved: bool = false |
| 23 | +## Whether touch point is in deadzone |
| 24 | +var is_in_deadzone: bool = false |
| 25 | +## When true, horizontal scrollbar is being dragged |
| 26 | +var h_scrollbar_dragging: bool = false |
| 27 | +## When true, vertical scrollbar is being dragged |
| 28 | +var v_scrollbar_dragging: bool = false |
| 29 | +## Whether mouse is on any scrollbar |
| 30 | +var mouse_on_scrollbar: bool = false |
| 31 | +## Drag state data: [0,1] relative accumulation, [2,3] start pos, [4-7] boundary distances |
| 32 | +var drag_temp_data: Array = [] |
| 33 | +#endregion |
| 34 | + |
| 35 | + |
| 36 | +## Initializes the input handler with a reference to the [param container]. |
| 37 | +func _init(container: SmoothScrollContainer) -> void: |
| 38 | + _container = container |
| 39 | + |
| 40 | + |
| 41 | +## Processes GUI input events for scrolling. [br] |
| 42 | +## Handles mouse wheel, dragging, pan gestures, and touch events from [param event]. |
| 43 | +func process_gui_input(event: InputEvent) -> void: |
| 44 | + # Show scrollbars on mouse motion |
| 45 | + if _container.hide_scrollbar_over_time and event is InputEventMouseMotion: |
| 46 | + _container.scrollbar_animator.show_scrollbars() |
| 47 | + |
| 48 | + # Mouse button events (wheel scrolling and drag start/end) |
| 49 | + if event is InputEventMouseButton: |
| 50 | + _process_mouse_button(event) |
| 51 | + |
| 52 | + # Drag motion events |
| 53 | + if (event is InputEventScreenDrag and drag_with_touch) \ |
| 54 | + or (event is InputEventMouseMotion and drag_with_mouse): |
| 55 | + _process_drag_motion(event) |
| 56 | + |
| 57 | + # Pan gesture events |
| 58 | + if event is InputEventPanGesture: |
| 59 | + _process_pan_gesture(event) |
| 60 | + |
| 61 | + # Touch events |
| 62 | + if event is InputEventScreenTouch: |
| 63 | + _process_screen_touch(event) |
| 64 | + |
| 65 | + # Mark input as handled if configured |
| 66 | + if handle_input: |
| 67 | + _container.get_tree().get_root().set_input_as_handled() |
| 68 | + |
| 69 | + |
| 70 | +## Processes scrollbar input events from [param event]. [br] |
| 71 | +## Handles both [param vertical] and horizontal scrollbar interactions. |
| 72 | +func process_scrollbar_input(event: InputEvent, vertical: bool) -> void: |
| 73 | + if event is InputEventMouseButton: |
| 74 | + # Forward wheel events to main input handler |
| 75 | + if event.button_index in [ |
| 76 | + MOUSE_BUTTON_WHEEL_DOWN, |
| 77 | + MOUSE_BUTTON_WHEEL_UP, |
| 78 | + MOUSE_BUTTON_WHEEL_LEFT, |
| 79 | + MOUSE_BUTTON_WHEEL_RIGHT |
| 80 | + ]: |
| 81 | + process_gui_input(event) |
| 82 | + |
| 83 | + # Handle scrollbar dragging |
| 84 | + if event.button_index == MOUSE_BUTTON_LEFT: |
| 85 | + _handle_scrollbar_drag_button(event, vertical) |
| 86 | + |
| 87 | + if event is InputEventScreenTouch: |
| 88 | + _handle_scrollbar_touch(event, vertical) |
| 89 | + |
| 90 | + |
| 91 | +## Called when mouse enters or exits scrollbar area based on [param entered]. |
| 92 | +func on_mouse_scrollbar(entered: bool) -> void: |
| 93 | + mouse_on_scrollbar = entered |
| 94 | + |
| 95 | + |
| 96 | +## Checks if any scrollbar is being dragged. |
| 97 | +func any_scrollbar_dragging() -> bool: |
| 98 | + return h_scrollbar_dragging or v_scrollbar_dragging |
| 99 | + |
| 100 | + |
| 101 | +## Initializes drag temporary data with current position and boundary distances. |
| 102 | +func init_drag_temp_data() -> void: |
| 103 | + var spare_size: Vector2 = ScrollLayout.get_spare_size(_container, _container.content_margins) |
| 104 | + var content_node_size_diff: Vector2 = ScrollLayout.get_child_size_diff( |
| 105 | + _container.content_node, |
| 106 | + spare_size, |
| 107 | + true, |
| 108 | + true |
| 109 | + ) |
| 110 | + var content_node_boundary_dist: Vector4 = ScrollLayout.get_boundary_dist( |
| 111 | + _container.pos, |
| 112 | + content_node_size_diff |
| 113 | + ) |
| 114 | + drag_temp_data = [ |
| 115 | + 0.0, # X relative accumulation |
| 116 | + 0.0, # Y relative accumulation |
| 117 | + _container.pos.x, # X start position |
| 118 | + _container.pos.y, # Y start position |
| 119 | + content_node_boundary_dist.x, # Left distance |
| 120 | + content_node_boundary_dist.y, # Right distance |
| 121 | + content_node_boundary_dist.z, # Top distance |
| 122 | + content_node_boundary_dist.w, # Bottom distance |
| 123 | + ] |
| 124 | + |
| 125 | + |
| 126 | +## Processes mouse button events for wheel scrolling and dragging from [param event]. |
| 127 | +func _process_mouse_button(event: InputEventMouseButton) -> void: |
| 128 | + match event.button_index: |
| 129 | + MOUSE_BUTTON_WHEEL_DOWN: |
| 130 | + if event.pressed: |
| 131 | + _handle_wheel_scroll(event, false, true) # Down direction |
| 132 | + MOUSE_BUTTON_WHEEL_UP: |
| 133 | + if event.pressed: |
| 134 | + _handle_wheel_scroll(event, true, true) # Up direction |
| 135 | + MOUSE_BUTTON_WHEEL_LEFT: |
| 136 | + if event.pressed: |
| 137 | + _handle_wheel_scroll(event, true, false) # Left direction |
| 138 | + MOUSE_BUTTON_WHEEL_RIGHT: |
| 139 | + if event.pressed: |
| 140 | + _handle_wheel_scroll(event, false, false) # Right direction |
| 141 | + MOUSE_BUTTON_LEFT: |
| 142 | + if event.pressed: |
| 143 | + _start_content_drag() |
| 144 | + else: |
| 145 | + _end_content_drag() |
| 146 | + |
| 147 | + |
| 148 | +## Handles mouse wheel scrolling from [param event]. [br] |
| 149 | +## Scrolls in [param positive] direction (up/left or down/right) for the specified axis ([param is_vertical]). |
| 150 | +func _handle_wheel_scroll(event: InputEventMouseButton, positive: bool, is_vertical: bool) -> void: |
| 151 | + _container.last_scroll_type = SmoothScrollContainer.SCROLL_TYPE.WHEEL |
| 152 | + _container.scroll_damper = _container.wheel_scroll_damper |
| 153 | + _container.scrollbar_animator.kill_scroll_tweens() |
| 154 | + |
| 155 | + var amount: float = speed * event.factor * (1.0 if positive else -1.0) |
| 156 | + |
| 157 | + # Determine which axis to scroll based on shift key and available scroll directions |
| 158 | + if is_vertical: |
| 159 | + if event.shift_pressed or not _container.should_scroll_vertical(): |
| 160 | + if _container.should_scroll_horizontal(): |
| 161 | + _container.velocity.x += amount |
| 162 | + else: |
| 163 | + if _container.should_scroll_vertical(): |
| 164 | + _container.velocity.y += amount |
| 165 | + else: # Horizontal wheel |
| 166 | + if event.shift_pressed: |
| 167 | + if _container.should_scroll_vertical(): |
| 168 | + _container.velocity.y += amount if positive else -amount |
| 169 | + else: |
| 170 | + if _container.should_scroll_horizontal(): |
| 171 | + _container.velocity.x += amount |
| 172 | + |
| 173 | + |
| 174 | +## Starts content dragging. |
| 175 | +func _start_content_drag() -> void: |
| 176 | + if not drag_with_mouse: return |
| 177 | + |
| 178 | + content_dragging = true |
| 179 | + is_in_deadzone = true |
| 180 | + _container.scroll_damper = _container.dragging_scroll_damper |
| 181 | + _container.last_scroll_type = SmoothScrollContainer.SCROLL_TYPE.DRAG |
| 182 | + init_drag_temp_data() |
| 183 | + _container.scrollbar_animator.kill_scroll_tweens() |
| 184 | + |
| 185 | + |
| 186 | +## Ends content dragging. |
| 187 | +func _end_content_drag() -> void: |
| 188 | + content_dragging = false |
| 189 | + is_in_deadzone = false |
| 190 | + |
| 191 | + |
| 192 | +## Processes drag motion events with relative movement from [param event]. |
| 193 | +func _process_drag_motion(event) -> void: |
| 194 | + if not content_dragging: return |
| 195 | + |
| 196 | + if _container.should_scroll_horizontal(): |
| 197 | + drag_temp_data[0] += event.relative.x |
| 198 | + if _container.should_scroll_vertical(): |
| 199 | + drag_temp_data[1] += event.relative.y |
| 200 | + |
| 201 | + _remove_all_children_focus(_container) |
| 202 | + _container.handle_content_dragging() |
| 203 | + |
| 204 | + |
| 205 | +## Processes pan gesture events from [param event]. |
| 206 | +func _process_pan_gesture(event: InputEventPanGesture) -> void: |
| 207 | + if _container.should_scroll_horizontal(): |
| 208 | + _container.velocity.x = -event.delta.x * speed |
| 209 | + _container.scrollbar_animator.kill_scroll_tweens() |
| 210 | + if _container.should_scroll_vertical(): |
| 211 | + _container.velocity.y = -event.delta.y * speed |
| 212 | + _container.scrollbar_animator.kill_scroll_tweens() |
| 213 | + |
| 214 | + |
| 215 | +## Processes screen touch events from [param event]. |
| 216 | +func _process_screen_touch(event: InputEventScreenTouch) -> void: |
| 217 | + if event.pressed: |
| 218 | + if not drag_with_touch: |
| 219 | + return |
| 220 | + |
| 221 | + content_dragging = true |
| 222 | + is_in_deadzone = true |
| 223 | + _container.scroll_damper = _container.dragging_scroll_damper |
| 224 | + _container.last_scroll_type = SmoothScrollContainer.SCROLL_TYPE.DRAG |
| 225 | + init_drag_temp_data() |
| 226 | + _container.scrollbar_animator.kill_scroll_tweens() |
| 227 | + else: |
| 228 | + content_dragging = false |
| 229 | + is_in_deadzone = false |
| 230 | + |
| 231 | + |
| 232 | +## Handles scrollbar dragging with mouse button from [param event]. [br] |
| 233 | +## Processes [param vertical] or horizontal scrollbar interactions. |
| 234 | +func _handle_scrollbar_drag_button(event: InputEventMouseButton, vertical: bool) -> void: |
| 235 | + if event.pressed: |
| 236 | + if vertical: |
| 237 | + v_scrollbar_dragging = true |
| 238 | + else: |
| 239 | + h_scrollbar_dragging = true |
| 240 | + _container.last_scroll_type = SmoothScrollContainer.SCROLL_TYPE.BAR |
| 241 | + _container.scrollbar_animator.kill_scroll_tweens() |
| 242 | + else: |
| 243 | + if vertical: |
| 244 | + v_scrollbar_dragging = false |
| 245 | + else: |
| 246 | + h_scrollbar_dragging = false |
| 247 | + |
| 248 | + |
| 249 | +## Handles scrollbar dragging with touch from [param event]. [br] |
| 250 | +## Processes [param vertical] or horizontal scrollbar interactions. |
| 251 | +func _handle_scrollbar_touch(event: InputEventScreenTouch, vertical: bool) -> void: |
| 252 | + if event.pressed: |
| 253 | + if vertical: |
| 254 | + v_scrollbar_dragging = true |
| 255 | + else: |
| 256 | + h_scrollbar_dragging = true |
| 257 | + _container.last_scroll_type = SmoothScrollContainer.SCROLL_TYPE.BAR |
| 258 | + _container.scrollbar_animator.kill_scroll_tweens() |
| 259 | + else: |
| 260 | + if vertical: |
| 261 | + v_scrollbar_dragging = false |
| 262 | + else: |
| 263 | + h_scrollbar_dragging = false |
| 264 | + |
| 265 | + |
| 266 | +## Recursively removes focus from the specified [param node] and all its children. |
| 267 | +func _remove_all_children_focus(node: Node) -> void: |
| 268 | + if node is Control: |
| 269 | + var control := node as Control |
| 270 | + control.release_focus() |
| 271 | + |
| 272 | + for child: Node in node.get_children(): |
| 273 | + _remove_all_children_focus(child) |
0 commit comments