extends ScrollContainer signal page_changed(index: int) const SNAP_DURATION := 0.25 const TEX_DEFAULT := preload("res://assets/graphics/gui/lobby/bar_default.svg") const TEX_ACTIVE := preload("res://assets/graphics/gui/lobby/bar_active.svg") @export var auto_advance_enabled: bool = true @export var auto_advance_delay: float = 4.0 @onready var _track: HBoxContainer = $CardMargin/HBoxContainer @onready var _page_dots: Array = get_node_or_null("../PageIndicator").get_children() if get_node_or_null("../PageIndicator") else [] var _dragging := false var _drag_start_pos := 0.0 var _drag_start_scroll := 0 var _snap_tween: Tween var _auto_timer: Timer var _last_reported_page := -1 func _ready() -> void: _update_indicator() _setup_auto_timer() func _setup_auto_timer() -> void: _auto_timer = Timer.new() _auto_timer.name = "AutoAdvanceTimer" _auto_timer.one_shot = true _auto_timer.timeout.connect(_on_auto_timer_timeout) add_child(_auto_timer) _restart_auto_timer() func _restart_auto_timer() -> void: if not auto_advance_enabled or _dragging or _card_count() <= 1: return _auto_timer.stop() _auto_timer.wait_time = auto_advance_delay _auto_timer.start() func _on_auto_timer_timeout() -> void: if _dragging: return var next_index := (_current_page() + 1) % _card_count() _animate_to_page(next_index) func _exit_tree() -> void: if _auto_timer: _auto_timer.stop() if _snap_tween: _snap_tween.kill() func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: if event.pressed: _start_drag(event.position.x) elif _dragging: _end_drag() elif event is InputEventMouseMotion and _dragging: _update_drag(event.position.x) elif event is InputEventScreenTouch and event.index == 0: if event.pressed: _start_drag(event.position.x) elif _dragging: _end_drag() elif event is InputEventScreenDrag and event.index == 0 and _dragging: _update_drag(event.position.x) func _start_drag(x: float) -> void: if _snap_tween: _snap_tween.kill() _auto_timer.stop() _dragging = true _drag_start_pos = x _drag_start_scroll = scroll_horizontal func _update_drag(x: float) -> void: var delta := _drag_start_pos - x scroll_horizontal = clampi(_drag_start_scroll + int(delta), 0, _max_scroll()) _update_indicator() func _end_drag() -> void: _dragging = false _snap_to_nearest_card() func _max_scroll() -> int: var bar := get_h_scroll_bar() return maxi(0, int(bar.max_value - bar.page)) func _card_count() -> int: return maxi(1, _track.get_child_count()) func _card_step() -> float: var count := _card_count() if count <= 1: return 0.0 return float(_max_scroll()) / float(count - 1) func _current_page() -> int: var step := _card_step() if step <= 0.0: return 0 return clampi(roundi(scroll_horizontal / step), 0, _card_count() - 1) func _snap_to_nearest_card() -> void: _animate_to_page(_current_page()) func _animate_to_page(target_index: int) -> void: target_index = clampi(target_index, 0, _card_count() - 1) var step := _card_step() var target_scroll := int(round(target_index * step)) if _snap_tween: _snap_tween.kill() _snap_tween = create_tween().set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) _snap_tween.tween_method(_set_scroll_and_indicator, float(scroll_horizontal), float(target_scroll), SNAP_DURATION) _snap_tween.finished.connect(_restart_auto_timer) func _set_scroll_and_indicator(value: float) -> void: scroll_horizontal = int(round(value)) _update_indicator() func _update_indicator() -> void: if _page_dots.is_empty(): return var active_index := _current_page() for i in _page_dots.size(): var dot: TextureRect = _page_dots[i] dot.texture = TEX_ACTIVE if i == active_index else TEX_DEFAULT if active_index != _last_reported_page: _last_reported_page = active_index page_changed.emit(active_index)