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.", ] func _init(): font = get_font("font") connect("resized", self, "build_labels") 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) # @DAM This is only used once so, why not inline it? func max_required_labels() -> int: var max_labels := ceil(rect_size.y / font.get_height()) + 1 return int(min(items.size(), max_labels)) 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 := rect_size.x limit = labels.rect_size.x # labels_positions.append(0) for it in items: var size := font.get_wordwrap_string_size(it, limit).y var lines = size / font.get_height() var height = size + font.get_descent() * lines position += height labels_positions.append(position) labels_sizes.append(height) vscrollbar.raise() 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) # offset_idx = 0 var idx := 0 for label in labels.get_children(): if idx + offset_idx >= items.size(): label.text = "" 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("> %s | %5.3f > %s > %d | lastPos: %s" % [labels.size(), offset, bs_value, offset_idx, labels_positions[-1]]) labels.margin_right = -vscrollbar.rect_size.x if vscrollbar.visible else 0.0