Compare commits
19 Commits
godot-4-de
...
main
Author | SHA1 | Date | |
---|---|---|---|
320d6ceb0b | |||
8fd89c3b4c | |||
467cec1c30 | |||
8888572829 | |||
f94a0e89d6 | |||
24ae05c425 | |||
0b39a3fa5c | |||
7b6b3feb56 | |||
126df5317f | |||
17e9a59f3c | |||
f37e3050b5 | |||
aea91c6261 | |||
b69be6e07c | |||
cbc64a0b02 | |||
397bc23ff5 | |||
cf171bcc63 | |||
17d8637156 | |||
24c4b98a5e | |||
66b25bae97 |
24
.gitignore
vendored
@ -1,2 +1,22 @@
|
||||
.godot/
|
||||
exports/
|
||||
# .gitignore generated by Hourglass
|
||||
# For more information on what files should be excluded from version control,
|
||||
# see <https://docs.godotengine.org/en/stable/getting_started/workflow/project_setup/version_control_systems.html#files-to-exclude-from-vcs>
|
||||
|
||||
.import/
|
||||
export.cfg
|
||||
exports/*.apk
|
||||
exports/*.exe
|
||||
exports/*.pck
|
||||
exports/*.x86_64
|
||||
exports/*.zip
|
||||
exports/web/
|
||||
|
||||
# Exclude imported translations
|
||||
*.translation
|
||||
|
||||
# Mono-specific
|
||||
.mono/
|
||||
data_*/
|
||||
|
||||
# Don't include those pesky .DS_Store files on macOS
|
||||
.DS_Store
|
||||
|
@ -1,14 +1,15 @@
|
||||
# Rat Times
|
||||
# ![Logo representing a rat's butt](assets/logo.png) Rat Times
|
||||
|
||||
Track your time, save it to a CSV file.
|
||||
|
||||
![screenshot of the app](info/screenshot.png)
|
||||
Uses Godot 3.5
|
||||
|
||||
Uses Godot 4
|
||||
![screenshot of the app in usage](exports/screenshot.png)
|
||||
|
||||
## Features
|
||||
|
||||
- Saves to custom CSV file. Sync this file with syncthing/nextcloud/dropbox/whatever you like
|
||||
- File watcher. Change the CSV externally, see the update in the UI
|
||||
- Versions for Linux, Windows, Android, and presumably IOS and Mac (untested)
|
||||
- Custom theming possible
|
||||
- Does not depend on a timer, so you can close the app immediately after starting a task
|
||||
|
@ -1,33 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://xixlsyswj6mv"
|
||||
path="res://.godot/imported/Cairo-VariableFont_slnt,wght.ttf-8e1ad3a3f88c2663b83086e73553b314.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/Cairo-VariableFont_slnt,wght.ttf"
|
||||
dest_files=["res://.godot/imported/Cairo-VariableFont_slnt,wght.ttf-8e1ad3a3f88c2663b83086e73553b314.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={}
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 155 KiB |
BIN
assets/logo.png
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
@ -1,9 +1,8 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cpmyyivxx0dlt"
|
||||
path="res://.godot/imported/logo.png-e2220799298e3631eb0e245316e0501a.ctex"
|
||||
type="StreamTexture"
|
||||
path="res://.import/logo.png-e2220799298e3631eb0e245316e0501a.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
@ -11,24 +10,26 @@ metadata={
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/logo.png"
|
||||
dest_files=["res://.godot/imported/logo.png-e2220799298e3631eb0e245316e0501a.ctex"]
|
||||
dest_files=[ "res://.import/logo.png-e2220799298e3631eb0e245316e0501a.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
|
@ -1,9 +1,8 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://fk5s6m8qlsei"
|
||||
path="res://.godot/imported/logo.svg-01597fe4b7eb446be26a49e8a22b6f42.ctex"
|
||||
type="StreamTexture"
|
||||
path="res://.import/logo.svg-01597fe4b7eb446be26a49e8a22b6f42.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
@ -11,27 +10,26 @@ metadata={
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/logo.svg"
|
||||
dest_files=["res://.godot/imported/logo.svg-01597fe4b7eb446be26a49e8a22b6f42.ctex"]
|
||||
dest_files=[ "res://.import/logo.svg-01597fe4b7eb446be26a49e8a22b6f42.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
@ -1,9 +1,8 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bcledyqebmri"
|
||||
path="res://.godot/imported/play.svg-1c68bc58d294f89383fc6b13dae7f4f1.ctex"
|
||||
type="StreamTexture"
|
||||
path="res://.import/play.svg-1c68bc58d294f89383fc6b13dae7f4f1.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
@ -11,27 +10,26 @@ metadata={
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/play.svg"
|
||||
dest_files=["res://.godot/imported/play.svg-1c68bc58d294f89383fc6b13dae7f4f1.ctex"]
|
||||
dest_files=[ "res://.import/play.svg-1c68bc58d294f89383fc6b13dae7f4f1.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
54
assets/play_small.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 3.4395833 3.4395833"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="play_small.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="35.356609"
|
||||
inkscape:cx="6.3495909"
|
||||
inkscape:cy="5.8263506"
|
||||
inkscape:window-width="1896"
|
||||
inkscape:window-height="1029"
|
||||
inkscape:window-x="12"
|
||||
inkscape:window-y="39"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid7036"
|
||||
originx="0"
|
||||
originy="0" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
id="rect11457"
|
||||
style="fill:#ffffff;stroke-width:1.85208;paint-order:stroke markers fill;stop-color:#000000"
|
||||
d="M 0.26458333,0.26458333 3.175,1.7197917 0.26458333,3.175 Z"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
35
assets/play_small.svg.import
Normal file
@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/play_small.svg-6ecf1cf55097c1673c0917a7e7624a3c.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/play_small.svg"
|
||||
dest_files=[ "res://.import/play_small.svg-6ecf1cf55097c1673c0917a7e7624a3c.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
@ -1,19 +1,15 @@
|
||||
[remap]
|
||||
|
||||
importer="oggvorbisstr"
|
||||
type="AudioStreamOggVorbis"
|
||||
uid="uid://cdsbhoidgyx70"
|
||||
path="res://.godot/imported/pop.ogg-1ea32f2d825ac1e6d9420f3fecda89df.oggvorbisstr"
|
||||
importer="ogg_vorbis"
|
||||
type="AudioStreamOGGVorbis"
|
||||
path="res://.import/pop.ogg-1ea32f2d825ac1e6d9420f3fecda89df.oggstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/pop.ogg"
|
||||
dest_files=["res://.godot/imported/pop.ogg-1ea32f2d825ac1e6d9420f3fecda89df.oggvorbisstr"]
|
||||
dest_files=[ "res://.import/pop.ogg-1ea32f2d825ac1e6d9420f3fecda89df.oggstr" ]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
|
BIN
assets/rcedit-x64.exe
Normal file
@ -1,9 +1,8 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c56y8w3k75rxc"
|
||||
path="res://.godot/imported/stop.svg-fc65124eb2fb3129fbdd4f17f48ea6f3.ctex"
|
||||
type="StreamTexture"
|
||||
path="res://.import/stop.svg-fc65124eb2fb3129fbdd4f17f48ea6f3.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
@ -11,27 +10,26 @@ metadata={
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/stop.svg"
|
||||
dest_files=["res://.godot/imported/stop.svg-fc65124eb2fb3129fbdd4f17f48ea6f3.ctex"]
|
||||
dest_files=[ "res://.import/stop.svg-fc65124eb2fb3129fbdd4f17f48ea6f3.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
57
assets/stop_small.svg
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 5.8208333 5.8208333"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="stop_small.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#323232"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="35.356609"
|
||||
inkscape:cx="12.402208"
|
||||
inkscape:cy="11.115319"
|
||||
inkscape:window-width="1896"
|
||||
inkscape:window-height="1029"
|
||||
inkscape:window-x="12"
|
||||
inkscape:window-y="39"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid7036"
|
||||
originx="0"
|
||||
originy="0"
|
||||
empspacing="8" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:0.994836;stroke-width:4;stroke-linecap:round;stroke-opacity:0;paint-order:stroke markers fill;stop-color:#000000"
|
||||
id="rect341"
|
||||
width="4.7624998"
|
||||
height="4.7624998"
|
||||
x="0.52916664"
|
||||
y="0.5291667" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
35
assets/stop_small.svg.import
Normal file
@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/stop_small.svg-d7819f3a82ff689d7390798487abb123.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/stop_small.svg"
|
||||
dest_files=[ "res://.import/stop_small.svg-d7819f3a82ff689d7390798487abb123.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=0.6
|
@ -1,9 +1,8 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ctxkpfc0syh2m"
|
||||
path="res://.godot/imported/tasks.svg-78ebb4f2e18a1507072d2a7762176d95.ctex"
|
||||
type="StreamTexture"
|
||||
path="res://.import/tasks.svg-78ebb4f2e18a1507072d2a7762176d95.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
@ -11,27 +10,26 @@ metadata={
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/tasks.svg"
|
||||
dest_files=["res://.godot/imported/tasks.svg-78ebb4f2e18a1507072d2a7762176d95.ctex"]
|
||||
dest_files=[ "res://.import/tasks.svg-78ebb4f2e18a1507072d2a7762176d95.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
53
assets/toggle_button_up.svg
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 3.4395833 3.4395833"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="toggle_button.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="50.001796"
|
||||
inkscape:cx="6.3597716"
|
||||
inkscape:cy="6.4797673"
|
||||
inkscape:window-width="1896"
|
||||
inkscape:window-height="1029"
|
||||
inkscape:window-x="12"
|
||||
inkscape:window-y="39"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid7036"
|
||||
originx="0"
|
||||
originy="0" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
id="rect11457"
|
||||
style="fill:#ffffff;stroke-width:1.85208;paint-order:stroke markers fill;stop-color:#000000"
|
||||
d="M 1.7197916 0.79374999 L 0.26458333 2.1166666 L 0.84645995 2.1166666 L 1.7197916 1.3229167 L 2.5931233 2.1166666 L 3.175 2.1166666 L 1.7197916 0.79374999 z " />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
35
assets/toggle_button_up.svg.import
Normal file
@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/toggle_button_up.svg-42d50bf036f845a14cc3c73e27b9d2ba.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/toggle_button_up.svg"
|
||||
dest_files=[ "res://.import/toggle_button_up.svg-42d50bf036f845a14cc3c73e27b9d2ba.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
@ -1,9 +1,8 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dskgsrua03gwe"
|
||||
path="res://.godot/imported/wheel.svg-6ab291b0608b06b66eec00e4d4332248.ctex"
|
||||
type="StreamTexture"
|
||||
path="res://.import/wheel.svg-6ab291b0608b06b66eec00e4d4332248.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
@ -11,27 +10,26 @@ metadata={
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/wheel.svg"
|
||||
dest_files=["res://.godot/imported/wheel.svg-6ab291b0608b06b66eec00e4d4332248.ctex"]
|
||||
dest_files=[ "res://.import/wheel.svg-6ab291b0608b06b66eec00e4d4332248.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
BIN
build/logo.icns
Normal file
BIN
build/logo.ico
Normal file
After Width: | Height: | Size: 155 KiB |
@ -1,6 +1,6 @@
|
||||
[gd_resource type="Resource" script_class="ConfigManager" load_steps=2 format=3]
|
||||
[gd_resource type="Resource" load_steps=2 format=2]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/config_manager.gd" id="1_xfu8y"]
|
||||
[ext_resource path="res://scripts/config_manager.gd" type="Script" id=1]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_xfu8y")
|
||||
script = ExtResource( 1 )
|
||||
|
7
default_env.tres
Normal file
@ -0,0 +1,7 @@
|
||||
[gd_resource type="Environment" load_steps=2 format=2]
|
||||
|
||||
[sub_resource type="ProceduralSky" id=1]
|
||||
|
||||
[resource]
|
||||
background_mode = 2
|
||||
background_sky = SubResource( 1 )
|
@ -1,81 +1,81 @@
|
||||
[preset.0]
|
||||
|
||||
name="Linux/X11"
|
||||
platform="Linux/X11"
|
||||
name="HTML5"
|
||||
platform="HTML5"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="exports/rat-times-x64.x86_64"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
export_path="exports/web/index.html"
|
||||
script_export_mode=1
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.0.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_script=1
|
||||
binary_format/embed_pck=false
|
||||
texture_format/bptc=false
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=false
|
||||
binary_format/architecture="x86_64"
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script="#!/usr/bin/env bash
|
||||
export DISPLAY=:0
|
||||
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
|
||||
\"{temp_dir}/{exe_name}\" {cmd_args}"
|
||||
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
|
||||
kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")
|
||||
rm -rf \"{temp_dir}\""
|
||||
variant/export_type=0
|
||||
vram_texture_compression/for_desktop=true
|
||||
vram_texture_compression/for_mobile=false
|
||||
html/export_icon=true
|
||||
html/custom_html_shell=""
|
||||
html/head_include=""
|
||||
html/canvas_resize_policy=2
|
||||
html/focus_canvas_on_start=true
|
||||
html/experimental_virtual_keyboard=false
|
||||
progressive_web_app/enabled=false
|
||||
progressive_web_app/offline_page=""
|
||||
progressive_web_app/display=1
|
||||
progressive_web_app/orientation=0
|
||||
progressive_web_app/icon_144x144=""
|
||||
progressive_web_app/icon_180x180=""
|
||||
progressive_web_app/icon_512x512=""
|
||||
progressive_web_app/background_color=Color( 0, 0, 0, 1 )
|
||||
|
||||
[preset.1]
|
||||
|
||||
name="macOS"
|
||||
platform="macOS"
|
||||
name="Mac OSX"
|
||||
platform="Mac OSX"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="exports/Rat Times.zip"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
export_path="exports/rat-times.zip"
|
||||
script_export_mode=1
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.1.options]
|
||||
|
||||
binary_format/architecture="universal"
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_script=1
|
||||
application/icon="res://logo.icns"
|
||||
application/icon_interpolation=4
|
||||
application/bundle_identifier="org.mutnt.io.rat-times"
|
||||
application/name="Rat Times"
|
||||
application/info="Made with Godot Engine"
|
||||
application/icon="res://assets/logo.icns"
|
||||
application/identifier="io.mutnt.app.rattimes"
|
||||
application/signature=""
|
||||
application/app_category="Developer-tools"
|
||||
application/app_category="Games"
|
||||
application/short_version="1.0"
|
||||
application/version="1.0"
|
||||
application/copyright=""
|
||||
application/copyright_localized={}
|
||||
display/high_res=true
|
||||
codesign/codesign=1
|
||||
display/high_res=false
|
||||
privacy/microphone_usage_description=""
|
||||
privacy/camera_usage_description=""
|
||||
privacy/location_usage_description=""
|
||||
privacy/address_book_usage_description=""
|
||||
privacy/calendar_usage_description=""
|
||||
privacy/photos_library_usage_description=""
|
||||
privacy/desktop_folder_usage_description=""
|
||||
privacy/documents_folder_usage_description="Documents required to save file"
|
||||
privacy/downloads_folder_usage_description=""
|
||||
privacy/network_volumes_usage_description=""
|
||||
privacy/removable_volumes_usage_description=""
|
||||
codesign/enable=true
|
||||
codesign/identity=""
|
||||
codesign/certificate_file=""
|
||||
codesign/certificate_password=""
|
||||
codesign/timestamp=true
|
||||
codesign/hardened_runtime=true
|
||||
codesign/replace_existing_signature=true
|
||||
codesign/entitlements/custom_file=""
|
||||
codesign/entitlements/allow_jit_code_execution=false
|
||||
codesign/entitlements/allow_unsigned_executable_memory=false
|
||||
@ -98,75 +98,105 @@ codesign/entitlements/app_sandbox/files_downloads=0
|
||||
codesign/entitlements/app_sandbox/files_pictures=0
|
||||
codesign/entitlements/app_sandbox/files_music=0
|
||||
codesign/entitlements/app_sandbox/files_movies=0
|
||||
codesign/entitlements/app_sandbox/helper_executables=[]
|
||||
codesign/custom_options=PackedStringArray()
|
||||
notarization/notarization=0
|
||||
codesign/custom_options=PoolStringArray( )
|
||||
notarization/enable=false
|
||||
notarization/apple_id_name=""
|
||||
notarization/apple_id_password=""
|
||||
notarization/apple_team_id=""
|
||||
notarization/api_uuid=""
|
||||
notarization/api_key=""
|
||||
notarization/api_key_id=""
|
||||
privacy/microphone_usage_description=""
|
||||
privacy/microphone_usage_description_localized={}
|
||||
privacy/camera_usage_description=""
|
||||
privacy/camera_usage_description_localized={}
|
||||
privacy/location_usage_description=""
|
||||
privacy/location_usage_description_localized={}
|
||||
privacy/address_book_usage_description=""
|
||||
privacy/address_book_usage_description_localized={}
|
||||
privacy/calendar_usage_description=""
|
||||
privacy/calendar_usage_description_localized={}
|
||||
privacy/photos_library_usage_description=""
|
||||
privacy/photos_library_usage_description_localized={}
|
||||
privacy/desktop_folder_usage_description=""
|
||||
privacy/desktop_folder_usage_description_localized={}
|
||||
privacy/documents_folder_usage_description=""
|
||||
privacy/documents_folder_usage_description_localized={}
|
||||
privacy/downloads_folder_usage_description=""
|
||||
privacy/downloads_folder_usage_description_localized={}
|
||||
privacy/network_volumes_usage_description=""
|
||||
privacy/network_volumes_usage_description_localized={}
|
||||
privacy/removable_volumes_usage_description=""
|
||||
privacy/removable_volumes_usage_description_localized={}
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script="#!/usr/bin/env bash
|
||||
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
|
||||
open \"{temp_dir}/{exe_name}.app\" --args {cmd_args}"
|
||||
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
|
||||
kill $(pgrep -x -f \"{temp_dir}/{exe_name}.app/Contents/MacOS/{exe_name} {cmd_args}\")
|
||||
rm -rf \"{temp_dir}\""
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=false
|
||||
|
||||
[preset.2]
|
||||
|
||||
name="Android"
|
||||
platform="Android"
|
||||
name="Linux/X11"
|
||||
platform="Linux/X11"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="exports/rat-times-x64.apk"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
export_path="exports/rat-times.x86_64"
|
||||
script_export_mode=1
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.2.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
gradle_build/use_gradle_build=false
|
||||
gradle_build/export_format=0
|
||||
gradle_build/min_sdk=""
|
||||
gradle_build/target_sdk=""
|
||||
architectures/armeabi-v7a=false
|
||||
binary_format/64_bits=true
|
||||
binary_format/embed_pck=false
|
||||
texture_format/bptc=false
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=false
|
||||
texture_format/no_bptc_fallbacks=true
|
||||
|
||||
[preset.3]
|
||||
|
||||
name="Windows Desktop"
|
||||
platform="Windows Desktop"
|
||||
runnable=true
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="exports/rat-times.exe"
|
||||
script_export_mode=1
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.3.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
binary_format/64_bits=true
|
||||
binary_format/embed_pck=false
|
||||
texture_format/bptc=false
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=false
|
||||
texture_format/no_bptc_fallbacks=true
|
||||
codesign/enable=false
|
||||
codesign/identity_type=0
|
||||
codesign/identity=""
|
||||
codesign/password=""
|
||||
codesign/timestamp=true
|
||||
codesign/timestamp_server_url=""
|
||||
codesign/digest_algorithm=1
|
||||
codesign/description=""
|
||||
codesign/custom_options=PoolStringArray( )
|
||||
application/modify_resources=true
|
||||
application/icon="res://assets/logo.ico"
|
||||
application/file_version=""
|
||||
application/product_version=""
|
||||
application/company_name="Mutnt"
|
||||
application/product_name=""
|
||||
application/file_description=""
|
||||
application/copyright=""
|
||||
application/trademarks=""
|
||||
|
||||
[preset.4]
|
||||
|
||||
name="Android"
|
||||
platform="Android"
|
||||
runnable=true
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="exports/rat-times.apk"
|
||||
script_export_mode=1
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.4.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
custom_build/use_custom_build=false
|
||||
custom_build/export_format=0
|
||||
custom_build/min_sdk=""
|
||||
custom_build/target_sdk=""
|
||||
architectures/armeabi-v7a=true
|
||||
architectures/arm64-v8a=true
|
||||
architectures/x86=false
|
||||
architectures/x86_64=false
|
||||
@ -176,12 +206,13 @@ keystore/debug_password="android"
|
||||
keystore/release=""
|
||||
keystore/release_user=""
|
||||
keystore/release_password=""
|
||||
one_click_deploy/clear_previous_install=false
|
||||
version/code=1
|
||||
version/name="1.0"
|
||||
package/unique_name="org.mutnt.io.ratstimes"
|
||||
package/unique_name="io.mutnt.app.$genname"
|
||||
package/name=""
|
||||
package/signed=true
|
||||
package/app_category=2
|
||||
package/classify_as_game=true
|
||||
package/retain_data_on_uninstall=false
|
||||
package/exclude_from_recents=false
|
||||
launcher_icons/main_192x192=""
|
||||
@ -202,7 +233,7 @@ command_line/extra_args=""
|
||||
apk_expansion/enable=false
|
||||
apk_expansion/SALT=""
|
||||
apk_expansion/public_key=""
|
||||
permissions/custom_permissions=PackedStringArray()
|
||||
permissions/custom_permissions=PoolStringArray( )
|
||||
permissions/access_checkin_properties=false
|
||||
permissions/access_coarse_location=false
|
||||
permissions/access_fine_location=false
|
||||
@ -349,145 +380,3 @@ permissions/write_sms=false
|
||||
permissions/write_social_stream=false
|
||||
permissions/write_sync_settings=false
|
||||
permissions/write_user_dictionary=false
|
||||
|
||||
[preset.3]
|
||||
|
||||
name="iOS"
|
||||
platform="iOS"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="exports/rat-times.ipa"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.3.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
architectures/arm64=true
|
||||
application/app_store_team_id="org.mutnt.io"
|
||||
application/provisioning_profile_uuid_debug=""
|
||||
application/code_sign_identity_debug=""
|
||||
application/export_method_debug=1
|
||||
application/provisioning_profile_uuid_release=""
|
||||
application/code_sign_identity_release=""
|
||||
application/export_method_release=0
|
||||
application/targeted_device_family=2
|
||||
application/bundle_identifier="rat-times"
|
||||
application/signature=""
|
||||
application/short_version="1.0"
|
||||
application/version="1.0"
|
||||
application/icon_interpolation=4
|
||||
application/launch_screens_interpolation=4
|
||||
capabilities/access_wifi=false
|
||||
capabilities/push_notifications=false
|
||||
user_data/accessible_from_files_app=false
|
||||
user_data/accessible_from_itunes_sharing=false
|
||||
privacy/camera_usage_description=""
|
||||
privacy/camera_usage_description_localized={}
|
||||
privacy/microphone_usage_description=""
|
||||
privacy/microphone_usage_description_localized={}
|
||||
privacy/photolibrary_usage_description=""
|
||||
privacy/photolibrary_usage_description_localized={}
|
||||
icons/iphone_120x120=""
|
||||
icons/iphone_180x180=""
|
||||
icons/ipad_76x76=""
|
||||
icons/ipad_152x152=""
|
||||
icons/ipad_167x167=""
|
||||
icons/app_store_1024x1024=""
|
||||
icons/spotlight_40x40=""
|
||||
icons/spotlight_80x80=""
|
||||
icons/settings_58x58=""
|
||||
icons/settings_87x87=""
|
||||
icons/notification_40x40=""
|
||||
icons/notification_60x60=""
|
||||
storyboard/use_launch_screen_storyboard=false
|
||||
storyboard/image_scale_mode=0
|
||||
storyboard/custom_image@2x=""
|
||||
storyboard/custom_image@3x=""
|
||||
storyboard/use_custom_bg_color=false
|
||||
storyboard/custom_bg_color=Color(0, 0, 0, 1)
|
||||
landscape_launch_screens/iphone_2436x1125=""
|
||||
landscape_launch_screens/iphone_2208x1242=""
|
||||
landscape_launch_screens/ipad_1024x768=""
|
||||
landscape_launch_screens/ipad_2048x1536=""
|
||||
portrait_launch_screens/iphone_640x960=""
|
||||
portrait_launch_screens/iphone_640x1136=""
|
||||
portrait_launch_screens/iphone_750x1334=""
|
||||
portrait_launch_screens/iphone_1125x2436=""
|
||||
portrait_launch_screens/ipad_768x1024=""
|
||||
portrait_launch_screens/ipad_1536x2048=""
|
||||
portrait_launch_screens/iphone_1242x2208=""
|
||||
|
||||
[preset.4]
|
||||
|
||||
name="Windows Desktop"
|
||||
platform="Windows Desktop"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="exports/rat-times-x64.exe"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_encryption_key=""
|
||||
|
||||
[preset.4.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_script=1
|
||||
binary_format/embed_pck=false
|
||||
texture_format/bptc=false
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=false
|
||||
binary_format/architecture="x86_64"
|
||||
codesign/enable=false
|
||||
codesign/identity_type=0
|
||||
codesign/identity=""
|
||||
codesign/password=""
|
||||
codesign/timestamp=true
|
||||
codesign/timestamp_server_url=""
|
||||
codesign/digest_algorithm=1
|
||||
codesign/description=""
|
||||
codesign/custom_options=PackedStringArray()
|
||||
application/modify_resources=false
|
||||
application/icon=""
|
||||
application/console_wrapper_icon=""
|
||||
application/icon_interpolation=4
|
||||
application/file_version=""
|
||||
application/product_version=""
|
||||
application/company_name=""
|
||||
application/product_name=""
|
||||
application/file_description=""
|
||||
application/copyright=""
|
||||
application/trademarks=""
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
|
||||
$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
|
||||
$trigger = New-ScheduledTaskTrigger -Once -At 00:00
|
||||
$settings = New-ScheduledTaskSettingsSet
|
||||
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
|
||||
Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
|
||||
Start-ScheduledTask -TaskName godot_remote_debug
|
||||
while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
|
||||
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
|
||||
ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
|
||||
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
|
||||
Remove-Item -Recurse -Force '{temp_dir}'"
|
||||
|
0
exports/.gdignore
Normal file
47
exports/install-linux.sh
Executable file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
usage(){
|
||||
echo "Copies the rat-times binary, data, and icon to the specified directory,"
|
||||
echo "then copies the desktop file to ~/.local/share/applications, while"
|
||||
echo "setting the correct path"
|
||||
echo ""
|
||||
echo "Example Usage: install-linux.sh ~/Applications"
|
||||
}
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
>&2 echo "No arguments provided, please provide a valid path for installation"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
if [ $1 = "--help" ]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ $1 = "-h" ]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
dir_resolve(){
|
||||
cd "$1" 2>/dev/null || return $? # cd to desired directory; if fail, quell any error messages but return exit status
|
||||
echo "`pwd -P`" # output full, link-resolved path
|
||||
}
|
||||
|
||||
if APP_PATH="`dir_resolve \"$1\"`"; then
|
||||
SHORTCUT_PATH="$HOME/.local/share/applications/rat-times.desktop"
|
||||
mkdir -p $APP_PATH
|
||||
cp rat-times.x86_64 rat-times.pck rat-times.svg $APP_PATH
|
||||
sed "s@REPLACE_WITH_PATH@$APP_PATH@g" rat-times.desktop > $SHORTCUT_PATH
|
||||
echo "Rat Times is installed. To remove, please delete:"
|
||||
echo "$APP_PATH/rat-times.x86_64"
|
||||
echo "$APP_PATH/rat-times.pck"
|
||||
echo "$APP_PATH/rat-times.svg"
|
||||
echo "$SHORTCUT_PATH"
|
||||
else
|
||||
echo "Could not reach $1"
|
||||
fi
|
||||
|
||||
|
8
exports/rat-times.desktop
Normal file
@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Name=Rat Times
|
||||
Comment=Track your time
|
||||
Exec=REPLACE_WITH_PATH/rat-times.x86_64
|
||||
Icon=REPLACE_WITH_PATH/rat-times.svg
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Game;
|
87
exports/rat-times.svg
Normal file
@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="64"
|
||||
height="64"
|
||||
viewBox="0 0 16.933333 16.933333"
|
||||
version="1.1"
|
||||
id="svg13794"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="logo.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview13796"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="6.2502245"
|
||||
inkscape:cx="22.479193"
|
||||
inkscape:cy="24.159132"
|
||||
inkscape:window-width="1896"
|
||||
inkscape:window-height="1029"
|
||||
inkscape:window-x="12"
|
||||
inkscape:window-y="39"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs13791" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<ellipse
|
||||
style="fill:#1d86ff;fill-opacity:1;stroke-width:1.85208;paint-order:stroke markers fill;stop-color:#000000"
|
||||
id="circle14391"
|
||||
cx="4.0016642"
|
||||
cy="4.8131599"
|
||||
rx="2.9889076"
|
||||
ry="2.9889104" />
|
||||
<path
|
||||
style="fill:#d588ff;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 10.040695,15.90349 6.892697,-1.588035 -1.494564,-0.466127 1.045969,-0.83042 -1.837551,0.445927 v -0.84077 z"
|
||||
id="path14447" />
|
||||
<path
|
||||
style="fill:#d588ff;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 6.8926369,16.389379 -5.9950227e-5,14.801343 1.4945043,14.335217 0.44853556,13.504797 2.2860868,13.950724 v -0.84077 z"
|
||||
id="path14449" />
|
||||
<ellipse
|
||||
style="fill:#1d86ff;fill-opacity:1;stroke-width:1.85208;paint-order:stroke markers fill;stop-color:#000000"
|
||||
id="path13969"
|
||||
cx="8.1552019"
|
||||
cy="9.9386654"
|
||||
rx="6.4920444"
|
||||
ry="6.4920506" />
|
||||
<path
|
||||
sodipodi:type="spiral"
|
||||
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#4200cb;stroke-width:0.230262;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path14024"
|
||||
sodipodi:cx="8.4666662"
|
||||
sodipodi:cy="8.4666662"
|
||||
sodipodi:expansion="2"
|
||||
sodipodi:revolution="1.5184656"
|
||||
sodipodi:radius="2.3796637"
|
||||
sodipodi:argument="-19.12742"
|
||||
sodipodi:t0="0"
|
||||
d="m 8.4666662,8.4666662 c 0.027537,-0.00785 0.021565,0.052556 0.017694,0.06203 -0.044361,0.108565 -0.2002796,0.066923 -0.2658124,0.00874 C 8.0210701,8.3621249 8.1306116,8.0511576 8.307424,7.9083989 8.6808232,7.6069157 9.2185608,7.8211538 9.4591415,8.1835689 9.8827565,8.8217104 9.5243913,9.6591442 8.9090057,10.017409 7.9391013,10.582067 6.7291388,10.039501 6.2335968,9.1036351 6.1901155,9.0215177 6.1515675,8.9367957 6.1180994,8.8501155"
|
||||
transform="matrix(-3.9529526,2.2775757,-2.3165148,-4.0094692,60.938259,26.017524)"
|
||||
inkscape:transform-center-x="-0.51754845"
|
||||
inkscape:transform-center-y="-2.4484256" />
|
||||
<ellipse
|
||||
style="fill:#1d86ff;fill-opacity:1;stroke-width:1.85208;paint-order:stroke markers fill;stop-color:#000000"
|
||||
id="circle14389"
|
||||
cx="11.931787"
|
||||
cy="4.8131599"
|
||||
rx="2.9889076"
|
||||
ry="2.9889104" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
BIN
exports/screenshot.png
Normal file
After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 9.3 KiB |
34
main.gd
@ -1,34 +0,0 @@
|
||||
#!/usr/bin/env -S godot --headless -s
|
||||
extends SceneTree
|
||||
|
||||
var config: ConfigManager = preload("res://config_manager.tres")
|
||||
|
||||
func _init():
|
||||
var cmd := CMD.new()
|
||||
for command in ["list", "stop", "start", "current"]:
|
||||
if cmd.has_argument(command):
|
||||
call(command)
|
||||
return
|
||||
print("no command provided -- exiting")
|
||||
quit()
|
||||
|
||||
|
||||
func list() -> void:
|
||||
var entries := config.timesheet.entries
|
||||
for item in entries:
|
||||
print(item)
|
||||
quit()
|
||||
|
||||
|
||||
func stop() -> void:
|
||||
if config.timesheet.current_entry:
|
||||
config.timesheet.close_entry()
|
||||
|
||||
|
||||
func start() -> void:
|
||||
pass
|
||||
|
||||
|
||||
func current() -> void:
|
||||
if config.timesheet.current_entry:
|
||||
print("{name}\t{start_time}\t{end_time}"%config.timesheet.current_entry)
|
48
main_cli.gd
Executable file
@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env -S godot --no-window -s
|
||||
extends SceneTree
|
||||
|
||||
var config: ConfigManager = preload("res://config_manager.tres")
|
||||
|
||||
const valid_commands := ["list", "stop", "start"]
|
||||
|
||||
func _init():
|
||||
var cmd := CMD.new()
|
||||
for command in valid_commands:
|
||||
if cmd.has_argument(command):
|
||||
call(command, cmd.get_argument(command))
|
||||
return
|
||||
print("no command provided -- exiting")
|
||||
help()
|
||||
quit(1)
|
||||
|
||||
|
||||
func help(_show: bool = true) -> void:
|
||||
print("Valid commands are:")
|
||||
for command in valid_commands:
|
||||
prints(" -",command)
|
||||
|
||||
|
||||
func list(show_all: bool = true) -> void:
|
||||
var entries := config.timesheet.entries
|
||||
for item in entries:
|
||||
if show_all:
|
||||
print(item)
|
||||
else:
|
||||
if not item.is_closed:
|
||||
print(item)
|
||||
quit()
|
||||
|
||||
|
||||
func stop(entry_name: String) -> void:
|
||||
var success := config.timesheet.stop_entry(entry_name)
|
||||
if success:
|
||||
print("closed %s"%[entry_name])
|
||||
else:
|
||||
print("could not close %s"%[entry_name])
|
||||
quit()
|
||||
|
||||
|
||||
func start(entry_name: String) -> void:
|
||||
# warning-ignore:return_value_discarded
|
||||
config.timesheet.add_entry(entry_name)
|
||||
quit()
|
104
project.godot
@ -6,39 +6,111 @@
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
config_version=4
|
||||
|
||||
_global_script_classes=[ {
|
||||
"base": "Reference",
|
||||
"class": "CMD",
|
||||
"language": "GDScript",
|
||||
"path": "res://scripts/cmd.gd"
|
||||
}, {
|
||||
"base": "Resource",
|
||||
"class": "ConfigManager",
|
||||
"language": "GDScript",
|
||||
"path": "res://scripts/config_manager.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "Consts",
|
||||
"language": "GDScript",
|
||||
"path": "res://scripts/consts.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "FileWatcher",
|
||||
"language": "GDScript",
|
||||
"path": "res://scripts/file_watcher.gd"
|
||||
}, {
|
||||
"base": "Tree",
|
||||
"class": "TimeEntriesItemsTree",
|
||||
"language": "GDScript",
|
||||
"path": "res://ui/tasks_list.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "TimeEntry",
|
||||
"language": "GDScript",
|
||||
"path": "res://scripts/time_entry.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "TimeEntryTreeItem",
|
||||
"language": "GDScript",
|
||||
"path": "res://scripts/time_entry_tree_item.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "TimeSheet",
|
||||
"language": "GDScript",
|
||||
"path": "res://scripts/time_sheet.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "TimeStamp",
|
||||
"language": "GDScript",
|
||||
"path": "res://scripts/time_stamp.gd"
|
||||
} ]
|
||||
_global_script_class_icons={
|
||||
"CMD": "",
|
||||
"ConfigManager": "",
|
||||
"Consts": "",
|
||||
"FileWatcher": "",
|
||||
"TimeEntriesItemsTree": "",
|
||||
"TimeEntry": "",
|
||||
"TimeEntryTreeItem": "",
|
||||
"TimeSheet": "",
|
||||
"TimeStamp": ""
|
||||
}
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Rat Times"
|
||||
config/description="Track your time(s)s"
|
||||
run/main_scene="res://ui/Main.tscn"
|
||||
run/main_scene="res://ui/composed_ui.tscn"
|
||||
config/use_custom_user_dir=true
|
||||
config/custom_user_dir_name="rat_times"
|
||||
config/features=PackedStringArray("4.0")
|
||||
run/low_processor_mode=true
|
||||
boot_splash/bg_color=Color(0.141176, 0.141176, 0.141176, 0)
|
||||
boot_splash/show_image=false
|
||||
config/icon="res://assets/logo.svg"
|
||||
config/macos_native_icon="res://logo.icns"
|
||||
config/windows_native_icon="res://logo.ico"
|
||||
boot_splash/bg_color=Color( 0.141176, 0.141176, 0.141176, 0 )
|
||||
config/icon="res://assets/logo.png"
|
||||
config/macos_native_icon="res://build/logo.icns"
|
||||
config/windows_native_icon="res://build/logo.ico"
|
||||
|
||||
[debug]
|
||||
|
||||
settings/fps/force_fps=60
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=500
|
||||
window/size/viewport_height=151
|
||||
window/size/width=360
|
||||
window/size/height=760
|
||||
window/size/resizable=false
|
||||
window/size/always_on_top=true
|
||||
window/size/transparent=true
|
||||
window/per_pixel_transparency/allowed=true
|
||||
window/subwindows/embed_subwindows=false
|
||||
window/per_pixel_transparency/enabled=true
|
||||
window/energy_saving/keep_screen_on=false
|
||||
window/handheld/orientation="portrait"
|
||||
window/ios/hide_home_indicator=false
|
||||
window/stretch/aspect="keep"
|
||||
|
||||
[physics]
|
||||
[global]
|
||||
|
||||
common/enable_pause_aware_picking=true
|
||||
rced=false
|
||||
|
||||
[gui]
|
||||
|
||||
theme/use_hidpi=true
|
||||
|
||||
[logging]
|
||||
|
||||
file_logging/log_path="user://logs/rat-times.log"
|
||||
|
||||
[rendering]
|
||||
|
||||
textures/vram_compression/import_etc2_astc=true
|
||||
viewport/transparent_background=true
|
||||
quality/driver/driver_name="GLES2"
|
||||
vram_compression/import_etc=true
|
||||
vram_compression/import_etc2=false
|
||||
environment/default_environment="res://default_env.tres"
|
||||
|
@ -1,5 +1,23 @@
|
||||
class_name CMD
|
||||
|
||||
|
||||
var _parsed := false
|
||||
|
||||
## @type Dictionary[String, String|bool]
|
||||
var command_line_arguments: Dictionary = {} setget set_command_line_arguments, get_command_line_arguments
|
||||
|
||||
|
||||
## Removes the first element find from the `quotes` array from the start and end of a string
|
||||
## Also removes any whitespace resulting from removing the quoting elements
|
||||
static func unsurround(value: String, quotes := PoolStringArray(['"', "'"])) -> String:
|
||||
for quote_str in quotes:
|
||||
if value.begins_with(quote_str) \
|
||||
and value.ends_with(quote_str) \
|
||||
and value[value.length() - 2] != "\\":
|
||||
return value.trim_prefix(quote_str).trim_suffix(quote_str).strip_edges()
|
||||
return value
|
||||
|
||||
|
||||
## Returns a dictionary of all arguments passed after `--` on the command line
|
||||
## arguments take one of 2 forms:
|
||||
## - `--arg` which is a boolean (using `--no-arg` for `false` is possible)
|
||||
@ -7,21 +25,14 @@ class_name CMD
|
||||
## unsurround the string
|
||||
## This function does no evaluation and does not attempt to guess the type of
|
||||
## arguments. You will receive either bools, or strings.
|
||||
var command_line_arguments: Dictionary = (func () -> Dictionary:
|
||||
var unsurround := func unsurround(value: String, quotes := PackedStringArray(['"', "'"])) -> String:
|
||||
for quote_str in quotes:
|
||||
if value.begins_with(quote_str) \
|
||||
and value.ends_with(quote_str) \
|
||||
and value[value.length() - 2] != "\\":
|
||||
return value.trim_prefix(quote_str).trim_suffix(quote_str).strip_edges()
|
||||
return value
|
||||
static func parse_cmd_arguments() -> Dictionary:
|
||||
var arguments := {}
|
||||
for argument in OS.get_cmdline_user_args():
|
||||
argument = argument.lstrip("--").to_lower()
|
||||
for arg in OS.get_cmdline_args():
|
||||
var argument: String = arg.lstrip("--").to_lower()
|
||||
if argument.find("=") > -1:
|
||||
var arg_tuple := argument.split("=")
|
||||
var key := arg_tuple[0]
|
||||
var value:String = unsurround.call(arg_tuple[1])
|
||||
var value := unsurround(arg_tuple[1])
|
||||
arguments[key] = value
|
||||
else:
|
||||
var key := argument
|
||||
@ -30,14 +41,30 @@ var command_line_arguments: Dictionary = (func () -> Dictionary:
|
||||
value = false
|
||||
key = argument.lstrip("no-")
|
||||
arguments[key] = value
|
||||
return arguments).call()
|
||||
return arguments
|
||||
|
||||
|
||||
func get_argument(name: String, default: Variant = null) -> Variant:
|
||||
if command_line_arguments.has(name):
|
||||
return command_line_arguments[name]
|
||||
func set_command_line_arguments(_arguments: Dictionary) -> void:
|
||||
printerr("get_command_line_arguments is a read only value")
|
||||
|
||||
|
||||
func get_command_line_arguments() -> Dictionary:
|
||||
if not _parsed:
|
||||
_parsed = true
|
||||
command_line_arguments = parse_cmd_arguments()
|
||||
return command_line_arguments
|
||||
|
||||
|
||||
## Returns a single argument passed after `--` on the command line
|
||||
## if the argument does not exist, `default` is returned instead
|
||||
## _parse_cmd_arguments() has to be called first
|
||||
func get_argument(name: String, default = null):
|
||||
if get_command_line_arguments().has(name):
|
||||
return get_command_line_arguments()[name]
|
||||
return default
|
||||
|
||||
|
||||
## Verifies an argument exists on the command line
|
||||
## _parse_cmd_arguments() has to be called first
|
||||
func has_argument(name: String) -> bool:
|
||||
return command_line_arguments.has(name)
|
||||
return get_command_line_arguments().has(name)
|
||||
|
@ -1,85 +1,180 @@
|
||||
## Reads the config, sets values. Acts a singleton because it proxies a const
|
||||
## file path.
|
||||
class_name ConfigManager extends Resource
|
||||
|
||||
|
||||
const CONFIG_PATH := "user://settings.cfg"
|
||||
|
||||
|
||||
var _config := ConfigFile.new()
|
||||
var _watcher: FileWatcher
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# SIGNAL
|
||||
#
|
||||
|
||||
signal time_sheet_loaded
|
||||
|
||||
func emit_loaded() -> void:
|
||||
emit_signal("time_sheet_loaded")
|
||||
|
||||
|
||||
var timesheet: TimeSheet:
|
||||
get:
|
||||
if timesheet == null:
|
||||
timesheet = TimeSheet.restore(current_file)
|
||||
return timesheet
|
||||
###############################################################################
|
||||
#
|
||||
# TIMESHEET FILE LOADING AND PARSING
|
||||
#
|
||||
|
||||
signal file_changed
|
||||
var current_file: String = "":
|
||||
set = set_current_file,
|
||||
get = get_current_file
|
||||
var timesheet: TimeSheet setget ,get_timesheet
|
||||
|
||||
|
||||
func set_current_file(value: String) -> void:
|
||||
timesheet = TimeSheet.restore(value)
|
||||
func get_timesheet() -> TimeSheet:
|
||||
if timesheet == null:
|
||||
timesheet = _load_timesheet(get_current_timesheet_file_path())
|
||||
return timesheet
|
||||
|
||||
|
||||
func _load_timesheet(path: String) -> TimeSheet:
|
||||
var new_timesheet := TimeSheet.restore(path)
|
||||
if new_timesheet == null:
|
||||
return null
|
||||
_watcher = FileWatcher.new()
|
||||
_watcher.file_name = path
|
||||
# warning-ignore:return_value_discarded
|
||||
_watcher.connect("file_changed", self, "reload_timesheet")
|
||||
_watcher.start()
|
||||
return new_timesheet
|
||||
|
||||
|
||||
func reload_timesheet() -> void:
|
||||
var new_timesheet = _load_timesheet(get_current_timesheet_file_path())
|
||||
if new_timesheet == null:
|
||||
printerr("failed to load new timesheet")
|
||||
return
|
||||
timesheet = new_timesheet
|
||||
emit_loaded()
|
||||
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# TIMESHEET FILE PATH
|
||||
#
|
||||
|
||||
var current_timesheet_file_path: String = "" setget set_current_timesheet_file_path, get_current_timesheet_file_path
|
||||
|
||||
|
||||
func set_current_timesheet_file_path(value: String) -> void:
|
||||
if current_timesheet_file_path == value:
|
||||
return
|
||||
timesheet = _load_timesheet(value)
|
||||
if timesheet == null:
|
||||
return
|
||||
current_file = value
|
||||
current_timesheet_file_path = value
|
||||
_config.set_value("MAIN", "file", value)
|
||||
file_changed.emit()
|
||||
emit_loaded()
|
||||
save()
|
||||
|
||||
|
||||
func get_current_file() -> String:
|
||||
var _default_path := OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS, true).path_join("mouse_timer.csv")
|
||||
func get_current_timesheet_file_path() -> String:
|
||||
var _default_path := OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS, true).plus_file("mouse_timer.csv")
|
||||
return _config.get_value("MAIN", "file", _default_path)
|
||||
|
||||
|
||||
var theme: Theme:
|
||||
get:
|
||||
###############################################################################
|
||||
#
|
||||
# THEME FILE LOADING AND PARSING
|
||||
#
|
||||
|
||||
|
||||
var theme: Theme setget , get_theme
|
||||
|
||||
|
||||
func get_theme() -> Theme:
|
||||
if theme == null:
|
||||
theme = ResourceLoader.load(theme_path, "Theme")
|
||||
theme = ResourceLoader.load(theme_file_path, "Theme")
|
||||
return theme
|
||||
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# THEME FILE PATH
|
||||
#
|
||||
|
||||
signal theme_changed
|
||||
var theme_path: String = "":
|
||||
set = set_theme_path,
|
||||
get = get_theme_path
|
||||
var theme_file_path: String = "" setget set_theme_file_path, get_theme_file_path
|
||||
|
||||
|
||||
func set_theme_path(value: String) -> void:
|
||||
func set_theme_file_path(value: String) -> void:
|
||||
var new_theme: Theme = ResourceLoader.load(value, "Theme")
|
||||
if new_theme != null:
|
||||
theme = new_theme
|
||||
theme_path = value
|
||||
theme_file_path = value
|
||||
_config.set_value("MAIN", "theme", value)
|
||||
theme_changed.emit()
|
||||
emit_signal("theme_changed")
|
||||
save()
|
||||
|
||||
|
||||
func get_theme_path() -> String:
|
||||
func get_theme_file_path() -> String:
|
||||
return _config.get_value("MAIN", "theme", preload("res://assets/default_theme.theme").resource_path)
|
||||
|
||||
|
||||
var last_task_name: String = "":
|
||||
set(value):
|
||||
last_task_name = value
|
||||
_config.set_value("MAIN", "last_task_name", value)
|
||||
save()
|
||||
get:
|
||||
return _config.get_value("MAIN", "last_task_name", "")
|
||||
###############################################################################
|
||||
#
|
||||
# SOUND OPTION
|
||||
#
|
||||
|
||||
var sound_fx_on: bool = true setget set_sound_fx_on, get_sound_fx_on
|
||||
|
||||
var sound_fx_on: bool = true:
|
||||
set(value):
|
||||
func set_sound_fx_on(value: bool) -> void:
|
||||
sound_fx_on = value
|
||||
_config.set_value("MAIN", "sound_fx_on", value)
|
||||
save()
|
||||
get:
|
||||
|
||||
func get_sound_fx_on() -> bool:
|
||||
return _config.get_value("MAIN", "sound_fx", true)
|
||||
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# SOME SETTINGS CACHE
|
||||
#
|
||||
|
||||
var current_task_name := "" setget set_current_task_name, get_current_task_name
|
||||
|
||||
|
||||
func set_current_task_name(value: String) -> void:
|
||||
current_task_name = value
|
||||
_config.set_value("CACHE", "current_task_name", value)
|
||||
save()
|
||||
|
||||
|
||||
func get_current_task_name() -> String:
|
||||
return _config.get_value("CACHE", "current_task_name", "")
|
||||
|
||||
|
||||
var last_window_position := Vector2() setget set_last_window_position, get_last_window_position
|
||||
|
||||
|
||||
func set_last_window_position(value: Vector2) -> void:
|
||||
last_window_position = value
|
||||
_config.set_value("CACHE", "last_window_position", value)
|
||||
save()
|
||||
|
||||
|
||||
func get_last_window_position() -> Vector2:
|
||||
return _config.get_value("CACHE", "last_window_position", OS.get_screen_size()/2)
|
||||
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# BOOTSTRAP
|
||||
#
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# warning-ignore:return_value_discarded
|
||||
_config.load(CONFIG_PATH)
|
||||
|
||||
|
||||
func save() -> void:
|
||||
# warning-ignore:return_value_discarded
|
||||
_config.save(CONFIG_PATH)
|
||||
|
60
scripts/file_watcher.gd
Normal file
@ -0,0 +1,60 @@
|
||||
class_name FileWatcher
|
||||
|
||||
signal file_changed
|
||||
|
||||
var file_name: String setget set_file_name
|
||||
var scan_delay_s: float = 1.0 setget set_scan_delay_s
|
||||
|
||||
var _file := File.new()
|
||||
var _last_modified: int = 0
|
||||
var _timer := Timer.new()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# warning-ignore:return_value_discarded
|
||||
_timer.connect("timeout", self, "check")
|
||||
_timer.wait_time = scan_delay_s
|
||||
|
||||
|
||||
func set_file_name(new_file_name: String) -> void:
|
||||
if file_name == new_file_name:
|
||||
return
|
||||
file_name = new_file_name
|
||||
if file_name.begins_with("res://") or file_name.begins_with("user://"):
|
||||
file_name = ProjectSettings.globalize_path(file_name)
|
||||
_last_modified = _file.get_modified_time(file_name)
|
||||
_timer.name = "__file_watcher_%s"%[file_name]
|
||||
|
||||
|
||||
func set_scan_delay_s(new_scan_delay_s: float) -> void:
|
||||
scan_delay_s = new_scan_delay_s
|
||||
_timer.wait_time = scan_delay_s
|
||||
|
||||
|
||||
func check() -> void:
|
||||
if file_name == "":
|
||||
return
|
||||
var new_modified := _file.get_modified_time(file_name)
|
||||
if new_modified != _last_modified:
|
||||
_last_modified = new_modified
|
||||
emit_signal("file_changed")
|
||||
|
||||
|
||||
func start() -> void:
|
||||
if not _timer.is_inside_tree():
|
||||
var main_loop: SceneTree = Engine.get_main_loop()
|
||||
var root := main_loop.root
|
||||
if root == null:
|
||||
printerr("Called start before any scene is loaded")
|
||||
return
|
||||
if not root.is_inside_tree():
|
||||
yield(root, "ready")
|
||||
root.call_deferred("add_child", _timer, true)
|
||||
yield(_timer, "ready")
|
||||
_timer.start()
|
||||
|
||||
|
||||
func stop() -> void:
|
||||
_timer.stop()
|
||||
|
||||
|
@ -2,21 +2,29 @@
|
||||
## Has a beginning and an end
|
||||
class_name TimeEntry
|
||||
|
||||
|
||||
var name := ""
|
||||
var closed := false
|
||||
var is_closed := false
|
||||
var start_time := TimeStamp.new()
|
||||
var end_time := TimeStamp.new()
|
||||
var previous_total := 0
|
||||
|
||||
|
||||
func start_recording() -> TimeEntry:
|
||||
start_time = start_time.from_current_time()
|
||||
end_time = end_time.from_current_time()
|
||||
# warning-ignore:return_value_discarded
|
||||
start_time.from_current_time()
|
||||
# warning-ignore:return_value_discarded
|
||||
end_time.from_current_time()
|
||||
return self
|
||||
|
||||
|
||||
func update() -> void:
|
||||
end_time = end_time.from_current_time()
|
||||
# warning-ignore:return_value_discarded
|
||||
end_time.from_current_time()
|
||||
|
||||
|
||||
func close() -> void:
|
||||
update()
|
||||
is_closed = true
|
||||
|
||||
|
||||
func get_elapsed_seconds() -> int:
|
||||
@ -24,56 +32,72 @@ func get_elapsed_seconds() -> int:
|
||||
return elapsed
|
||||
|
||||
|
||||
func get_total_elapsed_seconds() -> int:
|
||||
var elapsed := get_elapsed_seconds() + previous_total
|
||||
return elapsed
|
||||
|
||||
|
||||
func get_period() -> String:
|
||||
var time_in_secs := get_elapsed_seconds()
|
||||
return TimeEntry.time_to_period(time_in_secs)
|
||||
|
||||
|
||||
func get_total_period() -> String:
|
||||
var time_in_secs := get_total_elapsed_seconds()
|
||||
return TimeEntry.time_to_period(time_in_secs)
|
||||
return time_to_period(time_in_secs)
|
||||
|
||||
|
||||
static func time_to_period(time_in_secs: int) -> String:
|
||||
# warning-ignore:integer_division
|
||||
var seconds := time_in_secs%60
|
||||
@warning_ignore("integer_division")
|
||||
# warning-ignore:integer_division
|
||||
var minutes := (time_in_secs/60)%60
|
||||
@warning_ignore("integer_division")
|
||||
# warning-ignore:integer_division
|
||||
var hours := (time_in_secs/60)/60
|
||||
return "%02d:%02d:%02d" % [hours, minutes, seconds]
|
||||
|
||||
func to_csv_line() -> PackedStringArray:
|
||||
return PackedStringArray([
|
||||
|
||||
static func period_to_time(period_string: String) -> int:
|
||||
var period := period_string.split(":")
|
||||
if period.size() < 3:
|
||||
return -1
|
||||
var hours := int(period[0])
|
||||
var minutes := int(period[1])
|
||||
var seconds := int(period[2])
|
||||
var time := seconds + (minutes * 60) + (hours * 60 * 60)
|
||||
return time
|
||||
|
||||
|
||||
|
||||
func to_csv_line() -> PoolStringArray:
|
||||
return PoolStringArray([
|
||||
name,
|
||||
start_time,
|
||||
end_time,
|
||||
str(get_elapsed_seconds()) if closed else tr(Consts.ONGOING)
|
||||
str(get_elapsed_seconds()) if is_closed else tr(Consts.ONGOING)
|
||||
])
|
||||
|
||||
static func is_csv_line_valid(line: PackedStringArray) -> bool:
|
||||
|
||||
static func is_csv_line_valid(line: PoolStringArray) -> bool:
|
||||
return line.size() > 3
|
||||
|
||||
|
||||
func from_csv_line(line: PackedStringArray) -> TimeEntry:
|
||||
func from_csv_line(line: PoolStringArray) -> TimeEntry:
|
||||
name = line[0]
|
||||
|
||||
var start_time_string = line[1]
|
||||
# warning-ignore:return_value_discarded
|
||||
start_time.from_string(start_time_string)
|
||||
|
||||
var elapsed_seconds = int(line[3]) if line[3].is_valid_int() else 0
|
||||
closed = elapsed_seconds > 0
|
||||
var elapsed_seconds = int(line[3]) if line[3].is_valid_integer() else 0
|
||||
is_closed = elapsed_seconds > 0
|
||||
|
||||
if closed == true:
|
||||
if is_closed == true:
|
||||
var end_time_string = line[2]
|
||||
# warning-ignore:return_value_discarded
|
||||
end_time.from_string(end_time_string)
|
||||
else:
|
||||
# warning-ignore:return_value_discarded
|
||||
end_time.from_current_time()
|
||||
return self
|
||||
|
||||
|
||||
func to_dict() -> Dictionary:
|
||||
return {
|
||||
start_time = start_time,
|
||||
closed = is_closed,
|
||||
}
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
return "%s\t%s\t%s"%[name, Consts.ONGOING if closed == false else "", start_time]
|
||||
return "%s\t%s\t%s"%[name, tr(Consts.ONGOING) if is_closed == false else end_time.to_string(), start_time]
|
||||
|
77
scripts/time_entry_tree_item.gd
Normal file
@ -0,0 +1,77 @@
|
||||
class_name TimeEntryTreeItem
|
||||
|
||||
signal end_time_updated
|
||||
|
||||
var time_entry: TimeEntry
|
||||
var children := {}
|
||||
var time_entries := []
|
||||
|
||||
|
||||
func get_child(parts: Array, or_create := false):
|
||||
# workaround for cyclic dependencies bug
|
||||
var TimeEntryTreeItem = load("res://scripts/time_entry_tree_item.gd")
|
||||
if parts.size() == 0:
|
||||
return self
|
||||
var part: String = parts.pop_front()
|
||||
if not children.has(part):
|
||||
if or_create == false:
|
||||
return null
|
||||
var time_entry_tree_item = TimeEntryTreeItem.new()
|
||||
# warning-ignore:return_value_discarded
|
||||
time_entry_tree_item.connect("end_time_updated", self, "_on_end_time_updated")
|
||||
children[part] = time_entry_tree_item
|
||||
return children[part].get_child(parts, or_create)
|
||||
|
||||
|
||||
func find_active_time_entry() -> TimeEntry:
|
||||
for _time_entry_tree_item in time_entries:
|
||||
var time_entry_tree_item := _time_entry_tree_item as TimeEntryTreeItem
|
||||
var current_time_entry := time_entry_tree_item.time_entry
|
||||
if not current_time_entry.is_closed:
|
||||
return current_time_entry
|
||||
return null
|
||||
|
||||
|
||||
func append(new_time_entry: TimeEntry) -> void:
|
||||
var TimeEntryTreeItem = load("res://scripts/time_entry_tree_item.gd")
|
||||
var time_entry_tree_item = TimeEntryTreeItem.new()
|
||||
time_entry_tree_item.time_entry = new_time_entry
|
||||
time_entries.append(time_entry_tree_item)
|
||||
|
||||
|
||||
func _on_end_time_updated() -> void:
|
||||
emit_signal("end_time_updated")
|
||||
|
||||
|
||||
func get_elapsed_seconds() -> int:
|
||||
var seconds := time_entry.get_elapsed_seconds() if time_entry != null else 0
|
||||
for child_name in children:
|
||||
var child = children[child_name]
|
||||
seconds += child.get_elapsed_seconds()
|
||||
for entry in time_entries:
|
||||
seconds += entry.get_elapsed_seconds()
|
||||
return seconds
|
||||
|
||||
|
||||
func get_period() -> String:
|
||||
var time_in_secs := get_elapsed_seconds()
|
||||
return TimeEntry.time_to_period(time_in_secs)
|
||||
|
||||
|
||||
func to_dict() -> Dictionary:
|
||||
var json := {}
|
||||
var times := []
|
||||
if time_entries.size() > 0:
|
||||
for entry in time_entries:
|
||||
times.append(entry.to_dict())
|
||||
json["__time"] = times
|
||||
if children.size() > 0:
|
||||
for name in children:
|
||||
json[name] = children[name].to_dict()
|
||||
return json
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
var json := to_dict()
|
||||
var json_string := JSON.print(json, "\t")
|
||||
return json_string
|
@ -2,19 +2,21 @@ class_name TimeSheet
|
||||
|
||||
|
||||
var source_path := ""
|
||||
var entries: Array[TimeEntry] = []
|
||||
var entries_names := {}
|
||||
var current_entry: TimeEntry
|
||||
var entries := []
|
||||
# warning-ignore:integer_division
|
||||
var _last_update := Time.get_ticks_msec() / 1000
|
||||
|
||||
## Loads the data file
|
||||
func load_file() -> bool:
|
||||
var file := FileAccess.open(source_path, FileAccess.READ)
|
||||
if file == null:
|
||||
file = FileAccess.open(source_path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
var file := File.new()
|
||||
var success := file.open(source_path, File.READ)
|
||||
if success != OK:
|
||||
success = file.open(source_path, File.WRITE)
|
||||
if success != OK:
|
||||
printerr("Failed to open file %s"%[ProjectSettings.globalize_path(source_path)])
|
||||
return false
|
||||
return true
|
||||
|
||||
while not file.eof_reached():
|
||||
var line := file.get_csv_line()
|
||||
if line.size() == 0 or "".join(line).length() == 0:
|
||||
@ -24,63 +26,145 @@ func load_file() -> bool:
|
||||
continue
|
||||
var entry := TimeEntry.new().from_csv_line(line)
|
||||
entries.append(entry)
|
||||
if entry.closed == false:
|
||||
current_entry = entry
|
||||
if not entries_names.has(entry.name):
|
||||
entries_names[entry.name] = 0
|
||||
entries_names[entry.name] += entry.get_elapsed_seconds()
|
||||
file.close()
|
||||
|
||||
return true
|
||||
|
||||
|
||||
func get_active_entry_from_name(task_name: String) -> TimeEntry:
|
||||
for _entry in entries:
|
||||
var current_time_entry := _entry as TimeEntry
|
||||
if current_time_entry.name == task_name and not current_time_entry.is_closed:
|
||||
return current_time_entry
|
||||
return null
|
||||
|
||||
|
||||
## Adds a new time entry to the tree and to the data file
|
||||
func start_entry(entry_name: String) -> void:
|
||||
current_entry = TimeEntry.new().start_recording()
|
||||
func add_entry(entry_name: String) -> TimeEntry:
|
||||
var current_entry := TimeEntry.new().start_recording()
|
||||
current_entry.name = entry_name
|
||||
current_entry.closed = false
|
||||
if entry_name in entries_names:
|
||||
current_entry.previous_total = entries_names[entry_name]
|
||||
var file := FileAccess.open(source_path, FileAccess.READ_WRITE)
|
||||
if file == null:
|
||||
current_entry.is_closed = false
|
||||
var file := File.new()
|
||||
var success := file.open(source_path, File.READ_WRITE)
|
||||
if success != OK:
|
||||
printerr("Could not open file")
|
||||
return
|
||||
return null
|
||||
file.seek_end()
|
||||
entries.append(current_entry)
|
||||
file.store_csv_line(current_entry.to_csv_line())
|
||||
return current_entry
|
||||
|
||||
|
||||
func stop_entry(entry_name: String, do_save := true) -> bool:
|
||||
for _entry in entries:
|
||||
var current_time_entry := _entry as TimeEntry
|
||||
if current_time_entry.name == entry_name and not current_time_entry.is_closed:
|
||||
current_time_entry.close()
|
||||
if do_save:
|
||||
save()
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func toggle_entry(entry_name: String, do_save := true) -> void:
|
||||
if stop_entry(entry_name, do_save):
|
||||
return
|
||||
else:
|
||||
# warning-ignore:return_value_discarded
|
||||
add_entry(entry_name)
|
||||
|
||||
|
||||
func update() -> void:
|
||||
current_entry.update()
|
||||
entries_names[current_entry.name] = current_entry.get_total_elapsed_seconds()
|
||||
|
||||
|
||||
func close_entry() -> void:
|
||||
current_entry.closed = true
|
||||
save()
|
||||
|
||||
|
||||
func get_period() -> String:
|
||||
return current_entry.get_period()
|
||||
|
||||
|
||||
func get_total_elapsed_seconds() -> int:
|
||||
return current_entry.get_total_elapsed_seconds()
|
||||
# warning-ignore:integer_division
|
||||
var current_time := Time.get_ticks_msec() / 1000
|
||||
if current_time == _last_update:
|
||||
return
|
||||
_last_update = current_time
|
||||
for entry in entries:
|
||||
var time_entry := entry as TimeEntry
|
||||
if time_entry.is_closed == false:
|
||||
time_entry.update()
|
||||
|
||||
|
||||
func save() -> void:
|
||||
var file := FileAccess.open(source_path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
var file := File.new()
|
||||
var success := file.open(source_path, File.WRITE)
|
||||
if success != OK:
|
||||
printerr("Could not open file")
|
||||
return
|
||||
for time_entry in entries:
|
||||
file.store_csv_line(time_entry.to_csv_line())
|
||||
|
||||
|
||||
func make_items_tree() -> TimeEntryTreeItem:
|
||||
var sorted_entries := EntrySorter.new(entries).sort_by([["name"], ["start_date", true]]).entries
|
||||
|
||||
var tree := TimeEntryTreeItem.new()
|
||||
for entry_index in sorted_entries.size():
|
||||
var entry := sorted_entries[entry_index] as TimeEntry
|
||||
var parts := entry.name.split("/")
|
||||
var repo: TimeEntryTreeItem = tree.get_child(parts, true)
|
||||
repo.append(entry)
|
||||
return tree
|
||||
|
||||
|
||||
static func restore(file_path: String) -> TimeSheet:
|
||||
var timesheet := TimeSheet.new()
|
||||
var timesheet = load("res://scripts/time_sheet.gd").new()
|
||||
timesheet.source_path = file_path
|
||||
var success := timesheet.load_file()
|
||||
var success: bool = timesheet.load_file()
|
||||
if success:
|
||||
return timesheet
|
||||
return null
|
||||
|
||||
class EntrySorter:
|
||||
|
||||
var entries: Array
|
||||
var _sorters := PoolStringArray()
|
||||
|
||||
func _init(initial_entries: Array) -> void:
|
||||
entries = initial_entries.duplicate()
|
||||
|
||||
func by_name(reverse := false) -> EntrySorter:
|
||||
return sort_by_one("name", reverse)
|
||||
|
||||
func by_date(reverse := false) -> EntrySorter:
|
||||
return sort_by_one("date", reverse)
|
||||
|
||||
func sort_by_one(property: String, reverse := false) -> EntrySorter:
|
||||
var method_name := "_by_%s"%[property]
|
||||
assert(has_method(method_name), "%s is not a valid sorting property"%[property])
|
||||
entries.sort_custom(self, method_name)
|
||||
if reverse:
|
||||
entries.invert()
|
||||
return self
|
||||
|
||||
func sort_by(initial_sorters: Array) -> EntrySorter:
|
||||
for item in initial_sorters:
|
||||
var property = item[0]
|
||||
var reversed = item[1] if item.size() > 1 else false
|
||||
var method_name := "_by_%s"%[property]
|
||||
assert(has_method(method_name), "%s is not a valid sorting property"%[property])
|
||||
assert(reversed == null or reversed is bool, "The second item is not a boolean")
|
||||
return self
|
||||
_sorters = initial_sorters
|
||||
entries.sort_custom(self, "__by_multiple")
|
||||
_sorters = PoolStringArray()
|
||||
return self
|
||||
|
||||
func __by_multiple(a: TimeEntry, b: TimeEntry) -> bool:
|
||||
for item in _sorters:
|
||||
var property = item[0]
|
||||
var reversed = item[1]
|
||||
var method_name := "_by_%s"%[property]
|
||||
var result: bool = call(method_name, a, b)
|
||||
if reversed:
|
||||
result = not result
|
||||
if result == false:
|
||||
return false
|
||||
return true
|
||||
|
||||
func _by_name(a: TimeEntry, b: TimeEntry) -> bool:
|
||||
return a.name < b.name
|
||||
|
||||
func _by_date(a: TimeEntry, b: TimeEntry) -> bool:
|
||||
return a.start_time < b.start_time
|
||||
|
@ -50,5 +50,24 @@ func from_string(time_string: String) -> TimeStamp:
|
||||
return from_dict(time)
|
||||
|
||||
|
||||
func from_unix_time(unix_time: int) -> TimeStamp:
|
||||
var time := Time.get_datetime_dict_from_unix_time(unix_time)
|
||||
return from_dict(time)
|
||||
|
||||
|
||||
func equals(other) -> bool:
|
||||
return (
|
||||
other.year == year and \
|
||||
other.month == month and \
|
||||
other.day == day and \
|
||||
other.weekday == weekday and \
|
||||
other.hour == hour and \
|
||||
other.minute == minute and \
|
||||
other.second == second and \
|
||||
other.unix == unix
|
||||
)
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
return Time.get_datetime_string_from_datetime_dict(to_dict(), false)
|
||||
|
||||
|
118
ui/Main.gd
@ -1,118 +0,0 @@
|
||||
extends Control
|
||||
|
||||
|
||||
@onready var time_label: Label = %TimeLabel
|
||||
@onready var start_button: Button = %StartButton
|
||||
@onready var task_name_line_edit: LineEdit = %TaskNameLineEdit
|
||||
@onready var time_entries_items_tree: TimeEntriesItemsTree = %TimeEntriesItemsTree
|
||||
@onready var timer: Timer = %Timer
|
||||
@onready var tasks_button: Button = %TasksButton
|
||||
@onready var settings_button: Button = %SettingsButton
|
||||
@onready var previous_tasks_window: Window = %PreviousTasksWindow
|
||||
@onready var settings_window: Window = %SettingsWindow
|
||||
@onready var audio_stream_player: AudioStreamPlayer = %AudioStreamPlayer
|
||||
|
||||
|
||||
var config: ConfigManager = preload("res://config_manager.tres")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
||||
config.file_changed.connect(set_initial_state)
|
||||
config.theme_changed.connect(
|
||||
func theme_changed() -> void:
|
||||
theme = config.them
|
||||
)
|
||||
|
||||
get_tree().set_auto_accept_quit(false)
|
||||
|
||||
previous_tasks_window.hide()
|
||||
settings_window.hide()
|
||||
|
||||
timer.timeout.connect(
|
||||
func on_timer_timeout() -> void:
|
||||
config.timesheet.update()
|
||||
|
||||
time_label.text = config.timesheet.get_period()
|
||||
|
||||
var total_elapsed: int = config.timesheet.get_total_elapsed_seconds()
|
||||
time_entries_items_tree.set_time_elapsed(total_elapsed)
|
||||
)
|
||||
|
||||
start_button.tooltip_text = tr(Consts.START)
|
||||
start_button.toggle_mode = true
|
||||
start_button.toggled.connect(
|
||||
func start(is_on: bool) -> void:
|
||||
if config.sound_fx_on:
|
||||
audio_stream_player.play()
|
||||
if is_on:
|
||||
config.timesheet.start_entry(task_name_line_edit.text)
|
||||
set_button_as_started()
|
||||
else:
|
||||
config.timesheet.close_entry()
|
||||
set_button_as_stopped()
|
||||
)
|
||||
|
||||
|
||||
task_name_line_edit.text = config.last_task_name
|
||||
task_name_line_edit.text_changed.connect(
|
||||
func(new_text: String) -> void:
|
||||
config.last_task_name = new_text
|
||||
)
|
||||
time_entries_items_tree.item_selected.connect(
|
||||
func item_selected() -> void:
|
||||
task_name_line_edit.text = time_entries_items_tree.get_current_text()
|
||||
)
|
||||
|
||||
tasks_button.toggle_mode = true
|
||||
tasks_button.toggled.connect(
|
||||
func tasks_toggled(is_on: bool) -> void:
|
||||
previous_tasks_window.visible = is_on
|
||||
)
|
||||
previous_tasks_window.close_requested.connect(
|
||||
func close() -> void:
|
||||
tasks_button.set_pressed_no_signal(false)
|
||||
previous_tasks_window.hide()
|
||||
)
|
||||
|
||||
settings_button.toggle_mode = true
|
||||
settings_button.toggled.connect(
|
||||
func settings_toggled(is_on: bool) -> void:
|
||||
settings_window.visible = is_on
|
||||
)
|
||||
settings_window.close_requested.connect(
|
||||
func close() -> void:
|
||||
settings_button.set_pressed_no_signal(false)
|
||||
settings_window.hide()
|
||||
)
|
||||
|
||||
|
||||
|
||||
func set_button_as_started() -> void:
|
||||
time_entries_items_tree.set_current_item(config.timesheet.current_entry.name)
|
||||
start_button.tooltip_text = tr(Consts.STOP)
|
||||
start_button.theme_type_variation = Consts.THEME_OVERRIDE_STOP
|
||||
timer.start()
|
||||
|
||||
|
||||
func set_button_as_stopped() -> void:
|
||||
start_button.tooltip_text = tr(Consts.START)
|
||||
time_label.text = Consts.NO_TIME
|
||||
start_button.theme_type_variation = Consts.THEME_OVERRIDE_START
|
||||
timer.stop()
|
||||
|
||||
|
||||
func set_initial_state() -> void:
|
||||
if config.timesheet.current_entry != null and config.timesheet.current_entry.closed == false:
|
||||
start_button.set_pressed_no_signal(true)
|
||||
set_button_as_started()
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
## Unused; if a manual quit button is added, this would be used
|
||||
func quit() -> void:
|
||||
get_tree().notification(NOTIFICATION_WM_CLOSE_REQUEST)
|
132
ui/Main.tscn
@ -1,132 +0,0 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://bmlciwscreowf"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://bd8ancgbfsvmd" path="res://assets/default_theme.theme" id="1_2s8h2"]
|
||||
[ext_resource type="Script" path="res://ui/Main.gd" id="2_sl5q6"]
|
||||
[ext_resource type="Script" path="res://ui/time_entries_items_tree.gd" id="3_oxqux"]
|
||||
[ext_resource type="PackedScene" uid="uid://b07v41toqw355" path="res://ui/settings.tscn" id="4_4fa2j"]
|
||||
[ext_resource type="AudioStream" uid="uid://cdsbhoidgyx70" path="res://assets/pop.ogg" id="4_6ajaq"]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_guuii"]
|
||||
device = -1
|
||||
pressed = true
|
||||
keycode = 32
|
||||
unicode = 32
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_irhvi"]
|
||||
events = [SubResource("InputEventKey_guuii")]
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_2s8h2")
|
||||
theme_type_variation = &"background"
|
||||
|
||||
[node name="Main" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_top = 5
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_bottom = 5
|
||||
script = ExtResource("2_sl5q6")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Main"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="Main/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SettingsButton" type="Button" parent="Main/VBoxContainer/HBoxContainer2"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Settings"
|
||||
theme_type_variation = &"settings_button"
|
||||
icon_alignment = 1
|
||||
|
||||
[node name="TaskNameLineEdit" type="LineEdit" parent="Main/VBoxContainer/HBoxContainer2"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Task Name. Use \"/\" to create subtasks"
|
||||
caret_blink = true
|
||||
caret_blink_interval = 0.5
|
||||
|
||||
[node name="TasksButton" type="Button" parent="Main/VBoxContainer/HBoxContainer2"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Tasks"
|
||||
theme_type_variation = &"tasks_button"
|
||||
icon_alignment = 1
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Main/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="TimeLabel" type="Label" parent="Main/VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
theme_type_variation = &"time_label"
|
||||
text = "00:00:00"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="StartButton" type="Button" parent="Main/VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"play_button"
|
||||
shortcut = SubResource("Shortcut_irhvi")
|
||||
|
||||
[node name="Timer" type="Timer" parent="Main"]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="PreviousTasksWindow" type="Window" parent="Main"]
|
||||
unique_name_in_owner = true
|
||||
title = "Tasks"
|
||||
size = Vector2i(300, 300)
|
||||
visible = false
|
||||
always_on_top = true
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="Main/PreviousTasksWindow"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_type_variation = &"background"
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Main/PreviousTasksWindow/PanelContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/margin_left = 20
|
||||
theme_override_constants/margin_top = 20
|
||||
theme_override_constants/margin_right = 20
|
||||
theme_override_constants/margin_bottom = 20
|
||||
|
||||
[node name="TimeEntriesItemsTree" type="Tree" parent="Main/PreviousTasksWindow/PanelContainer/MarginContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
columns = 2
|
||||
script = ExtResource("3_oxqux")
|
||||
|
||||
[node name="SettingsWindow" type="Window" parent="Main"]
|
||||
unique_name_in_owner = true
|
||||
title = "Settings"
|
||||
size = Vector2i(300, 300)
|
||||
visible = false
|
||||
always_on_top = true
|
||||
|
||||
[node name="Settings" parent="Main/SettingsWindow" instance=ExtResource("4_4fa2j")]
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
stream = ExtResource("4_6ajaq")
|
51
ui/composed_ui.gd
Normal file
@ -0,0 +1,51 @@
|
||||
extends Control
|
||||
|
||||
var config: ConfigManager = preload("res://config_manager.tres")
|
||||
|
||||
onready var v_box_container_primary := $"%VBoxContainerPrimary" as VBoxContainer
|
||||
onready var toggle_tab_container_button := $"%ToggleTabContainerButton" as Button
|
||||
onready var tab_container := $"%TabContainer" as TabContainer
|
||||
|
||||
func _ready() -> void:
|
||||
toggle_tab_container_button.toggle_mode = true
|
||||
# warning-ignore:return_value_discarded
|
||||
toggle_tab_container_button.connect("toggled", self, "_on_toggle_tab_container_button_toggled")
|
||||
tab_container.visible = toggle_tab_container_button.pressed
|
||||
OS.window_position = config.last_window_position
|
||||
set_window_size()
|
||||
get_tree().get_root().set_transparent_background(true)
|
||||
|
||||
|
||||
func _on_toggle_tab_container_button_toggled(is_toggled: bool) -> void:
|
||||
tab_container.visible = is_toggled
|
||||
set_window_size()
|
||||
|
||||
|
||||
func set_window_size() -> void:
|
||||
var style_box := theme.get_stylebox("panel", "background") as StyleBoxFlat
|
||||
var margin := style_box.content_margin_bottom * 2
|
||||
OS.window_size = v_box_container_primary.rect_size + Vector2.ONE * margin
|
||||
if tab_container.visible:
|
||||
OS.window_size.y += tab_container.rect_size.y
|
||||
|
||||
|
||||
var mouse_button_is_pressed := false
|
||||
var dragging_start_position = Vector2()
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
if event.get_button_index() == 1:
|
||||
mouse_button_is_pressed = !mouse_button_is_pressed
|
||||
dragging_start_position = get_local_mouse_position()
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if mouse_button_is_pressed:
|
||||
OS.set_window_position(OS.window_position + get_global_mouse_position() - dragging_start_position)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
match what:
|
||||
MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
|
||||
config.last_window_position = OS.window_position
|
||||
get_tree().quit()
|
104
ui/composed_ui.tscn
Normal file
@ -0,0 +1,104 @@
|
||||
[gd_scene load_steps=6 format=2]
|
||||
|
||||
[ext_resource path="res://assets/default_theme.theme" type="Theme" id=1]
|
||||
[ext_resource path="res://ui/tasks_list.tscn" type="PackedScene" id=2]
|
||||
[ext_resource path="res://ui/time_counter.tscn" type="PackedScene" id=3]
|
||||
[ext_resource path="res://ui/settings.tscn" type="PackedScene" id=4]
|
||||
[ext_resource path="res://ui/composed_ui.gd" type="Script" id=5]
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
theme = ExtResource( 1 )
|
||||
theme_type_variation = "background"
|
||||
script = ExtResource( 5 )
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
margin_left = 5.0
|
||||
margin_top = 5.0
|
||||
margin_right = 355.0
|
||||
margin_bottom = 755.0
|
||||
|
||||
[node name="VBoxContainerPrimary" type="VBoxContainer" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
margin_right = 350.0
|
||||
margin_bottom = 142.0
|
||||
|
||||
[node name="TimeCounter" parent="VBoxContainer/VBoxContainerPrimary" instance=ExtResource( 3 )]
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_right = 350.0
|
||||
margin_bottom = 142.0
|
||||
|
||||
[node name="HBoxContainer2" parent="VBoxContainer/VBoxContainerPrimary/TimeCounter" index="0"]
|
||||
margin_right = 350.0
|
||||
margin_bottom = 40.0
|
||||
|
||||
[node name="ToggleTabContainerButton" type="Button" parent="VBoxContainer/VBoxContainerPrimary/TimeCounter/HBoxContainer2" index="0"]
|
||||
unique_name_in_owner = true
|
||||
margin_right = 40.0
|
||||
margin_bottom = 40.0
|
||||
theme_type_variation = "panel_toggle_button"
|
||||
toggle_mode = true
|
||||
|
||||
[node name="TaskNameLineEdit" parent="VBoxContainer/VBoxContainerPrimary/TimeCounter/HBoxContainer2" index="1"]
|
||||
margin_left = 48.0
|
||||
margin_right = 350.0
|
||||
margin_bottom = 40.0
|
||||
|
||||
[node name="HBoxContainer" parent="VBoxContainer/VBoxContainerPrimary/TimeCounter" index="1"]
|
||||
margin_top = 47.0
|
||||
margin_right = 350.0
|
||||
margin_bottom = 142.0
|
||||
|
||||
[node name="TimeLabel" parent="VBoxContainer/VBoxContainerPrimary/TimeCounter/HBoxContainer" index="0"]
|
||||
margin_top = 0.0
|
||||
margin_right = 270.0
|
||||
margin_bottom = 95.0
|
||||
|
||||
[node name="StartButton" parent="VBoxContainer/VBoxContainerPrimary/TimeCounter/HBoxContainer" index="1"]
|
||||
margin_left = 278.0
|
||||
margin_right = 350.0
|
||||
margin_bottom = 95.0
|
||||
|
||||
[node name="TabContainer" type="TabContainer" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
margin_top = 149.0
|
||||
margin_right = 350.0
|
||||
margin_bottom = 750.0
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Task List" type="VBoxContainer" parent="VBoxContainer/TabContainer"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
margin_left = 5.0
|
||||
margin_top = 46.0
|
||||
margin_right = -5.0
|
||||
margin_bottom = -5.0
|
||||
|
||||
[node name="TasksList" parent="VBoxContainer/TabContainer/Task List" instance=ExtResource( 2 )]
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_right = 340.0
|
||||
margin_bottom = 550.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Settings" type="ScrollContainer" parent="VBoxContainer/TabContainer"]
|
||||
visible = false
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
margin_left = 4.0
|
||||
margin_top = 32.0
|
||||
margin_right = -4.0
|
||||
margin_bottom = -4.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Settings" parent="VBoxContainer/TabContainer/Settings" instance=ExtResource( 4 )]
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_right = 342.0
|
||||
margin_bottom = 565.0
|
||||
|
||||
[editable path="VBoxContainer/VBoxContainerPrimary/TimeCounter"]
|
145
ui/settings.gd
@ -1,59 +1,124 @@
|
||||
extends PanelContainer
|
||||
|
||||
extends Control
|
||||
|
||||
var config: ConfigManager = preload("res://config_manager.tres")
|
||||
|
||||
|
||||
@onready var file_path_file_dialog: FileDialog = %FilePathFileDialog
|
||||
@onready var file_path_line_edit: LineEdit = %FilePathLineEdit
|
||||
@onready var file_path_button: Button = %FilePathButton
|
||||
@onready var theme_path_file_dialog: FileDialog = %ThemePathFileDialog
|
||||
@onready var theme_path_button: Button = %ThemePathButton
|
||||
@onready var sound_check_box: CheckBox = %SoundCheckBox
|
||||
@onready var attributions_rich_text_label: RichTextLabel = %AttributionsRichTextLabel
|
||||
@onready var open_data_dir_button: Button = %OpenDataDirButton
|
||||
onready var file_path_line_edit: LineEdit = $"%FilePathLineEdit" as LineEdit
|
||||
onready var file_path_button: Button = $"%FilePathButton" as Button
|
||||
onready var theme_path_button: Button = $"%ThemePathButton" as Button
|
||||
onready var sound_check_box: CheckBox = $"%SoundCheckBox" as CheckBox
|
||||
onready var open_data_dir_button: Button = $"%OpenDataDirButton" as Button
|
||||
onready var file_path_open_button: Button = $"%FilePathOpenButton" as Button
|
||||
onready var attributions_rich_text_label: RichTextLabel = $"%AttributionsRichTextLabel" as RichTextLabel
|
||||
onready var file_path_file_dialog: FileDialog = $"%FilePathFileDialog" as FileDialog
|
||||
onready var theme_path_file_dialog: FileDialog = $"%ThemePathFileDialog" as FileDialog
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
||||
config.theme_changed.connect(
|
||||
func set_current_theme() -> void:
|
||||
theme = config.theme
|
||||
)
|
||||
# warning-ignore:return_value_discarded
|
||||
config.connect("theme_changed", self, "_on_theme_changed")
|
||||
|
||||
config.file_changed.connect(
|
||||
func set_current_file() -> void:
|
||||
file_path_file_dialog.current_path = config.current_file
|
||||
file_path_file_dialog.current_dir = config.current_file.get_base_dir()
|
||||
file_path_line_edit.text = config.current_file
|
||||
)
|
||||
# warning-ignore:return_value_discarded
|
||||
config.connect("time_sheet_loaded", self, "_on_current_file_changed")
|
||||
|
||||
file_path_button.pressed.connect(
|
||||
file_path_file_dialog.popup_centered
|
||||
)
|
||||
# warning-ignore:return_value_discarded
|
||||
file_path_button.connect("pressed", self, "_on_file_path_button_pressed")
|
||||
|
||||
file_path_file_dialog.file_selected.connect(config.set_current_file)
|
||||
file_path_line_edit.text_submitted.connect(config.set_current_file)
|
||||
# warning-ignore:return_value_discarded
|
||||
file_path_file_dialog.connect("visibility_changed", self, "_resize_on_dialog", [file_path_file_dialog])
|
||||
# warning-ignore:return_value_discarded
|
||||
file_path_file_dialog.connect("file_selected", self, "_on_current_file_selected")
|
||||
|
||||
theme_path_button.pressed.connect(
|
||||
theme_path_file_dialog.popup_centered
|
||||
)
|
||||
theme_path_file_dialog.file_selected.connect(config.set_theme_path)
|
||||
# warning-ignore:return_value_discarded
|
||||
file_path_line_edit.connect("text_entered", self, "_on_current_file_selected")
|
||||
|
||||
# warning-ignore:return_value_discarded
|
||||
theme_path_button.connect("pressed", self, "_on_theme_path_button_pressed")
|
||||
|
||||
# warning-ignore:return_value_discarded
|
||||
theme_path_file_dialog.connect("visibility_changed", self, "_resize_on_dialog", [theme_path_file_dialog])
|
||||
# warning-ignore:return_value_discarded
|
||||
theme_path_file_dialog.connect("file_selected", self, "_on_new_theme_selected")
|
||||
|
||||
theme_path_file_dialog.hide()
|
||||
file_path_file_dialog.hide()
|
||||
|
||||
sound_check_box.button_pressed = config.sound_fx_on
|
||||
sound_check_box.toggled.connect(
|
||||
func sound_toggle(is_on: bool) -> void:
|
||||
config.sound_fx_on = is_on
|
||||
)
|
||||
|
||||
attributions_rich_text_label.meta_clicked.connect(OS.shell_open)
|
||||
sound_check_box.pressed = config.sound_fx_on
|
||||
|
||||
open_data_dir_button.pressed.connect(
|
||||
OS.shell_open.bind(OS.get_user_data_dir())
|
||||
)
|
||||
# warning-ignore:return_value_discarded
|
||||
sound_check_box.connect("toggled", self, "_on_sound_toggle")
|
||||
|
||||
# warning-ignore:return_value_discarded
|
||||
attributions_rich_text_label.connect("meta_clicked", self, "_on_attributions_rich_text_label_meta_clicked")
|
||||
|
||||
# warning-ignore:return_value_discarded
|
||||
open_data_dir_button.connect("pressed", self, "_on_open_data_dir_button_pressed")
|
||||
# warning-ignore:return_value_discarded
|
||||
file_path_open_button.connect("pressed", self, "_on_file_path_open_button_pressed")
|
||||
_on_current_file_changed()
|
||||
|
||||
|
||||
func _on_current_file_changed() -> void:
|
||||
#file_path_file_dialog.initial_path = config.current_timesheet_file_path.get_base_dir()
|
||||
file_path_line_edit.text = config.current_timesheet_file_path
|
||||
|
||||
|
||||
func _on_theme_changed() -> void:
|
||||
theme = config.theme
|
||||
|
||||
|
||||
func _on_file_path_button_pressed() -> void:
|
||||
_set_file_dialog_file_path(file_path_file_dialog, config.current_timesheet_file_path)
|
||||
|
||||
|
||||
func _on_current_file_selected(new_file: String) -> void:
|
||||
config.current_timesheet_file_path = new_file
|
||||
|
||||
|
||||
func _on_sound_toggle(is_on: bool) -> void:
|
||||
config.sound_fx_on = is_on
|
||||
|
||||
|
||||
func _on_theme_path_button_pressed() -> void:
|
||||
_set_file_dialog_file_path(theme_path_file_dialog, config.theme_file_path)
|
||||
theme_path_file_dialog.show()
|
||||
|
||||
|
||||
func _on_new_theme_selected(new_theme_path: String) -> void:
|
||||
config.theme_file_path = new_theme_path
|
||||
|
||||
|
||||
func _on_attributions_rich_text_label_meta_clicked(meta) -> void:
|
||||
if meta is String:
|
||||
# warning-ignore:return_value_discarded
|
||||
OS.shell_open(meta)
|
||||
|
||||
|
||||
func _on_open_data_dir_button_pressed() -> void:
|
||||
_open_path(OS.get_user_data_dir())
|
||||
|
||||
|
||||
func _on_file_path_open_button_pressed() -> void:
|
||||
_open_path(config.current_timesheet_file_path.get_base_dir())
|
||||
|
||||
|
||||
func _open_path(path: String):
|
||||
var canonical_path := ProjectSettings.globalize_path(path).strip_edges()
|
||||
# warning-ignore:return_value_discarded
|
||||
OS.shell_open("file://"+canonical_path)
|
||||
|
||||
func _set_file_dialog_file_path(dialog: FileDialog, path: String) -> void:
|
||||
#dialog.current_path = path
|
||||
#dialog.current_file = path
|
||||
dialog.current_dir = path.get_base_dir()
|
||||
dialog.show()
|
||||
dialog.invalidate()
|
||||
|
||||
var previous_window_size := OS.window_size
|
||||
|
||||
func _resize_on_dialog(dialog: Control) -> void:
|
||||
if dialog.visible == true:
|
||||
previous_window_size = OS.window_size
|
||||
OS.window_size = file_path_file_dialog.rect_size
|
||||
else:
|
||||
OS.window_size = previous_window_size
|
||||
|
166
ui/settings.tscn
@ -1,86 +1,101 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b07v41toqw355"]
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/settings.gd" id="1_cmilf"]
|
||||
[ext_resource path="res://ui/settings.gd" type="Script" id=1]
|
||||
|
||||
[node name="Settings" type="PanelContainer"]
|
||||
anchors_preset = 15
|
||||
[node name="Settings" type="MarginContainer"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_type_variation = &"background"
|
||||
script = ExtResource("1_cmilf")
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
margin_right = 505.0
|
||||
margin_bottom = 760.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/margin_left = 20
|
||||
theme_override_constants/margin_top = 20
|
||||
theme_override_constants/margin_right = 20
|
||||
theme_override_constants/margin_bottom = 20
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||
layout_mode = 2
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
margin_right = 505.0
|
||||
margin_bottom = 45.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||
margin_top = 10.0
|
||||
margin_right = 92.0
|
||||
margin_bottom = 35.0
|
||||
text = "File Path"
|
||||
|
||||
[node name="FilePathLineEdit" type="LineEdit" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||
[node name="FilePathLineEdit" type="LineEdit" parent="VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
margin_left = 100.0
|
||||
margin_right = 264.0
|
||||
margin_bottom = 45.0
|
||||
size_flags_horizontal = 3
|
||||
caret_blink = true
|
||||
caret_blink_interval = 0.5
|
||||
caret_blink_speed = 0.5
|
||||
|
||||
[node name="FilePathButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||
[node name="FilePathButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
margin_left = 272.0
|
||||
margin_right = 314.0
|
||||
margin_bottom = 45.0
|
||||
text = "..."
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = "Alternative theme
|
||||
"
|
||||
|
||||
[node name="ThemePathButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"]
|
||||
[node name="FilePathOpenButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
margin_left = 322.0
|
||||
margin_right = 505.0
|
||||
margin_bottom = 45.0
|
||||
text = "open directory"
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"]
|
||||
margin_top = 53.0
|
||||
margin_right = 505.0
|
||||
margin_bottom = 90.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer2"]
|
||||
margin_top = 6.0
|
||||
margin_right = 199.0
|
||||
margin_bottom = 31.0
|
||||
text = "Alternative Theme"
|
||||
|
||||
[node name="ThemePathButton" type="Button" parent="VBoxContainer/HBoxContainer2"]
|
||||
unique_name_in_owner = true
|
||||
margin_left = 207.0
|
||||
margin_right = 278.0
|
||||
margin_bottom = 37.0
|
||||
text = "load"
|
||||
|
||||
[node name="HBoxContainer3" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
[node name="HBoxContainer3" type="HBoxContainer" parent="VBoxContainer"]
|
||||
margin_top = 98.0
|
||||
margin_right = 505.0
|
||||
margin_bottom = 146.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="SoundCheckBox" type="CheckBox" parent="MarginContainer/VBoxContainer/HBoxContainer3"]
|
||||
[node name="SoundCheckBox" type="CheckBox" parent="VBoxContainer/HBoxContainer3"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
button_pressed = true
|
||||
text = "Sounds"
|
||||
margin_right = 134.0
|
||||
margin_bottom = 48.0
|
||||
text = "sounds"
|
||||
|
||||
[node name="OpenDataDirButton" type="Button" parent="MarginContainer/VBoxContainer"]
|
||||
[node name="OpenDataDirButton" type="Button" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "open data dir"
|
||||
margin_top = 154.0
|
||||
margin_right = 505.0
|
||||
margin_bottom = 191.0
|
||||
text = "Open data dir"
|
||||
|
||||
[node name="AttributionsRichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer"]
|
||||
[node name="AttributionsRichTextLabel" type="RichTextLabel" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
margin_top = 199.0
|
||||
margin_right = 505.0
|
||||
margin_bottom = 760.0
|
||||
size_flags_vertical = 3
|
||||
theme_type_variation = &"small_text"
|
||||
theme_type_variation = "small_text"
|
||||
bbcode_enabled = true
|
||||
text = "Font: Cairo, Designed by Mohamed Gaber, Accademia di Belle Arti di Urbino
|
||||
bbcode_text = "Font: Cairo, Designed by Mohamed Gaber, Accademia di Belle Arti di Urbino
|
||||
|
||||
Sound: [url]https://opengameart.org/content/bubbles-pop[/url]
|
||||
|
||||
@ -94,20 +109,43 @@ This game uses Godot Engine, available under the following license:
|
||||
|
||||
THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
"
|
||||
text = "Font: Cairo, Designed by Mohamed Gaber, Accademia di Belle Arti di Urbino
|
||||
|
||||
[node name="FilePathFileDialog" type="FileDialog" parent="."]
|
||||
unique_name_in_owner = true
|
||||
title = "Pick Tracker File"
|
||||
size = Vector2i(800, 600)
|
||||
ok_button_text = "Save"
|
||||
access = 2
|
||||
filters = PackedStringArray("*.csv ; Comma Separated Files")
|
||||
Sound: https://opengameart.org/content/bubbles-pop
|
||||
|
||||
[node name="ThemePathFileDialog" type="FileDialog" parent="."]
|
||||
This game uses Godot Engine, available under the following license:
|
||||
|
||||
Copyright (c) 2014-present Godot Engine contributors. Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
"
|
||||
|
||||
[node name="CanvasLayer" type="CanvasLayer" parent="."]
|
||||
|
||||
[node name="Popups" type="Control" parent="CanvasLayer"]
|
||||
margin_right = 40.0
|
||||
margin_bottom = 40.0
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="FilePathFileDialog" type="FileDialog" parent="CanvasLayer/Popups"]
|
||||
unique_name_in_owner = true
|
||||
title = "Open a File"
|
||||
size = Vector2i(800, 600)
|
||||
ok_button_text = "Open"
|
||||
file_mode = 0
|
||||
margin_right = 1092.0
|
||||
margin_bottom = 762.0
|
||||
window_title = "Select CSV"
|
||||
dialog_hide_on_ok = true
|
||||
access = 2
|
||||
filters = PackedStringArray("*.theme ; Theme Files")
|
||||
filters = PoolStringArray( "*.csv ; CSV Files" )
|
||||
|
||||
[node name="ThemePathFileDialog" type="FileDialog" parent="CanvasLayer/Popups"]
|
||||
unique_name_in_owner = true
|
||||
margin_right = 1092.0
|
||||
margin_bottom = 762.0
|
||||
window_title = "Select a Theme File"
|
||||
dialog_hide_on_ok = true
|
||||
mode = 0
|
||||
access = 2
|
||||
filters = PoolStringArray( "*.theme ; Theme Files" )
|
||||
|
155
ui/tasks_list.gd
Normal file
@ -0,0 +1,155 @@
|
||||
class_name TimeEntriesItemsTree extends Tree
|
||||
|
||||
enum COL{
|
||||
TEXT,
|
||||
TIME
|
||||
}
|
||||
|
||||
const META_KEY = "time_entry"
|
||||
|
||||
var config: ConfigManager = preload("res://config_manager.tres")
|
||||
|
||||
var _timer := Timer.new()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
hide_root = true
|
||||
add_child(_timer)
|
||||
# warning-ignore:return_value_discarded
|
||||
connect("button_pressed", self, "_on_button_pressed")
|
||||
# warning-ignore:return_value_discarded
|
||||
_timer.connect("timeout", self, "_on_timer_timeout")
|
||||
# warning-ignore:return_value_discarded
|
||||
config.connect("time_sheet_loaded", self, "populate_entries")
|
||||
# warning-ignore:return_value_discarded
|
||||
connect("item_edited", self, "on_Tree_item_edited")
|
||||
populate_entries()
|
||||
|
||||
|
||||
func on_Tree_item_edited():
|
||||
var tree_item := get_edited()
|
||||
var time_entry := tree_item.get_meta(META_KEY) as TimeEntry
|
||||
if time_entry == null:
|
||||
return
|
||||
|
||||
var edited_start_time_str := tree_item.get_text(COL.TEXT)
|
||||
var edited_start_time := TimeStamp.new().from_string(edited_start_time_str)
|
||||
if edited_start_time.year == 1970:
|
||||
_update_from_time_entry(time_entry, tree_item)
|
||||
return
|
||||
|
||||
var edited_duration_str := tree_item.get_text(COL.TIME)
|
||||
var edited_duration := TimeEntry.period_to_time(edited_duration_str)
|
||||
if edited_duration == -1:
|
||||
_update_from_time_entry(time_entry, tree_item)
|
||||
return
|
||||
|
||||
var edited_end_time_unix := edited_start_time.unix + edited_duration
|
||||
var edited_end_time := TimeStamp.new().from_unix_time(edited_end_time_unix)
|
||||
|
||||
if edited_start_time.equals(time_entry.start_time) \
|
||||
and edited_end_time.equals(time_entry.end_time):
|
||||
_update_from_time_entry(time_entry, tree_item)
|
||||
return
|
||||
|
||||
time_entry.start_time = edited_start_time
|
||||
time_entry.end_time = edited_end_time
|
||||
config.timesheet.save()
|
||||
|
||||
|
||||
|
||||
func populate_entries() -> void:
|
||||
clear()
|
||||
var tree_items_root := create_item()
|
||||
var item_entries_tree := config.timesheet.make_items_tree()
|
||||
_populate_from_entry(tree_items_root, item_entries_tree)
|
||||
_timer.start()
|
||||
|
||||
|
||||
func _on_timer_timeout() -> void:
|
||||
config.timesheet.update()
|
||||
|
||||
|
||||
func _populate_from_entry(tree_item_root: TreeItem, time_entry_item_root: TimeEntryTreeItem):
|
||||
var children := time_entry_item_root.children
|
||||
for time_entry_name in children:
|
||||
var time_entry_item: TimeEntryTreeItem = children[time_entry_name]
|
||||
var item := find_or_create_item(tree_item_root, time_entry_name)
|
||||
item.set_metadata(COL.TEXT, time_entry_name)
|
||||
item.set_text(COL.TIME, time_entry_item.get_period())
|
||||
# warning-ignore:return_value_discarded
|
||||
time_entry_item.connect("end_time_updated", self, "_on_time_entry_changed_update_item", [time_entry_item, item])
|
||||
_populate_from_entry(item, time_entry_item)
|
||||
var has_at_least_one_running_entry := time_entry_item.find_active_time_entry() != null
|
||||
var texture := preload("res://assets/stop_small.svg") \
|
||||
if has_at_least_one_running_entry \
|
||||
else preload("res://assets/play_small.svg")
|
||||
item.add_button(COL.TIME, texture)
|
||||
var entries = time_entry_item_root.time_entries
|
||||
for entry_item in entries:
|
||||
var time_entry_item := entry_item as TimeEntryTreeItem
|
||||
var item := create_item(tree_item_root)
|
||||
var time_entry := time_entry_item.time_entry
|
||||
item.set_meta(META_KEY, time_entry)
|
||||
item.set_metadata(COL.TEXT, time_entry.name)
|
||||
item.set_editable(COL.TEXT, true)
|
||||
item.set_editable(COL.TIME, true)
|
||||
|
||||
_update_from_time_entry(time_entry, item)
|
||||
# warning-ignore:return_value_discarded
|
||||
time_entry_item.connect("end_time_updated", self, "_on_time_entry_changed_update_item", [time_entry_item, item])
|
||||
|
||||
|
||||
func _update_from_time_entry(time_entry: TimeEntry, item: TreeItem) -> void:
|
||||
|
||||
item.set_text(COL.TEXT, time_entry.start_time.to_string())
|
||||
item.set_text(COL.TIME, time_entry.get_period())
|
||||
if time_entry.is_closed == false:
|
||||
if item.get_button_count(COL.TIME) < 1:
|
||||
var texture := preload("res://assets/stop_small.svg")
|
||||
item.add_button(COL.TIME, texture, 0)
|
||||
else:
|
||||
if item.get_button_count(COL.TIME) > 0:
|
||||
item.erase_button(COL.TIME, 0)
|
||||
|
||||
|
||||
func _on_time_entry_changed_update_item(time_entry_item: TimeEntryTreeItem, item: TreeItem) -> void:
|
||||
item.set_text(COL.TIME, time_entry_item.get_period())
|
||||
|
||||
|
||||
func _on_button_pressed(item: TreeItem, _column: int, _id: int) -> void:
|
||||
var task_name: String = item.get_metadata(COL.TEXT)
|
||||
if task_name == "":
|
||||
return
|
||||
config.timesheet.toggle_entry(task_name)
|
||||
|
||||
|
||||
## Unecessary in Godot 4, can bre replaced with get_children()
|
||||
static func _get_tree_item_children(item: TreeItem):
|
||||
var children = []
|
||||
var child = item.get_children()
|
||||
if child == null:
|
||||
return children
|
||||
children.append(child)
|
||||
child = child.get_next()
|
||||
while child != null:
|
||||
children.append(child)
|
||||
child = child.get_next()
|
||||
return children
|
||||
|
||||
|
||||
## Finds an item in the tree by text
|
||||
func find_item(root: TreeItem, item_name: String) -> TreeItem:
|
||||
for child in _get_tree_item_children(root):
|
||||
if child.get_text(COL.TEXT) == item_name:
|
||||
return child
|
||||
return null
|
||||
|
||||
|
||||
func find_or_create_item(root: TreeItem, item_name: String) -> TreeItem:
|
||||
var child := find_item(root, item_name)
|
||||
if child != null:
|
||||
return child
|
||||
child = create_item(root)
|
||||
child.set_text(COL.TEXT, item_name)
|
||||
return child
|
9
ui/tasks_list.tscn
Normal file
@ -0,0 +1,9 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://ui/tasks_list.gd" type="Script" id=1]
|
||||
|
||||
[node name="TasksList" type="Tree"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
columns = 2
|
||||
script = ExtResource( 1 )
|
74
ui/time_counter.gd
Normal file
@ -0,0 +1,74 @@
|
||||
extends VBoxContainer
|
||||
|
||||
|
||||
var config: ConfigManager = preload("res://config_manager.tres")
|
||||
|
||||
|
||||
onready var task_name_line_edit := $"%TaskNameLineEdit" as LineEdit
|
||||
onready var time_label := $"%TimeLabel" as Label
|
||||
onready var start_button := $"%StartButton" as Button
|
||||
onready var timer := $"%Timer" as Timer
|
||||
onready var audio_stream_player := $"%AudioStreamPlayer" as AudioStreamPlayer
|
||||
|
||||
var current_time_entry: TimeEntry
|
||||
|
||||
func _ready() -> void:
|
||||
# warning-ignore:return_value_discarded
|
||||
timer.connect("timeout", self, "_on_timer_timeout")
|
||||
start_button.hint_tooltip = tr(Consts.START)
|
||||
start_button.toggle_mode = true
|
||||
# warning-ignore:return_value_discarded
|
||||
start_button.connect("toggled", self, "_on_start_button_toggled")
|
||||
task_name_line_edit.text = config.current_task_name
|
||||
# warning-ignore:return_value_discarded
|
||||
task_name_line_edit.connect("text_changed", self, "_on_task_name_line_edit_text_changed")
|
||||
# warning-ignore:return_value_discarded
|
||||
config.connect("time_sheet_loaded", self, "_on_time_sheet_loaded")
|
||||
update_timer_state()
|
||||
|
||||
|
||||
func _on_timer_timeout() -> void:
|
||||
config.timesheet.update()
|
||||
time_label.text = current_time_entry.get_period()
|
||||
|
||||
|
||||
func _on_start_button_toggled(_is_on: bool) -> void:
|
||||
if config.sound_fx_on:
|
||||
audio_stream_player.play()
|
||||
if current_time_entry != null:
|
||||
# warning-ignore:return_value_discarded
|
||||
config.timesheet.stop_entry(task_name_line_edit.text)
|
||||
else:
|
||||
# warning-ignore:return_value_discarded
|
||||
config.timesheet.add_entry(task_name_line_edit.text)
|
||||
|
||||
|
||||
func _on_task_name_line_edit_text_changed(new_text: String) -> void:
|
||||
config.current_task_name = new_text
|
||||
update_timer_state()
|
||||
|
||||
|
||||
func _on_time_sheet_loaded():
|
||||
update_timer_state()
|
||||
|
||||
func update_timer_state() -> void:
|
||||
current_time_entry = config.timesheet.get_active_entry_from_name(config.current_task_name)
|
||||
if current_time_entry:
|
||||
set_button_as_started()
|
||||
else:
|
||||
set_button_as_stopped()
|
||||
|
||||
|
||||
func set_button_as_stopped() -> void:
|
||||
start_button.set_pressed_no_signal(false)
|
||||
start_button.hint_tooltip = tr(Consts.START)
|
||||
time_label.text = Consts.NO_TIME
|
||||
start_button.theme_type_variation = Consts.THEME_OVERRIDE_START
|
||||
timer.stop()
|
||||
|
||||
|
||||
func set_button_as_started() -> void:
|
||||
start_button.set_pressed_no_signal(true)
|
||||
start_button.hint_tooltip = tr(Consts.STOP)
|
||||
start_button.theme_type_variation = Consts.THEME_OVERRIDE_STOP
|
||||
timer.start()
|
51
ui/time_counter.tscn
Normal file
@ -0,0 +1,51 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://ui/time_counter.gd" type="Script" id=1]
|
||||
|
||||
[node name="TimeCounter" type="VBoxContainer"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="."]
|
||||
margin_right = 360.0
|
||||
margin_bottom = 45.0
|
||||
|
||||
[node name="TaskNameLineEdit" type="LineEdit" parent="HBoxContainer2"]
|
||||
unique_name_in_owner = true
|
||||
margin_right = 360.0
|
||||
margin_bottom = 45.0
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Task Name. Use \"/\" to create subtasks"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
margin_top = 53.0
|
||||
margin_right = 360.0
|
||||
margin_bottom = 760.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="TimeLabel" type="Label" parent="HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
margin_top = 341.0
|
||||
margin_right = 328.0
|
||||
margin_bottom = 366.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 6
|
||||
theme_type_variation = "time_label"
|
||||
text = "00:00:00"
|
||||
align = 1
|
||||
valign = 1
|
||||
|
||||
[node name="StartButton" type="Button" parent="HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
margin_left = 336.0
|
||||
margin_right = 360.0
|
||||
margin_bottom = 707.0
|
||||
theme_type_variation = "play_button"
|
||||
|
||||
[node name="Timer" type="Timer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
|
||||
unique_name_in_owner = true
|
@ -1,69 +0,0 @@
|
||||
class_name TimeEntriesItemsTree extends Tree
|
||||
|
||||
enum COL{
|
||||
TEXT,
|
||||
TIME
|
||||
}
|
||||
|
||||
var config: ConfigManager = preload("res://config_manager.tres")
|
||||
|
||||
var current_item: TreeItem
|
||||
|
||||
func _ready() -> void:
|
||||
config.file_changed.connect(populate_entries)
|
||||
populate_entries()
|
||||
|
||||
|
||||
func populate_entries() -> void:
|
||||
clear()
|
||||
var _root := create_item()
|
||||
var entries_names := config.timesheet.entries_names
|
||||
for entry_name in entries_names:
|
||||
append_name_to_tree(entry_name, entries_names[entry_name])
|
||||
|
||||
|
||||
func set_time_elapsed(total_elapsed: int) -> void:
|
||||
current_item.set_text(COL.TIME, TimeEntry.time_to_period(total_elapsed))
|
||||
current_item.set_metadata(COL.TIME, total_elapsed)
|
||||
|
||||
|
||||
func set_current_item(task_name: String) -> void:
|
||||
current_item = append_name_to_tree(task_name, 0)
|
||||
|
||||
|
||||
|
||||
func get_current_text() -> String:
|
||||
var item := get_selected()
|
||||
if not item:
|
||||
return ""
|
||||
|
||||
var resp = item.get_metadata(COL.TEXT)
|
||||
if resp is String:
|
||||
return resp
|
||||
return ""
|
||||
|
||||
|
||||
## Adds a new item to the tree, or returns the old item if it exists
|
||||
func append_name_to_tree(task_name: String, total_elapsed := 0) -> TreeItem:
|
||||
var item := get_root()
|
||||
for item_name in task_name.split("/"):
|
||||
item.collapsed = false
|
||||
item = find_item(item, item_name, true)
|
||||
item.set_metadata(COL.TEXT, task_name)
|
||||
item.set_text(COL.TIME, TimeEntry.time_to_period(total_elapsed))
|
||||
item.set_metadata(COL.TIME, total_elapsed)
|
||||
scroll_to_item(item)
|
||||
return item
|
||||
|
||||
|
||||
## Finds an item in the tree by text
|
||||
func find_item(root: TreeItem, item_name: String, or_create: bool) -> TreeItem:
|
||||
for child in root.get_children():
|
||||
if child.get_text(COL.TEXT) == item_name:
|
||||
return child
|
||||
if or_create:
|
||||
var child := root.create_child()
|
||||
child.set_text(COL.TEXT, item_name)
|
||||
child.set_text(COL.TIME, Consts.NO_TIME)
|
||||
return child
|
||||
return null
|