aboutsummaryrefslogtreecommitdiff
path: root/option_set/option_set_list.gd
blob: 3cff78d84b827edd2432d40151d83fa9f345a291 (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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
extends Control

var vscrollbar:			VScrollBar
var labels:				Control
var labels_positions:	Array
var labels_sizes:		Array
var font:				Font

var items: Array = [
	"item 1",
	"item 22",
	"item 333",
	"item 4444",
	"item 55555",
	"item 666666",
	"item 7777777",
	"item 88888888",
	"item 999999999",
	"This is the longest item of all, but eventually stops.",
	"item 1",
	"item 22",
	"item 333",
	"item 4444",
	"item 55555",
	"item 666666",
	"item 7777777",
	"item 88888888",
	"item 999999999",
	"This is the longest item of all, but eventually stops.",
	"item 1",
	"item 22",
	"item 333",
	"item 4444",
	"item 55555",
	"item 666666",
	"item 7777777",
	"item 88888888",
	"item 999999999",
	"This is the longest item of all, but eventually stops.",
]

# @DAM List of ideas to implement on this element:
# - Allow to toggle word-wrap on or off;
# - Allow to change items;
# - Only build and process labels when needed;

func _init():
	labels = Control.new()
	labels.anchor_right = 1.0
	labels.anchor_bottom = 1.0
	add_child(labels)
	
	vscrollbar = VScrollBar.new()
	vscrollbar.anchor_left		= 1.0
	vscrollbar.anchor_bottom	= 1.0
	vscrollbar.grow_horizontal = Control.GROW_DIRECTION_BEGIN
	vscrollbar.name = "vscrollbar"
	add_child(vscrollbar)
	
	font =  get_font("font")
	connect("resized", self, "build_labels")


# @DAM We connect build_labels to resized during init. This calls build_labels immediately and the
# labels.rect_size is still not properly set thus, making the max_required_labels to return an
# incorrect value. To patch this, we are calling build_labels again on the _ready. Maybe we can sort
# this out in a cleaner way?
func _ready():
	build_labels()



# @DAM This is only used once so, why not inline it?
func max_required_labels() -> int:
	var max_labels := ceil(labels.rect_size.y / get_line_height()) + 1
	return int(min(items.size(), max_labels))


func get_line_height() -> float:
	return font.get_height() + font.get_descent()


func build_labels():
	var new_max_required_labels := max_required_labels()
	var delta := new_max_required_labels - labels.get_child_count()

	while delta > 0:
		var label := Label.new()
		label.autowrap = true
		label.anchor_left = 0.0
		label.anchor_right = 1.0
		label.valign = Label.VALIGN_TOP
		labels.add_child(label)
		delta -= 1
	
	while delta < 0:
		var label := labels.get_child(0) as Label
		labels.remove_child(label)
		label.free()
		delta += 1


func process_labels():
	labels_positions.clear()
	labels_sizes.clear()
	var position := 0.0
	var limit := labels.rect_size.x
	for it in items:
		var size := font.get_wordwrap_string_size(it, limit).y
		
		# The get_wordwrap_string_size does not include the vertical spacing for the last line, thus
		# we need to include it manually.
		# Method 1
#		var lines = size / font.get_height()
#		var height = size + font.get_descent() * lines
		# Method 2
		var height = get_line_height() * ceil(size / get_line_height())
		# Method 3 - not correct
#		var height = size + font.get_descent()

		position += height
		labels_positions.append(position)
		labels_sizes.append(height)


func _process(delta):
	
	# @DAM We are recalculating the labels size and position on every update.
	# This only has to be done on resize or when items change.
	process_labels()
	
	vscrollbar.min_value = 0
	vscrollbar.max_value = rect_size.y
	var ratio := rect_size.y / float(labels_positions[labels_positions.size()-1])
	vscrollbar.visible = ratio < 1.0
	vscrollbar.page = ratio * (vscrollbar.max_value - vscrollbar.min_value)
	
	var offset := vscrollbar.value
	var bs_value := offset / ratio
	var offset_idx := labels_positions.bsearch(bs_value)

	var wasted := 0 # @DAM To be removed.
	var idx := 0
	for label in labels.get_children():
		if idx + offset_idx >= items.size():
			label.text = ""
			wasted += 1
			continue
#			break # @DAM Or should we use continue?
		label.text = items[idx + offset_idx]
		label.rect_position.y = labels_positions[idx + offset_idx] - labels_sizes[idx + offset_idx] - bs_value
		idx += 1
	
	if Engine.get_idle_frames() % 30 == 1:
		print_debug("Wasted: %s" % wasted)
	
	labels.margin_right = -vscrollbar.rect_size.x if vscrollbar.visible else 0.0