aboutsummaryrefslogtreecommitdiff
path: root/date_picker/value_picker.gd
blob: b70f1aaf4925902a1296a94cba3433f9341fac94 (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
127
128
129
130
131
132
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
const DRAG_THRESHOLD_CM: float = 0.250

export var min_value: int
export var max_value: int

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

onready var input			:= get_node("current/input") as LineEdit
onready var label_previous	:= get_node("previous") as Label
onready var label_current	:= get_node("current") as Label
onready var label_next		:= get_node("next") as Label
onready var screen_dpcm		:= float(OS.get_screen_dpi()) / 2.54


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
		if pointer.current_position.distance_to(pointer.initial_position) / screen_dpcm > DRAG_THRESHOLD_CM:
			pointer.was_dragged = true


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


func input_focus_entered():
	pointer.velocity = Vector2.ZERO # Avoid changing to other value once entering input.
	input.text = "%d" % value
	input.visible = true
	input.select_all()
	label_current.self_modulate.a = 0.0


func input_focus_exited():
	if input.text.is_valid_integer():
		value = wrapi(int(input.text), min_value, max_value + 1)
	input.visible = false
	label_current.self_modulate.a = 1.0