diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9aac21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Godot 4+ specific ignores +.godot/ + +# Godot-specific ignores +.import/ +export.cfg +export_presets.cfg + +# Imported translations (automatically generated from CSV files) +*.translation + +# Mono-specific ignores +.mono/ +data_*/ +mono_crash.*.json diff --git a/README.md b/README.md index eee2e79..eb18404 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,8 @@ A simple period tracker. Base idea is to track period but it can be used to track any other repetitive events. + +## Credit + +### Sniglet +[Sniglet](https://www.design-research.be/by-womxn/) by [Haley Fiege](https://fonts.google.com/?query=Haley+Fiege). diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..b370ceb --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..6230190 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cn3dbt5jkbtfe" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..a25590d --- /dev/null +++ b/project.godot @@ -0,0 +1,28 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="gpml" +run/main_scene="res://scene/calendar.tscn" +config/features=PackedStringArray("4.2", "Forward Plus") +config/icon="res://icon.svg" + +[input] + +shift_click={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(669, 17),"global_position":Vector2(673, 58),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +} + +[rendering] + +environment/defaults/default_clear_color=Color(0.162066, 0.162066, 0.162066, 1) diff --git a/scene/calendar.tscn b/scene/calendar.tscn new file mode 100644 index 0000000..a7b97c4 --- /dev/null +++ b/scene/calendar.tscn @@ -0,0 +1,43 @@ +[gd_scene load_steps=5 format=3 uid="uid://bkwvaljh0oj22"] + +[ext_resource type="Theme" uid="uid://bu07nib41exqy" path="res://ui/theme.tres" id="1_jufjt"] +[ext_resource type="Script" path="res://script/calendar.gd" id="1_nbpow"] +[ext_resource type="PackedScene" uid="uid://x8u7mduga68y" path="res://scene/month.tscn" id="2_g4ut4"] +[ext_resource type="Script" path="res://scene/periodmanager.gd" id="3_3jsi6"] + +[node name="Calendar" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("1_jufjt") +script = ExtResource("1_nbpow") +month_scene = ExtResource("2_g4ut4") + +[node name="CenterContainer" type="CenterContainer" parent="."] +layout_mode = 1 +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -576.0 +offset_right = 576.0 +offset_bottom = 27.0 +grow_horizontal = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"] +layout_mode = 2 + +[node name="YearLabel" type="Label" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +theme_override_font_sizes/font_size = 96 + +[node name="Year" type="GridContainer" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +columns = 4 + +[node name="PeriodManager" type="Node" parent="."] +script = ExtResource("3_3jsi6") diff --git a/scene/day.tscn b/scene/day.tscn new file mode 100644 index 0000000..86c607b --- /dev/null +++ b/scene/day.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=3 uid="uid://dt5l2bunc1iru"] + +[ext_resource type="Script" path="res://script/day.gd" id="1_jhrhj"] + +[node name="Day" type="Button"] +offset_right = 46.0 +offset_bottom = 40.0 +text = "12" +script = ExtResource("1_jhrhj") diff --git a/scene/month.tscn b/scene/month.tscn new file mode 100644 index 0000000..fd75b55 --- /dev/null +++ b/scene/month.tscn @@ -0,0 +1,63 @@ +[gd_scene load_steps=3 format=3 uid="uid://x8u7mduga68y"] + +[ext_resource type="Script" path="res://script/month.gd" id="1_tiy7h"] +[ext_resource type="PackedScene" uid="uid://dt5l2bunc1iru" path="res://scene/day.tscn" id="2_5kdiw"] + +[node name="Month" type="Control"] +custom_minimum_size = Vector2(250, 250) +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_tiy7h") +day_scene = ExtResource("2_5kdiw") + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 + +[node name="MonthLabel" type="Label" parent="HBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 + +[node name="DaysGridContainer" type="GridContainer" parent="HBoxContainer"] +layout_mode = 2 +columns = 7 + +[node name="Dimanche" type="Label" parent="HBoxContainer/DaysGridContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +text = "D" + +[node name="Lundi" type="Label" parent="HBoxContainer/DaysGridContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +text = "L" + +[node name="Mardi" type="Label" parent="HBoxContainer/DaysGridContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +text = "M" + +[node name="Mercredi" type="Label" parent="HBoxContainer/DaysGridContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +text = "M" + +[node name="Jeudi" type="Label" parent="HBoxContainer/DaysGridContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +text = "J" + +[node name="Vendredi" type="Label" parent="HBoxContainer/DaysGridContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +text = "V" + +[node name="Samedi" type="Label" parent="HBoxContainer/DaysGridContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +text = "S" diff --git a/scene/periodmanager.gd b/scene/periodmanager.gd new file mode 100644 index 0000000..bfc4e54 --- /dev/null +++ b/scene/periodmanager.gd @@ -0,0 +1,25 @@ +extends Node + + +var periods = [] +var last_period = null + + +func add(date): + if last_period != null: + last_period.end = date + last_period = null + dump() + return + + # If no new period has begun + var period = Period.new() + period.begin = date + periods.append(period) + last_period = period + dump() + + +func dump(): + for period in periods: + print("%s - %s" % [period.begin, period.end]) diff --git a/script/calendar.gd b/script/calendar.gd new file mode 100644 index 0000000..3751a75 --- /dev/null +++ b/script/calendar.gd @@ -0,0 +1,64 @@ +class_name Calendar +extends Control + +@export var month_scene: PackedScene + +@onready var year_label = $CenterContainer/VBoxContainer/YearLabel +@onready var year = $CenterContainer/VBoxContainer/Year +@onready var period_manager = $PeriodManager + +var last_focused_day = null +var new_focused_day = null +var all_days = [] + +# Called when the node enters the scene tree for the first time. +func _ready(): + var current_year = Time.get_date_dict_from_system().year + + year_label.text = "%s" % current_year + + for i in range (1, 13): + var month = month_scene.instantiate() + month.year = current_year + month.month = i + month.day_toggled.connect(_on_day_toggled) + month.fill_days.connect(_on_fill_days) + year.add_child(month) + + for element in month.days.get_children(): + if element is Day: + all_days.append(element) + + +func _on_day_toggled(date): + # period_manager.add(date) + + # Update last focused day. + var focused_node = get_viewport().gui_get_focus_owner() + if focused_node is Day: + last_focused_day = focused_node + + +func _on_fill_days(shift_clicked_day): + # Toogle day from last focused day to current day. + + # Assert that last focused day is different from date. + if last_focused_day == null: + return + + if shift_clicked_day == last_focused_day: + return + + # Determine if date is before or after the last focused day. + var first_day = last_focused_day if last_focused_day.is_before(shift_clicked_day) else shift_clicked_day + var last_day = shift_clicked_day if first_day == last_focused_day else last_focused_day + + # Toggle days between the two days. + var toggle = false + for day in all_days: + if first_day == day: + toggle = true + if toggle: + day.set_pressed_no_signal(true) + if last_day == day: + return diff --git a/script/day.gd b/script/day.gd new file mode 100644 index 0000000..d231412 --- /dev/null +++ b/script/day.gd @@ -0,0 +1,39 @@ +class_name Day +extends Button + + +signal day_toggled(date) +signal fill_days(date) + +var date = {} + + +func _ready(): + set_toggle_mode(true); + toggled.connect(self._on_day_toggled) + + +func _gui_input(event): + if event.is_action_pressed("shift_click"): + fill_days.emit(self) + accept_event() + + +func _on_day_toggled(_toggled_on): + day_toggled.emit(date) + + +func set_date(year, month, day): + date = {"year": year, "month": month, "day": day} + + +func is_before(other_day): + var day_unix_time = Time.get_unix_time_from_datetime_dict(date) + var other_day_unix_time = Time.get_unix_time_from_datetime_dict(other_day.date) + + if day_unix_time < other_day_unix_time: + return true + return false + +func dump(): + print("%s - %s - %s" % [date.year, date.month, date.day]) diff --git a/script/month.gd b/script/month.gd new file mode 100644 index 0000000..8c5606d --- /dev/null +++ b/script/month.gd @@ -0,0 +1,55 @@ +class_name Month +extends Control + + +signal day_toggled(date) +signal fill_days(date) + +const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + +@export var day_scene: PackedScene +var year: int +var month: int + +@onready var month_label = $HBoxContainer/MonthLabel +@onready var days = $HBoxContainer/DaysGridContainer + + +# Called when the node enters the scene tree for the first time. +func _ready(): + month_label.text = "%s" % month + + var days_in_month = find_days_in_month(year, month) + var date_string = "%s-%s-%s" % [year, month, 1] + var first_day = Time.get_datetime_dict_from_datetime_string(date_string, true) + + # Fill the container with number corresponding to days + for i in range(first_day.weekday): + var empty_label = Label.new() + days.add_child(empty_label) + for i in range(1, days_in_month + 1): + var day = day_scene.instantiate() + day.set_date(year, month, i) + day.text = "%s" % [i] + day.day_toggled.connect(_on_day_toggled) + day.fill_days.connect(_on_fill_days) + days.add_child(day) + + +func _on_day_toggled(date): + day_toggled.emit(date) + + +func _on_fill_days(day): + fill_days.emit(day) + + +func find_days_in_month(year: int, month: int): + assert(month >= 1 and month <= 12) + if (month == 2 and is_leap_year(year)): + return 29 + return DAYS_IN_MONTH[month - 1] + + +func is_leap_year(year: int): + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) diff --git a/script/period.gd b/script/period.gd new file mode 100644 index 0000000..528a39f --- /dev/null +++ b/script/period.gd @@ -0,0 +1,5 @@ +class_name Period +extends Node + +var begin = {} +var end = {} diff --git a/ui/Sniglet-ExtraBold.ttf b/ui/Sniglet-ExtraBold.ttf new file mode 100644 index 0000000..4c9a9e9 Binary files /dev/null and b/ui/Sniglet-ExtraBold.ttf differ diff --git a/ui/Sniglet-ExtraBold.ttf.import b/ui/Sniglet-ExtraBold.ttf.import new file mode 100644 index 0000000..32cd834 --- /dev/null +++ b/ui/Sniglet-ExtraBold.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://dkdwwfrrrxsv6" +path="res://.godot/imported/Sniglet-ExtraBold.ttf-e7d97d8b5ada3d87451c1bbbfe8eb998.fontdata" + +[deps] + +source_file="res://ui/Sniglet-ExtraBold.ttf" +dest_files=["res://.godot/imported/Sniglet-ExtraBold.ttf-e7d97d8b5ada3d87451c1bbbfe8eb998.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/ui/Sniglet-Regular.ttf b/ui/Sniglet-Regular.ttf new file mode 100644 index 0000000..6ea6036 Binary files /dev/null and b/ui/Sniglet-Regular.ttf differ diff --git a/ui/Sniglet-Regular.ttf.import b/ui/Sniglet-Regular.ttf.import new file mode 100644 index 0000000..afa5a6a --- /dev/null +++ b/ui/Sniglet-Regular.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://bqpsg4cosm86c" +path="res://.godot/imported/Sniglet-Regular.ttf-a388ba5acd2b2b968e010f49c0098b80.fontdata" + +[deps] + +source_file="res://ui/Sniglet-Regular.ttf" +dest_files=["res://.godot/imported/Sniglet-Regular.ttf-a388ba5acd2b2b968e010f49c0098b80.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/ui/theme.tres b/ui/theme.tres new file mode 100644 index 0000000..7136623 --- /dev/null +++ b/ui/theme.tres @@ -0,0 +1,44 @@ +[gd_resource type="Theme" load_steps=4 format=3 uid="uid://bu07nib41exqy"] + +[ext_resource type="FontFile" uid="uid://bqpsg4cosm86c" path="res://ui/Sniglet-Regular.ttf" id="1_8evya"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jjd3u"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1, 0.1, 0.1, 0.6) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 3 +border_color = Color(0.677521, 0.850448, 0.731593, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fxaw6"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.826838, 0.64006, 0.825632, 0.6) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 3 +border_color = Color(0.678431, 0.85098, 0.733333, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 5 + +[resource] +default_font = ExtResource("1_8evya") +default_font_size = 16 +Button/styles/normal = SubResource("StyleBoxFlat_jjd3u") +Button/styles/pressed = SubResource("StyleBoxFlat_fxaw6") +Label/colors/font_color = Color(0.800791, 0.644553, 0.798944, 1)