├── .github ├── dependabot.yml └── workflows │ ├── check.yml │ ├── release-plz.yml │ └── test.yml ├── .gitignore ├── .markdownlint.yaml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── cliff.toml ├── examples ├── widgets.tape └── widgets │ ├── app.rs │ ├── main.rs │ └── tabs │ ├── buttons.rs │ ├── stack.rs │ └── toggle_switch.rs ├── release-plz.toml └── src ├── button.rs ├── events.rs ├── events ├── crossterm.rs ├── termion.rs └── termwiz.rs ├── lib.rs ├── stack_container.rs └── toggle_switch.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | - package-ecosystem: "cargo" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | groups: 14 | all-dependencies: 15 | patterns: ["*"] 16 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | check: 11 | permissions: 12 | checks: write 13 | uses: joshka/github-workflows/.github/workflows/rust-check.yml@main 14 | with: 15 | msrv: 1.76.0 # this is optional defaults to 1.56.0 16 | -------------------------------------------------------------------------------- /.github/workflows/release-plz.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | release-plz: 15 | uses: joshka/github-workflows/.github/workflows/rust-release-plz.yml@main 16 | permissions: 17 | pull-requests: write 18 | contents: write 19 | secrets: 20 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | # Copy of https://github.com/joshka/github-workflows/blob/main/.github/workflows/rust-test.yml 10 | # because I need to disable building termion on Windows 11 | 12 | # This is the main CI workflow that runs the test suite on all pushes to main and all pull requests. 13 | # It runs the following jobs: 14 | # - required: runs the test suite on ubuntu with stable and beta rust toolchains 15 | # - minimal: runs the test suite with the minimal versions of the dependencies that satisfy the 16 | # requirements of this crate, and its dependencies 17 | # - os-check: runs the test suite on mac and windows 18 | # - coverage: runs the test suite and collects coverage information 19 | # See check.yml for information about how the concurrency cancellation and workflow triggering works 20 | 21 | # ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel 22 | # and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency 23 | concurrency: 24 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 25 | cancel-in-progress: true 26 | 27 | jobs: 28 | required: 29 | runs-on: ubuntu-latest 30 | name: ubuntu / ${{ matrix.toolchain }} 31 | strategy: 32 | matrix: 33 | # run on stable and beta to ensure that tests won't break on the next version of the rust 34 | # toolchain 35 | toolchain: [stable, beta] 36 | steps: 37 | - uses: actions/checkout@v4 38 | - name: Install ${{ matrix.toolchain }} 39 | uses: dtolnay/rust-toolchain@master 40 | with: 41 | toolchain: ${{ matrix.toolchain }} 42 | # enable this ci template to run regardless of whether the lockfile is checked in or not 43 | - name: cargo generate-lockfile 44 | if: hashFiles('Cargo.lock') == '' 45 | run: cargo generate-lockfile 46 | - name: cargo test --locked 47 | run: cargo test --locked --all-features --all-targets 48 | - name: cargo test --doc 49 | run: cargo test --locked --all-features --doc 50 | minimal-versions: 51 | # This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure 52 | # that this crate is compatible with the minimal version that this crate and its dependencies 53 | # require. This will pickup issues where this create relies on functionality that was introduced 54 | # later than the actual version specified (e.g., when we choose just a major version, but a 55 | # method was added after this version). 56 | # 57 | # This particular check can be difficult to get to succeed as often transitive dependencies may 58 | # be incorrectly specified (e.g., a dependency specifies 1.0 but really requires 1.1.5). There 59 | # is an alternative flag available -Zdirect-minimal-versions that uses the minimal versions for 60 | # direct dependencies of this crate, while selecting the maximal versions for the transitive 61 | # dependencies. Alternatively, you can add a line in your Cargo.toml to artificially increase 62 | # the minimal dependency, which you do with e.g.: 63 | # ```toml 64 | # # for minimal-versions 65 | # [target.'cfg(any())'.dependencies] 66 | # openssl = { version = "0.10.55", optional = true } # needed to allow foo to build with -Zminimal-versions 67 | # ``` 68 | # The optional = true is necessary in case that dependency isn't otherwise transitively required 69 | # by your library, and the target bit is so that this dependency edge never actually affects 70 | # Cargo build order. See also 71 | # https://github.com/jonhoo/fantoccini/blob/fde336472b712bc7ebf5b4e772023a7ba71b2262/Cargo.toml#L47-L49. 72 | # This action is run on ubuntu with the stable toolchain, as it is not expected to fail 73 | runs-on: ubuntu-latest 74 | name: ubuntu / stable / minimal-versions 75 | steps: 76 | - uses: actions/checkout@v4 77 | - name: Install Rust stable 78 | uses: dtolnay/rust-toolchain@stable 79 | - name: Install nightly for -Zdirect-minimal-versions 80 | uses: dtolnay/rust-toolchain@nightly 81 | - name: rustup default stable 82 | run: rustup default stable 83 | - name: cargo update -Zdirect-minimal-versions 84 | run: cargo +nightly update -Zdirect-minimal-versions 85 | - name: cargo test 86 | run: cargo test --locked --all-features --all-targets 87 | - name: Cache Cargo dependencies 88 | uses: Swatinem/rust-cache@v2 89 | os-check: 90 | # run cargo test on mac and windows 91 | runs-on: ${{ matrix.os }} 92 | name: ${{ matrix.os }} / stable 93 | strategy: 94 | fail-fast: false 95 | matrix: 96 | os: [macos-latest, windows-latest] 97 | steps: 98 | # if your project needs OpenSSL, uncomment this to fix Windows builds. 99 | # it's commented out by default as the install command takes 5-10m. 100 | # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append 101 | # if: runner.os == 'Windows' 102 | # - run: vcpkg install openssl:x64-windows-static-md 103 | # if: runner.os == 'Windows' 104 | - name: Checkout 105 | uses: actions/checkout@v4 106 | - name: Install Rust stable 107 | uses: dtolnay/rust-toolchain@stable 108 | - name: cargo generate-lockfile 109 | if: hashFiles('Cargo.lock') == '' 110 | run: cargo generate-lockfile 111 | - name: cargo test without termion on Windows 112 | if: runner.os == 'Windows' 113 | run: cargo test --locked --all-targets --features=termwiz,crossterm 114 | - name: cargo test all features 115 | if: runner.os != 'Windows' 116 | run: cargo test --locked --all-targets --all-features 117 | - name: Cache Cargo dependencies 118 | uses: Swatinem/rust-cache@v2 119 | coverage: 120 | # use llvm-cov to build and collect coverage and outputs in a format that 121 | # is compatible with codecov.io 122 | # 123 | # note that codecov as of v4 requires that CODECOV_TOKEN from 124 | # 125 | # https://app.codecov.io/gh///settings 126 | # 127 | # is set in two places on your repo: 128 | # 129 | # - https://github.com/jonhoo/guardian/settings/secrets/actions 130 | # - https://github.com/jonhoo/guardian/settings/secrets/dependabot 131 | # 132 | # (the former is needed for codecov uploads to work with Dependabot PRs) 133 | # 134 | # PRs coming from forks of your repo will not have access to the token, but 135 | # for those, codecov allows uploading coverage reports without a token. 136 | # it's all a little weird and inconvenient. see 137 | # 138 | # https://github.com/codecov/feedback/issues/112 139 | # 140 | # for lots of more discussion 141 | runs-on: ubuntu-latest 142 | name: ubuntu / stable / coverage 143 | steps: 144 | - name: Checkout 145 | uses: actions/checkout@v4 146 | - name: Install Rust stable 147 | uses: dtolnay/rust-toolchain@stable 148 | with: 149 | components: llvm-tools-preview 150 | - name: cargo install cargo-llvm-cov 151 | uses: taiki-e/install-action@cargo-llvm-cov 152 | - name: cargo generate-lockfile 153 | if: hashFiles('Cargo.lock') == '' 154 | run: cargo generate-lockfile 155 | - name: cargo llvm-cov 156 | run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info 157 | - name: Record Rust version 158 | run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" 159 | - name: Cache Cargo dependencies 160 | uses: Swatinem/rust-cache@v2 161 | - name: Upload to codecov.io 162 | uses: codecov/codecov-action@v4 163 | with: 164 | fail_ci_if_error: true 165 | token: ${{ secrets.CODECOV_TOKEN }} 166 | env_vars: OS,RUST 167 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | no-inline-html: 2 | allowed_elements: 3 | - br 4 | line-length: 5 | line_length: 100 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [0.2.2] - 2024-10-19 6 | 7 | ### Documentation 8 | 9 | - Update readme 10 | 11 | ### Miscellaneous Tasks 12 | 13 | - Mark crate as deprecated and move to tui-framework-experiment instead 14 | 15 | 16 | ## [0.2.1] - 2024-10-19 17 | 18 | ### Features 19 | 20 | - Added toggle switches ([#48](https://github.com/joshka/ratatui-widgets/pull/48)) 21 | 22 | 23 | ## [0.2.0] - 2024-10-02 24 | 25 | ### Miscellaneous Tasks 26 | 27 | - Remove default ratatui features from lib (#44) 28 | - Bump ratatui to 0.28.1 and related deps 29 | 30 | ## [0.1.11] - 2024-09-02 31 | 32 | ### Miscellaneous Tasks 33 | 34 | - Release (#40) 35 | 36 | ## [0.1.10] - 2024-08-06 37 | 38 | ### Miscellaneous Tasks 39 | 40 | - Release (#38) 41 | 42 | ## [0.1.9] - 2024-07-22 43 | 44 | ### Miscellaneous Tasks 45 | 46 | - Release (#36) 47 | 48 | ## [0.1.8] - 2024-07-15 49 | 50 | ### Miscellaneous Tasks 51 | 52 | - Release (#34) 53 | 54 | ## [0.1.7] - 2024-07-02 55 | 56 | ### Miscellaneous Tasks 57 | 58 | - Release (#29) 59 | 60 | ## [0.1.6] - 2024-05-27 61 | 62 | ### Bug Fixes 63 | 64 | - Disable testing termion for windows in CI (#24) 65 | 66 | ### Miscellaneous Tasks 67 | 68 | - Release (#25) 69 | 70 | ## [0.1.5] - 2024-04-24 71 | 72 | ### Miscellaneous Tasks 73 | 74 | - Group dependabot updates 75 | - Release (#20) 76 | 77 | ## [0.1.4] - 2024-04-01 78 | 79 | ### Miscellaneous Tasks 80 | 81 | - Release (#18) 82 | 83 | ## [0.1.3] - 2024-04-01 84 | 85 | ### Miscellaneous Tasks 86 | 87 | - Release (#17) 88 | 89 | ## [0.1.2] - 2024-03-12 90 | 91 | ### Miscellaneous Tasks 92 | 93 | - Use joshka/github-workflows (#12) 94 | - Release (#6) 95 | 96 | ## [0.1.1] - 2024-02-03 97 | 98 | ### Bug Fixes 99 | 100 | - Typo in crate name 101 | 102 | ### Documentation 103 | 104 | - Add button demo gif 105 | - Update readme and example gif 106 | - Update readme and stack container docs 107 | 108 | ### Features 109 | 110 | - Add basic button 111 | - Implement button press 112 | - Handle keyboard events generically 113 | - Add StackContainer widget 114 | 115 | ### Miscellaneous Tasks 116 | 117 | - Create dependabot.yml 118 | - Release 119 | 120 | ### Refactor 121 | 122 | - Examples to tabs, and button changes 123 | - Impl EventHandler on example app 124 | - Reimplement the mouse event handling 125 | - EventHandler trait now has handle_mouse and handle_keyboard methods 126 | - Buttons example is a little clearer 127 | 128 | ## [0.1.0] - 2024-01-19 129 | 130 | ### Miscellaneous Tasks 131 | 132 | - Initial stub implementation 133 | - Add changelog 134 | 135 | 136 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | First off, thank you for considering contributing to ratatui-widgets. 4 | 5 | If your contribution is not straightforward, please first discuss the change you wish to make by 6 | creating a new issue before making the change. 7 | 8 | ## Reporting issues 9 | 10 | Before reporting an issue on the [issue tracker](https://github.com/joshka/ratatui-widgets/issues), 11 | please check that it has not already been reported by searching for some related keywords. 12 | 13 | ## Pull requests 14 | 15 | Try to do one pull request per change. 16 | 17 | ## Commit Message Format 18 | 19 | This project adheres to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). 20 | A specification for adding human and machine readable meaning to commit messages. 21 | 22 | ### Commit Message Header 23 | 24 | ```plain 25 | (): 26 | │ │ │ 27 | │ │ └─⫸ Summary in present tense. Not capitalized. No period at the end. 28 | │ │ 29 | │ └─⫸ Commit Scope 30 | │ 31 | └─⫸ Commit Type: feat|fix|build|ci|docs|perf|refactor|test|chore 32 | ``` 33 | 34 | #### Type 35 | 36 | | feat | Features | A new feature 37 | |----------|--------------------------|---------------------------------------------------------| 38 | | fix | Bug Fixes | A bug fix | 39 | | docs | Documentation | Documentation only changes | 40 | | style | Styles | Changes that do not affect the meaning of the code\ 41 | (white-space, formatting, missing semi-colons, etc) | 42 | | refactor | Code Refactoring | A code change that neither fixes a bug nor adds a feature| 43 | | perf | Performance Improvements | A code change that improves performance | 44 | | test | Tests | Adding missing tests or correcting existing tests | 45 | | build | Builds | Changes that affect the build system or external dependencies (example scopes: main, serde) | 46 | | ci | Continuous Integrations | Changes to our CI configuration files and scripts (example scopes: Github Actions) | 47 | | chore | Chores | Other changes that don't modify src or test files | 48 | | revert | Reverts | Reverts a previous commit | 49 | 50 | ## Developing 51 | 52 | ### Set up 53 | 54 | This is no different than other Rust projects. 55 | 56 | ```shell 57 | git clone https://github.com/joshka/ratatui-widgets 58 | cd ratatui-widgets 59 | cargo test 60 | ``` 61 | 62 | ### Useful Commands 63 | 64 | - Run Clippy: 65 | 66 | ```shell 67 | cargo clippy --all-targets --all-features --workspace 68 | ``` 69 | 70 | - Run all tests: 71 | 72 | ```shell 73 | cargo test --all-features --workspace 74 | ``` 75 | 76 | - Check to see if there are code formatting issues 77 | 78 | ```shell 79 | cargo fmt --all -- --check 80 | ``` 81 | 82 | - Format the code in the project 83 | 84 | ```shell 85 | cargo fmt --all 86 | ``` 87 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.8.7" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" 25 | dependencies = [ 26 | "cfg-if", 27 | "once_cell", 28 | "version_check", 29 | "zerocopy", 30 | ] 31 | 32 | [[package]] 33 | name = "aho-corasick" 34 | version = "1.1.2" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 37 | dependencies = [ 38 | "memchr", 39 | ] 40 | 41 | [[package]] 42 | name = "allocator-api2" 43 | version = "0.2.16" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" 46 | 47 | [[package]] 48 | name = "anyhow" 49 | version = "1.0.79" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" 52 | 53 | [[package]] 54 | name = "atomic" 55 | version = "0.5.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" 58 | 59 | [[package]] 60 | name = "autocfg" 61 | version = "1.1.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 64 | 65 | [[package]] 66 | name = "backtrace" 67 | version = "0.3.69" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 70 | dependencies = [ 71 | "addr2line", 72 | "cc", 73 | "cfg-if", 74 | "libc", 75 | "miniz_oxide", 76 | "object", 77 | "rustc-demangle", 78 | ] 79 | 80 | [[package]] 81 | name = "base64" 82 | version = "0.21.7" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 85 | 86 | [[package]] 87 | name = "bit-set" 88 | version = "0.5.3" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 91 | dependencies = [ 92 | "bit-vec", 93 | ] 94 | 95 | [[package]] 96 | name = "bit-vec" 97 | version = "0.6.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 100 | 101 | [[package]] 102 | name = "bitflags" 103 | version = "1.3.2" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 106 | 107 | [[package]] 108 | name = "bitflags" 109 | version = "2.6.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 112 | 113 | [[package]] 114 | name = "block-buffer" 115 | version = "0.10.4" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 118 | dependencies = [ 119 | "generic-array", 120 | ] 121 | 122 | [[package]] 123 | name = "cassowary" 124 | version = "0.3.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 127 | 128 | [[package]] 129 | name = "castaway" 130 | version = "0.2.3" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" 133 | dependencies = [ 134 | "rustversion", 135 | ] 136 | 137 | [[package]] 138 | name = "cc" 139 | version = "1.0.83" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 142 | dependencies = [ 143 | "libc", 144 | ] 145 | 146 | [[package]] 147 | name = "cfg-if" 148 | version = "1.0.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 151 | 152 | [[package]] 153 | name = "color-eyre" 154 | version = "0.6.3" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 157 | dependencies = [ 158 | "backtrace", 159 | "color-spantrace", 160 | "eyre", 161 | "indenter", 162 | "once_cell", 163 | "owo-colors", 164 | "tracing-error", 165 | ] 166 | 167 | [[package]] 168 | name = "color-spantrace" 169 | version = "0.2.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" 172 | dependencies = [ 173 | "once_cell", 174 | "owo-colors", 175 | "tracing-core", 176 | "tracing-error", 177 | ] 178 | 179 | [[package]] 180 | name = "compact_str" 181 | version = "0.8.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" 184 | dependencies = [ 185 | "castaway", 186 | "cfg-if", 187 | "itoa", 188 | "rustversion", 189 | "ryu", 190 | "static_assertions", 191 | ] 192 | 193 | [[package]] 194 | name = "cpufeatures" 195 | version = "0.2.12" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 198 | dependencies = [ 199 | "libc", 200 | ] 201 | 202 | [[package]] 203 | name = "crossterm" 204 | version = "0.28.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 207 | dependencies = [ 208 | "bitflags 2.6.0", 209 | "crossterm_winapi", 210 | "mio", 211 | "parking_lot", 212 | "rustix", 213 | "signal-hook", 214 | "signal-hook-mio", 215 | "winapi", 216 | ] 217 | 218 | [[package]] 219 | name = "crossterm_winapi" 220 | version = "0.9.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 223 | dependencies = [ 224 | "winapi", 225 | ] 226 | 227 | [[package]] 228 | name = "crypto-common" 229 | version = "0.1.6" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 232 | dependencies = [ 233 | "generic-array", 234 | "typenum", 235 | ] 236 | 237 | [[package]] 238 | name = "csscolorparser" 239 | version = "0.6.2" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" 242 | dependencies = [ 243 | "lab", 244 | "phf", 245 | ] 246 | 247 | [[package]] 248 | name = "darling" 249 | version = "0.20.10" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 252 | dependencies = [ 253 | "darling_core", 254 | "darling_macro", 255 | ] 256 | 257 | [[package]] 258 | name = "darling_core" 259 | version = "0.20.10" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 262 | dependencies = [ 263 | "fnv", 264 | "ident_case", 265 | "proc-macro2", 266 | "quote", 267 | "strsim 0.11.1", 268 | "syn 2.0.79", 269 | ] 270 | 271 | [[package]] 272 | name = "darling_macro" 273 | version = "0.20.10" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 276 | dependencies = [ 277 | "darling_core", 278 | "quote", 279 | "syn 2.0.79", 280 | ] 281 | 282 | [[package]] 283 | name = "deltae" 284 | version = "0.3.2" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" 287 | 288 | [[package]] 289 | name = "derive_builder" 290 | version = "0.20.2" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" 293 | dependencies = [ 294 | "derive_builder_macro", 295 | ] 296 | 297 | [[package]] 298 | name = "derive_builder_core" 299 | version = "0.20.2" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 302 | dependencies = [ 303 | "darling", 304 | "proc-macro2", 305 | "quote", 306 | "syn 2.0.79", 307 | ] 308 | 309 | [[package]] 310 | name = "derive_builder_macro" 311 | version = "0.20.2" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 314 | dependencies = [ 315 | "derive_builder_core", 316 | "syn 2.0.79", 317 | ] 318 | 319 | [[package]] 320 | name = "digest" 321 | version = "0.10.7" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 324 | dependencies = [ 325 | "block-buffer", 326 | "crypto-common", 327 | ] 328 | 329 | [[package]] 330 | name = "dirs" 331 | version = "4.0.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 334 | dependencies = [ 335 | "dirs-sys", 336 | ] 337 | 338 | [[package]] 339 | name = "dirs-sys" 340 | version = "0.3.7" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 343 | dependencies = [ 344 | "libc", 345 | "redox_users", 346 | "winapi", 347 | ] 348 | 349 | [[package]] 350 | name = "either" 351 | version = "1.9.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 354 | 355 | [[package]] 356 | name = "equivalent" 357 | version = "1.0.1" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 360 | 361 | [[package]] 362 | name = "errno" 363 | version = "0.3.8" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 366 | dependencies = [ 367 | "libc", 368 | "windows-sys", 369 | ] 370 | 371 | [[package]] 372 | name = "euclid" 373 | version = "0.22.9" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" 376 | dependencies = [ 377 | "num-traits", 378 | ] 379 | 380 | [[package]] 381 | name = "eyre" 382 | version = "0.6.12" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 385 | dependencies = [ 386 | "indenter", 387 | "once_cell", 388 | ] 389 | 390 | [[package]] 391 | name = "fancy-regex" 392 | version = "0.11.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" 395 | dependencies = [ 396 | "bit-set", 397 | "regex", 398 | ] 399 | 400 | [[package]] 401 | name = "fastrand" 402 | version = "2.0.1" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 405 | 406 | [[package]] 407 | name = "filedescriptor" 408 | version = "0.8.2" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" 411 | dependencies = [ 412 | "libc", 413 | "thiserror", 414 | "winapi", 415 | ] 416 | 417 | [[package]] 418 | name = "finl_unicode" 419 | version = "1.2.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" 422 | 423 | [[package]] 424 | name = "fixedbitset" 425 | version = "0.4.2" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 428 | 429 | [[package]] 430 | name = "fnv" 431 | version = "1.0.7" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 434 | 435 | [[package]] 436 | name = "futures" 437 | version = "0.3.30" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 440 | dependencies = [ 441 | "futures-channel", 442 | "futures-core", 443 | "futures-executor", 444 | "futures-io", 445 | "futures-sink", 446 | "futures-task", 447 | "futures-util", 448 | ] 449 | 450 | [[package]] 451 | name = "futures-channel" 452 | version = "0.3.30" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 455 | dependencies = [ 456 | "futures-core", 457 | "futures-sink", 458 | ] 459 | 460 | [[package]] 461 | name = "futures-core" 462 | version = "0.3.30" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 465 | 466 | [[package]] 467 | name = "futures-executor" 468 | version = "0.3.30" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 471 | dependencies = [ 472 | "futures-core", 473 | "futures-task", 474 | "futures-util", 475 | ] 476 | 477 | [[package]] 478 | name = "futures-io" 479 | version = "0.3.30" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 482 | 483 | [[package]] 484 | name = "futures-macro" 485 | version = "0.3.30" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 488 | dependencies = [ 489 | "proc-macro2", 490 | "quote", 491 | "syn 2.0.79", 492 | ] 493 | 494 | [[package]] 495 | name = "futures-sink" 496 | version = "0.3.30" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 499 | 500 | [[package]] 501 | name = "futures-task" 502 | version = "0.3.30" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 505 | 506 | [[package]] 507 | name = "futures-timer" 508 | version = "3.0.3" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" 511 | 512 | [[package]] 513 | name = "futures-util" 514 | version = "0.3.30" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 517 | dependencies = [ 518 | "futures-channel", 519 | "futures-core", 520 | "futures-io", 521 | "futures-macro", 522 | "futures-sink", 523 | "futures-task", 524 | "memchr", 525 | "pin-project-lite", 526 | "pin-utils", 527 | "slab", 528 | ] 529 | 530 | [[package]] 531 | name = "generic-array" 532 | version = "0.14.7" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 535 | dependencies = [ 536 | "typenum", 537 | "version_check", 538 | ] 539 | 540 | [[package]] 541 | name = "getrandom" 542 | version = "0.2.12" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 545 | dependencies = [ 546 | "cfg-if", 547 | "libc", 548 | "wasi", 549 | ] 550 | 551 | [[package]] 552 | name = "gimli" 553 | version = "0.28.1" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 556 | 557 | [[package]] 558 | name = "glob" 559 | version = "0.3.1" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 562 | 563 | [[package]] 564 | name = "hashbrown" 565 | version = "0.14.3" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 568 | dependencies = [ 569 | "ahash", 570 | "allocator-api2", 571 | ] 572 | 573 | [[package]] 574 | name = "hashbrown" 575 | version = "0.15.0" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 578 | 579 | [[package]] 580 | name = "heck" 581 | version = "0.5.0" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 584 | 585 | [[package]] 586 | name = "hermit-abi" 587 | version = "0.3.9" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 590 | 591 | [[package]] 592 | name = "hex" 593 | version = "0.4.3" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 596 | 597 | [[package]] 598 | name = "ident_case" 599 | version = "1.0.1" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 602 | 603 | [[package]] 604 | name = "indenter" 605 | version = "0.3.3" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 608 | 609 | [[package]] 610 | name = "indexmap" 611 | version = "2.6.0" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 614 | dependencies = [ 615 | "equivalent", 616 | "hashbrown 0.15.0", 617 | ] 618 | 619 | [[package]] 620 | name = "instability" 621 | version = "0.3.2" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" 624 | dependencies = [ 625 | "quote", 626 | "syn 2.0.79", 627 | ] 628 | 629 | [[package]] 630 | name = "itertools" 631 | version = "0.12.1" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 634 | dependencies = [ 635 | "either", 636 | ] 637 | 638 | [[package]] 639 | name = "itertools" 640 | version = "0.13.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 643 | dependencies = [ 644 | "either", 645 | ] 646 | 647 | [[package]] 648 | name = "itoa" 649 | version = "1.0.10" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 652 | 653 | [[package]] 654 | name = "lab" 655 | version = "0.11.0" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" 658 | 659 | [[package]] 660 | name = "lazy_static" 661 | version = "1.4.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 664 | 665 | [[package]] 666 | name = "libc" 667 | version = "0.2.159" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 670 | 671 | [[package]] 672 | name = "libredox" 673 | version = "0.0.1" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" 676 | dependencies = [ 677 | "bitflags 2.6.0", 678 | "libc", 679 | "redox_syscall 0.4.1", 680 | ] 681 | 682 | [[package]] 683 | name = "libredox" 684 | version = "0.1.3" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 687 | dependencies = [ 688 | "bitflags 2.6.0", 689 | "libc", 690 | "redox_syscall 0.5.7", 691 | ] 692 | 693 | [[package]] 694 | name = "linux-raw-sys" 695 | version = "0.4.14" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 698 | 699 | [[package]] 700 | name = "lock_api" 701 | version = "0.4.11" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 704 | dependencies = [ 705 | "autocfg", 706 | "scopeguard", 707 | ] 708 | 709 | [[package]] 710 | name = "log" 711 | version = "0.4.20" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 714 | 715 | [[package]] 716 | name = "lru" 717 | version = "0.12.1" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "2994eeba8ed550fd9b47a0b38f0242bc3344e496483c6180b69139cc2fa5d1d7" 720 | dependencies = [ 721 | "hashbrown 0.14.3", 722 | ] 723 | 724 | [[package]] 725 | name = "mac_address" 726 | version = "1.1.5" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "4863ee94f19ed315bf3bc00299338d857d4b5bc856af375cc97d237382ad3856" 729 | dependencies = [ 730 | "nix 0.23.2", 731 | "winapi", 732 | ] 733 | 734 | [[package]] 735 | name = "memchr" 736 | version = "2.7.1" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 739 | 740 | [[package]] 741 | name = "memmem" 742 | version = "0.1.1" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" 745 | 746 | [[package]] 747 | name = "memoffset" 748 | version = "0.6.5" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 751 | dependencies = [ 752 | "autocfg", 753 | ] 754 | 755 | [[package]] 756 | name = "memoffset" 757 | version = "0.7.1" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 760 | dependencies = [ 761 | "autocfg", 762 | ] 763 | 764 | [[package]] 765 | name = "minimal-lexical" 766 | version = "0.2.1" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 769 | 770 | [[package]] 771 | name = "miniz_oxide" 772 | version = "0.7.1" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 775 | dependencies = [ 776 | "adler", 777 | ] 778 | 779 | [[package]] 780 | name = "mio" 781 | version = "1.0.2" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 784 | dependencies = [ 785 | "hermit-abi", 786 | "libc", 787 | "log", 788 | "wasi", 789 | "windows-sys", 790 | ] 791 | 792 | [[package]] 793 | name = "nix" 794 | version = "0.23.2" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" 797 | dependencies = [ 798 | "bitflags 1.3.2", 799 | "cc", 800 | "cfg-if", 801 | "libc", 802 | "memoffset 0.6.5", 803 | ] 804 | 805 | [[package]] 806 | name = "nix" 807 | version = "0.26.4" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" 810 | dependencies = [ 811 | "bitflags 1.3.2", 812 | "cfg-if", 813 | "libc", 814 | "memoffset 0.7.1", 815 | "pin-utils", 816 | ] 817 | 818 | [[package]] 819 | name = "nom" 820 | version = "7.1.3" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 823 | dependencies = [ 824 | "memchr", 825 | "minimal-lexical", 826 | ] 827 | 828 | [[package]] 829 | name = "num-derive" 830 | version = "0.3.3" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 833 | dependencies = [ 834 | "proc-macro2", 835 | "quote", 836 | "syn 1.0.109", 837 | ] 838 | 839 | [[package]] 840 | name = "num-traits" 841 | version = "0.2.17" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 844 | dependencies = [ 845 | "autocfg", 846 | ] 847 | 848 | [[package]] 849 | name = "numtoa" 850 | version = "0.2.4" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" 853 | 854 | [[package]] 855 | name = "object" 856 | version = "0.32.2" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 859 | dependencies = [ 860 | "memchr", 861 | ] 862 | 863 | [[package]] 864 | name = "once_cell" 865 | version = "1.19.0" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 868 | 869 | [[package]] 870 | name = "ordered-float" 871 | version = "4.2.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" 874 | dependencies = [ 875 | "num-traits", 876 | ] 877 | 878 | [[package]] 879 | name = "owo-colors" 880 | version = "3.5.0" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 883 | 884 | [[package]] 885 | name = "parking_lot" 886 | version = "0.12.1" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 889 | dependencies = [ 890 | "lock_api", 891 | "parking_lot_core", 892 | ] 893 | 894 | [[package]] 895 | name = "parking_lot_core" 896 | version = "0.9.9" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 899 | dependencies = [ 900 | "cfg-if", 901 | "libc", 902 | "redox_syscall 0.4.1", 903 | "smallvec", 904 | "windows-targets 0.48.5", 905 | ] 906 | 907 | [[package]] 908 | name = "paste" 909 | version = "1.0.14" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" 912 | 913 | [[package]] 914 | name = "pest" 915 | version = "2.7.6" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" 918 | dependencies = [ 919 | "memchr", 920 | "thiserror", 921 | "ucd-trie", 922 | ] 923 | 924 | [[package]] 925 | name = "pest_derive" 926 | version = "2.7.6" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" 929 | dependencies = [ 930 | "pest", 931 | "pest_generator", 932 | ] 933 | 934 | [[package]] 935 | name = "pest_generator" 936 | version = "2.7.6" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" 939 | dependencies = [ 940 | "pest", 941 | "pest_meta", 942 | "proc-macro2", 943 | "quote", 944 | "syn 2.0.79", 945 | ] 946 | 947 | [[package]] 948 | name = "pest_meta" 949 | version = "2.7.6" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" 952 | dependencies = [ 953 | "once_cell", 954 | "pest", 955 | "sha2", 956 | ] 957 | 958 | [[package]] 959 | name = "phf" 960 | version = "0.11.2" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 963 | dependencies = [ 964 | "phf_macros", 965 | "phf_shared", 966 | ] 967 | 968 | [[package]] 969 | name = "phf_codegen" 970 | version = "0.11.2" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" 973 | dependencies = [ 974 | "phf_generator", 975 | "phf_shared", 976 | ] 977 | 978 | [[package]] 979 | name = "phf_generator" 980 | version = "0.11.2" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" 983 | dependencies = [ 984 | "phf_shared", 985 | "rand", 986 | ] 987 | 988 | [[package]] 989 | name = "phf_macros" 990 | version = "0.11.2" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" 993 | dependencies = [ 994 | "phf_generator", 995 | "phf_shared", 996 | "proc-macro2", 997 | "quote", 998 | "syn 2.0.79", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "phf_shared" 1003 | version = "0.11.2" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 1006 | dependencies = [ 1007 | "siphasher", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "pin-project-lite" 1012 | version = "0.2.13" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 1015 | 1016 | [[package]] 1017 | name = "pin-utils" 1018 | version = "0.1.0" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1021 | 1022 | [[package]] 1023 | name = "ppv-lite86" 1024 | version = "0.2.17" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1027 | 1028 | [[package]] 1029 | name = "proc-macro-crate" 1030 | version = "3.2.0" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" 1033 | dependencies = [ 1034 | "toml_edit", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "proc-macro2" 1039 | version = "1.0.86" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 1042 | dependencies = [ 1043 | "unicode-ident", 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "quote" 1048 | version = "1.0.37" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1051 | dependencies = [ 1052 | "proc-macro2", 1053 | ] 1054 | 1055 | [[package]] 1056 | name = "rand" 1057 | version = "0.8.5" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1060 | dependencies = [ 1061 | "libc", 1062 | "rand_chacha", 1063 | "rand_core", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "rand_chacha" 1068 | version = "0.3.1" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1071 | dependencies = [ 1072 | "ppv-lite86", 1073 | "rand_core", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "rand_core" 1078 | version = "0.6.4" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1081 | dependencies = [ 1082 | "getrandom", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "ratatui" 1087 | version = "0.28.1" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" 1090 | dependencies = [ 1091 | "bitflags 2.6.0", 1092 | "cassowary", 1093 | "compact_str", 1094 | "crossterm", 1095 | "instability", 1096 | "itertools 0.13.0", 1097 | "lru", 1098 | "paste", 1099 | "strum", 1100 | "strum_macros", 1101 | "unicode-segmentation", 1102 | "unicode-truncate", 1103 | "unicode-width", 1104 | ] 1105 | 1106 | [[package]] 1107 | name = "ratatui-widgets" 1108 | version = "0.2.2" 1109 | dependencies = [ 1110 | "bitflags 2.6.0", 1111 | "color-eyre", 1112 | "crossterm", 1113 | "derive_builder", 1114 | "itertools 0.13.0", 1115 | "rand", 1116 | "ratatui", 1117 | "rstest", 1118 | "strum", 1119 | "termion", 1120 | "termwiz", 1121 | "thiserror", 1122 | ] 1123 | 1124 | [[package]] 1125 | name = "redox_syscall" 1126 | version = "0.4.1" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 1129 | dependencies = [ 1130 | "bitflags 1.3.2", 1131 | ] 1132 | 1133 | [[package]] 1134 | name = "redox_syscall" 1135 | version = "0.5.7" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 1138 | dependencies = [ 1139 | "bitflags 2.6.0", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "redox_termios" 1144 | version = "0.1.3" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" 1147 | 1148 | [[package]] 1149 | name = "redox_users" 1150 | version = "0.4.4" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" 1153 | dependencies = [ 1154 | "getrandom", 1155 | "libredox 0.0.1", 1156 | "thiserror", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "regex" 1161 | version = "1.11.0" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" 1164 | dependencies = [ 1165 | "aho-corasick", 1166 | "memchr", 1167 | "regex-automata", 1168 | "regex-syntax", 1169 | ] 1170 | 1171 | [[package]] 1172 | name = "regex-automata" 1173 | version = "0.4.8" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 1176 | dependencies = [ 1177 | "aho-corasick", 1178 | "memchr", 1179 | "regex-syntax", 1180 | ] 1181 | 1182 | [[package]] 1183 | name = "regex-syntax" 1184 | version = "0.8.5" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1187 | 1188 | [[package]] 1189 | name = "relative-path" 1190 | version = "1.9.3" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" 1193 | 1194 | [[package]] 1195 | name = "rstest" 1196 | version = "0.23.0" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" 1199 | dependencies = [ 1200 | "futures", 1201 | "futures-timer", 1202 | "rstest_macros", 1203 | "rustc_version", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "rstest_macros" 1208 | version = "0.23.0" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" 1211 | dependencies = [ 1212 | "cfg-if", 1213 | "glob", 1214 | "proc-macro-crate", 1215 | "proc-macro2", 1216 | "quote", 1217 | "regex", 1218 | "relative-path", 1219 | "rustc_version", 1220 | "syn 2.0.79", 1221 | "unicode-ident", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "rustc-demangle" 1226 | version = "0.1.23" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 1229 | 1230 | [[package]] 1231 | name = "rustc_version" 1232 | version = "0.4.1" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1235 | dependencies = [ 1236 | "semver 1.0.21", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "rustix" 1241 | version = "0.38.37" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 1244 | dependencies = [ 1245 | "bitflags 2.6.0", 1246 | "errno", 1247 | "libc", 1248 | "linux-raw-sys", 1249 | "windows-sys", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "rustversion" 1254 | version = "1.0.14" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 1257 | 1258 | [[package]] 1259 | name = "ryu" 1260 | version = "1.0.16" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 1263 | 1264 | [[package]] 1265 | name = "scopeguard" 1266 | version = "1.2.0" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1269 | 1270 | [[package]] 1271 | name = "semver" 1272 | version = "0.11.0" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" 1275 | dependencies = [ 1276 | "semver-parser", 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "semver" 1281 | version = "1.0.21" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" 1284 | 1285 | [[package]] 1286 | name = "semver-parser" 1287 | version = "0.10.2" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" 1290 | dependencies = [ 1291 | "pest", 1292 | ] 1293 | 1294 | [[package]] 1295 | name = "sha2" 1296 | version = "0.10.8" 1297 | source = "registry+https://github.com/rust-lang/crates.io-index" 1298 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1299 | dependencies = [ 1300 | "cfg-if", 1301 | "cpufeatures", 1302 | "digest", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "sharded-slab" 1307 | version = "0.1.7" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1310 | dependencies = [ 1311 | "lazy_static", 1312 | ] 1313 | 1314 | [[package]] 1315 | name = "signal-hook" 1316 | version = "0.3.17" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1319 | dependencies = [ 1320 | "libc", 1321 | "signal-hook-registry", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "signal-hook-mio" 1326 | version = "0.2.4" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1329 | dependencies = [ 1330 | "libc", 1331 | "mio", 1332 | "signal-hook", 1333 | ] 1334 | 1335 | [[package]] 1336 | name = "signal-hook-registry" 1337 | version = "1.4.1" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 1340 | dependencies = [ 1341 | "libc", 1342 | ] 1343 | 1344 | [[package]] 1345 | name = "siphasher" 1346 | version = "0.3.11" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 1349 | 1350 | [[package]] 1351 | name = "slab" 1352 | version = "0.4.9" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1355 | dependencies = [ 1356 | "autocfg", 1357 | ] 1358 | 1359 | [[package]] 1360 | name = "smallvec" 1361 | version = "1.13.1" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" 1364 | 1365 | [[package]] 1366 | name = "static_assertions" 1367 | version = "1.1.0" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1370 | 1371 | [[package]] 1372 | name = "strsim" 1373 | version = "0.10.0" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1376 | 1377 | [[package]] 1378 | name = "strsim" 1379 | version = "0.11.1" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1382 | 1383 | [[package]] 1384 | name = "strum" 1385 | version = "0.26.3" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 1388 | dependencies = [ 1389 | "strum_macros", 1390 | ] 1391 | 1392 | [[package]] 1393 | name = "strum_macros" 1394 | version = "0.26.4" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 1397 | dependencies = [ 1398 | "heck", 1399 | "proc-macro2", 1400 | "quote", 1401 | "rustversion", 1402 | "syn 2.0.79", 1403 | ] 1404 | 1405 | [[package]] 1406 | name = "syn" 1407 | version = "1.0.109" 1408 | source = "registry+https://github.com/rust-lang/crates.io-index" 1409 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1410 | dependencies = [ 1411 | "proc-macro2", 1412 | "quote", 1413 | "unicode-ident", 1414 | ] 1415 | 1416 | [[package]] 1417 | name = "syn" 1418 | version = "2.0.79" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 1421 | dependencies = [ 1422 | "proc-macro2", 1423 | "quote", 1424 | "unicode-ident", 1425 | ] 1426 | 1427 | [[package]] 1428 | name = "tempfile" 1429 | version = "3.9.0" 1430 | source = "registry+https://github.com/rust-lang/crates.io-index" 1431 | checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" 1432 | dependencies = [ 1433 | "cfg-if", 1434 | "fastrand", 1435 | "redox_syscall 0.4.1", 1436 | "rustix", 1437 | "windows-sys", 1438 | ] 1439 | 1440 | [[package]] 1441 | name = "terminfo" 1442 | version = "0.8.0" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "666cd3a6681775d22b200409aad3b089c5b99fb11ecdd8a204d9d62f8148498f" 1445 | dependencies = [ 1446 | "dirs", 1447 | "fnv", 1448 | "nom", 1449 | "phf", 1450 | "phf_codegen", 1451 | ] 1452 | 1453 | [[package]] 1454 | name = "termion" 1455 | version = "4.0.3" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "7eaa98560e51a2cf4f0bb884d8b2098a9ea11ecf3b7078e9c68242c74cc923a7" 1458 | dependencies = [ 1459 | "libc", 1460 | "libredox 0.1.3", 1461 | "numtoa", 1462 | "redox_termios", 1463 | ] 1464 | 1465 | [[package]] 1466 | name = "termios" 1467 | version = "0.3.3" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" 1470 | dependencies = [ 1471 | "libc", 1472 | ] 1473 | 1474 | [[package]] 1475 | name = "termwiz" 1476 | version = "0.22.0" 1477 | source = "registry+https://github.com/rust-lang/crates.io-index" 1478 | checksum = "5a75313e21da5d4406ea31402035b3b97aa74c04356bdfafa5d1043ab4e551d1" 1479 | dependencies = [ 1480 | "anyhow", 1481 | "base64", 1482 | "bitflags 2.6.0", 1483 | "fancy-regex", 1484 | "filedescriptor", 1485 | "finl_unicode", 1486 | "fixedbitset", 1487 | "hex", 1488 | "lazy_static", 1489 | "libc", 1490 | "log", 1491 | "memmem", 1492 | "nix 0.26.4", 1493 | "num-derive", 1494 | "num-traits", 1495 | "ordered-float", 1496 | "pest", 1497 | "pest_derive", 1498 | "phf", 1499 | "semver 0.11.0", 1500 | "sha2", 1501 | "signal-hook", 1502 | "siphasher", 1503 | "tempfile", 1504 | "terminfo", 1505 | "termios", 1506 | "thiserror", 1507 | "ucd-trie", 1508 | "unicode-segmentation", 1509 | "vtparse", 1510 | "wezterm-bidi", 1511 | "wezterm-blob-leases", 1512 | "wezterm-color-types", 1513 | "wezterm-dynamic", 1514 | "wezterm-input-types", 1515 | "winapi", 1516 | ] 1517 | 1518 | [[package]] 1519 | name = "thiserror" 1520 | version = "1.0.64" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 1523 | dependencies = [ 1524 | "thiserror-impl", 1525 | ] 1526 | 1527 | [[package]] 1528 | name = "thiserror-impl" 1529 | version = "1.0.64" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 1532 | dependencies = [ 1533 | "proc-macro2", 1534 | "quote", 1535 | "syn 2.0.79", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "thread_local" 1540 | version = "1.1.7" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 1543 | dependencies = [ 1544 | "cfg-if", 1545 | "once_cell", 1546 | ] 1547 | 1548 | [[package]] 1549 | name = "toml_datetime" 1550 | version = "0.6.8" 1551 | source = "registry+https://github.com/rust-lang/crates.io-index" 1552 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 1553 | 1554 | [[package]] 1555 | name = "toml_edit" 1556 | version = "0.22.22" 1557 | source = "registry+https://github.com/rust-lang/crates.io-index" 1558 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 1559 | dependencies = [ 1560 | "indexmap", 1561 | "toml_datetime", 1562 | "winnow", 1563 | ] 1564 | 1565 | [[package]] 1566 | name = "tracing" 1567 | version = "0.1.40" 1568 | source = "registry+https://github.com/rust-lang/crates.io-index" 1569 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1570 | dependencies = [ 1571 | "pin-project-lite", 1572 | "tracing-core", 1573 | ] 1574 | 1575 | [[package]] 1576 | name = "tracing-core" 1577 | version = "0.1.32" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1580 | dependencies = [ 1581 | "once_cell", 1582 | "valuable", 1583 | ] 1584 | 1585 | [[package]] 1586 | name = "tracing-error" 1587 | version = "0.2.0" 1588 | source = "registry+https://github.com/rust-lang/crates.io-index" 1589 | checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" 1590 | dependencies = [ 1591 | "tracing", 1592 | "tracing-subscriber", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "tracing-subscriber" 1597 | version = "0.3.18" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 1600 | dependencies = [ 1601 | "sharded-slab", 1602 | "thread_local", 1603 | "tracing-core", 1604 | ] 1605 | 1606 | [[package]] 1607 | name = "typenum" 1608 | version = "1.17.0" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1611 | 1612 | [[package]] 1613 | name = "ucd-trie" 1614 | version = "0.1.6" 1615 | source = "registry+https://github.com/rust-lang/crates.io-index" 1616 | checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" 1617 | 1618 | [[package]] 1619 | name = "unicode-ident" 1620 | version = "1.0.13" 1621 | source = "registry+https://github.com/rust-lang/crates.io-index" 1622 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1623 | 1624 | [[package]] 1625 | name = "unicode-segmentation" 1626 | version = "1.10.1" 1627 | source = "registry+https://github.com/rust-lang/crates.io-index" 1628 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 1629 | 1630 | [[package]] 1631 | name = "unicode-truncate" 1632 | version = "1.0.0" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" 1635 | dependencies = [ 1636 | "itertools 0.12.1", 1637 | "unicode-width", 1638 | ] 1639 | 1640 | [[package]] 1641 | name = "unicode-width" 1642 | version = "0.1.13" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" 1645 | 1646 | [[package]] 1647 | name = "utf8parse" 1648 | version = "0.2.1" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1651 | 1652 | [[package]] 1653 | name = "uuid" 1654 | version = "1.7.0" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" 1657 | dependencies = [ 1658 | "atomic", 1659 | "getrandom", 1660 | ] 1661 | 1662 | [[package]] 1663 | name = "valuable" 1664 | version = "0.1.0" 1665 | source = "registry+https://github.com/rust-lang/crates.io-index" 1666 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1667 | 1668 | [[package]] 1669 | name = "version_check" 1670 | version = "0.9.4" 1671 | source = "registry+https://github.com/rust-lang/crates.io-index" 1672 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1673 | 1674 | [[package]] 1675 | name = "vtparse" 1676 | version = "0.6.2" 1677 | source = "registry+https://github.com/rust-lang/crates.io-index" 1678 | checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" 1679 | dependencies = [ 1680 | "utf8parse", 1681 | ] 1682 | 1683 | [[package]] 1684 | name = "wasi" 1685 | version = "0.11.0+wasi-snapshot-preview1" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1688 | 1689 | [[package]] 1690 | name = "wezterm-bidi" 1691 | version = "0.2.3" 1692 | source = "registry+https://github.com/rust-lang/crates.io-index" 1693 | checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" 1694 | dependencies = [ 1695 | "log", 1696 | "wezterm-dynamic", 1697 | ] 1698 | 1699 | [[package]] 1700 | name = "wezterm-blob-leases" 1701 | version = "0.1.0" 1702 | source = "registry+https://github.com/rust-lang/crates.io-index" 1703 | checksum = "8e5a5e0adf7eed68976410def849a4bdab6f6e9f6163f152de9cb89deea9e60b" 1704 | dependencies = [ 1705 | "getrandom", 1706 | "mac_address", 1707 | "once_cell", 1708 | "sha2", 1709 | "thiserror", 1710 | "uuid", 1711 | ] 1712 | 1713 | [[package]] 1714 | name = "wezterm-color-types" 1715 | version = "0.3.0" 1716 | source = "registry+https://github.com/rust-lang/crates.io-index" 1717 | checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" 1718 | dependencies = [ 1719 | "csscolorparser", 1720 | "deltae", 1721 | "lazy_static", 1722 | "wezterm-dynamic", 1723 | ] 1724 | 1725 | [[package]] 1726 | name = "wezterm-dynamic" 1727 | version = "0.2.0" 1728 | source = "registry+https://github.com/rust-lang/crates.io-index" 1729 | checksum = "dfb128bacfa86734e07681fb6068e34c144698e84ee022d6e009145d1abb77b5" 1730 | dependencies = [ 1731 | "log", 1732 | "ordered-float", 1733 | "strsim 0.10.0", 1734 | "thiserror", 1735 | "wezterm-dynamic-derive", 1736 | ] 1737 | 1738 | [[package]] 1739 | name = "wezterm-dynamic-derive" 1740 | version = "0.1.0" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "0c9f5ef318442d07b3d071f9f43ea40b80992f87faee14bb4d017b6991c307f0" 1743 | dependencies = [ 1744 | "proc-macro2", 1745 | "quote", 1746 | "syn 1.0.109", 1747 | ] 1748 | 1749 | [[package]] 1750 | name = "wezterm-input-types" 1751 | version = "0.1.0" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" 1754 | dependencies = [ 1755 | "bitflags 1.3.2", 1756 | "euclid", 1757 | "lazy_static", 1758 | "wezterm-dynamic", 1759 | ] 1760 | 1761 | [[package]] 1762 | name = "winapi" 1763 | version = "0.3.9" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1766 | dependencies = [ 1767 | "winapi-i686-pc-windows-gnu", 1768 | "winapi-x86_64-pc-windows-gnu", 1769 | ] 1770 | 1771 | [[package]] 1772 | name = "winapi-i686-pc-windows-gnu" 1773 | version = "0.4.0" 1774 | source = "registry+https://github.com/rust-lang/crates.io-index" 1775 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1776 | 1777 | [[package]] 1778 | name = "winapi-x86_64-pc-windows-gnu" 1779 | version = "0.4.0" 1780 | source = "registry+https://github.com/rust-lang/crates.io-index" 1781 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1782 | 1783 | [[package]] 1784 | name = "windows-sys" 1785 | version = "0.52.0" 1786 | source = "registry+https://github.com/rust-lang/crates.io-index" 1787 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1788 | dependencies = [ 1789 | "windows-targets 0.52.0", 1790 | ] 1791 | 1792 | [[package]] 1793 | name = "windows-targets" 1794 | version = "0.48.5" 1795 | source = "registry+https://github.com/rust-lang/crates.io-index" 1796 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1797 | dependencies = [ 1798 | "windows_aarch64_gnullvm 0.48.5", 1799 | "windows_aarch64_msvc 0.48.5", 1800 | "windows_i686_gnu 0.48.5", 1801 | "windows_i686_msvc 0.48.5", 1802 | "windows_x86_64_gnu 0.48.5", 1803 | "windows_x86_64_gnullvm 0.48.5", 1804 | "windows_x86_64_msvc 0.48.5", 1805 | ] 1806 | 1807 | [[package]] 1808 | name = "windows-targets" 1809 | version = "0.52.0" 1810 | source = "registry+https://github.com/rust-lang/crates.io-index" 1811 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 1812 | dependencies = [ 1813 | "windows_aarch64_gnullvm 0.52.0", 1814 | "windows_aarch64_msvc 0.52.0", 1815 | "windows_i686_gnu 0.52.0", 1816 | "windows_i686_msvc 0.52.0", 1817 | "windows_x86_64_gnu 0.52.0", 1818 | "windows_x86_64_gnullvm 0.52.0", 1819 | "windows_x86_64_msvc 0.52.0", 1820 | ] 1821 | 1822 | [[package]] 1823 | name = "windows_aarch64_gnullvm" 1824 | version = "0.48.5" 1825 | source = "registry+https://github.com/rust-lang/crates.io-index" 1826 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1827 | 1828 | [[package]] 1829 | name = "windows_aarch64_gnullvm" 1830 | version = "0.52.0" 1831 | source = "registry+https://github.com/rust-lang/crates.io-index" 1832 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 1833 | 1834 | [[package]] 1835 | name = "windows_aarch64_msvc" 1836 | version = "0.48.5" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1839 | 1840 | [[package]] 1841 | name = "windows_aarch64_msvc" 1842 | version = "0.52.0" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 1845 | 1846 | [[package]] 1847 | name = "windows_i686_gnu" 1848 | version = "0.48.5" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1851 | 1852 | [[package]] 1853 | name = "windows_i686_gnu" 1854 | version = "0.52.0" 1855 | source = "registry+https://github.com/rust-lang/crates.io-index" 1856 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 1857 | 1858 | [[package]] 1859 | name = "windows_i686_msvc" 1860 | version = "0.48.5" 1861 | source = "registry+https://github.com/rust-lang/crates.io-index" 1862 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1863 | 1864 | [[package]] 1865 | name = "windows_i686_msvc" 1866 | version = "0.52.0" 1867 | source = "registry+https://github.com/rust-lang/crates.io-index" 1868 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 1869 | 1870 | [[package]] 1871 | name = "windows_x86_64_gnu" 1872 | version = "0.48.5" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1875 | 1876 | [[package]] 1877 | name = "windows_x86_64_gnu" 1878 | version = "0.52.0" 1879 | source = "registry+https://github.com/rust-lang/crates.io-index" 1880 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 1881 | 1882 | [[package]] 1883 | name = "windows_x86_64_gnullvm" 1884 | version = "0.48.5" 1885 | source = "registry+https://github.com/rust-lang/crates.io-index" 1886 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1887 | 1888 | [[package]] 1889 | name = "windows_x86_64_gnullvm" 1890 | version = "0.52.0" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 1893 | 1894 | [[package]] 1895 | name = "windows_x86_64_msvc" 1896 | version = "0.48.5" 1897 | source = "registry+https://github.com/rust-lang/crates.io-index" 1898 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1899 | 1900 | [[package]] 1901 | name = "windows_x86_64_msvc" 1902 | version = "0.52.0" 1903 | source = "registry+https://github.com/rust-lang/crates.io-index" 1904 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 1905 | 1906 | [[package]] 1907 | name = "winnow" 1908 | version = "0.6.20" 1909 | source = "registry+https://github.com/rust-lang/crates.io-index" 1910 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 1911 | dependencies = [ 1912 | "memchr", 1913 | ] 1914 | 1915 | [[package]] 1916 | name = "zerocopy" 1917 | version = "0.7.32" 1918 | source = "registry+https://github.com/rust-lang/crates.io-index" 1919 | checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" 1920 | dependencies = [ 1921 | "zerocopy-derive", 1922 | ] 1923 | 1924 | [[package]] 1925 | name = "zerocopy-derive" 1926 | version = "0.7.32" 1927 | source = "registry+https://github.com/rust-lang/crates.io-index" 1928 | checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" 1929 | dependencies = [ 1930 | "proc-macro2", 1931 | "quote", 1932 | "syn 2.0.79", 1933 | ] 1934 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ratatui-widgets" 3 | version = "0.2.2" 4 | authors = ["Joshka"] 5 | description = "A set of widgets for the ratatui terminal UI library" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/joshka/ratatui-widgets" 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | bitflags = "2.6.0" 14 | crossterm = { version = "0.28.1", optional = true } 15 | derive_builder = "0.20.2" 16 | itertools = "0.13.0" 17 | ratatui = { version = "0.28.1", features = [ 18 | "unstable-widget-ref", 19 | ], default-features = false } 20 | strum = { version = "0.26.3", features = ["derive"] } 21 | termion = { version = "4.0.3", optional = true } 22 | termwiz = { version = "0.22.0", optional = true } 23 | thiserror = "1.0.64" 24 | 25 | [dev-dependencies] 26 | color-eyre = "0.6.3" 27 | rand = "0.8.5" 28 | rstest = "0.23.0" 29 | strum = { version = "0.26.3", features = ["derive"] } 30 | ratatui = { version = "0.28.1", features = ["unstable-widget-ref"] } 31 | 32 | [features] 33 | default = ["crossterm"] 34 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Josh McKinney 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 | 2 | 3 | # Ratatui-widgets 4 | 5 | Please note: this crate is deprecated so that we can use the name for an internal Ratatui widget 6 | crate. Please use [tui-framework-experiment] instead. 7 | 8 | [tui-framework-experiment]: https://crates.io/crates/tui-framework-experiment 9 | 10 | [![Crates.io Badge]][Crate] [![License Badge]](#license) [![Docs.rs Badge]][API Docs]
11 | [![Deps.rs Badge]][Dependencies] [![Codecov.io Badge]][Coverage] [![Discord Badge]][Ratatui 12 | Discord] 13 | 14 | `ratatui-widgets` is a Rust crate with extra widgets for [Ratatui]. 15 | 16 | ## Installation 17 | 18 | ```shell 19 | cargo add ratatui-widgets 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```rust 25 | // TODO: Add usage examples 26 | ``` 27 | 28 | ## Example 29 | 30 | ![Button](https://vhs.charm.sh/vhs-MSE5G9byLklG23xdJwbqR.gif) 31 | 32 | [Crates.io Badge]: https://img.shields.io/crates/v/ratatui-widgets?logo=rust&style=for-the-badge 33 | [License Badge]: https://img.shields.io/crates/l/ratatui-widgets?style=for-the-badge 34 | [Docs.rs Badge]: https://img.shields.io/docsrs/ratatui-widgets?logo=rust&style=for-the-badge 35 | [Deps.rs Badge]: 36 | https://deps.rs/repo/github/joshka/ratatui-widgets/status.svg?style=for-the-badge 37 | [Codecov.io Badge]: 38 | https://img.shields.io/codecov/c/github/joshka/ratatui-widgets?logo=codecov&style=for-the-badge&token=BAQ8SOKEST 39 | [Discord Badge]: 40 | https://img.shields.io/discord/1070692720437383208?label=ratatui+discord&logo=discord&style=for-the-badge 41 | 42 | [Crate]: https://crates.io/crates/ratatui-widgets 43 | [API Docs]: https://docs.rs/crate/ratatui-widgets/ 44 | [Dependencies]: https://deps.rs/repo/github/joshka/ratatui-widgets 45 | [Coverage]: https://app.codecov.io/gh/joshka/ratatui-widgets 46 | [Ratatui Discord]: https://discord.gg/pMCEU9hNEj 47 | 48 | [Ratatui]: https://crates.io/crates/ratatui 49 | 50 | 51 | 52 | ## Status 53 | 54 | This README sets up an initial goal for a library of widgets to supplement Ratatui. This library 55 | **will release breaking changes regularly** - move fast and break things. A focus on fast delivery 56 | rather than gating on perfection is key here. Expect to see releases anytime a feature is changed or 57 | added. Release-plz will keep the version updated with respect to semver and will update the 58 | [CHANGELOG](./CHANGELOG.md) with information from every PR. 59 | 60 | This is not (yet?) an official Ratatui-org project, but may be at some point in the future. 61 | 62 | ## Features 63 | 64 | - Basic event handling abstraction and keyboard / mouse handling 65 | - Basic buttons 66 | 67 | ```rust 68 | let button = Button::new("Click me"); 69 | ``` 70 | 71 | - Stack container that handles widgets and layouts 72 | 73 | ```rust 74 | let stack = StackContainer::horizontal().with_widgets(vec![ 75 | (Box::new(Line::raw("Left")), Constraint::Fill(1)), 76 | (Box::new(Text::raw("Center")), Constraint::Fill(1)), 77 | (Box::new(Span::raw("Right")), Constraint::Fill(1)), 78 | ]); 79 | ``` 80 | 81 | ## TODO 82 | 83 | Most of this list of widgets from 84 | 85 | 86 | - [ ] Create an abstraction over the backend events to an event type for mouse and keyboard events 87 | - [x] Crossterm 88 | - [ ] Termion 89 | - [ ] Termwiz 90 | - [ ] Consider how to handle non mouse / keyboard events (resize, paste, focus, etc.) 91 | - [ ] Support keyboard shortcuts / accellerators for buttons / menus etc. 92 | - [ ] Consider supporting keyboard customization 93 | - [ ] Support themes that apply to all the widgets (colors and modifiers) 94 | - [ ] Decide on how to handle state (StatefulWidget vs other options) 95 | - [ ] Decide on how to handle events that occur on click / change etc. 96 | - [ ] Decide how to handle internal state (focus, hover) 97 | - [ ] Decide how to handle focus order / selection 98 | - [ ] Decide how containers work generally 99 | - Buttons 100 | - [ ] Button `[Submit]` 101 | - [ ] Radio Button `(*) item ( ) item` 102 | - [ ] Check Box `[x] item` 103 | - [X] Toggle Switch `<_ON_ / OFF >` 104 | - [ ] Toggle Button `[ON]` 105 | - [ ] Split Button `[Submit][↓]` 106 | - [ ] Cycle Button `[Red]` => `[Green]` => `[Blue]` 107 | - [ ] Slider `[------------|---------------]` 108 | - [ ] List Box (low priority as there is already one in core) 109 | - [ ] Spinner `[123][+][-]` 110 | - [ ] Drop-down List `[Selected Item][↓]` 111 | - [ ] Menu 112 | - [ ] Context Menu 113 | - [ ] Pie Menu (probably not worth it in the console, but perhaps there's uses for something similar) 114 | - [ ] Menu Bar ( might be worth bringing in) 115 | - [ ] Tool Bar (good for mouse UIs, bad for keyboard) 116 | - [ ] Ribbon (same) 117 | - [ ] Combo Box `[____________][↓]` 118 | - [ ] Icon (not sure what use we'd see out of this one) 119 | - [ ] Tree View () 120 | - [ ] Tree map (not listed on wiki page, but ) 121 | - [ ] Grid View / DataGrid (similar to the built-in table, but we can do much more with this) 122 | - [ ] Link `` (integrate with OSC 8) 123 | - [ ] Tab (compared to the built-in Tabs, this would be a container widget) 124 | - [ ] ScrollBar (built-in) 125 | - [ ] Text Box ( ) 126 | - [ ] Label (mostly the same as just a Paragraph, but links to another field) 127 | - [ ] Tooltip (displayed underneath when field is focused - dim text etc.) 128 | - [ ] Balloon help (similar to tooltip - perhaps this is a larger popup for help that has to be dismissed) 129 | - [ ] Status Bar (Mostly useful for putting a background, and adding multiple elements that are auto 130 | spaced) 131 | - [ ] Progress Bar (Gauge / Linegauge, but optimised for time / progress) 132 | - [ ] Infobar - like a popup or flash element, but generally non modal, dismissable, top of screen 133 | - Containers 134 | - [ ] Window (not sure we need this concept, but could be useful as a top-level idea) 135 | - [ ] Collapsible Panel - VSCode like panels 136 | - [ ] Drawers - related to panels - unsure where the distinction of this lies 137 | - [ ] Accordion - Vertically stacked list of items that can be expanded by selection 138 | - [ ] Modal Window - Popup that contains other elements 139 | - [ ] Dialog Box - Display info in a popup and wait for a response 140 | ( ) 141 | - [ ] Popup (not in wikipedia, ) 142 | - [ ] Palette Window / Utility Window - floating window with commands / buttons 143 | - [ ] Inspector Window - shows info about the selected item elsewhere (perhaps useful as a 144 | debugging tool) 145 | - [ ] Frame - Grouping mechanism - perhaps this is a border with a title and acts as a container 146 | - [ ] Canvas (built-in perhaps) 147 | - [ ] Cover flow - large selection usually with images / snapshots horizontal scroll 148 | - [ ] Bubble flow - discussion thread (example ) 149 | - [ ] Carousel - Display Visual Cards - different from cover flow in that it displays multiple 150 | cards at once 151 | - [ ] Scrollview - not in the wikipedia article, but 152 | - [ ] Suggestions for other items welcome! 153 | 154 | ## License 155 | 156 | Copyright (c) 2024 Josh McKinney 157 | 158 | This project is licensed under either of: 159 | 160 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 161 | ) 162 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 163 | 164 | at your option. 165 | 166 | ## Contribution 167 | 168 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the 169 | work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 170 | additional terms or conditions. 171 | 172 | See [CONTRIBUTING.md](CONTRIBUTING.md). 173 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # git-cliff ~ default configuration file 2 | # https://git-cliff.org/docs/configuration 3 | # 4 | # Lines starting with "#" are comments. 5 | # Configuration options are organized into tables and keys. 6 | # See documentation for more information on available options. 7 | 8 | [changelog] 9 | # changelog header 10 | header = """ 11 | # Changelog\n 12 | All notable changes to this project will be documented in this file.\n 13 | """ 14 | # template for the changelog body 15 | # https://keats.github.io/tera/docs/#introduction 16 | body = """ 17 | {% if version %}\ 18 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 19 | {% else %}\ 20 | ## [unreleased] 21 | {% endif %}\ 22 | {% for group, commits in commits | group_by(attribute="group") %} 23 | ### {{ group | upper_first }} 24 | {% for commit in commits %} 25 | - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ 26 | {% endfor %} 27 | {% endfor %}\n 28 | """ 29 | # remove the leading and trailing whitespace from the template 30 | trim = true 31 | # changelog footer 32 | footer = """ 33 | 34 | """ 35 | # postprocessors 36 | postprocessors = [ 37 | # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL 38 | ] 39 | [git] 40 | # parse the commits based on https://www.conventionalcommits.org 41 | conventional_commits = true 42 | # filter out the commits that are not conventional 43 | filter_unconventional = false 44 | # process each line of a commit as an individual commit 45 | split_commits = false 46 | # regex for preprocessing the commit messages 47 | commit_preprocessors = [ 48 | # { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, # replace issue numbers 49 | ] 50 | # regex for parsing and grouping commits 51 | commit_parsers = [ 52 | { message = "^feat", group = "Features" }, 53 | { message = "^fix", group = "Bug Fixes" }, 54 | { message = "^doc", group = "Documentation" }, 55 | { message = "^perf", group = "Performance" }, 56 | { message = "^refactor", group = "Refactor" }, 57 | { message = "^style", group = "Styling" }, 58 | { message = "^test", group = "Testing" }, 59 | { message = "^chore\\(release\\): prepare for", skip = true }, 60 | { message = "^chore\\(deps\\)", skip = true }, 61 | { message = "^chore\\(pr\\)", skip = true }, 62 | { message = "^chore\\(pull\\)", skip = true }, 63 | { message = "^chore|ci", group = "Miscellaneous Tasks" }, 64 | { body = ".*security", group = "Security" }, 65 | { message = "^revert", group = "Revert" }, 66 | { message = "^[^:]*$", group = "Miscellaneous Tasks" }, 67 | ] 68 | # protect breaking changes from being skipped due to matching a skipping commit_parser 69 | protect_breaking_commits = false 70 | # filter out the commits that are not matched by commit parsers 71 | filter_commits = false 72 | # regex for matching git tags 73 | tag_pattern = "v[0-9].*" 74 | 75 | # regex for skipping tags 76 | skip_tags = "v0.1.0-beta.1" 77 | # regex for ignoring tags 78 | ignore_tags = "" 79 | # sort the tags topologically 80 | topo_order = false 81 | # sort the commits inside sections by oldest/newest order 82 | sort_commits = "oldest" 83 | # limit the number of commits included in the changelog. 84 | # limit_commits = 42 85 | -------------------------------------------------------------------------------- /examples/widgets.tape: -------------------------------------------------------------------------------- 1 | # This is a vhs script. See https://github.com/charmbracelet/vhs for more info. 2 | # To run this script, install vhs and run `vhs ./examples/buttons.tape` 3 | Output "target/button.gif" 4 | Set Theme "Aardvark Blue" 5 | Set Width 1200 6 | Set Height 600 7 | Hide 8 | Type "cargo run --example=widgets" 9 | Enter 10 | Sleep 2s 11 | Show 12 | Sleep 1s 13 | Type @250ms " " 14 | Sleep 500ms 15 | Right @250ms 16 | Type @250ms " " 17 | Sleep 500ms 18 | Right @250ms 19 | Type @250ms" " 20 | Sleep 500ms 21 | Right @250ms 22 | Sleep 1s 23 | Tab @2s 3 24 | 25 | -------------------------------------------------------------------------------- /examples/widgets/app.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self}; 2 | 3 | use color_eyre::Result; 4 | use ratatui::{prelude::*, style::palette::tailwind, symbols::border::*, widgets::*}; 5 | use ratatui_widgets::events::*; 6 | use strum::{Display, EnumIter, IntoEnumIterator}; 7 | 8 | use crate::tabs::*; 9 | 10 | #[derive(Debug)] 11 | pub struct App { 12 | state: RunningState, 13 | selected_tab_index: usize, 14 | tabs: Vec, 15 | } 16 | 17 | #[derive(Debug, Default, PartialEq, Eq)] 18 | enum RunningState { 19 | #[default] 20 | Running, 21 | Quit, 22 | } 23 | 24 | #[derive(Debug, Display, EnumIter)] 25 | enum Tab { 26 | Buttons(ButtonsTab), 27 | Stack(StackTab), 28 | ToggleSwitch(ToggleSwitchTab), 29 | } 30 | 31 | impl Default for App { 32 | fn default() -> Self { 33 | Self::new() 34 | } 35 | } 36 | 37 | impl App { 38 | pub fn new() -> Self { 39 | Self { 40 | state: RunningState::Running, 41 | selected_tab_index: 0, 42 | tabs: Tab::iter().collect(), 43 | } 44 | } 45 | 46 | pub fn run(&mut self, mut terminal: Terminal) -> Result<()> { 47 | while self.is_running() { 48 | self.draw(&mut terminal)?; 49 | self.handle_events()?; 50 | } 51 | Ok(()) 52 | } 53 | 54 | fn is_running(&self) -> bool { 55 | self.state == RunningState::Running 56 | } 57 | 58 | fn draw(&mut self, terminal: &mut Terminal) -> io::Result<()> { 59 | terminal.draw(|frame| frame.render_widget(self, frame.area()))?; 60 | Ok(()) 61 | } 62 | 63 | fn handle_events(&mut self) -> Result<()> { 64 | match Event::try_from(crossterm::event::read()?) { 65 | Ok(event) => self.handle_event(event), 66 | Err(_) => { 67 | // ignore for now. Perhaps change the try_from approach to a method that returns 68 | // Option instead of Result 69 | } 70 | } 71 | Ok(()) 72 | } 73 | 74 | fn quit(&mut self) { 75 | self.state = RunningState::Quit; 76 | } 77 | } 78 | 79 | impl EventHandler for App { 80 | fn handle_key(&mut self, key_pressed_event: KeyPressedEvent) { 81 | use Key::*; 82 | match key_pressed_event.key { 83 | Tab => self.next_tab(), 84 | BackTab => self.prev_tab(), 85 | Char('q') | Esc => self.quit(), 86 | _ => { 87 | self.selected_tab_mut().handle_key(key_pressed_event); 88 | } 89 | } 90 | } 91 | 92 | fn handle_mouse(&mut self, event: MouseEvent) { 93 | self.selected_tab_mut().handle_mouse(event); 94 | } 95 | } 96 | 97 | impl App { 98 | fn selected_tab_mut(&mut self) -> &mut Tab { 99 | self.tabs.get_mut(self.selected_tab_index).unwrap() 100 | } 101 | 102 | pub fn next_tab(&mut self) { 103 | let tab_count = self.tabs.len(); 104 | self.selected_tab_index = (self.selected_tab_index + 1) % tab_count; 105 | } 106 | 107 | pub fn prev_tab(&mut self) { 108 | let tab_count = self.tabs.len(); 109 | self.selected_tab_index = (self.selected_tab_index + tab_count - 1) % tab_count; 110 | } 111 | } 112 | 113 | impl Widget for &mut App { 114 | fn render(self, area: Rect, buf: &mut Buffer) { 115 | use Constraint::*; 116 | let layout = Layout::vertical([Length(1), Fill(1), Length(1)]); 117 | let [header, body, footer] = layout.areas(area); 118 | let layout = Layout::horizontal([Fill(1), Length(15)]); 119 | let [tabs, title] = layout.areas(header); 120 | 121 | self.footer().render(footer, buf); 122 | self.title().render(title, buf); 123 | self.tabs().render(tabs, buf); 124 | self.selected_tab_mut().render(body, buf); 125 | } 126 | } 127 | 128 | impl App { 129 | fn footer(&self) -> impl Widget { 130 | Line::raw("Esc: quit, Tab: next tab, Shift+Tab: prev tab") 131 | .style(tailwind::SLATE.c300) 132 | .centered() 133 | } 134 | 135 | fn title(&self) -> impl Widget { 136 | Line::raw("ratatui-widgets") 137 | .style(tailwind::SLATE.c300) 138 | .centered() 139 | } 140 | 141 | fn tabs(&self) -> impl Widget { 142 | Tabs::new(self.tabs.iter().map(Tab::title)) 143 | .select(self.selected_tab_index) 144 | .divider(" ") 145 | .padding("", "") 146 | .highlight_style(Modifier::BOLD) 147 | } 148 | } 149 | 150 | impl Widget for &mut Tab { 151 | fn render(self, area: Rect, buf: &mut Buffer) { 152 | let block = Block::default() 153 | .borders(Borders::ALL) 154 | .border_set(PROPORTIONAL_TALL) 155 | .border_style(self.color()) 156 | .padding(Padding::horizontal(1)); 157 | let inner = block.inner(area); 158 | block.render(area, buf); 159 | 160 | match self { 161 | Tab::Buttons(buttons) => buttons.render(inner, buf), 162 | Tab::Stack(stack) => stack.render(inner, buf), 163 | Tab::ToggleSwitch(switches) => switches.render(inner, buf), 164 | } 165 | } 166 | } 167 | 168 | impl EventHandler for Tab { 169 | fn handle_key(&mut self, event: KeyPressedEvent) { 170 | match self { 171 | Tab::Buttons(buttons) => buttons.handle_key(event), 172 | Tab::Stack(stack) => stack.handle_key(event), 173 | Tab::ToggleSwitch(switches) => switches.handle_key(event), 174 | } 175 | } 176 | 177 | fn handle_mouse(&mut self, event: MouseEvent) { 178 | match self { 179 | Tab::Buttons(buttons) => buttons.handle_mouse(event), 180 | Tab::Stack(_) => {} 181 | Tab::ToggleSwitch(switches) => switches.handle_mouse(event), 182 | } 183 | } 184 | } 185 | 186 | impl Tab { 187 | fn title(&self) -> Span<'static> { 188 | // use blue, emerald, indigo, red, yellow, ... 189 | let bg = match self { 190 | Tab::Buttons(_) => tailwind::BLUE.c700, 191 | Tab::Stack(_) => tailwind::EMERALD.c700, 192 | Tab::ToggleSwitch(_) => tailwind::PURPLE.c700, 193 | }; 194 | format!(" {self} ").fg(tailwind::SLATE.c200).bg(bg) 195 | } 196 | 197 | fn color(&self) -> Color { 198 | match self { 199 | Tab::Buttons(_) => tailwind::BLUE.c700, 200 | Tab::Stack(_) => tailwind::EMERALD.c700, 201 | Tab::ToggleSwitch(_) => tailwind::PURPLE.c700, 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /examples/widgets/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::stdout; 2 | 3 | use color_eyre::Result; 4 | use crossterm::{ 5 | event::{DisableMouseCapture, EnableMouseCapture}, 6 | ExecutableCommand, 7 | }; 8 | use ratatui::DefaultTerminal; 9 | 10 | mod app; 11 | mod tabs { 12 | mod buttons; 13 | mod stack; 14 | mod toggle_switch; 15 | pub use buttons::ButtonsTab; 16 | pub use stack::StackTab; 17 | pub use toggle_switch::ToggleSwitchTab; 18 | } 19 | use app::App; 20 | 21 | fn main() -> Result<()> { 22 | color_eyre::install()?; 23 | let terminal = ratatui::init(); 24 | let result = run(terminal); 25 | ratatui::restore(); 26 | result 27 | } 28 | 29 | fn run(terminal: DefaultTerminal) -> Result<()> { 30 | stdout().execute(EnableMouseCapture)?; 31 | App::new().run(terminal)?; 32 | stdout().execute(DisableMouseCapture)?; 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /examples/widgets/tabs/buttons.rs: -------------------------------------------------------------------------------- 1 | use ratatui::{prelude::*, style::palette::tailwind}; 2 | use ratatui_widgets::{ 3 | button, 4 | events::{self, *}, 5 | Button, 6 | }; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct ButtonsTab { 10 | selected_index: usize, 11 | buttons: Vec>, 12 | button_areas: Vec, 13 | } 14 | 15 | impl Default for ButtonsTab { 16 | fn default() -> Self { 17 | Self { 18 | selected_index: 0, 19 | buttons: vec![ 20 | Button::new("Button 1").with_theme(button::themes::RED), 21 | Button::new("Button 2").with_theme(button::themes::GREEN), 22 | Button::new("Button 3").with_theme(button::themes::BLUE), 23 | ], 24 | button_areas: vec![], 25 | } 26 | } 27 | } 28 | 29 | impl EventHandler for ButtonsTab { 30 | fn handle_key(&mut self, event: KeyPressedEvent) { 31 | use events::Key::*; 32 | match event.key { 33 | Char('j') | Left => self.select_previous(), 34 | Char('k') | Right => self.select_next(), 35 | _ => self.selected_button_mut().handle_key(event), 36 | } 37 | } 38 | 39 | fn handle_mouse(&mut self, event: MouseEvent) { 40 | match event.kind { 41 | MouseEventKind::Down(MouseButton::Left) => self.click(event.column, event.row), 42 | MouseEventKind::Up(_) => self.release(), 43 | _ => {} 44 | } 45 | } 46 | } 47 | 48 | impl ButtonsTab { 49 | pub fn selected_button_mut(&mut self) -> &mut Button<'static> { 50 | &mut self.buttons[self.selected_index] 51 | } 52 | 53 | // TODO hit test should be a method on the widget / state 54 | fn click(&mut self, column: u16, row: u16) { 55 | for (i, area) in self.button_areas.iter().enumerate() { 56 | // TODO Rect should have a contains method 57 | let area_contains_click = area.left() <= column 58 | && column < area.right() 59 | && area.top() <= row 60 | && row < area.bottom(); 61 | if area_contains_click { 62 | // clear current selection 63 | self.release(); 64 | self.buttons[i].toggle_press(); 65 | self.selected_index = i; 66 | break; 67 | } 68 | } 69 | } 70 | 71 | fn release(&mut self) { 72 | self.selected_button_mut().select(); 73 | } 74 | 75 | pub fn select_next(&mut self) { 76 | self.select_index((self.selected_index + 1) % self.buttons.len()) 77 | } 78 | 79 | pub fn select_previous(&mut self) { 80 | self.select_index((self.selected_index + self.buttons.len() - 1) % self.buttons.len()); 81 | } 82 | 83 | pub fn select_index(&mut self, index: usize) { 84 | self.selected_button_mut().normal(); 85 | self.selected_index = index % self.buttons.len(); 86 | self.selected_button_mut().select(); 87 | } 88 | 89 | pub fn press(&mut self) { 90 | self.buttons[self.selected_index].toggle_press(); 91 | } 92 | } 93 | 94 | /// Required to be mutable because we need to store the button areas for hit testing 95 | impl Widget for &mut ButtonsTab { 96 | fn render(self, area: Rect, buf: &mut Buffer) { 97 | let layout = Layout::vertical([3, 0]); 98 | let [buttons, instructions] = layout.areas(area); 99 | let layout = Layout::horizontal([20, 1, 20, 1, 20, 0]); 100 | let [left, _, middle, _, right, _] = layout.areas(buttons); 101 | 102 | self.button_areas = vec![left, middle, right]; 103 | 104 | self.buttons[0].render(left, buf); 105 | self.buttons[1].render(middle, buf); 106 | self.buttons[2].render(right, buf); 107 | 108 | Line::raw("←/→: select, space/mouse: press") 109 | .style(tailwind::SLATE.c300) 110 | .render(instructions, buf); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /examples/widgets/tabs/stack.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | use ratatui::prelude::*; 3 | use ratatui::widgets::*; 4 | use ratatui_widgets::events::{EventHandler, Key, KeyPressedEvent, MouseEvent}; 5 | use ratatui_widgets::StackContainer; 6 | 7 | #[derive(Debug)] 8 | pub struct StackTab { 9 | counter: usize, 10 | stack: StackContainer, 11 | } 12 | 13 | impl Default for StackTab { 14 | fn default() -> Self { 15 | use Constraint::*; 16 | let stack = StackContainer::vertical().with_widgets(vec![ 17 | // a couple of widgets to start with 18 | ( 19 | Box::new( 20 | Span::raw("First Span") 21 | .bg(Color::LightBlue) 22 | .fg(Color::Black), 23 | ), 24 | Length(1), 25 | ), 26 | ( 27 | Box::new( 28 | Paragraph::new("Second\n(paragraph)") 29 | .bg(Color::LightYellow) 30 | .fg(Color::Black), 31 | ), 32 | Length(2), 33 | ), 34 | ]); 35 | StackTab { counter: 2, stack } 36 | } 37 | } 38 | 39 | impl Widget for &StackTab { 40 | fn render(self, area: Rect, buf: &mut Buffer) { 41 | use Constraint::*; 42 | let [instruction, items] = Layout::vertical([Length(1), Fill(1)]).areas(area); 43 | Line::from("Press space to add a new item, backspace to remove the last one.") 44 | .centered() 45 | .render(instruction, buf); 46 | self.stack.render(items, buf); 47 | } 48 | } 49 | 50 | impl EventHandler for StackTab { 51 | fn handle_key(&mut self, event: KeyPressedEvent) { 52 | match event.key { 53 | Key::Char(' ') => self.add_widget(), 54 | Key::Backspace => self.remove_widget(), 55 | _ => {} 56 | } 57 | } 58 | 59 | fn handle_mouse(&mut self, _event: MouseEvent) {} 60 | } 61 | 62 | impl StackTab { 63 | fn add_widget(&mut self) { 64 | self.counter += 1; 65 | let (widget, constraint) = self.random_widget(); 66 | self.stack.push(widget, constraint); 67 | } 68 | 69 | fn remove_widget(&mut self) { 70 | if self.counter == 0 { 71 | return; 72 | } 73 | self.counter -= 1; 74 | self.stack.remove(self.counter); 75 | } 76 | 77 | fn random_widget(&mut self) -> (Box, Constraint) { 78 | let text = format!("Item {}", self.counter); 79 | 80 | let mut rng = rand::thread_rng(); 81 | 82 | let color_index = rng.gen_range(0..=255); 83 | let style = Style::new() 84 | .bg(Color::Indexed(color_index)) 85 | .fg(Color::Black); 86 | 87 | let choice = rng.gen_range(0..3); 88 | 89 | let widget: Box = match choice { 90 | 0 => Box::new(Paragraph::new(format!("{}\n(paragraph)", text)).style(style)), 91 | 1 => Box::new(Line::styled(format!("{} (line)", text), style)), 92 | 2 => Box::new(Span::styled(format!("{} (span)", text), style)), 93 | _ => unreachable!(), 94 | }; 95 | let constraint = match choice { 96 | 0 => Constraint::Length(2), 97 | 1 => Constraint::Length(1), 98 | 2 => Constraint::Length(1), 99 | _ => unreachable!(), 100 | }; 101 | 102 | (widget, constraint) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /examples/widgets/tabs/toggle_switch.rs: -------------------------------------------------------------------------------- 1 | use ratatui::{prelude::*, style::palette::tailwind}; 2 | use ratatui_widgets::toggle_switch::ToggleSwitch; 3 | use ratatui_widgets::{ 4 | events::{self, *}, 5 | toggle_switch::State, 6 | }; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct ToggleSwitchTab { 10 | selected_index: usize, 11 | switches: Vec>, 12 | switch_areas: Vec, 13 | } 14 | 15 | impl Default for ToggleSwitchTab { 16 | fn default() -> Self { 17 | Self { 18 | selected_index: 0, 19 | switches: vec![ 20 | ToggleSwitch::new("Turned off", State::Off), 21 | ToggleSwitch::new("Turned on", State::On), 22 | ], 23 | switch_areas: vec![], 24 | } 25 | } 26 | } 27 | 28 | impl EventHandler for ToggleSwitchTab { 29 | fn handle_key(&mut self, event: KeyPressedEvent) { 30 | use events::Key::*; 31 | match event.key { 32 | Char('k') | Up => self.select_previous(), 33 | Char('j') | Down => self.select_next(), 34 | _ => self.selected_switch_mut().handle_key(event), 35 | } 36 | } 37 | 38 | fn handle_mouse(&mut self, event: MouseEvent) { 39 | if let MouseEventKind::Down(MouseButton::Left) = event.kind { 40 | self.click(event.column, event.row) 41 | } 42 | } 43 | } 44 | 45 | impl ToggleSwitchTab { 46 | pub fn selected_switch_mut(&mut self) -> &mut ToggleSwitch<'static> { 47 | &mut self.switches[self.selected_index] 48 | } 49 | 50 | // TODO hit test should be a method on the widget / state 51 | fn click(&mut self, column: u16, row: u16) { 52 | for (i, area) in self.switch_areas.iter().enumerate() { 53 | if area.contains(Position::new(column, row)) { 54 | // clear current selection 55 | self.release(); 56 | self.switches[i].toggle_state(); 57 | self.selected_index = i; 58 | break; 59 | } 60 | } 61 | } 62 | 63 | pub fn release(&mut self) { 64 | self.selected_switch_mut().blur(); 65 | } 66 | 67 | pub fn select_next(&mut self) { 68 | self.select_index((self.selected_index + 1) % self.switches.len()) 69 | } 70 | 71 | pub fn select_previous(&mut self) { 72 | self.select_index((self.selected_index + self.switches.len() - 1) % self.switches.len()); 73 | } 74 | 75 | pub fn select_index(&mut self, index: usize) { 76 | self.selected_switch_mut().blur(); 77 | self.selected_index = index % self.switches.len(); 78 | self.selected_switch_mut().focus(); 79 | } 80 | 81 | pub fn press(&mut self) { 82 | self.switches[self.selected_index].toggle_state(); 83 | } 84 | } 85 | 86 | /// Required to be mutable because we need to store the button areas for hit testing 87 | impl Widget for &mut ToggleSwitchTab { 88 | fn render(self, area: Rect, buf: &mut Buffer) { 89 | let layout = Layout::vertical([7, 0]); 90 | let [buttons, instructions] = layout.areas(area); 91 | let layout = Layout::vertical([3, 1, 3, 0]); 92 | let [top, _, bottom, _] = layout.areas(buttons); 93 | 94 | self.switch_areas = vec![top, bottom]; 95 | 96 | self.switches[0].render(top, buf); 97 | self.switches[1].render(bottom, buf); 98 | 99 | Line::raw("←/→: select, space/mouse: press") 100 | .style(tailwind::SLATE.c300) 101 | .render(instructions, buf); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /release-plz.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | changelog_config = "cliff.toml" 3 | -------------------------------------------------------------------------------- /src/button.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use ratatui::{prelude::*, widgets::Widget}; 4 | 5 | use crate::events::*; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct Button<'text> { 9 | text: Text<'text>, 10 | theme: Theme, 11 | state: State, 12 | } 13 | 14 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] 15 | pub enum State { 16 | #[default] 17 | Normal, 18 | Selected, 19 | Pressed, 20 | } 21 | 22 | #[derive(Debug, Clone, Copy)] 23 | pub struct Theme { 24 | normal_text: Color, 25 | normal_background: Color, 26 | selected_text: Color, 27 | selected_background: Color, 28 | pressed_text: Color, 29 | pressed_background: Color, 30 | highlight: Color, 31 | shadow: Color, 32 | } 33 | 34 | impl Default for Theme { 35 | fn default() -> Self { 36 | themes::NORMAL 37 | } 38 | } 39 | 40 | /// Config 41 | impl<'text> Button<'text> { 42 | pub fn new>>(text: T) -> Self { 43 | Self { 44 | text: text.into(), 45 | theme: Theme::default(), 46 | state: State::default(), 47 | } 48 | } 49 | 50 | pub fn with_theme(mut self, theme: Theme) -> Self { 51 | self.theme = theme; 52 | self 53 | } 54 | } 55 | 56 | impl EventHandler for Button<'_> { 57 | fn handle_key(&mut self, key_event: KeyPressedEvent) { 58 | match key_event.key { 59 | Key::Char(' ') | Key::Enter => self.toggle_press(), 60 | _ => {} 61 | } 62 | } 63 | } 64 | 65 | impl Button<'_> { 66 | pub fn toggle_press(&mut self) { 67 | match self.state { 68 | State::Normal => self.press(), 69 | State::Selected => self.press(), 70 | State::Pressed => self.select(), 71 | } 72 | } 73 | 74 | pub fn press(&mut self) { 75 | self.state = State::Pressed; 76 | } 77 | 78 | pub fn normal(&mut self) { 79 | self.state = State::Normal; 80 | } 81 | 82 | pub fn select(&mut self) { 83 | self.state = State::Selected; 84 | } 85 | } 86 | 87 | impl Widget for &Button<'_> { 88 | fn render(self, area: Rect, buf: &mut Buffer) { 89 | let theme = self.theme; 90 | 91 | // these are wrong 92 | let fg = match self.state { 93 | State::Normal => theme.normal_text, 94 | State::Selected => theme.selected_text, 95 | State::Pressed => theme.pressed_text, 96 | }; 97 | let bg = match self.state { 98 | State::Normal => theme.normal_background, 99 | State::Selected => theme.selected_background, 100 | State::Pressed => theme.pressed_background, 101 | }; 102 | let (top, bottom) = if self.state == State::Pressed { 103 | (theme.shadow, theme.highlight) 104 | } else { 105 | (theme.highlight, theme.shadow) 106 | }; 107 | 108 | buf.set_style(area, (fg, bg)); 109 | 110 | let rows = area.rows().collect::>(); 111 | let last_index = rows.len().saturating_sub(1); 112 | let (first, middle, last) = match rows.len() { 113 | 0 | 1 => (None, &rows[..], None), 114 | 2 => (None, &rows[..last_index], Some(rows[last_index])), 115 | _ => (Some(rows[0]), &rows[1..last_index], Some(rows[last_index])), 116 | }; 117 | 118 | // render top line if there's enough space 119 | if let Some(first) = first { 120 | "▔" 121 | .repeat(area.width as usize) 122 | .fg(top) 123 | .bg(bg) 124 | .render(first, buf); 125 | } 126 | // render bottom line if there's enough space 127 | if let Some(last) = last { 128 | "▁" 129 | .repeat(area.width as usize) 130 | .fg(bottom) 131 | .bg(bg) 132 | .render(last, buf); 133 | } 134 | self.text.clone().centered().render(middle[0], buf); 135 | } 136 | } 137 | 138 | pub mod themes { 139 | use super::Theme; 140 | use ratatui::style::palette::tailwind; 141 | 142 | pub const NORMAL: Theme = Theme { 143 | normal_text: tailwind::GRAY.c200, 144 | normal_background: tailwind::GRAY.c800, 145 | selected_text: tailwind::GRAY.c100, 146 | selected_background: tailwind::GRAY.c700, 147 | pressed_text: tailwind::GRAY.c300, 148 | pressed_background: tailwind::GRAY.c900, 149 | highlight: tailwind::GRAY.c600, 150 | shadow: tailwind::GRAY.c950, 151 | }; 152 | 153 | pub const RED: Theme = Theme { 154 | normal_text: tailwind::RED.c200, 155 | normal_background: tailwind::RED.c800, 156 | selected_text: tailwind::RED.c100, 157 | selected_background: tailwind::RED.c700, 158 | pressed_text: tailwind::RED.c300, 159 | pressed_background: tailwind::RED.c900, 160 | highlight: tailwind::RED.c600, 161 | shadow: tailwind::RED.c950, 162 | }; 163 | 164 | pub const GREEN: Theme = Theme { 165 | normal_text: tailwind::GREEN.c200, 166 | normal_background: tailwind::GREEN.c800, 167 | selected_text: tailwind::GREEN.c100, 168 | selected_background: tailwind::GREEN.c700, 169 | pressed_text: tailwind::GREEN.c300, 170 | pressed_background: tailwind::GREEN.c900, 171 | highlight: tailwind::GREEN.c600, 172 | shadow: tailwind::GREEN.c950, 173 | }; 174 | 175 | pub const BLUE: Theme = Theme { 176 | normal_text: tailwind::BLUE.c200, 177 | normal_background: tailwind::BLUE.c800, 178 | selected_text: tailwind::BLUE.c100, 179 | selected_background: tailwind::BLUE.c700, 180 | pressed_text: tailwind::BLUE.c300, 181 | pressed_background: tailwind::BLUE.c900, 182 | highlight: tailwind::BLUE.c600, 183 | shadow: tailwind::BLUE.c950, 184 | }; 185 | } 186 | -------------------------------------------------------------------------------- /src/events.rs: -------------------------------------------------------------------------------- 1 | //! Events module. 2 | //! 3 | //! This module provides a cross-backend interface for handling the events that are generated by 4 | //! the terminal. 5 | //! 6 | //! This is not yet stable and will likely change in the future - each backend has some quirks that 7 | //! are difficult to handle in a generic way. 8 | 9 | use bitflags::bitflags; 10 | use strum::EnumIs; 11 | 12 | #[cfg(feature = "crossterm")] 13 | mod crossterm; 14 | 15 | #[cfg(feature = "termion")] 16 | mod termion; 17 | 18 | #[cfg(feature = "termwiz")] 19 | mod termwiz; 20 | 21 | pub trait EventHandler { 22 | fn handle_event(&mut self, event: Event) { 23 | match event { 24 | Event::KeyPressed(key_pressed_event) => self.handle_key(key_pressed_event), 25 | Event::Mouse(mouse_event) => self.handle_mouse(mouse_event), 26 | } 27 | } 28 | 29 | #[allow(unused_variables)] 30 | fn handle_key(&mut self, event: KeyPressedEvent) {} 31 | 32 | #[allow(unused_variables)] 33 | fn handle_mouse(&mut self, event: MouseEvent) {} 34 | } 35 | 36 | pub enum Event { 37 | KeyPressed(KeyPressedEvent), 38 | Mouse(MouseEvent), 39 | } 40 | 41 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 42 | pub struct KeyPressedEvent { 43 | pub key: Key, 44 | pub modifiers: KeyModifiers, 45 | } 46 | 47 | #[derive(Debug, Clone, PartialEq, Eq, Hash, EnumIs)] 48 | pub enum Key { 49 | Char(char), 50 | Backspace, 51 | Delete, 52 | Insert, 53 | Enter, 54 | Left, 55 | Right, 56 | Up, 57 | Esc, 58 | Down, 59 | Home, 60 | End, 61 | PageUp, 62 | PageDown, 63 | Tab, 64 | BackTab, 65 | F(u8), 66 | Null, 67 | } 68 | 69 | bitflags! { 70 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] 71 | pub struct KeyModifiers: u8 { 72 | /// Shift key 73 | const SHIFT = 1; 74 | /// Control key 75 | const CTRL = 1 << 1; 76 | /// Alias for `CTRL` 77 | const CONTROL = KeyModifiers::CTRL.bits(); 78 | /// Alt key (Option on macOS) 79 | const ALT = 1 << 2; 80 | /// Alias for `ALT` 81 | const OPTION = KeyModifiers::ALT.bits(); 82 | /// Super key (Windows key on Windows, Command key on macOS) 83 | const SUPER = 1 << 3; 84 | /// Alias for `SUPER` 85 | const WIN = KeyModifiers::SUPER.bits(); 86 | /// Alias for `SUPER` 87 | const COMMAND = KeyModifiers::SUPER.bits(); 88 | /// Hyper key 89 | const HYPER = 1 << 4; 90 | /// Meta key 91 | const META = 1 << 5; 92 | } 93 | } 94 | 95 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 96 | pub struct MouseEvent { 97 | pub column: u16, 98 | pub row: u16, 99 | pub kind: MouseEventKind, 100 | pub modifiers: KeyModifiers, 101 | } 102 | 103 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 104 | pub enum MouseEventKind { 105 | Down(MouseButton), 106 | Up(MouseButton), 107 | Drag(MouseButton), 108 | Moved, 109 | ScrollUp, 110 | ScrollDown, 111 | ScrollLeft, 112 | ScrollRight, 113 | } 114 | 115 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 116 | pub enum MouseButton { 117 | Left, 118 | Right, 119 | Middle, 120 | } 121 | -------------------------------------------------------------------------------- /src/events/crossterm.rs: -------------------------------------------------------------------------------- 1 | use crossterm::event::{ 2 | Event as CrosstermEvent, KeyCode as CrosstermKeyCode, KeyEvent as CrosstermKeyEvent, 3 | KeyEventKind, KeyModifiers as CrosstermKeyModifiers, MouseEvent as CrosstermMouseEvent, 4 | }; 5 | use thiserror::Error; 6 | 7 | use super::MouseEventKind; 8 | use super::{Event, Key, KeyModifiers, KeyPressedEvent, MouseButton, MouseEvent}; 9 | 10 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Error)] 11 | pub enum ConversionError { 12 | #[error("Unknown crossterm event: {event:?}")] 13 | UnknownEvent { event: CrosstermEvent }, 14 | #[error("Unknown crossterm key code: {key_code:?}")] 15 | UnknownKey { key_code: CrosstermKeyCode }, 16 | #[error("Unknown crossterm modifiers: {modifiers:?}")] 17 | UnknownModifiers { modifiers: u8 }, 18 | #[error("Key repeated event not supported")] 19 | KeyReleasedEventNotSupported, 20 | #[error("Key repeated event not supported")] 21 | KeyRepeatedEventNotSupported, 22 | } 23 | 24 | impl TryFrom for Event { 25 | type Error = ConversionError; 26 | fn try_from(event: CrosstermEvent) -> Result { 27 | use CrosstermEvent::*; 28 | let event = match event { 29 | Key(key_event) => Event::KeyPressed(key_event.try_into()?), 30 | Mouse(mouse_event) => Event::Mouse(mouse_event.into()), 31 | _ => return Err(ConversionError::UnknownEvent { event }), 32 | // TODO maybe handle these later if needed 33 | // FocusGained => todo!(), 34 | // FocusLost => todo!(), 35 | // Paste(_) => todo!(), 36 | // Resize(_, _) => todo!(), 37 | }; 38 | Ok(event) 39 | } 40 | } 41 | 42 | impl TryFrom for KeyPressedEvent { 43 | type Error = ConversionError; 44 | fn try_from(key_event: CrosstermKeyEvent) -> Result { 45 | match key_event.kind { 46 | KeyEventKind::Press => Ok(KeyPressedEvent { 47 | key: key_event.code.try_into()?, 48 | modifiers: key_event.modifiers.into(), 49 | }), 50 | KeyEventKind::Release => Err(ConversionError::KeyReleasedEventNotSupported), 51 | KeyEventKind::Repeat => Err(ConversionError::KeyRepeatedEventNotSupported), 52 | } 53 | } 54 | } 55 | 56 | impl TryFrom for Key { 57 | type Error = ConversionError; 58 | fn try_from(key_code: CrosstermKeyCode) -> Result { 59 | use Key::*; 60 | let key = match key_code { 61 | CrosstermKeyCode::Char(c) => Char(c), 62 | CrosstermKeyCode::Esc => Esc, 63 | CrosstermKeyCode::Enter => Enter, 64 | CrosstermKeyCode::Tab => Tab, 65 | CrosstermKeyCode::BackTab => BackTab, 66 | CrosstermKeyCode::Backspace => Backspace, 67 | CrosstermKeyCode::Delete => Delete, 68 | CrosstermKeyCode::Insert => Insert, 69 | CrosstermKeyCode::Left => Left, 70 | CrosstermKeyCode::Right => Right, 71 | CrosstermKeyCode::Up => Up, 72 | CrosstermKeyCode::Down => Down, 73 | CrosstermKeyCode::Home => Home, 74 | CrosstermKeyCode::End => End, 75 | CrosstermKeyCode::PageUp => PageUp, 76 | CrosstermKeyCode::PageDown => PageDown, 77 | CrosstermKeyCode::F(num) => F(num), 78 | CrosstermKeyCode::Null => Null, 79 | 80 | key_code => return Err(ConversionError::UnknownKey { key_code }), 81 | // TODO maybe handle these later if needed 82 | // CrosstermKeyCode::CapsLock => todo!(), 83 | // CrosstermKeyCode::ScrollLock => todo!(), 84 | // CrosstermKeyCode::NumLock => todo!(), 85 | // CrosstermKeyCode::PrintScreen => todo!(), 86 | // CrosstermKeyCode::Pause => todo!(), 87 | // CrosstermKeyCode::Menu => todo!(), 88 | // CrosstermKeyCode::KeypadBegin => todo!(), 89 | // CrosstermKeyCode::Media(_) => todo!(), 90 | // CrosstermKeyCode::Modifier(_) => todo!(), 91 | }; 92 | Ok(key) 93 | } 94 | } 95 | 96 | impl From for KeyModifiers { 97 | fn from(modifiers: CrosstermKeyModifiers) -> Self { 98 | // our modifiers area superset of crossterm's modifiers and are bit compatible so this is 99 | // safe 100 | KeyModifiers::from_bits(modifiers.bits()).unwrap() 101 | } 102 | } 103 | 104 | impl From for MouseEvent { 105 | fn from(mouse_event: CrosstermMouseEvent) -> Self { 106 | MouseEvent { 107 | column: mouse_event.column, 108 | row: mouse_event.row, 109 | kind: mouse_event.kind.into(), 110 | modifiers: mouse_event.modifiers.into(), 111 | } 112 | } 113 | } 114 | use crossterm::event::MouseButton as CrosstermMouseButton; 115 | use crossterm::event::MouseEventKind as CrosstermMouseEventKind; 116 | 117 | impl From for MouseButton { 118 | fn from(mouse_button: CrosstermMouseButton) -> Self { 119 | match mouse_button { 120 | CrosstermMouseButton::Left => MouseButton::Left, 121 | CrosstermMouseButton::Right => MouseButton::Right, 122 | CrosstermMouseButton::Middle => MouseButton::Middle, 123 | } 124 | } 125 | } 126 | 127 | impl From for MouseEventKind { 128 | fn from(mouse_event_kind: CrosstermMouseEventKind) -> Self { 129 | match mouse_event_kind { 130 | CrosstermMouseEventKind::Down(button) => MouseEventKind::Down(button.into()), 131 | CrosstermMouseEventKind::Up(button) => MouseEventKind::Up(button.into()), 132 | CrosstermMouseEventKind::Drag(button) => MouseEventKind::Drag(button.into()), 133 | CrosstermMouseEventKind::Moved => MouseEventKind::Moved, 134 | CrosstermMouseEventKind::ScrollUp => MouseEventKind::ScrollUp, 135 | CrosstermMouseEventKind::ScrollDown => MouseEventKind::ScrollDown, 136 | CrosstermMouseEventKind::ScrollLeft => MouseEventKind::ScrollLeft, 137 | CrosstermMouseEventKind::ScrollRight => MouseEventKind::ScrollRight, 138 | } 139 | } 140 | } 141 | 142 | #[cfg(test)] 143 | mod tests { 144 | use super::*; 145 | use rstest::rstest; 146 | 147 | #[rstest] 148 | #[case(CrosstermKeyModifiers::empty(), KeyModifiers::empty())] 149 | #[case(CrosstermKeyModifiers::NONE, KeyModifiers::empty())] 150 | #[case(CrosstermKeyModifiers::SHIFT, KeyModifiers::SHIFT)] 151 | #[case(CrosstermKeyModifiers::CONTROL, KeyModifiers::CTRL)] 152 | #[case(CrosstermKeyModifiers::CONTROL, KeyModifiers::CONTROL)] 153 | #[case(CrosstermKeyModifiers::ALT, KeyModifiers::ALT)] 154 | #[case(CrosstermKeyModifiers::ALT, KeyModifiers::OPTION)] 155 | #[case(CrosstermKeyModifiers::SUPER, KeyModifiers::SUPER)] 156 | #[case(CrosstermKeyModifiers::SUPER, KeyModifiers::WIN)] 157 | #[case(CrosstermKeyModifiers::SUPER, KeyModifiers::COMMAND)] 158 | #[case(CrosstermKeyModifiers::HYPER, KeyModifiers::HYPER)] 159 | #[case(CrosstermKeyModifiers::META, KeyModifiers::META)] 160 | #[case( 161 | CrosstermKeyModifiers::all(), 162 | KeyModifiers::SHIFT | KeyModifiers::CTRL | KeyModifiers::ALT | KeyModifiers::SUPER | 163 | KeyModifiers::HYPER | KeyModifiers::META, 164 | )] 165 | fn try_from_modifiers( 166 | #[case] modifiers: CrosstermKeyModifiers, 167 | #[case] expected: KeyModifiers, 168 | ) { 169 | let result = KeyModifiers::from(modifiers); 170 | assert_eq!(result, expected); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/events/termion.rs: -------------------------------------------------------------------------------- 1 | use termion::event::Key as TermionKey; 2 | 3 | use super::Key; 4 | 5 | impl From for Key { 6 | fn from(key: TermionKey) -> Self { 7 | use Key::*; 8 | match key { 9 | TermionKey::Backspace => Backspace, 10 | TermionKey::Left => Left, 11 | TermionKey::Right => Right, 12 | TermionKey::Up => Up, 13 | TermionKey::Down => Down, 14 | TermionKey::Home => Home, 15 | TermionKey::End => End, 16 | TermionKey::PageUp => PageUp, 17 | TermionKey::PageDown => PageDown, 18 | TermionKey::BackTab => BackTab, 19 | TermionKey::Delete => Delete, 20 | TermionKey::Insert => Insert, 21 | TermionKey::F(n) => F(n), 22 | TermionKey::Char(c) => Char(c), 23 | TermionKey::Alt(c) => Char(c), 24 | TermionKey::Ctrl(c) => Char(c), 25 | TermionKey::Null => Null, 26 | TermionKey::Esc => Esc, 27 | _ => Null, 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/events/termwiz.rs: -------------------------------------------------------------------------------- 1 | use termwiz::input::KeyCode as TermwizKeyCode; 2 | 3 | use super::Key; 4 | 5 | impl From for Key { 6 | fn from(key_code: TermwizKeyCode) -> Self { 7 | use Key::*; 8 | match key_code { 9 | TermwizKeyCode::Char(c) => Char(c), 10 | // TermwizKeyCode::Hyper => Hyper, 11 | // TermwizKeyCode::Super => Super, 12 | // TermwizKeyCode::Meta => Meta, 13 | // TermwizKeyCode::Cancel => Cancel, 14 | TermwizKeyCode::Backspace => Backspace, 15 | TermwizKeyCode::Tab => Tab, 16 | // TermwizKeyCode::Clear => Clear, 17 | TermwizKeyCode::Enter => Enter, 18 | // TermwizKeyCode::Shift => Shift, 19 | TermwizKeyCode::Escape => Esc, 20 | // TermwizKeyCode::LeftShift => LeftShift, 21 | // TermwizKeyCode::RightShift => RightShift, 22 | // TermwizKeyCode::Control => Control, 23 | // TermwizKeyCode::LeftControl => LeftControl, 24 | // TermwizKeyCode::RightControl => RightControl, 25 | // TermwizKeyCode::Alt => Alt, 26 | // TermwizKeyCode::LeftAlt => LeftAlt, 27 | // TermwizKeyCode::RightAlt => RightAlt, 28 | // TermwizKeyCode::Menu => Menu, 29 | // TermwizKeyCode::LeftMenu => LeftMenu, 30 | // TermwizKeyCode::RightMenu => RightMenu, 31 | // TermwizKeyCode::Pause => Pause, 32 | // TermwizKeyCode::CapsLock => CapsLock, 33 | TermwizKeyCode::PageUp => PageUp, 34 | TermwizKeyCode::PageDown => PageDown, 35 | TermwizKeyCode::End => End, 36 | TermwizKeyCode::Home => Home, 37 | // TermwizKeyCode::LeftArrow => LeftArrow, 38 | // TermwizKeyCode::RightArrow => RightArrow, 39 | // TermwizKeyCode::UpArrow => UpArrow, 40 | // TermwizKeyCode::DownArrow => DownArrow, 41 | // TermwizKeyCode::Select => Select, 42 | // TermwizKeyCode::Print => Print, 43 | // TermwizKeyCode::Execute => Execute, 44 | // TermwizKeyCode::PrintScreen => PrintScreen, 45 | TermwizKeyCode::Insert => Insert, 46 | TermwizKeyCode::Delete => Delete, 47 | // TermwizKeyCode::Help => Help, 48 | // TermwizKeyCode::LeftWindows => LeftWindows, 49 | // TermwizKeyCode::RightWindows => RightWindows, 50 | // TermwizKeyCode::Applications => Applications, 51 | // TermwizKeyCode::Sleep => Sleep, 52 | // TermwizKeyCode::Numpad0 => Numpad0, 53 | // TermwizKeyCode::Numpad1 => Numpad1, 54 | // TermwizKeyCode::Numpad2 => Numpad2, 55 | // TermwizKeyCode::Numpad3 => Numpad3, 56 | // TermwizKeyCode::Numpad4 => Numpad4, 57 | // TermwizKeyCode::Numpad5 => Numpad5, 58 | // TermwizKeyCode::Numpad6 => Numpad6, 59 | // TermwizKeyCode::Numpad7 => Numpad7, 60 | // TermwizKeyCode::Numpad8 => Numpad8, 61 | // TermwizKeyCode::Numpad9 => Numpad9, 62 | // TermwizKeyCode::Multiply => Multiply, 63 | // TermwizKeyCode::Add => Add, 64 | // TermwizKeyCode::Separator => Separator, 65 | // TermwizKeyCode::Subtract => Subtract, 66 | // TermwizKeyCode::Decimal => Decimal, 67 | // TermwizKeyCode::Divide => Divide, 68 | // TermwizKeyCode::Function(n) => Function(n), 69 | // TermwizKeyCode::NumLock => NumLock, 70 | // TermwizKeyCode::ScrollLock => ScrollLock, 71 | // TermwizKeyCode::Copy => Copy, 72 | // TermwizKeyCode::Cut => Cut, 73 | // TermwizKeyCode::Paste => Paste, 74 | // TermwizKeyCode::BrowserBack => BrowserBack, 75 | // TermwizKeyCode::BrowserForward => BrowserForward, 76 | // TermwizKeyCode::BrowserRefresh => BrowserRefresh, 77 | // TermwizKeyCode::BrowserStop => BrowserStop, 78 | // TermwizKeyCode::BrowserSearch => BrowserSearch, 79 | // TermwizKeyCode::BrowserFavorites => BrowserFavorites, 80 | // TermwizKeyCode::BrowserHome => BrowserHome, 81 | // TermwizKeyCode::VolumeMute => VolumeMute, 82 | // TermwizKeyCode::VolumeDown => VolumeDown, 83 | // TermwizKeyCode::VolumeUp => VolumeUp, 84 | // TermwizKeyCode::MediaNextTrack => MediaNextTrack, 85 | // TermwizKeyCode::MediaPrevTrack => MediaPrevTrack, 86 | // TermwizKeyCode::MediaStop => MediaStop, 87 | // TermwizKeyCode::MediaPlayPause => MediaPlayPause, 88 | // TermwizKeyCode::ApplicationLeftArrow => ApplicationLeftArrow, 89 | // TermwizKeyCode::ApplicationRightArrow => ApplicationRightArrow, 90 | // TermwizKeyCode::ApplicationUpArrow => ApplicationUpArrow, 91 | // TermwizKeyCode::ApplicationDownArrow => ApplicationDownArrow, 92 | // TermwizKeyCode::KeyPadHome => KeyPadHome, 93 | // TermwizKeyCode::KeyPadEnd => KeyPadEnd, 94 | // TermwizKeyCode::KeyPadPageUp => KeyPadPageUp, 95 | // TermwizKeyCode::KeyPadPageDown => KeyPadPageDown, 96 | // TermwizKeyCode::KeyPadBegin => KeyPadBegin, 97 | _ => todo!(), 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Ratatui-widgets 2 | //! 3 | //! Please note: this crate is deprecated so that we can use the name for an internal Ratatui widget 4 | //! crate. Please use [tui-framework-experiment] instead. 5 | //! 6 | //! [tui-framework-experiment]: https://crates.io/crates/tui-framework-experiment 7 | //! 8 | //! [![Crates.io Badge]][Crate] [![License Badge]](#license) [![Docs.rs Badge]][API Docs]
9 | //! [![Deps.rs Badge]][Dependencies] [![Codecov.io Badge]][Coverage] [![Discord Badge]][Ratatui 10 | //! Discord] 11 | //! 12 | //! `ratatui-widgets` is a Rust crate with extra widgets for [Ratatui]. 13 | //! 14 | //! ## Installation 15 | //! 16 | //! ```shell 17 | //! cargo add ratatui-widgets 18 | //! ``` 19 | //! 20 | //! ## Usage 21 | //! 22 | //! ```rust 23 | //! // TODO: Add usage examples 24 | //! ``` 25 | //! 26 | //! ## Example 27 | //! 28 | //! ![Button](https://vhs.charm.sh/vhs-MSE5G9byLklG23xdJwbqR.gif) 29 | //! 30 | //! [Crates.io Badge]: https://img.shields.io/crates/v/ratatui-widgets?logo=rust&style=for-the-badge 31 | //! [License Badge]: https://img.shields.io/crates/l/ratatui-widgets?style=for-the-badge 32 | //! [Docs.rs Badge]: https://img.shields.io/docsrs/ratatui-widgets?logo=rust&style=for-the-badge 33 | //! [Deps.rs Badge]: 34 | //! https://deps.rs/repo/github/joshka/ratatui-widgets/status.svg?style=for-the-badge 35 | //! [Codecov.io Badge]: 36 | //! https://img.shields.io/codecov/c/github/joshka/ratatui-widgets?logo=codecov&style=for-the-badge&token=BAQ8SOKEST 37 | //! [Discord Badge]: 38 | //! https://img.shields.io/discord/1070692720437383208?label=ratatui+discord&logo=discord&style=for-the-badge 39 | //! 40 | //! [Crate]: https://crates.io/crates/ratatui-widgets 41 | //! [API Docs]: https://docs.rs/crate/ratatui-widgets/ 42 | //! [Dependencies]: https://deps.rs/repo/github/joshka/ratatui-widgets 43 | //! [Coverage]: https://app.codecov.io/gh/joshka/ratatui-widgets 44 | //! [Ratatui Discord]: https://discord.gg/pMCEU9hNEj 45 | //! 46 | //! [Ratatui]: https://crates.io/crates/ratatui 47 | 48 | #[deprecated(note = "Use tui-framework-experiment instead")] 49 | pub mod button; 50 | #[deprecated(note = "Use tui-framework-experiment instead")] 51 | pub mod events; 52 | #[deprecated(note = "Use tui-framework-experiment instead")] 53 | pub mod stack_container; 54 | #[deprecated(note = "Use tui-framework-experiment instead")] 55 | pub mod toggle_switch; 56 | 57 | #[deprecated(note = "Use tui-framework-experiment instead")] 58 | pub use button::{Button, State as ButtonState, Theme as ButtonTheme}; 59 | #[deprecated(note = "Use tui-framework-experiment instead")] 60 | pub use stack_container::StackContainer; 61 | -------------------------------------------------------------------------------- /src/stack_container.rs: -------------------------------------------------------------------------------- 1 | use ratatui::layout::Flex; 2 | use ratatui::prelude::*; 3 | use ratatui::widgets::WidgetRef; 4 | use std::fmt; 5 | 6 | /// A container that stacks widgets in a given direction 7 | /// 8 | /// # Examples 9 | /// 10 | /// ```rust 11 | /// use ratatui::prelude::*; 12 | /// use ratatui::widgets::*; 13 | /// use ratatui_widgets::StackContainer; 14 | /// 15 | /// let mut stack = StackContainer::horizontal(); 16 | /// stack.push(Box::new(Paragraph::new("Left")), Constraint::Fill(1)); 17 | /// stack.push(Box::new(Paragraph::new("Center")), Constraint::Fill(2)); 18 | /// stack.push(Box::new(Paragraph::new("Right")), Constraint::Fill(1)); 19 | /// 20 | /// // or 21 | /// 22 | /// let stack = StackContainer::horizontal().with_widgets(vec![ 23 | /// (Box::new(Paragraph::new("Left")), Constraint::Fill(1)), 24 | /// (Box::new(Paragraph::new("Center")), Constraint::Fill(2)), 25 | /// (Box::new(Paragraph::new("Right")), Constraint::Fill(1)), 26 | /// ]); 27 | /// ``` 28 | #[derive(Default)] 29 | pub struct StackContainer { 30 | direction: Direction, 31 | flex: Flex, 32 | margin: u16, 33 | spacing: u16, 34 | widgets: Vec<(Box, Constraint)>, 35 | } 36 | 37 | impl fmt::Debug for StackContainer { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | f.debug_struct("StackContainer") 40 | .field("direction", &self.direction) 41 | .finish_non_exhaustive() 42 | } 43 | } 44 | 45 | impl StackContainer { 46 | pub fn new(direction: Direction) -> Self { 47 | Self { 48 | direction, 49 | ..Default::default() 50 | } 51 | } 52 | 53 | pub fn horizontal() -> Self { 54 | Self::new(Direction::Horizontal) 55 | } 56 | 57 | pub fn vertical() -> Self { 58 | Self::new(Direction::Vertical) 59 | } 60 | 61 | pub fn with_margin(mut self, margin: u16) -> Self { 62 | self.margin = margin; 63 | self 64 | } 65 | 66 | pub fn with_spacing(mut self, spacing: u16) -> Self { 67 | self.spacing = spacing; 68 | self 69 | } 70 | 71 | pub fn with_flex(mut self, flex: Flex) -> Self { 72 | self.flex = flex; 73 | self 74 | } 75 | 76 | pub fn with_widget(mut self, widget: Box, constraint: Constraint) -> Self { 77 | self.widgets.push((widget, constraint)); 78 | self 79 | } 80 | 81 | pub fn with_widgets(mut self, widgets: Vec<(Box, Constraint)>) -> Self { 82 | self.widgets = widgets; 83 | self 84 | } 85 | 86 | pub fn push(&mut self, widget: Box, constraint: Constraint) { 87 | self.widgets.push((widget, constraint)); 88 | } 89 | 90 | pub fn remove(&mut self, index: usize) { 91 | self.widgets.remove(index); 92 | } 93 | } 94 | 95 | impl Widget for &StackContainer { 96 | fn render(self, area: Rect, buf: &mut Buffer) { 97 | let layout = Layout::default() 98 | .direction(self.direction) 99 | .flex(self.flex) 100 | .margin(self.margin) 101 | .spacing(self.spacing) 102 | .constraints(self.widgets.iter().map(|(_, c)| *c)); 103 | let areas = layout.split(area); 104 | let widgets = self.widgets.iter().map(|(w, _)| w); 105 | for (widget, area) in widgets.zip(areas.iter()) { 106 | widget.render_ref(*area, buf); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/toggle_switch.rs: -------------------------------------------------------------------------------- 1 | use crate::events::{EventHandler, Key, KeyPressedEvent}; 2 | use itertools::Itertools; 3 | use ratatui::{ 4 | buffer::Buffer, 5 | layout::{Constraint, Layout, Rect}, 6 | style::{Color, Stylize}, 7 | text::Text, 8 | widgets::Widget, 9 | }; 10 | 11 | /// A toggle switch widget 12 | /// 13 | /// Displays a switch that can be toggled on or off 14 | /// 15 | /// # Examples 16 | /// 17 | /// ```rust 18 | /// use ratatui::widgets::Widget; 19 | /// use ratatui_widgets::toggle_switch::{ToggleSwitch, State}; 20 | /// 21 | /// # fn draw(frame: &mut ratatui::Frame) { 22 | /// let toggle_switch = ToggleSwitch::new("Toggle me", State::Off); 23 | /// frame.render_widget(toggle_switch, frame.area()); 24 | /// # } 25 | /// ``` 26 | #[derive(Debug, Clone)] 27 | pub struct ToggleSwitch<'text> { 28 | text: Text<'text>, 29 | theme: Theme, 30 | state: State, 31 | focus: Focus, 32 | } 33 | 34 | #[derive(Default, PartialEq, Eq, Clone, Debug, Copy)] 35 | pub enum State { 36 | On, 37 | #[default] 38 | Off, 39 | } 40 | 41 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 42 | pub enum Focus { 43 | Focused, 44 | Unfocused, 45 | } 46 | 47 | #[derive(Copy, Clone, Debug)] 48 | pub struct Theme { 49 | focused_text: Color, 50 | 51 | focused_on_fg: Color, 52 | focused_on_bg_main: Color, 53 | focused_on_bg_highlight: Color, 54 | focused_on_bg_shadow: Color, 55 | 56 | focused_off_fg: Color, 57 | focused_off_bg_main: Color, 58 | focused_off_bg_highlight: Color, 59 | focused_off_bg_shadow: Color, 60 | 61 | unfocused_text: Color, 62 | 63 | unfocused_on_fg: Color, 64 | unfocused_on_bg_main: Color, 65 | unfocused_on_bg_highlight: Color, 66 | unfocused_on_bg_shadow: Color, 67 | 68 | unfocused_off_fg: Color, 69 | unfocused_off_bg_main: Color, 70 | unfocused_off_bg_highlight: Color, 71 | unfocused_off_bg_shadow: Color, 72 | } 73 | 74 | impl Default for Theme { 75 | fn default() -> Self { 76 | themes::NORMAL 77 | } 78 | } 79 | 80 | impl<'text> ToggleSwitch<'text> { 81 | pub fn new>>(text: T, default_state: State) -> Self { 82 | Self { 83 | text: text.into(), 84 | theme: Theme::default(), 85 | state: default_state, 86 | focus: Focus::Unfocused, 87 | } 88 | } 89 | 90 | pub fn with_theme(mut self, theme: Theme) -> Self { 91 | self.theme = theme; 92 | self 93 | } 94 | } 95 | 96 | impl EventHandler for ToggleSwitch<'_> { 97 | fn handle_key(&mut self, key_event: KeyPressedEvent) { 98 | match key_event.key { 99 | Key::Char(' ') | Key::Enter => self.toggle_state(), 100 | Key::Char('h') | Key::Left => self.toggle_off(), 101 | Key::Char('l') | Key::Right => self.toggle_on(), 102 | _ => {} 103 | } 104 | } 105 | } 106 | 107 | impl ToggleSwitch<'_> { 108 | pub fn toggle_state(&mut self) { 109 | self.focus(); 110 | match self.state { 111 | State::On => self.toggle_off(), 112 | State::Off => self.toggle_on(), 113 | } 114 | } 115 | 116 | pub fn toggle_on(&mut self) { 117 | self.state = State::On; 118 | } 119 | 120 | pub fn toggle_off(&mut self) { 121 | self.state = State::Off; 122 | } 123 | 124 | pub fn focus(&mut self) { 125 | self.focus = Focus::Focused; 126 | } 127 | 128 | pub fn blur(&mut self) { 129 | self.focus = Focus::Unfocused; 130 | } 131 | } 132 | 133 | impl Widget for &ToggleSwitch<'_> { 134 | fn render(self, area: Rect, buf: &mut Buffer) { 135 | let theme = self.theme; 136 | 137 | // TODO: refactor this to use a more generic approach 138 | let (tick_fg, tick_bg, cross_fg, cross_bg) = match (self.focus, self.state) { 139 | (Focus::Focused, State::On) => ( 140 | theme.focused_on_fg, 141 | theme.focused_on_bg_main, 142 | theme.focused_off_fg, 143 | theme.focused_off_bg_main, 144 | ), 145 | (Focus::Focused, State::Off) => ( 146 | theme.focused_off_fg, 147 | theme.focused_off_bg_main, 148 | theme.focused_on_fg, 149 | theme.focused_on_bg_main, 150 | ), 151 | (Focus::Unfocused, State::On) => ( 152 | theme.unfocused_on_fg, 153 | theme.unfocused_on_bg_main, 154 | theme.unfocused_off_fg, 155 | theme.unfocused_off_bg_main, 156 | ), 157 | (Focus::Unfocused, State::Off) => ( 158 | theme.unfocused_off_fg, 159 | theme.unfocused_off_bg_main, 160 | theme.unfocused_on_fg, 161 | theme.unfocused_on_bg_main, 162 | ), 163 | }; 164 | 165 | let (tick_highlight, tick_shadow, cross_highlight, cross_shadow) = 166 | match (self.state, self.focus) { 167 | (State::On, Focus::Focused) => ( 168 | theme.focused_on_bg_highlight, 169 | theme.focused_on_bg_shadow, 170 | theme.focused_off_bg_highlight, 171 | theme.focused_off_bg_shadow, 172 | ), 173 | (State::On, Focus::Unfocused) => ( 174 | theme.unfocused_on_bg_highlight, 175 | theme.unfocused_on_bg_shadow, 176 | theme.unfocused_off_bg_highlight, 177 | theme.unfocused_off_bg_shadow, 178 | ), 179 | (State::Off, Focus::Focused) => ( 180 | theme.focused_off_bg_highlight, 181 | theme.focused_off_bg_shadow, 182 | theme.focused_on_bg_highlight, 183 | theme.focused_on_bg_shadow, 184 | ), 185 | (State::Off, Focus::Unfocused) => ( 186 | theme.unfocused_off_bg_highlight, 187 | theme.unfocused_off_bg_shadow, 188 | theme.unfocused_on_bg_highlight, 189 | theme.unfocused_on_bg_shadow, 190 | ), 191 | }; 192 | 193 | let [switch, label] = Layout::horizontal([Constraint::Max(10), Constraint::Fill(1)]) 194 | .spacing(2) 195 | .areas(area); 196 | let [cross, tick] = Layout::horizontal([Constraint::Fill(1); 2]).areas(switch); 197 | 198 | buf.set_style(cross, (cross_fg, cross_bg)); 199 | buf.set_style(tick, (tick_fg, tick_bg)); 200 | 201 | let rows = switch.rows().collect_vec(); 202 | let last_index = rows.len().saturating_sub(1); 203 | let (first, middle, last) = match rows.len() { 204 | 0 | 1 => (None, &rows[..], None), 205 | 2 => (None, &rows[..last_index], Some(rows[last_index])), 206 | _ => (Some(rows[0]), &rows[1..last_index], Some(rows[last_index])), 207 | }; 208 | 209 | // render top line if there's enough space 210 | if let Some(first) = first { 211 | let [left, right] = Layout::horizontal([Constraint::Fill(1); 2]).areas(first); 212 | "▔" 213 | .repeat(cross.width as usize) 214 | .fg(cross_highlight) 215 | .bg(cross_bg) 216 | .render(left, buf); 217 | "▔" 218 | .repeat(tick.width as usize) 219 | .fg(tick_highlight) 220 | .bg(tick_bg) 221 | .render(right, buf); 222 | } 223 | // render bottom line if there's enough space 224 | if let Some(last) = last { 225 | let [left, right] = Layout::horizontal([Constraint::Fill(1); 2]).areas(last); 226 | "▁" 227 | .repeat(cross.width as usize) 228 | .fg(cross_shadow) 229 | .bg(cross_bg) 230 | .render(left, buf); 231 | "▁" 232 | .repeat(tick.width as usize) 233 | .fg(tick_shadow) 234 | .bg(tick_bg) 235 | .render(right, buf); 236 | } 237 | let text_style = match self.focus { 238 | Focus::Focused => theme.focused_text, 239 | Focus::Unfocused => theme.unfocused_text, 240 | }; 241 | buf.set_style(label, text_style); 242 | let middle_row_index = label.height as usize / 2; 243 | let middle_row = label.rows().collect_vec()[middle_row_index]; 244 | self.text.clone().left_aligned().render(middle_row, buf); 245 | 246 | let middle_row_index = middle.len() / 2; 247 | let middle_row = middle[middle_row_index]; 248 | let [cross, tick] = Layout::horizontal([Constraint::Fill(1); 2]).areas(middle_row); 249 | Text::from("✗").centered().render(cross, buf); 250 | Text::from("✓").centered().render(tick, buf); 251 | } 252 | } 253 | 254 | pub mod themes { 255 | use super::Theme; 256 | use ratatui::style::palette::tailwind; 257 | 258 | pub const NORMAL: Theme = Theme { 259 | focused_text: tailwind::SLATE.c300, 260 | 261 | focused_on_fg: tailwind::BLUE.c100, 262 | focused_on_bg_main: tailwind::BLUE.c500, 263 | focused_on_bg_highlight: tailwind::BLUE.c300, 264 | focused_on_bg_shadow: tailwind::BLUE.c700, 265 | 266 | focused_off_fg: tailwind::SLATE.c300, 267 | focused_off_bg_main: tailwind::SLATE.c700, 268 | focused_off_bg_highlight: tailwind::SLATE.c500, 269 | focused_off_bg_shadow: tailwind::SLATE.c900, 270 | 271 | unfocused_text: tailwind::SLATE.c400, 272 | 273 | unfocused_on_fg: tailwind::BLUE.c200, 274 | unfocused_on_bg_main: tailwind::BLUE.c600, 275 | unfocused_on_bg_highlight: tailwind::BLUE.c400, 276 | unfocused_on_bg_shadow: tailwind::BLUE.c800, 277 | 278 | unfocused_off_fg: tailwind::SLATE.c400, 279 | unfocused_off_bg_main: tailwind::SLATE.c800, 280 | unfocused_off_bg_highlight: tailwind::SLATE.c600, 281 | unfocused_off_bg_shadow: tailwind::SLATE.c950, 282 | }; 283 | } 284 | --------------------------------------------------------------------------------