Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec18da0664 | ||
|
|
59c99f4f5c | ||
|
|
6d2bf5ac09 | ||
|
|
b1efb1eb62 | ||
|
|
4ee5d7b4e9 | ||
|
|
9ea9f88266 | ||
|
|
c8af76c9e5 | ||
|
|
468b4a5abf | ||
|
|
8603a823e4 | ||
|
|
94bdeeab11 | ||
|
|
66c6d7fc46 |
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -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
|
||||||
|
|||||||
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
@@ -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
|
||||||
|
|||||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.0.0 - 2025-01-10
|
||||||
|
|
||||||
|
Happy `v1.0.0` 🎉
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- (countdown) Mission Elapsed Time ([MET](https://en.wikipedia.org/wiki/Mission_Elapsed_Time)). [#45](https://github.com/sectore/timr-tui/pull/45), [#46](https://github.com/sectore/timr-tui/pull/46)
|
||||||
|
- (footer) Local time. Optional and with custom formats. [#42](https://github.com/sectore/timr-tui/pull/42), [#43](https://github.com/sectore/timr-tui/pull/43)
|
||||||
|
- (docs) More installation instructions: Cargo, AUR (Arch Linux) [#41](https://github.com/sectore/timr-tui/pull/41), pre-built release binaries (Linux, macOS, Windows) [#47](https://github.com/sectore/timr-tui/pull/47)
|
||||||
|
|
||||||
## v0.9.0 - 2025-01-03
|
## v0.9.0 - 2025-01-03
|
||||||
|
|
||||||
Initial version.
|
Initial version.
|
||||||
@@ -8,5 +18,6 @@ Initial version.
|
|||||||
|
|
||||||
- Add `Pomodoro`, `Timer`, `Countdown`
|
- Add `Pomodoro`, `Timer`, `Countdown`
|
||||||
- Persist application state
|
- Persist application state
|
||||||
- Change styles
|
- Custom styles for digits
|
||||||
|
- Toggle deciseconds
|
||||||
- CLI
|
- CLI
|
||||||
|
|||||||
348
Cargo.lock
generated
348
Cargo.lock
generated
@@ -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",
|
||||||
@@ -297,10 +297,13 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diff"
|
name = "deranged"
|
||||||
version = "0.1.13"
|
version = "0.3.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "directories"
|
name = "directories"
|
||||||
@@ -336,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]]
|
||||||
@@ -363,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"
|
||||||
@@ -490,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"
|
||||||
@@ -516,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",
|
||||||
@@ -557,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"
|
||||||
@@ -573,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"
|
||||||
@@ -589,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"
|
||||||
@@ -628,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",
|
||||||
@@ -649,6 +657,21 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.32.2"
|
version = "0.32.2"
|
||||||
@@ -713,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"
|
||||||
@@ -724,29 +747,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_assertions"
|
name = "powerfmt"
|
||||||
version = "1.4.1"
|
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 = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
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",
|
||||||
]
|
]
|
||||||
@@ -774,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",
|
||||||
]
|
]
|
||||||
@@ -844,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"
|
||||||
@@ -875,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",
|
||||||
@@ -894,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",
|
||||||
@@ -1010,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",
|
||||||
@@ -1050,9 +1156,42 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num-conv",
|
||||||
|
"num_threads",
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "timr-tui"
|
name = "timr-tui"
|
||||||
version = "0.9.0"
|
version = "1.1.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
@@ -1063,6 +1202,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum",
|
"strum",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -1072,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",
|
||||||
@@ -1090,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",
|
||||||
@@ -1101,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",
|
||||||
@@ -1112,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",
|
||||||
@@ -1194,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"
|
||||||
@@ -1241,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"
|
||||||
@@ -1416,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"
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "timr-tui"
|
name = "timr-tui"
|
||||||
version = "0.9.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"
|
||||||
@@ -26,3 +26,4 @@ tracing = "0.1.41"
|
|||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
clap = { version = "4.5.23", features = ["derive"] }
|
clap = { version = "4.5.23", features = ["derive"] }
|
||||||
|
time = { version = "0.3.37", features = ["formatting", "local-offset"] }
|
||||||
|
|||||||
40
README.md
40
README.md
@@ -6,9 +6,9 @@ TUI to organize your time: Pomodoro, Countdown, Timer.
|
|||||||
- `[c]ountdown` Use it for your workout, yoga session, meditation, handstand or whatever.
|
- `[c]ountdown` Use it for your workout, yoga session, meditation, handstand or whatever.
|
||||||
- `[p]omodoro` Organize your working time to be focused all the time by following the [Pomodoro Technique](https://en.wikipedia.org/wiki/Pomodoro_Technique).
|
- `[p]omodoro` Organize your working time to be focused all the time by following the [Pomodoro Technique](https://en.wikipedia.org/wiki/Pomodoro_Technique).
|
||||||
|
|
||||||
It's built with [`Ratatui`](https://ratatui.rs/) written in [Rust 🦀](https://www.rust-lang.org/).
|
Built with [Ratatui](https://ratatui.rs/) / [Rust 🦀](https://www.rust-lang.org/).
|
||||||
|
|
||||||
# Preview
|
# Features
|
||||||
|
|
||||||
_Side note:_ Theme colors depend on your terminal preferences.
|
_Side note:_ Theme colors depend on your terminal preferences.
|
||||||
|
|
||||||
@@ -48,6 +48,18 @@ _Side note:_ Theme colors depend on your terminal preferences.
|
|||||||
<img alt="menu" src="demo/menu.gif" />
|
<img alt="menu" src="demo/menu.gif" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
## Local time
|
||||||
|
|
||||||
|
<a href="demo/local-time.gif">
|
||||||
|
<img alt="menu" src="demo/local-time.gif" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## Mission Elapsed Time ([MET](https://en.wikipedia.org/wiki/Mission_Elapsed_Time))
|
||||||
|
|
||||||
|
<a href="demo/countdown-met.gif">
|
||||||
|
<img alt="menu" src="demo/countdown-met.gif" />
|
||||||
|
</a>
|
||||||
|
|
||||||
# CLI
|
# CLI
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -70,19 +82,35 @@ Options:
|
|||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
From [crates.io](https://crates.io/crates/timr-tui) run:
|
## Cargo
|
||||||
|
|
||||||
|
### From [crates.io](https://crates.io/crates/timr-tui)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo install timr-tui
|
cargo install timr-tui
|
||||||
```
|
```
|
||||||
|
|
||||||
Latest version from git repository:
|
### From GitHub repository
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo install --git https://github.com/sectore/timr-tui
|
cargo install --git https://github.com/sectore/timr-tui
|
||||||
```
|
```
|
||||||
|
|
||||||
# Build from source 🔧
|
## Arch Linux
|
||||||
|
|
||||||
|
Install [from the AUR](https://aur.archlinux.org/packages/timr/):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
paru -S timr
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Release binaries
|
||||||
|
|
||||||
|
Pre-built artifacts are available to download from [latest GitHub release](https://github.com/sectore/timr-tui/releases).
|
||||||
|
|
||||||
|
|
||||||
|
# Development
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -152,7 +180,7 @@ In `debug` mode only. Locations:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Linux
|
# Linux
|
||||||
~/.local/state/timr/logs/app.log
|
~/.local/state/timr-tui/logs/app.log
|
||||||
# macOS
|
# macOS
|
||||||
/Users/{user}/Library/Application Support/timr-tui/logs/app.log
|
/Users/{user}/Library/Application Support/timr-tui/logs/app.log
|
||||||
# `Windows`
|
# `Windows`
|
||||||
|
|||||||
BIN
demo/countdown-met.gif
Normal file
BIN
demo/countdown-met.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
22
demo/countdown-met.tape
Normal file
22
demo/countdown-met.tape
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Output demo/countdown-met.gif
|
||||||
|
|
||||||
|
# https://github.com/charmbracelet/vhs/blob/main/THEMES.md
|
||||||
|
Set Theme "iceberg-light"
|
||||||
|
|
||||||
|
Set FontSize 14
|
||||||
|
Set Width 800
|
||||||
|
Set Height 400
|
||||||
|
Set Padding 0
|
||||||
|
Set Margin 1
|
||||||
|
|
||||||
|
# --- START ---
|
||||||
|
Set LoopOffset 4
|
||||||
|
Hide
|
||||||
|
Type "cargo run -- -m c -c 3"
|
||||||
|
Enter
|
||||||
|
Sleep 0.2
|
||||||
|
Show
|
||||||
|
Type "s"
|
||||||
|
Sleep 6
|
||||||
|
Type "r"
|
||||||
|
Sleep 1
|
||||||
BIN
demo/local-time.gif
Normal file
BIN
demo/local-time.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
22
demo/local-time.tape
Normal file
22
demo/local-time.tape
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Output demo/local-time.gif
|
||||||
|
|
||||||
|
# https://github.com/charmbracelet/vhs/blob/main/THEMES.md
|
||||||
|
Set Theme "AtomOneLight"
|
||||||
|
|
||||||
|
Set FontSize 14
|
||||||
|
Set Width 800
|
||||||
|
Set Height 400
|
||||||
|
Set Padding 0
|
||||||
|
Set Margin 1
|
||||||
|
|
||||||
|
# --- START ---
|
||||||
|
Set LoopOffset 4
|
||||||
|
Hide
|
||||||
|
Type "cargo run -- -m c"
|
||||||
|
Enter
|
||||||
|
Sleep 0.2
|
||||||
|
Show
|
||||||
|
Sleep 1
|
||||||
|
# --- toggle local time ---
|
||||||
|
Type@1.5s ":::"
|
||||||
|
Sleep 1.5
|
||||||
BIN
demo/rocket-countdown_no-ds.gif
Normal file
BIN
demo/rocket-countdown_no-ds.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
24
flake.lock
generated
24
flake.lock
generated
@@ -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": {
|
||||||
|
|||||||
22
flake.nix
22
flake.nix
@@ -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
|
||||||
|
|||||||
15
justfile
15
justfile
@@ -49,6 +49,11 @@ alias dc := demo-countdown
|
|||||||
demo-countdown:
|
demo-countdown:
|
||||||
vhs demo/countdown.tape
|
vhs demo/countdown.tape
|
||||||
|
|
||||||
|
alias dcm := demo-countdown-met
|
||||||
|
|
||||||
|
demo-countdown-met:
|
||||||
|
vhs demo/countdown-met.tape
|
||||||
|
|
||||||
alias ds := demo-style
|
alias ds := demo-style
|
||||||
|
|
||||||
demo-style:
|
demo-style:
|
||||||
@@ -63,3 +68,13 @@ alias dm := demo-menu
|
|||||||
|
|
||||||
demo-menu:
|
demo-menu:
|
||||||
vhs demo/menu.tape
|
vhs demo/menu.tape
|
||||||
|
|
||||||
|
alias dlt := demo-local-time
|
||||||
|
|
||||||
|
demo-local-time:
|
||||||
|
vhs demo/local-time.tape
|
||||||
|
|
||||||
|
alias drc := demo-rocket-countdown
|
||||||
|
|
||||||
|
demo-rocket-countdown:
|
||||||
|
vhs demo/met.tape
|
||||||
|
|||||||
127
src/app.rs
127
src/app.rs
@@ -1,17 +1,17 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
args::Args,
|
args::Args,
|
||||||
common::{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,
|
||||||
terminal::Terminal,
|
terminal::Terminal,
|
||||||
widgets::{
|
widgets::{
|
||||||
clock::{self, Clock, ClockArgs},
|
clock::{self, ClockState, ClockStateArgs},
|
||||||
countdown::{Countdown, CountdownWidget},
|
countdown::{Countdown, CountdownState},
|
||||||
footer::Footer,
|
footer::{Footer, FooterState},
|
||||||
header::Header,
|
header::Header,
|
||||||
pomodoro::{Mode as PomodoroMode, Pomodoro, PomodoroArgs, PomodoroWidget},
|
pomodoro::{Mode as PomodoroMode, PomodoroState, PomodoroStateArgs, PomodoroWidget},
|
||||||
timer::{Timer, TimerWidget},
|
timer::{Timer, TimerState},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
@@ -22,6 +22,7 @@ use ratatui::{
|
|||||||
widgets::{StatefulWidget, Widget},
|
widgets::{StatefulWidget, Widget},
|
||||||
};
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use time::OffsetDateTime;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -34,18 +35,20 @@ enum Mode {
|
|||||||
pub struct App {
|
pub struct App {
|
||||||
content: Content,
|
content: Content,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
show_menu: bool,
|
app_time: AppTime,
|
||||||
countdown: Countdown,
|
countdown: CountdownState,
|
||||||
timer: Timer,
|
timer: TimerState,
|
||||||
pomodoro: Pomodoro,
|
pomodoro: PomodoroState,
|
||||||
style: Style,
|
style: Style,
|
||||||
with_decis: bool,
|
with_decis: bool,
|
||||||
|
footer: FooterState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppArgs {
|
pub struct AppArgs {
|
||||||
pub style: Style,
|
pub style: Style,
|
||||||
pub with_decis: bool,
|
pub with_decis: bool,
|
||||||
pub show_menu: bool,
|
pub show_menu: bool,
|
||||||
|
pub app_time_format: AppTimeFormat,
|
||||||
pub content: Content,
|
pub content: Content,
|
||||||
pub pomodoro_mode: PomodoroMode,
|
pub pomodoro_mode: PomodoroMode,
|
||||||
pub initial_value_work: Duration,
|
pub initial_value_work: Duration,
|
||||||
@@ -54,6 +57,7 @@ pub struct AppArgs {
|
|||||||
pub current_value_pause: Duration,
|
pub current_value_pause: Duration,
|
||||||
pub initial_value_countdown: Duration,
|
pub initial_value_countdown: Duration,
|
||||||
pub current_value_countdown: Duration,
|
pub current_value_countdown: Duration,
|
||||||
|
pub elapsed_value_countdown: Duration,
|
||||||
pub current_value_timer: Duration,
|
pub current_value_timer: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +68,7 @@ impl From<(Args, AppStorage)> for AppArgs {
|
|||||||
AppArgs {
|
AppArgs {
|
||||||
with_decis: args.decis || stg.with_decis,
|
with_decis: args.decis || stg.with_decis,
|
||||||
show_menu: args.menu || stg.show_menu,
|
show_menu: args.menu || stg.show_menu,
|
||||||
|
app_time_format: stg.app_time_format,
|
||||||
content: args.mode.unwrap_or(stg.content),
|
content: args.mode.unwrap_or(stg.content),
|
||||||
style: args.style.unwrap_or(stg.style),
|
style: args.style.unwrap_or(stg.style),
|
||||||
pomodoro_mode: stg.pomodoro_mode,
|
pomodoro_mode: stg.pomodoro_mode,
|
||||||
@@ -76,62 +81,80 @@ impl From<(Args, AppStorage)> for AppArgs {
|
|||||||
initial_value_countdown: args.countdown.unwrap_or(stg.inital_value_countdown),
|
initial_value_countdown: args.countdown.unwrap_or(stg.inital_value_countdown),
|
||||||
// invalidate `current_value_countdown` if an initial value is set via args
|
// invalidate `current_value_countdown` if an initial value is set via args
|
||||||
current_value_countdown: args.countdown.unwrap_or(stg.current_value_countdown),
|
current_value_countdown: args.countdown.unwrap_or(stg.current_value_countdown),
|
||||||
|
elapsed_value_countdown: stg.elapsed_value_countdown,
|
||||||
current_value_timer: stg.current_value_timer,
|
current_value_timer: stg.current_value_timer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_app_time() -> AppTime {
|
||||||
|
match OffsetDateTime::now_local() {
|
||||||
|
Ok(t) => AppTime::Local(t),
|
||||||
|
Err(_) => AppTime::Utc(OffsetDateTime::now_utc()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(args: AppArgs) -> Self {
|
pub fn new(args: AppArgs) -> Self {
|
||||||
let AppArgs {
|
let AppArgs {
|
||||||
style,
|
style,
|
||||||
show_menu,
|
show_menu,
|
||||||
|
app_time_format,
|
||||||
initial_value_work,
|
initial_value_work,
|
||||||
initial_value_pause,
|
initial_value_pause,
|
||||||
initial_value_countdown,
|
initial_value_countdown,
|
||||||
current_value_work,
|
current_value_work,
|
||||||
current_value_pause,
|
current_value_pause,
|
||||||
current_value_countdown,
|
current_value_countdown,
|
||||||
|
elapsed_value_countdown,
|
||||||
current_value_timer,
|
current_value_timer,
|
||||||
content,
|
content,
|
||||||
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,
|
||||||
show_menu,
|
app_time,
|
||||||
style,
|
style,
|
||||||
with_decis,
|
with_decis,
|
||||||
countdown: Countdown::new(Clock::<clock::Countdown>::new(ClockArgs {
|
countdown: CountdownState::new(
|
||||||
|
ClockState::<clock::Countdown>::new(ClockStateArgs {
|
||||||
initial_value: initial_value_countdown,
|
initial_value: initial_value_countdown,
|
||||||
current_value: current_value_countdown,
|
current_value: current_value_countdown,
|
||||||
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
||||||
style,
|
|
||||||
with_decis,
|
with_decis,
|
||||||
})),
|
}),
|
||||||
timer: Timer::new(Clock::<clock::Timer>::new(ClockArgs {
|
elapsed_value_countdown,
|
||||||
|
app_time,
|
||||||
|
),
|
||||||
|
timer: TimerState::new(ClockState::<clock::Timer>::new(ClockStateArgs {
|
||||||
initial_value: Duration::ZERO,
|
initial_value: Duration::ZERO,
|
||||||
current_value: current_value_timer,
|
current_value: current_value_timer,
|
||||||
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
||||||
style,
|
|
||||||
with_decis,
|
with_decis,
|
||||||
})),
|
})),
|
||||||
pomodoro: Pomodoro::new(PomodoroArgs {
|
pomodoro: PomodoroState::new(PomodoroStateArgs {
|
||||||
mode: pomodoro_mode,
|
mode: pomodoro_mode,
|
||||||
initial_value_work,
|
initial_value_work,
|
||||||
current_value_work,
|
current_value_work,
|
||||||
initial_value_pause,
|
initial_value_pause,
|
||||||
current_value_pause,
|
current_value_pause,
|
||||||
style,
|
|
||||||
with_decis,
|
with_decis,
|
||||||
}),
|
}),
|
||||||
|
footer: FooterState::new(show_menu, app_time_format),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(mut self, mut terminal: Terminal, mut events: Events) -> Result<Self> {
|
pub async fn run(mut self, mut terminal: Terminal, mut events: Events) -> Result<Self> {
|
||||||
while self.is_running() {
|
while self.is_running() {
|
||||||
if let Some(event) = events.next().await {
|
if let Some(event) = events.next().await {
|
||||||
|
if matches!(event, Event::Tick) {
|
||||||
|
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
|
||||||
if let Some(unhandled) = match self.content {
|
if let Some(unhandled) = match self.content {
|
||||||
Content::Countdown => self.countdown.update(event.clone()),
|
Content::Countdown => self.countdown.update(event.clone()),
|
||||||
@@ -155,17 +178,38 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clock_is_running(&self) -> bool {
|
fn clock_is_running(&self) -> bool {
|
||||||
match self.content {
|
match self.content {
|
||||||
Content::Countdown => self.countdown.get_clock().is_running(),
|
Content::Countdown => self.countdown.is_running(),
|
||||||
Content::Timer => self.timer.get_clock().is_running(),
|
Content::Timer => self.timer.get_clock().is_running(),
|
||||||
Content::Pomodoro => self.pomodoro.get_clock().is_running(),
|
Content::Pomodoro => self.pomodoro.get_clock().is_running(),
|
||||||
}
|
}
|
||||||
@@ -186,13 +230,12 @@ impl App {
|
|||||||
KeyCode::Char('c') => self.content = Content::Countdown,
|
KeyCode::Char('c') => self.content = Content::Countdown,
|
||||||
KeyCode::Char('t') => self.content = Content::Timer,
|
KeyCode::Char('t') => self.content = Content::Timer,
|
||||||
KeyCode::Char('p') => self.content = Content::Pomodoro,
|
KeyCode::Char('p') => self.content = Content::Pomodoro,
|
||||||
KeyCode::Char('m') => self.show_menu = !self.show_menu,
|
// toogle app time format
|
||||||
|
KeyCode::Char(':') => self.footer.toggle_app_time_format(),
|
||||||
|
// toogle menu
|
||||||
|
KeyCode::Char('m') => self.footer.set_show_menu(!self.footer.get_show_menu()),
|
||||||
KeyCode::Char(',') => {
|
KeyCode::Char(',') => {
|
||||||
self.style = self.style.next();
|
self.style = self.style.next();
|
||||||
// update clocks
|
|
||||||
self.timer.set_style(self.style);
|
|
||||||
self.countdown.set_style(self.style);
|
|
||||||
self.pomodoro.set_style(self.style);
|
|
||||||
}
|
}
|
||||||
KeyCode::Char('.') => {
|
KeyCode::Char('.') => {
|
||||||
self.with_decis = !self.with_decis;
|
self.with_decis = !self.with_decis;
|
||||||
@@ -201,8 +244,8 @@ impl App {
|
|||||||
self.countdown.set_with_decis(self.with_decis);
|
self.countdown.set_with_decis(self.with_decis);
|
||||||
self.pomodoro.set_with_decis(self.with_decis);
|
self.pomodoro.set_with_decis(self.with_decis);
|
||||||
}
|
}
|
||||||
KeyCode::Up => self.show_menu = true,
|
KeyCode::Up => self.footer.set_show_menu(true),
|
||||||
KeyCode::Down => self.show_menu = false,
|
KeyCode::Down => self.footer.set_show_menu(false),
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -217,7 +260,8 @@ impl App {
|
|||||||
pub fn to_storage(&self) -> AppStorage {
|
pub fn to_storage(&self) -> AppStorage {
|
||||||
AppStorage {
|
AppStorage {
|
||||||
content: self.content,
|
content: self.content,
|
||||||
show_menu: self.show_menu,
|
show_menu: self.footer.get_show_menu(),
|
||||||
|
app_time_format: *self.footer.app_time_format(),
|
||||||
style: self.style,
|
style: self.style,
|
||||||
with_decis: self.with_decis,
|
with_decis: self.with_decis,
|
||||||
pomodoro_mode: self.pomodoro.get_mode().clone(),
|
pomodoro_mode: self.pomodoro.get_mode().clone(),
|
||||||
@@ -233,6 +277,7 @@ impl App {
|
|||||||
current_value_countdown: Duration::from(
|
current_value_countdown: Duration::from(
|
||||||
*self.countdown.get_clock().get_current_value(),
|
*self.countdown.get_clock().get_current_value(),
|
||||||
),
|
),
|
||||||
|
elapsed_value_countdown: Duration::from(*self.countdown.get_elapsed_value()),
|
||||||
current_value_timer: Duration::from(*self.timer.get_clock().get_current_value()),
|
current_value_timer: Duration::from(*self.timer.get_clock().get_current_value()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,9 +288,15 @@ struct AppWidget;
|
|||||||
impl AppWidget {
|
impl AppWidget {
|
||||||
fn render_content(&self, area: Rect, buf: &mut Buffer, state: &mut App) {
|
fn render_content(&self, area: Rect, buf: &mut Buffer, state: &mut App) {
|
||||||
match state.content {
|
match state.content {
|
||||||
Content::Timer => TimerWidget.render(area, buf, &mut state.timer.clone()),
|
Content::Timer => {
|
||||||
Content::Countdown => CountdownWidget.render(area, buf, &mut state.countdown.clone()),
|
Timer { style: state.style }.render(area, buf, &mut state.timer);
|
||||||
Content::Pomodoro => PomodoroWidget.render(area, buf, &mut state.pomodoro.clone()),
|
}
|
||||||
|
Content::Countdown => {
|
||||||
|
Countdown { style: state.style }.render(area, buf, &mut state.countdown)
|
||||||
|
}
|
||||||
|
Content::Pomodoro => {
|
||||||
|
PomodoroWidget { style: state.style }.render(area, buf, &mut state.pomodoro)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +307,7 @@ impl StatefulWidget for AppWidget {
|
|||||||
let [v0, v1, v2] = Layout::vertical([
|
let [v0, v1, v2] = Layout::vertical([
|
||||||
Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
Constraint::Percentage(100),
|
Constraint::Percentage(100),
|
||||||
Constraint::Length(if state.show_menu { 4 } else { 1 }),
|
Constraint::Length(if state.footer.get_show_menu() { 4 } else { 1 }),
|
||||||
])
|
])
|
||||||
.areas(area);
|
.areas(area);
|
||||||
|
|
||||||
@@ -269,11 +320,11 @@ impl StatefulWidget for AppWidget {
|
|||||||
self.render_content(v1, buf, state);
|
self.render_content(v1, buf, state);
|
||||||
// footer
|
// footer
|
||||||
Footer {
|
Footer {
|
||||||
show_menu: state.show_menu,
|
|
||||||
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,
|
||||||
}
|
}
|
||||||
.render(v2, buf);
|
.render(v2, buf, &mut state.footer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
128
src/common.rs
128
src/common.rs
@@ -1,6 +1,8 @@
|
|||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use ratatui::symbols::shade;
|
use ratatui::symbols::shade;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::format_description;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default, Serialize, Deserialize,
|
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default, Serialize, Deserialize,
|
||||||
@@ -62,3 +64,129 @@ impl Style {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||||
|
pub enum AppTimeFormat {
|
||||||
|
/// `hh:mm:ss`
|
||||||
|
#[default]
|
||||||
|
HhMmSs,
|
||||||
|
/// `hh:mm`
|
||||||
|
HhMm,
|
||||||
|
/// `hh:mm AM` (or PM)
|
||||||
|
Hh12Mm,
|
||||||
|
/// `` (empty)
|
||||||
|
Hidden,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppTimeFormat {
|
||||||
|
pub fn next(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
AppTimeFormat::HhMmSs => AppTimeFormat::HhMm,
|
||||||
|
AppTimeFormat::HhMm => AppTimeFormat::Hh12Mm,
|
||||||
|
AppTimeFormat::Hh12Mm => AppTimeFormat::Hidden,
|
||||||
|
AppTimeFormat::Hidden => AppTimeFormat::HhMmSs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum AppTime {
|
||||||
|
Local(OffsetDateTime),
|
||||||
|
Utc(OffsetDateTime),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AppTime> for OffsetDateTime {
|
||||||
|
fn from(app_time: AppTime) -> Self {
|
||||||
|
match app_time {
|
||||||
|
AppTime::Local(t) => t,
|
||||||
|
AppTime::Utc(t) => t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppTime {
|
||||||
|
pub fn format(&self, app_format: &AppTimeFormat) -> String {
|
||||||
|
let parse_str = match app_format {
|
||||||
|
AppTimeFormat::HhMmSs => Some("[hour]:[minute]:[second]"),
|
||||||
|
AppTimeFormat::HhMm => Some("[hour]:[minute]"),
|
||||||
|
AppTimeFormat::Hh12Mm => Some("[hour repr:12 padding:none]:[minute] [period]"),
|
||||||
|
AppTimeFormat::Hidden => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(str) = parse_str {
|
||||||
|
format_description::parse(str)
|
||||||
|
.map_err(|_| "parse error")
|
||||||
|
.and_then(|fd| {
|
||||||
|
OffsetDateTime::from(*self)
|
||||||
|
.format(&fd)
|
||||||
|
.map_err(|_| "format error")
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|e| e.to_string())
|
||||||
|
} else {
|
||||||
|
"".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AppEditMode {
|
||||||
|
None,
|
||||||
|
Clock,
|
||||||
|
Time,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use time::{Date, Month, PrimitiveDateTime, Time};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_app_time() {
|
||||||
|
let dt = PrimitiveDateTime::new(
|
||||||
|
Date::from_calendar_date(2025, Month::January, 6).unwrap(),
|
||||||
|
Time::from_hms(18, 6, 10).unwrap(),
|
||||||
|
)
|
||||||
|
.assume_utc();
|
||||||
|
// hh:mm:ss
|
||||||
|
assert_eq!(
|
||||||
|
AppTime::Utc(dt).format(&AppTimeFormat::HhMmSs),
|
||||||
|
"18:06:10",
|
||||||
|
"utc"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
AppTime::Local(dt).format(&AppTimeFormat::HhMmSs),
|
||||||
|
"18:06:10",
|
||||||
|
"local"
|
||||||
|
);
|
||||||
|
// hh:mm
|
||||||
|
assert_eq!(
|
||||||
|
AppTime::Utc(dt).format(&AppTimeFormat::HhMm),
|
||||||
|
"18:06",
|
||||||
|
"utc"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
AppTime::Local(dt).format(&AppTimeFormat::HhMm),
|
||||||
|
"18:06",
|
||||||
|
"local"
|
||||||
|
);
|
||||||
|
// hh:mm period
|
||||||
|
assert_eq!(
|
||||||
|
AppTime::Utc(dt).format(&AppTimeFormat::Hh12Mm),
|
||||||
|
"6:06 PM",
|
||||||
|
"utc"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
AppTime::Local(dt).format(&AppTimeFormat::Hh12Mm),
|
||||||
|
"6:06 PM",
|
||||||
|
"local"
|
||||||
|
);
|
||||||
|
// hidden
|
||||||
|
assert_eq!(AppTime::Utc(dt).format(&AppTimeFormat::Hidden), "", "utc");
|
||||||
|
assert_eq!(
|
||||||
|
AppTime::Local(dt).format(&AppTimeFormat::Hidden),
|
||||||
|
"",
|
||||||
|
"local"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -86,6 +90,10 @@ impl DurationEx {
|
|||||||
let inner = self.inner.saturating_sub(ex.inner);
|
let inner = self.inner.saturating_sub(ex.inner);
|
||||||
Self { inner }
|
Self { inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_string_with_decis(self) -> String {
|
||||||
|
format!("{}.{}", self, self.decis())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DurationEx {
|
impl fmt::Display for DurationEx {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
common::{Content, Style},
|
common::{AppTimeFormat, Content, Style},
|
||||||
widgets::pomodoro::Mode as PomodoroMode,
|
widgets::pomodoro::Mode as PomodoroMode,
|
||||||
};
|
};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
@@ -12,6 +12,7 @@ use std::time::Duration;
|
|||||||
pub struct AppStorage {
|
pub struct AppStorage {
|
||||||
pub content: Content,
|
pub content: Content,
|
||||||
pub show_menu: bool,
|
pub show_menu: bool,
|
||||||
|
pub app_time_format: AppTimeFormat,
|
||||||
pub style: Style,
|
pub style: Style,
|
||||||
pub with_decis: bool,
|
pub with_decis: bool,
|
||||||
pub pomodoro_mode: PomodoroMode,
|
pub pomodoro_mode: PomodoroMode,
|
||||||
@@ -24,6 +25,7 @@ pub struct AppStorage {
|
|||||||
// countdown
|
// countdown
|
||||||
pub inital_value_countdown: Duration,
|
pub inital_value_countdown: Duration,
|
||||||
pub current_value_countdown: Duration,
|
pub current_value_countdown: Duration,
|
||||||
|
pub elapsed_value_countdown: Duration,
|
||||||
// timer
|
// timer
|
||||||
pub current_value_timer: Duration,
|
pub current_value_timer: Duration,
|
||||||
}
|
}
|
||||||
@@ -36,6 +38,7 @@ impl Default for AppStorage {
|
|||||||
AppStorage {
|
AppStorage {
|
||||||
content: Content::default(),
|
content: Content::default(),
|
||||||
show_menu: true,
|
show_menu: true,
|
||||||
|
app_time_format: AppTimeFormat::default(),
|
||||||
style: Style::default(),
|
style: Style::default(),
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
pomodoro_mode: PomodoroMode::Work,
|
pomodoro_mode: PomodoroMode::Work,
|
||||||
@@ -48,6 +51,7 @@ impl Default for AppStorage {
|
|||||||
// countdown
|
// countdown
|
||||||
inital_value_countdown: DEFAULT_COUNTDOWN,
|
inital_value_countdown: DEFAULT_COUNTDOWN,
|
||||||
current_value_countdown: DEFAULT_COUNTDOWN,
|
current_value_countdown: DEFAULT_COUNTDOWN,
|
||||||
|
elapsed_value_countdown: Duration::ZERO,
|
||||||
// timer
|
// timer
|
||||||
current_value_timer: Duration::ZERO,
|
current_value_timer: Duration::ZERO,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -73,26 +66,45 @@ pub enum Format {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Clock<T> {
|
pub struct ClockState<T> {
|
||||||
initial_value: DurationEx,
|
initial_value: DurationEx,
|
||||||
current_value: DurationEx,
|
current_value: DurationEx,
|
||||||
tick_value: DurationEx,
|
tick_value: DurationEx,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
format: Format,
|
format: Format,
|
||||||
pub style: Style,
|
|
||||||
pub with_decis: bool,
|
pub with_decis: bool,
|
||||||
phantom: PhantomData<T>,
|
phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClockArgs {
|
pub struct ClockStateArgs {
|
||||||
pub initial_value: Duration,
|
pub initial_value: Duration,
|
||||||
pub current_value: Duration,
|
pub current_value: Duration,
|
||||||
pub tick_value: Duration,
|
pub tick_value: Duration,
|
||||||
pub style: Style,
|
|
||||||
pub with_decis: bool,
|
pub with_decis: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Clock<T> {
|
impl<T> ClockState<T> {
|
||||||
|
pub fn with_mode(mut self, mode: Mode) -> Self {
|
||||||
|
self.mode = mode;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mode(&self) -> &Mode {
|
||||||
|
&self.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_initial(&self) -> bool {
|
||||||
|
self.mode == Mode::Initial
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) {
|
||||||
|
self.mode = Mode::Tick
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_running(&self) -> bool {
|
||||||
|
self.mode == Mode::Tick
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toggle_pause(&mut self) {
|
pub fn toggle_pause(&mut self) {
|
||||||
self.mode = if self.mode == Mode::Tick {
|
self.mode = if self.mode == Mode::Tick {
|
||||||
Mode::Pause
|
Mode::Pause
|
||||||
@@ -109,6 +121,11 @@ impl<T> Clock<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) => {
|
||||||
@@ -187,6 +204,7 @@ impl<T> Clock<T> {
|
|||||||
};
|
};
|
||||||
self.update_format();
|
self.update_format();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn edit_current_down(&mut self) {
|
pub fn edit_current_down(&mut self) {
|
||||||
self.current_value = match self.mode {
|
self.current_value = match self.mode {
|
||||||
Mode::Editable(Time::Decis, _) => {
|
Mode::Editable(Time::Decis, _) => {
|
||||||
@@ -205,14 +223,6 @@ impl<T> Clock<T> {
|
|||||||
self.update_mode();
|
self.update_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mode(&self) -> &Mode {
|
|
||||||
&self.mode
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_running(&self) -> bool {
|
|
||||||
self.mode == Mode::Tick
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_edit_mode(&self) -> bool {
|
pub fn is_edit_mode(&self) -> bool {
|
||||||
matches!(self.mode, Mode::Editable(_, _))
|
matches!(self.mode, Mode::Editable(_, _))
|
||||||
}
|
}
|
||||||
@@ -324,13 +334,12 @@ impl<T> Clock<T> {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Countdown {}
|
pub struct Countdown {}
|
||||||
|
|
||||||
impl Clock<Countdown> {
|
impl ClockState<Countdown> {
|
||||||
pub fn new(args: ClockArgs) -> Self {
|
pub fn new(args: ClockStateArgs) -> Self {
|
||||||
let ClockArgs {
|
let ClockStateArgs {
|
||||||
initial_value,
|
initial_value,
|
||||||
current_value,
|
current_value,
|
||||||
tick_value,
|
tick_value,
|
||||||
style,
|
|
||||||
with_decis,
|
with_decis,
|
||||||
} = args;
|
} = args;
|
||||||
let mut instance = Self {
|
let mut instance = Self {
|
||||||
@@ -345,7 +354,6 @@ impl Clock<Countdown> {
|
|||||||
Mode::Pause
|
Mode::Pause
|
||||||
},
|
},
|
||||||
format: Format::S,
|
format: Format::S,
|
||||||
style,
|
|
||||||
with_decis,
|
with_decis,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
};
|
};
|
||||||
@@ -394,13 +402,12 @@ impl Clock<Countdown> {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Timer {}
|
pub struct Timer {}
|
||||||
|
|
||||||
impl Clock<Timer> {
|
impl ClockState<Timer> {
|
||||||
pub fn new(args: ClockArgs) -> Self {
|
pub fn new(args: ClockStateArgs) -> Self {
|
||||||
let ClockArgs {
|
let ClockStateArgs {
|
||||||
initial_value,
|
initial_value,
|
||||||
current_value,
|
current_value,
|
||||||
tick_value,
|
tick_value,
|
||||||
style,
|
|
||||||
with_decis,
|
with_decis,
|
||||||
} = args;
|
} = args;
|
||||||
let mut instance = Self {
|
let mut instance = Self {
|
||||||
@@ -416,7 +423,6 @@ impl Clock<Timer> {
|
|||||||
},
|
},
|
||||||
format: Format::S,
|
format: Format::S,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
style,
|
|
||||||
with_decis,
|
with_decis,
|
||||||
};
|
};
|
||||||
// update format once
|
// update format once
|
||||||
@@ -455,12 +461,11 @@ impl Clock<Timer> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SPACE_WIDTH: u16 = 1;
|
|
||||||
|
|
||||||
pub struct ClockWidget<T>
|
pub struct ClockWidget<T>
|
||||||
where
|
where
|
||||||
T: std::fmt::Debug,
|
T: std::fmt::Debug,
|
||||||
{
|
{
|
||||||
|
style: Style,
|
||||||
phantom: PhantomData<T>,
|
phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,8 +473,9 @@ impl<T> ClockWidget<T>
|
|||||||
where
|
where
|
||||||
T: std::fmt::Debug,
|
T: std::fmt::Debug,
|
||||||
{
|
{
|
||||||
pub fn new() -> Self {
|
pub fn new(style: Style) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
style,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -489,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,
|
||||||
@@ -507,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,
|
||||||
@@ -519,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,
|
||||||
@@ -533,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,
|
||||||
@@ -541,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,
|
||||||
@@ -568,12 +574,12 @@ impl<T> StatefulWidget for ClockWidget<T>
|
|||||||
where
|
where
|
||||||
T: std::fmt::Debug,
|
T: std::fmt::Debug,
|
||||||
{
|
{
|
||||||
type State = Clock<T>;
|
type State = ClockState<T>;
|
||||||
|
|
||||||
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 with_decis = state.with_decis;
|
let with_decis = state.with_decis;
|
||||||
let format = state.format;
|
let format = state.format;
|
||||||
let symbol = state.style.get_digit_symbol();
|
let symbol = self.style.get_digit_symbol();
|
||||||
let widths = self.get_horizontal_lengths(&format, with_decis);
|
let widths = self.get_horizontal_lengths(&format, with_decis);
|
||||||
let area = center_horizontal(
|
let area = center_horizontal(
|
||||||
area,
|
area,
|
||||||
|
|||||||
@@ -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] = [
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
common::Style,
|
|
||||||
duration::{ONE_DECI_SECOND, ONE_HOUR, ONE_MINUTE, ONE_SECOND},
|
duration::{ONE_DECI_SECOND, ONE_HOUR, ONE_MINUTE, ONE_SECOND},
|
||||||
widgets::clock::*,
|
widgets::clock::*,
|
||||||
};
|
};
|
||||||
@@ -7,11 +6,10 @@ use std::time::Duration;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_toggle_edit() {
|
fn test_toggle_edit() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_HOUR,
|
initial_value: ONE_HOUR,
|
||||||
current_value: ONE_HOUR,
|
current_value: ONE_HOUR,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: true,
|
with_decis: true,
|
||||||
});
|
});
|
||||||
// off by default
|
// off by default
|
||||||
@@ -26,11 +24,10 @@ fn test_toggle_edit() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_edit_mode_hhmmss() {
|
fn test_default_edit_mode_hhmmss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_HOUR,
|
initial_value: ONE_HOUR,
|
||||||
current_value: ONE_HOUR,
|
current_value: ONE_HOUR,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: true,
|
with_decis: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -41,11 +38,10 @@ fn test_default_edit_mode_hhmmss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_edit_mode_mmss() {
|
fn test_default_edit_mode_mmss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_MINUTE,
|
initial_value: ONE_MINUTE,
|
||||||
current_value: ONE_MINUTE,
|
current_value: ONE_MINUTE,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: true,
|
with_decis: true,
|
||||||
});
|
});
|
||||||
// toggle on
|
// toggle on
|
||||||
@@ -55,11 +51,10 @@ fn test_default_edit_mode_mmss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_edit_mode_ss() {
|
fn test_default_edit_mode_ss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_SECOND,
|
initial_value: ONE_SECOND,
|
||||||
current_value: ONE_SECOND,
|
current_value: ONE_SECOND,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: true,
|
with_decis: true,
|
||||||
});
|
});
|
||||||
// toggle on
|
// toggle on
|
||||||
@@ -69,11 +64,10 @@ fn test_default_edit_mode_ss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_next_hhmmssd() {
|
fn test_edit_next_hhmmssd() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_HOUR,
|
initial_value: ONE_HOUR,
|
||||||
current_value: ONE_HOUR,
|
current_value: ONE_HOUR,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: true,
|
with_decis: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,11 +85,10 @@ fn test_edit_next_hhmmssd() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_next_hhmmss() {
|
fn test_edit_next_hhmmss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_HOUR,
|
initial_value: ONE_HOUR,
|
||||||
current_value: ONE_HOUR,
|
current_value: ONE_HOUR,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,11 +104,10 @@ fn test_edit_next_hhmmss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_next_mmssd() {
|
fn test_edit_next_mmssd() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_MINUTE,
|
initial_value: ONE_MINUTE,
|
||||||
current_value: ONE_MINUTE,
|
current_value: ONE_MINUTE,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: true,
|
with_decis: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -131,11 +123,10 @@ fn test_edit_next_mmssd() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_next_mmss() {
|
fn test_edit_next_mmss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_MINUTE,
|
initial_value: ONE_MINUTE,
|
||||||
current_value: ONE_MINUTE,
|
current_value: ONE_MINUTE,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -149,11 +140,10 @@ fn test_edit_next_mmss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_next_ssd() {
|
fn test_edit_next_ssd() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_SECOND * 3,
|
initial_value: ONE_SECOND * 3,
|
||||||
current_value: ONE_SECOND * 3,
|
current_value: ONE_SECOND * 3,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: true,
|
with_decis: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -165,11 +155,10 @@ fn test_edit_next_ssd() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_next_ss() {
|
fn test_edit_next_ss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_SECOND * 3,
|
initial_value: ONE_SECOND * 3,
|
||||||
current_value: ONE_SECOND * 3,
|
current_value: ONE_SECOND * 3,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -182,11 +171,10 @@ fn test_edit_next_ss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_prev_hhmmssd() {
|
fn test_edit_prev_hhmmssd() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_HOUR,
|
initial_value: ONE_HOUR,
|
||||||
current_value: ONE_HOUR,
|
current_value: ONE_HOUR,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: true,
|
with_decis: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,11 +191,10 @@ fn test_edit_prev_hhmmssd() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_prev_hhmmss() {
|
fn test_edit_prev_hhmmss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_HOUR,
|
initial_value: ONE_HOUR,
|
||||||
current_value: ONE_HOUR,
|
current_value: ONE_HOUR,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -222,11 +209,10 @@ fn test_edit_prev_hhmmss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_prev_mmssd() {
|
fn test_edit_prev_mmssd() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_MINUTE,
|
initial_value: ONE_MINUTE,
|
||||||
current_value: ONE_MINUTE,
|
current_value: ONE_MINUTE,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: true,
|
with_decis: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -243,11 +229,10 @@ fn test_edit_prev_mmssd() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_prev_mmss() {
|
fn test_edit_prev_mmss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_MINUTE,
|
initial_value: ONE_MINUTE,
|
||||||
current_value: ONE_MINUTE,
|
current_value: ONE_MINUTE,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -262,11 +247,10 @@ fn test_edit_prev_mmss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_prev_ssd() {
|
fn test_edit_prev_ssd() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_SECOND,
|
initial_value: ONE_SECOND,
|
||||||
current_value: ONE_SECOND,
|
current_value: ONE_SECOND,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: true,
|
with_decis: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -281,11 +265,10 @@ fn test_edit_prev_ssd() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_prev_ss() {
|
fn test_edit_prev_ss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: ONE_SECOND,
|
initial_value: ONE_SECOND,
|
||||||
current_value: ONE_SECOND,
|
current_value: ONE_SECOND,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -298,11 +281,10 @@ fn test_edit_prev_ss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_up_ss() {
|
fn test_edit_up_ss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: Duration::ZERO,
|
initial_value: Duration::ZERO,
|
||||||
current_value: Duration::ZERO,
|
current_value: Duration::ZERO,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -315,11 +297,10 @@ fn test_edit_up_ss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_up_mmss() {
|
fn test_edit_up_mmss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: Duration::ZERO,
|
initial_value: Duration::ZERO,
|
||||||
current_value: Duration::from_secs(60),
|
current_value: Duration::from_secs(60),
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -335,11 +316,10 @@ fn test_edit_up_mmss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_up_hhmmss() {
|
fn test_edit_up_hhmmss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: Duration::ZERO,
|
initial_value: Duration::ZERO,
|
||||||
current_value: Duration::from_secs(3600),
|
current_value: Duration::from_secs(3600),
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -357,11 +337,10 @@ fn test_edit_up_hhmmss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_down_ss() {
|
fn test_edit_down_ss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: Duration::ZERO,
|
initial_value: Duration::ZERO,
|
||||||
current_value: ONE_SECOND,
|
current_value: ONE_SECOND,
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -378,11 +357,10 @@ fn test_edit_down_ss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_down_mmss() {
|
fn test_edit_down_mmss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: Duration::ZERO,
|
initial_value: Duration::ZERO,
|
||||||
current_value: Duration::from_secs(120),
|
current_value: Duration::from_secs(120),
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -401,11 +379,10 @@ fn test_edit_down_mmss() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_down_hhmmss() {
|
fn test_edit_down_hhmmss() {
|
||||||
let mut c = Clock::<Timer>::new(ClockArgs {
|
let mut c = ClockState::<Timer>::new(ClockStateArgs {
|
||||||
initial_value: Duration::ZERO,
|
initial_value: Duration::ZERO,
|
||||||
current_value: Duration::from_secs(3600),
|
current_value: Duration::from_secs(3600),
|
||||||
tick_value: ONE_DECI_SECOND,
|
tick_value: ONE_DECI_SECOND,
|
||||||
style: Style::default(),
|
|
||||||
with_decis: false,
|
with_decis: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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,69 +17,238 @@ use ratatui::{
|
|||||||
text::Line,
|
text::Line,
|
||||||
widgets::{StatefulWidget, Widget},
|
widgets::{StatefulWidget, Widget},
|
||||||
};
|
};
|
||||||
use std::cmp::max;
|
|
||||||
|
|
||||||
use crate::{
|
use std::ops::Sub;
|
||||||
common::Style,
|
use std::{cmp::max, time::Duration};
|
||||||
events::{Event, EventHandler},
|
use time::OffsetDateTime;
|
||||||
utils::center,
|
|
||||||
widgets::clock::{self, Clock, ClockWidget},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
use super::edit_time::{EditTimeStateArgs, EditTimeWidget};
|
||||||
|
|
||||||
|
/// State for Countdown Widget
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Countdown {
|
pub struct CountdownState {
|
||||||
clock: Clock<clock::Countdown>,
|
/// clock to count down
|
||||||
|
clock: ClockState<clock::Countdown>,
|
||||||
|
/// clock to count time after `DONE` - similar to Mission Elapsed Time (MET)
|
||||||
|
elapsed_clock: ClockState<clock::Timer>,
|
||||||
|
app_time: AppTime,
|
||||||
|
/// Edit by local time
|
||||||
|
edit_time: Option<EditTimeState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Countdown {
|
impl CountdownState {
|
||||||
pub const fn new(clock: Clock<clock::Countdown>) -> Self {
|
pub fn new(
|
||||||
Self { clock }
|
clock: ClockState<clock::Countdown>,
|
||||||
|
elapsed_value: Duration,
|
||||||
|
app_time: AppTime,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
clock,
|
||||||
|
elapsed_clock: ClockState::<clock::Timer>::new(ClockStateArgs {
|
||||||
|
initial_value: Duration::ZERO,
|
||||||
|
current_value: elapsed_value,
|
||||||
|
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
||||||
|
with_decis: false,
|
||||||
|
})
|
||||||
|
// A previous `elapsed_value > 0` means the `Clock` was running before,
|
||||||
|
// but not in `Initial` state anymore. Updating `Mode` here
|
||||||
|
// is needed to handle `Event::Tick` in `EventHandler::update` properly
|
||||||
|
.with_mode(if elapsed_value.gt(&Duration::ZERO) {
|
||||||
|
ClockMode::Pause
|
||||||
|
} else {
|
||||||
|
ClockMode::Initial
|
||||||
|
}),
|
||||||
|
app_time,
|
||||||
|
edit_time: None,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_style(&mut self, style: Style) {
|
|
||||||
self.clock.style = style;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_with_decis(&mut self, with_decis: bool) {
|
pub fn set_with_decis(&mut self, with_decis: bool) {
|
||||||
self.clock.with_decis = with_decis;
|
self.clock.with_decis = with_decis;
|
||||||
|
self.elapsed_clock.with_decis = with_decis;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_clock(&self) -> &Clock<clock::Countdown> {
|
pub fn get_clock(&self) -> &ClockState<clock::Countdown> {
|
||||||
&self.clock
|
&self.clock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_running(&self) -> bool {
|
||||||
|
self.clock.is_running() || self.elapsed_clock.is_running()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_elapsed_value(&self) -> &DurationEx {
|
||||||
|
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 Countdown {
|
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() {
|
||||||
self.clock.tick();
|
self.clock.tick();
|
||||||
|
} else {
|
||||||
|
self.elapsed_clock.tick();
|
||||||
|
if self.elapsed_clock.is_initial() {
|
||||||
|
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) if key.code == KeyCode::Char('r') => {
|
|
||||||
self.clock.reset();
|
|
||||||
}
|
}
|
||||||
Event::Key(key) => match key.code {
|
Event::Key(key) => match key.code {
|
||||||
KeyCode::Char('r') => {
|
KeyCode::Char('r') => {
|
||||||
|
// reset both clocks to use intial values
|
||||||
self.clock.reset();
|
self.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
|
||||||
|
if !self.clock.is_done() {
|
||||||
self.clock.toggle_pause();
|
self.clock.toggle_pause();
|
||||||
|
} else {
|
||||||
|
self.elapsed_clock.toggle_pause();
|
||||||
}
|
}
|
||||||
KeyCode::Char('e') => {
|
|
||||||
|
// finish `edit_time` and continue for using `clock`
|
||||||
|
if let Some(edit_time) = &mut self.edit_time.clone() {
|
||||||
|
self.edit_time_done(edit_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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();
|
||||||
}
|
}
|
||||||
KeyCode::Left if 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() {
|
||||||
|
self.elapsed_clock.toggle_pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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 => {
|
||||||
self.clock.edit_up();
|
// safe unwrap because of previous check in `is_edit_time`
|
||||||
|
self.edit_time.as_mut().unwrap().prev();
|
||||||
}
|
}
|
||||||
KeyCode::Down if edit_mode => {
|
KeyCode::Up if is_edit_clock => {
|
||||||
|
self.clock.edit_up();
|
||||||
|
// whenever `clock`'s value is changed, reset `elapsed_clock`
|
||||||
|
self.elapsed_clock.reset();
|
||||||
|
}
|
||||||
|
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
|
||||||
|
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),
|
||||||
},
|
},
|
||||||
@@ -77,26 +258,81 @@ impl EventHandler for Countdown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CountdownWidget;
|
pub struct Countdown {
|
||||||
|
pub style: Style,
|
||||||
|
}
|
||||||
|
|
||||||
impl StatefulWidget for CountdownWidget {
|
fn human_days_diff(a: &OffsetDateTime, b: &OffsetDateTime) -> String {
|
||||||
type State = Countdown;
|
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 {
|
||||||
|
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();
|
// render `edit_time` OR `clock`
|
||||||
let label = Line::raw((format!("Countdown {}", state.clock.get_mode())).to_uppercase());
|
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(
|
||||||
|
if state.clock.is_done() {
|
||||||
|
if state.clock.with_decis {
|
||||||
|
format!(
|
||||||
|
"Countdown {} +{}",
|
||||||
|
state.clock.get_mode(),
|
||||||
|
state
|
||||||
|
.elapsed_clock
|
||||||
|
.get_current_value()
|
||||||
|
.to_string_with_decis()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"Countdown {} +{}",
|
||||||
|
state.clock.get_mode(),
|
||||||
|
state.elapsed_clock.get_current_value()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("Countdown {}", state.clock.get_mode())
|
||||||
|
}
|
||||||
|
.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
231
src/widgets/edit_time.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,57 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::common::Content;
|
use crate::common::{AppEditMode, AppTime, AppTimeFormat, Content};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
layout::{Constraint, Layout, Rect},
|
layout::{Constraint, Layout, Rect},
|
||||||
style::{Modifier, Style},
|
style::{Modifier, Style},
|
||||||
symbols::{border, scrollbar},
|
symbols::{border, scrollbar},
|
||||||
text::{Line, Span},
|
text::{Line, Span},
|
||||||
widgets::{Block, Borders, Cell, Row, Table, Widget},
|
widgets::{Block, Borders, Cell, Row, StatefulWidget, Table, Widget},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Footer {
|
pub struct FooterState {
|
||||||
pub show_menu: bool,
|
show_menu: bool,
|
||||||
pub running_clock: bool,
|
app_time_format: AppTimeFormat,
|
||||||
pub selected_content: Content,
|
|
||||||
pub edit_mode: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Footer {
|
impl FooterState {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
pub const fn new(show_menu: bool, app_time_format: AppTimeFormat) -> Self {
|
||||||
|
Self {
|
||||||
|
show_menu,
|
||||||
|
app_time_format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_show_menu(&mut self, value: bool) {
|
||||||
|
self.show_menu = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn get_show_menu(&self) -> bool {
|
||||||
|
self.show_menu
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn app_time_format(&self) -> &AppTimeFormat {
|
||||||
|
&self.app_time_format
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_app_time_format(&mut self) {
|
||||||
|
self.app_time_format = self.app_time_format.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Footer {
|
||||||
|
pub running_clock: bool,
|
||||||
|
pub selected_content: Content,
|
||||||
|
pub app_edit_mode: AppEditMode,
|
||||||
|
pub app_time: AppTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatefulWidget for Footer {
|
||||||
|
type State = FooterState;
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
let content_labels: BTreeMap<Content, &str> = BTreeMap::from([
|
let content_labels: BTreeMap<Content, &str> = BTreeMap::from([
|
||||||
(Content::Countdown, "[c]ountdown"),
|
(Content::Countdown, "[c]ountdown"),
|
||||||
(Content::Timer, "[t]imer"),
|
(Content::Timer, "[t]imer"),
|
||||||
@@ -31,15 +63,25 @@ impl Widget for Footer {
|
|||||||
|
|
||||||
let [border_area, menu_area] =
|
let [border_area, menu_area] =
|
||||||
Layout::vertical([Constraint::Length(1), Constraint::Percentage(100)]).areas(area);
|
Layout::vertical([Constraint::Length(1), Constraint::Percentage(100)]).areas(area);
|
||||||
|
|
||||||
Block::new()
|
Block::new()
|
||||||
.borders(Borders::TOP)
|
.borders(Borders::TOP)
|
||||||
.title(
|
.title(
|
||||||
format! {"[m]enu {:} ", if self.show_menu {scrollbar::VERTICAL.end} else {scrollbar::VERTICAL.begin}},
|
format! {"[m]enu {:} ", if state.show_menu {scrollbar::VERTICAL.end} else {scrollbar::VERTICAL.begin}},
|
||||||
)
|
)
|
||||||
|
.title(
|
||||||
|
Line::from(
|
||||||
|
match state.app_time_format {
|
||||||
|
// `Hidden` -> no (empty) title
|
||||||
|
AppTimeFormat::Hidden => "".into(),
|
||||||
|
// others -> add some space around
|
||||||
|
_ => format!(" {} ", self.app_time.format(&state.app_time_format))
|
||||||
|
}
|
||||||
|
).right_aligned())
|
||||||
.border_set(border::PLAIN)
|
.border_set(border::PLAIN)
|
||||||
.render(border_area, buf);
|
.render(border_area, buf);
|
||||||
// show menu
|
// show menu
|
||||||
if self.show_menu {
|
if state.show_menu {
|
||||||
let content_labels: Vec<Span> = content_labels
|
let content_labels: Vec<Span> = content_labels
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -60,7 +102,7 @@ impl Widget for Footer {
|
|||||||
|
|
||||||
const SPACE: &str = " "; // 2 empty spaces
|
const SPACE: &str = " "; // 2 empty spaces
|
||||||
let widths = [Constraint::Length(12), Constraint::Percentage(100)];
|
let widths = [Constraint::Length(12), Constraint::Percentage(100)];
|
||||||
Table::new(
|
let table = Table::new(
|
||||||
[
|
[
|
||||||
// content
|
// content
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
@@ -80,6 +122,14 @@ impl Widget for Footer {
|
|||||||
Span::from("[,]change style"),
|
Span::from("[,]change style"),
|
||||||
Span::from(SPACE),
|
Span::from(SPACE),
|
||||||
Span::from("[.]toggle deciseconds"),
|
Span::from("[.]toggle deciseconds"),
|
||||||
|
Span::from(SPACE),
|
||||||
|
Span::from(format!(
|
||||||
|
"[:]toggle {} time",
|
||||||
|
match self.app_time {
|
||||||
|
AppTime::Local(_) => "local",
|
||||||
|
AppTime::Utc(_) => "utc",
|
||||||
|
}
|
||||||
|
)),
|
||||||
])),
|
])),
|
||||||
]),
|
]),
|
||||||
// edit
|
// edit
|
||||||
@@ -89,21 +139,8 @@ impl Widget 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"
|
||||||
@@ -115,6 +152,12 @@ impl Widget 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),
|
||||||
@@ -123,13 +166,32 @@ impl Widget 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)), // ↓,
|
||||||
|
],
|
||||||
|
}
|
||||||
})),
|
})),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
widths,
|
widths,
|
||||||
)
|
)
|
||||||
.column_spacing(1)
|
.column_spacing(1);
|
||||||
.render(menu_area, buf);
|
|
||||||
|
Widget::render(table, menu_area, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::{
|
|||||||
constants::TICK_VALUE_MS,
|
constants::TICK_VALUE_MS,
|
||||||
events::{Event, EventHandler},
|
events::{Event, EventHandler},
|
||||||
utils::center,
|
utils::center,
|
||||||
widgets::clock::{Clock, ClockWidget, Countdown},
|
widgets::clock::{ClockState, ClockWidget, Countdown},
|
||||||
};
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
@@ -18,7 +18,7 @@ use strum::Display;
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::clock::ClockArgs;
|
use super::clock::ClockStateArgs;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Display, Hash, Eq, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Display, Hash, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
@@ -28,18 +28,18 @@ pub enum Mode {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ClockMap {
|
pub struct ClockMap {
|
||||||
work: Clock<Countdown>,
|
work: ClockState<Countdown>,
|
||||||
pause: Clock<Countdown>,
|
pause: ClockState<Countdown>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClockMap {
|
impl ClockMap {
|
||||||
fn get_mut(&mut self, mode: &Mode) -> &mut Clock<Countdown> {
|
fn get_mut(&mut self, mode: &Mode) -> &mut ClockState<Countdown> {
|
||||||
match mode {
|
match mode {
|
||||||
Mode::Work => &mut self.work,
|
Mode::Work => &mut self.work,
|
||||||
Mode::Pause => &mut self.pause,
|
Mode::Pause => &mut self.pause,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn get(&self, mode: &Mode) -> &Clock<Countdown> {
|
fn get(&self, mode: &Mode) -> &ClockState<Countdown> {
|
||||||
match mode {
|
match mode {
|
||||||
Mode::Work => &self.work,
|
Mode::Work => &self.work,
|
||||||
Mode::Pause => &self.pause,
|
Mode::Pause => &self.pause,
|
||||||
@@ -48,66 +48,62 @@ impl ClockMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Pomodoro {
|
pub struct PomodoroState {
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
clock_map: ClockMap,
|
clock_map: ClockMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PomodoroArgs {
|
pub struct PomodoroStateArgs {
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
pub initial_value_work: Duration,
|
pub initial_value_work: Duration,
|
||||||
pub current_value_work: Duration,
|
pub current_value_work: Duration,
|
||||||
pub initial_value_pause: Duration,
|
pub initial_value_pause: Duration,
|
||||||
pub current_value_pause: Duration,
|
pub current_value_pause: Duration,
|
||||||
pub style: Style,
|
|
||||||
pub with_decis: bool,
|
pub with_decis: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pomodoro {
|
impl PomodoroState {
|
||||||
pub fn new(args: PomodoroArgs) -> Self {
|
pub fn new(args: PomodoroStateArgs) -> Self {
|
||||||
let PomodoroArgs {
|
let PomodoroStateArgs {
|
||||||
mode,
|
mode,
|
||||||
initial_value_work,
|
initial_value_work,
|
||||||
current_value_work,
|
current_value_work,
|
||||||
initial_value_pause,
|
initial_value_pause,
|
||||||
current_value_pause,
|
current_value_pause,
|
||||||
style,
|
|
||||||
with_decis,
|
with_decis,
|
||||||
} = args;
|
} = args;
|
||||||
Self {
|
Self {
|
||||||
mode,
|
mode,
|
||||||
clock_map: ClockMap {
|
clock_map: ClockMap {
|
||||||
work: Clock::<Countdown>::new(ClockArgs {
|
work: ClockState::<Countdown>::new(ClockStateArgs {
|
||||||
initial_value: initial_value_work,
|
initial_value: initial_value_work,
|
||||||
current_value: current_value_work,
|
current_value: current_value_work,
|
||||||
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
||||||
style,
|
|
||||||
with_decis,
|
with_decis,
|
||||||
}),
|
}),
|
||||||
pause: Clock::<Countdown>::new(ClockArgs {
|
pause: ClockState::<Countdown>::new(ClockStateArgs {
|
||||||
initial_value: initial_value_pause,
|
initial_value: initial_value_pause,
|
||||||
current_value: current_value_pause,
|
current_value: current_value_pause,
|
||||||
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
||||||
style,
|
|
||||||
with_decis,
|
with_decis,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_clock_mut(&mut self) -> &mut Clock<Countdown> {
|
fn get_clock_mut(&mut self) -> &mut ClockState<Countdown> {
|
||||||
self.clock_map.get_mut(&self.mode)
|
self.clock_map.get_mut(&self.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_clock(&self) -> &Clock<Countdown> {
|
pub fn get_clock(&self) -> &ClockState<Countdown> {
|
||||||
self.clock_map.get(&self.mode)
|
self.clock_map.get(&self.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_clock_work(&self) -> &Clock<Countdown> {
|
pub fn get_clock_work(&self) -> &ClockState<Countdown> {
|
||||||
&self.clock_map.work
|
&self.clock_map.work
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_clock_pause(&self) -> &Clock<Countdown> {
|
pub fn get_clock_pause(&self) -> &ClockState<Countdown> {
|
||||||
&self.clock_map.pause
|
&self.clock_map.pause
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +111,6 @@ impl Pomodoro {
|
|||||||
&self.mode
|
&self.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_style(&mut self, style: Style) {
|
|
||||||
self.clock_map.work.style = style;
|
|
||||||
self.clock_map.pause.style = style;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_with_decis(&mut self, with_decis: bool) {
|
pub fn set_with_decis(&mut self, with_decis: bool) {
|
||||||
self.clock_map.work.with_decis = with_decis;
|
self.clock_map.work.with_decis = with_decis;
|
||||||
self.clock_map.pause.with_decis = with_decis;
|
self.clock_map.pause.with_decis = with_decis;
|
||||||
@@ -133,7 +124,7 @@ impl Pomodoro {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler for Pomodoro {
|
impl EventHandler for PomodoroState {
|
||||||
fn update(&mut self, event: Event) -> Option<Event> {
|
fn update(&mut self, event: Event) -> Option<Event> {
|
||||||
let edit_mode = self.get_clock().is_edit_mode();
|
let edit_mode = self.get_clock().is_edit_mode();
|
||||||
match event {
|
match event {
|
||||||
@@ -177,12 +168,14 @@ impl EventHandler for Pomodoro {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PomodoroWidget;
|
pub struct PomodoroWidget {
|
||||||
|
pub style: Style,
|
||||||
|
}
|
||||||
|
|
||||||
impl StatefulWidget for PomodoroWidget {
|
impl StatefulWidget for PomodoroWidget {
|
||||||
type State = Pomodoro;
|
type State = PomodoroState;
|
||||||
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_widget = ClockWidget::new();
|
let clock_widget = ClockWidget::new(self.style);
|
||||||
let label = Line::raw(
|
let label = Line::raw(
|
||||||
(format!(
|
(format!(
|
||||||
"Pomodoro {} {}",
|
"Pomodoro {} {}",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::{
|
|||||||
common::Style,
|
common::Style,
|
||||||
events::{Event, EventHandler},
|
events::{Event, EventHandler},
|
||||||
utils::center,
|
utils::center,
|
||||||
widgets::clock::{self, Clock, ClockWidget},
|
widgets::clock::{self, ClockState, ClockWidget},
|
||||||
};
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
@@ -14,29 +14,25 @@ use ratatui::{
|
|||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Timer {
|
pub struct TimerState {
|
||||||
clock: Clock<clock::Timer>,
|
clock: ClockState<clock::Timer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timer {
|
impl TimerState {
|
||||||
pub const fn new(clock: Clock<clock::Timer>) -> Self {
|
pub const fn new(clock: ClockState<clock::Timer>) -> Self {
|
||||||
Self { clock }
|
Self { clock }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_style(&mut self, style: Style) {
|
|
||||||
self.clock.style = style;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_with_decis(&mut self, with_decis: bool) {
|
pub fn set_with_decis(&mut self, with_decis: bool) {
|
||||||
self.clock.with_decis = with_decis;
|
self.clock.with_decis = with_decis;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_clock(&self) -> &Clock<clock::Timer> {
|
pub fn get_clock(&self) -> &ClockState<clock::Timer> {
|
||||||
&self.clock
|
&self.clock
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler for Timer {
|
impl EventHandler for TimerState {
|
||||||
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 edit_mode = self.clock.is_edit_mode();
|
||||||
match event {
|
match event {
|
||||||
@@ -73,13 +69,15 @@ impl EventHandler for Timer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TimerWidget;
|
pub struct Timer {
|
||||||
|
pub style: Style,
|
||||||
|
}
|
||||||
|
|
||||||
impl StatefulWidget for &TimerWidget {
|
impl StatefulWidget for Timer {
|
||||||
type State = Timer;
|
type State = TimerState;
|
||||||
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 = &mut state.clock;
|
let clock = &mut state.clock;
|
||||||
let clock_widget = ClockWidget::new();
|
let clock_widget = ClockWidget::new(self.style);
|
||||||
let label = Line::raw((format!("Timer {}", clock.get_mode())).to_uppercase());
|
let label = Line::raw((format!("Timer {}", clock.get_mode())).to_uppercase());
|
||||||
|
|
||||||
let area = center(
|
let area = center(
|
||||||
|
|||||||
Reference in New Issue
Block a user