aboutsummaryrefslogtreecommitdiff
path: root/date_picker/value_picker.gd
blob: d25a143ae4baaf616fc99379acfad8578c6dd41d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
extends Control

class_name ValuePicker

const VELOCITY_DECAYING_FACTOR: float = 5.5
const BINNING_THRESHOLD: float = 0.75
const BINNING_ADJUST_P: float = 5.0

export var min_value: int
export var max_value: int

onready var input: LineEdit			= get_node("current/input")
onready var label_previous: Label 	= get_node("previous")
onready var label_current: Label	= get_node("current")
onready var label_next: Label		= get_node("next")

var pointer: Dictionary
var anchor: float
var value: int
var offset: float

var scroll_unit_height: float
var label_previous_base_position: float
var label_current_base_position: float
var label_next_base_position: float


func _ready():
	pointer = {
		index = -1,
		initial_position = Vector2.ZERO,
		current_position = Vector2.ZERO,
		velocity = Vector2.ZERO,
		was_dragged = false,
		is_active = false,
	}
	
	input.connect("text_entered", self, "input_text_entered")
	input.connect("focus_entered", self, "input_focus_entered")
	input.connect("focus_exited", self, "input_focus_exited")
	
	scroll_unit_height =  label_current.rect_size.y
	label_previous_base_position = label_previous.rect_position.y
	label_current_base_position = label_current.rect_position.y
	label_next_base_position = label_next.rect_position.y
	
	value = min_value
	offset = 0.0


func _process(delta: float):
	
	if pointer.is_active:
		var dragged_distance: float = - (pointer.current_position.y - pointer.initial_position.y)
		offset = anchor + (dragged_distance / scroll_unit_height)
		value = int(offset)
		offset -= value
	else:
		pointer.velocity *= clamp((1.0 - VELOCITY_DECAYING_FACTOR * delta), 0.0, 1.0)
		offset -= pointer.velocity.y * delta / scroll_unit_height
		if abs(pointer.velocity.y) < BINNING_THRESHOLD * scroll_unit_height:
			offset -= offset * BINNING_ADJUST_P * delta
	
	# Using 'offset * 2.0' (equivalent to 'offset / 0.5') rounds the value based on 0.5 units.
	var cummulative_displacement := int(offset * 2.0)
	value = wrapi(value + cummulative_displacement, min_value, max_value + 1)
	offset -= cummulative_displacement
	
	label_current.text = "%d" % value
	label_next.text = "%d" % wrapi(value + 1, min_value, max_value + 1)
	label_previous.text = "%d" % wrapi(value - 1, min_value, max_value + 1)
	
	var offset_position := offset * scroll_unit_height
	label_current.rect_position.y = label_current_base_position - offset_position
	label_previous.rect_position.y = label_previous_base_position - offset_position
	label_next.rect_position.y = label_next_base_position - offset_position
	
	label_previous.modulate.a = 0.5 - offset
	label_next.modulate.a = offset + 0.5


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 = touch.position
		if pointer.is_active:
			input.release_focus()
			pointer.index = touch.index
			pointer.initial_position = touch.position
			anchor = value + offset
		else:
			if pointer.was_dragged == false: # Click detected.
				input.grab_focus()
			pointer.index = -1
			pointer.was_dragged = false
	
	if event is InputEventScreenDrag && event.index == pointer.index:
		var drag := event as InputEventScreenDrag
		pointer.current_position = drag.position
		pointer.velocity = drag.speed
		pointer.was_dragged = true


func input_text_entered(new_text: String):
	input.release_focus()


func input_focus_entered():
	input.text = "%d" % value
	input.visible = true
	input.select_all()


func input_focus_exited():
	if input.text.is_valid_integer():
		value = wrapi(int(input.text), min_value, max_value)
	input.visible = false