Skip to content

Commit abbbc04

Browse files
committed
Code organization and modularization and documentation
1 parent 00480bb commit abbbc04

20 files changed

Lines changed: 1808 additions & 1184 deletions

addons/SmoothScroll/SmoothScrollContainer.gd

Lines changed: 0 additions & 1082 deletions
This file was deleted.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
class_name ScrollDebugger
2+
## Static utility class for [SmoothScrollContainer] debug visualization.
3+
##
4+
## This class will progvide debug drawing functions for dev use.
5+
6+
7+
## Debug gradient for visual debugging (green = safe, red = overdrag)
8+
static var debug_gradient: Gradient = null
9+
10+
11+
## Sets up the gradient for debug visualization
12+
static func setup_debug_drawing() -> void:
13+
if debug_gradient == null:
14+
debug_gradient = Gradient.new()
15+
debug_gradient.set_color(0.0, Color.GREEN)
16+
debug_gradient.set_color(1.0, Color.RED)
17+
18+
19+
## Draws debug visualization showing overdrag distances and velocity.
20+
## [param container] - The SmoothScrollContainer to draw debug info for
21+
static func draw_debug(container: SmoothScrollContainer) -> void:
22+
if not container.content_node: return
23+
24+
# Calculate the size difference between container and content_node
25+
var spare_size: Vector2 = ScrollLayout.get_spare_size(container, container.content_margins)
26+
var size_diff: Vector2 = ScrollLayout.get_child_size_diff(
27+
container.content_node,
28+
spare_size,
29+
false,
30+
false
31+
)
32+
33+
# Calculate distance to left, right, top and bottom
34+
var boundary_dist: Vector4 = ScrollLayout.get_boundary_dist(
35+
container.pos,
36+
size_diff
37+
)
38+
var bottom_distance: float = boundary_dist.w
39+
var top_distance: float = boundary_dist.z
40+
var right_distance: float = boundary_dist.y
41+
var left_distance: float = boundary_dist.x
42+
43+
# Overdrag lines
44+
# Top + Bottom
45+
container.draw_line(
46+
Vector2(0.0, 0.0),
47+
Vector2(0.0, top_distance),
48+
debug_gradient.sample(clamp(top_distance / container.size.y, 0.0, 1.0)),
49+
5.0
50+
)
51+
container.draw_line(
52+
Vector2(0.0, container.size.y),
53+
Vector2(0.0, container.size.y + bottom_distance),
54+
debug_gradient.sample(clamp(-bottom_distance / container.size.y, 0.0, 1.0)),
55+
5.0
56+
)
57+
58+
# Left + Right
59+
container.draw_line(
60+
Vector2(0.0, container.size.y),
61+
Vector2(left_distance, container.size.y),
62+
debug_gradient.sample(clamp(left_distance / container.size.y, 0.0, 1.0)),
63+
5.0
64+
)
65+
container.draw_line(
66+
Vector2(container.size.x, container.size.y),
67+
Vector2(container.size.x + right_distance, container.size.y),
68+
debug_gradient.sample(clamp(-right_distance / container.size.y, 0.0, 1.0)),
69+
5.0
70+
)
71+
72+
# Velocity lines
73+
var origin := Vector2(5.0, container.size.y / 2)
74+
container.draw_line(
75+
origin,
76+
origin + Vector2(0.0, container.velocity.y * 0.01),
77+
debug_gradient.sample(clamp(container.velocity.y * 2 / container.size.y, 0.0, 1.0)),
78+
5.0
79+
)
80+
container.draw_line(
81+
origin,
82+
origin + Vector2(0.0, container.velocity.x * 0.01),
83+
debug_gradient.sample(clamp(container.velocity.x * 2 / container.size.x, 0.0, 1.0)),
84+
5.0
85+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://da87uxk9lw1en
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
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)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://cw0ipi7ukmd8y

0 commit comments

Comments
 (0)