diff options
| author | dam <dam@gudinoff> | 2022-04-10 08:32:49 +0000 |
|---|---|---|
| committer | dam <dam@gudinoff> | 2022-04-10 08:32:49 +0000 |
| commit | 2d7da6bbc23fb917dfe931eaeb5566da0a102ac3 (patch) | |
| tree | ae4d7541267dd1a7a5633ff79ac7de1e9055437b /ui/option_set/option_set_list.gd | |
| parent | 75791aecbff0d8adc1011f45a69877cabff616e0 (diff) | |
| download | surgery-log-2d7da6bbc23fb917dfe931eaeb5566da0a102ac3.tar.zst surgery-log-2d7da6bbc23fb917dfe931eaeb5566da0a102ac3.zip | |
Code cleanup.
Diffstat (limited to 'ui/option_set/option_set_list.gd')
| -rw-r--r-- | ui/option_set/option_set_list.gd | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/ui/option_set/option_set_list.gd b/ui/option_set/option_set_list.gd new file mode 100644 index 0000000..fda2c04 --- /dev/null +++ b/ui/option_set/option_set_list.gd @@ -0,0 +1,294 @@ +extends Control +class_name OptionSetList + +const POINTER_VELOCITY_DECAYING_FACTOR: float = PI +const POINTER_VELOCITY_BOOST_FACTOR: float = 1.25 +const EXACT_SELECTION: bool = false + +signal selection_changed # (idx: int, text: String) + +export var clear_signals_on_hide := true +export var autowrap := true +export var separation := 25 + +var v_scroll_bar : VScrollBar +var sensor : PointerInputSensor +var is_pointer_dragging := false +var pointer_drag_velocity := 0.0 +var when_last_dragged := 0 + +var labels : Control +var labels_end_positions : Array +var labels_sizes : Array + +var items : Array +var selected_idx := -1 +var selected_item := "" +var is_dirty := true + +var normal_style : StyleBoxFlat +var selected_style : StyleBoxFlat +var border_size := 5 + +onready var font : Font = get_font("font") +onready var screen_dpcm : float = float(OS.get_screen_dpi()) / 2.54 + +func _init(): + self.anchor_right = 1.0 + self.anchor_bottom = 1.0 + self.rect_clip_content = true + self.connect("hide", self, "_clear_signals") + + labels = Control.new() + labels.anchor_right = 1.0 + labels.anchor_bottom = 1.0 + labels.mouse_filter = Control.MOUSE_FILTER_IGNORE + labels.name = "labels" + add_child(labels) + + v_scroll_bar = VScrollBar.new() + v_scroll_bar.anchor_left = 1.0 + v_scroll_bar.anchor_bottom = 1.0 + v_scroll_bar.grow_horizontal = Control.GROW_DIRECTION_BEGIN + v_scroll_bar.name = "v_scroll_bar" + add_child(v_scroll_bar) + + sensor = PointerInputSensor.new() + sensor.anchor_right = 1.0 + sensor.anchor_bottom = 1.0 + sensor.name = "sensor" + add_child(sensor) + + connect("resized", self, "mark_as_dirty") + + sensor.connect("on_press", self, "pointer_input_on_press_handler") + sensor.connect("on_drag", self, "pointer_input_on_drag_handler") + sensor.connect("on_end_drag", self, "pointer_input_on_end_drag_handler") + sensor.connect("on_click", self, "pointer_input_on_click_handler") + sensor.connect("on_scroll", self, "pointer_input_on_scroll_handler") + + +func _ready(): + var button_style := get_stylebox("pressed", "Button") + var label_style := get_stylebox("normal", "Label") + + normal_style = StyleBoxFlat.new() + normal_style.border_width_bottom = border_size + normal_style.border_color = Color.white * 0.333 + normal_style.bg_color = Color.transparent + normal_style.content_margin_left = label_style.content_margin_left + normal_style.content_margin_top = label_style.content_margin_top + normal_style.content_margin_right = label_style.content_margin_right + + var bg_color := button_style.bg_color as Color + var corner_radius := button_style.corner_radius_top_right as int + selected_style = StyleBoxFlat.new() + selected_style.bg_color = bg_color + selected_style.corner_radius_top_left = corner_radius + selected_style.corner_radius_top_right = corner_radius + selected_style.corner_radius_bottom_right = corner_radius + selected_style.corner_radius_bottom_left = corner_radius + selected_style.content_margin_left = label_style.content_margin_left + selected_style.content_margin_top = label_style.content_margin_top + selected_style.content_margin_right = label_style.content_margin_right + + +func mark_as_dirty(): + is_dirty = true + + +func add_item(text: String): + items.append(text) + is_dirty = true + + +func add_items(texts: Array): + items.append_array(texts) + is_dirty = true + + +func set_item(index: int, text: String): + items[index] = text + is_dirty = true + + +func set_items(indices: Array, texts: Array): + for idx in indices.size(): + items[indices[idx]] = texts[idx] + is_dirty = true + + +func remove_item(index: int): + items.remove(index) + is_dirty = true + + +func remove_items(indices: Array): + indices.sort() + var idx := indices.size()-1 + while idx >= 0: + items.remove(indices[idx]) + idx -= 1 + is_dirty = true + + +func clear_items(): + items.clear() + selected_idx = -1 + selected_item = "" + v_scroll_bar.value = 0.0 + is_dirty = true + + +func build_labels(): + + var num_of_labels := int(min( + items.size(), + ceil(labels.rect_size.y / (font.get_height() + border_size)) + 1 + )) + + var delta := num_of_labels - labels.get_child_count() + + while delta > 0: + var label := Label.new() + label.autowrap = autowrap + 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 + + labels_end_positions.clear() + labels_sizes.clear() + + if num_of_labels == 0: + return + + var position := 0.0 + var proto_label := labels.get_child(0) as Label + var line_spacing := proto_label.get_constant("line_spacing") + var line_height := proto_label.get_line_height() + for it in items: + proto_label.text = it + var line_count := proto_label.get_line_count() + var height := line_count * line_height + (line_count - 1) * line_spacing + border_size + separation + + position += height + labels_end_positions.append(position) + labels_sizes.append(height) + + +func _process(delta): + + if is_dirty: + build_labels() + is_dirty = false + + var viewable_ratio := 1.0 + var viewable_height := labels.rect_size.y + if items.size() > 0: + viewable_ratio = viewable_height / float(labels_end_positions.back()) + + v_scroll_bar.min_value = 0 + v_scroll_bar.max_value = viewable_height / viewable_ratio + v_scroll_bar.visible = viewable_ratio < 1.0 + v_scroll_bar.page = viewable_height + + var labels_offset := v_scroll_bar.value + var idx_offset := labels_end_positions.bsearch(labels_offset) + + var idx := idx_offset + for label in labels.get_children(): + + if idx >= items.size(): + label.visible = false + continue + + label.visible = true + label.text = items[idx] + label.rect_size.y = labels_sizes[idx] + label.rect_position.y = labels_end_positions[idx] - labels_sizes[idx] - labels_offset + + if idx == selected_idx: + label.add_stylebox_override("normal", selected_style) + else: + label.add_stylebox_override("normal", normal_style) + + idx += 1 + + var right_margin = - v_scroll_bar.rect_size.x if v_scroll_bar.visible else 0.0 + labels.margin_right = right_margin + sensor.margin_right = right_margin + + # Apply drag movement inertia. + if is_pointer_dragging == false && abs(pointer_drag_velocity) > 0.5: + pointer_drag_velocity *= clamp((1.0 - POINTER_VELOCITY_DECAYING_FACTOR * delta), 0.0, 1.0) + v_scroll_bar.value -= pointer_drag_velocity * delta + + +func get_item_at_position(mouse_position: Vector2) -> int: + var labels_offset := v_scroll_bar.value + var position := mouse_position.y + labels_offset + var item_idx := labels_end_positions.bsearch(position) + + if item_idx == items.size(): + item_idx = -1 + return item_idx + + +func select(index: int): + selected_idx = index + selected_item = items[selected_idx] if selected_idx >= 0 && selected_idx < items.size() else "" + emit_signal("selection_changed", selected_idx, selected_item) + + +func unselect(): + select(-1) + + +func pointer_input_on_press_handler(pointer: PointerInputSensor.PointerInputData): + is_pointer_dragging = true + grab_focus() + + +func pointer_input_on_drag_handler(pointer: PointerInputSensor.PointerInputData): + is_pointer_dragging = true + var reported_velocity_abs := abs(pointer.velocity.y) + var relative_velocity := pointer.relative_position.y * Engine.get_frames_per_second() + var relative_velocity_abs := abs(relative_velocity) + pointer_drag_velocity = pointer.velocity.y if reported_velocity_abs > relative_velocity_abs else relative_velocity + v_scroll_bar.value -= pointer.relative_position.y + when_last_dragged = OS.get_ticks_msec() + + +func pointer_input_on_end_drag_handler(pointer: PointerInputSensor.PointerInputData): + is_pointer_dragging = false + if OS.get_ticks_msec() - when_last_dragged > 20: + pointer_drag_velocity = 0.0 + else: + pointer_drag_velocity *= POINTER_VELOCITY_BOOST_FACTOR + + +func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): + var item_idx := get_item_at_position(pointer.current_position - rect_global_position) + select(item_idx) + + +func pointer_input_on_scroll_handler(pointer: PointerInputSensor.PointerInputData): + pointer_drag_velocity -= pointer.scroll * POINTER_VELOCITY_BOOST_FACTOR * screen_dpcm + + +func _clear_signals(): + if clear_signals_on_hide == false: + return + + for signal_name in ["selection_changed"]: + for it in get_signal_connection_list(signal_name): + disconnect(it.signal, it.target, it.method) + + |
