aboutsummaryrefslogtreecommitdiff
path: root/ui/date_picker
diff options
context:
space:
mode:
authordam <dam@gudinoff>2022-04-10 08:32:49 +0000
committerdam <dam@gudinoff>2022-04-10 08:32:49 +0000
commit2d7da6bbc23fb917dfe931eaeb5566da0a102ac3 (patch)
treeae4d7541267dd1a7a5633ff79ac7de1e9055437b /ui/date_picker
parent75791aecbff0d8adc1011f45a69877cabff616e0 (diff)
downloadsurgery-log-2d7da6bbc23fb917dfe931eaeb5566da0a102ac3.tar.zst
surgery-log-2d7da6bbc23fb917dfe931eaeb5566da0a102ac3.zip
Code cleanup.
Diffstat (limited to 'ui/date_picker')
-rw-r--r--ui/date_picker/date_picker.gd64
-rw-r--r--ui/date_picker/date_picker.tscn189
-rw-r--r--ui/date_picker/value_picker.gd132
3 files changed, 385 insertions, 0 deletions
diff --git a/ui/date_picker/date_picker.gd b/ui/date_picker/date_picker.gd
new file mode 100644
index 0000000..e2a793f
--- /dev/null
+++ b/ui/date_picker/date_picker.gd
@@ -0,0 +1,64 @@
+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 := get_node("year") as ValuePicker
+onready var month_picker := get_node("month") as ValuePicker
+onready var day_picker := get_node("day") as ValuePicker
+
+
+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/ui/date_picker/date_picker.tscn b/ui/date_picker/date_picker.tscn
new file mode 100644
index 0000000..e3d88c4
--- /dev/null
+++ b/ui/date_picker/date_picker.tscn
@@ -0,0 +1,189 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://ui/date_picker/value_picker.gd" type="Script" id=1]
+[ext_resource path="res://ui/date_picker/date_picker.gd" type="Script" id=2]
+
+[node name="date_picker" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 2 )
+
+[node name="year" type="Control" parent="."]
+anchor_right = 0.333
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+min_value = 1
+max_value = 9999
+
+[node name="previous" type="Label" parent="year"]
+anchor_right = 1.0
+anchor_bottom = 0.333
+mouse_filter = 1
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="current" type="Label" parent="year"]
+anchor_top = 0.333
+anchor_right = 1.0
+anchor_bottom = 0.666
+mouse_filter = 1
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="input" type="LineEdit" parent="year/current"]
+visible = false
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+focus_next = NodePath("../../../month/current/input")
+custom_constants/minimum_spaces = 0
+align = 1
+max_length = 4
+caret_blink = true
+
+[node name="next" type="Label" parent="year"]
+anchor_top = 0.666
+anchor_right = 1.0
+anchor_bottom = 1.0
+mouse_filter = 1
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="month" type="Control" parent="."]
+anchor_left = 0.333
+anchor_right = 0.666
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+min_value = 1
+max_value = 12
+
+[node name="previous" type="Label" parent="month"]
+anchor_right = 1.0
+anchor_bottom = 0.333
+mouse_filter = 1
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="current" type="Label" parent="month"]
+anchor_top = 0.333
+anchor_right = 1.0
+anchor_bottom = 0.666
+mouse_filter = 1
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="input" type="LineEdit" parent="month/current"]
+visible = false
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+focus_next = NodePath("../../../day/current/input")
+focus_previous = NodePath("../../../year/current/input")
+custom_constants/minimum_spaces = 0
+align = 1
+max_length = 2
+caret_blink = true
+
+[node name="next" type="Label" parent="month"]
+anchor_top = 0.666
+anchor_right = 1.0
+anchor_bottom = 1.0
+mouse_filter = 1
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="day" type="Control" parent="."]
+anchor_left = 0.666
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+min_value = 1
+max_value = 31
+
+[node name="previous" type="Label" parent="day"]
+anchor_right = 1.0
+anchor_bottom = 0.333
+mouse_filter = 1
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="current" type="Label" parent="day"]
+anchor_top = 0.333
+anchor_right = 1.0
+anchor_bottom = 0.666
+mouse_filter = 1
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="input" type="LineEdit" parent="day/current"]
+visible = false
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+focus_previous = NodePath("../../../month/current/input")
+custom_constants/minimum_spaces = 0
+align = 1
+max_length = 2
+caret_blink = true
+
+[node name="next" type="Label" parent="day"]
+anchor_top = 0.666
+anchor_right = 1.0
+anchor_bottom = 1.0
+mouse_filter = 1
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="split_upper" type="ColorRect" parent="."]
+anchor_top = 0.333
+anchor_right = 1.0
+anchor_bottom = 0.343
+color = Color( 1, 1, 1, 0.25 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="split_lower" type="ColorRect" parent="."]
+anchor_top = 0.666
+anchor_right = 1.0
+anchor_bottom = 0.676
+color = Color( 1, 1, 1, 0.25 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
diff --git a/ui/date_picker/value_picker.gd b/ui/date_picker/value_picker.gd
new file mode 100644
index 0000000..b70f1aa
--- /dev/null
+++ b/ui/date_picker/value_picker.gd
@@ -0,0 +1,132 @@
+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
+const DRAG_THRESHOLD_CM: float = 0.250
+
+export var min_value: int
+export var max_value: int
+
+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
+
+onready var input := get_node("current/input") as LineEdit
+onready var label_previous := get_node("previous") as Label
+onready var label_current := get_node("current") as Label
+onready var label_next := get_node("next") as Label
+onready var screen_dpcm := float(OS.get_screen_dpi()) / 2.54
+
+
+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
+ if pointer.current_position.distance_to(pointer.initial_position) / screen_dpcm > DRAG_THRESHOLD_CM:
+ pointer.was_dragged = true
+
+
+func input_text_entered(new_text: String):
+ input.release_focus()
+
+
+func input_focus_entered():
+ pointer.velocity = Vector2.ZERO # Avoid changing to other value once entering input.
+ input.text = "%d" % value
+ input.visible = true
+ input.select_all()
+ label_current.self_modulate.a = 0.0
+
+
+func input_focus_exited():
+ if input.text.is_valid_integer():
+ value = wrapi(int(input.text), min_value, max_value + 1)
+ input.visible = false
+ label_current.self_modulate.a = 1.0
+
+