├── .github └── workflows │ ├── check-coverage.yml │ ├── public-api.yml │ ├── record-coverage.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── codecov.yml ├── examples ├── iced-demo │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── OverusedGrotesk-Black.ttf │ │ └── main.rs ├── iced-indicator │ ├── Cargo.lock │ ├── Cargo.toml │ ├── resources │ │ ├── check.svg │ │ └── warn.svg │ └── src │ │ └── main.rs ├── iced-minimal │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs └── macroquad-example │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── main.rs └── src ├── animated.rs ├── lib.rs ├── public_api_test.rs ├── snapshots └── lilt__public_api_test__public_api.snap └── traits.rs /.github/workflows/check-coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage diff 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | coverage: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Install Rust 14 | run: rustup update stable 15 | 16 | - name: Install cargo-llvm-cov 17 | uses: taiki-e/install-action@cargo-llvm-cov 18 | 19 | - name: Generate PR coverage report 20 | run: cargo llvm-cov --json --summary-only > coverage-summary.json 21 | 22 | - name: Checkout coverage branch for baseline data 23 | uses: actions/checkout@v4 24 | with: 25 | ref: coverage 26 | path: coverage 27 | 28 | - name: Get latest baseline coverage report 29 | run: | 30 | latest=$(ls -t coverage/coverage-data/cov-action-*.json | head -n1) 31 | echo "Using baseline coverage report: $latest" 32 | cp "$latest" baseline_coverage.json 33 | 34 | - name: Compare coverage using shell script 35 | run: | 36 | pr_lines=$(jq '.data[0].totals.lines.percent' coverage-summary.json) 37 | base_lines=$(jq '.data[0].totals.lines.percent' baseline_coverage.json) 38 | echo "PR line coverage: $pr_lines%" 39 | echo "Baseline line coverage: $base_lines%" 40 | drop=$(echo "$base_lines - $pr_lines" | bc -l) 41 | echo "Coverage drop: $drop%" 42 | threshold=1.0 43 | if (( $(echo "$drop > $threshold" | bc -l) )); then 44 | echo "Coverage drop of $drop% exceeds allowed threshold of $threshold%." 45 | exit 1 46 | else 47 | echo "Coverage is within acceptable limits." 48 | fi 49 | -------------------------------------------------------------------------------- /.github/workflows/public-api.yml: -------------------------------------------------------------------------------- 1 | name: public api 2 | 3 | on: 4 | pull_request: 5 | branches: ["main"] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | build: 16 | name: check public api 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Run tests 22 | run: cargo test --verbose --features test-api 23 | -------------------------------------------------------------------------------- /.github/workflows/record-coverage.yml: -------------------------------------------------------------------------------- 1 | name: record coverage 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | record-coverage: 10 | permissions: 11 | contents: write 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Install Rust 17 | run: rustup update stable 18 | 19 | - name: Install cargo-llvm-cov 20 | uses: taiki-e/install-action@cargo-llvm-cov 21 | 22 | - name: Run coverage and output JSON 23 | id: run_cov 24 | run: | 25 | set -euxo pipefail 26 | cargo llvm-cov --json --summary-only > coverage-summary.json 27 | cat coverage-summary.json 28 | 29 | - name: Determine branch name 30 | id: get_branch 31 | run: | 32 | set -euxo pipefail 33 | if [ -n "${GITHUB_HEAD_REF}" ]; then 34 | branch=${GITHUB_HEAD_REF} 35 | else 36 | branch=${GITHUB_REF#refs/heads/} 37 | fi 38 | echo "Branch is: $branch" 39 | echo "branch=${branch}" >> $GITHUB_OUTPUT 40 | 41 | - name: Checkout coverage branch 42 | uses: actions/checkout@v3 43 | with: 44 | ref: coverage 45 | token: ${{ secrets.GITHUB_TOKEN }} 46 | path: coverage_branch 47 | 48 | - name: Generate badge from template 49 | run: | 50 | COVERAGE=$(jq -r '.data[0].totals.lines.percent' coverage-summary.json) 51 | COVERAGE_ROUNDED=$(printf "%.0f" "$COVERAGE") 52 | COVERAGE_DISPLAY="${COVERAGE_ROUNDED}%" 53 | echo "Generating badge.svg with coverage: ${COVERAGE_DISPLAY}" 54 | sed "s/%%/${COVERAGE_DISPLAY}/g" coverage_branch/template.svg > coverage_branch/badge.svg 55 | 56 | - name: Save coverage JSON and badge for branch 57 | run: | 58 | set -euxo pipefail 59 | branch=${{ steps.get_branch.outputs.branch }} 60 | commit=${GITHUB_SHA} 61 | mkdir -p coverage_branch/coverage-data 62 | rm -f coverage_branch/coverage-data/${branch}-*.json || true 63 | cp coverage-summary.json "coverage_branch/coverage-data/${branch}-${commit}.json" 64 | cd coverage_branch 65 | git config user.name "github-actions[bot]" 66 | git config user.email "github-actions[bot]@users.noreply.github.com" 67 | git add -A 68 | if ! git diff --cached --quiet; then 69 | git commit -m "Update coverage for branch ${branch} at commit ${commit} on $(date -u)" 70 | git push origin coverage 71 | else 72 | echo "No changes to commit." 73 | fi 74 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: rust 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | build: 18 | name: build & test 🧪 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Build 24 | run: cargo build --verbose 25 | - name: Run tests 26 | run: cargo test --verbose 27 | - name: Build examples 28 | run: | 29 | set -euxo pipefail 30 | for d in examples/*/ ; do 31 | echo "Building example in directory $d" 32 | (cd "$d" && CARGO_TARGET_DIR=../target cargo build --verbose) 33 | done 34 | 35 | clippy: 36 | name: clippy 🪢 37 | runs-on: ubuntu-latest 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | - name: Run clippy 42 | run: cargo clippy -- --deny warnings 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pkg/ 3 | **/*.rs.bk 4 | dist/ 5 | traces/ 6 | *.DS_Store 7 | *.wasm 8 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "bumpalo" 7 | version = "3.16.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 10 | 11 | [[package]] 12 | name = "camino" 13 | version = "1.1.9" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" 16 | dependencies = [ 17 | "serde", 18 | ] 19 | 20 | [[package]] 21 | name = "cargo-manifest" 22 | version = "0.17.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8b2ce2075c35e4b492b93e3d5dd1dd3670de553f15045595daef8164ed9a3751" 25 | dependencies = [ 26 | "serde", 27 | "thiserror 1.0.69", 28 | "toml", 29 | ] 30 | 31 | [[package]] 32 | name = "cargo-platform" 33 | version = "0.1.9" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" 36 | dependencies = [ 37 | "serde", 38 | ] 39 | 40 | [[package]] 41 | name = "cargo_metadata" 42 | version = "0.18.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" 45 | dependencies = [ 46 | "camino", 47 | "cargo-platform", 48 | "semver", 49 | "serde", 50 | "serde_json", 51 | "thiserror 1.0.69", 52 | ] 53 | 54 | [[package]] 55 | name = "cfg-if" 56 | version = "1.0.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 59 | 60 | [[package]] 61 | name = "console" 62 | version = "0.15.10" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" 65 | dependencies = [ 66 | "encode_unicode", 67 | "libc", 68 | "once_cell", 69 | "windows-sys", 70 | ] 71 | 72 | [[package]] 73 | name = "encode_unicode" 74 | version = "1.0.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 77 | 78 | [[package]] 79 | name = "equivalent" 80 | version = "1.0.1" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 83 | 84 | [[package]] 85 | name = "hashbag" 86 | version = "0.1.12" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "98f494b2060b2a8f5e63379e1e487258e014cee1b1725a735816c0107a2e9d93" 89 | 90 | [[package]] 91 | name = "hashbrown" 92 | version = "0.15.2" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 95 | 96 | [[package]] 97 | name = "indexmap" 98 | version = "2.7.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 101 | dependencies = [ 102 | "equivalent", 103 | "hashbrown", 104 | ] 105 | 106 | [[package]] 107 | name = "insta" 108 | version = "1.42.1" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "71c1b125e30d93896b365e156c33dadfffab45ee8400afcbba4752f59de08a86" 111 | dependencies = [ 112 | "console", 113 | "linked-hash-map", 114 | "once_cell", 115 | "pin-project", 116 | "similar", 117 | ] 118 | 119 | [[package]] 120 | name = "itoa" 121 | version = "1.0.14" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 124 | 125 | [[package]] 126 | name = "js-sys" 127 | version = "0.3.76" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" 130 | dependencies = [ 131 | "once_cell", 132 | "wasm-bindgen", 133 | ] 134 | 135 | [[package]] 136 | name = "libc" 137 | version = "0.2.167" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" 140 | 141 | [[package]] 142 | name = "lilt" 143 | version = "0.8.1" 144 | dependencies = [ 145 | "insta", 146 | "public-api", 147 | "rustdoc-json", 148 | "rustup-toolchain", 149 | "web-time", 150 | ] 151 | 152 | [[package]] 153 | name = "linked-hash-map" 154 | version = "0.5.6" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 157 | 158 | [[package]] 159 | name = "log" 160 | version = "0.4.22" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 163 | 164 | [[package]] 165 | name = "memchr" 166 | version = "2.7.4" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 169 | 170 | [[package]] 171 | name = "once_cell" 172 | version = "1.20.2" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 175 | 176 | [[package]] 177 | name = "pin-project" 178 | version = "1.1.7" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" 181 | dependencies = [ 182 | "pin-project-internal", 183 | ] 184 | 185 | [[package]] 186 | name = "pin-project-internal" 187 | version = "1.1.7" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" 190 | dependencies = [ 191 | "proc-macro2", 192 | "quote", 193 | "syn", 194 | ] 195 | 196 | [[package]] 197 | name = "pin-project-lite" 198 | version = "0.2.15" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 201 | 202 | [[package]] 203 | name = "proc-macro2" 204 | version = "1.0.92" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 207 | dependencies = [ 208 | "unicode-ident", 209 | ] 210 | 211 | [[package]] 212 | name = "public-api" 213 | version = "0.43.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "31529be2a00213a5eeca25ed983569db17036d90de2abe40c55aceaa0915795b" 216 | dependencies = [ 217 | "hashbag", 218 | "rustdoc-types", 219 | "serde", 220 | "serde_json", 221 | "thiserror 2.0.11", 222 | ] 223 | 224 | [[package]] 225 | name = "quote" 226 | version = "1.0.37" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 229 | dependencies = [ 230 | "proc-macro2", 231 | ] 232 | 233 | [[package]] 234 | name = "rustdoc-json" 235 | version = "0.9.4" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "b9cba50605762dcbc2e103a3a8b195c83b6978aca443c7dd6699ed2971c213e5" 238 | dependencies = [ 239 | "cargo-manifest", 240 | "cargo_metadata", 241 | "serde", 242 | "thiserror 2.0.11", 243 | "toml", 244 | "tracing", 245 | ] 246 | 247 | [[package]] 248 | name = "rustdoc-types" 249 | version = "0.35.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "bf583db9958b3161d7980a56a8ee3c25e1a40708b81259be72584b7e0ea07c95" 252 | dependencies = [ 253 | "serde", 254 | ] 255 | 256 | [[package]] 257 | name = "rustup-toolchain" 258 | version = "0.1.9" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "1b1146c7808061ba181ab8e2f0836e91b0cc2fd10a70e64dae5c55381ce6d6cf" 261 | dependencies = [ 262 | "thiserror 2.0.11", 263 | ] 264 | 265 | [[package]] 266 | name = "ryu" 267 | version = "1.0.19" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 270 | 271 | [[package]] 272 | name = "semver" 273 | version = "1.0.25" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" 276 | dependencies = [ 277 | "serde", 278 | ] 279 | 280 | [[package]] 281 | name = "serde" 282 | version = "1.0.215" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 285 | dependencies = [ 286 | "serde_derive", 287 | ] 288 | 289 | [[package]] 290 | name = "serde_derive" 291 | version = "1.0.215" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 294 | dependencies = [ 295 | "proc-macro2", 296 | "quote", 297 | "syn", 298 | ] 299 | 300 | [[package]] 301 | name = "serde_json" 302 | version = "1.0.138" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" 305 | dependencies = [ 306 | "itoa", 307 | "memchr", 308 | "ryu", 309 | "serde", 310 | ] 311 | 312 | [[package]] 313 | name = "serde_spanned" 314 | version = "0.6.8" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 317 | dependencies = [ 318 | "serde", 319 | ] 320 | 321 | [[package]] 322 | name = "similar" 323 | version = "2.7.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" 326 | 327 | [[package]] 328 | name = "syn" 329 | version = "2.0.90" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 332 | dependencies = [ 333 | "proc-macro2", 334 | "quote", 335 | "unicode-ident", 336 | ] 337 | 338 | [[package]] 339 | name = "thiserror" 340 | version = "1.0.69" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 343 | dependencies = [ 344 | "thiserror-impl 1.0.69", 345 | ] 346 | 347 | [[package]] 348 | name = "thiserror" 349 | version = "2.0.11" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" 352 | dependencies = [ 353 | "thiserror-impl 2.0.11", 354 | ] 355 | 356 | [[package]] 357 | name = "thiserror-impl" 358 | version = "1.0.69" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 361 | dependencies = [ 362 | "proc-macro2", 363 | "quote", 364 | "syn", 365 | ] 366 | 367 | [[package]] 368 | name = "thiserror-impl" 369 | version = "2.0.11" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" 372 | dependencies = [ 373 | "proc-macro2", 374 | "quote", 375 | "syn", 376 | ] 377 | 378 | [[package]] 379 | name = "toml" 380 | version = "0.8.19" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 383 | dependencies = [ 384 | "indexmap", 385 | "serde", 386 | "serde_spanned", 387 | "toml_datetime", 388 | "toml_edit", 389 | ] 390 | 391 | [[package]] 392 | name = "toml_datetime" 393 | version = "0.6.8" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 396 | dependencies = [ 397 | "serde", 398 | ] 399 | 400 | [[package]] 401 | name = "toml_edit" 402 | version = "0.22.22" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 405 | dependencies = [ 406 | "indexmap", 407 | "serde", 408 | "serde_spanned", 409 | "toml_datetime", 410 | "winnow", 411 | ] 412 | 413 | [[package]] 414 | name = "tracing" 415 | version = "0.1.41" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 418 | dependencies = [ 419 | "pin-project-lite", 420 | "tracing-attributes", 421 | "tracing-core", 422 | ] 423 | 424 | [[package]] 425 | name = "tracing-attributes" 426 | version = "0.1.28" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 429 | dependencies = [ 430 | "proc-macro2", 431 | "quote", 432 | "syn", 433 | ] 434 | 435 | [[package]] 436 | name = "tracing-core" 437 | version = "0.1.33" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 440 | dependencies = [ 441 | "once_cell", 442 | ] 443 | 444 | [[package]] 445 | name = "unicode-ident" 446 | version = "1.0.14" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 449 | 450 | [[package]] 451 | name = "wasm-bindgen" 452 | version = "0.2.99" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" 455 | dependencies = [ 456 | "cfg-if", 457 | "once_cell", 458 | "wasm-bindgen-macro", 459 | ] 460 | 461 | [[package]] 462 | name = "wasm-bindgen-backend" 463 | version = "0.2.99" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" 466 | dependencies = [ 467 | "bumpalo", 468 | "log", 469 | "proc-macro2", 470 | "quote", 471 | "syn", 472 | "wasm-bindgen-shared", 473 | ] 474 | 475 | [[package]] 476 | name = "wasm-bindgen-macro" 477 | version = "0.2.99" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" 480 | dependencies = [ 481 | "quote", 482 | "wasm-bindgen-macro-support", 483 | ] 484 | 485 | [[package]] 486 | name = "wasm-bindgen-macro-support" 487 | version = "0.2.99" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" 490 | dependencies = [ 491 | "proc-macro2", 492 | "quote", 493 | "syn", 494 | "wasm-bindgen-backend", 495 | "wasm-bindgen-shared", 496 | ] 497 | 498 | [[package]] 499 | name = "wasm-bindgen-shared" 500 | version = "0.2.99" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" 503 | 504 | [[package]] 505 | name = "web-time" 506 | version = "1.1.0" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 509 | dependencies = [ 510 | "js-sys", 511 | "wasm-bindgen", 512 | ] 513 | 514 | [[package]] 515 | name = "windows-sys" 516 | version = "0.59.0" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 519 | dependencies = [ 520 | "windows-targets", 521 | ] 522 | 523 | [[package]] 524 | name = "windows-targets" 525 | version = "0.52.6" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 528 | dependencies = [ 529 | "windows_aarch64_gnullvm", 530 | "windows_aarch64_msvc", 531 | "windows_i686_gnu", 532 | "windows_i686_gnullvm", 533 | "windows_i686_msvc", 534 | "windows_x86_64_gnu", 535 | "windows_x86_64_gnullvm", 536 | "windows_x86_64_msvc", 537 | ] 538 | 539 | [[package]] 540 | name = "windows_aarch64_gnullvm" 541 | version = "0.52.6" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 544 | 545 | [[package]] 546 | name = "windows_aarch64_msvc" 547 | version = "0.52.6" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 550 | 551 | [[package]] 552 | name = "windows_i686_gnu" 553 | version = "0.52.6" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 556 | 557 | [[package]] 558 | name = "windows_i686_gnullvm" 559 | version = "0.52.6" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 562 | 563 | [[package]] 564 | name = "windows_i686_msvc" 565 | version = "0.52.6" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 568 | 569 | [[package]] 570 | name = "windows_x86_64_gnu" 571 | version = "0.52.6" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 574 | 575 | [[package]] 576 | name = "windows_x86_64_gnullvm" 577 | version = "0.52.6" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 580 | 581 | [[package]] 582 | name = "windows_x86_64_msvc" 583 | version = "0.52.6" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 586 | 587 | [[package]] 588 | name = "winnow" 589 | version = "0.6.20" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 592 | dependencies = [ 593 | "memchr", 594 | ] 595 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lilt" 3 | version = "0.8.1" 4 | edition = "2024" 5 | description = " A simple library for running interruptable, transition based animations as a function of time." 6 | repository = "https://github.com/cyypherus/lilt" 7 | license = "MIT" 8 | keywords = ["animation", "interpolation"] 9 | authors = ["cyypherus"] 10 | 11 | [lib] 12 | crate-type = ["lib"] 13 | 14 | [features] 15 | test-api = [] 16 | 17 | [dev-dependencies] 18 | insta = "1.42.1" 19 | public-api = "0.43.0" 20 | rustdoc-json = "0.9.4" 21 | rustup-toolchain = "0.1.9" 22 | 23 | [dependencies] 24 | web-time = "1.1.0" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ejjonny 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lilt 2 | 3 | 4 | 5 | ![rust](https://github.com/ejjonny/lilt/actions/workflows/rust.yml/badge.svg) 6 | ![coverage](https://raw.githubusercontent.com/cyypherus/lilt/coverage/badge.svg) 7 | [![crates.io](https://img.shields.io/crates/v/lilt.svg)](https://crates.io/crates/lilt) 8 | [![docs.rs](https://img.shields.io/badge/docs.rs-lilt-blue?logo=docs.rs)](https://docs.rs/lilt) 9 | [![downloads](https://img.shields.io/crates/d/lilt.svg)](https://crates.io/crates/lilt) 10 | [![license](https://img.shields.io/crates/l/lilt.svg)](https://github.com/lilt/blob/master/LICENSE) 11 | 12 | A simple library for running interruptable, transition based animations as a function of time. 13 | 14 | This library only implements animations & would be most useful along with a GUI library that can do GUI things (like [iced](https://github.com/iced-rs/iced)). 15 | 16 | ## Getting Started 17 | 18 | ### Define 19 | 20 | Embed the state you want to animate in an `Animated` struct. 21 | 22 | ```rust 23 | struct MyViewState { 24 | toggle: Animated, 25 | } 26 | ``` 27 | 28 | When you initialize your view state - define the initial state & configure the animation to your liking. 29 | 30 | ```rust 31 | let mut state = MyViewState { 32 | toggle: Animated::new(false) 33 | .duration(300.) 34 | .easing(Easing::EaseOut) 35 | .delay(30.) 36 | .repeat(3), 37 | }; 38 | ``` 39 | 40 | ### Transition 41 | 42 | When your state needs an update, call the `transition` function on your animated state, passing the current time. 43 | 44 | ```rust 45 | let now = std::time::Instant::now(); 46 | state 47 | .toggle 48 | .transition(!state.animated_toggle.value, now); 49 | ``` 50 | 51 | ### Render 52 | 53 | While rendering a view based on your state - use the `animate` function on your state to get the interpolated value for the current frame. 54 | 55 | ```rust 56 | let now = std::time::Instant::now(); 57 | 58 | // The wrapped value can be used to interpolate any values that implement `Interpolable` 59 | let animated_width = self.toggle.animate_bool(100., 500., now); 60 | 61 | // If the wrapped value itself is `Interpolable`, it can easily be interpolated in place 62 | let animated_width = self.width.animate_wrapped(now); 63 | 64 | // There are plenty of `animate` methods for interpolating things based on the wrapped value. 65 | ``` 66 | 67 | ### What's the point? 68 | 69 | lilt emerged from the need for ELM compatible / reactive animations. 70 | 71 | The animations modeled by this library don't require periodic mutation like a 'tick' function - all interim states of the animation are predefined when 'transition' is called, & then accessed while rendering based on the current time. 72 | 73 | lilt animations are fully independent of frame rate or tick frequency & only need to be computed if they're used during rendering. 74 | 75 | ## [Examples](examples/) 76 | 77 | Examples aren't included in lilt's manifest to reduce the overhead from large GUI crates while maintainers work on the main crate. To run examples, simply navigate to the desired example & run it as if it were a standalone crate with `cargo run` 78 | 79 | ![indicator](https://github.com/ejjonny/lilt/assets/17223924/e4f81d63-67a4-4586-a2cf-309c687fd59d) 80 | 81 | ### Contributing 82 | 83 | This repo uses `cargo insta` to snapshot test the public API. 84 | 85 | If your PR changes the public API, one of the checks will fail by default. 86 | 87 | If the changes to the public API were intentional you can update the snapshot by running: 88 | 89 | `INSTA_UPDATE=always cargo test --features test-api` 90 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: "diff" 3 | -------------------------------------------------------------------------------- /examples/iced-demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iced-demo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | iced = { version = "0.13.1", features = ["canvas", "tokio", "svg"] } 8 | lilt = { path = "../../" } 9 | iced_futures = "0.13.1" 10 | -------------------------------------------------------------------------------- /examples/iced-demo/src/OverusedGrotesk-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyypherus/lilt/526ddfa4396c02dc09c844ec40aa811cc80f63d6/examples/iced-demo/src/OverusedGrotesk-Black.ttf -------------------------------------------------------------------------------- /examples/iced-demo/src/main.rs: -------------------------------------------------------------------------------- 1 | use iced::font::Weight; 2 | use iced::widget::canvas::path::lyon_path::geom::euclid::Transform2D; 3 | use iced::widget::canvas::path::lyon_path::geom::Angle; 4 | use iced::widget::canvas::path::lyon_path::math::vector; 5 | use iced::widget::canvas::path::Arc; 6 | use iced::widget::canvas::{self, Frame, Geometry, Path, Program, Stroke}; 7 | use iced::widget::{horizontal_space, text, vertical_space, Column, Container, Row, Space, Stack}; 8 | use iced::window::frames; 9 | use iced::{mouse, Color, Font, Point, Rectangle, Renderer, Task}; 10 | use iced::{Element, Length, Theme}; 11 | use lilt::Animated; 12 | use lilt::Easing; 13 | use std::f32::consts::PI; 14 | use std::time::Instant; 15 | 16 | pub fn main() -> iced::Result { 17 | iced::application("Iced Demo", Example::update, Example::view) 18 | .font(include_bytes!("OverusedGrotesk-Black.ttf")) 19 | .subscription(Example::subscription) 20 | .run() 21 | } 22 | 23 | struct Example { 24 | spinner_trim: Animated, 25 | spinner_rotation: Animated, 26 | bars: Vec>, 27 | } 28 | 29 | #[derive(Debug, Clone, Copy)] 30 | enum AppMessage { 31 | Tick, 32 | } 33 | 34 | impl Default for Example { 35 | fn default() -> Self { 36 | Self::new() 37 | } 38 | } 39 | 40 | impl Example { 41 | fn new() -> Self { 42 | let time = std::time::Instant::now(); 43 | let left: Vec> = (0..50) 44 | .map(|i| { 45 | Animated::new(false) 46 | .duration(800.) 47 | .easing(Easing::EaseInOutBounce) 48 | .delay(i as f32 * 30.) 49 | .repeat_forever() 50 | .auto_start(true, time) 51 | }) 52 | .rev() 53 | .collect(); 54 | let right: Vec> = (0..50) 55 | .map(|i| { 56 | Animated::new(false) 57 | .duration(800.) 58 | .easing(Easing::EaseInOutBounce) 59 | .delay(i as f32 * 30.) 60 | .repeat_forever() 61 | .auto_start(true, time) 62 | }) 63 | .collect(); 64 | Self { 65 | spinner_trim: Animated::new(false) 66 | .duration(900.) 67 | .repeat_forever() 68 | .auto_reverse() 69 | .auto_start(true, time), 70 | spinner_rotation: Animated::new(false) 71 | .easing(Easing::Linear) 72 | .duration(900.) 73 | .repeat_forever() 74 | .auto_start(true, time), 75 | bars: [left, right].concat(), 76 | } 77 | } 78 | 79 | fn subscription(&self) -> iced::Subscription { 80 | frames().map(|_| AppMessage::Tick) 81 | } 82 | 83 | fn update(&mut self, message: AppMessage) -> Task { 84 | match message { 85 | AppMessage::Tick => (), 86 | } 87 | Task::none() 88 | } 89 | 90 | fn view(&self) -> Element { 91 | let time = std::time::Instant::now(); 92 | let mut overused_font: Font = Font::with_name("Overused Grotesk Roman"); 93 | overused_font.weight = Weight::Black; 94 | Stack::new() 95 | .push( 96 | Row::new() 97 | .extend( 98 | self.bars 99 | .iter() 100 | .map(|b| { 101 | Container::new(Space::new( 102 | Length::Fill, 103 | b.animate_bool(10., 300., time), 104 | )) 105 | .style(move |_| { 106 | iced::widget::container::Style::default().background( 107 | Color::from_rgb8( 108 | b.animate_bool(0., 108., time) as u8, 109 | b.animate_bool(0., 74., time) as u8, 110 | b.animate_bool(0., 181., time) as u8, 111 | ), 112 | ) 113 | }) 114 | .into() 115 | }) 116 | .collect::>(), 117 | ) 118 | .height(Length::Fill) 119 | .align_y(iced::Alignment::Center), 120 | ) 121 | .push( 122 | Column::new() 123 | .push(vertical_space()) 124 | .push( 125 | Row::new() 126 | .push(horizontal_space()) 127 | .push(text("lilt").font(overused_font).size(100.)) 128 | .push(horizontal_space()), 129 | ) 130 | .push(vertical_space()), 131 | ) 132 | .push( 133 | Column::new() 134 | .push(vertical_space()) 135 | .push( 136 | Row::new() 137 | .push(horizontal_space()) 138 | .push( 139 | iced::widget::canvas(Spinner { 140 | trim: self.spinner_trim.animate_bool(0.5, 0., time), 141 | rotation: self.spinner_rotation.animate_bool(0., 1., time), 142 | }) 143 | .height(Length::Fixed(250.)) 144 | .width(Length::Fixed(250.)), 145 | ) 146 | .push(horizontal_space()), 147 | ) 148 | .push(vertical_space()), 149 | ) 150 | .width(Length::Fill) 151 | .height(Length::Fill) 152 | .into() 153 | } 154 | } 155 | 156 | #[derive(Debug)] 157 | struct Spinner { 158 | trim: f32, 159 | rotation: f32, 160 | } 161 | 162 | impl Program for Spinner { 163 | type State = (); 164 | 165 | fn draw( 166 | &self, 167 | _state: &(), 168 | renderer: &Renderer, 169 | _theme: &Theme, 170 | bounds: Rectangle, 171 | _cursor: mouse::Cursor, 172 | ) -> Vec { 173 | let mut frame = Frame::new(renderer, bounds.size()); 174 | let stroke = 30.; 175 | let radius = (f32::min(bounds.width, bounds.height) * 0.5) - (stroke * 0.5); 176 | let circle = Path::new(|p| { 177 | p.arc(Arc { 178 | center: Point::new(bounds.center_x() - bounds.x, bounds.center_y() - bounds.y), 179 | radius, 180 | start_angle: iced::Radians(0.), 181 | end_angle: iced::Radians(self.trim * 2. * PI), 182 | }); 183 | }) 184 | .transform( 185 | &Transform2D::identity() 186 | .then_translate(vector(-bounds.width * 0.5, -bounds.height * 0.5)) 187 | .then_rotate(Angle::radians(self.rotation * 2. * PI)) 188 | .then_translate(vector(bounds.width * 0.5, bounds.height * 0.5)), 189 | ); 190 | // let _debug_frame = Path::rectangle(Point::ORIGIN, bounds.size()); 191 | // frame.fill(&_debug_frame, Color::from_rgb8(255, 0, 0)); 192 | frame.stroke( 193 | &circle, 194 | Stroke::default() 195 | .with_width(stroke) 196 | .with_line_cap(canvas::LineCap::Round) 197 | .with_color(Color::WHITE), 198 | ); 199 | vec![frame.into_geometry()] 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /examples/iced-indicator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iced-demo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | iced = { version = "0.13.1", features = ["canvas", "tokio", "svg"] } 8 | lilt = { path = "../../" } 9 | -------------------------------------------------------------------------------- /examples/iced-indicator/resources/check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/iced-indicator/resources/warn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/iced-indicator/src/main.rs: -------------------------------------------------------------------------------- 1 | use iced::border::Radius; 2 | use iced::font::Weight; 3 | use iced::widget::canvas::path::lyon_path::geom::euclid::Transform2D; 4 | use iced::widget::canvas::path::lyon_path::geom::Angle; 5 | use iced::widget::canvas::path::lyon_path::math::vector; 6 | use iced::widget::canvas::path::Arc; 7 | use iced::widget::canvas::{self, Frame, Geometry, Path, Program, Stroke}; 8 | use iced::widget::{center, container, svg, text, vertical_space, Container, Row, Stack}; 9 | use iced::window::frames; 10 | use iced::{ 11 | mouse, Background, Border, Color, Font, Point, Rectangle, Renderer, Subscription, Task, 12 | }; 13 | use iced::{Element, Length, Theme}; 14 | use lilt::{Animated, FloatRepresentable}; 15 | use lilt::{Easing, Interpolable}; 16 | use std::default::Default; 17 | use std::f32::consts::PI; 18 | use std::time::{Duration, Instant}; 19 | 20 | pub fn main() -> iced::Result { 21 | iced::application("Iced Demo", Example::update, Example::view) 22 | .subscription(Example::subscription) 23 | .run() 24 | } 25 | 26 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 27 | enum IndicatorState { 28 | Analyzing, 29 | Safe, 30 | Warning, 31 | } 32 | 33 | impl FloatRepresentable for IndicatorState { 34 | fn float_value(&self) -> f32 { 35 | match self { 36 | IndicatorState::Analyzing => 0., 37 | IndicatorState::Safe => 1., 38 | IndicatorState::Warning => 2., 39 | } 40 | } 41 | } 42 | 43 | struct Example { 44 | spinner_rotation: Animated, 45 | spinner_rotation_speed: Animated, 46 | indicator_state: Animated, 47 | } 48 | 49 | #[derive(Debug, Clone, Copy)] 50 | enum AppMessage { 51 | Tick, 52 | UpdateStatus, 53 | } 54 | 55 | impl Default for Example { 56 | fn default() -> Self { 57 | Self::new() 58 | } 59 | } 60 | 61 | impl Example { 62 | fn new() -> Self { 63 | let time = std::time::Instant::now(); 64 | Self { 65 | spinner_rotation: Animated::new(false) 66 | .easing(Easing::Linear) 67 | .duration(300.) 68 | .repeat_forever() 69 | .auto_start(true, time), 70 | spinner_rotation_speed: Animated::new(false) 71 | .easing(Easing::EaseInOut) 72 | .duration(500.) 73 | .repeat_forever() 74 | .auto_reverse() 75 | .auto_start(true, time), 76 | indicator_state: Animated::new(IndicatorState::Analyzing) 77 | .easing(Easing::Custom(|l| { 78 | if l < 0.5 { 79 | Easing::EaseInOutCirc.value(l) 80 | } else { 81 | Easing::EaseInOutElastic.value(l) 82 | } 83 | })) 84 | .duration(200.), 85 | } 86 | } 87 | 88 | fn subscription(&self) -> iced::Subscription { 89 | Subscription::batch(vec![ 90 | iced::time::every(std::time::Duration::from_millis(2000)) 91 | .map(|_| AppMessage::UpdateStatus), 92 | frames().map(|_| AppMessage::Tick), 93 | ]) 94 | } 95 | 96 | fn update(&mut self, message: AppMessage) -> Task { 97 | let time = std::time::Instant::now(); 98 | match message { 99 | AppMessage::Tick => (), 100 | AppMessage::UpdateStatus => match self.indicator_state.value { 101 | IndicatorState::Analyzing => { 102 | self.indicator_state.transition(IndicatorState::Safe, time) 103 | } 104 | IndicatorState::Safe => self 105 | .indicator_state 106 | .transition(IndicatorState::Warning, time), 107 | IndicatorState::Warning => self 108 | .indicator_state 109 | .transition(IndicatorState::Analyzing, time), 110 | }, 111 | } 112 | Task::none() 113 | } 114 | 115 | fn view(&self) -> Element { 116 | let time = std::time::Instant::now(); 117 | let backing_color_analyzing = Color::from_rgb8(187, 218, 252); 118 | let fg_color_analyzing = Color::from_rgb8(96, 162, 241); 119 | let capsule_color_analyzing = Color::from_rgb8(230, 242, 254); 120 | 121 | let backing_color_warning = Color::from_rgb8(187, 218, 252); 122 | let fg_color_warning = Color::from_rgb8(235, 75, 67); 123 | let capsule_color_warning = Color::from_rgb8(250, 226, 227); 124 | 125 | let backing_color_safe = Color::from_rgb8(187, 218, 252); 126 | let fg_color_safe = Color::from_rgb8(96, 189, 93); 127 | let capsule_color_safe = Color::from_rgb8(220, 242, 220); 128 | 129 | let backing_color = self 130 | .indicator_state 131 | .animate::( 132 | |a| match a { 133 | IndicatorState::Safe => backing_color_safe.into(), 134 | IndicatorState::Warning => backing_color_warning.into(), 135 | IndicatorState::Analyzing => backing_color_analyzing.into(), 136 | }, 137 | time, 138 | ) 139 | .color; 140 | let fg_color = self 141 | .indicator_state 142 | .animate::( 143 | |a| match a { 144 | IndicatorState::Safe => fg_color_safe.into(), 145 | IndicatorState::Warning => fg_color_warning.into(), 146 | IndicatorState::Analyzing => fg_color_analyzing.into(), 147 | }, 148 | time, 149 | ) 150 | .color; 151 | let capsule_color = self 152 | .indicator_state 153 | .animate::( 154 | |a| match a { 155 | IndicatorState::Safe => capsule_color_safe.into(), 156 | IndicatorState::Warning => capsule_color_warning.into(), 157 | IndicatorState::Analyzing => capsule_color_analyzing.into(), 158 | }, 159 | time, 160 | ) 161 | .color; 162 | 163 | let height = 150.; 164 | let font = Font { 165 | weight: Weight::Bold, 166 | ..Default::default() 167 | }; 168 | let warn_icon = svg(svg::Handle::from_memory(include_bytes!( 169 | "../resources/warn.svg" 170 | ))); 171 | let check_icon = svg(svg::Handle::from_memory(include_bytes!( 172 | "../resources/check.svg" 173 | ))); 174 | 175 | let icon_small = 0.2; 176 | let icon_big = 0.4; 177 | 178 | Container::new(center( 179 | Row::new() 180 | .push( 181 | Stack::new() 182 | .push( 183 | container( 184 | Row::new() 185 | .height(Length::Fixed(height)) 186 | .push(vertical_space().width(Length::Fixed(height * 0.3))) 187 | .push( 188 | Stack::new() 189 | .width(Length::Fixed(height * 0.3)) 190 | .height(Length::Fixed(height * 0.3)) 191 | .push( 192 | iced::widget::canvas(Spinner { 193 | backing_color, 194 | spinning_color: fg_color, 195 | stroke: height * 0.06, 196 | rotation: self.spinner_rotation.animate( 197 | |v| if v { 0. } else { 1. }, 198 | time.checked_add(Duration::from_millis( 199 | self.spinner_rotation_speed 200 | .animate_bool(0., 150., time) 201 | as u64, 202 | )) 203 | .unwrap_or(time), 204 | ), 205 | opacity: self.indicator_state.animate_if_eq( 206 | IndicatorState::Analyzing, 207 | 1., 208 | 0., 209 | time, 210 | ), 211 | }) 212 | .width(Length::Fixed( 213 | height 214 | * self.indicator_state.animate_if_eq( 215 | IndicatorState::Analyzing, 216 | icon_big, 217 | icon_small, 218 | time, 219 | ), 220 | )) 221 | .height(Length::Fixed( 222 | height 223 | * self.indicator_state.animate_if_eq( 224 | IndicatorState::Analyzing, 225 | icon_big, 226 | icon_small, 227 | time, 228 | ), 229 | )), 230 | ) 231 | .push(center( 232 | warn_icon 233 | .width(Length::Fixed( 234 | height 235 | * self.indicator_state.animate_if_eq( 236 | IndicatorState::Warning, 237 | icon_big, 238 | icon_small, 239 | time, 240 | ), 241 | )) 242 | .height(Length::Fixed( 243 | height 244 | * self.indicator_state.animate_if_eq( 245 | IndicatorState::Warning, 246 | icon_big, 247 | icon_small, 248 | time, 249 | ), 250 | )) 251 | .style(move |_, _| iced::widget::svg::Style { 252 | color: Some(fg_color_warning), 253 | }) 254 | .opacity(self.indicator_state.animate_if_eq( 255 | IndicatorState::Warning, 256 | 1., 257 | 0., 258 | time, 259 | )), 260 | )) 261 | .push(center( 262 | check_icon 263 | .width(Length::Fixed( 264 | height 265 | * self.indicator_state.animate_if_eq( 266 | IndicatorState::Safe, 267 | icon_big, 268 | icon_small, 269 | time, 270 | ), 271 | )) 272 | .height(Length::Fixed( 273 | height 274 | * self.indicator_state.animate_if_eq( 275 | IndicatorState::Safe, 276 | icon_big, 277 | icon_small, 278 | time, 279 | ), 280 | )) 281 | .style(move |_, _| iced::widget::svg::Style { 282 | color: Some(fg_color_safe), 283 | }) 284 | .opacity(self.indicator_state.animate_if_eq( 285 | IndicatorState::Safe, 286 | 1., 287 | 0., 288 | time, 289 | )), 290 | )), 291 | ) 292 | .push( 293 | Stack::new() 294 | .width(Length::Fixed( 295 | self.indicator_state.animate::( 296 | |a| match a { 297 | IndicatorState::Safe => 150., 298 | IndicatorState::Warning => 225., 299 | IndicatorState::Analyzing => 700., 300 | }, 301 | time, 302 | ), 303 | )) 304 | .push( 305 | text("safe").font(font).size(height * 0.35).color( 306 | fg_color.scale_alpha( 307 | self.indicator_state.animate_if_eq( 308 | IndicatorState::Safe, 309 | 1., 310 | 0., 311 | time, 312 | ), 313 | ), 314 | ), 315 | ) 316 | .push( 317 | text("warning") 318 | .font(font) 319 | .size(height * 0.35) 320 | .color(fg_color.scale_alpha( 321 | self.indicator_state.animate_if_eq( 322 | IndicatorState::Warning, 323 | 1., 324 | 0., 325 | time, 326 | ), 327 | )), 328 | ) 329 | .push( 330 | text("analyzing transaction") 331 | .font(font) 332 | .size(height * 0.35) 333 | .color(fg_color.scale_alpha( 334 | self.indicator_state.animate_if_eq( 335 | IndicatorState::Analyzing, 336 | 1., 337 | 0., 338 | time, 339 | ), 340 | )), 341 | ), 342 | ) 343 | .push(vertical_space().width(Length::Fixed(height * 0.3))) 344 | .align_y(iced::Alignment::Center) 345 | .spacing(height * 0.2), 346 | ) 347 | .style(move |_| iced::widget::container::Style { 348 | border: Border { 349 | color: Color::BLACK, 350 | width: 0., 351 | radius: Radius::new(height * 0.5), 352 | }, 353 | background: Some(Background::Color(capsule_color)), 354 | ..Default::default() 355 | }) 356 | .height(Length::Fixed(height)) 357 | .width(Length::Shrink), 358 | ) 359 | .width(Length::Shrink) 360 | .height(Length::Shrink), 361 | ) 362 | .padding(30.), 363 | )) 364 | .style(move |_| iced::widget::container::Style { 365 | background: Some(Background::Color(Color::WHITE)), 366 | ..Default::default() 367 | }) 368 | .into() 369 | } 370 | } 371 | 372 | impl From for InterpolableColor { 373 | fn from(value: Color) -> Self { 374 | Self { color: value } 375 | } 376 | } 377 | 378 | struct InterpolableColor { 379 | color: Color, 380 | } 381 | 382 | impl Interpolable for InterpolableColor { 383 | fn interpolated(&self, other: Self, ratio: f32) -> Self { 384 | let a = self.color; 385 | let b = other.color; 386 | let mix = Color::from_rgb( 387 | a.r + ((b.r - a.r) * ratio), 388 | a.g + ((b.g - a.g) * ratio), 389 | a.b + ((b.b - a.b) * ratio), 390 | ); 391 | Self { color: mix } 392 | } 393 | } 394 | 395 | #[derive(Debug)] 396 | struct Spinner { 397 | backing_color: Color, 398 | spinning_color: Color, 399 | stroke: f32, 400 | rotation: f32, 401 | opacity: f32, 402 | } 403 | 404 | impl Program for Spinner { 405 | type State = (); 406 | 407 | fn draw( 408 | &self, 409 | _state: &(), 410 | renderer: &Renderer, 411 | _theme: &Theme, 412 | bounds: Rectangle, 413 | _cursor: mouse::Cursor, 414 | ) -> Vec { 415 | let mut frame = Frame::new(renderer, bounds.size()); 416 | let stroke = self.stroke; 417 | let radius = (f32::min(bounds.width, bounds.height) * 0.5) - (stroke * 0.5); 418 | let backing = Path::new(|p| { 419 | p.arc(Arc { 420 | center: Point::new(bounds.center_x() - bounds.x, bounds.center_y() - bounds.y), 421 | radius, 422 | start_angle: iced::Radians(0.), 423 | end_angle: iced::Radians(2. * PI), 424 | }); 425 | }); 426 | let circle = Path::new(|p| { 427 | p.arc(Arc { 428 | center: Point::new(bounds.center_x() - bounds.x, bounds.center_y() - bounds.y), 429 | radius, 430 | start_angle: iced::Radians(0.), 431 | end_angle: iced::Radians(0.5 * PI), 432 | }); 433 | }) 434 | .transform( 435 | &Transform2D::identity() 436 | .then_translate(vector(-bounds.width * 0.5, -bounds.height * 0.5)) 437 | .then_rotate(Angle::radians(self.rotation * 2. * PI)) 438 | .then_translate(vector(bounds.width * 0.5, bounds.height * 0.5)), 439 | ); 440 | // let _debug_frame = Path::rectangle(Point::ORIGIN, bounds.size()); 441 | // frame.fill(&_debug_frame, Color::from_rgb8(255, 0, 0)); 442 | frame.stroke( 443 | &backing, 444 | Stroke::default() 445 | .with_width(stroke) 446 | .with_line_cap(canvas::LineCap::Round) 447 | .with_color(self.backing_color.scale_alpha(self.opacity)), 448 | ); 449 | frame.stroke( 450 | &circle, 451 | Stroke::default() 452 | .with_width(stroke) 453 | .with_line_cap(canvas::LineCap::Round) 454 | .with_color(self.spinning_color.scale_alpha(self.opacity)), 455 | ); 456 | vec![frame.into_geometry()] 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /examples/iced-minimal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iced-minimal" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | iced = { version = "0.13.1", features = ["canvas", "tokio", "svg"] } 8 | lilt = { path = "../../" } 9 | iced_futures = "0.13.1" 10 | -------------------------------------------------------------------------------- /examples/iced-minimal/src/main.rs: -------------------------------------------------------------------------------- 1 | use iced::widget::{horizontal_space, vertical_space, Button, Column, Row, Text}; 2 | use iced::window::frames; 3 | use iced::Task; 4 | use iced::{Element, Length}; 5 | use lilt::Animated; 6 | use lilt::Easing; 7 | use std::time::Instant; 8 | 9 | pub fn main() -> iced::Result { 10 | iced::application("Iced Minimal", Example::update, Example::view) 11 | .subscription(Example::subscription) 12 | .run() 13 | } 14 | 15 | struct Example { 16 | animated_toggle: Animated, 17 | } 18 | 19 | #[derive(Debug, Clone, Copy)] 20 | enum AppMessage { 21 | Animate, 22 | Tick, 23 | } 24 | 25 | impl Default for Example { 26 | fn default() -> Self { 27 | Self::new() 28 | } 29 | } 30 | 31 | impl Example { 32 | fn new() -> Self { 33 | Self { 34 | animated_toggle: Animated::new(false).duration(300.).easing(Easing::EaseOut), 35 | } 36 | } 37 | 38 | fn subscription(&self) -> iced::Subscription { 39 | let now = std::time::Instant::now(); 40 | if self.animated_toggle.in_progress(now) { 41 | frames().map(|_| AppMessage::Tick) 42 | } else { 43 | iced::Subscription::none() 44 | } 45 | } 46 | 47 | fn update(&mut self, message: AppMessage) -> Task { 48 | let now = std::time::Instant::now(); 49 | match message { 50 | AppMessage::Animate => self 51 | .animated_toggle 52 | .transition(!self.animated_toggle.value, now), 53 | AppMessage::Tick => (), 54 | } 55 | Task::none() 56 | } 57 | 58 | fn view(&self) -> Element { 59 | let now = std::time::Instant::now(); 60 | Column::new() 61 | .align_x(iced::Alignment::Center) 62 | .push(vertical_space()) 63 | .push( 64 | Button::new( 65 | Row::new() 66 | .push(horizontal_space()) 67 | .push(Text::new("Animate!")) 68 | .push(horizontal_space()), 69 | ) 70 | .on_press(AppMessage::Animate) 71 | .width(self.animated_toggle.animate_bool(100., 300., now)), 72 | ) 73 | .push(vertical_space()) 74 | .width(Length::Fill) 75 | .height(Length::Fill) 76 | .into() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/macroquad-example/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 10 | 11 | [[package]] 12 | name = "ahash" 13 | version = "0.8.11" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 16 | dependencies = [ 17 | "cfg-if", 18 | "once_cell", 19 | "version_check", 20 | "zerocopy", 21 | ] 22 | 23 | [[package]] 24 | name = "autocfg" 25 | version = "1.4.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "1.3.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 34 | 35 | [[package]] 36 | name = "bumpalo" 37 | version = "3.17.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 40 | 41 | [[package]] 42 | name = "bytemuck" 43 | version = "1.20.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" 46 | 47 | [[package]] 48 | name = "byteorder" 49 | version = "1.5.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 52 | 53 | [[package]] 54 | name = "cfg-if" 55 | version = "1.0.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 58 | 59 | [[package]] 60 | name = "color_quant" 61 | version = "1.1.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 64 | 65 | [[package]] 66 | name = "crc32fast" 67 | version = "1.4.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 70 | dependencies = [ 71 | "cfg-if", 72 | ] 73 | 74 | [[package]] 75 | name = "fdeflate" 76 | version = "0.3.6" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" 79 | dependencies = [ 80 | "simd-adler32", 81 | ] 82 | 83 | [[package]] 84 | name = "flate2" 85 | version = "1.0.35" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" 88 | dependencies = [ 89 | "crc32fast", 90 | "miniz_oxide", 91 | ] 92 | 93 | [[package]] 94 | name = "fontdue" 95 | version = "0.7.3" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc" 98 | dependencies = [ 99 | "hashbrown", 100 | "ttf-parser", 101 | ] 102 | 103 | [[package]] 104 | name = "glam" 105 | version = "0.27.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" 108 | 109 | [[package]] 110 | name = "hashbrown" 111 | version = "0.13.2" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" 114 | dependencies = [ 115 | "ahash", 116 | ] 117 | 118 | [[package]] 119 | name = "image" 120 | version = "0.24.9" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" 123 | dependencies = [ 124 | "bytemuck", 125 | "byteorder", 126 | "color_quant", 127 | "num-traits", 128 | "png", 129 | ] 130 | 131 | [[package]] 132 | name = "js-sys" 133 | version = "0.3.77" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 136 | dependencies = [ 137 | "once_cell", 138 | "wasm-bindgen", 139 | ] 140 | 141 | [[package]] 142 | name = "libc" 143 | version = "0.2.166" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" 146 | 147 | [[package]] 148 | name = "lilt" 149 | version = "0.8.0" 150 | dependencies = [ 151 | "web-time", 152 | ] 153 | 154 | [[package]] 155 | name = "log" 156 | version = "0.4.26" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 159 | 160 | [[package]] 161 | name = "macroquad" 162 | version = "0.4.13" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "81fef16b2d4de22ac372b5d7d76273c0ead0a31f9de9bc649af8f816816db8a8" 165 | dependencies = [ 166 | "fontdue", 167 | "glam", 168 | "image", 169 | "macroquad_macro", 170 | "miniquad", 171 | "quad-rand", 172 | "slotmap", 173 | ] 174 | 175 | [[package]] 176 | name = "macroquad-example" 177 | version = "0.1.0" 178 | dependencies = [ 179 | "lilt", 180 | "macroquad", 181 | ] 182 | 183 | [[package]] 184 | name = "macroquad_macro" 185 | version = "0.1.8" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "64b1d96218903768c1ce078b657c0d5965465c95a60d2682fd97443c9d2483dd" 188 | 189 | [[package]] 190 | name = "malloc_buf" 191 | version = "0.0.6" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 194 | dependencies = [ 195 | "libc", 196 | ] 197 | 198 | [[package]] 199 | name = "miniquad" 200 | version = "0.4.6" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "2124e5e6bab86d96f263d78dc763c29e0da4c4f7ff0e1bb33f1d3905d1121262" 203 | dependencies = [ 204 | "libc", 205 | "ndk-sys", 206 | "objc", 207 | "winapi", 208 | ] 209 | 210 | [[package]] 211 | name = "miniz_oxide" 212 | version = "0.8.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 215 | dependencies = [ 216 | "adler2", 217 | "simd-adler32", 218 | ] 219 | 220 | [[package]] 221 | name = "ndk-sys" 222 | version = "0.2.2" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" 225 | 226 | [[package]] 227 | name = "num-traits" 228 | version = "0.2.19" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 231 | dependencies = [ 232 | "autocfg", 233 | ] 234 | 235 | [[package]] 236 | name = "objc" 237 | version = "0.2.7" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 240 | dependencies = [ 241 | "malloc_buf", 242 | ] 243 | 244 | [[package]] 245 | name = "once_cell" 246 | version = "1.20.2" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 249 | 250 | [[package]] 251 | name = "png" 252 | version = "0.17.14" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" 255 | dependencies = [ 256 | "bitflags", 257 | "crc32fast", 258 | "fdeflate", 259 | "flate2", 260 | "miniz_oxide", 261 | ] 262 | 263 | [[package]] 264 | name = "proc-macro2" 265 | version = "1.0.92" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 268 | dependencies = [ 269 | "unicode-ident", 270 | ] 271 | 272 | [[package]] 273 | name = "quad-rand" 274 | version = "0.2.3" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "5a651516ddc9168ebd67b24afd085a718be02f8858fe406591b013d101ce2f40" 277 | 278 | [[package]] 279 | name = "quote" 280 | version = "1.0.37" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 283 | dependencies = [ 284 | "proc-macro2", 285 | ] 286 | 287 | [[package]] 288 | name = "simd-adler32" 289 | version = "0.3.7" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 292 | 293 | [[package]] 294 | name = "slotmap" 295 | version = "1.0.7" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" 298 | dependencies = [ 299 | "version_check", 300 | ] 301 | 302 | [[package]] 303 | name = "syn" 304 | version = "2.0.89" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" 307 | dependencies = [ 308 | "proc-macro2", 309 | "quote", 310 | "unicode-ident", 311 | ] 312 | 313 | [[package]] 314 | name = "ttf-parser" 315 | version = "0.15.2" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" 318 | 319 | [[package]] 320 | name = "unicode-ident" 321 | version = "1.0.14" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 324 | 325 | [[package]] 326 | name = "version_check" 327 | version = "0.9.5" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 330 | 331 | [[package]] 332 | name = "wasm-bindgen" 333 | version = "0.2.100" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 336 | dependencies = [ 337 | "cfg-if", 338 | "once_cell", 339 | "wasm-bindgen-macro", 340 | ] 341 | 342 | [[package]] 343 | name = "wasm-bindgen-backend" 344 | version = "0.2.100" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 347 | dependencies = [ 348 | "bumpalo", 349 | "log", 350 | "proc-macro2", 351 | "quote", 352 | "syn", 353 | "wasm-bindgen-shared", 354 | ] 355 | 356 | [[package]] 357 | name = "wasm-bindgen-macro" 358 | version = "0.2.100" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 361 | dependencies = [ 362 | "quote", 363 | "wasm-bindgen-macro-support", 364 | ] 365 | 366 | [[package]] 367 | name = "wasm-bindgen-macro-support" 368 | version = "0.2.100" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 371 | dependencies = [ 372 | "proc-macro2", 373 | "quote", 374 | "syn", 375 | "wasm-bindgen-backend", 376 | "wasm-bindgen-shared", 377 | ] 378 | 379 | [[package]] 380 | name = "wasm-bindgen-shared" 381 | version = "0.2.100" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 384 | dependencies = [ 385 | "unicode-ident", 386 | ] 387 | 388 | [[package]] 389 | name = "web-time" 390 | version = "1.1.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 393 | dependencies = [ 394 | "js-sys", 395 | "wasm-bindgen", 396 | ] 397 | 398 | [[package]] 399 | name = "winapi" 400 | version = "0.3.9" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 403 | dependencies = [ 404 | "winapi-i686-pc-windows-gnu", 405 | "winapi-x86_64-pc-windows-gnu", 406 | ] 407 | 408 | [[package]] 409 | name = "winapi-i686-pc-windows-gnu" 410 | version = "0.4.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 413 | 414 | [[package]] 415 | name = "winapi-x86_64-pc-windows-gnu" 416 | version = "0.4.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 419 | 420 | [[package]] 421 | name = "zerocopy" 422 | version = "0.7.35" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 425 | dependencies = [ 426 | "zerocopy-derive", 427 | ] 428 | 429 | [[package]] 430 | name = "zerocopy-derive" 431 | version = "0.7.35" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 434 | dependencies = [ 435 | "proc-macro2", 436 | "quote", 437 | "syn", 438 | ] 439 | -------------------------------------------------------------------------------- /examples/macroquad-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macroquad-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | lilt = { path = "../.." } 8 | macroquad = { version = "0.4.13" } 9 | -------------------------------------------------------------------------------- /examples/macroquad-example/src/main.rs: -------------------------------------------------------------------------------- 1 | use lilt::*; 2 | use macroquad::prelude::*; 3 | use std::f32::consts::PI; 4 | 5 | #[macroquad::main("BasicShapes")] 6 | async fn main() { 7 | let time = get_time() as f32 * 1000.; 8 | let animation1: Animated = Animated::new(false) 9 | .duration(2000.) 10 | .easing(Easing::EaseInOutQuint) 11 | .repeat_forever() 12 | .auto_reverse() 13 | .auto_start(true, time); 14 | 15 | let animation2: Animated = Animated::new(false) 16 | .duration(2000.) 17 | .easing(Easing::Linear) 18 | .repeat_forever() 19 | .auto_start(true, time); 20 | 21 | loop { 22 | let time = get_time() as f32 * 1000.; 23 | clear_background(LIGHTGRAY); 24 | 25 | let rect_left = animation1.animate_bool(100., screen_width() - 200.0, time); 26 | let rotation = animation2.animate_bool(0., 2., time); 27 | 28 | let line_origin_x = 300.; 29 | let line_origin_y = 300.; 30 | let length = 400.; 31 | draw_line( 32 | line_origin_x, 33 | line_origin_y, 34 | (rotation * PI).cos() * (length - line_origin_x) 35 | - (rotation * PI).sin() * (length - line_origin_x) 36 | + line_origin_x, 37 | (rotation * PI).sin() * (length - line_origin_y) 38 | + (rotation * PI).cos() * (length - line_origin_y) 39 | + line_origin_y, 40 | 15.0, 41 | BLUE, 42 | ); 43 | draw_rectangle(rect_left, 100.0, 120.0, 60.0, GREEN); 44 | 45 | next_frame().await 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/animated.rs: -------------------------------------------------------------------------------- 1 | use crate::traits::{AnimationTime, FloatRepresentable, Interpolable}; 2 | /// Wraps state to enable interpolated transitions 3 | /// 4 | /// # Example 5 | /// 6 | /// ```rust 7 | /// use lilt::Animated; 8 | /// use std::time::Instant; 9 | /// 10 | /// struct MyViewState { 11 | /// animated_toggle: Animated, 12 | /// } 13 | /// // Initialize 14 | /// let mut state = MyViewState { 15 | /// animated_toggle: Animated::new(false), 16 | /// }; 17 | /// // Update 18 | /// let now = std::time::Instant::now(); 19 | /// state 20 | /// .animated_toggle 21 | /// .transition(!state.animated_toggle.value, now); 22 | /// // Animate 23 | /// let animated_width = state.animated_toggle.animate_bool(0., 100., now); 24 | /// let animated_width = state.animated_toggle.animate( 25 | /// |on| if on { 0. } else { 100. }, 26 | /// now, 27 | /// ); 28 | /// ``` 29 | /// 30 | /// An `Animated` struct represents a single animation axis. Multiple axes require multiple `Animated` structs. 31 | /// For example - to animate an x and a y position on the screen with different durations you'd need to 32 | /// wrap multiple float values independently. 33 | /// 34 | /// ```rust 35 | /// use std::time::Instant; 36 | /// use lilt::Animated; 37 | /// 38 | /// struct MyState { 39 | /// animated_x: Animated, 40 | /// animated_y: Animated, 41 | /// } 42 | /// ``` 43 | #[derive(Clone, Debug, Default)] 44 | pub struct Animated 45 | where 46 | T: FloatRepresentable + Clone + Copy + PartialEq, 47 | Time: AnimationTime, 48 | { 49 | animation: Animation