From ab3e8de3e1d107d42ba62571ea7d17e741203123 Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 6 Dec 2021 00:45:18 +0000 Subject: Avoid invalid dates on date picker. --- date_picker/date_picker.gd | 65 ++++++++++++++++++++ date_picker/date_picker.tscn | 48 +++++++-------- date_picker/scroll_picker.gd | 137 ------------------------------------------- date_picker/value_picker.gd | 126 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 216 insertions(+), 160 deletions(-) create mode 100644 date_picker/date_picker.gd delete mode 100644 date_picker/scroll_picker.gd create mode 100644 date_picker/value_picker.gd (limited to 'date_picker') diff --git a/date_picker/date_picker.gd b/date_picker/date_picker.gd new file mode 100644 index 0000000..6298156 --- /dev/null +++ b/date_picker/date_picker.gd @@ -0,0 +1,65 @@ +extends Control + +class_name DatePicker + +const days_per_month: Dictionary = { + 1: 31, + 2: 28, + 3: 31, + 4: 30, + 5: 31, + 6: 30, + 7: 31, + 8: 31, + 9: 30, + 10: 31, + 11: 30, + 12: 31, +} + +onready var year_picker: ValuePicker = get_node("year") +onready var month_picker: ValuePicker = get_node("month") +onready var day_picker: ValuePicker = get_node("day") + + +func _process(delta: float): + var year := year_picker.value + var month := month_picker.value + var day := day_picker.value + var days_on_month: int = days_per_month[month] + + var is_leap_year := (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 + if is_leap_year && month == 2: + days_on_month = 29 + + if day > days_on_month: + day_picker.value = days_on_month + day_picker.max_value = days_on_month + + +func get_day() -> int: + return day_picker.value + + +func get_month() -> int: + return month_picker.value + + +func get_year() -> int: + return year_picker.value + + +func get_date() -> Dictionary: + return { + year = year_picker.value, + month = month_picker.value, + day = day_picker.value, + } + + +func set_date(new_year: int, new_month: int, new_day: int): + year_picker.value = new_year + month_picker.value = new_month + day_picker.value = new_day + + diff --git a/date_picker/date_picker.tscn b/date_picker/date_picker.tscn index 3414283..c7fc7c7 100644 --- a/date_picker/date_picker.tscn +++ b/date_picker/date_picker.tscn @@ -1,16 +1,18 @@ -[gd_scene load_steps=2 format=2] +[gd_scene load_steps=3 format=2] -[ext_resource path="res://date_picker/scroll_picker.gd" type="Script" id=1] +[ext_resource path="res://date_picker/value_picker.gd" type="Script" id=1] +[ext_resource path="res://date_picker/date_picker.gd" type="Script" id=2] [node name="date_picker" type="Control"] anchor_right = 1.0 anchor_bottom = 1.0 mouse_filter = 2 +script = ExtResource( 2 ) __meta__ = { "_edit_use_anchors_": false } -[node name="year_value" type="Control" parent="."] +[node name="year" type="Control" parent="."] anchor_right = 0.333 anchor_bottom = 1.0 script = ExtResource( 1 ) @@ -19,7 +21,7 @@ __meta__ = { } max_value = 9999 -[node name="previous" type="Label" parent="year_value"] +[node name="previous" type="Label" parent="year"] anchor_right = 1.0 anchor_bottom = 0.333 mouse_filter = 1 @@ -29,7 +31,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="current" type="Label" parent="year_value"] +[node name="current" type="Label" parent="year"] anchor_top = 0.333 anchor_right = 1.0 anchor_bottom = 0.666 @@ -40,16 +42,16 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="input" type="LineEdit" parent="year_value/current"] +[node name="input" type="LineEdit" parent="year/current"] visible = false anchor_right = 1.0 anchor_bottom = 1.0 -focus_next = NodePath("../../../month_value/current/input") +focus_next = NodePath("../../../month/current/input") align = 1 max_length = 4 caret_blink = true -[node name="next" type="Label" parent="year_value"] +[node name="next" type="Label" parent="year"] anchor_top = 0.666 anchor_right = 1.0 anchor_bottom = 1.0 @@ -60,7 +62,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="month_value" type="Control" parent="."] +[node name="month" type="Control" parent="."] anchor_left = 0.333 anchor_right = 0.666 anchor_bottom = 1.0 @@ -68,7 +70,7 @@ script = ExtResource( 1 ) min_value = 1 max_value = 12 -[node name="previous" type="Label" parent="month_value"] +[node name="previous" type="Label" parent="month"] anchor_right = 1.0 anchor_bottom = 0.333 mouse_filter = 1 @@ -78,7 +80,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="current" type="Label" parent="month_value"] +[node name="current" type="Label" parent="month"] anchor_top = 0.333 anchor_right = 1.0 anchor_bottom = 0.666 @@ -89,17 +91,17 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="input" type="LineEdit" parent="month_value/current"] +[node name="input" type="LineEdit" parent="month/current"] visible = false anchor_right = 1.0 anchor_bottom = 1.0 -focus_next = NodePath("../../../day_value/current/input") -focus_previous = NodePath("../../../year_value/current/input") +focus_next = NodePath("../../../day/current/input") +focus_previous = NodePath("../../../year/current/input") align = 1 max_length = 2 caret_blink = true -[node name="next" type="Label" parent="month_value"] +[node name="next" type="Label" parent="month"] anchor_top = 0.666 anchor_right = 1.0 anchor_bottom = 1.0 @@ -110,7 +112,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="day_value" type="Control" parent="."] +[node name="day" type="Control" parent="."] anchor_left = 0.666 anchor_right = 1.0 anchor_bottom = 1.0 @@ -121,7 +123,7 @@ __meta__ = { min_value = 1 max_value = 31 -[node name="previous" type="Label" parent="day_value"] +[node name="previous" type="Label" parent="day"] anchor_right = 1.0 anchor_bottom = 0.333 mouse_filter = 1 @@ -131,7 +133,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="current" type="Label" parent="day_value"] +[node name="current" type="Label" parent="day"] anchor_top = 0.333 anchor_right = 1.0 anchor_bottom = 0.666 @@ -142,16 +144,16 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="input" type="LineEdit" parent="day_value/current"] +[node name="input" type="LineEdit" parent="day/current"] visible = false anchor_right = 1.0 anchor_bottom = 1.0 -focus_previous = NodePath("../../../month_value/current/input") +focus_previous = NodePath("../../../month/current/input") align = 1 max_length = 2 caret_blink = true -[node name="next" type="Label" parent="day_value"] +[node name="next" type="Label" parent="day"] anchor_top = 0.666 anchor_right = 1.0 anchor_bottom = 1.0 @@ -162,7 +164,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="split_previous" type="ColorRect" parent="."] +[node name="split_upper" type="ColorRect" parent="."] anchor_top = 0.333 anchor_right = 1.0 anchor_bottom = 0.343 @@ -171,7 +173,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="split_next" type="ColorRect" parent="."] +[node name="split_lower" type="ColorRect" parent="."] anchor_top = 0.666 anchor_right = 1.0 anchor_bottom = 0.676 diff --git a/date_picker/scroll_picker.gd b/date_picker/scroll_picker.gd deleted file mode 100644 index 7d3004a..0000000 --- a/date_picker/scroll_picker.gd +++ /dev/null @@ -1,137 +0,0 @@ -extends Control - -const VELOCITY_DECAYING_FACTOR: float = 5.5 -const BINNING_THRESHOLD: float = 0.75 -const BINNING_ADJUST_P: float = 5.0 - -export var min_value: int -export var max_value: int - -var pointer: Dictionary -var anchor: float -var value: int -var offset: float - -var input: LineEdit -var label_previous: Label -var label_current: Label -var label_next: Label - -var scroll_unit_height: float -var label_previous_base_position: float -var label_current_base_position: float -var label_next_base_position: float - - -func _ready(): - pointer = { - index = -1, - initial_position = Vector2.ZERO, - current_position = Vector2.ZERO, - velocity = Vector2.ZERO, - was_dragged = false, - is_active = false, - } - - input = get_node("current/input") - input.connect("text_entered", self, "input_text_entered") - input.connect("focus_entered", self, "input_focus_entered") - input.connect("focus_exited", self, "input_focus_exited") - - label_previous = get_node("previous") - label_current = get_node("current") - label_next = get_node("next") - - 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 - pointer.was_dragged = true - - -func input_text_entered(new_text: String): - input.release_focus() - - -func input_focus_entered(): - input.text = "%d" % value - input.visible = true - input.select_all() - - -func input_focus_exited(): - if input.text.is_valid_integer(): - value = int(input.text) - input.visible = false - - -func set_value(new_value: int): - value = new_value - - -func get_value() -> int: - return value - - diff --git a/date_picker/value_picker.gd b/date_picker/value_picker.gd new file mode 100644 index 0000000..98c9755 --- /dev/null +++ b/date_picker/value_picker.gd @@ -0,0 +1,126 @@ +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 + +export var min_value: int +export var max_value: int + +onready var input: LineEdit = get_node("current/input") +onready var label_previous: Label = get_node("previous") +onready var label_current: Label = get_node("current") +onready var label_next: Label = get_node("next") + +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 + + +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 + pointer.was_dragged = true + + +func input_text_entered(new_text: String): + input.release_focus() + + +func input_focus_entered(): + input.text = "%d" % value + input.visible = true + input.select_all() + + +func input_focus_exited(): + if input.text.is_valid_integer(): + value = int(input.text) + input.visible = false + + -- cgit v1.2.3