├── .cargo └── config.toml ├── .git_hooks └── pre-commit ├── .gitattributes ├── .github └── workflows │ └── check.yml ├── .gitignore ├── Cargo.toml ├── Changelog.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── check.sh ├── examples ├── cli.rs ├── hello-world.rs ├── info.rs ├── input.rs ├── plane.rs └── visual.rs └── src ├── color ├── alpha.rs ├── channel.rs ├── channels.rs ├── mod.rs ├── palette.rs └── rgb.rs ├── error.rs ├── input ├── input.rs ├── input_type.rs ├── key.rs ├── key_mod.rs ├── mice_events.rs ├── mod.rs └── received.rs ├── lib.rs ├── macros.rs ├── notcurses ├── builder.rs ├── capabilities.rs ├── log_level.rs ├── mod.rs ├── notcurses.rs └── statistics.rs ├── plane ├── align.rs ├── builder.rs ├── cell.rs ├── geometry.rs ├── mod.rs ├── plane.rs └── style.rs └── visual ├── blitter.rs ├── builder.rs ├── geometry.rs ├── mod.rs ├── options.rs ├── pixel.rs ├── scale.rs └── visual.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | 3 | # CHECK 4 | cl = "clippy" 5 | clq = "clippy --quiet" 6 | 7 | # CLEAN PACKAGE 8 | cp = "clean --package" 9 | crp = "clean --release --package" 10 | 11 | # DOC 12 | d = "doc --no-deps" 13 | do = "doc --no-deps --open" 14 | dd = "doc" 15 | ddo = "doc --open" 16 | # +nightly 17 | nd = "doc --no-deps --all-features" 18 | ndo = "doc --no-deps --open --all-features" 19 | ndd = "doc --all-features" 20 | nddo = "doc --open --all-features" 21 | 22 | # BUILD 23 | b = "build" 24 | bb = "build --bin" 25 | be = "build --example" 26 | bq = "build --quiet" 27 | br = "build --release" 28 | bqb = "build --quiet --bin" 29 | bqe = "build --quiet --example" 30 | brb = "build --release --bin" 31 | bre = "build --release --example" 32 | brqb = "build --release --quiet --bin" 33 | brqe = "build --release --quiet --example" 34 | 35 | # RUN 36 | r = "run" 37 | rb = "run --bin" 38 | re = "run --example" 39 | rq = "run --quiet" 40 | rr = "run --release" 41 | rqb = "run --quiet --bin" 42 | rqe = "run --quiet --example" 43 | rrb = "run --release --bin" 44 | rre = "run --release --example" 45 | rrqb = "run --release --quiet --bin" 46 | rrqe = "run --release --quiet --example" 47 | 48 | # TEST 49 | # fix IO errors: https://github.com/dankamongmen/notcurses/issues/766 50 | t = "test -- --test-threads 1 --nocapture" 51 | t_all = "test --no-fail-fast -- --test-threads 1 --nocapture" 52 | 53 | # TREE 54 | tr = "tree" 55 | trf = "tree --format {p}:{f}" # with feature list 56 | trr = "tree --format {p}:{r}" # with repository url 57 | 58 | # PUBLISH 59 | p = "publish --dry-run" 60 | pa = "publish --dry-run --allow-dirty" 61 | pp = "publish" 62 | ppa = "publish --allow-dirty" 63 | 64 | # Cargo subcommands: 65 | ## cargo-expand 66 | E = "expand" 67 | Eb = "expand --bin" 68 | Ee = "expand --example" 69 | El = "expand --lib" 70 | -------------------------------------------------------------------------------- /.git_hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # in order to configure git locally to use the new hook: 4 | 5 | # ```sh 6 | # git config core.hooksPath "./.git_hooks" 7 | # ``` 8 | 9 | CARGO_FMT="cargo +stable fmt" 10 | CLIPPY="cargo +stable clippy" 11 | 12 | $CARGO_FMT --version &>/dev/null 13 | if [ $? != 0 ]; then 14 | printf "[pre_commit] \033[0;31merror\033[0m: \"$CARGO_FMT\" not available?\n" 15 | exit 1 16 | fi 17 | 18 | $CLIPPY --version &>/dev/null 19 | if [ $? != 0 ]; then 20 | printf "[pre_commit] \033[0;31merror\033[0m: \"$CLIPPY\" not available?\n" 21 | exit 1 22 | fi 23 | 24 | # --- 25 | 26 | $CARGO_FMT -- --check 27 | result=$? 28 | 29 | printf "[pre_commit] $CARGO_FMT → " 30 | if [ $result != 0 ]; then 31 | printf "\033[0;31merror\033[0m \n" 32 | exit $result 33 | else 34 | printf "\033[0;32mOK\033[0m \n" 35 | fi 36 | 37 | $CLIPPY 38 | result=$? 39 | 40 | printf "[pre_commit] $CLIPPY → " 41 | if [ $result != 0 ]; then 42 | printf "\033[0;31merror\033[0m \n" 43 | else 44 | printf "\033[0;32mOK\033[0m \n" 45 | fi 46 | 47 | exit $result 48 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # https://github.com/github/linguist/blob/master/docs/overrides.md#using-gitattributes 2 | 3 | * linguist-vendored 4 | *.rs linguist-vendored=false 5 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | permissions: 2 | contents: read 3 | 4 | # runs on push to the main branch, and PRs 5 | on: 6 | push: 7 | branches: [main] 8 | pull_request: 9 | name: check 10 | 11 | jobs: 12 | # 1. format 13 | fmt: 14 | name: "stable / fmt" 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: "checkout" 18 | uses: actions/checkout@v3 19 | with: 20 | submodules: true 21 | 22 | - name: "Install stable" 23 | uses: dtolnay/rust-toolchain@stable 24 | with: 25 | components: rustfmt 26 | 27 | - name: "cargo fmt --check" 28 | run: cargo fmt --check 29 | 30 | # # 2. clippy lints 31 | # clippy: 32 | # name: "${{ matrix.toolchain }} / clippy" 33 | # runs-on: ubuntu-latest 34 | # permissions: 35 | # contents: read 36 | # checks: write 37 | # strategy: 38 | # fail-fast: false 39 | # matrix: 40 | # toolchain: [stable] 41 | # # toolchain: [stable, beta] 42 | # steps: 43 | # - name: "checkout" 44 | # uses: actions/checkout@v3 45 | # with: 46 | # submodules: true 47 | # 48 | # - name: "Install ${{ matrix.toolchain }}" 49 | # uses: dtolnay/rust-toolchain@master 50 | # with: 51 | # toolchain: ${{ matrix.toolchain }} 52 | # components: clippy 53 | # 54 | # - name: "cargo clippy" 55 | # run: cargo clippy --features=vendored 56 | 57 | # 3. documentation 58 | doc: 59 | name: "nightly / doc" 60 | runs-on: ubuntu-latest 61 | steps: 62 | - name: "checkout" 63 | uses: actions/checkout@v3 64 | with: 65 | submodules: true 66 | 67 | - name: "Install nightly" 68 | uses: dtolnay/rust-toolchain@nightly 69 | 70 | - name: "cargo doc" 71 | run: cargo doc --features=nightly_docs 72 | env: 73 | RUSTDOCFLAGS: --cfg docsrs 74 | 75 | # 4. minimum supported Rust version 76 | msrv: 77 | name: "ubuntu / ${{ matrix.msrv }}" 78 | runs-on: ubuntu-latest 79 | # we use a matrix here just because env can't be used in job names 80 | # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability 81 | strategy: 82 | matrix: 83 | msrv: [1.65.0] # sync with ../../{README.md, Cargo.toml, check.sh} 84 | steps: 85 | - name: "checkout" 86 | uses: actions/checkout@v3 87 | with: 88 | submodules: true 89 | 90 | - name: "Install ${{ matrix.msrv }}" 91 | uses: dtolnay/rust-toolchain@master 92 | with: 93 | toolchain: ${{ matrix.msrv }} 94 | 95 | - name: "cargo +${{ matrix.msrv }} check" 96 | run: cargo check --features=vendored 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cargo/credentials 2 | Cargo.lock 3 | /target 4 | _* 5 | *.swp 6 | /tools/ 7 | *.swo 8 | strace* 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notcurses" 3 | version = "3.6.0" 4 | edition = "2021" 5 | rust-version = "1.65.0" # sync with README.md, check.sh & .github/workflows/check.yml 6 | authors = ["José Luis Cruz ", "nick black "] 7 | license = "MIT OR Apache-2.0" 8 | description = "A high level Rust wrapper for the notcurses C library" 9 | repository = "https://github.com/dankamongmen/notcurses-rs" 10 | homepage = "https://nick-black.com/dankwiki/index.php/Notcurses" 11 | readme = "./README.md" 12 | categories = [ 13 | "api-bindings", 14 | "command-line-interface", 15 | "visualization", 16 | "rendering", 17 | ] 18 | keywords = ["tui", "cli", "terminal", "ncurses"] 19 | include = [ 20 | "/src/**/*rs", 21 | "/examples/hello-world.rs", 22 | "/Cargo.toml", 23 | "/LICENSE-*", 24 | "/README.md", 25 | ] 26 | publish = true 27 | 28 | [features] 29 | default = [] 30 | 31 | nightly_docs = ["vendored"] 32 | vendored = ['libnotcurses-sys/use_vendored_bindings'] 33 | 34 | [dependencies] 35 | libnotcurses-sys = { version = "3.11.0", features = ["std"] } # notcurses v3.0.11 36 | # libnotcurses-sys = { path = "../libnotcurses-sys", features = ["std"] } # master 37 | 38 | once_cell = "1.20.1" 39 | paste = "1.0.15" 40 | rgb = { version = "0.8.50", default-features = false } 41 | cuadra = "0.3.0" 42 | 43 | [dev-dependencies] 44 | rand = "0.8" 45 | 46 | [package.metadata.docs.rs] 47 | features = ["nightly_docs"] 48 | 49 | [badges] 50 | maintenance = { status = "actively-developed" } 51 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog], and this project adheres to 6 | [Semantic Versioning]. 7 | 8 | ## [Unreleased] 9 | 10 | ## [3.6.0] - 2024-10-03 11 | - bump `libnotcurses-sys` to `v3.11.0`. 12 | - wrap the raw notcurses pointer using reference counting to ensure proper dropping order. 13 | - The stopping of the notcurses context is now deferred until all dependent objects are dropped. 14 | - This fixes potential UB if `Notcurses` was dropped before planes or visuals. 15 | - change all methods that previously required a mutable `Notcurses` reference to require a shared reference. 16 | - require a new shared `Notcurses` reference in `Visual` and `VisualBuilder` constructors. 17 | - update all examples. 18 | 19 | ## [3.5.1] - 2024-10-01 20 | - update dependencies; bump `libnotcurses-sys` to `v3.10.1`. 21 | - fix `Plane::reparent_family`. 22 | - fix `Visual::from_file`. 23 | - add `check.sh` script. 24 | - add changelog. 25 | - update CI. 26 | 27 | ## [3.5.0] - 2023-09-08 28 | 29 | ### Added 30 | - new feature `nightly_docs`. 31 | 32 | ### Changed 33 | - bump `libnotcurses-sys` to `v3.10.0`. 34 | - bump MSRV to `1.65.0`. 35 | 36 | ### Fixed 37 | - avoid destructuring enums. 38 | - update CI. 39 | 40 | ## [3.4.1] - 2023-04-23 41 | 42 | ### Changed 43 | - update libnotcurses-sys to `v3.9.1`. 44 | 45 | ### Fixes 46 | - fix compilation on MacOs. 47 | 48 | ## [3.4.0] - 2023-04-09 49 | 50 | ### Added 51 | - add `cuadra` dependency to use their `Position` and `Size` replacing the old types. 52 | 53 | ### Changed 54 | - update `Blitter` methods related to cell size to use the expected order of width first. 55 | 56 | ### Removed 57 | - remove `az` dependency. 58 | 59 | ## [3.3.0] - 2023-04-08 60 | 61 | ### Added 62 | - add missing `Plane` methods: `erase`, `erase_region`, `contents`, `contents_region`. 63 | - add multiple missing inline attributes. 64 | 65 | ### Changed 66 | - update libnotcurses to `3.9.0`. 67 | - make `VisualOptions` public. 68 | - rename `Error` and `Result` to `NotcursesError` and `NotcursesResult`, respectively (**breaking change**). 69 | 70 | ### Fixed 71 | - fix `Plane`'s `set_base_*` methods. 72 | - update impl `Debug` for `Style`. 73 | 74 | ## [3.2.4] - 2023-03-22 75 | 76 | ### Fixed 77 | - update CIs. 78 | - update docs. 79 | - update lints. 80 | - minor refactor. 81 | 82 | ## [3.2.3] - 2023-03-22 83 | 84 | ### Changed 85 | - update `libnotcurses-sys` to `v3.7.5`. 86 | 87 | ### Fixed 88 | - fix `Nc`'s `osversion` method. 89 | 90 | ## [3.2.2] - 2023-02-10 91 | 92 | ### Changed 93 | - update libnotcurses-sys to `v3.7.4`. 94 | - remove `vendored` as a default feature. 95 | 96 | ## [3.2.1] - 2023-02-09 97 | 98 | ### Added 99 | - impl from `KeyMod` for `u32`. 100 | - add `rgb` interoperability dependency. 101 | 102 | ### Changed 103 | - update libnotcurses sys to `v3.7.2`. 104 | - depend on `core` when possible instead of `std`. 105 | - make `vendored` a default feature. 106 | 107 | ## [3.2.0] - 2023-01-19 108 | 109 | - update dependencies. 110 | - update libnotcurses-sys to `v3.7.1`. 111 | - refactor methods for `Input:` 112 | - rename `is_received` to `received`. 113 | - `has_key`, `has_char` for `some_key`, `some_char`. 114 | - add `is_press`, `is_release`, `is_repeat`. 115 | - new methods for `Received`: `is_key`, `is_char`, `key`, `char`. 116 | - new methods for `InputType`: `is_press`, `is_repeat`, `is_release`. 117 | - add missing inlines. 118 | 119 | ## [3.1.0] - 2022-09-26 120 | 121 | - rename methods `Visual::set_pixel` to `set_blitter_pixel` and `VisualBuilder::pixel` to `blitter_pixel`. 122 | - add `Visual` methods `set_pixel` & `get_pixel`. 123 | - update `Plane`: 124 | - fix method `duplicate` to accept a shared self reference. 125 | - impl `Clone`. 126 | - fix `Plane::resize_simple` order of dimensions. 127 | - accept impl `Into` for `resize` && `resize_simple`. 128 | - add methods: `styles`, `on_styles`, `off_styles`, `set_styles`, `set_fg_palindex`, `set_bg_palindex`. 129 | - update `Plane` methods: `set_base_bg`, `set_base_fg` & `set_base_channels`, now accepting `impl Into` instead of `Channel`. 130 | - fix double free when dropping order is not ideal. 131 | - add `new` `const` constructor for `Rgb` & `Rgba`. 132 | - add conversions for `Rgb` and `Rgba`. 133 | - update `Style`: 134 | - fix method `to_vec` to only include `None` if there are no other styles present. 135 | - improve `Display` & `Debug` impls. 136 | - fix `From`/`Into` `NcStyle` impls. 137 | - fix supported styles (WIP→ sys?) 138 | - rename method `add` to `set`. 139 | - add new method `unset`. 140 | - improve example `info`. 141 | - Update `Channels`: 142 | - new constructors: `with_default`, `from_rgb`, `from_rgb_both`, `from_rgb_alpha`, `from_rgb_alpha_both`, `combine`. 143 | - new methods: `reverse`, `set_fg`, `set_bg`, `set_channels`. 144 | - new `From` impls from tuples and arrays of primitives, representing different bg & fg channels: `(r,g,b,r,g,b)`, `((r,g,b),(r,g,b))`, `[r,g,b,r,g,b]`, `[r,g,b,r,g,b]`, `[[r,g,b],[r,g,b]]`, same bg & fg channels: `(r,g,b)`, `[r,g,b]`, and a combination of bg & fg impl Into `(fg, bg)`, `[fg, bg]`. 145 | - impl `Display` for `Capabilities`. 146 | 147 | ## [3.0.3] - 2022-07-25 148 | 149 | ### Added 150 | - update `Channels`: 151 | - new constructors: `with_default`, `from_rgb`, `from_rgb_both`, `from_rgb_alpha`, `from_rgb_alpha_both`, `combine`. 152 | - new methods: `reverse`, `set_fg`, `set_bg`, `set_channels`. 153 | - new `From` impls from tuples and arrays of primitives, representing different bg & fg channels: `(r,g,b,r,g,b)`, `((r,g,b),(r,g,b))`, `[r,g,b,r,g,b]`, `[r,g,b,r,g,b]`, `[[r,g,b],[r,g,b]]`, same bg & fg channels: `(r,g,b)`, `[r,g,b]`, and a combination of bg & fg impl Into `(fg, bg)`, `[fg, bg]`. 154 | 155 | ### Changed 156 | - update `Plane` methods: `set_base_bg`, `set_base_fg` & `set_base_channels`, now accepting `impl Into` instead of `Channel`. 157 | - impl `Display` for `Capabilities`. 158 | - bump libnotcurses-sys to `v3.6.2`. 159 | 160 | ### Fixed 161 | - fix clippy lints. 162 | - minor refactors. 163 | 164 | ## [3.0.2] - 2022-06-17 165 | 166 | ### Fixed 167 | - fix compilation on apple M1. 168 | 169 | ## [3.0.1] - 2022-06-12 170 | 171 | ### Fixed 172 | - fix docs.rs build. 173 | 174 | ## [3.0.0] - 2022-06-12 175 | 176 | Too many changes. 177 | 178 | ## [2.0.0] - 2021-04-20 179 | 180 | A clean slate. 181 | 182 | 183 | [unreleased]: https://github.com/dankamongmen/notcurses-rs/compare/v3.5.0...HEAD 184 | [3.5.0]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.5.0 185 | [3.4.1]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.4.1 186 | [3.4.0]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.4.0 187 | [3.3.0]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.3.0 188 | [3.2.3]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.2.3 189 | [3.2.2]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.2.2 190 | [3.2.1]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.2.1 191 | [3.2.0]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.2.0 192 | [3.1.0]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.1.0 193 | [3.0.3]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.0.3 194 | [3.0.2]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.0.2 195 | [3.0.1]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v3.0.1 196 | [2.0.0]: https://github.com/dankamongmen/notcurses-rs/releases/tag/v2.0.0 197 | 198 | [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ 199 | [Semantic Versioning]: https://semver.org/spec/v2.0.0.html 200 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2020-2023 José Luis Cruz and Nick Black 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2020-2023 José Luis Cruz and Nick Black 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crate](https://img.shields.io/crates/v/notcurses.svg)](https://crates.io/crates/notcurses) 2 | [![API](https://docs.rs/notcurses/badge.svg)](https://docs.rs/notcurses/) 3 | [![MSRV: 1.65.0](https://flat.badgen.net/badge/MSRV/1.65.0/purple)](https://releases.rs/docs/1.65.0/) 4 | 5 | A rusty wrapper over [notcurses][0], the most blingful TUI library. 6 | 7 | [0]:https://github.com/dankamongmen/notcurses 8 | 9 | ## Example 10 | 11 | ```rust 12 | use notcurses::*; 13 | 14 | fn main() -> Result<()> { 15 | let nc = Notcurses::new_cli()?; 16 | let mut cli = nc.cli_plane()?; 17 | cli.putstrln("\nhello world!")?; 18 | cli.render()?; 19 | Ok(()) 20 | } 21 | ``` 22 | 23 | ## Status of the library 24 | 25 | The latest released version is compatible with notcurses [`3.0.11`]. 26 | The unreleased version is compatible with notcurses unreleased master branch. 27 | 28 | *Current major version `3` is considered a development version*. 29 | 30 | **Main differences with `libnotcurses-sys`:** 31 | - Fully safe public API. 32 | - Allocating types have the `Drop` trait implemented. 33 | - Coordinates are used in the most common order: *x, y*. 34 | - There is no *direct* mode, just use the *CLI* mode. 35 | - The *standard* plane is now known as the *CLI* plane. 36 | - The `*Options` structs are replaced by `*Builder`s. 37 | 38 | [`3.0.11`]: https://github.com/dankamongmen/notcurses/releases/tag/v3.0.11 39 | -------------------------------------------------------------------------------- /check.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # 3 | # Verifies multi-feature compilation, test runs, and documentation build. 4 | 5 | set -e # stops on error 6 | 7 | MSRV="1.65.0" # sync with README.md, Cargo.toml & .github/workflows/check.yml 8 | RCMD="rustup -v run $MSRV" 9 | 10 | rustup override set $MSRV 11 | 12 | # install additional targets 13 | # 14 | # (1) x86_64-unknown-linux-gnu 15 | rustup target add x86_64-unknown-linux-gnu 16 | # 17 | # (2) x86_64-pc-windows-msvc 18 | rustup target add x86_64-pc-windows-msvc 19 | # 20 | # (3) x86_64-apple-darwin 21 | rustup target add x86_64-apple-darwin 22 | # 23 | # (5) Bare x86_64, softfloat, 64-bit, no_std 24 | # https://doc.rust-lang.org/nightly/rustc/platform-support/x86_64-unknown-none.html 25 | rustup target add x86_64-unknown-none # tier 2 (_64) 26 | # 27 | # (6) Linux i686, 32-bit, std, little-endian, (kernel 3.2+, glibc 2.17+) 28 | # may need to install libc6-dev-amd64-i386-cross for testing 29 | rustup target add i686-unknown-linux-gnu # tier 1 (_686) 30 | # 31 | # (7) Bare ARM64, hardfloat, 64-bit, no_std, little-endian, A64 set, (M1, M2 processors) 32 | rustup target add aarch64-unknown-none # tier 2 (_aarch) 33 | # 34 | # (8) Bare ARMv7-M, 32-bit, no_std, little-endian, Thumb set, (Cortex-M processors) 35 | rustup target add thumbv7m-none-eabi # tier 2 (_thumb) 36 | 37 | 38 | # check 39 | cmd="$RCMD cargo c"; echo "std, safe\n$ " $cmd; $cmd 40 | # cmd="$RCMD cargo cu"; echo "std, unsafe\n$" $cmd; $cmd 41 | # cmd="$RCMD cargo cn"; echo "no_std, safe\n$" $cmd; $cmd 42 | 43 | # check additional targets 44 | # cmd="$RCMD cargo cuT1"; echo "std, unsafe\n$" $cmd; $cmd 45 | # cmd="$RCMD cargo cuT2"; echo "std, unsafe\n$" $cmd; $cmd 46 | # cmd="$RCMD cargo cuT3"; echo "std, unsafe\n$" $cmd; $cmd 47 | # cmd="$RCMD cargo cnuT5"; echo "no_std, no-alloc, unsafe\n$" $cmd; $cmd 48 | # cmd="$RCMD cargo cuT6"; echo "std, unsafe\n$" $cmd; $cmd 49 | # cmd="$RCMD cargo cnuT7"; echo "no_std, no-alloc, unsafe\n$" $cmd; $cmd 50 | # cmd="$RCMD cargo cnuT8"; echo "no_std, no-alloc, unsafe\n$" $cmd; $cmd 51 | 52 | # test 53 | cmd="$RCMD cargo t"; echo "tests\n$" $cmd; $cmd 54 | # cmd="$RCMD cargo tu"; echo "tests, unsafe\n$" $cmd; $cmd 55 | 56 | # docs 57 | cmd="cargo +nightly nd"; echo "docs\n$" $cmd; $cmd 58 | -------------------------------------------------------------------------------- /examples/cli.rs: -------------------------------------------------------------------------------- 1 | // noturses::examples::cli 2 | 3 | use notcurses::*; 4 | 5 | fn main() -> NotcursesResult<()> { 6 | let nc = Notcurses::new_cli()?; 7 | let mut cli = nc.cli_plane()?; 8 | 9 | putstrln![cli, "{cli:?}"]?; 10 | 11 | cli.set_fg(0xDE935F); 12 | putstr![cli, "·←cursor:{} ", cli.cursor()]?; 13 | putstrln![cli, "·← cursor {}", cli.cursor()]?; 14 | cli.unset_fg(); 15 | 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /examples/hello-world.rs: -------------------------------------------------------------------------------- 1 | use notcurses::*; 2 | 3 | fn main() -> NotcursesResult<()> { 4 | let nc = Notcurses::new_cli()?; 5 | let mut cli = nc.cli_plane()?; 6 | cli.putstrln("\nhello world!")?; 7 | cli.render()?; 8 | Ok(()) 9 | } 10 | -------------------------------------------------------------------------------- /examples/info.rs: -------------------------------------------------------------------------------- 1 | // notcurses::examples::info 2 | 3 | use notcurses::*; 4 | 5 | fn main() -> NotcursesResult<()> { 6 | let nc = Notcurses::new_cli()?; 7 | let mut cli = nc.cli_plane()?; 8 | 9 | let caps = nc.capabilities(); 10 | let styles = nc.supported_styles(); 11 | 12 | putstrln!(cli, "\n{caps:#?}")?; 13 | putstrln!( 14 | cli, 15 | "\nSupported styles: {}.", 16 | styles.to_string().replace(" ", ", ") 17 | )?; 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /examples/input.rs: -------------------------------------------------------------------------------- 1 | // noturses::examples::input 2 | 3 | use notcurses::*; 4 | use std::time::Instant; 5 | use std::{thread::sleep, time::Duration}; 6 | 7 | fn main() -> NotcursesResult<()> { 8 | let nc = Notcurses::new()?; 9 | nc.mice_enable(MiceEvents::All)?; 10 | 11 | let mut plane = Plane::new(&nc)?; 12 | plane.set_scrolling(true); 13 | 14 | // blocking 15 | 16 | putstrln!(+render plane, 17 | "Waiting for a blocking input event. Do anything to continue:" 18 | )?; 19 | 20 | let event = nc.get_event()?; 21 | putstrln![+render plane, "{event:?}"]?; 22 | 23 | // non-blocking 24 | 25 | putstrln!(+render plane, 26 | "\n{0}\nStarting non-blocking event loop. Press `F01` to exit:\n{}\n", 27 | "-".repeat(50) 28 | )?; 29 | 30 | let mut counting_time = Instant::now(); 31 | loop { 32 | let event = nc.poll_event()?; 33 | 34 | if event.received() { 35 | putstrln![+render plane, "\n{event:?}"]?; 36 | 37 | if event.is_key(Key::F01) { 38 | putstr![+render plane, "\nBye!"]?; 39 | sleep(Duration::from_millis(500)); 40 | for _ in 0..3 { 41 | putstr![+render plane, " ."]?; 42 | sleep(Duration::from_millis(50)); 43 | } 44 | sleep(Duration::from_millis(250)); 45 | break; 46 | } 47 | } 48 | 49 | // do other things in-between 50 | if counting_time.elapsed().as_millis() > 100 { 51 | putstr![+render plane, "."]?; 52 | counting_time = Instant::now() 53 | } 54 | } 55 | 56 | Ok(()) 57 | } 58 | -------------------------------------------------------------------------------- /examples/plane.rs: -------------------------------------------------------------------------------- 1 | // notcurses::examples::plane 2 | // 3 | //! 4 | // 5 | 6 | #![allow(unused_variables, unused_mut)] 7 | 8 | use notcurses::*; 9 | 10 | fn main() -> NotcursesResult<()> { 11 | let nc = Notcurses::new_cli()?; 12 | 13 | // # constructors 14 | 15 | // create a root plane at (1, 1), with a child at (2, 2) 16 | let mut rootp = Plane::new_at(&nc, (1, 1))?; 17 | let child = rootp.new_child_at((2, 2))?; 18 | 19 | // check their position relative to their parent 20 | assert_eq![rootp.position(), Position::new(1, 1)]; 21 | assert_eq![child.position(), Position::new(2, 2)]; 22 | 23 | // check their position relative to the root of their pile 24 | assert_eq![rootp.root_position(), Position::new(1, 1)]; // same for a root plane 25 | assert_eq![child.root_position(), Position::new(3, 3)]; 26 | 27 | // # translate position coordinates 28 | 29 | // create a square of Size::new(5, 5) at Position::new(10, 10) 30 | let size = Size::new(5, 5); 31 | let top_left = Position::new(10, 10); 32 | let p1 = Plane::new_sized_at(&nc, size, top_left)?; 33 | 34 | // check top-left and bottom-right square coordinates are inside the plane: 35 | assert_eq![p1.translate_root(top_left), (Position::new(0, 0), true)]; 36 | assert_eq![p1.translate_root((14, 14)), (Position::new(4, 4), true)]; 37 | // assert_eq![p1.translate_root(top_left + size -1), (Position::new(4, 4), true)]; 38 | 39 | // some other coordinates outside the plane: 40 | assert_eq![p1.translate_root((2, 2)), (Position::new(-8, -8), false)]; 41 | assert_eq![p1.translate_root((20, 20)), (Position::new(10, 10), false)]; 42 | 43 | // # cursor 44 | // ... 45 | 46 | // # strings 47 | // let mut p1 = Plane::new(&nc)?; 48 | let mut p1 = Plane::new_sized(&nc, (4, 4))?; 49 | p1.set_scrolling(true); 50 | 51 | assert_eq!["hello world".len() as u32, p1.putstr("hello world")?]; 52 | 53 | p1.render()?; 54 | 55 | Ok(()) 56 | } 57 | -------------------------------------------------------------------------------- /examples/visual.rs: -------------------------------------------------------------------------------- 1 | // notcurses::examples::visual 2 | // 3 | //! 4 | // 5 | 6 | #![allow(unused_mut, unused_variables)] 7 | 8 | use notcurses::*; 9 | use rand::{distributions::Uniform, Rng}; 10 | use std::{thread::sleep, time::Duration}; 11 | 12 | const H: u32 = 20; 13 | const W: u32 = 10; 14 | const NUMPIX: usize = (H * W) as usize; 15 | 16 | fn main() -> NotcursesResult<()> { 17 | let nc = Notcurses::new_cli()?; 18 | 19 | // Create a byte buffer with random rgba pixels: 20 | let mut rng = rand::thread_rng(); 21 | let range = Uniform::from(50..=200); 22 | let mut rgba_buf = Vec::::with_capacity(NUMPIX * 4); 23 | for _ in 0..=NUMPIX { 24 | rgba_buf.push(rng.sample(&range)); 25 | rgba_buf.push(rng.sample(&range)); 26 | rgba_buf.push(rng.sample(&range)); 27 | rgba_buf.push(255); 28 | } 29 | 30 | // Create a visual from the rgba buffer: 31 | let mut visual = Visual::from_rgba(&nc, rgba_buf.as_slice(), (W, H))?; 32 | visual.set_blitter_pixel(); 33 | 34 | // Blit the visual to a new plane: 35 | let mut new_plane = visual.blit(&nc)?; 36 | new_plane.render()?; 37 | sleep(Duration::from_millis(1000)); 38 | 39 | // Blit the visual to a pre-existing plane: 40 | let mut existing_plane = Plane::builder().position((0, 25)).build(&nc)?; 41 | visual.blit_plane(&nc, &mut existing_plane)?; 42 | existing_plane.render()?; 43 | sleep(Duration::from_millis(1000)); 44 | 45 | // Blit the visual into a new child plane: 46 | let mut parent_plane = Plane::builder().position((10, 50)).build(&nc)?; 47 | let mut child = visual.blit_child(&nc, &mut parent_plane)?; 48 | parent_plane.render()?; 49 | // child.render()?; 50 | sleep(Duration::from_millis(1000)); 51 | 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /src/color/alpha.rs: -------------------------------------------------------------------------------- 1 | // notcurses::color::alpha 2 | // 3 | //! 4 | // 5 | 6 | /// Alpha information, part of a [`Channel`][super::Channel]. 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 8 | pub enum Alpha { 9 | /// Indicates a [`Cell`][crate::plane::Cell]'s foreground or background color 10 | /// is used unchanged. 11 | /// 12 | /// This is the default. 13 | Opaque, 14 | 15 | /// Indicates a [`Cell`][crate::plane::Cell]'s foreground or background color 16 | /// is derived entirely from the `Cell`s underneath it. 17 | Transparent, 18 | 19 | /// Indicates a [`Cell`][crate::plane::Cell]'s foreground or background color will 20 | /// be a composite between its color and the `Cell`s' corresponding colors. 21 | Blend, 22 | 23 | /// Indicates the foreground color will be high-contrast, 24 | /// relative to the computed background. 25 | /// 26 | /// The background cannot be high-contrast. 27 | HighContrast, 28 | } 29 | 30 | mod core_impls { 31 | use super::Alpha; 32 | use crate::sys::{c_api::NcAlpha_u32, NcAlpha}; 33 | use core::fmt; 34 | 35 | impl Default for Alpha { 36 | fn default() -> Self { 37 | Self::Opaque 38 | } 39 | } 40 | 41 | impl fmt::Display for Alpha { 42 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 43 | write!( 44 | f, 45 | "{}", 46 | match self { 47 | Alpha::Opaque => "Opaque", 48 | Alpha::Transparent => "Transparent", 49 | Alpha::Blend => "Blend", 50 | Alpha::HighContrast => "HighContrast", 51 | } 52 | ) 53 | } 54 | } 55 | 56 | // 57 | 58 | impl From for Alpha { 59 | fn from(nc: NcAlpha) -> Alpha { 60 | match nc { 61 | NcAlpha::Opaque => Alpha::Opaque, 62 | NcAlpha::Transparent => Alpha::Transparent, 63 | NcAlpha::Blend => Alpha::Blend, 64 | NcAlpha::HighContrast => Alpha::HighContrast, 65 | } 66 | } 67 | } 68 | impl From for NcAlpha { 69 | fn from(alpha: Alpha) -> NcAlpha { 70 | match alpha { 71 | Alpha::Opaque => NcAlpha::Opaque, 72 | Alpha::Transparent => NcAlpha::Transparent, 73 | Alpha::Blend => NcAlpha::Blend, 74 | Alpha::HighContrast => NcAlpha::HighContrast, 75 | } 76 | } 77 | } 78 | 79 | impl From for Alpha { 80 | fn from(ncu: NcAlpha_u32) -> Alpha { 81 | NcAlpha::from(ncu).into() 82 | } 83 | } 84 | impl From for NcAlpha_u32 { 85 | fn from(align: Alpha) -> NcAlpha_u32 { 86 | NcAlpha::from(align).into() 87 | } 88 | } 89 | } 90 | 91 | impl Alpha { 92 | /// Displays the short name identifier of the alpha value. 93 | pub fn display_short(&self) -> &str { 94 | match self { 95 | Alpha::Blend => "B", 96 | Alpha::HighContrast => "H", 97 | Alpha::Opaque => "O", 98 | Alpha::Transparent => "T", 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/color/channel.rs: -------------------------------------------------------------------------------- 1 | // notcurses::color::channel 2 | // 3 | //! 4 | // 5 | 6 | use crate::{ 7 | color::{Alpha, Rgb}, 8 | sys::NcChannel, 9 | }; 10 | 11 | /// The [`Rgb`] + [`Alpha`] of a [`Cell`][crate::plane::Cell]'s background or foreground. 12 | #[derive(Clone, Copy, Default, PartialEq, Eq)] 13 | #[repr(transparent)] 14 | pub struct Channel { 15 | pub nc: NcChannel, 16 | } 17 | 18 | mod core_impls { 19 | use super::*; 20 | use core::fmt::{self, Write as _}; 21 | 22 | #[rustfmt::skip] 23 | impl fmt::Display for Channel { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | let mut s = String::new(); 26 | 27 | if self.is_rgb() { 28 | let _ = write![s, "rgb:{:02X}_{:02X}_{:02X}", self.r(), self.g(), self.b()]; 29 | } else if self.is_palindex() { 30 | let _ = write![s, "palindex:{:03}", self.palindex()]; 31 | } else { 32 | s += "defaultcolor"; 33 | } 34 | write!(f, "{}+{}", s, self.alpha().display_short()) 35 | } 36 | } 37 | 38 | #[rustfmt::skip] 39 | impl fmt::Debug for Channel { 40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 41 | // s += &format![ " (b{:08b}+x{:06X})", (self.nc.0 >> 24), (self.nc.0 & 0xFFFFFF) ]; 42 | write!(f, "Channel {{{}}}", self) 43 | } 44 | } 45 | 46 | // 47 | 48 | impl From for NcChannel { 49 | fn from(channel: Channel) -> NcChannel { 50 | channel.nc 51 | } 52 | } 53 | 54 | impl From for Channel { 55 | fn from(nc: NcChannel) -> Channel { 56 | Self { nc } 57 | } 58 | } 59 | 60 | // 61 | 62 | /// Helper for `impl From<{int} | ({int}, …) | [{int; …}]> for Channel`. 63 | macro_rules! impl_from_int_tuple_array { 64 | ($( $int:ty ),+) => { 65 | $( impl_from_int_tuple_array![single: $int]; )+ 66 | }; 67 | (single: $int:ty) => { 68 | impl From<($int, $int, $int)> for Channel { 69 | /// Performs a saturating cast to `[u8; 3]`. 70 | fn from(tuple: ($int, $int, $int)) -> Channel { 71 | let arr_u8 = [ 72 | tuple.0.clamp(0, <$int>::MAX) as u8, 73 | tuple.1.clamp(0, <$int>::MAX) as u8, 74 | tuple.2.clamp(0, <$int>::MAX) as u8, 75 | ]; 76 | Self::from_rgb(arr_u8) 77 | } 78 | } 79 | impl From<[$int; 3]> for Channel { 80 | /// Performs a saturating cast to `[u8; 3]`. 81 | fn from(arr: [$int; 3]) -> Channel { 82 | Self::from((arr[0], arr[1], arr[2])) 83 | } 84 | } 85 | impl From<$int> for Channel { 86 | /// Performs a saturating cast to `u32` 87 | /// and then extracts the components from the first three bytes. 88 | fn from(int: $int) -> Channel { 89 | NcChannel::from_rgb(int.clamp(0, <$int>::MAX) as u32).into() 90 | } 91 | } 92 | }; 93 | } 94 | impl_from_int_tuple_array!(i8, u8, i16, u16, i32, u32, i64, u64, isize, usize); 95 | } 96 | 97 | /// # Constructors 98 | impl Channel { 99 | pub fn new() -> Channel { 100 | NcChannel::new().into() 101 | } 102 | 103 | /// Creates a new channel with the default color. 104 | pub fn with_default() -> Channel { 105 | NcChannel::with_default().into() 106 | } 107 | 108 | pub fn from_rgb(rgb: impl Into) -> Channel { 109 | NcChannel::from_rgb(rgb.into()).into() 110 | } 111 | 112 | pub fn from_rgb_alpha(rgb: impl Into, alpha: Alpha) -> Channel { 113 | NcChannel::from_rgb_alpha(rgb.into(), alpha.into()).into() 114 | } 115 | } 116 | 117 | /// # Default color methods 118 | impl Channel { 119 | /// Is this channel using the default color? (vs. RGB or palette indexed). 120 | pub fn is_default(&self) -> bool { 121 | self.nc.default_p() 122 | } 123 | 124 | /// (Un)Sets the usage of the default color. 125 | /// 126 | /// Setting default to true also marks the channel as [`Opaque`][Alpha::Opaque]. 127 | pub fn set_default(&mut self, default: bool) { 128 | if default { 129 | self.nc.set_default(); 130 | } else { 131 | self.nc.set_not_default(); 132 | } 133 | } 134 | } 135 | 136 | /// # Alpha and RGB methods 137 | impl Channel { 138 | /// Gets the Alpha. 139 | pub fn alpha(&self) -> Alpha { 140 | self.nc.alpha().into() 141 | } 142 | 143 | /// Sets the Alpha. 144 | /// 145 | /// Also marks the channel as NOT using the “default color”. 146 | pub fn set_alpha(&mut self, alpha: Alpha) { 147 | self.nc.set_alpha(alpha); 148 | } 149 | 150 | /// Is this channel using RGB color? (vs. default or palette indexed). 151 | pub fn is_rgb(&self) -> bool { 152 | self.nc.rgb_p() 153 | } 154 | 155 | /// Gets the RGB values. 156 | pub fn rgb(&self) -> Rgb { 157 | self.nc.rgb().into() 158 | } 159 | 160 | /// Gets the red color component. 161 | pub fn r(&self) -> u8 { 162 | self.nc.r() 163 | } 164 | 165 | /// Gets the green color component. 166 | pub fn g(&self) -> u8 { 167 | self.nc.g() 168 | } 169 | 170 | /// Gets the blue color component. 171 | pub fn b(&self) -> u8 { 172 | self.nc.b() 173 | } 174 | 175 | /// Sets the RGB value. 176 | /// 177 | /// Also marks the channel as NOT using the “default color”. 178 | pub fn set_rgb(&mut self, rgb: impl Into) { 179 | self.nc.set_rgb(rgb.into()); 180 | } 181 | 182 | /// Sets the red color component. 183 | pub fn set_r(&mut self, red: impl Into) { 184 | self.nc.set_r(red.into()); 185 | } 186 | 187 | /// Sets the green color component. 188 | pub fn set_g(&mut self, green: impl Into) { 189 | self.nc.set_r(green.into()); 190 | } 191 | 192 | /// Sets the blue color component. 193 | pub fn set_b(&mut self, blue: impl Into) { 194 | self.nc.set_b(blue.into()); 195 | } 196 | } 197 | 198 | /// # Indexed palette methods 199 | impl Channel { 200 | /// Is this channel using indexed palette colors? (vs. default or RGB) 201 | pub fn is_palindex(&self) -> bool { 202 | self.nc.palindex_p() 203 | } 204 | 205 | /// Gets the palette index from the channel. 206 | pub fn palindex(&self) -> u8 { 207 | self.nc.palindex() 208 | } 209 | 210 | /// Sets the palette index of the channel. 211 | /// 212 | /// Also marks the channel as [`Opaque`][Alpha::Opaque]. 213 | pub fn set_palindex(&mut self, index: impl Into) { 214 | self.nc.set_palindex(index.into()); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/color/channels.rs: -------------------------------------------------------------------------------- 1 | // notcurses::color::channels 2 | // 3 | //! 4 | // 5 | 6 | use crate::{ 7 | color::{Alpha, Channel, Rgb}, 8 | sys::{c_api::NcChannels_u64, NcChannels}, 9 | }; 10 | 11 | /// A pair of both foreground and background [`Channel`]s. 12 | #[derive(Clone, Copy, Default, PartialEq, Eq)] 13 | #[repr(transparent)] 14 | pub struct Channels { 15 | pub nc: NcChannels, 16 | } 17 | 18 | mod core_impls { 19 | use super::*; 20 | use core::fmt; 21 | 22 | impl fmt::Display for Channels { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | let (fg, bg) = self.into(); 25 | write!(f, "[{fg}, {bg}]") 26 | } 27 | } 28 | impl fmt::Debug for Channels { 29 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 30 | let (fg, bg) = self.into(); 31 | write!(f, "Channels {{ fg: {fg:?}, bg: {bg:?} }}") 32 | } 33 | } 34 | 35 | impl From for NcChannels { 36 | fn from(c: Channels) -> NcChannels { 37 | c.nc 38 | } 39 | } 40 | 41 | impl From for Channels { 42 | fn from(nc: NcChannels) -> Channels { 43 | Self { nc } 44 | } 45 | } 46 | 47 | impl From for Channels { 48 | fn from(nc_u64: NcChannels_u64) -> Channels { 49 | NcChannels::from(nc_u64).into() 50 | } 51 | } 52 | 53 | /// Converts a `Channels` into a (fg, bg) `Channel` tuple. 54 | impl From for (Channel, Channel) { 55 | fn from(c: Channels) -> (Channel, Channel) { 56 | (c.fg(), c.bg()) 57 | } 58 | } 59 | impl From<&Channels> for (Channel, Channel) { 60 | fn from(c: &Channels) -> (Channel, Channel) { 61 | (c.fg(), c.bg()) 62 | } 63 | } 64 | 65 | /// Helper for `impl From<{int} | ({int}, …) | [{int; …}]> for Channels`. 66 | macro_rules! impl_from_int_tuple_array { 67 | ($( $int:ty ),+) => { 68 | $( impl_from_int_tuple_array![single: $int]; )+ 69 | }; 70 | (single: $i:ty) => { 71 | /* different fg & bg channels */ 72 | 73 | impl From<($i, $i, $i, $i, $i, $i)> for Channels { 74 | /// New `Channels` from different foreground & background (r, g, b, r, g, b). 75 | /// 76 | /// Performs a saturating cast to `[u8; 3], [u8; 3]`. 77 | fn from(tuple: ($i, $i, $i, $i, $i, $i)) -> Channels { 78 | let fg_arr_u8 = [ 79 | tuple.0.clamp(0, <$i>::MAX) as u8, 80 | tuple.1.clamp(0, <$i>::MAX) as u8, 81 | tuple.2.clamp(0, <$i>::MAX) as u8, 82 | ]; 83 | let bg_arr_u8 = [ 84 | tuple.3.clamp(0, <$i>::MAX) as u8, 85 | tuple.4.clamp(0, <$i>::MAX) as u8, 86 | tuple.5.clamp(0, <$i>::MAX) as u8, 87 | ]; 88 | Self::from_rgb(fg_arr_u8, bg_arr_u8) 89 | } 90 | } 91 | impl From<(($i, $i, $i), ($i, $i, $i))> for Channels { 92 | /// New `Channels` from different foreground & background ((r, g, b), (r, g, b)). 93 | /// 94 | /// Performs a saturating cast to `[u8; 3], [u8; 3]`. 95 | fn from(tuple: (($i, $i, $i), ($i, $i, $i))) -> Channels { 96 | Self::from(( 97 | tuple.0.0, 98 | tuple.0.1, 99 | tuple.0.2, 100 | tuple.1.0, 101 | tuple.1.1, 102 | tuple.1.2, 103 | )) 104 | } 105 | } 106 | impl From<[$i; 6]> for Channels { 107 | /// New `Channels` from different foreground & background [r, g, b, r, g, b]). 108 | /// 109 | /// Performs a saturating cast to `[u8; 3], [u8; 3]`. 110 | fn from(arr: [$i; 6]) -> Channels { 111 | Self::from(( 112 | arr[0], 113 | arr[1], 114 | arr[2], 115 | arr[3], 116 | arr[4], 117 | arr[5], 118 | )) 119 | } 120 | } 121 | impl From<[[$i; 3]; 2]> for Channels { 122 | /// New `Channels` from different foreground & background [[r, g, b], [r, g, b]]). 123 | /// 124 | /// Performs a saturating cast to `[u8; 3], [u8; 3]`. 125 | fn from(arr: [[$i; 3]; 2]) -> Channels { 126 | Self::from(( 127 | arr[0][0], 128 | arr[0][1], 129 | arr[0][2], 130 | arr[1][0], 131 | arr[1][1], 132 | arr[1][2], 133 | )) 134 | } 135 | } 136 | 137 | /* same bg & fg channels */ 138 | 139 | impl From<($i, $i, $i)> for Channels { 140 | /// New `Channels` from same foreground & background (r, g, b). 141 | /// 142 | /// Performs a saturating cast to `[u8; 3], [u8; 3]`. 143 | fn from(tuple: ($i, $i, $i)) -> Channels { 144 | let arr_u8 = [ 145 | tuple.0.clamp(0, <$i>::MAX) as u8, 146 | tuple.1.clamp(0, <$i>::MAX) as u8, 147 | tuple.2.clamp(0, <$i>::MAX) as u8, 148 | ]; 149 | Self::from_rgb_both(arr_u8) 150 | } 151 | } 152 | impl From<[$i; 3]> for Channels { 153 | /// New `Channels` from same foreground & background (r, g, b). 154 | /// 155 | /// Performs a saturating cast to `[u8; 3], [u8; 3]`. 156 | fn from(arr: [$i; 3]) -> Channels { 157 | Self::from((arr[0], arr[1], arr[2])) 158 | } 159 | } 160 | 161 | /* from impl Into */ 162 | 163 | impl From<($i, $i)> for Channels { 164 | /// New `Channels` from a pair of (fg, bg) impl Into<`Channel`>'s. 165 | fn from(tuple: ($i, $i)) -> Channels { 166 | Channels::combine(tuple.0, tuple.1) 167 | } 168 | } 169 | impl From<[$i; 2]> for Channels { 170 | /// New `Channels` from a pair of [fg, bg] impl Into<`Channel`>'s. 171 | fn from(arr: [$i; 2]) -> Channels { 172 | Channels::combine(arr[0], arr[1]) 173 | } 174 | } 175 | }; 176 | } 177 | impl_from_int_tuple_array!(i8, u8, i16, u16, i32, u32, i64, u64, isize, usize); 178 | } 179 | 180 | /// # constructors 181 | impl Channels { 182 | /// A new pair of channels, set to black and NOT using the default colors. 183 | pub fn new() -> Channels { 184 | NcChannels::new().into() 185 | } 186 | 187 | /// A new pair of channels set to black and using the default colors. 188 | pub fn with_default() -> Channels { 189 | NcChannels::with_default().into() 190 | } 191 | 192 | /// A new pair of channels, using separate foreground and background colors. 193 | pub fn from_rgb(fg_rgb: impl Into, bg_rgb: impl Into) -> Channels { 194 | NcChannels::from_rgb(fg_rgb.into(), bg_rgb.into()).into() 195 | } 196 | 197 | /// A new pair of channels, using the same foreground and background colors. 198 | pub fn from_rgb_both(rgb: impl Into) -> Channels { 199 | NcChannels::from_rgb_both(rgb.into()).into() 200 | } 201 | 202 | /// A new pair of channels, using separate foreground and background colors, 203 | /// and alpha. 204 | pub fn from_rgb_alpha( 205 | fg_rgb: impl Into, 206 | fg_alpha: impl Into, 207 | bg_rgb: impl Into, 208 | bg_alpha: impl Into, 209 | ) -> Channels { 210 | NcChannels::from_rgb_alpha( 211 | fg_rgb.into(), 212 | fg_alpha.into(), 213 | bg_rgb.into(), 214 | bg_alpha.into(), 215 | ) 216 | .into() 217 | } 218 | 219 | /// A new pair of channels, using the same foreground and background colors. 220 | pub fn from_rgb_alpha_both(rgb: impl Into, alpha: impl Into) -> Channels { 221 | NcChannels::from_rgb_alpha_both(rgb.into(), alpha.into()).into() 222 | } 223 | 224 | /// Combines two separate `Channel`s into `Channels`. 225 | pub fn combine(fg: impl Into, bg: impl Into) -> Channels { 226 | NcChannels::combine(fg.into(), bg.into()).into() 227 | } 228 | } 229 | 230 | /// # methods 231 | impl Channels { 232 | /// Gets the foreground channel. 233 | pub fn fg(&self) -> Channel { 234 | self.nc.fchannel().into() 235 | } 236 | 237 | /// Gets the background channel. 238 | pub fn bg(&self) -> Channel { 239 | self.nc.bchannel().into() 240 | } 241 | 242 | /// Sets the foreground channel. 243 | pub fn set_fg(&mut self, fg: impl Into) -> Channels { 244 | self.nc.set_fchannel(fg.into()).into() 245 | } 246 | 247 | /// Sets the background channel. 248 | pub fn set_bg(&mut self, bg: impl Into) -> Channels { 249 | self.nc.set_fchannel(bg.into()).into() 250 | } 251 | 252 | /// Gets the alpha and coloring bits as `Channels`. 253 | pub fn channels(&self) -> Channels { 254 | self.nc.channels().into() 255 | } 256 | 257 | /// Sets the alpha and coloring bits from another `Channels`. 258 | pub fn set_channels(&mut self, other: impl Into) -> Channels { 259 | self.nc.set_channels(other.into()).into() 260 | } 261 | 262 | /// Returns the Channels with the foreground and background's color information 263 | /// swapped, but without touching the rest of the bits. 264 | /// 265 | /// Alpha is retained unless it would lead to an illegal state: 266 | /// [`HighContrast`], [`Transparent`] and [`Blend`] are taken to [`Opaque`] 267 | /// unless the new value is Rgb. 268 | /// 269 | /// [`HighContrast`]: Alpha#HighContrast 270 | /// [`Transparent`]: Alpha#Transparent 271 | /// [`Blend`]: Alpha#Blend 272 | /// [`Opaque`]: Alpha#Opaque 273 | pub fn reverse(&mut self) -> Self { 274 | self.nc.reverse().into() 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/color/mod.rs: -------------------------------------------------------------------------------- 1 | // notcurses::color 2 | // 3 | //! 4 | // 5 | 6 | mod alpha; 7 | mod channel; 8 | mod channels; 9 | mod palette; 10 | mod rgb; 11 | 12 | pub use self::rgb::{Rgb, Rgba}; 13 | pub use alpha::Alpha; 14 | pub use channel::Channel; 15 | pub use channels::Channels; 16 | pub use palette::Palette; 17 | -------------------------------------------------------------------------------- /src/color/palette.rs: -------------------------------------------------------------------------------- 1 | // notcurses::color::palette 2 | // 3 | //! 4 | // 5 | 6 | use crate::{ 7 | color::{Channel, Rgb}, 8 | error::NotcursesResult as Result, 9 | sys::{NcChannel, NcPalette}, 10 | Notcurses, 11 | }; 12 | 13 | /// An array of 256 [`Channel`]s. 14 | #[derive(Clone, PartialEq, Eq)] 15 | pub struct Palette { 16 | nc: *mut NcPalette, 17 | } 18 | 19 | mod core_impls { 20 | use super::{NcPalette, Palette}; 21 | use core::fmt; 22 | 23 | impl Drop for Palette { 24 | fn drop(&mut self) { 25 | if crate::Notcurses::is_initialized() { 26 | self.into_ref_mut().free() 27 | } 28 | } 29 | } 30 | 31 | impl fmt::Debug for Palette { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | write!( 34 | f, 35 | "Palette {{ {:?}, {:?}, {:?}, … }}", 36 | self.get_channel(0), 37 | self.get_channel(1), 38 | self.get_channel(2), 39 | ) 40 | } 41 | } 42 | 43 | impl From<&mut NcPalette> for Palette { 44 | fn from(ncplane: &mut NcPalette) -> Palette { 45 | Palette { 46 | nc: ncplane as *mut NcPalette, 47 | } 48 | } 49 | } 50 | } 51 | 52 | /// # constructors & desconstructors 53 | impl Palette { 54 | /// Creates a new palette, that's initialized with our best 55 | /// knowledge of the currently configured palette. 56 | pub fn new(terminal: &Notcurses) -> Palette { 57 | terminal.with_nc_mut(|nc| Self { 58 | nc: NcPalette::new(nc), 59 | }) 60 | } 61 | 62 | // 63 | 64 | /// Returns a shared reference to the inner [`NcPalette`]. 65 | pub fn into_ref(&self) -> &NcPalette { 66 | unsafe { &*self.nc } 67 | } 68 | 69 | /// Returns an exclusive reference to the inner [`NcPalette`]. 70 | pub fn into_ref_mut(&mut self) -> &mut NcPalette { 71 | unsafe { &mut *self.nc } 72 | } 73 | } 74 | 75 | /// # methods 76 | impl Palette { 77 | /// Attempts to use this palette in the `terminal`. 78 | pub fn use_in(&self, terminal: &Notcurses) -> Result<()> { 79 | terminal.with_nc_mut(|nc| Ok(self.into_ref().r#use(nc)?)) 80 | } 81 | 82 | /// Returns the `Rgb` value at `index`. 83 | pub fn get(&self, index: impl Into) -> Rgb { 84 | self.into_ref().get(index.into()).into() 85 | } 86 | 87 | /// Sets the `Rgb` value at `index`. 88 | pub fn set(&mut self, index: impl Into, rgb: impl Into) { 89 | self.into_ref_mut().set(index.into(), rgb.into()); 90 | } 91 | 92 | /// Returns the channel at `index`. 93 | pub fn get_channel(&self, index: impl Into) -> Channel { 94 | NcChannel::from(self.into_ref().chans[index.into() as usize]).into() 95 | } 96 | 97 | /// Sets the `channel` value at `index`. 98 | pub fn set_channel(&mut self, index: impl Into, channel: impl Into) { 99 | let ncc = NcChannel::from(channel.into()); 100 | self.into_ref_mut().chans[index.into() as usize] = ncc.into(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/color/rgb.rs: -------------------------------------------------------------------------------- 1 | // notcurses::color::rgb 2 | // 3 | //! 4 | // 5 | 6 | use crate::sys::{ 7 | c_api::{NcRgb_u32, NcRgba_u32}, 8 | NcRgb, NcRgba, 9 | }; 10 | 11 | /// A 24-bit RGB value. 12 | #[derive(Clone, Copy, Default, PartialEq, Eq)] 13 | pub struct Rgb(NcRgb); 14 | impl Rgb { 15 | /// New const RGB color. 16 | pub const fn new(r: u8, g: u8, b: u8) -> Self { 17 | Self(NcRgb( 18 | (r as NcRgb_u32) << 16 | (g as NcRgb_u32) << 8 | b as NcRgb_u32, 19 | )) 20 | } 21 | } 22 | 23 | /// A 32-bit RGBA value. 24 | #[derive(Clone, Copy, Default, PartialEq, Eq)] 25 | pub struct Rgba(NcRgba); 26 | impl Rgba { 27 | /// New const RGBA color. 28 | pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { 29 | Self(NcRgba( 30 | (a as NcRgba_u32) << 24 31 | | (r as NcRgba_u32) << 16 32 | | (g as NcRgba_u32) << 8 33 | | b as NcRgba_u32, 34 | )) 35 | } 36 | } 37 | 38 | mod core_impls { 39 | use super::{Rgb, Rgba}; 40 | use crate::sys::{ 41 | c_api::{NcRgb_u32, NcRgba_u32}, 42 | NcRgb, NcRgba, 43 | }; 44 | use core::fmt; 45 | use rgb::{RGB, RGBA}; 46 | 47 | impl fmt::Display for Rgb { 48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 49 | write!(f, "{:06X}", self.0) 50 | } 51 | } 52 | impl fmt::Debug for Rgb { 53 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 54 | write!(f, "Rgb({})", self.0) 55 | } 56 | } 57 | 58 | impl fmt::Display for Rgba { 59 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 60 | write!(f, "{:08X}", self.0) 61 | } 62 | } 63 | impl fmt::Debug for Rgba { 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | write!(f, "Rgb({})", self.0) 66 | } 67 | } 68 | 69 | // 70 | 71 | impl From for Rgb { 72 | fn from(nc: NcRgb) -> Rgb { 73 | Rgb(nc) 74 | } 75 | } 76 | impl From for NcRgb { 77 | fn from(rgb: Rgb) -> NcRgb { 78 | rgb.0 79 | } 80 | } 81 | 82 | impl From for Rgba { 83 | fn from(nc: NcRgba) -> Rgba { 84 | Rgba(nc) 85 | } 86 | } 87 | impl From for NcRgba { 88 | fn from(rgba: Rgba) -> NcRgba { 89 | rgba.0 90 | } 91 | } 92 | 93 | // 94 | 95 | impl From for Rgb { 96 | fn from(ncu: NcRgb_u32) -> Rgb { 97 | Rgb(NcRgb::from(ncu)) 98 | } 99 | } 100 | impl From for NcRgb_u32 { 101 | fn from(rgb: Rgb) -> NcRgb_u32 { 102 | rgb.0.into() 103 | } 104 | } 105 | 106 | impl From for Rgba { 107 | fn from(ncu: NcRgba_u32) -> Rgba { 108 | Rgba(NcRgba::from(ncu)) 109 | } 110 | } 111 | impl From for NcRgba_u32 { 112 | fn from(rgba: Rgba) -> NcRgba_u32 { 113 | rgba.0.into() 114 | } 115 | } 116 | 117 | // 118 | 119 | impl From<[u8; 3]> for Rgb { 120 | fn from(array: [u8; 3]) -> Self { 121 | Rgb(array.into()) 122 | } 123 | } 124 | impl From<&[u8; 3]> for Rgb { 125 | fn from(array: &[u8; 3]) -> Self { 126 | Rgb(array.into()) 127 | } 128 | } 129 | impl From for [u8; 3] { 130 | fn from(rgb: Rgb) -> Self { 131 | rgb.0.into() 132 | } 133 | } 134 | impl From<(u8, u8, u8)> for Rgb { 135 | fn from(tuple: (u8, u8, u8)) -> Self { 136 | Rgb(tuple.into()) 137 | } 138 | } 139 | impl From for (u8, u8, u8) { 140 | fn from(rgb: Rgb) -> Self { 141 | rgb.0.into() 142 | } 143 | } 144 | 145 | impl From<[u8; 4]> for Rgba { 146 | fn from(array: [u8; 4]) -> Self { 147 | Rgba(array.into()) 148 | } 149 | } 150 | impl From<&[u8; 4]> for Rgba { 151 | fn from(array: &[u8; 4]) -> Self { 152 | Rgba(array.into()) 153 | } 154 | } 155 | impl From for [u8; 4] { 156 | fn from(rgba: Rgba) -> Self { 157 | rgba.0.into() 158 | } 159 | } 160 | impl From<(u8, u8, u8, u8)> for Rgba { 161 | fn from(tuple: (u8, u8, u8, u8)) -> Self { 162 | Rgba(tuple.into()) 163 | } 164 | } 165 | impl From for (u8, u8, u8, u8) { 166 | fn from(rgba: Rgba) -> Self { 167 | rgba.0.into() 168 | } 169 | } 170 | 171 | // between Rgb & Rgba 172 | 173 | impl From for Rgba { 174 | #[inline] 175 | fn from(rgb: Rgb) -> Self { 176 | let a: [u8; 3] = rgb.into(); 177 | [a[0], a[1], a[2], 255].into() 178 | } 179 | } 180 | impl From for Rgb { 181 | #[inline] 182 | fn from(rgba: Rgba) -> Self { 183 | let a: [u8; 4] = rgba.into(); 184 | [a[0], a[1], a[2]].into() 185 | } 186 | } 187 | 188 | // for rgb crate 189 | 190 | impl From> for Rgb { 191 | fn from(item: RGB) -> Self { 192 | Self::new(item.r, item.g, item.b) 193 | } 194 | } 195 | impl From for RGB { 196 | fn from(item: Rgb) -> Self { 197 | let a: [u8; 3] = item.into(); 198 | Self::new(a[0], a[1], a[2]) 199 | } 200 | } 201 | 202 | impl From> for Rgba { 203 | fn from(item: RGBA) -> Self { 204 | Self::new(item.r, item.g, item.b, item.a) 205 | } 206 | } 207 | impl From for RGBA { 208 | fn from(item: Rgba) -> Self { 209 | let a: [u8; 4] = item.into(); 210 | Self::new(a[0], a[1], a[2], a[3]) 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // notcurses::error 2 | // 3 | //! 4 | // 5 | 6 | use std::{io::Error as IoError, result}; 7 | 8 | use crate::sys::NcError; 9 | 10 | /// The *Notcurses* result type. 11 | pub type NotcursesResult = result::Result; 12 | 13 | /// The *Notcurses* error type. 14 | #[derive(Debug)] 15 | #[non_exhaustive] 16 | pub enum NotcursesError { 17 | /// A `libnotcurses-sys` error. 18 | NcError(NcError), 19 | 20 | /// An `std::io::Error`. 21 | IoError(IoError), 22 | 23 | /// An error message string. 24 | Message(String), 25 | } 26 | 27 | /// # Methods 28 | impl NotcursesError { 29 | /// Returns a `NotcursesError::Message` already wraped in a `Result`. 30 | pub fn msg(string: &str) -> NotcursesResult { 31 | Err(Self::Message(string.into())) 32 | } 33 | } 34 | 35 | mod core_impls { 36 | use super::{NcError, NotcursesError}; 37 | use core::fmt; 38 | 39 | impl fmt::Display for NotcursesError { 40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 41 | match self { 42 | NotcursesError::NcError(e) => e.fmt(f), 43 | NotcursesError::IoError(e) => e.fmt(f), 44 | NotcursesError::Message(string) => write!(f, "Message: {}", string), 45 | } 46 | } 47 | } 48 | 49 | impl From for NotcursesError { 50 | fn from(e: NcError) -> Self { 51 | Self::NcError(e) 52 | } 53 | } 54 | } 55 | 56 | mod std_impls { 57 | use super::NotcursesError; 58 | 59 | impl std::error::Error for NotcursesError {} 60 | } 61 | -------------------------------------------------------------------------------- /src/input/input.rs: -------------------------------------------------------------------------------- 1 | // notcurses::input::input 2 | // 3 | //! 4 | // 5 | 6 | use crate::{ 7 | input::{InputType, Key, KeyMod, Received}, 8 | Position, 9 | }; 10 | 11 | /// A received input. 12 | #[derive(Clone, Copy, PartialEq, Eq)] 13 | pub struct Input { 14 | /// The received input event. 15 | pub received: Received, 16 | 17 | /// Keyboard modifiers. 18 | pub keymod: KeyMod, 19 | 20 | /// The type of the input. 21 | pub itype: InputType, 22 | 23 | /// The cell position of the event, if defined. 24 | pub cell: Option, 25 | 26 | /// Pixel offset within the cell, if defined. 27 | pub offset: Option, 28 | } 29 | 30 | mod core_impls { 31 | use super::{Input, Position}; 32 | use crate::sys::{NcInput, NcReceived}; 33 | use core::fmt; 34 | 35 | impl fmt::Display for Input { 36 | #[rustfmt::skip] 37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 | let cell = if let Some(c) = self.cell { c.to_string() } else { "None".into() }; 39 | let offset = if let Some(o) = self.offset { o.to_string() } else { "None".into() }; 40 | write!(f, 41 | "{} {} {} {} {}", 42 | self.received, self.keymod, self.itype, cell, offset, 43 | ) 44 | } 45 | } 46 | 47 | impl fmt::Debug for Input { 48 | #[rustfmt::skip] 49 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | let cell = if let Some(c) = self.cell { c.to_string() } else { "None".into() }; 51 | let offset = if let Some(o) = self.offset { o.to_string() } else { "None".into() }; 52 | write!(f, 53 | "Input {{received:{} mod:{} type:{} cell:{} offset:{} }}", 54 | self.received, self.keymod, self.itype, cell, offset, 55 | ) 56 | } 57 | } 58 | 59 | impl From<(NcReceived, NcInput)> for Input { 60 | fn from(received_input: (NcReceived, NcInput)) -> Input { 61 | let (received, input) = received_input; 62 | 63 | // cell position & offset is only relevant for mouse events 64 | let (mut cell, mut offset) = (None, None); 65 | if let NcReceived::Key(k) = received { 66 | if k.is_mouse() { 67 | if input.y != -1 { 68 | // != undefined 69 | cell = Some(Position::new(input.x, input.y)); 70 | } 71 | if input.ypx != -1 { 72 | offset = Some(Position::new(input.xpx, input.ypx)); 73 | } 74 | } 75 | }; 76 | 77 | Input { 78 | received: received.into(), 79 | keymod: input.modifiers.into(), 80 | itype: input.evtype.into(), 81 | cell, 82 | offset, 83 | } 84 | } 85 | } 86 | } 87 | 88 | /// # methods 89 | impl Input { 90 | /* Received */ 91 | 92 | /// Returns `true` if any actual input has been received. 93 | #[inline] 94 | pub const fn received(&self) -> bool { 95 | !matches![self.received, Received::NoInput] 96 | } 97 | 98 | /// Returns `true` if some [`Key`] has been received. 99 | #[inline] 100 | pub const fn some_key(&self) -> bool { 101 | matches!(self.received, Received::Key(_)) 102 | } 103 | 104 | /// Returns `true` if a specific [`Key`] has been received. 105 | #[inline] 106 | pub fn is_key(&self, key: Key) -> bool { 107 | self.received.is_key(key) 108 | } 109 | 110 | /// Returns `true` if some `character` has been received. 111 | #[inline] 112 | pub const fn some_char(&self) -> bool { 113 | matches!(self.received, Received::Char(_)) 114 | } 115 | 116 | /// Returns `true` if a specific `character` has been received. 117 | #[inline] 118 | pub const fn is_char(&self, character: char) -> bool { 119 | self.received.is_char(character) 120 | } 121 | 122 | /* InputType */ 123 | 124 | /// Returns `true` if this' a `Press` input type. 125 | #[inline] 126 | pub const fn is_press(&self) -> bool { 127 | self.itype.is_press() 128 | } 129 | 130 | /// Returns `true` if this' a `Repeat` input type. 131 | #[inline] 132 | pub const fn is_repeat(&self) -> bool { 133 | self.itype.is_repeat() 134 | } 135 | 136 | /// Returns `true` if this' a `Release` input type. 137 | #[inline] 138 | pub const fn is_release(&self) -> bool { 139 | self.itype.is_release() 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/input/input_type.rs: -------------------------------------------------------------------------------- 1 | // notcurses::input::input_type 2 | // 3 | //! 4 | // 5 | 6 | /// The type of the [`Input`][crate::Input] event. 7 | /// 8 | /// Note: *Unknown* and *Press* are considered equivalent. 9 | #[derive(Clone, Copy, PartialEq, Eq)] 10 | pub enum InputType { 11 | /// 12 | Unknown, 13 | 14 | /// 15 | Press, 16 | 17 | /// 18 | Repeat, 19 | 20 | /// 21 | Release, 22 | } 23 | 24 | impl InputType { 25 | /// Returns `true` if it's a `Press` input type. 26 | pub const fn is_press(&self) -> bool { 27 | matches!(self, Self::Press) 28 | } 29 | 30 | /// Returns `true` if it's a `Repeat` input type. 31 | pub const fn is_repeat(&self) -> bool { 32 | matches!(self, Self::Repeat) 33 | } 34 | 35 | /// Returns `true` if it's a `Release` input type. 36 | pub const fn is_release(&self) -> bool { 37 | matches!(self, Self::Release) 38 | } 39 | } 40 | 41 | mod core_impls { 42 | use super::InputType; 43 | use crate::sys::{c_api::NcInputType_u32, NcInputType}; 44 | use core::fmt; 45 | 46 | impl Default for InputType { 47 | fn default() -> Self { 48 | Self::Unknown 49 | } 50 | } 51 | 52 | impl fmt::Display for InputType { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | write!( 55 | f, 56 | "{}", 57 | match self { 58 | InputType::Unknown => "Unknown", 59 | InputType::Press => "Press", 60 | InputType::Repeat => "Repeat", 61 | InputType::Release => "Release", 62 | } 63 | ) 64 | } 65 | } 66 | 67 | impl fmt::Debug for InputType { 68 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 69 | write!(f, "InputType::{}", self) 70 | } 71 | } 72 | 73 | impl From for InputType { 74 | fn from(nc: NcInputType) -> Self { 75 | match nc { 76 | NcInputType::Unknown => InputType::Unknown, 77 | NcInputType::Press => InputType::Press, 78 | NcInputType::Repeat => InputType::Repeat, 79 | NcInputType::Release => InputType::Release, 80 | } 81 | } 82 | } 83 | impl From for NcInputType { 84 | fn from(me: InputType) -> Self { 85 | match me { 86 | InputType::Unknown => NcInputType::Unknown, 87 | InputType::Press => NcInputType::Press, 88 | InputType::Repeat => NcInputType::Repeat, 89 | InputType::Release => NcInputType::Release, 90 | } 91 | } 92 | } 93 | 94 | impl From for InputType { 95 | fn from(ncu: NcInputType_u32) -> Self { 96 | NcInputType::from(ncu).into() 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/input/key.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::sys::{c_api, NcKey}; 4 | 5 | /// A synthesized [`Received`][crate::Received] input event other than a `char`. 6 | #[repr(transparent)] 7 | #[derive(Clone, Copy, PartialEq, Eq)] 8 | pub struct Key(u32); 9 | 10 | mod core_impls { 11 | use super::{Key, NcKey}; 12 | use core::fmt; 13 | 14 | impl fmt::Display for Key { 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | write!(f, "{}", self.name()) 17 | } 18 | } 19 | 20 | impl fmt::Debug for Key { 21 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 22 | write!(f, "Key::{}", self) 23 | } 24 | } 25 | 26 | impl From for Key { 27 | fn from(nc: NcKey) -> Key { 28 | Key(nc.0) 29 | } 30 | } 31 | 32 | impl From for NcKey { 33 | fn from(k: Key) -> NcKey { 34 | NcKey(k.0) 35 | } 36 | } 37 | 38 | // TEMP 39 | // impl From for u32 { 40 | // fn from(k: NcKey) -> Self { 41 | // k.0 42 | // } 43 | // } 44 | } 45 | 46 | /// # constants 47 | #[allow(non_upper_case_globals)] 48 | impl Key { 49 | pub const Invalid: Key = Key(c_api::NCKEY_INVALID); 50 | /// we received `SIGWINCH`. 51 | pub const Resize: Key = Key(c_api::NCKEY_RESIZE); 52 | pub const Up: Key = Key(c_api::NCKEY_UP); 53 | pub const Right: Key = Key(c_api::NCKEY_RIGHT); 54 | pub const Down: Key = Key(c_api::NCKEY_DOWN); 55 | pub const Left: Key = Key(c_api::NCKEY_LEFT); 56 | pub const Ins: Key = Key(c_api::NCKEY_INS); 57 | pub const Del: Key = Key(c_api::NCKEY_DEL); 58 | pub const Backspace: Key = Key(c_api::NCKEY_BACKSPACE); 59 | pub const PgDown: Key = Key(c_api::NCKEY_PGDOWN); 60 | pub const PgUp: Key = Key(c_api::NCKEY_PGUP); 61 | pub const Home: Key = Key(c_api::NCKEY_HOME); 62 | pub const End: Key = Key(c_api::NCKEY_END); 63 | pub const F00: Key = Key(c_api::NCKEY_F00); 64 | pub const F01: Key = Key(c_api::NCKEY_F01); 65 | pub const F02: Key = Key(c_api::NCKEY_F02); 66 | pub const F03: Key = Key(c_api::NCKEY_F03); 67 | pub const F04: Key = Key(c_api::NCKEY_F04); 68 | pub const F05: Key = Key(c_api::NCKEY_F05); 69 | pub const F06: Key = Key(c_api::NCKEY_F06); 70 | pub const F07: Key = Key(c_api::NCKEY_F07); 71 | pub const F08: Key = Key(c_api::NCKEY_F08); 72 | pub const F09: Key = Key(c_api::NCKEY_F09); 73 | pub const F10: Key = Key(c_api::NCKEY_F10); 74 | pub const F11: Key = Key(c_api::NCKEY_F11); 75 | pub const F12: Key = Key(c_api::NCKEY_F12); 76 | pub const F13: Key = Key(c_api::NCKEY_F13); 77 | pub const F14: Key = Key(c_api::NCKEY_F14); 78 | pub const F15: Key = Key(c_api::NCKEY_F15); 79 | pub const F16: Key = Key(c_api::NCKEY_F16); 80 | pub const F17: Key = Key(c_api::NCKEY_F17); 81 | pub const F18: Key = Key(c_api::NCKEY_F18); 82 | pub const F19: Key = Key(c_api::NCKEY_F19); 83 | pub const F20: Key = Key(c_api::NCKEY_F20); 84 | pub const F21: Key = Key(c_api::NCKEY_F21); 85 | pub const F22: Key = Key(c_api::NCKEY_F22); 86 | pub const F23: Key = Key(c_api::NCKEY_F23); 87 | pub const F24: Key = Key(c_api::NCKEY_F24); 88 | pub const F25: Key = Key(c_api::NCKEY_F25); 89 | pub const F26: Key = Key(c_api::NCKEY_F26); 90 | pub const F27: Key = Key(c_api::NCKEY_F27); 91 | pub const F28: Key = Key(c_api::NCKEY_F28); 92 | pub const F29: Key = Key(c_api::NCKEY_F29); 93 | pub const F30: Key = Key(c_api::NCKEY_F30); 94 | pub const F31: Key = Key(c_api::NCKEY_F31); 95 | pub const F32: Key = Key(c_api::NCKEY_F32); 96 | pub const F33: Key = Key(c_api::NCKEY_F33); 97 | pub const F34: Key = Key(c_api::NCKEY_F34); 98 | pub const F35: Key = Key(c_api::NCKEY_F35); 99 | pub const F36: Key = Key(c_api::NCKEY_F36); 100 | pub const F37: Key = Key(c_api::NCKEY_F37); 101 | pub const F38: Key = Key(c_api::NCKEY_F38); 102 | pub const F39: Key = Key(c_api::NCKEY_F39); 103 | pub const F40: Key = Key(c_api::NCKEY_F40); 104 | pub const F41: Key = Key(c_api::NCKEY_F41); 105 | pub const F42: Key = Key(c_api::NCKEY_F42); 106 | pub const F43: Key = Key(c_api::NCKEY_F43); 107 | pub const F44: Key = Key(c_api::NCKEY_F44); 108 | pub const F45: Key = Key(c_api::NCKEY_F45); 109 | pub const F46: Key = Key(c_api::NCKEY_F46); 110 | pub const F47: Key = Key(c_api::NCKEY_F47); 111 | pub const F48: Key = Key(c_api::NCKEY_F48); 112 | pub const F49: Key = Key(c_api::NCKEY_F49); 113 | pub const F50: Key = Key(c_api::NCKEY_F50); 114 | pub const F51: Key = Key(c_api::NCKEY_F51); 115 | pub const F52: Key = Key(c_api::NCKEY_F52); 116 | pub const F53: Key = Key(c_api::NCKEY_F53); 117 | pub const F54: Key = Key(c_api::NCKEY_F54); 118 | pub const F55: Key = Key(c_api::NCKEY_F55); 119 | pub const F56: Key = Key(c_api::NCKEY_F56); 120 | pub const F57: Key = Key(c_api::NCKEY_F57); 121 | pub const F58: Key = Key(c_api::NCKEY_F58); 122 | pub const F59: Key = Key(c_api::NCKEY_F59); 123 | pub const F60: Key = Key(c_api::NCKEY_F60); 124 | 125 | // ... leave room for function keys. 126 | 127 | pub const Enter: Key = Key(c_api::NCKEY_ENTER); 128 | /// "clear-screen or erase" 129 | pub const Cls: Key = Key(c_api::NCKEY_CLS); 130 | /// down + left on keypad 131 | pub const DLeft: Key = Key(c_api::NCKEY_DLEFT); 132 | pub const DRight: Key = Key(c_api::NCKEY_DRIGHT); 133 | /// up + left on keypad 134 | pub const ULeft: Key = Key(c_api::NCKEY_ULEFT); 135 | pub const URight: Key = Key(c_api::NCKEY_URIGHT); 136 | pub const Center: Key = Key(c_api::NCKEY_CENTER); 137 | pub const Begin: Key = Key(c_api::NCKEY_BEGIN); 138 | pub const Cancel: Key = Key(c_api::NCKEY_CANCEL); 139 | pub const Close: Key = Key(c_api::NCKEY_CLOSE); 140 | pub const Command: Key = Key(c_api::NCKEY_COMMAND); 141 | pub const Copy: Key = Key(c_api::NCKEY_COPY); 142 | pub const Exit: Key = Key(c_api::NCKEY_EXIT); 143 | pub const Print: Key = Key(c_api::NCKEY_PRINT); 144 | pub const Refresh: Key = Key(c_api::NCKEY_REFRESH); 145 | 146 | // these keys aren't generally available outside of the kitty protocol: 147 | 148 | pub const CapsLock: Key = Key(c_api::NCKEY_CAPS_LOCK); 149 | pub const ScrollLock: Key = Key(c_api::NCKEY_SCROLL_LOCK); 150 | pub const NumLock: Key = Key(c_api::NCKEY_NUM_LOCK); 151 | pub const PrintScreen: Key = Key(c_api::NCKEY_PRINT_SCREEN); 152 | pub const Pause: Key = Key(c_api::NCKEY_PAUSE); 153 | pub const Menu: Key = Key(c_api::NCKEY_MENU); 154 | 155 | // media keys, similarly only available through kitty's protocol: 156 | 157 | pub const MediaPlay: Key = Key(c_api::NCKEY_MEDIA_PLAY); 158 | pub const MediaPause: Key = Key(c_api::NCKEY_MEDIA_PAUSE); 159 | pub const MediaPPause: Key = Key(c_api::NCKEY_MEDIA_PPAUSE); 160 | pub const MediaRev: Key = Key(c_api::NCKEY_MEDIA_REV); 161 | pub const MediaStop: Key = Key(c_api::NCKEY_MEDIA_STOP); 162 | pub const MediaFF: Key = Key(c_api::NCKEY_MEDIA_FF); 163 | pub const MediaRewind: Key = Key(c_api::NCKEY_MEDIA_REWIND); 164 | pub const MediaNext: Key = Key(c_api::NCKEY_MEDIA_NEXT); 165 | pub const MediaPrev: Key = Key(c_api::NCKEY_MEDIA_PREV); 166 | pub const MediaRecord: Key = Key(c_api::NCKEY_MEDIA_RECORD); 167 | pub const MediaLVol: Key = Key(c_api::NCKEY_MEDIA_LVOL); 168 | pub const MediaRVol: Key = Key(c_api::NCKEY_MEDIA_RVOL); 169 | pub const MediaMute: Key = Key(c_api::NCKEY_MEDIA_MUTE); 170 | 171 | // modifiers when pressed by themselves. this ordering comes from the Kitty 172 | // keyboard protocol, and mustn't be changed without updating handlers: 173 | 174 | pub const LShift: Key = Key(c_api::NCKEY_LSHIFT); 175 | pub const LCtrl: Key = Key(c_api::NCKEY_LCTRL); 176 | pub const LAlt: Key = Key(c_api::NCKEY_LALT); 177 | pub const LSuper: Key = Key(c_api::NCKEY_LSUPER); 178 | pub const LHyper: Key = Key(c_api::NCKEY_LHYPER); 179 | pub const LMeta: Key = Key(c_api::NCKEY_LMETA); 180 | pub const RShift: Key = Key(c_api::NCKEY_RSHIFT); 181 | pub const RCtrl: Key = Key(c_api::NCKEY_RCTRL); 182 | pub const RAlt: Key = Key(c_api::NCKEY_RALT); 183 | pub const RSuper: Key = Key(c_api::NCKEY_RSUPER); 184 | pub const RHyper: Key = Key(c_api::NCKEY_RHYPER); 185 | pub const RMeta: Key = Key(c_api::NCKEY_RMETA); 186 | /// `AltGr` in european keyboards 187 | pub const L3Shift: Key = Key(c_api::NCKEY_L3SHIFT); 188 | pub const L5Shift: Key = Key(c_api::NCKEY_L5SHIFT); 189 | 190 | // Mouse events. We encode which button was pressed into the number, 191 | // but position information is embedded in the larger ncinput event: 192 | 193 | pub const Motion: Key = Key(c_api::NCKEY_MOTION); 194 | pub const Button1: Key = Key(c_api::NCKEY_BUTTON1); 195 | pub const Button2: Key = Key(c_api::NCKEY_BUTTON2); 196 | pub const Button3: Key = Key(c_api::NCKEY_BUTTON3); 197 | /// scrollwheel up 198 | pub const Button4: Key = Key(c_api::NCKEY_BUTTON4); 199 | /// scrollwheel down 200 | pub const Button5: Key = Key(c_api::NCKEY_BUTTON5); 201 | pub const Button6: Key = Key(c_api::NCKEY_BUTTON6); 202 | pub const Button7: Key = Key(c_api::NCKEY_BUTTON7); 203 | pub const Button8: Key = Key(c_api::NCKEY_BUTTON8); 204 | pub const Button9: Key = Key(c_api::NCKEY_BUTTON9); 205 | pub const Button10: Key = Key(c_api::NCKEY_BUTTON10); 206 | pub const Button11: Key = Key(c_api::NCKEY_BUTTON11); 207 | 208 | /// we received SIGCONT 209 | pub const Signal: Key = Key(c_api::NCKEY_SIGNAL); 210 | 211 | /// Will be returned upon reaching the end of input. 212 | pub const Eof: Key = Key(c_api::NCKEY_EOF); 213 | 214 | // Aliases from the 128 characters common to ASCII+UTF8: 215 | pub const Tab: Key = Key(c_api::NCKEY_TAB); 216 | pub const Esc: Key = Key(c_api::NCKEY_ESC); 217 | pub const Space: Key = Key(c_api::NCKEY_SPACE); 218 | } 219 | 220 | /// # Aliases 221 | #[allow(non_upper_case_globals)] 222 | impl Key { 223 | /// Alias of [`Button4`][Key::Button4] 224 | pub const ScrollUp: Key = Key(c_api::NCKEY_SCROLL_UP); 225 | /// Alias of [`Button5`][Key::Button5] 226 | pub const Scrolldown: Key = Key(c_api::NCKEY_SCROLL_DOWN); 227 | /// Alias of [`Enter`][Key::Enter] 228 | pub const Return: Key = Key(c_api::NCKEY_RETURN); 229 | } 230 | /// # methods 231 | impl Key { 232 | /// Checks whether a number falls in the range of synthesized events. 233 | pub fn is(num: u32) -> bool { 234 | NcKey::is(num) 235 | } 236 | 237 | /// Returns a new `Key` if the provided number falls in the correct range. 238 | pub fn new(num: u32) -> Option { 239 | if Self::is(num) { 240 | Some(Self(num)) 241 | } else { 242 | None 243 | } 244 | } 245 | 246 | // 247 | 248 | /// Returns `true` if it's a function key event. 249 | pub fn is_function(&self) -> bool { 250 | matches!(self.0, c_api::NCKEY_F00..=c_api::NCKEY_F60) 251 | } 252 | 253 | /// Returns `true` if it's a multimedia key event. 254 | pub fn is_media(&self) -> bool { 255 | matches!(self.0, c_api::NCKEY_MEDIA_PLAY..=c_api::NCKEY_MEDIA_MUTE) 256 | } 257 | 258 | /// Returns `true` if it's a mouse event. 259 | pub fn is_mouse(&self) -> bool { 260 | matches!(self.0, c_api::NCKEY_MOTION..=c_api::NCKEY_BUTTON11) 261 | } 262 | 263 | /// Returns `true` if it's a resize event. 264 | pub fn is_resize(&self) -> bool { 265 | matches!(self.0, c_api::NCKEY_RESIZE) 266 | } 267 | 268 | // 269 | 270 | /// Returns the name of the current `Key`. 271 | pub fn name(&self) -> &'static str { 272 | Self::check_name(self.0) 273 | } 274 | 275 | /// Returns the name of the `Key` the number would be. 276 | pub fn check_name(num: u32) -> &'static str { 277 | NcKey::check_name(num) 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/input/key_mod.rs: -------------------------------------------------------------------------------- 1 | // notcurses::input::key_mod 2 | // 3 | //! 4 | // 5 | 6 | use crate::sys::NcKeyMod; 7 | 8 | /// A bitmask of keyboard modifiers. 9 | #[derive(Clone, Copy, PartialEq, Eq)] 10 | pub struct KeyMod(u32); 11 | 12 | /// # Flags 13 | #[allow(non_upper_case_globals)] 14 | impl KeyMod { 15 | /// 16 | pub const Shift: Self = Self(NcKeyMod::Shift.0); 17 | 18 | /// 19 | pub const Alt: Self = Self(NcKeyMod::Alt.0); 20 | 21 | /// 22 | pub const Ctrl: Self = Self(NcKeyMod::Ctrl.0); 23 | 24 | /// 25 | pub const Super: Self = Self(NcKeyMod::Super.0); 26 | 27 | /// 28 | pub const Hyper: Self = Self(NcKeyMod::Hyper.0); 29 | 30 | /// 31 | pub const Meta: Self = Self(NcKeyMod::Meta.0); 32 | 33 | /// 34 | pub const CapsLock: Self = Self(NcKeyMod::CapsLock.0); 35 | 36 | /// 37 | pub const NumLock: Self = Self(NcKeyMod::NumLock.0); 38 | 39 | /// None of the modifiers (all bits set to 0). 40 | pub const None: Self = Self(0); 41 | 42 | /// The modifier mask (all bits set to 1). 43 | pub const Mask: Self = Self(u32::MAX); 44 | } 45 | 46 | mod core_impls { 47 | use super::{KeyMod, NcKeyMod}; 48 | use core::fmt; 49 | 50 | impl Default for KeyMod { 51 | fn default() -> Self { 52 | Self::None 53 | } 54 | } 55 | 56 | impl fmt::Display for KeyMod { 57 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 58 | let mut string = String::new(); 59 | 60 | if self.has_none() { 61 | string += "None "; 62 | } else { 63 | if self.has_capslock() { 64 | string += "CapsLock+"; 65 | } 66 | if self.has_numlock() { 67 | string += "NumLock+"; 68 | } 69 | if self.has_ctrl() { 70 | string += "Ctrl+"; 71 | } 72 | if self.has_shift() { 73 | string += "Shift+"; 74 | } 75 | if self.has_alt() { 76 | string += "Alt+"; 77 | } 78 | if self.has_meta() { 79 | string += "Meta+"; 80 | } 81 | if self.has_super() { 82 | string += "Super+"; 83 | } 84 | if self.has_hyper() { 85 | string += "Hyper+"; 86 | } 87 | } 88 | string.pop(); 89 | 90 | write!(f, "{}", string) 91 | } 92 | } 93 | 94 | impl fmt::Debug for KeyMod { 95 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 96 | write!(f, "KeyMod::{}", self) 97 | } 98 | } 99 | crate::from_primitive![KeyMod, u32]; 100 | crate::unit_impl_ops![bitwise; KeyMod, u32]; 101 | crate::unit_impl_fmt![bases; KeyMod]; 102 | 103 | impl From for KeyMod { 104 | fn from(nc: NcKeyMod) -> Self { 105 | Self(nc.into()) 106 | } 107 | } 108 | impl From for NcKeyMod { 109 | fn from(k: KeyMod) -> Self { 110 | k.0.into() 111 | } 112 | } 113 | 114 | impl From for KeyMod { 115 | fn from(u: u32) -> KeyMod { 116 | Self(u) 117 | } 118 | } 119 | impl From for u32 { 120 | fn from(km: KeyMod) -> u32 { 121 | km.0 122 | } 123 | } 124 | } 125 | 126 | /// # methods 127 | impl KeyMod { 128 | /// Returns `true` if no modifiers are present. 129 | #[inline] 130 | pub fn has_none(&self) -> bool { 131 | *self == KeyMod::None 132 | } 133 | 134 | /// Returns `true` if the `Shift` modifier is present. 135 | #[inline] 136 | pub fn has_shift(&self) -> bool { 137 | *self & KeyMod::Shift != KeyMod::None 138 | } 139 | 140 | /// Returns `true` if the `Alt` modifier is present. 141 | #[inline] 142 | pub fn has_alt(&self) -> bool { 143 | *self & KeyMod::Alt != KeyMod::None 144 | } 145 | 146 | /// Returns `true` if the `Ctrl` modifier is present. 147 | #[inline] 148 | pub fn has_ctrl(&self) -> bool { 149 | *self & KeyMod::Ctrl != KeyMod::None 150 | } 151 | 152 | /// Returns `true` if the `Super` modifier is present. 153 | #[inline] 154 | pub fn has_super(&self) -> bool { 155 | *self & KeyMod::Super != KeyMod::None 156 | } 157 | 158 | /// Returns `true` if the `Hyper` modifier is present. 159 | #[inline] 160 | pub fn has_hyper(&self) -> bool { 161 | *self & KeyMod::Hyper != KeyMod::None 162 | } 163 | 164 | /// Returns `true` if the `Meta` modifier is present. 165 | #[inline] 166 | pub fn has_meta(&self) -> bool { 167 | *self & KeyMod::Meta != KeyMod::None 168 | } 169 | 170 | /// Returns `true` if the `CapsLock` modifier is present. 171 | #[inline] 172 | pub fn has_capslock(&self) -> bool { 173 | *self & KeyMod::CapsLock != KeyMod::None 174 | } 175 | 176 | /// Returns `true` if the `NumLock` modifier is present. 177 | #[inline] 178 | pub fn has_numlock(&self) -> bool { 179 | *self & KeyMod::NumLock != KeyMod::None 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/input/mice_events.rs: -------------------------------------------------------------------------------- 1 | // notcurses::input::mice_events 2 | // 3 | //! 4 | // 5 | 6 | use crate::sys::{c_api::NcMiceEvents_u32, NcMiceEvents}; 7 | 8 | /// A bitmask of mice input events. 9 | /// 10 | /// # Used by 11 | /// - [`Notcurses.mice_enable`][crate::Notcurses#method.mice_enable] 12 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 13 | pub struct MiceEvents(NcMiceEvents_u32); 14 | 15 | /// # Flags 16 | #[allow(non_upper_case_globals)] 17 | impl MiceEvents { 18 | /// Disables all mice events. 19 | pub const None: Self = Self(NcMiceEvents::None.0); 20 | 21 | /// Enables mice move events. 22 | pub const Move: Self = Self(NcMiceEvents::Move.0); 23 | 24 | /// Enables mice button events. 25 | pub const Button: Self = Self(NcMiceEvents::Button.0); 26 | 27 | /// Enables mice drag events. 28 | pub const Drag: Self = Self(NcMiceEvents::Drag.0); 29 | 30 | /// Enables all mice tracking events. 31 | pub const All: Self = Self(NcMiceEvents::All.0); 32 | } 33 | 34 | mod core_impls { 35 | use super::{MiceEvents, NcMiceEvents, NcMiceEvents_u32}; 36 | 37 | impl Default for MiceEvents { 38 | fn default() -> Self { 39 | Self::None 40 | } 41 | } 42 | 43 | crate::from_primitive![MiceEvents, NcMiceEvents_u32]; 44 | crate::unit_impl_ops![bitwise; MiceEvents, NcMiceEvents_u32]; 45 | crate::unit_impl_fmt![bases; MiceEvents]; 46 | 47 | impl From for MiceEvents { 48 | fn from(nc: NcMiceEvents) -> Self { 49 | match nc { 50 | NcMiceEvents::None => MiceEvents::None, 51 | NcMiceEvents::Move => MiceEvents::Move, 52 | NcMiceEvents::Button => MiceEvents::Button, 53 | NcMiceEvents::Drag => MiceEvents::Drag, 54 | NcMiceEvents::All => MiceEvents::All, 55 | _ => MiceEvents::None, 56 | } 57 | } 58 | } 59 | impl From for NcMiceEvents { 60 | fn from(me: MiceEvents) -> Self { 61 | match me { 62 | MiceEvents::None => NcMiceEvents::None, 63 | MiceEvents::Move => NcMiceEvents::Move, 64 | MiceEvents::Button => NcMiceEvents::Button, 65 | MiceEvents::Drag => NcMiceEvents::Drag, 66 | MiceEvents::All => NcMiceEvents::All, 67 | _ => NcMiceEvents::None, 68 | } 69 | } 70 | } 71 | } 72 | 73 | /// # methods 74 | impl MiceEvents { 75 | /// Returns `true` if the current mice events has `other` included. 76 | pub fn has(&self, other: NcMiceEvents) -> bool { 77 | (self.0 & other.0) == other.0 78 | } 79 | 80 | /// Adds `other` to the current mice events. 81 | pub fn add(&mut self, other: NcMiceEvents) { 82 | self.0 |= other.0 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/input/mod.rs: -------------------------------------------------------------------------------- 1 | // notcurses::input 2 | // 3 | //! 4 | // 5 | 6 | mod input; 7 | mod input_type; 8 | mod key; 9 | mod key_mod; 10 | mod mice_events; 11 | mod received; 12 | 13 | pub use input::Input; 14 | pub use input_type::InputType; 15 | pub use key::Key; 16 | pub use key_mod::KeyMod; 17 | pub use mice_events::MiceEvents; 18 | pub use received::Received; 19 | -------------------------------------------------------------------------------- /src/input/received.rs: -------------------------------------------------------------------------------- 1 | // notcurses::input::received 2 | // 3 | //! 4 | // 5 | 6 | use super::Key; 7 | 8 | /// A received [`char`] or [`Key`]. 9 | #[derive(Clone, Copy, PartialEq, Eq)] 10 | pub enum Received { 11 | /// No input was received. 12 | /// 13 | /// A `0x00` (NUL) was received, meaning no input. 14 | NoInput, 15 | 16 | /// A synthesized event was received. 17 | Key(Key), 18 | 19 | /// A valid [`char`] was received. 20 | Char(char), 21 | } 22 | 23 | impl Received { 24 | /// Returns `true` if a specific `Key` has been received. 25 | #[inline] 26 | pub fn is_key(&self, key: Key) -> bool { 27 | matches!(self, Self::Key(k) if *k == key) 28 | } 29 | 30 | /// Returns `true` if a specific `char` has been received. 31 | #[inline] 32 | pub const fn is_char(&self, c: char) -> bool { 33 | matches!(self, Self::Char(ch) if *ch == c) 34 | } 35 | 36 | /// Returns the received `Key`, if any. 37 | #[inline] 38 | pub const fn key(&self) -> Option { 39 | if let Self::Key(k) = self { 40 | Some(*k) 41 | } else { 42 | None 43 | } 44 | } 45 | 46 | /// Returns the received `char`, if any. 47 | #[inline] 48 | pub const fn char(&self) -> Option { 49 | if let Self::Char(c) = self { 50 | Some(*c) 51 | } else { 52 | None 53 | } 54 | } 55 | } 56 | 57 | mod core_impls { 58 | use super::Received; 59 | use crate::sys::NcReceived; 60 | use core::fmt; 61 | 62 | impl Default for Received { 63 | fn default() -> Self { 64 | Self::NoInput 65 | } 66 | } 67 | 68 | impl fmt::Display for Received { 69 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 70 | let string = match self { 71 | Received::Key(k) => format!["{k}"], 72 | Received::Char(c) => format!["{c:?}"], 73 | Received::NoInput => "NoInput".to_string(), 74 | }; 75 | write!(f, "{}", string) 76 | } 77 | } 78 | impl fmt::Debug for Received { 79 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 80 | let string = match self { 81 | Received::Key(k) => format!["Key({k})"], 82 | Received::Char(c) => format!["Char({c:?})"], 83 | Received::NoInput => "NoInput".to_string(), 84 | }; 85 | write!(f, "Received::{}", string) 86 | } 87 | } 88 | 89 | impl From for Received { 90 | fn from(nc: NcReceived) -> Self { 91 | match nc { 92 | NcReceived::NoInput => Received::NoInput, 93 | NcReceived::Key(k) => Received::Key(k.into()), 94 | NcReceived::Char(c) => Received::Char(c), 95 | } 96 | } 97 | } 98 | impl From for NcReceived { 99 | fn from(r: Received) -> Self { 100 | match r { 101 | Received::NoInput => NcReceived::NoInput, 102 | Received::Key(k) => NcReceived::Key(k.into()), 103 | Received::Char(c) => NcReceived::Char(c), 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // notcurses::lib 2 | // 3 | //! A rusty notcurses wrapper. 4 | //! 5 | //! 6 | //! ### Example 7 | #![doc = concat!["```\n", include_str!("../examples/hello-world.rs"), "\n```" ]] 8 | //! 9 | // 10 | 11 | #![warn(clippy::all)] 12 | #![allow(clippy::module_inception, non_upper_case_globals)] 13 | 14 | use core::cell::RefCell; 15 | use once_cell::sync::OnceCell; 16 | 17 | mod color; 18 | mod error; 19 | mod input; 20 | mod macros; 21 | mod notcurses; 22 | mod plane; 23 | mod visual; 24 | 25 | pub use self::notcurses::{Capabilities, LogLevel, Notcurses, NotcursesBuilder, Statistics}; 26 | pub use color::{Alpha, Channel, Channels, Palette, Rgb, Rgba}; 27 | pub use error::{NotcursesError, NotcursesResult}; 28 | pub use input::{Input, InputType, Key, KeyMod, MiceEvents, Received}; 29 | pub use plane::{Align, Cell, Plane, PlaneBuilder, PlaneGeometry, Style}; 30 | pub use visual::{ 31 | Blitter, PixelImplementation, Scale, Visual, VisualBuilder, VisualGeometry, VisualOptions, 32 | }; 33 | 34 | // 35 | 36 | thread_local!( 37 | /// Restricts initializing more than one `Notcurses` instance per thread, at the same time. 38 | static NOTCURSES_LOCK: RefCell> = RefCell::new(OnceCell::new()); 39 | 40 | /// Restricts instancing the standard `Plane` more than once per `Notcurses` instance. 41 | static CLI_PLANE_LOCK: RefCell> = RefCell::new(OnceCell::new()); 42 | ); 43 | 44 | /// Reexport of [`libnotcurses-sys`](https://docs.rs/libnotcurses-sys). 45 | /// 46 | /// --- 47 | #[doc(inline)] 48 | pub use libnotcurses_sys as sys; 49 | 50 | pub(crate) use sys::from_primitive; 51 | pub(crate) use sys::unit_impl_fmt; 52 | pub(crate) use sys::unit_impl_ops; 53 | 54 | #[doc(inline)] 55 | pub use cuadra::{Position32 as Position, Size32 as Size}; 56 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | // norcurses::macros 2 | // 3 | //! 4 | // 5 | 6 | /// Prints to a plane, similarly as [`print!`]. 7 | /// 8 | /// `Plane`.[`putstr`] using the [`format!`] syntax. 9 | /// 10 | /// [`putstr`]: crate::plane::Plane#method.putstr 11 | /// 12 | /// Optionally renders with `+render` as first argument. 13 | /// 14 | /// # Example 15 | /// ``` 16 | /// # use notcurses::*; 17 | /// # fn main() -> NotcursesResult<()> { 18 | /// # let mut nc = Notcurses::new_cli()?; 19 | /// # let mut plane = Plane::new(&mut nc)?; 20 | /// # plane.set_scrolling(true); 21 | /// assert_eq![12, putstr!(plane, "hello\nworld\n")?]; 22 | /// putstr!(plane, "formatted text: {:?}\n", (0, 1.0, "two") )?; 23 | /// putstr!(+render plane, "renders afterwards = {}", true)?; 24 | /// # Ok(()) 25 | /// # } 26 | /// ``` 27 | #[macro_export] 28 | macro_rules! putstr { 29 | ($plane:expr, $($args:tt)*) => { 30 | ({ 31 | let res = $plane.putstr(&format![$($args)*])?; 32 | Ok(res) 33 | }) as $crate::NotcursesResult 34 | }; 35 | (+render $plane:expr, $($args:tt)*) => { 36 | ({ 37 | let res = $plane.putstr(&format![$($args)*])?; 38 | $plane.render()?; 39 | Ok(res) 40 | }) as $crate::NotcursesResult 41 | }; 42 | 43 | } 44 | 45 | /// Prints to a plane, with a new line, similarly as [`println!`]. 46 | /// 47 | /// `Plane`.[`putstrln`] using the [`format!`] syntax. 48 | /// 49 | /// [`putstrln`]: crate::plane::Plane#method.putstrln 50 | /// 51 | /// Optionally renders with `+render` as first argument. 52 | /// 53 | /// # Example 54 | /// ``` 55 | /// # use notcurses::*; 56 | /// # fn main() -> NotcursesResult<()> { 57 | /// # let mut nc = Notcurses::new_cli()?; 58 | /// # let mut plane = Plane::new(&mut nc)?; 59 | /// # plane.set_scrolling(true); 60 | /// assert_eq![12, putstrln!(plane, "hello world")?]; 61 | /// putstrln!(plane, "formatted text: {:?}", (0, 1.0, "two") )?; 62 | /// putstrln!(+render plane)?; 63 | /// # Ok(()) 64 | /// # } 65 | /// ``` 66 | #[macro_export] 67 | macro_rules! putstrln { 68 | ($plane:expr) => { 69 | ({ 70 | let res = $plane.putln()?; 71 | Ok(res) 72 | }) as $crate::NotcursesResult 73 | }; 74 | ($plane:expr, $($args:tt)*) => { 75 | ({ 76 | let res = $plane.putstrln(&format![$($args)*])?; 77 | Ok(res) 78 | }) as $crate::NotcursesResult 79 | }; 80 | (+render $plane:expr) => { 81 | ({ 82 | let res = $plane.putln()?; 83 | $plane.render()?; 84 | Ok(res) 85 | }) as $crate::NotcursesResult 86 | }; 87 | (+render $plane:expr, $($args:tt)*) => { 88 | ({ 89 | let res = $plane.putstrln(&format![$($args)*])?; 90 | $plane.render()?; 91 | Ok(res) 92 | }) as $crate::NotcursesResult 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /src/notcurses/builder.rs: -------------------------------------------------------------------------------- 1 | // notcurses::notcurses::builder 2 | // 3 | //! 4 | // 5 | 6 | use crate::{ 7 | error::NotcursesResult as Result, 8 | notcurses::{LogLevel, Notcurses, NotcursesInner}, 9 | sys::{Nc, NcOptionsBuilder}, 10 | }; 11 | 12 | /// A [`Notcurses`] builder. 13 | #[derive(Clone, Copy, Debug)] 14 | pub struct NotcursesBuilder { 15 | options: NcOptionsBuilder, 16 | } 17 | 18 | mod core_impls { 19 | use super::{NcOptionsBuilder, NotcursesBuilder}; 20 | 21 | impl Default for NotcursesBuilder { 22 | fn default() -> Self { 23 | Self { 24 | options: NcOptionsBuilder::new().suppress_banners(true), 25 | } 26 | } 27 | } 28 | } 29 | 30 | /// # constructors 31 | impl NotcursesBuilder { 32 | /// Returns a new default `NotcursesBuilder`, without banners. 33 | pub fn new() -> Self { 34 | Self::default() 35 | } 36 | 37 | /// Returns a `Notcurses` instance. 38 | pub fn build(self) -> Result { 39 | Notcurses::lock_notcurses()?; 40 | let nc = unsafe { Nc::with_options(self.options.build())? }; 41 | 42 | Ok(Notcurses { 43 | inner: NotcursesInner::new(nc), 44 | options: self.options, 45 | }) 46 | } 47 | } 48 | 49 | /// # methods (chainable) 50 | impl NotcursesBuilder { 51 | /// Sets the log level. 52 | pub fn log_level(mut self, log_level: LogLevel) -> Self { 53 | self.options.set_log_level(log_level.into()); 54 | self 55 | } 56 | 57 | /// Sets the margins. 58 | pub fn margins(mut self, top: u32, right: u32, bottom: u32, left: u32) -> Self { 59 | self.options.set_margins(top, right, bottom, left); 60 | self 61 | } 62 | 63 | /// Sets the top margin. 64 | pub fn margin_top(mut self, top: u32) -> Self { 65 | self.options.set_margin_top(top); 66 | self 67 | } 68 | 69 | /// Sets the right margin. 70 | pub fn margin_right(mut self, right: u32) -> Self { 71 | self.options.set_margin_right(right); 72 | self 73 | } 74 | 75 | /// Sets the bottom margin. 76 | pub fn margin_bottom(mut self, bottom: u32) -> Self { 77 | self.options.set_margin_bottom(bottom); 78 | self 79 | } 80 | 81 | /// Sets the left margin. 82 | pub fn margin_left(mut self, left: u32) -> Self { 83 | self.options.set_margin_left(left); 84 | self 85 | } 86 | 87 | // flags 88 | 89 | /// If `true`, Input may be freely dropped. 90 | /// 91 | /// This ought be provided when the program does not intend to handle input. 92 | /// Otherwise, input can accumulate in internal buffers, eventually preventing 93 | /// Notcurses from processing terminal messages. 94 | pub fn drain_input(mut self, drain: bool) -> Self { 95 | self.options.set_drain_input(drain); 96 | self 97 | } 98 | 99 | /// If `true`, wont call setlocale(). 100 | pub fn inhibit_set_locale(mut self, inhibit: bool) -> Self { 101 | self.options.set_inhibit_set_locale(inhibit); 102 | self 103 | } 104 | 105 | /// If `true`, wont enter alternate mode. 106 | pub fn no_alternate_screen(mut self, no_alternate: bool) -> Self { 107 | self.options.set_no_alternate_screen(no_alternate); 108 | self 109 | } 110 | 111 | /// If `true`, wont try to clear any preexisting bitmaps. 112 | pub fn no_clear_bitmaps(mut self, no_clear: bool) -> Self { 113 | self.options.set_no_clear_bitmaps(no_clear); 114 | self 115 | } 116 | 117 | /// If `true`, wont modify the font. 118 | pub fn no_font_changes(mut self, no_font_changes: bool) -> Self { 119 | self.options.set_no_font_changes(no_font_changes); 120 | self 121 | } 122 | 123 | /// If `true`, wont handle `SIGINT`, `SIGSEGV`, `SIGABRT` nor `SIGQUIT`. 124 | pub fn no_quit_sig_handlers(mut self, no_quit: bool) -> Self { 125 | self.options.set_no_quit_sig_handlers(no_quit); 126 | self 127 | } 128 | 129 | /// If `true`, wont handle `SIGWINCH`. 130 | pub fn no_winch_sig_handler(mut self, no_winch: bool) -> Self { 131 | self.options.set_no_winch_sig_handler(no_winch); 132 | self 133 | } 134 | 135 | /// If `true`, will initializes the CLI plane’s virtual cursor to match 136 | /// the physical cursor at context creation time. 137 | pub fn preserve_cursor(mut self, preserve: bool) -> Self { 138 | self.options.set_preserve_cursor(preserve); 139 | self 140 | } 141 | 142 | /// If `true`, will prepare the CLI plane in scrolling mode. 143 | pub fn scrolling(mut self, scrolling: bool) -> Self { 144 | self.options.set_scrolling(scrolling); 145 | self 146 | } 147 | 148 | /// A shortcut for setting the following options together: 149 | /// `no_alternate_screen`, `no_clear_bitmaps`, `preserve_cursor` & `scrolling`. 150 | pub fn cli_mode(mut self, cli_mode: bool) -> Self { 151 | self.options.set_cli_mode(cli_mode); 152 | self 153 | } 154 | 155 | /// If `true`, wont print banners. 156 | pub fn suppress_banners(mut self, suppress_banners: bool) -> Self { 157 | self.options.set_suppress_banners(suppress_banners); 158 | self 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/notcurses/capabilities.rs: -------------------------------------------------------------------------------- 1 | //! 2 | 3 | use crate::{ 4 | visual::{Blitter, PixelImplementation}, 5 | Notcurses, 6 | }; 7 | 8 | /// The detected current terminal capabilities. 9 | /// 10 | /// It can be generated from 11 | /// [`Notcurses.capabilities()`][crate::Notcurses#method.capabilities]. 12 | #[non_exhaustive] 13 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 14 | pub struct Capabilities { 15 | pub(crate) utf8: bool, 16 | pub(crate) halfblock: bool, 17 | pub(crate) quadrant: bool, 18 | pub(crate) sextant: bool, 19 | pub(crate) braille: bool, 20 | pub(crate) pixel: bool, 21 | pub(crate) pixel_implementation: PixelImplementation, 22 | pub(crate) images: bool, 23 | pub(crate) videos: bool, 24 | pub(crate) fade: bool, 25 | pub(crate) truecolor: bool, 26 | pub(crate) palette_size: u32, 27 | pub(crate) palette_change: bool, 28 | } 29 | 30 | mod core_impls { 31 | use super::Capabilities; 32 | use core::fmt::{self, Write as _}; 33 | 34 | impl fmt::Display for Capabilities { 35 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 36 | let mut string = String::new(); 37 | if self.utf8 { 38 | string += "utf8 " 39 | } 40 | if self.halfblock { 41 | string += "halfblock " 42 | } 43 | if self.quadrant { 44 | string += "quadrant " 45 | } 46 | if self.sextant { 47 | string += "sextant " 48 | } 49 | if self.braille { 50 | string += "braille " 51 | } 52 | if self.pixel { 53 | let _ = write![string, "pixel:{} ", self.pixel_implementation]; 54 | } 55 | if self.images { 56 | string += "images " 57 | } 58 | if self.videos { 59 | string += "videos " 60 | } 61 | if self.fade { 62 | string += "fade " 63 | } 64 | if self.truecolor { 65 | string += "rgb " 66 | } 67 | let _ = write![string, "palette:{} ", self.palette_size]; 68 | if self.palette_change { 69 | string += "palchange " 70 | } 71 | let _ = string.pop(); 72 | write!(f, "{}", string) 73 | } 74 | } 75 | } 76 | 77 | impl Capabilities { 78 | /// New `Capabilities` from a [`Notcurses`] context. 79 | pub fn new(nc: &Notcurses) -> Self { 80 | nc.capabilities() 81 | } 82 | 83 | /// Returns the best Blitter available, using the following rules 84 | /// of *graceful degradation*: 85 | /// 86 | /// [`Pixel`] > [`Sextant`] > [`Quadrant`] > [`Half`] > [`Ascii`]. 87 | /// 88 | /// [`Pixel`]: crate::sys::NcBlitter#variant.Pixel 89 | /// [`Sextant`]: crate::sys::NcBlitter#variant.Sextant 90 | /// [`Quadrant`]: crate::sys::NcBlitter#variant.Quadrant 91 | /// [`Half`]: crate::sys::NcBlitter#variant.Half 92 | /// [`Ascii`]: crate::sys::NcBlitter#variant.Ascii 93 | pub fn best_blitter(&self) -> Blitter { 94 | if self.pixel { 95 | Blitter::Pixel 96 | } else if self.sextant { 97 | Blitter::Sextant 98 | } else if self.quadrant { 99 | Blitter::Quadrant 100 | } else if self.halfblock { 101 | Blitter::Half 102 | } else { 103 | Blitter::Ascii 104 | } 105 | } 106 | 107 | /// Returns `true` if the provided [`Blitter`] is among the capabilities. 108 | pub fn can_blitter(&self, blitter: Blitter) -> bool { 109 | match blitter { 110 | Blitter::Default => true, 111 | Blitter::Ascii => true, 112 | Blitter::Half => self.halfblock, 113 | Blitter::Quadrant => self.quadrant, 114 | Blitter::Sextant => self.sextant, 115 | Blitter::Braille => self.braille, 116 | Blitter::Pixel => self.pixel, 117 | Blitter::_4x1 => self.utf8, 118 | Blitter::_8x1 => self.utf8, 119 | } 120 | } 121 | 122 | /// Returns *true* if we can reliably use Unicode half blocks. 123 | pub fn halfblock(&self) -> bool { 124 | self.halfblock 125 | } 126 | 127 | /// Returns *true* if we can reliably use Unicode quadrant blocks. 128 | pub fn quadrant(&self) -> bool { 129 | self.quadrant 130 | } 131 | 132 | /// Returns *true* if we can reliably use Unicode sextant blocks. 133 | pub fn sextant(&self) -> bool { 134 | self.sextant 135 | } 136 | 137 | /// Returns *true* if we can reliably use Unicode Braille. 138 | pub fn braille(&self) -> bool { 139 | self.braille 140 | } 141 | 142 | /// Returns *true* if the encoding is UTF-8. 143 | pub fn utf8(&self) -> bool { 144 | self.utf8 145 | } 146 | 147 | /// Returns *true* if loading images is possible. 148 | /// 149 | /// This requires that notcurse is built against FFmpeg/OIIO. 150 | pub fn images(&self) -> bool { 151 | self.images 152 | } 153 | 154 | /// Returns *true* if loading videos is possible. 155 | /// 156 | /// This requires that notcurse is built against FFmpeg/OIIO. 157 | pub fn videos(&self) -> bool { 158 | self.videos 159 | } 160 | 161 | /// Returns *true* if we can blit pixel-accurate bitmaps. 162 | pub fn pixel(&self) -> bool { 163 | self.pixel 164 | } 165 | 166 | /// Returns the detected pixel-blitting implementation. 167 | pub const fn pixel_implementation(&self) -> PixelImplementation { 168 | self.pixel_implementation 169 | } 170 | 171 | /// Returns *true* if fading is possible. 172 | pub fn fade(&self) -> bool { 173 | self.fade 174 | } 175 | 176 | /// Returns *true* if it's possible to directly specify RGB values per Cell, 177 | /// or *false* if it's only possible to use palettes. 178 | pub fn truecolor(&self) -> bool { 179 | self.truecolor 180 | } 181 | 182 | /// Returns *true* if the "hardware" palette can be changed. 183 | /// 184 | /// Requires the "ccc" terminfo capability, and that the number of colors 185 | /// supported is at least the size of the `Palette` structure. 186 | pub fn palette_change(&self) -> bool { 187 | self.palette_change 188 | } 189 | 190 | /// Returns the number of simultaneous colors claimed to be supported, 191 | /// if there is color support. 192 | /// 193 | /// Note that several terminal emulators advertise more colors than they 194 | /// actually support, downsampling internally. 195 | pub fn palette_size(&self) -> u32 { 196 | self.palette_size 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/notcurses/log_level.rs: -------------------------------------------------------------------------------- 1 | // notcurses::notcurses::log_level 2 | // 3 | //! 4 | // 5 | 6 | /// Stderr log level. 7 | /// 8 | /// Note that if stderr is connected to the same terminal on which we're 9 | /// rendering, any kind of logging will disrupt the output. 10 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 11 | pub enum LogLevel { 12 | /// Default. print nothing once fullscreen service begins. 13 | Silent, 14 | 15 | /// Print diagnostics immediately related to crashing. 16 | Panic, 17 | 18 | /// We're hanging around, but we've had a horrible fault. 19 | Fatal, 20 | 21 | /// We can't keep doin' this, but we can do other things. 22 | Error, 23 | 24 | /// You probably don't want what's happening to happen. 25 | Warning, 26 | 27 | /// "Standard information". 28 | Info, 29 | 30 | /// "Detailed information". 31 | Verbose, 32 | 33 | /// This is honestly a bit much. 34 | Debug, 35 | 36 | /// There's probably a better way to do what you want. 37 | Trace, 38 | } 39 | 40 | mod core_impls { 41 | use super::LogLevel; 42 | use crate::sys::{c_api::NcLogLevel_i32, NcLogLevel}; 43 | use core::fmt; 44 | 45 | impl Default for LogLevel { 46 | fn default() -> Self { 47 | Self::Silent 48 | } 49 | } 50 | 51 | impl fmt::Display for LogLevel { 52 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 53 | write!( 54 | f, 55 | "{}", 56 | match self { 57 | LogLevel::Silent => "Silent", 58 | LogLevel::Panic => "Panic", 59 | LogLevel::Fatal => "Fatal", 60 | LogLevel::Error => "Error", 61 | LogLevel::Warning => "Warning", 62 | LogLevel::Info => "Info", 63 | LogLevel::Verbose => "Verbose", 64 | LogLevel::Debug => "Debug", 65 | LogLevel::Trace => "Trace", 66 | } 67 | ) 68 | } 69 | } 70 | 71 | // 72 | 73 | impl From for LogLevel { 74 | fn from(nc: NcLogLevel) -> LogLevel { 75 | match nc { 76 | NcLogLevel::Silent => LogLevel::Silent, 77 | NcLogLevel::Panic => LogLevel::Panic, 78 | NcLogLevel::Fatal => LogLevel::Fatal, 79 | NcLogLevel::Error => LogLevel::Error, 80 | NcLogLevel::Warning => LogLevel::Warning, 81 | NcLogLevel::Info => LogLevel::Info, 82 | NcLogLevel::Verbose => LogLevel::Verbose, 83 | NcLogLevel::Debug => LogLevel::Debug, 84 | NcLogLevel::Trace => LogLevel::Trace, 85 | } 86 | } 87 | } 88 | impl From for NcLogLevel { 89 | fn from(ll: LogLevel) -> NcLogLevel { 90 | match ll { 91 | LogLevel::Silent => NcLogLevel::Silent, 92 | LogLevel::Panic => NcLogLevel::Panic, 93 | LogLevel::Fatal => NcLogLevel::Fatal, 94 | LogLevel::Error => NcLogLevel::Error, 95 | LogLevel::Warning => NcLogLevel::Warning, 96 | LogLevel::Info => NcLogLevel::Info, 97 | LogLevel::Verbose => NcLogLevel::Verbose, 98 | LogLevel::Debug => NcLogLevel::Debug, 99 | LogLevel::Trace => NcLogLevel::Trace, 100 | } 101 | } 102 | } 103 | 104 | impl From for LogLevel { 105 | fn from(nci: NcLogLevel_i32) -> LogLevel { 106 | NcLogLevel::from(nci).into() 107 | } 108 | } 109 | impl From for NcLogLevel_i32 { 110 | fn from(pi: LogLevel) -> NcLogLevel_i32 { 111 | NcLogLevel::from(pi).into() 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/notcurses/mod.rs: -------------------------------------------------------------------------------- 1 | // notcurses::notcurses 2 | // 3 | //! 4 | // 5 | 6 | mod builder; 7 | mod capabilities; 8 | mod log_level; 9 | mod notcurses; 10 | mod statistics; 11 | 12 | pub use self::notcurses::Notcurses; 13 | pub(crate) use self::notcurses::NotcursesInner; 14 | pub use builder::NotcursesBuilder; 15 | pub use capabilities::Capabilities; 16 | pub use log_level::LogLevel; 17 | pub use statistics::Statistics; 18 | -------------------------------------------------------------------------------- /src/notcurses/statistics.rs: -------------------------------------------------------------------------------- 1 | // notcurses::notcurses::statistics 2 | // 3 | //! 4 | // 5 | 6 | use crate::{sys::NcStats, Notcurses}; 7 | 8 | /// Runtime statistics. 9 | #[derive(Clone, PartialEq, Eq)] 10 | pub struct Statistics { 11 | nc: *mut NcStats, 12 | } 13 | 14 | mod core_impls { 15 | use super::{NcStats, Statistics}; 16 | use crate::sys::c_api::libc::free; 17 | use core::fmt; 18 | 19 | impl Drop for Statistics { 20 | fn drop(&mut self) { 21 | if crate::Notcurses::is_initialized() { 22 | unsafe { free(self.into_ref_mut() as *mut NcStats as *mut core::ffi::c_void) } 23 | } 24 | } 25 | } 26 | 27 | impl fmt::Debug for Statistics { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | write!( 30 | f, 31 | "Statistics {{ 32 | renders: ok:{} err:{} ns:[{} max:{} min:{}] 33 | rasters: ok:{} err:{} ns:[{} max:{} min:{}] bytes:[{} max:{} min:{}] 34 | cells:[{} eli:{} changes:{}] sprixel:[{} eli:{} bytes:{} changes:{}] 35 | fg:[{} eli:{}] bg:[{} eli:{}] default:[{} eli:{}] 36 | input:[{} err:{} hpa:{}] 37 | planes:{} fb_bytes:{} refreshes:{} app_sync:{} 38 | }}", 39 | self.renders(), 40 | self.failed_renders(), 41 | self.render_ns(), 42 | self.render_max_ns(), 43 | self.render_min_ns(), 44 | // 45 | self.writeouts(), 46 | self.failed_writeouts(), 47 | self.raster_ns(), 48 | self.raster_max_ns(), 49 | self.raster_min_ns(), 50 | self.raster_bytes(), 51 | self.raster_max_bytes(), 52 | self.raster_min_bytes(), 53 | // 54 | self.cell_emissions(), 55 | self.cell_elisions(), 56 | self.cell_geo_changes(), 57 | // 58 | self.sprixel_emissions(), 59 | self.sprixel_elisions(), 60 | self.sprixel_bytes(), 61 | self.pixel_geo_changes(), 62 | // 63 | self.fg_emissions(), 64 | self.fg_elisions(), 65 | self.bg_emissions(), 66 | self.bg_elisions(), 67 | self.default_emissions(), 68 | self.default_elisions(), 69 | // 70 | self.input_events(), 71 | self.input_errors(), 72 | self.hpa_gratuitous(), 73 | // 74 | self.planes(), 75 | self.fb_bytes(), 76 | self.refreshes(), 77 | self.appsync_updates(), 78 | ) 79 | } 80 | } 81 | 82 | impl From<&mut NcStats> for Statistics { 83 | fn from(nc: &mut NcStats) -> Statistics { 84 | Self { nc } 85 | } 86 | } 87 | } 88 | 89 | /// # constructors & deconstructors 90 | impl Statistics { 91 | /// Allocates a [`Statistics`] object. 92 | pub fn new(nc: &Notcurses) -> Self { 93 | let mut stats = nc.with_nc_mut(|nc| Self { 94 | nc: nc.stats_alloc(), 95 | }); 96 | stats.update(nc); 97 | stats 98 | } 99 | 100 | // 101 | 102 | /// Returns a shared reference to the inner [`NcStats`]. 103 | pub fn into_ref(&self) -> &NcStats { 104 | unsafe { &*self.nc } 105 | } 106 | 107 | /// Returns an exclusive reference to the inner [`NcStats`]. 108 | pub fn into_ref_mut(&mut self) -> &mut NcStats { 109 | unsafe { &mut *self.nc } 110 | } 111 | } 112 | 113 | /// # manager methods 114 | impl Statistics { 115 | /// Acquires an atomic snapshot of the notcurses object's stats. 116 | pub fn update(&mut self, nc: &Notcurses) { 117 | nc.with_nc_mut(|nc| nc.stats(self.into_ref_mut())); 118 | } 119 | 120 | /// Resets all cumulative stats. 121 | /// 122 | /// Immediate ones, such as fbbytes, are not reset. 123 | pub fn reset(&mut self, nc: &Notcurses) { 124 | nc.with_nc_mut(|nc| nc.stats_reset(self.into_ref_mut())); 125 | } 126 | } 127 | 128 | /// # query methods 129 | impl Statistics { 130 | /// Successful renders. 131 | pub fn renders(&self) -> u64 { 132 | self.into_ref().renders 133 | } 134 | 135 | /// Failed renders. 136 | pub fn failed_renders(&self) -> u64 { 137 | self.into_ref().failed_renders 138 | } 139 | 140 | /// Nanoseconds spent rendering. 141 | pub fn render_ns(&self) -> u64 { 142 | self.into_ref().render_ns 143 | } 144 | 145 | /// Max ns spent in render for a frame. 146 | pub fn render_max_ns(&self) -> i64 { 147 | self.into_ref().render_max_ns 148 | } 149 | 150 | /// Min ns spent in render for a frame. 151 | pub fn render_min_ns(&self) -> i64 { 152 | self.into_ref().render_min_ns 153 | } 154 | 155 | // 156 | 157 | /// Successful rasterizations. 158 | pub fn writeouts(&self) -> u64 { 159 | self.into_ref().writeouts 160 | } 161 | 162 | /// Failed rasterizations. 163 | pub fn failed_writeouts(&self) -> u64 { 164 | self.into_ref().failed_writeouts 165 | } 166 | 167 | /// Bytes emitted to ttyfp. 168 | pub fn raster_bytes(&self) -> u64 { 169 | self.into_ref().raster_bytes 170 | } 171 | 172 | /// Max bytes emitted for a frame. 173 | pub fn raster_max_bytes(&self) -> i64 { 174 | self.into_ref().raster_max_bytes 175 | } 176 | 177 | /// Min bytes emitted for a frame. 178 | pub fn raster_min_bytes(&self) -> i64 { 179 | self.into_ref().raster_min_bytes 180 | } 181 | 182 | /// Nanoseconds spent rasterizing. 183 | pub fn raster_ns(&self) -> u64 { 184 | self.into_ref().raster_ns 185 | } 186 | 187 | /// Max ns spent in raster for a frame. 188 | pub fn raster_max_ns(&self) -> i64 { 189 | self.into_ref().raster_max_ns 190 | } 191 | 192 | /// Min ns spent in raster for a frame. 193 | pub fn raster_min_ns(&self) -> i64 { 194 | self.into_ref().raster_min_ns 195 | } 196 | 197 | // 198 | 199 | /// Cells we elided entirely thanks to damage maps. 200 | pub fn cell_elisions(&self) -> u64 { 201 | self.into_ref().cellelisions 202 | } 203 | 204 | /// Total number of cells emitted to terminal. 205 | pub fn cell_emissions(&self) -> u64 { 206 | self.into_ref().cellemissions 207 | } 208 | 209 | /// RGB fg elision count. 210 | pub fn fg_elisions(&self) -> u64 { 211 | self.into_ref().fgelisions 212 | } 213 | 214 | /// RGB fg emissions. 215 | pub fn fg_emissions(&self) -> u64 { 216 | self.into_ref().fgemissions 217 | } 218 | 219 | /// RGB bg elision count. 220 | pub fn bg_elisions(&self) -> u64 { 221 | self.into_ref().bgelisions 222 | } 223 | 224 | /// RGB bg emissions. 225 | pub fn bg_emissions(&self) -> u64 { 226 | self.into_ref().bgemissions 227 | } 228 | 229 | /// Default color was emitted. 230 | pub fn default_elisions(&self) -> u64 { 231 | self.into_ref().defaultelisions 232 | } 233 | 234 | /// Default color was elided. 235 | pub fn default_emissions(&self) -> u64 { 236 | self.into_ref().defaultemissions 237 | } 238 | 239 | /// Sprixel draw count. 240 | pub fn sprixel_emissions(&self) -> u64 { 241 | self.into_ref().sprixelemissions 242 | } 243 | 244 | /// Sprixel elision count. 245 | pub fn sprixel_elisions(&self) -> u64 { 246 | self.into_ref().sprixelelisions 247 | } 248 | 249 | /// Sprixel bytes emitted. 250 | pub fn sprixel_bytes(&self) -> u64 { 251 | self.into_ref().sprixelbytes 252 | } 253 | 254 | // 255 | 256 | /// Refresh requests (non-optimized redraw). 257 | pub fn refreshes(&self) -> u64 { 258 | self.into_ref().refreshes 259 | } 260 | 261 | // 262 | 263 | /// How many application-synchronized updates? 264 | pub fn appsync_updates(&self) -> u64 { 265 | self.into_ref().appsync_updates 266 | } 267 | 268 | /// Errors processing control sequences/utf8. 269 | pub fn input_errors(&self) -> u64 { 270 | self.into_ref().input_errors 271 | } 272 | 273 | /// Characters returned to userspace. 274 | pub fn input_events(&self) -> u64 { 275 | self.into_ref().input_events 276 | } 277 | 278 | /// Unnecessary hpas issued. 279 | /// 280 | /// The number of hpa (horizontal position absolute, see terminfo(5)) control 281 | /// sequences issued where not strictly necessary. 282 | /// 283 | /// This is done to cope with fundamental ambiguities regarding glyph width. 284 | /// It is not generally possible to know how wide a glyph will be rendered 285 | /// on a given combination of font, font rendering engine, and terminal. 286 | /// Indeed, it is not even generally possible to know how many glyphs will 287 | /// result from a sequence of EGCs. As a result, Notcurses sometimes issues 288 | /// "gratuitous" hpa controls. 289 | pub fn hpa_gratuitous(&self) -> u64 { 290 | self.into_ref().hpa_gratuitous 291 | } 292 | 293 | /// Cell geometry changes (resizes). 294 | /// 295 | /// The number of changes to the visible area's cell geometry. 296 | /// 297 | /// The cell geometry changes whenever the visible area is resized without a 298 | /// corresponding cell-pixel geometry change. 299 | /// 300 | /// Both can change at the same time if e.g. a terminal undergoes a 301 | /// font size change without changing its total size. 302 | pub fn cell_geo_changes(&self) -> u64 { 303 | self.into_ref().cell_geo_changes 304 | } 305 | 306 | /// Pixel geometry changes (font resize). 307 | /// 308 | /// The number of changes to cells' pixel geometry (i.e. the height and 309 | /// width of each cell), and changes whenever the font size changes. 310 | pub fn pixel_geo_changes(&self) -> u64 { 311 | self.into_ref().pixel_geo_changes 312 | } 313 | 314 | /// Total bytes devoted to all active framebuffers. 315 | pub fn fb_bytes(&self) -> u64 { 316 | self.into_ref().fbbytes 317 | } 318 | 319 | /// Number of planes currently in existence. 320 | pub fn planes(&self) -> u32 { 321 | self.into_ref().planes 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/plane/align.rs: -------------------------------------------------------------------------------- 1 | // notcurses::plane::align 2 | // 3 | //! 4 | // 5 | 6 | /// Alignment within a [`Plane`][super::Plane] or terminal. 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 8 | pub enum Align { 9 | /// Anyhing unaligned wont be rendered. 10 | Unaligned, 11 | /// Left (== Top) alignment. 12 | /// 13 | /// This is the default alignment. 14 | Left, 15 | /// Center alignment. 16 | Center, 17 | /// Right (== Bottom) alignment. 18 | Right, 19 | } 20 | 21 | /// # aliases 22 | #[allow(non_upper_case_globals)] 23 | impl Align { 24 | /// Top (== Left) alignment. 25 | /// 26 | /// This is the default alignment. 27 | pub const Top: Align = Align::Left; 28 | /// Bottom (== Right]) alignment. 29 | pub const Bottom: Align = Align::Right; 30 | } 31 | 32 | mod core_impls { 33 | use super::Align; 34 | use crate::sys::{c_api::NcAlign_u32, NcAlign}; 35 | use core::fmt; 36 | 37 | impl Default for Align { 38 | fn default() -> Self { 39 | Self::Left 40 | } 41 | } 42 | 43 | impl fmt::Display for Align { 44 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 45 | write!( 46 | f, 47 | "{}", 48 | match self { 49 | Align::Left => "Left", 50 | Align::Center => "Center", 51 | Align::Right => "Right", 52 | Align::Unaligned => "Unaligned", 53 | } 54 | ) 55 | } 56 | } 57 | 58 | // 59 | 60 | impl From for Align { 61 | fn from(nc: NcAlign) -> Align { 62 | match nc { 63 | NcAlign::Left => Align::Left, 64 | NcAlign::Center => Align::Center, 65 | NcAlign::Right => Align::Right, 66 | NcAlign::Unaligned => Align::Unaligned, 67 | } 68 | } 69 | } 70 | impl From for NcAlign { 71 | fn from(align: Align) -> NcAlign { 72 | match align { 73 | Align::Left => NcAlign::Left, 74 | Align::Center => NcAlign::Center, 75 | Align::Right => NcAlign::Right, 76 | Align::Unaligned => NcAlign::Unaligned, 77 | } 78 | } 79 | } 80 | 81 | impl From for Align { 82 | fn from(ncu: NcAlign_u32) -> Align { 83 | NcAlign::from(ncu).into() 84 | } 85 | } 86 | impl From for NcAlign_u32 { 87 | fn from(align: Align) -> NcAlign_u32 { 88 | NcAlign::from(align).into() 89 | } 90 | } 91 | impl From for Align { 92 | fn from(nci: i32) -> Align { 93 | NcAlign::from(nci).into() 94 | } 95 | } 96 | impl From for i32 { 97 | fn from(align: Align) -> i32 { 98 | NcAlign::from(align).into() 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/plane/builder.rs: -------------------------------------------------------------------------------- 1 | // notcurses::plane::builder 2 | // 3 | //! 4 | // 5 | 6 | use crate::{ 7 | error::NotcursesResult as Result, 8 | plane::{Align, Plane}, 9 | sys::{Nc, NcPlane, NcPlaneOptionsBuilder}, 10 | Notcurses, Position, Size, 11 | }; 12 | 13 | /// A [`Plane`] builder. 14 | #[derive(Debug, Default)] 15 | pub struct PlaneBuilder { 16 | options: NcPlaneOptionsBuilder, 17 | } 18 | 19 | /// # Constructors 20 | impl PlaneBuilder { 21 | /// Returns a new default `PlaneBuilder`. 22 | /// 23 | /// Size, position and margins are set to 0. 24 | /// The plane will be maximized to its parent size. 25 | pub fn new() -> Self { 26 | Self::default() 27 | } 28 | 29 | /// Returns a new standalone `Plane`. 30 | pub fn build(self, nc: &Notcurses) -> Result { 31 | let notcurses = nc.inner.clone(); 32 | let ncplane = { 33 | let inner_nc = notcurses.borrow_mut(); 34 | let nc_ptr: *mut Nc = inner_nc.nc; 35 | 36 | // SAFETY: ensured via RefCell's borrowing rules 37 | let nc_ref: &mut Nc = unsafe { &mut *nc_ptr }; 38 | NcPlane::new_pile(nc_ref, &self.options.build())? 39 | }; 40 | Ok(Plane { 41 | nc: ncplane, 42 | notcurses, 43 | }) 44 | } 45 | 46 | /// Returns a new child `Plane` of the provided parent. 47 | pub fn build_child(self, parent: &mut Plane) -> Result { 48 | let ncplane = NcPlane::new_child(parent.into_ref_mut(), &self.options.build())?; 49 | Ok(Plane { 50 | nc: ncplane, 51 | notcurses: parent.notcurses.clone(), 52 | }) 53 | } 54 | } 55 | 56 | /// # Methods (chainable) 57 | impl PlaneBuilder { 58 | /// Sets the vertical position relative to the parent plane. 59 | /// 60 | /// Default: *`0`*. 61 | /// 62 | /// Effect: Sets the *`y`* coordinate and unsets vertical alignment. 63 | pub fn y(mut self, y: i32) -> Self { 64 | self.options.set_y(y); 65 | self 66 | } 67 | 68 | /// Sets the horizontal position relative to the parent plane. 69 | /// 70 | /// Default: *`0`*. 71 | /// 72 | /// Effect: Sets the *`x`* coordinate and unsets vertical alignment. 73 | pub fn x(mut self, x: i32) -> Self { 74 | self.options.set_x(x); 75 | self 76 | } 77 | 78 | /// Sets the position relative to parent plane. 79 | /// 80 | /// Default: *`0`*, *`0`*. 81 | /// 82 | /// Effect: Sets both *`x`* & *`y`* coordinates and unsets both horizontal and 83 | /// vertical alignment. 84 | pub fn position(mut self, position: impl Into) -> Self { 85 | let (x, y) = position.into().into(); 86 | self.options.set_yx(y, x); 87 | self 88 | } 89 | 90 | // 91 | 92 | /// Sets the vertical alignment. 93 | /// 94 | /// Default: *[`Align::Top`]*. 95 | /// 96 | /// Effect: Sets *`v`* alignment. 97 | pub fn valign(mut self, vertical: Align) -> Self { 98 | self.options.set_valign(vertical); 99 | self 100 | } 101 | 102 | /// Sets the horizontal alignment. 103 | /// 104 | /// Default: *[`Align::Left`]*. 105 | /// 106 | /// Effect: Sets *`h`* alignment. 107 | pub fn halign(mut self, horizontal: Align) -> Self { 108 | self.options.set_halign(horizontal); 109 | self 110 | } 111 | 112 | /// Sets the vertical & horizontal placement relative to parent plane. 113 | /// 114 | /// Default: *`0`*, *`0`*. 115 | /// 116 | /// Effect: Sets both horizontal and vertical alignment. 117 | pub fn align(mut self, vertical: Align, horizontal: Align) -> Self { 118 | self.options.set_align(vertical, horizontal); 119 | self 120 | } 121 | 122 | // 123 | 124 | /// Sets the height of the plane (rows). 125 | /// 126 | /// Default: *`0`*. 127 | /// 128 | /// Effect: Sets the height of the plane and *unmaximizes* it. 129 | pub fn height(mut self, height: u32) -> Self { 130 | self.options.set_rows(height); 131 | self 132 | } 133 | 134 | /// Sets the width for the plane (columns). 135 | /// 136 | /// Default: *`0`*. 137 | /// 138 | /// Effect: Sets the width of the plane and *unmaximizes* it. 139 | pub fn width(mut self, width: u32) -> Self { 140 | self.options.set_cols(width); 141 | self 142 | } 143 | 144 | /// Sets the size of the plane (rows, columns). 145 | /// 146 | /// Default: *`0`*. 147 | /// 148 | /// Effect: Sets the height and width of the plane and *unmaximizes* it. 149 | pub fn size(mut self, size: impl Into) -> Self { 150 | let (width, height) = size.into().into(); 151 | self.options.set_rows_cols(height, width); 152 | self 153 | } 154 | 155 | // 156 | 157 | /// Maximizes the plane, with optional right & bottom margins. 158 | /// 159 | /// Default: *`(0, 0)`*. 160 | /// 161 | /// Effect: maximizes the plane relative to the parent plane, minus the 162 | /// provided margins. 163 | /// 164 | /// See also: [`sys::NcPlaneFlag::Marginalized`]. 165 | /// 166 | /// [`sys::NcPlaneFlag::Marginalized`]: crate::sys::NcPlaneFlag#associatedconstant.Marginalized 167 | pub fn maximize(mut self, right_bottom: impl Into) -> Self { 168 | let (x_right, y_bottom) = right_bottom.into().into(); 169 | self.options.set_margins(y_bottom, x_right); 170 | self 171 | } 172 | 173 | /// If `true`, the plane will **not** scroll with the parent. 174 | /// 175 | /// Default: *`false`* (scrolls with the parent). 176 | /// 177 | /// Effect: (un)fixes the plane. 178 | /// 179 | /// See also: [`sys::NcPlaneFlag::Fixed`]. 180 | /// 181 | /// [`sys::NcPlaneFlag::Fixed`]: crate::sys::NcPlaneFlag#associatedconstant.Fixed 182 | pub fn fixed(mut self, fixed: bool) -> Self { 183 | self.options.set_fixed(fixed); 184 | self 185 | } 186 | 187 | /// If `true`, the plane will scroll vertically to accommodate output. 188 | /// 189 | /// This is equivalent to immediately calling [`set_scrolling(true)`] 190 | /// following `Plane` creation. 191 | /// 192 | /// Default: *`false`*. 193 | /// 194 | /// Effect: (un)sets vertical scrolling. 195 | /// 196 | /// [`set_scrolling(true)`]: super::Plane#method.set_scrolling 197 | pub fn scroll(mut self, scroll: bool) -> Self { 198 | self.options.set_vscroll(scroll); 199 | self 200 | } 201 | 202 | /// If `true`, the plane will grow automatically. 203 | /// 204 | /// Default: *`false`*. 205 | /// 206 | /// Effect: (un)sets the plane to automatically grow to accomodate output. 207 | /// 208 | /// Note that just setting `autogrow` makes the plane grow to the right, 209 | /// and setting `autogrow` and `scroll` makes the plane grow downwards. 210 | pub fn autogrow(mut self, autogrow: bool) -> Self { 211 | self.options.set_autogrow(autogrow); 212 | self 213 | } 214 | 215 | // TODO: resizecb 216 | } 217 | -------------------------------------------------------------------------------- /src/plane/cell.rs: -------------------------------------------------------------------------------- 1 | // notcurses::plane::cell 2 | // 3 | //! 4 | // 5 | 6 | use crate::{ 7 | color::{Alpha, Channel, Channels}, 8 | error::NotcursesResult as Result, 9 | plane::{Plane, Style}, 10 | sys::NcCell, 11 | }; 12 | 13 | /// A `Cell` corresponds to a single *[grapheme cluster]* on some [`Plane`], 14 | /// 15 | /// A `Cell` is bounded to n `Plane`, but the cell doesn't store anything 16 | /// about the plane. 17 | /// 18 | /// At any `NcCell`, we can have a theoretically arbitrarily long UTF-8 string, 19 | /// a foreground color, a background color, and a [`Style`] attribute set. 20 | /// 21 | /// [grapheme cluster]: http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries 22 | #[derive(Clone, Copy, Default, PartialEq, Eq)] 23 | #[repr(transparent)] 24 | pub struct Cell { 25 | nc: NcCell, 26 | } 27 | 28 | mod core_impls { 29 | use super::{Cell, Channels, NcCell, Style}; 30 | use core::fmt; 31 | 32 | impl fmt::Display for Cell { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | let width = self.nc.width; 35 | let egc = if let Some(s) = self.try_egc() { 36 | format!["\"{s}\""] 37 | } else { 38 | "&ref".into() 39 | }; 40 | 41 | let style = Style::from(self.nc.stylemask); 42 | let channels = Channels::from(self.nc.channels); 43 | 44 | write!(f, "{egc} {width} {style} {channels}") 45 | } 46 | } 47 | impl fmt::Debug for Cell { 48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 49 | let width = self.nc.width; 50 | let egc = if let Some(s) = self.try_egc() { 51 | format!["\"{s}\""] 52 | } else { 53 | "&ref".into() 54 | }; 55 | 56 | let style = Style::from(self.nc.stylemask); 57 | let channels = Channels::from(self.nc.channels); 58 | 59 | write!(f, "Cell {{{egc:} width:{width} {style:?} {channels}}}") 60 | } 61 | } 62 | 63 | impl From for Cell { 64 | fn from(nc: NcCell) -> Cell { 65 | Self { nc } 66 | } 67 | } 68 | 69 | impl From for NcCell { 70 | fn from(c: Cell) -> NcCell { 71 | c.nc 72 | } 73 | } 74 | impl<'c> From<&'c Cell> for &'c NcCell { 75 | fn from(c: &'c Cell) -> &'c NcCell { 76 | &c.nc 77 | } 78 | } 79 | } 80 | 81 | /// # constructors 82 | impl Cell { 83 | /// Creates an empty cell. 84 | pub fn new() -> Cell { 85 | NcCell::new().into() 86 | } 87 | 88 | /// Returns a Cell from a string. 89 | /// 90 | /// It only stores the first extended grapheme cluster from the string. 91 | pub fn from_str(plane: &mut Plane, string: &str) -> Result { 92 | Ok(NcCell::from_str(plane.into_ref_mut(), string)?.into()) 93 | } 94 | } 95 | 96 | /// # *egc* methods 97 | impl Cell { 98 | /// Returns `true` if the egc is stored in the associated plane's *egc pool*, 99 | /// or `false` if the egc is stored entirely within the cell, 100 | /// 101 | /// Egcs of up to 4 bytes are stored in the cell. 102 | #[inline] 103 | pub const fn uses_egcpool(&self) -> bool { 104 | // If the first byte is 0x01, the rest is a 24-bit adress to an egcpool 105 | self.nc.gcluster >> 24 == 0x01 106 | } 107 | 108 | /// Returns the extended grapheme cluster only if it's stored in the cell. 109 | /// 110 | /// Returns none if the egc is stored in the associated plane. 111 | pub fn try_egc(&self) -> Option { 112 | if self.uses_egcpool() { 113 | None 114 | } else { 115 | let bytes = self.nc.gcluster.to_ne_bytes(); 116 | let no_nuls = bytes.split(|b| *b == 0).next().unwrap(); 117 | core::str::from_utf8(no_nuls).ok().map(|s| s.to_string()) 118 | } 119 | } 120 | 121 | /// Returns a reference to the *egc*. 122 | pub fn egc(&self, plane: &mut Plane) -> &str { 123 | self.nc.egc(plane.into_ref_mut()) 124 | } 125 | } 126 | 127 | /// # channel methods 128 | impl Cell { 129 | /// Gets the channels. 130 | pub fn channels(&self) -> Channels { 131 | self.nc.channels().into() 132 | } 133 | 134 | /// Gets the foreground channel. 135 | pub fn fg(&self) -> Channel { 136 | self.nc.fchannel().into() 137 | } 138 | 139 | /// Gets the background channel. 140 | pub fn bg(&self) -> Channel { 141 | self.nc.bchannel().into() 142 | } 143 | 144 | /// Sets the channels, returning the previous value. 145 | pub fn set_channels(&mut self, channels: impl Into) -> Channels { 146 | let prev = self.channels(); 147 | self.nc.set_channels(channels.into()); 148 | prev 149 | } 150 | 151 | /// Sets the foreground channel, returning the previous value. 152 | pub fn set_fg(&mut self, channel: impl Into) -> Channel { 153 | let fg = self.fg(); 154 | self.nc.set_fchannel(channel.into()); 155 | fg 156 | } 157 | 158 | /// Sets the background channel, returning the previous value. 159 | pub fn set_bg(&mut self, channel: impl Into) -> Channel { 160 | let bg = self.fg(); 161 | self.nc.set_bchannel(channel.into()); 162 | bg 163 | } 164 | 165 | // 166 | 167 | /// Chain-sets the channels. 168 | pub fn cset_channels(mut self, channels: impl Into) -> Self { 169 | self.nc.set_channels(channels.into()); 170 | self 171 | } 172 | 173 | /// Chain-sets the foreground channel. 174 | pub fn cset_fg(mut self, channel: impl Into) -> Self { 175 | self.nc.set_fchannel(channel.into()); 176 | self 177 | } 178 | 179 | /// Chain-sets the foreground channel. 180 | pub fn cset_bg(mut self, channel: impl Into) -> Self { 181 | self.nc.set_bchannel(channel.into()); 182 | self 183 | } 184 | } 185 | 186 | /// # alpha methods 187 | impl Cell { 188 | /// Gets the foreground alpha. 189 | pub fn fg_alpha(&self) -> Alpha { 190 | self.nc.fg_alpha().into() 191 | } 192 | 193 | /// Gets the background alpha. 194 | pub fn bg_alpha(&self) -> Alpha { 195 | self.nc.bg_alpha().into() 196 | } 197 | 198 | /// Sets the `foreground` alpha, returning the previous value. 199 | pub fn set_fg_alpha(&mut self, foreground: Alpha) -> Alpha { 200 | let prev = self.fg_alpha(); 201 | self.nc.set_fg_alpha(foreground); 202 | prev 203 | } 204 | 205 | /// Gets the background alpha, returning the previous value. 206 | pub fn set_bg_alpha(&mut self, background: Alpha) -> Alpha { 207 | let prev = self.bg_alpha(); 208 | self.nc.set_bg_alpha(background); 209 | prev 210 | } 211 | } 212 | 213 | /// # style methods 214 | impl Cell { 215 | /// Gets the styles. 216 | pub fn styles(&self) -> Style { 217 | self.nc.styles().into() 218 | } 219 | 220 | /// Sets the `styles`, returning the previous value. 221 | pub fn set_styles(&mut self, styles: impl Into