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