diff options
| -rw-r--r-- | date_picker/date_picker.tscn | 3 | ||||
| -rw-r--r-- | logic/database.gd | 2 | ||||
| -rw-r--r-- | logic/popup.gd | 36 | ||||
| -rw-r--r-- | logic/popup_list.gd | 28 | ||||
| -rw-r--r-- | main.gd | 6 | ||||
| -rw-r--r-- | main.tscn | 28 | ||||
| -rw-r--r-- | option_set/option_set.gd | 39 | ||||
| -rw-r--r-- | option_set/option_set.tscn | 3 | ||||
| -rw-r--r-- | option_set/option_set_list.gd | 293 | ||||
| -rw-r--r-- | option_set/option_set_list.tscn | 1 | ||||
| -rw-r--r-- | project.godot | 18 | ||||
| -rw-r--r-- | touch_item_list/touch_item_list.gd | 1 | ||||
| -rw-r--r-- | touch_item_list/touch_item_list.tscn | 3 |
13 files changed, 290 insertions, 171 deletions
diff --git a/date_picker/date_picker.tscn b/date_picker/date_picker.tscn index 54b2b82..8b082f2 100644 --- a/date_picker/date_picker.tscn +++ b/date_picker/date_picker.tscn @@ -7,9 +7,6 @@ anchor_right = 1.0 anchor_bottom = 1.0 script = ExtResource( 2 ) -__meta__ = { -"_edit_use_anchors_": false -} [node name="year" type="Control" parent="."] anchor_right = 0.333 diff --git a/logic/database.gd b/logic/database.gd index c4482f4..370a679 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -118,7 +118,7 @@ func save_stage(entry: Dictionary): next_selected_idx = staged_idx else: db.append(entry) - add_item(get_entry_view(entry)) + self.add_item(get_entry_view(entry)) next_selected_idx = db.size() - 1 select(next_selected_idx) diff --git a/logic/popup.gd b/logic/popup.gd new file mode 100644 index 0000000..1b1813a --- /dev/null +++ b/logic/popup.gd @@ -0,0 +1,36 @@ +extends Control +class_name ModalPopup + +var control : Control + + +func _init(): + anchor_right = 1.0 + anchor_bottom = 1.0 + +func popup_control(item: Control): + control = item + control.connect("visibility_changed", self, "closed") + var background = $background + control.anchor_left = background.anchor_left + control.anchor_top = background.anchor_top + control.anchor_right = background.anchor_right + control.anchor_bottom = background.anchor_bottom + control.margin_left = 20 + control.margin_top = 20 + control.margin_right = -20 + control.margin_bottom = -20 + add_child(control) + show() + control.show() +# self.visible = true + + +func closed(): + if control.visible == true: + return + control.disconnect("visibility_changed", self, "closed") + remove_child(control) + self.hide() + + diff --git a/logic/popup_list.gd b/logic/popup_list.gd deleted file mode 100644 index 7e049fb..0000000 --- a/logic/popup_list.gd +++ /dev/null @@ -1,28 +0,0 @@ -extends Popup -class_name PopupList - -signal item_selected - -onready var item_list := get_node("list") as TouchItemList -onready var blur := get_node("blur") as ColorRect - - -func _ready(): - item_list.connect("item_selected", self, "selected") - - -func popup_options(options: Array): - item_list.v_scroll_bar.value = 0.0 - item_list.clear() - for it in options: - item_list.add_item(it) - self.popup_centered_ratio(0.9) - blur.rect_global_position = Vector2.ZERO - blur.rect_size = get_viewport_rect().size - - -func selected(index: int): - self.hide() - emit_signal("item_selected", index) - - @@ -34,6 +34,12 @@ func _process(delta: float): for it in controls_sensible_to_keyboard: it.margin_bottom = -keyboard_height + # ~1650 fps with database using ItemList + Engine.target_fps = 0 + OS.vsync_enabled = false + OS.set_window_title("%03d" % [Engine.get_frames_per_second()]) + return + if power_throttle_timeout > 0.0: power_throttle_timeout -= delta else: @@ -1,4 +1,4 @@ -[gd_scene load_steps=12 format=2] +[gd_scene load_steps=13 format=2] [ext_resource path="res://main.gd" type="Script" id=1] [ext_resource path="res://date_picker/date_picker.tscn" type="PackedScene" id=2] @@ -7,8 +7,9 @@ [ext_resource path="res://logic/database.gd" type="Script" id=5] [ext_resource path="res://fonts/font_icons.tres" type="DynamicFont" id=6] [ext_resource path="res://option_set/option_set.tscn" type="PackedScene" id=7] +[ext_resource path="res://option_set/option_set_list.gd" type="Script" id=8] [ext_resource path="res://logic/stage.gd" type="Script" id=9] -[ext_resource path="res://logic/popup_list.gd" type="Script" id=10] +[ext_resource path="res://logic/popup.gd" type="Script" id=10] [ext_resource path="res://touch_item_list/touch_item_list.tscn" type="PackedScene" id=11] [ext_resource path="res://touch_vertical_container/touch_vertical_container.tscn" type="PackedScene" id=12] @@ -17,9 +18,6 @@ anchor_right = 1.0 anchor_bottom = 1.0 theme = ExtResource( 3 ) script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} [node name="database" parent="." instance=ExtResource( 11 )] script = ExtResource( 5 ) @@ -246,19 +244,23 @@ window_title = "CONFIRM ACTION" dialog_text = "Do you confirm this action?" dialog_autowrap = true -[node name="popup_list" type="Popup" parent="."] +[node name="popup" type="ColorRect" parent="."] +visible = false anchor_right = 1.0 anchor_bottom = 1.0 -popup_exclusive = true +color = Color( 0, 0, 0, 0.501961 ) script = ExtResource( 10 ) -[node name="blur" type="ColorRect" parent="popup_list"] -show_behind_parent = true -anchor_right = 1.0 -anchor_bottom = 1.0 -color = Color( 0, 0, 0, 0.75 ) +[node name="background" type="Panel" parent="popup"] +anchor_left = 0.05 +anchor_top = 0.025 +anchor_right = 0.95 +anchor_bottom = 0.975 -[node name="list" parent="popup_list" instance=ExtResource( 11 )] +[node name="option_set_list" type="Control" parent="."] +visible = false +rect_clip_content = true +script = ExtResource( 8 ) [node name="debug" type="Label" parent="."] visible = false diff --git a/option_set/option_set.gd b/option_set/option_set.gd index 62d10d0..040566a 100644 --- a/option_set/option_set.gd +++ b/option_set/option_set.gd @@ -1,7 +1,6 @@ extends Control class_name OptionSet -var options: Array var text: String setget set_text, get_text func set_text(var value: String): @@ -10,9 +9,12 @@ func set_text(var value: String): func get_text() -> String: return input.text +var selected_idx: int + onready var input := get_node("input") as LineEdit onready var button := get_node("options") as Button # @DAM Maybe rename this. Also requires renaming on stage. -onready var popup := get_node("/root/main/popup_list") as PopupList +onready var popup := get_node("/root/main/popup") as ModalPopup +onready var options := get_node("/root/main/option_set_list") as OptionSetList func _ready(): @@ -23,14 +25,31 @@ func _ready(): func show_options(options_array: Array): - options = options_array - popup.connect("item_selected", self, "option_selected", [], CONNECT_ONESHOT) - popup.popup_options(options) + options.clear_items() + var main = get_node("/root/main") + main.remove_child(options) + options.add_items(options_array) + if options_array[selected_idx] == input.text: + options.select(selected_idx) + else: + options.unselect() + options.connect("item_selected", self, "option_selected", [], CONNECT_ONESHOT) + popup.popup_control(options) +# popup.connect("item_selected", self, "option_selected", [], CONNECT_ONESHOT) +# popup.clear_items() +# popup_opt.visible = true +# get_node("/root/main/popup/blur") +# popup.add_items(options) + + +func option_selected(index: int, text: String): + selected_idx = index + input.text = text + input.caret_position = input.max_length + button.release_focus() + options.visible = false + var main = get_node("/root/main") + main.add_child(options) -func option_selected(index: int): - if index >= 0 && index < options.size(): - input.text = options[index] - input.caret_position = input.max_length - button.release_focus() diff --git a/option_set/option_set.tscn b/option_set/option_set.tscn index bf15806..8864e45 100644 --- a/option_set/option_set.tscn +++ b/option_set/option_set.tscn @@ -6,9 +6,6 @@ anchor_right = 1.0 anchor_bottom = 1.0 script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} [node name="input" type="LineEdit" parent="."] anchor_right = 1.0 diff --git a/option_set/option_set_list.gd b/option_set/option_set_list.gd index 3cff78d..cad9e9c 100644 --- a/option_set/option_set_list.gd +++ b/option_set/option_set_list.gd @@ -1,93 +1,115 @@ extends Control +class_name OptionSetList + +signal item_selected # (idx: int, text: String) +signal nothing_selected # () + +export var autowrap := true + +var vscrollbar : VScrollBar +var labels : Control +var labels_end_positions: Array +var labels_sizes : Array + +var items : Array +var selected_idx := -1 +var is_dirty := true + +var normal_style : StyleBoxFlat +var selected_style : StyleBoxFlat +var border_size := 5 + +onready var font : Font = get_font("font") -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(): + self.anchor_right = 1.0 + self.anchor_bottom = 1.0 + self.rect_clip_content = true + labels = Control.new() - labels.anchor_right = 1.0 - labels.anchor_bottom = 1.0 + labels.anchor_right = 1.0 + labels.anchor_bottom = 1.0 + labels.mouse_filter = Control.MOUSE_FILTER_IGNORE + labels.name = "labels" 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" + vscrollbar.grow_horizontal = Control.GROW_DIRECTION_BEGIN + vscrollbar.name = "vscrollbar" add_child(vscrollbar) - font = get_font("font") - connect("resized", self, "build_labels") + 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 + + selected_style = StyleBoxFlat.new() + selected_style.border_width_bottom = border_size + selected_style.border_color = Color.white * 0.333 + selected_style.bg_color = Color.dimgray + + connect("resized", self, "mark_as_dirty") -# @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() +func mark_as_dirty(): + is_dirty = true +func add_item(text: String): + items.append(text) + is_dirty = true -# @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 add_items(texts: Array): + items.append_array(texts) + is_dirty = true -func get_line_height() -> float: - return font.get_height() + font.get_descent() +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 build_labels(): - var new_max_required_labels := max_required_labels() - var delta := new_max_required_labels - labels.get_child_count() +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() + 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 = true - label.anchor_left = 0.0 + label.autowrap = autowrap label.anchor_right = 1.0 label.valign = Label.VALIGN_TOP labels.add_child(label) @@ -98,61 +120,124 @@ func build_labels(): labels.remove_child(label) label.free() delta += 1 - - -func process_labels(): - labels_positions.clear() + + labels_end_positions.clear() labels_sizes.clear() - var position := 0.0 - var limit := labels.rect_size.x + + 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: - var size := font.get_wordwrap_string_size(it, limit).y + 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 - # 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_end_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() + if is_dirty: + build_labels() + is_dirty = false + + var ratio := 1.0 + + if items.size() > 0: + ratio = vscrollbar.max_value / float(labels_end_positions.back()) vscrollbar.min_value = 0 - vscrollbar.max_value = rect_size.y - var ratio := rect_size.y / float(labels_positions[labels_positions.size()-1]) + vscrollbar.max_value = labels.rect_size.y 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 + var scrollbar_offset := vscrollbar.value + var labels_offset := scrollbar_offset / ratio + var idx_offset := labels_end_positions.bsearch(labels_offset) + + var idx := idx_offset for label in labels.get_children(): - if idx + offset_idx >= items.size(): - label.text = "" - wasted += 1 + + if idx >= items.size(): + label.visible = false 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 + + 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 - 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 + +func get_item_at_position(mouse_position: Vector2) -> int: + if items.size() == 0: + return -1 + + var ratio := 1.0 + + if items.size() > 0: + ratio = vscrollbar.max_value / float(labels_end_positions.back()) + + var scrollbar_offset := vscrollbar.value + var labels_offset := scrollbar_offset / ratio + var position := mouse_position.y + labels_offset + var item_idx := labels_end_positions.bsearch(position) + + return int(min(item_idx, items.size()-1)) # Return last item when position is below it. + + +func select(index: int): + selected_idx = index + emit_signal("item_selected", selected_idx, items[selected_idx]) + + +func unselect(): + selected_idx = -1 + emit_signal("nothing_selected") + + +#func _input(event: InputEvent): +## @DAM Test and debug code. Delete when ready. +# if event is InputEventKey: +# if event.pressed && event.scancode == KEY_C: +# clear_items() +# if event.pressed && event.scancode == KEY_F: +# for idx in range(1, 26): +# if idx % 10 == 0: +# add_item("-- item %06d : This is the longest item of all, but eventually stops. --" % idx) +# else: +# add_item("-- item %06d --" % idx) + + +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 InputEventMouseButton == false: + return + + if event is InputEventScreenTouch && event.pressed == false: + return + + var item_idx := get_item_at_position(get_local_mouse_position()) + if item_idx >= 0 && item_idx < items.size(): + select(item_idx) + + diff --git a/option_set/option_set_list.tscn b/option_set/option_set_list.tscn index f8361fa..d336961 100644 --- a/option_set/option_set_list.tscn +++ b/option_set/option_set_list.tscn @@ -5,4 +5,5 @@ [node name="option_set_list" type="Control"] anchor_right = 1.0 anchor_bottom = 1.0 +rect_clip_content = true script = ExtResource( 1 ) diff --git a/project.godot b/project.godot index b414dff..4a59943 100644 --- a/project.godot +++ b/project.godot @@ -25,19 +25,24 @@ _global_script_classes=[ { "path": "res://date_picker/date_picker.gd" }, { "base": "Control", +"class": "ModalPopup", +"language": "GDScript", +"path": "res://logic/popup.gd" +}, { +"base": "Control", "class": "OptionSet", "language": "GDScript", "path": "res://option_set/option_set.gd" }, { "base": "Control", -"class": "PointerInputSensor", +"class": "OptionSetList", "language": "GDScript", -"path": "res://pointer_input_sensor.gd" +"path": "res://option_set/option_set_list.gd" }, { -"base": "Popup", -"class": "PopupList", +"base": "Control", +"class": "PointerInputSensor", "language": "GDScript", -"path": "res://logic/popup_list.gd" +"path": "res://pointer_input_sensor.gd" }, { "base": "TouchVerticalContainer", "class": "Stage", @@ -63,9 +68,10 @@ _global_script_class_icons={ "Database": "", "DatabaseEntry": "", "DatePicker": "", +"ModalPopup": "", "OptionSet": "", +"OptionSetList": "", "PointerInputSensor": "", -"PopupList": "", "Stage": "", "TouchItemList": "", "TouchVerticalContainer": "", diff --git a/touch_item_list/touch_item_list.gd b/touch_item_list/touch_item_list.gd index 67f88d7..82f94f2 100644 --- a/touch_item_list/touch_item_list.gd +++ b/touch_item_list/touch_item_list.gd @@ -1,4 +1,5 @@ extends ItemList +#extends OptionSetList class_name TouchItemList const POINTER_VELOCITY_DECAYING_FACTOR: float = PI diff --git a/touch_item_list/touch_item_list.tscn b/touch_item_list/touch_item_list.tscn index 072f5b8..31fed01 100644 --- a/touch_item_list/touch_item_list.tscn +++ b/touch_item_list/touch_item_list.tscn @@ -8,9 +8,6 @@ anchor_right = 1.0 anchor_bottom = 1.0 mouse_filter = 2 script = ExtResource( 2 ) -__meta__ = { -"_edit_use_anchors_": false -} [node name="sensor" type="Control" parent="."] anchor_right = 1.0 |
