4 Commits

Author SHA1 Message Date
Jens Krause
ec18da0664 fix(ci): remove magic nix cache action (#57) 2025-01-22 09:56:28 +01:00
Jens Krause
59c99f4f5c fix(build): statically linked binaries for linux (#55) 2025-01-22 09:43:51 +01:00
Jens Krause
6d2bf5ac09 Edit countdown by local time (#49) 2025-01-13 18:44:56 +01:00
Jens Krause
b1efb1eb62 rust 1.84, update deps (#48)
- `nix flake update`
- `cargo update`
2025-01-11 16:48:37 +01:00
15 changed files with 810 additions and 254 deletions

View File

@@ -12,7 +12,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- name: Check formatting - name: Check formatting
run: nix develop --command cargo fmt --all -- --check run: nix develop --command cargo fmt --all -- --check
- name: Run clippy - name: Run clippy
@@ -25,7 +24,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- name: Run tests - name: Run tests
run: nix develop --command cargo test run: nix develop --command cargo test
@@ -34,6 +32,5 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- name: Build project - name: Build project
run: nix build .#timr run: nix build .#timr

View File

@@ -31,7 +31,7 @@ jobs:
- os: ubuntu-latest - os: ubuntu-latest
os_target: linux os_target: linux
binary_name: timr-tui binary_name: timr-tui
arch: x86_64 # `x86_64` by default arch: x86_64 # based on target 'x86_64-unknown-linux-musl' defined by `CARGO_BUILD_TARGET` in flake.nix
- os: ubuntu-latest - os: ubuntu-latest
os_target: windows os_target: windows
binary_name: timr-tui.exe binary_name: timr-tui.exe
@@ -43,14 +43,17 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- name: Build (windows) - name: Build (windows)
if: matrix.os_target == 'windows' if: matrix.os_target == 'windows'
run: nix build .#windows run: nix build .#windows
- name: Build (linux/macos) - name: Build (linux)
if: matrix.os_target != 'windows' if: matrix.os_target == 'linux'
run: nix build .#linuxStatic
- name: Build (macos)
if: matrix.os_target == 'macos'
run: nix build run: nix build
- name: Copy artifact - name: Copy artifact

296
Cargo.lock generated
View File

@@ -28,9 +28,9 @@ dependencies = [
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.20" version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "anstream" name = "anstream"
@@ -104,9 +104,9 @@ dependencies = [
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.6.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@@ -134,9 +134,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.1" version = "1.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" checksum = "ad0cf6e91fde44c773c6ee7ec6bba798504641a8bc2eb7e37a04ffbf4dfaa55a"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@@ -149,9 +149,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.23" version = "4.5.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -159,9 +159,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.23" version = "4.5.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -171,9 +171,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.18" version = "4.5.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@@ -222,9 +222,9 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "compact_str" name = "compact_str"
version = "0.8.0" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
dependencies = [ dependencies = [
"castaway", "castaway",
"cfg-if", "cfg-if",
@@ -305,12 +305,6 @@ dependencies = [
"powerfmt", "powerfmt",
] ]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]] [[package]]
name = "directories" name = "directories"
version = "5.0.1" version = "5.0.1"
@@ -345,13 +339,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "erased-serde"
version = "0.3.9" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -372,9 +376,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]] [[package]]
name = "foldhash" name = "foldhash"
version = "0.1.3" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]] [[package]]
name = "futures" name = "futures"
@@ -499,12 +503,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"
@@ -525,13 +523,12 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]] [[package]]
name = "instability" name = "instability"
version = "0.3.3" version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e" checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d"
dependencies = [ dependencies = [
"darling", "darling",
"indoc", "indoc",
"pretty_assertions",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
@@ -566,9 +563,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.166" version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]] [[package]]
name = "libredox" name = "libredox"
@@ -582,9 +579,9 @@ dependencies = [
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@@ -598,9 +595,12 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.22" version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "3d6ea2a48c204030ee31a7d7fc72c93294c92fe87ecb1789881c9543516e1a0d"
dependencies = [
"value-bag",
]
[[package]] [[package]]
name = "lru" name = "lru"
@@ -637,11 +637,10 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [ dependencies = [
"hermit-abi",
"libc", "libc",
"log", "log",
"wasi", "wasi",
@@ -737,9 +736,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.15" version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@@ -753,30 +752,20 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.92" version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.37" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -804,9 +793,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.7" version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@@ -874,22 +863,22 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.41" version = "0.38.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.18" version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]] [[package]]
name = "ryu" name = "ryu"
@@ -905,18 +894,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.215" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.215" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -924,10 +913,19 @@ dependencies = [
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_fmt"
version = "1.0.134" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4"
dependencies = [
"serde",
]
[[package]]
name = "serde_json"
version = "1.0.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -1040,10 +1038,88 @@ dependencies = [
] ]
[[package]] [[package]]
name = "syn" name = "sval"
version = "2.0.89" version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" checksum = "f6dc0f9830c49db20e73273ffae9b5240f63c42e515af1da1fceefb69fceafd8"
[[package]]
name = "sval_buffer"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "429922f7ad43c0ef8fd7309e14d750e38899e32eb7e8da656ea169dd28ee212f"
dependencies = [
"sval",
"sval_ref",
]
[[package]]
name = "sval_dynamic"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f16ff5d839396c11a30019b659b0976348f3803db0626f736764c473b50ff4"
dependencies = [
"sval",
]
[[package]]
name = "sval_fmt"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c01c27a80b6151b0557f9ccbe89c11db571dc5f68113690c1e028d7e974bae94"
dependencies = [
"itoa",
"ryu",
"sval",
]
[[package]]
name = "sval_json"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0deef63c70da622b2a8069d8600cf4b05396459e665862e7bdb290fd6cf3f155"
dependencies = [
"itoa",
"ryu",
"sval",
]
[[package]]
name = "sval_nested"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a39ce5976ae1feb814c35d290cf7cf8cd4f045782fe1548d6bc32e21f6156e9f"
dependencies = [
"sval",
"sval_buffer",
"sval_ref",
]
[[package]]
name = "sval_ref"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7c6ee3751795a728bc9316a092023529ffea1783499afbc5c66f5fabebb1fa"
dependencies = [
"sval",
]
[[package]]
name = "sval_serde"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a5572d0321b68109a343634e3a5d576bf131b82180c6c442dee06349dfc652a"
dependencies = [
"serde",
"sval",
"sval_nested",
]
[[package]]
name = "syn"
version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1115,7 +1191,7 @@ dependencies = [
[[package]] [[package]]
name = "timr-tui" name = "timr-tui"
version = "1.0.0" version = "1.1.0-alpha"
dependencies = [ dependencies = [
"clap", "clap",
"color-eyre", "color-eyre",
@@ -1136,9 +1212,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.41.1" version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -1154,9 +1230,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.4.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1165,9 +1241,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.16" version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"pin-project-lite", "pin-project-lite",
@@ -1176,9 +1252,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.12" version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -1258,6 +1334,12 @@ dependencies = [
"tracing-log", "tracing-log",
] ]
[[package]]
name = "typeid"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.14" version = "1.0.14"
@@ -1305,6 +1387,42 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2"
dependencies = [
"value-bag-serde1",
"value-bag-sval2",
]
[[package]]
name = "value-bag-serde1"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb773bd36fd59c7ca6e336c94454d9c66386416734817927ac93d81cb3c5b0b"
dependencies = [
"erased-serde",
"serde",
"serde_fmt",
]
[[package]]
name = "value-bag-sval2"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a916a702cac43a88694c97657d449775667bcd14b70419441d05b7fea4a83a"
dependencies = [
"sval",
"sval_buffer",
"sval_dynamic",
"sval_fmt",
"sval_json",
"sval_ref",
"sval_serde",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
@@ -1480,9 +1598,3 @@ name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"

View File

@@ -1,9 +1,9 @@
[package] [package]
name = "timr-tui" name = "timr-tui"
version = "1.0.0" version = "1.1.0-alpha"
description = "TUI to organize your time: Pomodoro, Countdown, Timer." description = "TUI to organize your time: Pomodoro, Countdown, Timer."
edition = "2021" edition = "2021"
rust-version = "1.82.0" rust-version = "1.84.0"
homepage = "https://github.com/sectore/timr-tui" homepage = "https://github.com/sectore/timr-tui"
repository = "https://github.com/sectore/timr-tui" repository = "https://github.com/sectore/timr-tui"
readme = "README.md" readme = "README.md"

24
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": { "nodes": {
"crane": { "crane": {
"locked": { "locked": {
"lastModified": 1733286231, "lastModified": 1736566337,
"narHash": "sha256-mlIDSv1/jqWnH8JTiOV7GMUNPCXL25+6jmD+7hdxx5o=", "narHash": "sha256-SC0eDcZPqISVt6R0UfGPyQLrI0+BppjjtQ3wcSlk0oI=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "af1556ecda8bcf305820f68ec2f9d77b41d9cc80", "rev": "9172acc1ee6c7e1cbafc3044ff850c568c75a5a3",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -23,11 +23,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1732689334, "lastModified": 1736577158,
"narHash": "sha256-yKI1KiZ0+bvDvfPTQ1ZT3oP/nIu3jPYm4dnbRd6hYg4=", "narHash": "sha256-ngnAENZ+vmzOFgnj0EDtHj22nuH7MQB+EqzUmdbvaqA=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "a8a983027ca02b363dfc82fbe3f7d9548a8d3dce", "rev": "05dcdb02ea657f81b13d99bd0ca36b09d25f4c43",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -56,11 +56,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1733212471, "lastModified": 1736344531,
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=", "narHash": "sha256-8YVQ9ZbSfuUk2bUf2KRj60NRraLPKPS0Q4QFTbc+c2c=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776", "rev": "bffc22eb12172e6db3c5dde9e3e5628f8e3e7912",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -81,11 +81,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1732633904, "lastModified": 1736517563,
"narHash": "sha256-7VKcoLug9nbAN2txqVksWHHJplqK9Ou8dXjIZAIYSGc=", "narHash": "sha256-YJ5ajpMsyXITc91ZfnI0Mdocd+tmCFkZ3BLozUkB44M=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "8d5e91c94f80c257ce6dbdfba7bd63a5e8a03fa6", "rev": "4f35021ca9a8e7f9ed4344139b9eaf770a2e5725",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -1,7 +1,5 @@
{ {
inputs = { inputs = {
# Disable `nixos-unstable` for now, it introduced some `VScode` related errors:
# error: function 'buildVscodeExtension' called without required argument 'pname'
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
crane.url = "github:ipetkov/crane"; crane.url = "github:ipetkov/crane";
@@ -20,12 +18,12 @@
}: }:
flake-utils.lib.eachDefaultSystem (system: let flake-utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
# Using stable toolchain as base
toolchain = with fenix.packages.${system}; toolchain = with fenix.packages.${system};
combine [ combine [
minimal.rustc minimal.rustc
minimal.cargo minimal.cargo
targets.x86_64-pc-windows-gnu.latest.rust-std targets.x86_64-pc-windows-gnu.latest.rust-std
targets.x86_64-unknown-linux-musl.latest.rust-std
]; ];
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain; craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
@@ -35,19 +33,24 @@
cargoArtifacts = craneLib.buildDepsOnly { cargoArtifacts = craneLib.buildDepsOnly {
src = craneLib.cleanCargoSource ./.; src = craneLib.cleanCargoSource ./.;
}; };
strictDeps = true;
doCheck = false; # skip tests during nix build doCheck = false; # skip tests during nix build
}; };
# Native build # Native build
timr = craneLib.buildPackage commonArgs; timr = craneLib.buildPackage commonArgs;
# Linux build w/ statically linked binaries
staticLinuxBuild = craneLib.buildPackage (commonArgs
// {
CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl";
CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static";
});
# Windows cross-compilation build # Windows cross-compilation build
# @see https://crane.dev/examples/cross-windows.html # @see https://crane.dev/examples/cross-windows.html
crossBuild = craneLib.buildPackage { windowsBuild = craneLib.buildPackage {
src = craneLib.cleanCargoSource ./.; inherit (commonArgs) src strictDeps doCheck;
strictDeps = true;
doCheck = false;
CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu"; CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu";
@@ -68,7 +71,8 @@
packages = { packages = {
inherit timr; inherit timr;
default = timr; default = timr;
windows = crossBuild; linuxStatic = staticLinuxBuild;
windows = windowsBuild;
}; };
# Development shell with all necessary tools # Development shell with all necessary tools

View File

@@ -1,6 +1,6 @@
use crate::{ use crate::{
args::Args, args::Args,
common::{AppTime, AppTimeFormat, Content, Style}, common::{AppEditMode, AppTime, AppTimeFormat, Content, Style},
constants::TICK_VALUE_MS, constants::TICK_VALUE_MS,
events::{Event, EventHandler, Events}, events::{Event, EventHandler, Events},
storage::AppStorage, storage::AppStorage,
@@ -112,10 +112,11 @@ impl App {
with_decis, with_decis,
pomodoro_mode, pomodoro_mode,
} = args; } = args;
let app_time = get_app_time();
Self { Self {
mode: Mode::Running, mode: Mode::Running,
content, content,
app_time: get_app_time(), app_time,
style, style,
with_decis, with_decis,
countdown: CountdownState::new( countdown: CountdownState::new(
@@ -126,6 +127,7 @@ impl App {
with_decis, with_decis,
}), }),
elapsed_value_countdown, elapsed_value_countdown,
app_time,
), ),
timer: TimerState::new(ClockState::<clock::Timer>::new(ClockStateArgs { timer: TimerState::new(ClockState::<clock::Timer>::new(ClockStateArgs {
initial_value: Duration::ZERO, initial_value: Duration::ZERO,
@@ -150,6 +152,7 @@ impl App {
if let Some(event) = events.next().await { if let Some(event) = events.next().await {
if matches!(event, Event::Tick) { if matches!(event, Event::Tick) {
self.app_time = get_app_time(); self.app_time = get_app_time();
self.countdown.set_app_time(self.app_time);
} }
// Pipe events into subviews and handle only 'unhandled' events afterwards // Pipe events into subviews and handle only 'unhandled' events afterwards
@@ -175,11 +178,32 @@ impl App {
self.mode != Mode::Quit self.mode != Mode::Quit
} }
fn is_edit_mode(&self) -> bool { fn get_edit_mode(&self) -> AppEditMode {
match self.content { match self.content {
Content::Countdown => self.countdown.get_clock().is_edit_mode(), Content::Countdown => {
Content::Timer => self.timer.get_clock().is_edit_mode(), if self.countdown.is_clock_edit_mode() {
Content::Pomodoro => self.pomodoro.get_clock().is_edit_mode(), AppEditMode::Clock
} else if self.countdown.is_time_edit_mode() {
AppEditMode::Time
} else {
AppEditMode::None
}
}
Content::Timer => {
if self.timer.get_clock().is_edit_mode() {
AppEditMode::Clock
} else {
AppEditMode::None
}
}
Content::Pomodoro => {
if self.pomodoro.get_clock().is_edit_mode() {
AppEditMode::Clock
} else {
AppEditMode::None
}
}
} }
} }
@@ -298,7 +322,7 @@ impl StatefulWidget for AppWidget {
Footer { Footer {
running_clock: state.clock_is_running(), running_clock: state.clock_is_running(),
selected_content: state.content, selected_content: state.content,
edit_mode: state.is_edit_mode(), app_edit_mode: state.get_edit_mode(),
app_time: state.app_time, app_time: state.app_time,
} }
.render(v2, buf, &mut state.footer); .render(v2, buf, &mut state.footer);

View File

@@ -128,6 +128,13 @@ impl AppTime {
} }
} }
#[derive(Debug)]
pub enum AppEditMode {
None,
Clock,
Time,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@@ -20,6 +20,10 @@ pub const MINS_PER_HOUR: u64 = 60;
// https://doc.rust-lang.org/src/core/time.rs.html#36 // https://doc.rust-lang.org/src/core/time.rs.html#36
const HOURS_PER_DAY: u64 = 24; const HOURS_PER_DAY: u64 = 24;
// max. 99:59:59
pub const MAX_DURATION: Duration =
Duration::from_secs(100 * MINS_PER_HOUR * SECS_PER_MINUTE).saturating_sub(ONE_SECOND);
#[derive(Debug, Clone, Copy, PartialOrd)] #[derive(Debug, Clone, Copy, PartialOrd)]
pub struct DurationEx { pub struct DurationEx {
inner: Duration, inner: Duration,

View File

@@ -5,6 +5,7 @@ pub mod clock_elements_test;
#[cfg(test)] #[cfg(test)]
pub mod clock_test; pub mod clock_test;
pub mod countdown; pub mod countdown;
pub mod edit_time;
pub mod footer; pub mod footer;
pub mod header; pub mod header;
pub mod pomodoro; pub mod pomodoro;

View File

@@ -11,20 +11,13 @@ use ratatui::{
use crate::{ use crate::{
common::Style, common::Style,
duration::{ duration::{DurationEx, MAX_DURATION, ONE_DECI_SECOND, ONE_HOUR, ONE_MINUTE, ONE_SECOND},
DurationEx, MINS_PER_HOUR, ONE_DECI_SECOND, ONE_HOUR, ONE_MINUTE, ONE_SECOND,
SECS_PER_MINUTE,
},
utils::center_horizontal, utils::center_horizontal,
widgets::clock_elements::{ widgets::clock_elements::{
Colon, Digit, Dot, COLON_WIDTH, DIGIT_HEIGHT, DIGIT_WIDTH, DOT_WIDTH, Colon, Digit, Dot, COLON_WIDTH, DIGIT_HEIGHT, DIGIT_SPACE_WIDTH, DIGIT_WIDTH, DOT_WIDTH,
}, },
}; };
// max. 99:59:59
const MAX_DURATION: Duration =
Duration::from_secs(100 * MINS_PER_HOUR * SECS_PER_MINUTE).saturating_sub(ONE_SECOND);
#[derive(Debug, Copy, Clone, Display, PartialEq, Eq)] #[derive(Debug, Copy, Clone, Display, PartialEq, Eq)]
pub enum Time { pub enum Time {
Decis, Decis,
@@ -128,6 +121,11 @@ impl<T> ClockState<T> {
&self.current_value &self.current_value
} }
pub fn set_current_value(&mut self, duration: DurationEx) {
self.current_value = duration;
self.update_format();
}
pub fn toggle_edit(&mut self) { pub fn toggle_edit(&mut self) {
self.mode = match self.mode.clone() { self.mode = match self.mode.clone() {
Mode::Editable(_, prev) => { Mode::Editable(_, prev) => {
@@ -463,8 +461,6 @@ impl ClockState<Timer> {
} }
} }
const SPACE_WIDTH: u16 = 1;
pub struct ClockWidget<T> pub struct ClockWidget<T>
where where
T: std::fmt::Debug, T: std::fmt::Debug,
@@ -499,15 +495,15 @@ where
Format::HhMmSs => add_decis( Format::HhMmSs => add_decis(
vec![ vec![
DIGIT_WIDTH, // h DIGIT_WIDTH, // h
SPACE_WIDTH, // (space) DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // h DIGIT_WIDTH, // h
COLON_WIDTH, // : COLON_WIDTH, // :
DIGIT_WIDTH, // m DIGIT_WIDTH, // m
SPACE_WIDTH, // (space) DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // m DIGIT_WIDTH, // m
COLON_WIDTH, // : COLON_WIDTH, // :
DIGIT_WIDTH, // s DIGIT_WIDTH, // s
SPACE_WIDTH, // (space) DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // s DIGIT_WIDTH, // s
], ],
with_decis, with_decis,
@@ -517,11 +513,11 @@ where
DIGIT_WIDTH, // h DIGIT_WIDTH, // h
COLON_WIDTH, // : COLON_WIDTH, // :
DIGIT_WIDTH, // m DIGIT_WIDTH, // m
SPACE_WIDTH, // (space) DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // m DIGIT_WIDTH, // m
COLON_WIDTH, // : COLON_WIDTH, // :
DIGIT_WIDTH, // s DIGIT_WIDTH, // s
SPACE_WIDTH, // (space) DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // s DIGIT_WIDTH, // s
], ],
with_decis, with_decis,
@@ -529,11 +525,11 @@ where
Format::MmSs => add_decis( Format::MmSs => add_decis(
vec![ vec![
DIGIT_WIDTH, // m DIGIT_WIDTH, // m
SPACE_WIDTH, // (space) DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // m DIGIT_WIDTH, // m
COLON_WIDTH, // : COLON_WIDTH, // :
DIGIT_WIDTH, // s DIGIT_WIDTH, // s
SPACE_WIDTH, // (space) DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // s DIGIT_WIDTH, // s
], ],
with_decis, with_decis,
@@ -543,7 +539,7 @@ where
DIGIT_WIDTH, // m DIGIT_WIDTH, // m
COLON_WIDTH, // : COLON_WIDTH, // :
DIGIT_WIDTH, // s DIGIT_WIDTH, // s
SPACE_WIDTH, // (space) DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // s DIGIT_WIDTH, // s
], ],
with_decis, with_decis,
@@ -551,7 +547,7 @@ where
Format::Ss => add_decis( Format::Ss => add_decis(
vec![ vec![
DIGIT_WIDTH, // s DIGIT_WIDTH, // s
SPACE_WIDTH, // (space) DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // s DIGIT_WIDTH, // s
], ],
with_decis, with_decis,

View File

@@ -9,6 +9,7 @@ pub const DIGIT_WIDTH: u16 = DIGIT_SIZE as u16;
pub const DIGIT_HEIGHT: u16 = DIGIT_SIZE as u16 + 1 /* border height */; pub const DIGIT_HEIGHT: u16 = DIGIT_SIZE as u16 + 1 /* border height */;
pub const COLON_WIDTH: u16 = 4; // incl. padding left + padding right pub const COLON_WIDTH: u16 = 4; // incl. padding left + padding right
pub const DOT_WIDTH: u16 = 4; // incl. padding left + padding right pub const DOT_WIDTH: u16 = 4; // incl. padding left + padding right
pub const DIGIT_SPACE_WIDTH: u16 = 1; // space between digits
#[rustfmt::skip] #[rustfmt::skip]
const DIGIT_0: [u8; DIGIT_SIZE * DIGIT_SIZE] = [ const DIGIT_0: [u8; DIGIT_SIZE * DIGIT_SIZE] = [

View File

@@ -1,3 +1,15 @@
use crate::{
common::{AppTime, Style},
constants::TICK_VALUE_MS,
duration::{DurationEx, MAX_DURATION},
events::{Event, EventHandler},
utils::center,
widgets::{
clock::{self, ClockState, ClockStateArgs, ClockWidget, Mode as ClockMode},
edit_time::EditTimeState,
},
};
use crossterm::event::KeyModifiers;
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
crossterm::event::KeyCode, crossterm::event::KeyCode,
@@ -5,16 +17,12 @@ use ratatui::{
text::Line, text::Line,
widgets::{StatefulWidget, Widget}, widgets::{StatefulWidget, Widget},
}; };
use std::{cmp::max, time::Duration};
use crate::{ use std::ops::Sub;
common::Style, use std::{cmp::max, time::Duration};
constants::TICK_VALUE_MS, use time::OffsetDateTime;
duration::DurationEx,
events::{Event, EventHandler}, use super::edit_time::{EditTimeStateArgs, EditTimeWidget};
utils::center,
widgets::clock::{self, ClockState, ClockStateArgs, ClockWidget, Mode as ClockMode},
};
/// State for Countdown Widget /// State for Countdown Widget
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -23,10 +31,17 @@ pub struct CountdownState {
clock: ClockState<clock::Countdown>, clock: ClockState<clock::Countdown>,
/// clock to count time after `DONE` - similar to Mission Elapsed Time (MET) /// clock to count time after `DONE` - similar to Mission Elapsed Time (MET)
elapsed_clock: ClockState<clock::Timer>, elapsed_clock: ClockState<clock::Timer>,
app_time: AppTime,
/// Edit by local time
edit_time: Option<EditTimeState>,
} }
impl CountdownState { impl CountdownState {
pub fn new(clock: ClockState<clock::Countdown>, elapsed_value: Duration) -> Self { pub fn new(
clock: ClockState<clock::Countdown>,
elapsed_value: Duration,
app_time: AppTime,
) -> Self {
Self { Self {
clock, clock,
elapsed_clock: ClockState::<clock::Timer>::new(ClockStateArgs { elapsed_clock: ClockState::<clock::Timer>::new(ClockStateArgs {
@@ -43,6 +58,8 @@ impl CountdownState {
} else { } else {
ClockMode::Initial ClockMode::Initial
}), }),
app_time,
edit_time: None,
} }
} }
@@ -62,11 +79,55 @@ impl CountdownState {
pub fn get_elapsed_value(&self) -> &DurationEx { pub fn get_elapsed_value(&self) -> &DurationEx {
self.elapsed_clock.get_current_value() self.elapsed_clock.get_current_value()
} }
pub fn set_app_time(&mut self, app_time: AppTime) {
self.app_time = app_time;
}
fn time_to_edit(&self) -> OffsetDateTime {
// get current value
let d: Duration = (*self.clock.get_current_value()).into();
// transform
let dd = time::Duration::try_from(d).unwrap_or(time::Duration::ZERO);
// substract from `app_time`
OffsetDateTime::from(self.app_time).saturating_add(dd)
}
pub fn min_time_to_edit(&self) -> OffsetDateTime {
OffsetDateTime::from(self.app_time)
}
fn max_time_to_edit(&self) -> OffsetDateTime {
OffsetDateTime::from(self.app_time)
.saturating_add(time::Duration::try_from(MAX_DURATION).unwrap_or(time::Duration::ZERO))
}
fn edit_time_done(&mut self, edit_time: &mut EditTimeState) {
// get diff
let d: time::Duration = edit_time
.get_time()
.sub(OffsetDateTime::from(self.app_time));
// transfrom
let dx: DurationEx = Duration::try_from(d).unwrap_or(Duration::ZERO).into();
// update clock
self.clock.set_current_value(dx);
// remove `edit_time`
self.edit_time = None;
}
pub fn is_clock_edit_mode(&self) -> bool {
self.clock.is_edit_mode()
}
pub fn is_time_edit_mode(&self) -> bool {
self.edit_time.is_some()
}
} }
impl EventHandler for CountdownState { impl EventHandler for CountdownState {
fn update(&mut self, event: Event) -> Option<Event> { fn update(&mut self, event: Event) -> Option<Event> {
let edit_mode = self.clock.is_edit_mode(); let is_edit_clock = self.clock.is_edit_mode();
let is_edit_time = self.edit_time.is_some();
match event { match event {
Event::Tick => { Event::Tick => {
if !self.clock.is_done() { if !self.clock.is_done() {
@@ -77,12 +138,24 @@ impl EventHandler for CountdownState {
self.elapsed_clock.run(); self.elapsed_clock.run();
} }
} }
let min_time = self.min_time_to_edit();
let max_time = self.max_time_to_edit();
if let Some(edit_time) = &mut self.edit_time {
edit_time.set_min_time(min_time);
edit_time.set_max_time(max_time);
}
} }
Event::Key(key) => match key.code { Event::Key(key) => match key.code {
KeyCode::Char('r') => { KeyCode::Char('r') => {
// reset both clocks // reset both clocks to use intial values
self.clock.reset(); self.clock.reset();
self.elapsed_clock.reset(); self.elapsed_clock.reset();
// reset `edit_time` back initial value
let time = self.time_to_edit();
if let Some(edit_time) = &mut self.edit_time {
edit_time.set_time(time);
}
} }
KeyCode::Char('s') => { KeyCode::Char('s') => {
// toggle pause status depending on which clock is running // toggle pause status depending on which clock is running
@@ -91,30 +164,92 @@ impl EventHandler for CountdownState {
} else { } else {
self.elapsed_clock.toggle_pause(); self.elapsed_clock.toggle_pause();
} }
// finish `edit_time` and continue for using `clock`
if let Some(edit_time) = &mut self.edit_time.clone() {
self.edit_time_done(edit_time);
} }
KeyCode::Char('e') => { }
// STRG + e => toggle edit time
KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
// stop editing clock
if self.clock.is_edit_mode() {
// toggle edit mode
self.clock.toggle_edit(); self.clock.toggle_edit();
// stop + reset timer entering `edit` mode }
if let Some(edit_time) = &mut self.edit_time.clone() {
self.edit_time_done(edit_time)
} else {
// update `edit_time`
self.edit_time = Some(EditTimeState::new(EditTimeStateArgs {
time: self.time_to_edit(),
min: self.min_time_to_edit(),
max: self.max_time_to_edit(),
}));
}
// stop `clock`
if self.clock.is_running() {
self.clock.toggle_pause();
}
// stop `elapsed_clock`
if self.elapsed_clock.is_running() { if self.elapsed_clock.is_running() {
self.elapsed_clock.toggle_pause(); self.elapsed_clock.toggle_pause();
} }
} }
KeyCode::Left if edit_mode => { // STRG + e => toggle edit clock
KeyCode::Char('e') => {
// toggle edit mode
self.clock.toggle_edit();
// stop `elapsed_clock`
if self.elapsed_clock.is_running() {
self.elapsed_clock.toggle_pause();
}
// finish `edit_time` and continue for using `clock`
if let Some(edit_time) = &mut self.edit_time.clone() {
self.edit_time_done(edit_time);
}
}
KeyCode::Left if is_edit_clock => {
self.clock.edit_next(); self.clock.edit_next();
} }
KeyCode::Right if edit_mode => { KeyCode::Left if is_edit_time => {
// safe unwrap because of previous check in `is_edit_time`
self.edit_time.as_mut().unwrap().next();
}
KeyCode::Right if is_edit_clock => {
self.clock.edit_prev(); self.clock.edit_prev();
} }
KeyCode::Up if edit_mode => { KeyCode::Right if is_edit_time => {
// safe unwrap because of previous check in `is_edit_time`
self.edit_time.as_mut().unwrap().prev();
}
KeyCode::Up if is_edit_clock => {
self.clock.edit_up(); self.clock.edit_up();
// whenever `clock`'s value is changed, reset `elapsed_clock` // whenever `clock`'s value is changed, reset `elapsed_clock`
self.elapsed_clock.reset(); self.elapsed_clock.reset();
} }
KeyCode::Down if edit_mode => { KeyCode::Up if is_edit_time => {
// safe unwrap because of previous check in `is_edit_time`
self.edit_time.as_mut().unwrap().up();
// whenever `clock`'s value is changed, reset `elapsed_clock`
self.elapsed_clock.reset();
}
KeyCode::Down if is_edit_clock => {
self.clock.edit_down(); self.clock.edit_down();
// whenever clock value is changed, reset timer // whenever clock value is changed, reset timer
self.elapsed_clock.reset(); self.elapsed_clock.reset();
} }
KeyCode::Down if is_edit_time => {
// safe unwrap because of previous check in `is_edit_time`
self.edit_time.as_mut().unwrap().down();
// whenever clock value is changed, reset timer
self.elapsed_clock.reset();
}
_ => return Some(event), _ => return Some(event),
}, },
_ => return Some(event), _ => return Some(event),
@@ -127,11 +262,40 @@ pub struct Countdown {
pub style: Style, pub style: Style,
} }
fn human_days_diff(a: &OffsetDateTime, b: &OffsetDateTime) -> String {
let days_diff = (a.date() - b.date()).whole_days();
match days_diff {
0 => "today".to_owned(),
1 => "tomorrow".to_owned(),
n => format!("+{}days", n),
}
}
impl StatefulWidget for Countdown { impl StatefulWidget for Countdown {
type State = CountdownState; type State = CountdownState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let clock = ClockWidget::new(self.style); // render `edit_time` OR `clock`
if let Some(edit_time) = &mut state.edit_time {
let label = Line::raw(
format!(
"Countdown {} {}",
edit_time.get_selected().clone(),
human_days_diff(edit_time.get_time(), &state.app_time.into())
)
.to_uppercase(),
);
let widget = EditTimeWidget::new(self.style);
let area = center(
area,
Constraint::Length(max(widget.get_width(), label.width() as u16)),
Constraint::Length(widget.get_height() + 1 /* height of label */),
);
let [v1, v2] =
Layout::vertical(Constraint::from_lengths([widget.get_height(), 1])).areas(area);
widget.render(v1, buf, edit_time);
label.centered().render(v2, buf);
} else {
let label = Line::raw( let label = Line::raw(
if state.clock.is_done() { if state.clock.is_done() {
if state.clock.with_decis { if state.clock.with_decis {
@@ -155,19 +319,20 @@ impl StatefulWidget for Countdown {
} }
.to_uppercase(), .to_uppercase(),
); );
let widget = ClockWidget::new(self.style);
let area = center( let area = center(
area, area,
Constraint::Length(max( Constraint::Length(max(
clock.get_width(&state.clock.get_format(), state.clock.with_decis), widget.get_width(&state.clock.get_format(), state.clock.with_decis),
label.width() as u16, label.width() as u16,
)), )),
Constraint::Length(clock.get_height() + 1 /* height of label */), Constraint::Length(widget.get_height() + 1 /* height of label */),
); );
let [v1, v2] = let [v1, v2] =
Layout::vertical(Constraint::from_lengths([clock.get_height(), 1])).areas(area); Layout::vertical(Constraint::from_lengths([widget.get_height(), 1])).areas(area);
clock.render(v1, buf, &mut state.clock); widget.render(v1, buf, &mut state.clock);
label.centered().render(v2, buf); label.centered().render(v2, buf);
} }
} }
}

231
src/widgets/edit_time.rs Normal file
View File

@@ -0,0 +1,231 @@
use std::fmt;
use time::OffsetDateTime;
use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Rect},
widgets::{StatefulWidget, Widget},
};
use crate::{
common::Style,
widgets::clock_elements::{Colon, Digit, COLON_WIDTH, DIGIT_SPACE_WIDTH, DIGIT_WIDTH},
};
use super::clock_elements::DIGIT_HEIGHT;
#[derive(Debug, Clone)]
pub enum Selected {
Seconds,
Minutes,
Hours,
}
impl Selected {
pub fn next(&self) -> Self {
match self {
Selected::Seconds => Selected::Minutes,
Selected::Minutes => Selected::Hours,
Selected::Hours => Selected::Seconds,
}
}
pub fn prev(&self) -> Self {
match self {
Selected::Seconds => Selected::Hours,
Selected::Minutes => Selected::Seconds,
Selected::Hours => Selected::Minutes,
}
}
}
impl fmt::Display for Selected {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Selected::Seconds => write!(f, "[edit seconds]"),
Selected::Minutes => write!(f, "[edit minutes]"),
Selected::Hours => write!(f, "[edit hours]"),
}
}
}
#[derive(Debug, Clone)]
pub struct EditTimeState {
selected: Selected,
time: OffsetDateTime,
min: OffsetDateTime,
max: OffsetDateTime,
}
#[derive(Debug, Clone)]
pub struct EditTimeStateArgs {
pub time: OffsetDateTime,
pub min: OffsetDateTime,
pub max: OffsetDateTime,
}
impl EditTimeState {
pub fn new(args: EditTimeStateArgs) -> Self {
EditTimeState {
time: args.time,
min: args.min,
max: args.max,
selected: Selected::Minutes,
}
}
pub fn set_time(&mut self, time: OffsetDateTime) {
self.time = time;
}
pub fn set_min_time(&mut self, min: OffsetDateTime) {
self.min = min;
}
pub fn set_max_time(&mut self, min: OffsetDateTime) {
self.max = min;
}
pub fn get_time(&mut self) -> &OffsetDateTime {
&self.time
}
pub fn get_selected(&mut self) -> &Selected {
&self.selected
}
pub fn next(&mut self) {
self.selected = self.selected.next();
}
pub fn prev(&mut self) {
self.selected = self.selected.prev();
}
pub fn up(&mut self) {
self.time = match self.selected {
Selected::Seconds => {
if self
.time
.lt(&self.max.saturating_sub(time::Duration::new(1, 0)))
{
self.time.saturating_add(time::Duration::new(1, 0))
} else {
self.time
}
}
Selected::Minutes => {
if self
.time
.lt(&self.max.saturating_sub(time::Duration::new(60, 0)))
{
self.time.saturating_add(time::Duration::new(60, 0))
} else {
self.time
}
}
Selected::Hours => {
if self
.time
.lt(&self.max.saturating_sub(time::Duration::new(60 * 60, 0)))
{
self.time.saturating_add(time::Duration::new(60 * 60, 0))
} else {
self.time
}
}
}
}
pub fn down(&mut self) {
self.time = match self.selected {
Selected::Seconds => {
if self
.time
.ge(&self.min.saturating_add(time::Duration::new(1, 0)))
{
self.time.saturating_sub(time::Duration::new(1, 0))
} else {
self.time
}
}
Selected::Minutes => {
if self
.time
.ge(&self.min.saturating_add(time::Duration::new(60, 0)))
{
self.time.saturating_sub(time::Duration::new(60, 0))
} else {
self.time
}
}
Selected::Hours => {
if self
.time
.ge(&self.min.saturating_add(time::Duration::new(60 * 60, 0)))
{
self.time.saturating_sub(time::Duration::new(60 * 60, 0))
} else {
self.time
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct EditTimeWidget {
style: Style,
}
impl EditTimeWidget {
pub fn new(style: Style) -> Self {
Self { style }
}
fn get_horizontal_lengths(&self) -> Vec<u16> {
vec![
DIGIT_WIDTH, // h
DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // h
COLON_WIDTH, // :
DIGIT_WIDTH, // m
DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // m
COLON_WIDTH, // :
DIGIT_WIDTH, // s
DIGIT_SPACE_WIDTH, // (space)
DIGIT_WIDTH, // s
]
}
pub fn get_width(&self) -> u16 {
self.get_horizontal_lengths().iter().sum()
}
pub fn get_height(&self) -> u16 {
DIGIT_HEIGHT
}
}
impl StatefulWidget for EditTimeWidget {
type State = EditTimeState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let symbol = self.style.get_digit_symbol();
let edit_hours = matches!(state.selected, Selected::Hours);
let edit_minutes = matches!(state.selected, Selected::Minutes);
let edit_secs = matches!(state.selected, Selected::Seconds);
let [hh, _, h, c_hm, mm, _, m, c_ms, ss, _, s] =
Layout::horizontal(Constraint::from_lengths(self.get_horizontal_lengths())).areas(area);
Digit::new((state.time.hour() as u64) / 10, edit_hours, symbol).render(hh, buf);
Digit::new((state.time.hour() as u64) % 10, edit_hours, symbol).render(h, buf);
Colon::new(symbol).render(c_hm, buf);
Digit::new((state.time.minute() as u64) / 10, edit_minutes, symbol).render(mm, buf);
Digit::new((state.time.minute() as u64) % 10, edit_minutes, symbol).render(m, buf);
Colon::new(symbol).render(c_ms, buf);
Digit::new((state.time.second() as u64) / 10, edit_secs, symbol).render(ss, buf);
Digit::new((state.time.second() as u64) % 10, edit_secs, symbol).render(s, buf);
}
}

View File

@@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::common::{AppTime, AppTimeFormat, Content}; use crate::common::{AppEditMode, AppTime, AppTimeFormat, Content};
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
layout::{Constraint, Layout, Rect}, layout::{Constraint, Layout, Rect},
@@ -45,7 +45,7 @@ impl FooterState {
pub struct Footer { pub struct Footer {
pub running_clock: bool, pub running_clock: bool,
pub selected_content: Content, pub selected_content: Content,
pub edit_mode: bool, pub app_edit_mode: AppEditMode,
pub app_time: AppTime, pub app_time: AppTime,
} }
@@ -139,21 +139,8 @@ impl StatefulWidget for Footer {
Style::default().add_modifier(Modifier::BOLD), Style::default().add_modifier(Modifier::BOLD),
)), )),
Cell::from(Line::from({ Cell::from(Line::from({
if self.edit_mode { match self.app_edit_mode {
vec![ AppEditMode::None => {
Span::from("[e]dit done"),
Span::from(SPACE),
Span::from(format!(
"[{} {}]edit selection",
scrollbar::HORIZONTAL.begin,
scrollbar::HORIZONTAL.end
)), // ← →,
Span::from(SPACE),
Span::from(format!("[{}]edit up", scrollbar::VERTICAL.begin)), // ↑
Span::from(SPACE),
Span::from(format!("[{}]edit up", scrollbar::VERTICAL.end)), // ↓,
]
} else {
let mut spans = vec![ let mut spans = vec![
Span::from(if self.running_clock { Span::from(if self.running_clock {
"[s]top" "[s]top"
@@ -165,6 +152,12 @@ impl StatefulWidget for Footer {
Span::from(SPACE), Span::from(SPACE),
Span::from("[e]dit"), Span::from("[e]dit"),
]; ];
if self.selected_content == Content::Countdown {
spans.extend_from_slice(&[
Span::from(SPACE),
Span::from("[ctrl+e]dit by local time"),
]);
}
if self.selected_content == Content::Pomodoro { if self.selected_content == Content::Pomodoro {
spans.extend_from_slice(&[ spans.extend_from_slice(&[
Span::from(SPACE), Span::from(SPACE),
@@ -173,6 +166,24 @@ impl StatefulWidget for Footer {
} }
spans spans
} }
others => vec![
Span::from(match others {
AppEditMode::Clock => "[e]dit done",
AppEditMode::Time => "[ctrl+e]dit done",
_ => "",
}),
Span::from(SPACE),
Span::from(format!(
"[{} {}]edit selection",
scrollbar::HORIZONTAL.begin,
scrollbar::HORIZONTAL.end
)), // ← →,
Span::from(SPACE),
Span::from(format!("[{}]edit up", scrollbar::VERTICAL.begin)), // ↑
Span::from(SPACE),
Span::from(format!("[{}]edit up", scrollbar::VERTICAL.end)), // ↓,
],
}
})), })),
]), ]),
], ],