├── src ├── windows │ ├── win32.rs │ ├── win32a.rs │ ├── mod.rs │ └── constants.rs ├── input.rs ├── colorpair.rs ├── unix │ ├── mod.rs │ └── constants.rs ├── attributes.rs ├── lib.rs └── window.rs ├── .gitignore ├── .travis.yml ├── examples ├── hello.rs ├── getch_example.rs ├── firework.rs ├── rain.rs ├── newtest.rs └── newdemo.rs ├── .github └── workflows │ └── rust.yml ├── Cargo.toml ├── LICENSE.md ├── appveyor.yml └── README.md /src/windows/win32.rs: -------------------------------------------------------------------------------- 1 | pub fn pre_init() {} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .project 3 | Cargo.lock 4 | .settings/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | -------------------------------------------------------------------------------- /examples/hello.rs: -------------------------------------------------------------------------------- 1 | extern crate pancurses; 2 | 3 | use pancurses::{initscr, endwin}; 4 | 5 | fn main() { 6 | let window = initscr(); 7 | window.printw("Hello Rust"); 8 | window.refresh(); 9 | window.getch(); 10 | endwin(); 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest] 18 | 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Build 24 | run: cargo build --verbose 25 | -------------------------------------------------------------------------------- /examples/getch_example.rs: -------------------------------------------------------------------------------- 1 | extern crate pancurses; 2 | 3 | use pancurses::{initscr, endwin, Input, noecho}; 4 | 5 | fn main() { 6 | let window = initscr(); 7 | window.printw("Type things, press delete to quit\n"); 8 | window.refresh(); 9 | window.keypad(true); 10 | noecho(); 11 | loop { 12 | match window.getch() { 13 | Some(Input::Character(c)) => { 14 | window.addch(c); 15 | } 16 | Some(Input::KeyDC) => break, 17 | Some(input) => { 18 | window.addstr(&format!("{:?}", input)); 19 | } 20 | None => (), 21 | } 22 | } 23 | endwin(); 24 | } 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pancurses" 3 | description = """ 4 | pancurses is a curses libary for Rust that supports both Unix and Windows 5 | platforms by abstracting away the backend that it uses 6 | (ncurses-rs and pdcurses-sys respectively). 7 | """ 8 | homepage = "https://github.com/ihalila/pancurses" 9 | documentation = "https://docs.rs/pancurses" 10 | repository = "https://github.com/ihalila/pancurses" 11 | readme = "README.md" 12 | license = "MIT" 13 | keywords = ["pancurses", "curses", "ncurses", "pdcurses"] 14 | version = "0.17.0" 15 | authors = ["Ilkka Halila "] 16 | 17 | [lib] 18 | name = "pancurses" 19 | 20 | [dependencies] 21 | log = "0.4" 22 | libc = "0.2" 23 | 24 | [target.'cfg(windows)'.dependencies] 25 | pdcurses-sys = "0.7" 26 | winreg = "0.5" 27 | [target.'cfg(unix)'.dependencies] 28 | ncurses = "5.101.0" 29 | 30 | [dev-dependencies] 31 | rand = "0.8.4" 32 | 33 | [features] 34 | wide = ["ncurses/wide"] 35 | win32 = ["pdcurses-sys/win32"] 36 | win32a = ["pdcurses-sys/win32a"] 37 | show_menu = [] 38 | disable_resize = [] 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Ilkka Halila 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 2 | pub enum Input { 3 | Character(char), 4 | Unknown(i32), 5 | KeyCodeYes, 6 | 7 | KeyBreak, 8 | KeyDown, 9 | KeyUp, 10 | KeyLeft, 11 | KeyRight, 12 | KeyHome, 13 | KeyBackspace, 14 | 15 | KeyF0, 16 | KeyF1, 17 | KeyF2, 18 | KeyF3, 19 | KeyF4, 20 | KeyF5, 21 | KeyF6, 22 | KeyF7, 23 | KeyF8, 24 | KeyF9, 25 | KeyF10, 26 | KeyF11, 27 | KeyF12, 28 | KeyF13, 29 | KeyF14, 30 | KeyF15, 31 | 32 | KeyDL, 33 | KeyIL, 34 | KeyDC, 35 | KeyIC, 36 | KeyEIC, 37 | KeyClear, 38 | KeyEOS, 39 | KeyEOL, 40 | KeySF, 41 | KeySR, 42 | KeyNPage, 43 | KeyPPage, 44 | KeySTab, 45 | KeyCTab, 46 | KeyCATab, 47 | KeyEnter, 48 | KeySReset, 49 | KeyReset, 50 | KeyPrint, 51 | KeyLL, 52 | KeyAbort, 53 | KeySHelp, 54 | KeyLHelp, 55 | KeyBTab, 56 | KeyBeg, 57 | KeyCancel, 58 | KeyClose, 59 | KeyCommand, 60 | KeyCopy, 61 | KeyCreate, 62 | KeyEnd, 63 | KeyExit, 64 | KeyFind, 65 | KeyHelp, 66 | KeyMark, 67 | KeyMessage, 68 | KeyMove, 69 | KeyNext, 70 | KeyOpen, 71 | KeyOptions, 72 | KeyPrevious, 73 | KeyRedo, 74 | KeyReference, 75 | KeyRefresh, 76 | KeyReplace, 77 | KeyRestart, 78 | KeyResume, 79 | KeySave, 80 | KeySBeg, 81 | KeySCancel, 82 | KeySCommand, 83 | KeySCopy, 84 | KeySCreate, 85 | KeySDC, 86 | KeySDL, 87 | KeySelect, 88 | KeySEnd, 89 | KeySEOL, 90 | KeySExit, 91 | KeySFind, 92 | KeySHome, 93 | KeySIC, 94 | 95 | KeySLeft, 96 | KeySMessage, 97 | KeySMove, 98 | KeySNext, 99 | KeySOptions, 100 | KeySPrevious, 101 | KeySPrint, 102 | KeySRedo, 103 | KeySReplace, 104 | KeySRight, 105 | KeySResume, 106 | KeySSave, 107 | KeySSuspend, 108 | KeySUndo, 109 | KeySuspend, 110 | KeyUndo, 111 | 112 | KeyResize, 113 | KeyEvent, 114 | KeyMouse, 115 | 116 | KeyA1, 117 | KeyA3, 118 | KeyB2, 119 | KeyC1, 120 | KeyC3, 121 | } 122 | -------------------------------------------------------------------------------- /src/colorpair.rs: -------------------------------------------------------------------------------- 1 | use std::ops::BitOr; 2 | use super::{chtype, COLOR_PAIR}; 3 | use crate::attributes::{Attribute, Attributes}; 4 | 5 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 6 | pub struct ColorPair(pub u8); 7 | 8 | impl From for chtype { 9 | fn from(color_pair: ColorPair) -> chtype { 10 | COLOR_PAIR(chtype::from(color_pair.0)) 11 | } 12 | } 13 | 14 | /// Implement the | operator for setting a color pair on an `Attributes` object 15 | /// 16 | /// # Example 17 | /// 18 | /// ``` 19 | /// use pancurses::{Attribute, Attributes}; 20 | /// use pancurses::colorpair::ColorPair; 21 | /// 22 | /// let mut attributes = Attributes::new(); 23 | /// assert!(attributes.color_pair().0 == 0); 24 | /// attributes = attributes | ColorPair(1); 25 | /// assert!(attributes.color_pair().0 == 1); 26 | /// ``` 27 | impl BitOr for Attributes { 28 | type Output = Attributes; 29 | 30 | fn bitor(mut self, rhs: ColorPair) -> Attributes { 31 | self.set_color_pair(rhs); 32 | self 33 | } 34 | } 35 | 36 | /// Implement the | operator for combining a `ColorPair` and an `Attribute` to produce `Attributes` 37 | /// 38 | /// # Example 39 | /// 40 | /// ``` 41 | /// use pancurses::Attribute; 42 | /// use pancurses::colorpair::ColorPair; 43 | /// 44 | /// let attributes = ColorPair(5) | Attribute::Blink; 45 | /// assert!(attributes.color_pair().0 == 5); 46 | /// assert!(!attributes.is_bold()); 47 | /// assert!(attributes.is_blink()); 48 | /// ``` 49 | impl BitOr for ColorPair { 50 | type Output = Attributes; 51 | 52 | fn bitor(self, rhs: Attribute) -> Attributes { 53 | Attributes::new() | self | rhs 54 | } 55 | } 56 | 57 | /// Implement the | operator for combining an `Attribute` and a `ColorPair` to produce `Attributes` 58 | /// 59 | /// # Example 60 | /// 61 | /// ``` 62 | /// use pancurses::Attribute; 63 | /// use pancurses::colorpair::ColorPair; 64 | /// 65 | /// let attributes = Attribute::Blink | ColorPair(2); 66 | /// assert!(attributes.color_pair().0 == 2); 67 | /// assert!(!attributes.is_bold()); 68 | /// assert!(attributes.is_blink()); 69 | /// ``` 70 | impl BitOr for Attribute { 71 | type Output = Attributes; 72 | 73 | fn bitor(self, rhs: ColorPair) -> Attributes { 74 | Attributes::new() | self | rhs 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/windows/win32a.rs: -------------------------------------------------------------------------------- 1 | extern crate winreg; 2 | 3 | use std::env; 4 | use std::ffi::OsStr; 5 | use std::i32; 6 | use std::path::Path; 7 | 8 | use self::winreg::RegKey; 9 | use self::winreg::enums::HKEY_CURRENT_USER; 10 | use self::winreg::enums::KEY_SET_VALUE; 11 | 12 | pub fn pre_init() { 13 | let exe_name = env::current_exe() 14 | .ok() 15 | .as_ref() 16 | .map(Path::new) 17 | .and_then(Path::file_name) 18 | .and_then(OsStr::to_str) 19 | .map(String::from) 20 | .map(|mut x| { 21 | let last_dot = x.rfind('.').unwrap_or(0); 22 | x.truncate(last_dot); 23 | x 24 | }); 25 | 26 | if exe_name.is_none() { 27 | warn!("Could not determine name of exe"); 28 | return; 29 | } 30 | let exe_name = exe_name.unwrap(); 31 | 32 | // The format of the registry value is: 33 | // x,,,,;,,,: 34 | // Example: 80x25,12,312,312,1;2,2147483647,2,2147483647:Courier New 35 | let hkcu = RegKey::predef(HKEY_CURRENT_USER); 36 | let existing_value = 37 | hkcu.open_subkey("Software\\PDCurses") 38 | .and_then(|reg_key| reg_key.get_value::(&exe_name)) 39 | .unwrap_or_else(|_| "80x25,12,0,0,1;25,25,80,80:Courier New".to_string()); 40 | 41 | let rejoined_menu: String; 42 | let maxi32 = format!("{}", i32::MAX); 43 | let mut split: Vec<&str> = existing_value.split(',').collect(); 44 | 45 | // The value at index 4 will be ; because we split on , 46 | let mut menu_split: Vec<&str> = split[4].split(';').collect(); 47 | 48 | // Set the menu_shown value to 0 to always hide it 49 | #[cfg(not(feature = "show_menu"))] 50 | { 51 | menu_split[0] = "0"; 52 | } 53 | 54 | // Set the resize limits to 2 - i32::MAX to allow unlimited resizing 55 | #[cfg(not(feature = "disable_resize"))] 56 | { 57 | menu_split[1] = "2"; 58 | split[5] = &maxi32; 59 | split[6] = "2"; 60 | split[7] = &maxi32; 61 | } 62 | 63 | // Re-join the values back into a string 64 | rejoined_menu = menu_split.join(";"); 65 | split[4] = &rejoined_menu; 66 | 67 | // Write the modified values back into the registry 68 | if let Err(e) = hkcu.open_subkey_with_flags("Software\\PDCurses", KEY_SET_VALUE).and_then(|reg_key| { 69 | reg_key.set_value::(&exe_name, &split.join(",")) 70 | }) 71 | { 72 | warn!("Failed to set registry value: {}", e); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/firework.rs: -------------------------------------------------------------------------------- 1 | extern crate pancurses; 2 | extern crate rand; 3 | 4 | use pancurses::*; 5 | use rand::Rng; 6 | 7 | const DELAYSIZE: i32 = 200; 8 | 9 | const COLOR_TABLE: [i16; 8] = [ 10 | COLOR_RED, 11 | COLOR_BLUE, 12 | COLOR_GREEN, 13 | COLOR_CYAN, 14 | COLOR_RED, 15 | COLOR_MAGENTA, 16 | COLOR_YELLOW, 17 | COLOR_WHITE, 18 | ]; 19 | 20 | fn main() { 21 | let window = initscr(); 22 | window.nodelay(true); 23 | noecho(); 24 | 25 | if has_colors() { 26 | start_color(); 27 | } 28 | 29 | for (i, color) in COLOR_TABLE.iter().enumerate() { 30 | init_pair(i as i16, *color, COLOR_BLACK); 31 | } 32 | 33 | let mut rng = rand::thread_rng(); 34 | 35 | let mut flag = 0; 36 | 37 | let lines = window.get_max_y(); 38 | let cols = window.get_max_x(); 39 | 40 | while window.getch().is_none() { 41 | let mut start = 0; 42 | let mut direction = 0; 43 | let mut diff = 0; 44 | 45 | while diff < 2 || diff >= lines - 2 { 46 | start = rng.gen::() % (cols - 3); 47 | let mut end = rng.gen::() % (cols - 3); 48 | start = if start < 2 { 2 } else { start }; 49 | end = if end < 2 { 2 } else { end }; 50 | direction = if start > end { -1 } else { 1 }; 51 | diff = (start - end).abs(); 52 | } 53 | 54 | window.attrset(A_NORMAL); 55 | 56 | for row in 0..diff { 57 | window.mvaddstr( 58 | lines - row, 59 | row * direction + start, 60 | if direction < 0 { "\\" } else { "/" }, 61 | ); 62 | 63 | if flag != 0 { 64 | myrefresh(&window); 65 | window.erase(); 66 | flag = 0; 67 | } else { 68 | flag += 1; 69 | } 70 | } 71 | let row = diff; 72 | 73 | if flag != 0 { 74 | myrefresh(&window); 75 | flag = 0; 76 | } else { 77 | flag += 1; 78 | } 79 | 80 | explode(lines - row, diff * direction + start, &window, &mut rng); 81 | window.erase(); 82 | myrefresh(&window); 83 | } 84 | 85 | endwin(); 86 | } 87 | 88 | fn explode(row: i32, mut col: i32, window: &Window, rng: &mut T) { 89 | window.erase(); 90 | window.mvaddstr(row, col, "-"); 91 | myrefresh(window); 92 | 93 | col -= 1; 94 | 95 | get_color(rng, window); 96 | window.mvaddstr(row - 1, col, " - "); 97 | window.mvaddstr(row, col, "-+-"); 98 | window.mvaddstr(row + 1, col, " - "); 99 | myrefresh(window); 100 | 101 | col -= 1; 102 | 103 | get_color(rng, window); 104 | window.mvaddstr(row - 2, col, " --- "); 105 | window.mvaddstr(row - 1, col, "-+++-"); 106 | window.mvaddstr(row, col, "-+#+-"); 107 | window.mvaddstr(row + 1, col, "-+++-"); 108 | window.mvaddstr(row + 2, col, " --- "); 109 | myrefresh(window); 110 | 111 | get_color(rng, window); 112 | window.mvaddstr(row - 2, col, " +++ "); 113 | window.mvaddstr(row - 1, col, "++#++"); 114 | window.mvaddstr(row, col, "+# #+"); 115 | window.mvaddstr(row + 1, col, "++#++"); 116 | window.mvaddstr(row + 2, col, " +++ "); 117 | myrefresh(window); 118 | 119 | get_color(rng, window); 120 | window.mvaddstr(row - 2, col, " # "); 121 | window.mvaddstr(row - 1, col, "## ##"); 122 | window.mvaddstr(row, col, "# #"); 123 | window.mvaddstr(row + 1, col, "## ##"); 124 | window.mvaddstr(row + 2, col, " # "); 125 | myrefresh(window); 126 | 127 | get_color(rng, window); 128 | window.mvaddstr(row - 2, col, " # # "); 129 | window.mvaddstr(row - 1, col, "# #"); 130 | window.mvaddstr(row, col, " "); 131 | window.mvaddstr(row + 1, col, "# #"); 132 | window.mvaddstr(row + 2, col, " # # "); 133 | myrefresh(window); 134 | } 135 | 136 | fn myrefresh(window: &Window) { 137 | napms(DELAYSIZE); 138 | window.mv(window.get_max_y() - 1, window.get_max_x() - 1); 139 | window.refresh(); 140 | } 141 | 142 | fn get_color(rng: &mut T, window: &Window) { 143 | let bold = if rng.gen::() { A_BOLD } else { A_NORMAL } as chtype; 144 | window.attrset(COLOR_PAIR(rng.gen::() % 8) | bold); 145 | } 146 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Appveyor configuration template for Rust using rustup for Rust installation 2 | # https://github.com/starkat99/appveyor-rust 3 | 4 | ## Operating System (VM environment) ## 5 | 6 | # Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets. 7 | os: Visual Studio 2015 8 | 9 | ## Build Matrix ## 10 | 11 | # This configuration will setup a build for each channel & target combination (12 windows 12 | # combinations in all). 13 | # 14 | # There are 3 channels: stable, beta, and nightly. 15 | # 16 | # Alternatively, the full version may be specified for the channel to build using that specific 17 | # version (e.g. channel: 1.5.0) 18 | # 19 | # The values for target are the set of windows Rust build targets. Each value is of the form 20 | # 21 | # ARCH-pc-windows-TOOLCHAIN 22 | # 23 | # Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker 24 | # toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for 25 | # a description of the toolchain differences. 26 | # See https://github.com/rust-lang-nursery/rustup.rs/#toolchain-specification for description of 27 | # toolchains and host triples. 28 | # 29 | # Comment out channel/target combos you do not wish to build in CI. 30 | # 31 | # You may use the `cargoflags` and `RUSTFLAGS` variables to set additional flags for cargo commands 32 | # and rustc, respectively. For instance, you can uncomment the cargoflags lines in the nightly 33 | # channels to enable unstable features when building for nightly. Or you could add additional 34 | # matrix entries to test different combinations of features. 35 | environment: 36 | matrix: 37 | 38 | ### MSVC Toolchains ### 39 | 40 | # Stable 64-bit MSVC 41 | - channel: stable 42 | target: x86_64-pc-windows-msvc 43 | # Stable 32-bit MSVC 44 | - channel: stable 45 | target: i686-pc-windows-msvc 46 | # Beta 64-bit MSVC 47 | - channel: beta 48 | target: x86_64-pc-windows-msvc 49 | # Beta 32-bit MSVC 50 | - channel: beta 51 | target: i686-pc-windows-msvc 52 | # Nightly 64-bit MSVC 53 | - channel: nightly 54 | target: x86_64-pc-windows-msvc 55 | #cargoflags: --features "unstable" 56 | # Nightly 32-bit MSVC 57 | - channel: nightly 58 | target: i686-pc-windows-msvc 59 | #cargoflags: --features "unstable" 60 | 61 | ### GNU Toolchains ### 62 | 63 | # # Stable 64-bit GNU 64 | # - channel: stable 65 | # target: x86_64-pc-windows-gnu 66 | # MSYS_BITS: 64 67 | # # Stable 32-bit GNU 68 | # - channel: stable 69 | # target: i686-pc-windows-gnu 70 | # MSYS_BITS: 32 71 | # # Beta 64-bit GNU 72 | # - channel: beta 73 | # target: x86_64-pc-windows-gnu 74 | # MSYS_BITS: 64 75 | # # Beta 32-bit GNU 76 | # - channel: beta 77 | # target: i686-pc-windows-gnu 78 | # MSYS_BITS: 32 79 | # # Nightly 64-bit GNU 80 | # - channel: nightly 81 | # target: x86_64-pc-windows-gnu 82 | # MSYS_BITS: 64 83 | # #cargoflags: --features "unstable" 84 | # # Nightly 32-bit GNU 85 | # - channel: nightly 86 | # target: i686-pc-windows-gnu 87 | # MSYS_BITS: 32 88 | # #cargoflags: --features "unstable" 89 | 90 | ### Allowed failures ### 91 | 92 | # See Appveyor documentation for specific details. In short, place any channel or targets you wish 93 | # to allow build failures on (usually nightly at least is a wise choice). This will prevent a build 94 | # or test failure in the matching channels/targets from failing the entire build. 95 | matrix: 96 | allow_failures: 97 | - channel: nightly 98 | 99 | # If you only care about stable channel build failures, uncomment the following line: 100 | #- channel: beta 101 | 102 | ## Install Script ## 103 | 104 | # This is the most important part of the Appveyor configuration. This installs the version of Rust 105 | # specified by the 'channel' and 'target' environment variables from the build matrix. This uses 106 | # rustup to install Rust. 107 | # 108 | # For simple configurations, instead of using the build matrix, you can simply set the 109 | # default-toolchain and default-host manually here. 110 | install: 111 | - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 112 | - rustup-init -yv --default-toolchain %channel% --default-host %target% 113 | - set PATH=%PATH%;%USERPROFILE%\.cargo\bin 114 | - if defined MSYS_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS_BITS%\bin;C:\msys64\usr\bin 115 | - rustc -vV 116 | - cargo -vV 117 | 118 | ## Build Script ## 119 | 120 | # 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents 121 | # the "directory does not contain a project or solution file" error. 122 | build: false 123 | 124 | # Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs 125 | #directly or perform other testing commands. Rust will automatically be placed in the PATH 126 | # environment variable. 127 | test_script: 128 | - cargo test --verbose %cargoflags% 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pancurses [![Build Status](https://travis-ci.org/ihalila/pancurses.svg?branch=master)](https://travis-ci.org/ihalila/pancurses) [![Build status](https://ci.appveyor.com/api/projects/status/x4j52ihig9n2e25y?svg=true)](https://ci.appveyor.com/project/ihalila/pancurses) [![Crates.io](https://img.shields.io/crates/v/pancurses.svg)](https://crates.io/crates/pancurses) 2 | 3 | pancurses is a curses library for Rust that supports both Linux and Windows 4 | by abstracting away the backend that it uses 5 | ([ncurses-rs](https://github.com/jeaye/ncurses-rs) and 6 | [pdcurses-sys](https://github.com/ihalila/pdcurses-sys) respectively). 7 | 8 | The aim is to provide a more Rustic interface over the usual curses functions 9 | for ease of use while remaining close enough to curses to make porting easy. 10 | 11 | ## [Documentation](https://docs.rs/pancurses) 12 | 13 | ## Requirements 14 | #### Linux 15 | ncurses-rs links with the native ncurses library so that needs to be installed 16 | so that the linker can find it. 17 | 18 | Check [ncurses-rs](https://github.com/jeaye/ncurses-rs) for more details. 19 | 20 | #### Windows 21 | pdcurses-sys compiles the native PDCurses library as part of the build process, 22 | so you need to have a compatible C compiler available that matches the ABI of 23 | the version of Rust you're using (so either gcc for the GNU ABI or cl for MSVC) 24 | 25 | Check [pdcurses-sys](https://github.com/ihalila/pdcurses-sys) for more details. 26 | 27 | ## Usage 28 | Cargo.toml 29 | ```toml 30 | [dependencies] 31 | pancurses = "0.17" 32 | ``` 33 | 34 | main.rs 35 | ```rust 36 | extern crate pancurses; 37 | 38 | use pancurses::{initscr, endwin}; 39 | 40 | fn main() { 41 | let window = initscr(); 42 | window.printw("Hello Rust"); 43 | window.refresh(); 44 | window.getch(); 45 | endwin(); 46 | } 47 | ``` 48 | 49 | ## Pattern matching with getch() 50 | 51 | ```rust 52 | extern crate pancurses; 53 | 54 | use pancurses::{initscr, endwin, Input, noecho}; 55 | 56 | fn main() { 57 | let window = initscr(); 58 | window.printw("Type things, press delete to quit\n"); 59 | window.refresh(); 60 | window.keypad(true); 61 | noecho(); 62 | loop { 63 | match window.getch() { 64 | Some(Input::Character(c)) => { window.addch(c); }, 65 | Some(Input::KeyDC) => break, 66 | Some(input) => { window.addstr(&format!("{:?}", input)); }, 67 | None => () 68 | } 69 | } 70 | endwin(); 71 | } 72 | ``` 73 | 74 | ## Handling mouse input 75 | 76 | To receive mouse events you need to both enable keypad mode and set a mouse mask that corresponds 77 | to the events you are interested in. Mouse events are received in the same way as keyboard events, 78 | ie. by calling getch(). 79 | 80 | ```rust 81 | extern crate pancurses; 82 | 83 | use pancurses::{ALL_MOUSE_EVENTS, endwin, getmouse, initscr, mousemask, Input}; 84 | 85 | fn main() { 86 | let window = initscr(); 87 | 88 | window.keypad(true); // Set keypad mode 89 | mousemask(ALL_MOUSE_EVENTS, std::ptr::null_mut()); // Listen to all mouse events 90 | 91 | window.printw("Click in the terminal, press q to exit\n"); 92 | window.refresh(); 93 | 94 | loop { 95 | match window.getch() { 96 | Some(Input::KeyMouse) => { 97 | if let Ok(mouse_event) = getmouse() { 98 | window.mvprintw(1, 0, 99 | &format!("Mouse at {},{}", mouse_event.x, mouse_event.y), 100 | ); 101 | }; 102 | } 103 | Some(Input::Character(x)) if x == 'q' => break, 104 | _ => (), 105 | } 106 | } 107 | endwin(); 108 | } 109 | ``` 110 | 111 | You can also receive events for the mouse simply moving (as long as the terminal you're running on 112 | supports it) by also specifying the REPORT_MOUSE_POSITION flag: 113 | ```rust 114 | mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, std::ptr::null_mut()); 115 | ``` 116 | 117 | ## Terminal resizing 118 | 119 | Whenever the terminal is resized by the user a Input::KeyResize event is raised. You should handle 120 | this by calling ```resize_term(0, 0)``` to have curses adjust it's internal structures to match the 121 | new size. 122 | 123 | ## PDCurses (Windows) details 124 | 125 | pdcurses-sys supports two flavors of PDCurses, win32a and win32. win32a is the GDI mode while win32 126 | runs in the Windows console. win32a has better support for colors and text effects. 127 | 128 | By default the win32a flavor is used, but you can specify which one you want to use by using Cargo 129 | flags. Simply specify the feature in Cargo.toml like so: 130 | 131 | ```rust 132 | [dependencies.pancurses] 133 | version = "0.17" 134 | features = ["win32a"] 135 | ``` 136 | or 137 | 138 | ```rust 139 | [dependencies.pancurses] 140 | version = "0.17" 141 | features = ["win32"] 142 | ``` 143 | 144 | ### (Font, Paste) menu 145 | 146 | PDCurses win32a has a menu that allows you to change the font and paste text into the window. 147 | pancurses disables the window by default, though the user can still right-click the title bar to 148 | access it. If you want to retain the PDCurses default behaviour of having the menu there set the 149 | feature ```"show_menu"```. 150 | 151 | ### Resizing 152 | 153 | On win32a the default is to allow the user to freely resize the window. If you wish to disable 154 | resizing set the feature ```"disable_resize"``` 155 | 156 | ## License 157 | 158 | Licensed under the MIT license, see [LICENSE.md](LICENSE.md) 159 | -------------------------------------------------------------------------------- /examples/rain.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::many_single_char_names)] 2 | 3 | /// ************************************************************************** 4 | /// Copyright (c) 2002 Free Software Foundation, Inc. * 5 | /// * 6 | /// Permission is hereby granted, free of charge, to any person obtaining a * 7 | /// copy of this software and associated documentation files (the * 8 | /// "Software"), to deal in the Software without restriction, including * 9 | /// without limitation the rights to use, copy, modify, merge, publish, * 10 | /// distribute, distribute with modifications, sublicense, and/or sell * 11 | /// copies of the Software, and to permit persons to whom the Software is * 12 | /// furnished to do so, subject to the following conditions: * 13 | /// * 14 | /// The above copyright notice and this permission notice shall be included * 15 | /// in all copies or substantial portions of the Software. * 16 | /// * 17 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 18 | /// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 19 | /// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 20 | /// IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 21 | /// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 22 | /// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 23 | /// THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 24 | /// * 25 | /// Except as contained in this notice, the name(s) of the above copyright * 26 | /// holders shall not be used in advertising or otherwise to promote the * 27 | /// sale, use or other dealings in this Software without prior written * 28 | /// authorization. * 29 | /// ************************************************************************* 30 | 31 | extern crate pancurses; 32 | extern crate rand; 33 | 34 | use pancurses::*; 35 | use rand::Rng; 36 | 37 | fn next_j(mut j: usize, rng: &mut T, window: &Window) -> usize { 38 | if j == 0 { 39 | j = 4; 40 | } else { 41 | j -= 1; 42 | } 43 | 44 | if has_colors() { 45 | let z = rng.gen::() % 3; 46 | let mut color = COLOR_PAIR(z); 47 | 48 | if z != 0 { 49 | color |= A_BOLD; 50 | } 51 | 52 | window.attrset(color); 53 | } 54 | 55 | j 56 | } 57 | 58 | fn main() { 59 | let window = initscr(); 60 | 61 | let mut rng = rand::thread_rng(); 62 | 63 | if has_colors() { 64 | let mut bg = COLOR_BLACK; 65 | 66 | start_color(); 67 | if use_default_colors() == OK { 68 | bg = -1; 69 | } 70 | 71 | init_pair(1, COLOR_BLUE, bg); 72 | init_pair(2, COLOR_CYAN, bg); 73 | } 74 | 75 | nl(); 76 | noecho(); 77 | curs_set(0); 78 | window.timeout(0); 79 | window.keypad(true); 80 | 81 | let mut r = window.get_max_y() - 4; 82 | let mut c = window.get_max_x() - 4; 83 | 84 | let mut xpos = [0; 5]; 85 | let mut ypos = [0; 5]; 86 | 87 | for j in (0..5).rev() { 88 | xpos[j] = rng.gen::() % c + 2; 89 | ypos[j] = rng.gen::() % r + 2; 90 | } 91 | 92 | let mut j = 0; 93 | 94 | loop { 95 | let x = rng.gen::() % c + 2; 96 | let y = rng.gen::() % r + 2; 97 | 98 | window.mvaddch(y, x, '.'); 99 | 100 | window.mvaddch(ypos[j], xpos[j], 'o'); 101 | 102 | j = next_j(j, &mut rng, &window); 103 | window.mvaddch(ypos[j], xpos[j], 'O'); 104 | 105 | j = next_j(j, &mut rng, &window); 106 | window.mvaddch(ypos[j] - 1, xpos[j], '-'); 107 | window.mvaddstr(ypos[j], xpos[j] - 1, "|.|"); 108 | window.mvaddch(ypos[j] + 1, xpos[j], '-'); 109 | 110 | j = next_j(j, &mut rng, &window); 111 | window.mvaddch(ypos[j] - 2, xpos[j], '-'); 112 | window.mvaddstr(ypos[j] - 1, xpos[j] - 1, "/ \\"); 113 | window.mvaddstr(ypos[j], xpos[j] - 2, "| O |"); 114 | window.mvaddstr(ypos[j] + 1, xpos[j] - 1, "\\ /"); 115 | window.mvaddch(ypos[j] + 2, xpos[j], '-'); 116 | 117 | j = next_j(j, &mut rng, &window); 118 | window.mvaddch(ypos[j] - 2, xpos[j], ' '); 119 | window.mvaddstr(ypos[j] - 1, xpos[j] - 1, " "); 120 | window.mvaddstr(ypos[j], xpos[j] - 2, " "); 121 | window.mvaddstr(ypos[j] + 1, xpos[j] - 1, " "); 122 | window.mvaddch(ypos[j] + 2, xpos[j], ' '); 123 | 124 | xpos[j] = x; 125 | ypos[j] = y; 126 | 127 | match window.getch() { 128 | Some(Input::Character(q)) if q == 'q' || q == 'Q' => { 129 | curs_set(1); 130 | endwin(); 131 | return; 132 | } 133 | Some(Input::Character('s')) => { 134 | window.nodelay(false); 135 | } 136 | Some(Input::Character(' ')) => { 137 | window.nodelay(true); 138 | } 139 | Some(Input::KeyResize) => { 140 | resize_term(0, 0); 141 | window.erase(); 142 | r = window.get_max_y() - 4; 143 | c = window.get_max_x() - 4; 144 | } 145 | _ => {} 146 | } 147 | napms(50); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/unix/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, non_snake_case)] 2 | pub mod constants; 3 | use self::constants::*; 4 | 5 | use ncurses::{box_, getmouse, keyname, COLORS, COLOR_PAIRS}; 6 | use ncurses::ll::{chtype, ungetch, wattroff, wattron, wattrset, MEVENT, NCURSES_ATTR_T, WINDOW}; 7 | use ncurses::ll::{resize_term, wgetch}; 8 | 9 | use libc::{c_int, setlocale, LC_ALL}; 10 | use crate::input::Input; 11 | 12 | use std::ffi::CString; 13 | use std::string::FromUtf8Error; 14 | 15 | pub fn pre_init() { 16 | let buf = CString::new("").unwrap(); 17 | unsafe { setlocale(LC_ALL, buf.as_ptr()) }; 18 | } 19 | 20 | pub fn _attron(w: WINDOW, attributes: chtype) -> i32 { 21 | unsafe { wattron(w, attributes as NCURSES_ATTR_T) } 22 | } 23 | 24 | pub fn _attroff(w: WINDOW, attributes: chtype) -> i32 { 25 | unsafe { wattroff(w, attributes as NCURSES_ATTR_T) } 26 | } 27 | 28 | pub fn _attrset(w: WINDOW, attributes: chtype) -> i32 { 29 | unsafe { wattrset(w, attributes as NCURSES_ATTR_T) } 30 | } 31 | 32 | pub fn _COLORS() -> i32 { 33 | COLORS() 34 | } 35 | 36 | pub fn _COLOR_PAIRS() -> i32 { 37 | COLOR_PAIRS() 38 | } 39 | 40 | pub fn _draw_box(w: WINDOW, verch: chtype, horch: chtype) -> i32 { 41 | box_(w, verch, horch) 42 | } 43 | 44 | pub fn _getmouse() -> Result { 45 | let mut mevent = MEVENT { 46 | id: 0, 47 | x: 0, 48 | y: 0, 49 | z: 0, 50 | bstate: 0, 51 | }; 52 | let error = getmouse(&mut mevent); 53 | if error == 0 { 54 | Ok(mevent) 55 | } else { 56 | Err(error) 57 | } 58 | } 59 | 60 | pub fn _keyname(code: i32) -> Option { 61 | keyname(code) 62 | } 63 | 64 | pub fn _resize_term(nlines: i32, ncols: i32) -> i32 { 65 | unsafe { resize_term(nlines, ncols) } 66 | } 67 | 68 | pub fn _set_blink(_: bool) -> i32 { 69 | 0 // Not supported 70 | } 71 | 72 | pub fn _set_title(_: &str) { 73 | //Not supported 74 | } 75 | 76 | /// Converts an integer returned by getch() to a Input value 77 | pub fn to_special_keycode(i: i32) -> Option { 78 | let index = if i <= KEY_F15 { 79 | i - KEY_OFFSET 80 | } else { 81 | i - KEY_OFFSET - 48 82 | }; 83 | if index < 0 || index as usize >= SPECIAL_KEY_CODES.len() { 84 | None 85 | } else { 86 | Some(SPECIAL_KEY_CODES[index as usize]) 87 | } 88 | } 89 | 90 | pub fn _ungetch(input: &Input) -> i32 { 91 | match *input { 92 | Input::Character(c) => { 93 | // Need to convert to UTF-8 bytes, it's how we get them from getch() 94 | let mut utf8_buffer = [0; 4]; 95 | c.encode_utf8(&mut utf8_buffer) 96 | .as_bytes() 97 | .iter() 98 | .rev() 99 | .map(|x| unsafe { ungetch(*x as c_int) }) 100 | .fold(0, |res, x| res.min(x)) 101 | } 102 | Input::Unknown(i) => unsafe { ungetch(i) }, 103 | specialKeyCode => { 104 | for (i, skc) in SPECIAL_KEY_CODES.iter().enumerate() { 105 | if *skc == specialKeyCode { 106 | let result = i as c_int + KEY_OFFSET; 107 | if result <= KEY_F15 { 108 | return unsafe { ungetch(result) }; 109 | } else { 110 | return unsafe { ungetch(result + 48) }; 111 | } 112 | } 113 | } 114 | panic!("Failed to convert Input back to a c_int"); 115 | } 116 | } 117 | } 118 | 119 | pub fn _wgetch(w: WINDOW) -> Option { 120 | let i = unsafe { wgetch(w) }; 121 | if i < 0 { 122 | None 123 | } else { 124 | Some(to_special_keycode(i).unwrap_or_else(|| { 125 | // Assume that on Linux input is UTF-8 126 | fn try_decode(mut v: Vec, w: WINDOW) -> Result { 127 | let res = String::from_utf8(v.clone()); 128 | if res.is_err() && v.len() < 4 { 129 | let next_byte = unsafe { wgetch(w) }; 130 | v.push(next_byte as u8); 131 | try_decode(v, w) 132 | } else { 133 | res 134 | } 135 | } 136 | 137 | let v = vec![i as u8]; 138 | try_decode(v, w) 139 | .map(|s| Input::Character(s.chars().next().unwrap())) 140 | .unwrap_or_else(|error| { 141 | warn!("Decoding input as UTF-8 failed: {:?}", error); 142 | Input::Unknown(i) 143 | }) 144 | })) 145 | } 146 | } 147 | 148 | #[cfg(test)] 149 | mod tests { 150 | use super::*; 151 | use crate::input::Input; 152 | use ncurses::{endwin, initscr}; 153 | 154 | #[test] 155 | fn test_key_dl_to_special_keycode() { 156 | let keyDl = 0o510; 157 | assert_eq!(Input::KeyDL, to_special_keycode(keyDl).unwrap()); 158 | } 159 | 160 | #[test] 161 | fn test_key_f15_to_input() { 162 | let keyF15 = 0o410 + 15; 163 | assert_eq!(Input::KeyF15, to_special_keycode(keyF15).unwrap()); 164 | } 165 | 166 | #[test] 167 | fn test_key_up_to_input() { 168 | let keyUp = 0o403; 169 | assert_eq!(Input::KeyUp, to_special_keycode(keyUp).unwrap()); 170 | } 171 | 172 | #[test] 173 | fn test_ungetch() { 174 | let w = initscr(); 175 | 176 | let chars = [ 177 | 'a', 'b', 'c', 'ä', 'ö', 'å', 'A', 'B', 'C', 'Ä', 'Ö', 'Å', '𤭢', '𐍈', 178 | '€', 'ᚠ', 'ᛇ', 'ᚻ', 'þ', 'ð', 'γ', 'λ', 'ώ', 'б', 'е', 'р', 'ვ', 179 | 'ე', 'პ', 'ხ', 'இ', 'ங', 'க', 'ಬ', 'ಇ', 'ಲ', 'ಸ', 180 | ]; 181 | 182 | chars.iter().for_each(|c| { 183 | _ungetch(&Input::Character(*c)); 184 | assert_eq!(_wgetch(w).unwrap(), Input::Character(*c)); 185 | }); 186 | 187 | SPECIAL_KEY_CODES.iter().for_each(|i| { 188 | _ungetch(i); 189 | assert_eq!(_wgetch(w).unwrap(), *i); 190 | }); 191 | 192 | endwin(); 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /src/unix/constants.rs: -------------------------------------------------------------------------------- 1 | use crate::input::Input; 2 | use ncurses; 3 | use ncurses::ll::{attr_t, chtype, mmask_t}; 4 | 5 | pub use ncurses::{ACS_BBSS, ACS_BSSB, ACS_SBBS, ACS_SBSS, ACS_SSBB, ACS_SSBS, ACS_SSSB}; 6 | pub use ncurses::{ 7 | ACS_BLOCK, ACS_BOARD, ACS_DARROW, ACS_LANTERN, ACS_LARROW, ACS_RARROW, ACS_UARROW, 8 | }; 9 | pub use ncurses::{ACS_BSBS, ACS_BSSS, ACS_SBSB, ACS_SSSS}; 10 | pub use ncurses::{ACS_BTEE, ACS_HLINE, ACS_LTEE, ACS_PLUS, ACS_RTEE, ACS_TTEE, ACS_VLINE}; 11 | pub use ncurses::{ACS_BULLET, ACS_CKBOARD, ACS_DEGREE, ACS_DIAMOND, ACS_PLMINUS, ACS_S1, ACS_S9}; 12 | pub use ncurses::{ACS_GEQUAL, ACS_LEQUAL, ACS_NEQUAL, ACS_PI, ACS_S3, ACS_S7, ACS_STERLING}; 13 | pub use ncurses::{ACS_LLCORNER, ACS_LRCORNER, ACS_ULCORNER, ACS_URCORNER}; 14 | 15 | // We need to re-export the BUTTONX_Y constants as 'mmask_t's, otherwise you can't compare them 16 | // to a MEVENT's bstate without casting 17 | pub const BUTTON1_PRESSED: mmask_t = ncurses::BUTTON1_PRESSED as mmask_t; 18 | pub const BUTTON1_RELEASED: mmask_t = ncurses::BUTTON1_RELEASED as mmask_t; 19 | pub const BUTTON1_CLICKED: mmask_t = ncurses::BUTTON1_CLICKED as mmask_t; 20 | pub const BUTTON1_DOUBLE_CLICKED: mmask_t = ncurses::BUTTON1_DOUBLE_CLICKED as mmask_t; 21 | pub const BUTTON1_TRIPLE_CLICKED: mmask_t = ncurses::BUTTON1_TRIPLE_CLICKED as mmask_t; 22 | 23 | pub const BUTTON2_PRESSED: mmask_t = ncurses::BUTTON2_PRESSED as mmask_t; 24 | pub const BUTTON2_RELEASED: mmask_t = ncurses::BUTTON2_RELEASED as mmask_t; 25 | pub const BUTTON2_CLICKED: mmask_t = ncurses::BUTTON2_CLICKED as mmask_t; 26 | pub const BUTTON2_DOUBLE_CLICKED: mmask_t = ncurses::BUTTON2_DOUBLE_CLICKED as mmask_t; 27 | pub const BUTTON2_TRIPLE_CLICKED: mmask_t = ncurses::BUTTON2_TRIPLE_CLICKED as mmask_t; 28 | 29 | pub const BUTTON3_PRESSED: mmask_t = ncurses::BUTTON3_PRESSED as mmask_t; 30 | pub const BUTTON3_RELEASED: mmask_t = ncurses::BUTTON3_RELEASED as mmask_t; 31 | pub const BUTTON3_CLICKED: mmask_t = ncurses::BUTTON3_CLICKED as mmask_t; 32 | pub const BUTTON3_DOUBLE_CLICKED: mmask_t = ncurses::BUTTON3_DOUBLE_CLICKED as mmask_t; 33 | pub const BUTTON3_TRIPLE_CLICKED: mmask_t = ncurses::BUTTON3_TRIPLE_CLICKED as mmask_t; 34 | 35 | pub const BUTTON4_PRESSED: mmask_t = ncurses::BUTTON4_PRESSED as mmask_t; 36 | pub const BUTTON4_RELEASED: mmask_t = ncurses::BUTTON4_RELEASED as mmask_t; 37 | pub const BUTTON4_CLICKED: mmask_t = ncurses::BUTTON4_CLICKED as mmask_t; 38 | pub const BUTTON4_DOUBLE_CLICKED: mmask_t = ncurses::BUTTON4_DOUBLE_CLICKED as mmask_t; 39 | pub const BUTTON4_TRIPLE_CLICKED: mmask_t = ncurses::BUTTON4_TRIPLE_CLICKED as mmask_t; 40 | 41 | pub const BUTTON5_PRESSED: mmask_t = ncurses::BUTTON5_PRESSED as mmask_t; 42 | pub const BUTTON5_RELEASED: mmask_t = ncurses::BUTTON5_RELEASED as mmask_t; 43 | pub const BUTTON5_CLICKED: mmask_t = ncurses::BUTTON5_CLICKED as mmask_t; 44 | pub const BUTTON5_DOUBLE_CLICKED: mmask_t = ncurses::BUTTON5_DOUBLE_CLICKED as mmask_t; 45 | pub const BUTTON5_TRIPLE_CLICKED: mmask_t = ncurses::BUTTON5_TRIPLE_CLICKED as mmask_t; 46 | 47 | pub const REPORT_MOUSE_POSITION: mmask_t = ncurses::REPORT_MOUSE_POSITION as mmask_t; 48 | pub const BUTTON_SHIFT: mmask_t = ncurses::BUTTON_SHIFT as mmask_t; 49 | pub const BUTTON_CTRL: mmask_t = ncurses::BUTTON_CTRL as mmask_t; 50 | pub const BUTTON_ALT: mmask_t = ncurses::BUTTON_ALT as mmask_t; 51 | 52 | pub fn COLOR_PAIR(n: chtype) -> attr_t { 53 | ncurses::COLOR_PAIR(n as i16) 54 | } 55 | 56 | pub use ncurses::COLOR_BLACK; 57 | pub use ncurses::COLOR_BLUE; 58 | pub use ncurses::COLOR_CYAN; 59 | pub use ncurses::COLOR_GREEN; 60 | pub use ncurses::COLOR_MAGENTA; 61 | pub use ncurses::COLOR_RED; 62 | pub use ncurses::COLOR_WHITE; 63 | pub use ncurses::COLOR_YELLOW; 64 | 65 | pub const A_ALTCHARSET: attr_t = ncurses::A_ALTCHARSET(); 66 | pub const A_ATTRIBUTES: attr_t = ncurses::A_ATTRIBUTES(); 67 | pub const A_BLINK: attr_t = ncurses::A_BLINK(); 68 | pub const A_BOLD: attr_t = ncurses::A_BOLD(); 69 | pub const A_CHARTEXT: attr_t = ncurses::A_CHARTEXT(); 70 | pub const A_COLOR: attr_t = ncurses::A_COLOR(); 71 | pub const A_DIM: attr_t = ncurses::A_DIM(); 72 | pub const A_ITALIC: attr_t = ncurses::A_ITALIC(); 73 | pub const A_INVIS: attr_t = ncurses::A_INVIS(); 74 | pub const A_LEFTLINE: attr_t = 0; // Not supported on ncurses 75 | pub const A_NORMAL: attr_t = ncurses::A_NORMAL(); 76 | pub const A_OVERLINE: attr_t = 0; // Not supported on ncurses 77 | pub const A_REVERSE: attr_t = ncurses::A_REVERSE(); 78 | pub const A_RIGHTLINE: attr_t = 0; // Not supported on ncurses 79 | pub const A_STANDOUT: attr_t = ncurses::A_STANDOUT(); 80 | pub const A_STRIKEOUT: attr_t = 0; // Not supported on ncurses 81 | pub const A_UNDERLINE: attr_t = ncurses::A_UNDERLINE(); 82 | 83 | pub const KEY_OFFSET: i32 = 0o0400; 84 | pub const KEY_RESIZE: i32 = ncurses::KEY_RESIZE; 85 | pub const KEY_F15: i32 = ncurses::KEY_F15; 86 | pub const KEY_EVENT: i32 = ncurses::KEY_EVENT; 87 | 88 | pub const SPECIAL_KEY_CODES: [Input; 108] = [ 89 | Input::KeyCodeYes, 90 | Input::KeyBreak, 91 | Input::KeyDown, 92 | Input::KeyUp, 93 | Input::KeyLeft, 94 | Input::KeyRight, 95 | Input::KeyHome, 96 | Input::KeyBackspace, 97 | Input::KeyF0, 98 | Input::KeyF1, 99 | Input::KeyF2, 100 | Input::KeyF3, 101 | Input::KeyF4, 102 | Input::KeyF5, 103 | Input::KeyF6, 104 | Input::KeyF7, 105 | Input::KeyF8, 106 | Input::KeyF9, 107 | Input::KeyF10, 108 | Input::KeyF11, 109 | Input::KeyF12, 110 | Input::KeyF13, 111 | Input::KeyF14, 112 | Input::KeyF15, 113 | // ncurses reserves space for 64 function keys, but we've 114 | // only implemented 15. This has to be taken into account 115 | // when converting the integer into an index of this array 116 | Input::KeyDL, 117 | Input::KeyIL, 118 | Input::KeyDC, 119 | Input::KeyIC, 120 | Input::KeyEIC, 121 | Input::KeyClear, 122 | Input::KeyEOS, 123 | Input::KeyEOL, 124 | Input::KeySF, 125 | Input::KeySR, 126 | Input::KeyNPage, 127 | Input::KeyPPage, 128 | Input::KeySTab, 129 | Input::KeyCTab, 130 | Input::KeyCATab, 131 | Input::KeyEnter, 132 | Input::KeySReset, 133 | Input::KeyReset, 134 | Input::KeyPrint, 135 | Input::KeyLL, 136 | Input::KeyA1, 137 | Input::KeyA3, 138 | Input::KeyB2, 139 | Input::KeyC1, 140 | Input::KeyC3, 141 | Input::KeyBTab, 142 | Input::KeyBeg, 143 | Input::KeyCancel, 144 | Input::KeyClose, 145 | Input::KeyCommand, 146 | Input::KeyCopy, 147 | Input::KeyCreate, 148 | Input::KeyEnd, 149 | Input::KeyExit, 150 | Input::KeyFind, 151 | Input::KeyHelp, 152 | Input::KeyMark, 153 | Input::KeyMessage, 154 | Input::KeyMove, 155 | Input::KeyNext, 156 | Input::KeyOpen, 157 | Input::KeyOptions, 158 | Input::KeyPrevious, 159 | Input::KeyRedo, 160 | Input::KeyReference, 161 | Input::KeyRefresh, 162 | Input::KeyReplace, 163 | Input::KeyRestart, 164 | Input::KeyResume, 165 | Input::KeySave, 166 | Input::KeySBeg, 167 | Input::KeySCancel, 168 | Input::KeySCommand, 169 | Input::KeySCopy, 170 | Input::KeySCreate, 171 | Input::KeySDC, 172 | Input::KeySDL, 173 | Input::KeySelect, 174 | Input::KeySEnd, 175 | Input::KeySEOL, 176 | Input::KeySExit, 177 | Input::KeySFind, 178 | Input::KeySHelp, 179 | Input::KeySHome, 180 | Input::KeySIC, 181 | Input::KeySLeft, 182 | Input::KeySMessage, 183 | Input::KeySMove, 184 | Input::KeySNext, 185 | Input::KeySOptions, 186 | Input::KeySPrevious, 187 | Input::KeySPrint, 188 | Input::KeySRedo, 189 | Input::KeySReplace, 190 | Input::KeySRight, 191 | Input::KeySResume, 192 | Input::KeySSave, 193 | Input::KeySSuspend, 194 | Input::KeySUndo, 195 | Input::KeySuspend, 196 | Input::KeyUndo, 197 | Input::KeyMouse, 198 | Input::KeyResize, 199 | Input::KeyEvent, 200 | ]; 201 | 202 | pub const ALL_MOUSE_EVENTS: mmask_t = ncurses::ALL_MOUSE_EVENTS as mmask_t; 203 | -------------------------------------------------------------------------------- /src/windows/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, non_snake_case)] 2 | use pdcurses::*; 3 | use libc::c_int; 4 | 5 | use std::ffi::{CStr, CString}; 6 | use std::char::decode_utf16; 7 | use std::iter; 8 | use std::cmp; 9 | 10 | pub mod constants; 11 | use self::constants::*; 12 | 13 | use input::Input; 14 | 15 | #[cfg(any(feature = "win32a", all(not(feature = "win32"), not(feature = "win32a"))))] 16 | mod win32a; 17 | #[cfg(any(feature = "win32a", all(not(feature = "win32"), not(feature = "win32a"))))] 18 | use self::win32a as flavor; 19 | 20 | #[cfg(feature = "win32")] 21 | mod win32; 22 | #[cfg(feature = "win32")] 23 | use self::win32 as flavor; 24 | 25 | pub use self::flavor::pre_init; 26 | 27 | pub fn _attron(w: *mut WINDOW, attributes: chtype) -> i32 { 28 | unsafe { wattron(w, attributes) } 29 | } 30 | 31 | pub fn _attroff(w: *mut WINDOW, attributes: chtype) -> i32 { 32 | unsafe { wattroff(w, attributes) } 33 | } 34 | 35 | pub fn _attrset(w: *mut WINDOW, attributes: chtype) -> i32 { 36 | unsafe { wattrset(w, attributes) } 37 | } 38 | 39 | pub fn _COLORS() -> i32 { 40 | unsafe { COLORS } 41 | } 42 | 43 | pub fn _COLOR_PAIRS() -> i32 { 44 | unsafe { COLOR_PAIRS } 45 | } 46 | 47 | pub fn _draw_box(w: *mut WINDOW, verch: chtype, horch: chtype) -> i32 { 48 | unsafe { _box(w, verch, horch) } 49 | } 50 | 51 | pub fn _getmouse() -> Result { 52 | let mut mevent = MEVENT { 53 | id: 0, 54 | x: 0, 55 | y: 0, 56 | z: 0, 57 | bstate: 0, 58 | }; 59 | let error = unsafe { nc_getmouse(&mut mevent) }; 60 | if error == 0 { 61 | Ok(mevent) 62 | } else { 63 | Err(error) 64 | } 65 | } 66 | 67 | pub fn _keyname(code: i32) -> Option { 68 | let ptr = unsafe { keyname(code) }; 69 | if ptr.is_null() { 70 | None 71 | } else { 72 | unsafe { 73 | // First, get a byte slice of the returned name 74 | let bytes = CStr::from_ptr(ptr).to_bytes(); 75 | // Then assume it's proper UF8 and allocate a String for it. 76 | Some(String::from_utf8_unchecked(bytes.to_vec())) 77 | } 78 | } 79 | } 80 | 81 | pub fn _resize_term(nlines: i32, ncols: i32) -> i32 { 82 | unsafe { resize_term(nlines, ncols) } 83 | } 84 | 85 | pub fn _set_blink(enabled: bool) -> i32 { 86 | unsafe { PDC_set_blink(enabled as u8) } 87 | } 88 | 89 | pub fn _set_title(title: &str) { 90 | let s = CString::new(title).unwrap(); 91 | unsafe { PDC_set_title(s.as_ptr()) } 92 | } 93 | 94 | /// Converts an integer returned by getch() to an Input value 95 | pub fn to_special_keycode(i: i32) -> Option { 96 | // There's two sets of integer constants defined: 97 | // - The SPECIAL_KEY_CODES array that contains all codes that are adjacent to each 98 | // other for easy indexing into it 99 | // - A bunch of scattered constants that need to be checked for 100 | // TODO: Unify the constants into a map 101 | match i { 102 | KEY_RESIZE => Some(Input::KeyResize), 103 | KEY_MOUSE => Some(Input::KeyMouse), 104 | 105 | KEY_NUMPAD_UP => Some(Input::KeyUp), 106 | KEY_NUMPAD_DOWN => Some(Input::KeyDown), 107 | KEY_NUMPAD_LEFT => Some(Input::KeyLeft), 108 | KEY_NUMPAD_RIGHT => Some(Input::KeyRight), 109 | KEY_NUMPAD_END => Some(Input::KeyEnd), 110 | KEY_NUMPAD_HOME => Some(Input::KeyHome), 111 | KEY_NUMPAD_PAGE_UP => Some(Input::KeyPPage), 112 | KEY_NUMPAD_PAGE_DOWN => Some(Input::KeyNPage), 113 | KEY_NUMPAD_INSERT => Some(Input::KeyIC), 114 | KEY_NUMPAD_DELETE => Some(Input::KeyDC), 115 | KEY_NUMPAD_ENTER => Some(Input::Character('\n')), 116 | KEY_NUMPAD_PLUS => Some(Input::Character('+')), 117 | KEY_NUMPAD_MINUS => Some(Input::Character('-')), 118 | KEY_NUMPAD_ASTERISK => Some(Input::Character('*')), 119 | KEY_NUMPAD_SLASH => Some(Input::Character('/')), 120 | 121 | _ => { 122 | // Since not all special key codes have been added to the SPECIAL_KEY_CODES array, 123 | // we need to do some basic math if this input lands into it. 124 | let index = if i <= KEY_F15 { 125 | i - KEY_OFFSET // Input that is less than KEY_F15 can be converted directly into an 126 | // an index of the SPECIAL_KEY_CODES array. 127 | } else { 128 | i - KEY_OFFSET - 48 // Input past KEY_F15 has to be offset down a bit, since PDCurses 129 | // has values for 64 function keys 130 | }; 131 | if index < 0 || index as usize >= SPECIAL_KEY_CODES.len() { 132 | // Input is something else. This may require more processing to convert properly into utf8 133 | None 134 | } else { 135 | Some(SPECIAL_KEY_CODES[index as usize]) 136 | } 137 | } 138 | } 139 | } 140 | 141 | pub fn _wgetch(w: *mut WINDOW) -> Option { 142 | let i = unsafe { wgetch(w) }; 143 | if i < 0 { 144 | None 145 | } else { 146 | Some(to_special_keycode(i).unwrap_or_else(|| { 147 | // Assume that on Windows input is UTF-16 148 | // If decoding the single input value fails, it should mean that it is the leading part of a 149 | // surrogate pair so calling getch() again should return the trailing part 150 | 151 | decode_utf16(iter::once(i as u16)) 152 | .map(|result| { 153 | result 154 | .map(Input::Character) 155 | .unwrap_or_else(|first_error| { 156 | let trailing = unsafe { wgetch(w) }; 157 | let data = [i as u16, trailing as u16]; 158 | decode_utf16(data.iter().cloned()) 159 | .map(|result| { 160 | result.map(Input::Character).unwrap_or_else( 161 | |second_error| { 162 | warn!("Decoding input as UTF-16 failed. The two values that could not be decoded were {} and {}.", first_error.unpaired_surrogate(), second_error.unpaired_surrogate()); 163 | Input::Unknown(second_error.unpaired_surrogate() as i32) 164 | }, 165 | ) 166 | }) 167 | .next() 168 | .unwrap() 169 | }) 170 | }) 171 | .next() 172 | .unwrap() 173 | })) 174 | } 175 | } 176 | 177 | pub fn _ungetch(input: &Input) -> i32 { 178 | match *input { 179 | Input::Character(c) => { 180 | // Need to convert to UTF-16 since Rust chars are UTF-8 while PDCurses deals with UTF-16 181 | let mut utf16_buffer = [0; 2]; 182 | c.encode_utf16(&mut utf16_buffer) 183 | .iter_mut() 184 | .rev() 185 | .map(|x| unsafe { PDC_ungetch(*x as c_int) }) 186 | .fold(0, cmp::min) 187 | } 188 | Input::Unknown(i) => unsafe { PDC_ungetch(i) }, 189 | Input::KeyResize => unsafe { PDC_ungetch(KEY_RESIZE) }, 190 | Input::KeyMouse => unsafe { PDC_ungetch(KEY_MOUSE) }, 191 | specialKeyCode => { 192 | for (i, skc) in SPECIAL_KEY_CODES.iter().enumerate() { 193 | if *skc == specialKeyCode { 194 | let result = i as c_int + KEY_OFFSET; 195 | if result <= KEY_F15 { 196 | return unsafe { PDC_ungetch(result) }; 197 | } else { 198 | return unsafe { PDC_ungetch(result + 48) }; 199 | } 200 | } 201 | } 202 | panic!("Failed to convert Input back to a c_int"); 203 | } 204 | } 205 | } 206 | 207 | #[cfg(test)] 208 | mod tests { 209 | use super::*; 210 | use input::Input; 211 | 212 | #[test] 213 | fn test_key_dl_to_special_keycode() { 214 | assert_eq!(Input::KeyDL, to_special_keycode(KEY_OFFSET + 0x48).unwrap()); 215 | } 216 | 217 | #[test] 218 | fn test_key_f15_to_input() { 219 | assert_eq!( 220 | Input::KeyF15, 221 | to_special_keycode(KEY_OFFSET + 0x08 + 15).unwrap() 222 | ); 223 | } 224 | 225 | #[test] 226 | fn test_key_up_to_input() { 227 | assert_eq!(Input::KeyUp, to_special_keycode(KEY_OFFSET + 3).unwrap()); 228 | } 229 | 230 | #[test] 231 | fn test_ungetch() { 232 | let w = unsafe { initscr() }; 233 | 234 | let chars = [ 235 | 'a', 'b', 'c', 'ä', 'ö', 'å', 'A', 'B', 'C', 'Ä', 'Ö', 'Å', '𤭢', '𐍈', 236 | '€', 'ᚠ', 'ᛇ', 'ᚻ', 'þ', 'ð', 'γ', 'λ', 'ώ', 'б', 'е', 'р', 'ვ', 237 | 'ე', 'პ', 'ხ', 'இ', 'ங', 'க', 'ಬ', 'ಇ', 'ಲ', 'ಸ', 238 | ]; 239 | 240 | chars.iter().for_each(|c| { 241 | _ungetch(&Input::Character(*c)); 242 | assert_eq!(_wgetch(w).unwrap(), Input::Character(*c)); 243 | }); 244 | 245 | SPECIAL_KEY_CODES.iter().for_each(|i| { 246 | _ungetch(i); 247 | assert_eq!(_wgetch(w).unwrap(), *i); 248 | }); 249 | 250 | unsafe { 251 | endwin(); 252 | } 253 | } 254 | 255 | } 256 | -------------------------------------------------------------------------------- /examples/newtest.rs: -------------------------------------------------------------------------------- 1 | extern crate pancurses; 2 | 3 | use pancurses::*; 4 | 5 | fn main() { 6 | let window = initscr(); 7 | 8 | start_color(); 9 | use_default_colors(); 10 | set_blink(true); 11 | 12 | cbreak(); 13 | noecho(); 14 | 15 | window.clear(); 16 | window.refresh(); 17 | 18 | window.keypad(true); 19 | 20 | init_pair(1, 15, COLOR_BLACK); 21 | init_pair(2, COLOR_BLACK, COLOR_YELLOW); 22 | 23 | set_title("NewTest: tests various pancurses features"); 24 | 25 | mousemask(ALL_MOUSE_EVENTS, None); 26 | 27 | window.attrset(COLOR_PAIR(1)); 28 | 29 | let mut quit = false; 30 | let mut redraw = true; 31 | 32 | const COL1: i32 = 2; 33 | const COL2: i32 = COL1 + 30; 34 | const COL3: i32 = 72; 35 | const N_CURSORS: i32 = 9; 36 | 37 | let mut unicode_offset = 0x80; 38 | let mut blink_state = true; 39 | 40 | let mut cursor_state_1 = 2; 41 | let mut cursor_state_2 = 3; 42 | 43 | while !quit { 44 | let (y_max, x_max) = window.get_max_yx(); 45 | let color_block_start = 54; 46 | let mut color_block_cols = (x_max - color_block_start) / 2; 47 | let color_block_lines = 19; 48 | 49 | let cursor_state_text = [ 50 | "Invisible (click to change) ", 51 | "Underscore (click to change)", 52 | "Block (click to change) ", 53 | "Outline (click to change) ", 54 | "Caret (click to change) ", 55 | "Half-block (click to change)", 56 | "Central (click to change) ", 57 | "Cross (click to change) ", 58 | "Heavy box (click to change) ", 59 | ]; 60 | 61 | if color_block_cols < 0 { 62 | color_block_cols = 0; 63 | } 64 | 65 | if redraw { 66 | window.mvaddstr(1, COL1, "'Normal' white-on-black"); 67 | 68 | window.attron(Attribute::Dim); 69 | window.mvaddstr(2, COL1, "Dimmed text"); 70 | window.attroff(Attribute::Dim); 71 | 72 | window.attron(Attribute::Blink); 73 | window.mvaddstr(6, 40, "Blinking"); 74 | window.attron(Attribute::Bold); 75 | window.mvaddstr(8, 40, "BlinkBold"); 76 | window.attron(Attribute::Italic); 77 | window.mvaddstr(0, COL2, "BlinkBoldItalic"); 78 | window.attrset(ColorPair(3)); 79 | window.attron(Attribute::Underline); 80 | 81 | window.mvaddstr(1, COL2, "Underlined"); 82 | 83 | window.attrset(ColorPair(1)); 84 | window.attron(Attribute::Underline | Attribute::Italic); 85 | window.mvaddstr(2, COL2, "UnderlinedItalic"); 86 | window.attrset(ColorPair(2)); 87 | window.attron(Attribute::Blink); 88 | window.mvaddstr(4, COL1, "Black-on-yellow blinking"); 89 | 90 | window.attrset(ColorPair(1)); 91 | window.mv(4, COL2); 92 | text_in_a_box("Text in a box", &window); 93 | 94 | window.attrset(ColorPair(6)); 95 | window.attron(Attribute::Strikeout); 96 | window.mvaddstr(10, 40, "Strikeout"); 97 | window.attrset(ColorPair(1)); 98 | 99 | window.mv(11, 40); 100 | text_in_a_box("Next Ucode pg", &window); 101 | if unicode_offset != 0 { 102 | window.mv(12, 40); 103 | text_in_a_box("Prev Ucode pg", &window); 104 | } 105 | window.mvprintw(13, 40, &format!("U+{:04X} ", unicode_offset)); 106 | 107 | for i in 0..128 { 108 | // Show extended characters 109 | window.mvaddstr( 110 | 5 + i % 16, 111 | (i / 16) * 5, 112 | &format!("{:02X} ", i + unicode_offset), 113 | ); 114 | if i + unicode_offset > ' ' as i32 { 115 | window.addch((i + unicode_offset) as chtype); 116 | } else { 117 | window.addch(' '); 118 | } 119 | window.addch(' '); 120 | } 121 | 122 | // Skipped the full RGB example, I don't think there's a suitable feature in ncurses 123 | 124 | redraw = false; 125 | window.attrset(COLOR_PAIR(1)); 126 | 127 | for i in 0..6 { 128 | let line = 24 + i / 3; 129 | let col = 5 + 25 * (i % 3); 130 | 131 | let spanish = "Español"; 132 | let russian = "Русский язык"; 133 | let greek = "Ελληνικά"; 134 | let georgian = "ქართული ენა"; 135 | let fullwidth = " Fullwidth"; 136 | let combining_marks = "Cmin  r "; 137 | 138 | let texts = [ 139 | spanish, 140 | russian, 141 | greek, 142 | georgian, 143 | fullwidth, 144 | combining_marks, 145 | ]; 146 | 147 | if line < y_max && col < x_max { 148 | window.mvaddnstr(line, 5 + 25 * (i % 3), texts[i as usize], x_max - col); 149 | } 150 | } 151 | 152 | window.mvaddstr(1, COL3, "Click on cursor descriptions to"); 153 | window.mvaddstr(2, COL3, "cycle through possible cursors"); 154 | window.mvaddstr(3, COL3, "Click on colors at left to change"); 155 | window.mvaddstr(4, COL3, "colors used for under/over/outlining"); 156 | window.mvaddstr(5, COL3, "Click 'Blink' at bottom to toggle"); 157 | window.mvaddstr(6, COL3, "'real' blinking vs. 'highlit' blink"); 158 | } 159 | 160 | window.mvaddnstr( 161 | 19, 162 | color_block_start, 163 | cursor_state_text[cursor_state_1], 164 | x_max - color_block_start, 165 | ); 166 | window.mvaddnstr( 167 | 20, 168 | color_block_start, 169 | cursor_state_text[cursor_state_2], 170 | x_max - color_block_start, 171 | ); 172 | 173 | for i in 0..color_block_cols * color_block_lines { 174 | let n_color_blocks = 256; 175 | 176 | window.attrset(COLOR_PAIR( 177 | if i >= n_color_blocks { 2 } else { i as chtype }, 178 | )); 179 | if i > 2 && i < n_color_blocks { 180 | init_pair(i as i16, i as i16, COLOR_BLACK); 181 | } 182 | if i % color_block_cols == 0 { 183 | window.mv(i / color_block_cols, color_block_start); 184 | } 185 | window.attron(A_REVERSE); 186 | window.addstr(" "); 187 | } 188 | 189 | window.mv(19, color_block_start - 3); 190 | window.refresh(); 191 | let c = window.getch(); 192 | window.attrset(COLOR_PAIR(1)); 193 | 194 | match c { 195 | Some(Input::KeyResize) => { 196 | redraw = true; 197 | resize_term(0, 0); 198 | } 199 | Some(Input::KeyF1) => quit = true, 200 | Some(Input::Character(x)) if x == 27 as char => quit = true, 201 | Some(Input::KeyF2) => { 202 | blink_state = !blink_state; 203 | set_blink(blink_state); 204 | } 205 | Some(x) if x != Input::KeyMouse => { 206 | window.mvaddstr(0, COL1, &format!("Key {:?} hit ", x)); 207 | } 208 | Some(Input::KeyMouse) => { 209 | if let Ok(mouse_event) = getmouse() { 210 | window.mvaddstr( 211 | 0, 212 | COL1, 213 | &format!( 214 | "Mouse at {} x {}: {}", 215 | mouse_event.x, 216 | mouse_event.y, 217 | mouse_event.bstate 218 | ), 219 | ); 220 | if mouse_event.x >= color_block_start { 221 | if mouse_event.y == 19 { 222 | // blink/non-blink toggle 223 | cursor_state_1 = (cursor_state_1 + 1) % N_CURSORS as usize; 224 | } else if mouse_event.y == 20 { 225 | // cycle cursor state 226 | cursor_state_2 = (cursor_state_2 + 1) % N_CURSORS as usize; 227 | } 228 | } else if mouse_event.x >= 40 && mouse_event.x < 40 + 10 { 229 | if mouse_event.y == 11 { 230 | redraw = true; 231 | unicode_offset += 0x80; 232 | } else if mouse_event.y == 12 && unicode_offset != 0 { 233 | redraw = true; 234 | unicode_offset -= 0x80; 235 | } 236 | } 237 | } 238 | } 239 | _ => (), 240 | } 241 | } 242 | 243 | endwin(); 244 | } 245 | 246 | fn text_in_a_box(text: &str, window: &Window) { 247 | let len = text.len(); 248 | 249 | window.attron(A_OVERLINE | A_UNDERLINE | A_LEFTLINE); 250 | if len == 1 { 251 | window.attron(A_RIGHTLINE); 252 | } 253 | 254 | window.addnstr(text, 1); 255 | if len > 1 { 256 | window.attroff(A_LEFTLINE); 257 | if len > 2 { 258 | window.addnstr(&text[1..], len - 2); 259 | } 260 | window.attron(A_RIGHTLINE); 261 | window.addnstr(&text[len - 1..], 1); 262 | } 263 | 264 | window.attroff(A_OVERLINE | A_UNDERLINE | A_LEFTLINE | A_RIGHTLINE); 265 | } 266 | -------------------------------------------------------------------------------- /src/attributes.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{BitOr, BitXor}; 2 | use super::{chtype, A_ALTCHARSET, A_BLINK, A_BOLD, A_CHARTEXT, A_DIM, A_INVIS, A_LEFTLINE}; 3 | use super::{A_ITALIC, A_OVERLINE, A_REVERSE, A_RIGHTLINE, A_STRIKEOUT, A_UNDERLINE}; 4 | use super::colorpair::ColorPair; 5 | 6 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 7 | pub enum Attribute { 8 | AlternativeCharSet, 9 | Bold, 10 | Blink, 11 | CharText, 12 | Dim, 13 | Leftline, 14 | Invisible, 15 | Italic, 16 | Normal, 17 | Overline, 18 | Reverse, 19 | Rightline, 20 | Strikeout, 21 | Underline, 22 | } 23 | 24 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 25 | pub struct Attributes { 26 | raw: chtype, 27 | color_pair: ColorPair, 28 | } 29 | 30 | macro_rules! attribute_setter { 31 | ($name:ident, $attr:ident) => { 32 | pub fn $name(&mut self, enabled: bool) { 33 | if enabled { 34 | self.raw |= $attr; 35 | } else { 36 | self.raw ^= $attr; 37 | } 38 | } 39 | }; 40 | } 41 | 42 | impl Attributes { 43 | pub fn new() -> Attributes { 44 | Attributes { 45 | raw: 0, 46 | color_pair: ColorPair(0), 47 | } 48 | } 49 | 50 | pub fn is_alternative_char_set(&self) -> bool { 51 | (self.raw & A_ALTCHARSET) > 0 52 | } 53 | attribute_setter!(set_alternative_char_set, A_ALTCHARSET); 54 | 55 | pub fn is_bold(&self) -> bool { 56 | (self.raw & A_BOLD) > 0 57 | } 58 | attribute_setter!(set_bold, A_BOLD); 59 | 60 | pub fn is_blink(&self) -> bool { 61 | (self.raw & A_BLINK) > 0 62 | } 63 | attribute_setter!(set_blink, A_BLINK); 64 | 65 | pub fn is_char_text(&self) -> bool { 66 | (self.raw & A_CHARTEXT) > 0 67 | } 68 | attribute_setter!(set_char_text, A_CHARTEXT); 69 | 70 | pub fn is_dim(&self) -> bool { 71 | (self.raw & A_DIM) > 0 72 | } 73 | attribute_setter!(set_dim, A_DIM); 74 | 75 | #[cfg_attr(unix, allow(clippy::bad_bit_mask))] 76 | pub fn is_leftline(&self) -> bool { 77 | (self.raw & A_LEFTLINE) > 0 78 | } 79 | attribute_setter!(set_leftline, A_LEFTLINE); 80 | 81 | pub fn is_invisible(&self) -> bool { 82 | (self.raw & A_INVIS) > 0 83 | } 84 | attribute_setter!(set_invisible, A_INVIS); 85 | 86 | pub fn is_italic(&self) -> bool { 87 | (self.raw & A_ITALIC) > 0 88 | } 89 | attribute_setter!(set_italic, A_ITALIC); 90 | 91 | pub fn is_normal(&self) -> bool { 92 | self.raw == 0 93 | } 94 | pub fn set_normal(&mut self) { 95 | self.raw = 0 96 | } 97 | 98 | #[cfg_attr(unix, allow(clippy::bad_bit_mask))] 99 | pub fn is_overline(&self) -> bool { 100 | (self.raw & A_OVERLINE) > 0 101 | } 102 | attribute_setter!(set_overline, A_OVERLINE); 103 | 104 | pub fn is_reverse(&self) -> bool { 105 | (self.raw & A_REVERSE) > 0 106 | } 107 | attribute_setter!(set_reverse, A_REVERSE); 108 | 109 | #[cfg_attr(unix, allow(clippy::bad_bit_mask))] 110 | pub fn is_rightline(&self) -> bool { 111 | (self.raw & A_RIGHTLINE) > 0 112 | } 113 | attribute_setter!(set_rightline, A_RIGHTLINE); 114 | 115 | #[cfg_attr(unix, allow(clippy::bad_bit_mask))] 116 | pub fn is_strikeout(&self) -> bool { 117 | (self.raw & A_STRIKEOUT) > 0 118 | } 119 | attribute_setter!(set_strikeout, A_STRIKEOUT); 120 | 121 | pub fn is_underline(&self) -> bool { 122 | (self.raw & A_UNDERLINE) > 0 123 | } 124 | attribute_setter!(set_underline, A_UNDERLINE); 125 | 126 | pub fn color_pair(&self) -> ColorPair { 127 | self.color_pair 128 | } 129 | pub fn set_color_pair(&mut self, color_pair: ColorPair) { 130 | let color_chtype: chtype = color_pair.into(); 131 | self.raw |= color_chtype; 132 | self.color_pair = color_pair; 133 | } 134 | } 135 | 136 | /// Implement the | operator for adding an Attribute to Attributes 137 | /// 138 | /// # Example 139 | /// 140 | /// ``` 141 | /// use pancurses::{Attribute, Attributes}; 142 | /// 143 | /// let mut attributes = Attributes::new(); 144 | /// assert!(!attributes.is_bold()); 145 | /// attributes = attributes | Attribute::Bold; 146 | /// assert!(attributes.is_bold()); 147 | /// ``` 148 | impl BitOr for Attributes { 149 | type Output = Attributes; 150 | 151 | fn bitor(mut self, rhs: Attribute) -> Attributes { 152 | match rhs { 153 | Attribute::AlternativeCharSet => self.set_alternative_char_set(true), 154 | Attribute::Bold => self.set_bold(true), 155 | Attribute::Blink => self.set_blink(true), 156 | Attribute::CharText => self.set_char_text(true), 157 | Attribute::Dim => self.set_dim(true), 158 | Attribute::Leftline => self.set_leftline(true), 159 | Attribute::Invisible => self.set_invisible(true), 160 | Attribute::Italic => self.set_italic(true), 161 | Attribute::Normal => self.set_normal(), 162 | Attribute::Overline => self.set_overline(true), 163 | Attribute::Reverse => self.set_reverse(true), 164 | Attribute::Rightline => self.set_rightline(true), 165 | Attribute::Strikeout => self.set_strikeout(true), 166 | Attribute::Underline => self.set_underline(true), 167 | } 168 | self 169 | } 170 | } 171 | 172 | /// Implement the ^ operator for disabling an Attribute from Attributes 173 | /// 174 | /// # Example 175 | /// 176 | /// ``` 177 | /// use pancurses::{Attribute, Attributes}; 178 | /// 179 | /// let mut attributes = Attributes::from(Attribute::Bold); 180 | /// assert!(attributes.is_bold()); 181 | /// attributes = attributes ^ Attribute::Bold; 182 | /// assert!(!attributes.is_bold()); 183 | /// ``` 184 | impl BitXor for Attributes { 185 | type Output = Attributes; 186 | 187 | fn bitxor(mut self, rhs: Attribute) -> Attributes { 188 | match rhs { 189 | Attribute::AlternativeCharSet => self.set_alternative_char_set(false), 190 | Attribute::Bold => self.set_bold(false), 191 | Attribute::Blink => self.set_blink(false), 192 | Attribute::CharText => self.set_char_text(false), 193 | Attribute::Dim => self.set_dim(false), 194 | Attribute::Leftline => self.set_leftline(false), 195 | Attribute::Invisible => self.set_invisible(false), 196 | Attribute::Italic => self.set_italic(false), 197 | Attribute::Normal => (), 198 | Attribute::Overline => self.set_overline(false), 199 | Attribute::Reverse => self.set_reverse(false), 200 | Attribute::Rightline => self.set_rightline(false), 201 | Attribute::Strikeout => self.set_strikeout(false), 202 | Attribute::Underline => self.set_underline(false), 203 | } 204 | self 205 | } 206 | } 207 | 208 | /// Implement the | operator for adding Attributes to Attributes 209 | /// 210 | /// # Example 211 | /// 212 | /// ``` 213 | /// use pancurses::{Attribute, Attributes}; 214 | /// 215 | /// let mut attributes = Attributes::new() | Attribute::Bold; 216 | /// let other = Attributes::new() | Attribute::Reverse; 217 | /// attributes = attributes | other; 218 | /// assert!(attributes.is_bold()); 219 | /// assert!(attributes.is_reverse()); 220 | /// assert!(!attributes.is_italic()); 221 | /// ``` 222 | impl BitOr for Attributes { 223 | type Output = Attributes; 224 | 225 | fn bitor(self, rhs: Attributes) -> Attributes { 226 | Attributes { 227 | raw: self.raw | rhs.raw, 228 | color_pair: ColorPair(self.color_pair.0 | rhs.color_pair.0), 229 | } 230 | } 231 | } 232 | 233 | /// Implement the ^ operator for removing Attributes from Attributes 234 | /// 235 | /// # Example 236 | /// 237 | /// ``` 238 | /// use pancurses::{Attribute, Attributes}; 239 | /// 240 | /// let mut attributes = Attributes::new() | Attribute::Blink | Attribute::Bold; 241 | /// let other = Attributes::new() | Attribute::Reverse | Attribute::Bold; 242 | /// attributes = attributes ^ other; 243 | /// assert!(!attributes.is_bold()); 244 | /// assert!(attributes.is_reverse()); 245 | /// assert!(attributes.is_blink()); 246 | /// ``` 247 | impl BitXor for Attributes { 248 | type Output = Attributes; 249 | 250 | fn bitxor(self, rhs: Attributes) -> Attributes { 251 | Attributes { 252 | raw: self.raw ^ rhs.raw, 253 | color_pair: ColorPair(self.color_pair.0 ^ rhs.color_pair.0), 254 | } 255 | } 256 | } 257 | 258 | /// Implement the | operator for combining two 'Attribute's into Attributes 259 | /// 260 | /// # Example 261 | /// 262 | /// ``` 263 | /// use pancurses::{Attribute, Attributes}; 264 | /// 265 | /// let attributes = Attribute::Blink | Attribute::Reverse; 266 | /// assert!(!attributes.is_bold()); 267 | /// assert!(attributes.is_blink()); 268 | /// assert!(attributes.is_reverse()); 269 | /// ``` 270 | impl BitOr for Attribute { 271 | type Output = Attributes; 272 | 273 | fn bitor(self, rhs: Attribute) -> Attributes { 274 | Attributes::new() | self | rhs 275 | } 276 | } 277 | 278 | /// Implement Default for Attributes 279 | /// 280 | /// # Example 281 | /// 282 | /// ``` 283 | /// use pancurses::Attributes; 284 | /// let attributes: Attributes = Default::default(); 285 | /// assert_eq!(attributes, Attributes::new()); 286 | /// ``` 287 | impl Default for Attributes { 288 | fn default() -> Self { 289 | Self::new() 290 | } 291 | } 292 | 293 | impl From for Attributes { 294 | fn from(attribute: Attribute) -> Attributes { 295 | Attributes::new() | attribute 296 | } 297 | } 298 | 299 | impl From for chtype { 300 | fn from(attribute: Attribute) -> chtype { 301 | chtype::from(Attributes::from(attribute)) 302 | } 303 | } 304 | 305 | impl From for chtype { 306 | fn from(attributes: Attributes) -> chtype { 307 | attributes.raw 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/windows/constants.rs: -------------------------------------------------------------------------------- 1 | use input::Input; 2 | use pdcurses::chtype; 3 | use pdcurses::mmask_t; 4 | 5 | pub fn COLOR_PAIR(n: chtype) -> chtype { 6 | (n << PDC_COLOR_SHIFT) & A_COLOR 7 | } 8 | 9 | pub const COLOR_BLACK: i16 = 0; 10 | pub const COLOR_RED: i16 = 1; 11 | pub const COLOR_GREEN: i16 = 2; 12 | pub const COLOR_YELLOW: i16 = 3; 13 | pub const COLOR_BLUE: i16 = 4; 14 | pub const COLOR_MAGENTA: i16 = 5; 15 | pub const COLOR_CYAN: i16 = 6; 16 | pub const COLOR_WHITE: i16 = 7; 17 | 18 | pub const PDC_CHARTEXT_BITS: chtype = 21; 19 | pub const PDC_COLOR_SHIFT: chtype = PDC_CHARTEXT_BITS + 12; 20 | 21 | pub const A_ALTCHARSET: chtype = 0x001 << PDC_CHARTEXT_BITS; 22 | pub const A_BOLD: chtype = 0x080 << PDC_CHARTEXT_BITS; 23 | pub const A_BLINK: chtype = 0x040 << PDC_CHARTEXT_BITS; 24 | pub const A_COLOR: chtype = 0x7fffffff << PDC_COLOR_SHIFT; 25 | pub const A_CHARTEXT: chtype = (0x1 << PDC_CHARTEXT_BITS) - 1; 26 | pub const A_DIM: chtype = 0x400 << PDC_CHARTEXT_BITS; 27 | pub const A_LEFTLINE: chtype = 0x004 << PDC_CHARTEXT_BITS; 28 | pub const A_INVIS: chtype = 0x008 << PDC_CHARTEXT_BITS; 29 | pub const A_ITALIC: chtype = A_INVIS; 30 | pub const A_NORMAL: chtype = 0; 31 | pub const A_OVERLINE: chtype = 0x100 << PDC_CHARTEXT_BITS; 32 | pub const A_REVERSE: chtype = 0x020 << PDC_CHARTEXT_BITS; 33 | pub const A_RIGHTLINE: chtype = 0x002 << PDC_CHARTEXT_BITS; 34 | pub const A_STRIKEOUT: chtype = 0x200 << PDC_CHARTEXT_BITS; 35 | pub const A_UNDERLINE: chtype = 0x010 << PDC_CHARTEXT_BITS; 36 | 37 | pub const KEY_OFFSET: i32 = 0xec00; 38 | pub const KEY_F15: i32 = KEY_OFFSET + 0x17; 39 | pub const KEY_UNDO: i32 = KEY_OFFSET + 0x96; 40 | pub const KEY_RESIZE: i32 = KEY_OFFSET + 0x122; 41 | pub const KEY_MOUSE: i32 = KEY_OFFSET + 0x11b; 42 | 43 | pub const KEY_NUMPAD_UP: i32 = 60610; 44 | pub const KEY_NUMPAD_DOWN: i32 = 60616; 45 | pub const KEY_NUMPAD_LEFT: i32 = 60612; 46 | pub const KEY_NUMPAD_RIGHT: i32 = 60614; 47 | 48 | pub const KEY_NUMPAD_END: i32 = 60615; 49 | pub const KEY_NUMPAD_HOME: i32 = 60609; 50 | pub const KEY_NUMPAD_PAGE_UP: i32 = 60611; 51 | pub const KEY_NUMPAD_PAGE_DOWN: i32 = 60617; 52 | pub const KEY_NUMPAD_INSERT: i32 = 60666; 53 | pub const KEY_NUMPAD_DELETE: i32 = 60622; 54 | pub const KEY_NUMPAD_ENTER: i32 = 60619; 55 | pub const KEY_NUMPAD_PLUS: i32 = 60625; 56 | pub const KEY_NUMPAD_MINUS: i32 = 60624; 57 | pub const KEY_NUMPAD_ASTERISK: i32 = 60623; 58 | pub const KEY_NUMPAD_SLASH: i32 = 60618; 59 | 60 | pub const SPECIAL_KEY_CODES: [Input; 102] = [ 61 | Input::KeyCodeYes, 62 | Input::KeyBreak, 63 | Input::KeyDown, 64 | Input::KeyUp, 65 | Input::KeyLeft, 66 | Input::KeyRight, 67 | Input::KeyHome, 68 | Input::KeyBackspace, 69 | Input::KeyF0, 70 | Input::KeyF1, 71 | Input::KeyF2, 72 | Input::KeyF3, 73 | Input::KeyF4, 74 | Input::KeyF5, 75 | Input::KeyF6, 76 | Input::KeyF7, 77 | Input::KeyF8, 78 | Input::KeyF9, 79 | Input::KeyF10, 80 | Input::KeyF11, 81 | Input::KeyF12, 82 | Input::KeyF13, 83 | Input::KeyF14, 84 | Input::KeyF15, 85 | // PDcurses reserves space for 64 function keys, but we've 86 | // only implemented 15. This has to be taken into account 87 | // when converting the integer into an index of this array 88 | Input::KeyDL, 89 | Input::KeyIL, 90 | Input::KeyDC, 91 | Input::KeyIC, 92 | Input::KeyEIC, 93 | Input::KeyClear, 94 | Input::KeyEOS, 95 | Input::KeyEOL, 96 | Input::KeySF, 97 | Input::KeySR, 98 | Input::KeyNPage, 99 | Input::KeyPPage, 100 | Input::KeySTab, 101 | Input::KeyCTab, 102 | Input::KeyCATab, 103 | Input::KeyEnter, 104 | Input::KeySReset, 105 | Input::KeyReset, 106 | Input::KeyPrint, 107 | Input::KeyLL, 108 | Input::KeyAbort, 109 | Input::KeySHelp, 110 | Input::KeyLHelp, 111 | Input::KeyBTab, 112 | Input::KeyBeg, 113 | Input::KeyCancel, 114 | Input::KeyClose, 115 | Input::KeyCommand, 116 | Input::KeyCopy, 117 | Input::KeyCreate, 118 | Input::KeyEnd, 119 | Input::KeyExit, 120 | Input::KeyFind, 121 | Input::KeyHelp, 122 | Input::KeyMark, 123 | Input::KeyMessage, 124 | Input::KeyMove, 125 | Input::KeyNext, 126 | Input::KeyOpen, 127 | Input::KeyOptions, 128 | Input::KeyPrevious, 129 | Input::KeyRedo, 130 | Input::KeyReference, 131 | Input::KeyRefresh, 132 | Input::KeyReplace, 133 | Input::KeyRestart, 134 | Input::KeyResume, 135 | Input::KeySave, 136 | Input::KeySBeg, 137 | Input::KeySCancel, 138 | Input::KeySCommand, 139 | Input::KeySCopy, 140 | Input::KeySCreate, 141 | Input::KeySDC, 142 | Input::KeySDL, 143 | Input::KeySelect, 144 | Input::KeySEnd, 145 | Input::KeySEOL, 146 | Input::KeySExit, 147 | Input::KeySFind, 148 | Input::KeySHome, 149 | Input::KeySIC, 150 | Input::KeySLeft, 151 | Input::KeySMessage, 152 | Input::KeySMove, 153 | Input::KeySNext, 154 | Input::KeySOptions, 155 | Input::KeySPrevious, 156 | Input::KeySPrint, 157 | Input::KeySRedo, 158 | Input::KeySReplace, 159 | Input::KeySRight, 160 | Input::KeySResume, 161 | Input::KeySSave, 162 | Input::KeySSuspend, 163 | Input::KeySUndo, 164 | Input::KeySuspend, 165 | Input::KeyUndo, 166 | ]; 167 | 168 | pub const ALL_MOUSE_EVENTS: mmask_t = 0x1fff_ffff; 169 | 170 | pub const BUTTON1_RELEASED: mmask_t = 0x0000_0001; 171 | pub const BUTTON1_PRESSED: mmask_t = 0x0000_0002; 172 | pub const BUTTON1_CLICKED: mmask_t = 0x0000_0004; 173 | pub const BUTTON1_DOUBLE_CLICKED: mmask_t = 0x0000_0008; 174 | pub const BUTTON1_TRIPLE_CLICKED: mmask_t = 0x0000_0010; 175 | 176 | pub const BUTTON2_RELEASED: mmask_t = 0x0000_0020; 177 | pub const BUTTON2_PRESSED: mmask_t = 0x0000_0040; 178 | pub const BUTTON2_CLICKED: mmask_t = 0x0000_0080; 179 | pub const BUTTON2_DOUBLE_CLICKED: mmask_t = 0x0000_0100; 180 | pub const BUTTON2_TRIPLE_CLICKED: mmask_t = 0x0000_0200; 181 | 182 | pub const BUTTON3_RELEASED: mmask_t = 0x0000_0400; 183 | pub const BUTTON3_PRESSED: mmask_t = 0x0000_0800; 184 | pub const BUTTON3_CLICKED: mmask_t = 0x0000_1000; 185 | pub const BUTTON3_DOUBLE_CLICKED: mmask_t = 0x0000_2000; 186 | pub const BUTTON3_TRIPLE_CLICKED: mmask_t = 0x0000_4000; 187 | 188 | pub const BUTTON4_RELEASED: mmask_t = 0x0000_8000; 189 | pub const BUTTON4_PRESSED: mmask_t = 0x0001_0000; 190 | pub const BUTTON4_CLICKED: mmask_t = 0x0002_0000; 191 | pub const BUTTON4_DOUBLE_CLICKED: mmask_t = 0x0004_0000; 192 | pub const BUTTON4_TRIPLE_CLICKED: mmask_t = 0x0008_0000; 193 | 194 | pub const BUTTON5_RELEASED: mmask_t = 0x0010_0000; 195 | pub const BUTTON5_PRESSED: mmask_t = 0x0020_0000; 196 | pub const BUTTON5_CLICKED: mmask_t = 0x0040_0000; 197 | pub const BUTTON5_DOUBLE_CLICKED: mmask_t = 0x0080_0000; 198 | pub const BUTTON5_TRIPLE_CLICKED: mmask_t = 0x0100_0000; 199 | 200 | pub const REPORT_MOUSE_POSITION: mmask_t = 0x2000_0000; 201 | pub const BUTTON_SHIFT: mmask_t = 0x0400_0000; 202 | pub const BUTTON_CTRL: mmask_t = 0x0800_0000; 203 | pub const BUTTON_ALT: mmask_t = 0x1000_0000; 204 | 205 | pub fn ACS_LRCORNER() -> chtype { 206 | 'V' as chtype | A_ALTCHARSET 207 | } 208 | pub fn ACS_URCORNER() -> chtype { 209 | 'W' as chtype | A_ALTCHARSET 210 | } 211 | pub fn ACS_ULCORNER() -> chtype { 212 | 'X' as chtype | A_ALTCHARSET 213 | } 214 | pub fn ACS_LLCORNER() -> chtype { 215 | 'Y' as chtype | A_ALTCHARSET 216 | } 217 | pub fn ACS_PLUS() -> chtype { 218 | 'Z' as chtype | A_ALTCHARSET 219 | } 220 | pub fn ACS_LTEE() -> chtype { 221 | '[' as chtype | A_ALTCHARSET 222 | } 223 | pub fn ACS_RTEE() -> chtype { 224 | '\\' as chtype | A_ALTCHARSET 225 | } 226 | pub fn ACS_BTEE() -> chtype { 227 | ']' as chtype | A_ALTCHARSET 228 | } 229 | pub fn ACS_TTEE() -> chtype { 230 | '^' as chtype | A_ALTCHARSET 231 | } 232 | pub fn ACS_HLINE() -> chtype { 233 | '_' as chtype | A_ALTCHARSET 234 | } 235 | pub fn ACS_VLINE() -> chtype { 236 | '`' as chtype | A_ALTCHARSET 237 | } 238 | 239 | pub fn ACS_S1() -> chtype { 240 | 'l' as chtype | A_ALTCHARSET 241 | } 242 | pub fn ACS_S9() -> chtype { 243 | 'o' as chtype | A_ALTCHARSET 244 | } 245 | pub fn ACS_DIAMOND() -> chtype { 246 | 'j' as chtype | A_ALTCHARSET 247 | } 248 | pub fn ACS_CKBOARD() -> chtype { 249 | 'k' as chtype | A_ALTCHARSET 250 | } 251 | pub fn ACS_DEGREE() -> chtype { 252 | 'w' as chtype | A_ALTCHARSET 253 | } 254 | pub fn ACS_PLMINUS() -> chtype { 255 | 'x' as chtype | A_ALTCHARSET 256 | } 257 | pub fn ACS_BULLET() -> chtype { 258 | 'h' as chtype | A_ALTCHARSET 259 | } 260 | 261 | pub fn ACS_LARROW() -> chtype { 262 | '!' as chtype | A_ALTCHARSET 263 | } 264 | pub fn ACS_RARROW() -> chtype { 265 | ' ' as chtype | A_ALTCHARSET 266 | } 267 | pub fn ACS_DARROW() -> chtype { 268 | '#' as chtype | A_ALTCHARSET 269 | } 270 | pub fn ACS_UARROW() -> chtype { 271 | '"' as chtype | A_ALTCHARSET 272 | } 273 | pub fn ACS_BOARD() -> chtype { 274 | '+' as chtype | A_ALTCHARSET 275 | } 276 | pub fn ACS_LANTERN() -> chtype { 277 | 'z' as chtype | A_ALTCHARSET 278 | } 279 | pub fn ACS_BLOCK() -> chtype { 280 | 't' as chtype | A_ALTCHARSET 281 | } 282 | 283 | pub fn ACS_S3() -> chtype { 284 | 'm' as chtype | A_ALTCHARSET 285 | } 286 | pub fn ACS_S7() -> chtype { 287 | 'n' as chtype | A_ALTCHARSET 288 | } 289 | pub fn ACS_LEQUAL() -> chtype { 290 | 'u' as chtype | A_ALTCHARSET 291 | } 292 | pub fn ACS_GEQUAL() -> chtype { 293 | 'v' as chtype | A_ALTCHARSET 294 | } 295 | pub fn ACS_PI() -> chtype { 296 | '$' as chtype | A_ALTCHARSET 297 | } 298 | pub fn ACS_NEQUAL() -> chtype { 299 | '%' as chtype | A_ALTCHARSET 300 | } 301 | pub fn ACS_STERLING() -> chtype { 302 | '~' as chtype | A_ALTCHARSET 303 | } 304 | 305 | pub fn ACS_BSSB() -> chtype { 306 | ACS_ULCORNER() 307 | } 308 | pub fn ACS_SSBB() -> chtype { 309 | ACS_LLCORNER() 310 | } 311 | pub fn ACS_BBSS() -> chtype { 312 | ACS_URCORNER() 313 | } 314 | pub fn ACS_SBBS() -> chtype { 315 | ACS_LRCORNER() 316 | } 317 | pub fn ACS_SBSS() -> chtype { 318 | ACS_RTEE() 319 | } 320 | pub fn ACS_SSSB() -> chtype { 321 | ACS_LTEE() 322 | } 323 | pub fn ACS_SSBS() -> chtype { 324 | ACS_BTEE() 325 | } 326 | pub fn ACS_BSSS() -> chtype { 327 | ACS_TTEE() 328 | } 329 | pub fn ACS_BSBS() -> chtype { 330 | ACS_HLINE() 331 | } 332 | pub fn ACS_SBSB() -> chtype { 333 | ACS_VLINE() 334 | } 335 | pub fn ACS_SSSS() -> chtype { 336 | ACS_PLUS() 337 | } 338 | -------------------------------------------------------------------------------- /examples/newdemo.rs: -------------------------------------------------------------------------------- 1 | extern crate pancurses; 2 | extern crate rand; 3 | 4 | use pancurses::*; 5 | 6 | use rand::Rng; 7 | 8 | const AUS_MAP: [&str; 13] = [ 9 | " A ", 10 | " AA AA ", 11 | " N.T. AAAAA AAAA ", 12 | " AAAAAAAAAAA AAAAAAAA ", 13 | " AAAAAAAAAAAAAAAAAAAAAAAAA Qld.", 14 | " AAAAAAAAAAAAAAAAAAAAAAAAAAAA ", 15 | " AAAAAAAAAAAAAAAAAAAAAAAAAAAAA ", 16 | " AAAAAAAAAAAAAAAAAAAAAAAAAAAA ", 17 | " AAAAAAAAAAAAAAAAAAAAAAAAA N.S.W.", 18 | "W.A. AAAAAAAAA AAAAAA Vic.", 19 | " AAA S.A. AA", 20 | " A Tas.", 21 | "", 22 | ]; 23 | 24 | const MESSAGES: [&str; 6] = [ 25 | "Hello from the Land Down Under", 26 | "The Land of crocs, and a big Red Rock", 27 | "Where the sunflower runs along the highways", 28 | "The dusty red roads lead one to loneliness", 29 | "Blue sky in the morning and", 30 | "Freezing nights and twinkling stars", 31 | ]; 32 | 33 | fn main() { 34 | let main_window = initscr(); 35 | 36 | start_color(); 37 | 38 | use_default_colors(); 39 | 40 | cbreak(); 41 | noecho(); 42 | 43 | curs_set(0); 44 | 45 | // Refresh stdscr so that reading from it will not cause it to overwrite the other windows 46 | // that are being created 47 | main_window.refresh(); 48 | 49 | // Create a drawing window 50 | let width = 48; 51 | let height = 15; 52 | 53 | let win = newwin( 54 | height, 55 | width, 56 | (main_window.get_max_y() - height) / 2, 57 | (main_window.get_max_x() - width) / 2, 58 | ); 59 | 60 | loop { 61 | init_pair(1, COLOR_WHITE, COLOR_BLUE); 62 | win.bkgd(COLOR_PAIR(1)); 63 | win.erase(); 64 | 65 | init_pair(2, COLOR_RED, COLOR_RED); 66 | win.attrset(ColorPair(2)); 67 | win.draw_box(' ', ' '); 68 | win.refresh(); 69 | 70 | win.attrset(Attribute::Normal); 71 | 72 | // Do random output of a character 73 | 74 | let mut ch = 'a'; 75 | 76 | main_window.nodelay(true); 77 | 78 | let mut rng = rand::thread_rng(); 79 | 80 | for i in 0..5000 { 81 | let x = (rng.gen::() % (width - 2)).abs() + 1; 82 | let y = (rng.gen::() % (height - 2)).abs() + 1; 83 | 84 | assert!(x != 0); 85 | assert!(y != 0); 86 | win.mvaddch(y, x, ch); 87 | win.refresh(); 88 | 89 | if main_window.getch().is_some() { 90 | break; 91 | } 92 | 93 | if i == 2000 { 94 | ch = 'b'; 95 | init_pair(3, COLOR_CYAN, COLOR_YELLOW); 96 | win.attrset(ColorPair(3)); 97 | } 98 | } 99 | 100 | main_window.nodelay(false); 101 | 102 | sub_win_test(&main_window, &win).unwrap(); 103 | 104 | // Erase and draw green window 105 | 106 | init_pair(4, COLOR_YELLOW, COLOR_GREEN); 107 | win.bkgd(COLOR_PAIR(4)); 108 | win.attrset(Attribute::Bold); 109 | win.erase(); 110 | win.refresh(); 111 | 112 | // Draw RED bounding box 113 | 114 | win.attrset(ColorPair(2)); 115 | win.draw_box(' ', ' '); 116 | win.refresh(); 117 | 118 | // Display Australia map 119 | 120 | win.attrset(Attribute::Bold); 121 | 122 | for (i, s) in AUS_MAP.iter().enumerate() { 123 | win.mvaddstr(i as i32 + 1, 8, s); 124 | win.refresh(); 125 | napms(100); 126 | } 127 | 128 | init_pair(5, COLOR_BLUE, COLOR_WHITE); 129 | win.attrset(ColorPair(5) | Attribute::Blink); 130 | win.mvaddstr(height - 2, 3, " pancurses - Linux, Win32"); 131 | win.refresh(); 132 | 133 | // Draw running messages 134 | 135 | init_pair(6, COLOR_BLACK, COLOR_WHITE); 136 | win.attrset(ColorPair(6)); 137 | let w = width - 2; 138 | win.nodelay(true); 139 | 140 | // jbuhler's re-hacked scrolling messages 141 | 142 | for message in &MESSAGES { 143 | let msg_len = message.len() as i32; 144 | let mut visbuf = String::with_capacity(w as usize); 145 | let mut stop = false; 146 | 147 | for i in (0..w + msg_len).rev() { 148 | visbuf.clear(); 149 | for visbuf_i in 0..visbuf.capacity() { 150 | let i = i - msg_len as i32; 151 | let char_index = visbuf_i as i32 - i; 152 | let ch = if char_index >= 0 && char_index < message.len() as i32 { 153 | let char_index = char_index as usize; 154 | message[char_index..char_index + 1].chars().next().unwrap_or(' ') 155 | } else { 156 | ' ' 157 | }; 158 | visbuf.push(ch); 159 | } 160 | 161 | win.mvaddstr(height / 2, 1, &visbuf); 162 | win.refresh(); 163 | 164 | if win.getch().is_some() { 165 | flushinp(); 166 | stop = true; 167 | break; 168 | } 169 | 170 | napms(100); 171 | } 172 | 173 | if stop { 174 | break; 175 | } 176 | } 177 | 178 | // Draw running 'A's across in RED 179 | 180 | init_pair(7, COLOR_RED, COLOR_GREEN); 181 | win.attron(ColorPair(7)); 182 | 183 | let mut save: [chtype; 80] = [0; 80]; 184 | 185 | for (index, pos) in (2..width - 4).enumerate() { 186 | let ch = win.mvinch(5, pos); 187 | save[index] = ch; 188 | let ch = ch & 0x7F; 189 | win.mvaddch(5, pos, ch); 190 | } 191 | 192 | win.refresh(); 193 | 194 | // Put a message up; wait for a key 195 | 196 | let i = height - 2; 197 | win.attrset(ColorPair(5)); 198 | win.mvaddstr(i, 3, " Type a key to continue or ESC to quit "); 199 | win.refresh(); 200 | 201 | if wait_for_user(&main_window) { 202 | break; 203 | } 204 | 205 | // Restore the old line 206 | 207 | win.attrset(Attribute::Normal); 208 | 209 | for (index, pos) in (2..width - 4).enumerate() { 210 | win.mvaddch(5, pos, save[index]); 211 | } 212 | 213 | win.refresh(); 214 | 215 | bouncing_balls(&main_window, &win, &mut rng); 216 | 217 | // bouncing_balls() leaves a keystroke in the queue 218 | 219 | if wait_for_user(&main_window) { 220 | break; 221 | } 222 | } 223 | 224 | endwin(); 225 | } 226 | 227 | fn wait_for_user(main_window: &Window) -> bool { 228 | main_window.nodelay(true); 229 | half_delay(50); 230 | 231 | let ch = main_window.getch(); 232 | 233 | main_window.nodelay(false); 234 | nocbreak(); // Reset the halfdelay() value 235 | cbreak(); 236 | 237 | matches!(ch, Some(Input::Character('\x1B'))) 238 | } 239 | 240 | fn sub_win_test(main_window: &Window, win: &Window) -> Result<(), i32> { 241 | win.attrset(Attribute::Normal); 242 | let (h, w) = win.get_max_yx(); 243 | let (by, bx) = win.get_beg_yx(); 244 | 245 | let sw = w / 3; 246 | let sh = h / 3; 247 | 248 | let swin1 = win.derwin(sh, sw, 3, 5)?; 249 | let swin2 = win.subwin(sh, sw, by + 4, bx + 8)?; 250 | let swin3 = win.subwin(sh, sw, by + 5, bx + 11)?; 251 | 252 | init_pair(8, COLOR_RED, COLOR_BLUE); 253 | swin1.bkgd(COLOR_PAIR(8)); 254 | swin1.erase(); 255 | swin1.mvaddstr(0, 3, "Sub-window 1"); 256 | swin1.refresh(); 257 | 258 | init_pair(9, COLOR_CYAN, COLOR_MAGENTA); 259 | swin2.bkgd(COLOR_PAIR(9)); 260 | swin2.erase(); 261 | swin2.mvaddstr(0, 3, "Sub-window 2"); 262 | swin2.refresh(); 263 | 264 | init_pair(10, COLOR_YELLOW, COLOR_GREEN); 265 | swin3.bkgd(COLOR_PAIR(10)); 266 | swin3.erase(); 267 | swin3.mvaddstr(0, 3, "Sub-window 3"); 268 | swin3.refresh(); 269 | 270 | swin1.delwin(); 271 | swin2.delwin(); 272 | swin3.delwin(); 273 | 274 | wait_for_user(main_window); 275 | 276 | Ok(()) 277 | } 278 | 279 | fn bouncing_balls(main_window: &Window, win: &Window, rng: &mut T) { 280 | curs_set(0); 281 | 282 | win.bkgd(COLOR_PAIR(1)); 283 | win.refresh(); 284 | win.attrset(Attribute::Normal); 285 | 286 | init_pair(11, COLOR_RED, COLOR_GREEN); 287 | init_pair(12, COLOR_BLUE, COLOR_RED); 288 | init_pair(13, COLOR_YELLOW, COLOR_WHITE); 289 | 290 | let ball1 = 'O' as chtype | COLOR_PAIR(11); 291 | let ball2 = '*' as chtype | COLOR_PAIR(12); 292 | let ball3 = '@' as chtype | COLOR_PAIR(13); 293 | 294 | let (h, w) = win.get_max_yx(); 295 | 296 | let mut x1 = 2 + (rng.gen::() % (w - 4)).abs(); 297 | let mut y1 = 2 + (rng.gen::() % (h - 4)).abs(); 298 | let mut x2 = 2 + (rng.gen::() % (w - 4)).abs(); 299 | let mut y2 = 2 + (rng.gen::() % (h - 4)).abs(); 300 | let mut x3 = 2 + (rng.gen::() % (w - 4)).abs(); 301 | let mut y3 = 2 + (rng.gen::() % (h - 4)).abs(); 302 | 303 | let mut xd1 = 1; 304 | let mut yd1 = 1; 305 | let mut xd2 = 1; 306 | let mut yd2 = -1; 307 | let mut xd3 = -1; 308 | let mut yd3 = 1; 309 | 310 | main_window.nodelay(true); 311 | 312 | let mut c = main_window.getch(); 313 | while c.is_none() { 314 | x1 += xd1; 315 | if x1 <= 1 || x1 >= w - 2 { 316 | xd1 *= -1; 317 | } 318 | 319 | y1 += yd1; 320 | if y1 <= 1 || y1 >= h - 2 { 321 | yd1 *= -1; 322 | } 323 | 324 | x2 += xd2; 325 | if x2 <= 1 || x2 >= w - 2 { 326 | xd2 *= -1; 327 | } 328 | 329 | y2 += yd2; 330 | if y2 <= 1 || y2 >= h - 2 { 331 | yd2 *= -1; 332 | } 333 | 334 | x3 += xd3; 335 | if x3 <= 1 || x3 >= w - 2 { 336 | xd3 *= -1; 337 | } 338 | 339 | y3 += yd3; 340 | if y3 <= 1 || y3 >= h - 2 { 341 | yd3 *= -1; 342 | } 343 | 344 | let c1 = win.mvinch(y1, x1); 345 | let c2 = win.mvinch(y2, x2); 346 | let c3 = win.mvinch(y3, x3); 347 | 348 | win.mvaddch(y1, x1, ball1); 349 | win.mvaddch(y2, x2, ball2); 350 | win.mvaddch(y3, x3, ball3); 351 | 352 | win.mv(0, 0); 353 | win.refresh(); 354 | 355 | win.mvaddch(y1, x1, c1); 356 | win.mvaddch(y2, x2, c2); 357 | win.mvaddch(y3, x3, c3); 358 | 359 | napms(150); 360 | c = main_window.getch(); 361 | } 362 | 363 | main_window.nodelay(false); 364 | c.map(|c| main_window.ungetch(&c)); 365 | } 366 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, non_snake_case)] 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | extern crate libc; 7 | 8 | #[cfg(unix)] 9 | extern crate ncurses; 10 | #[cfg(windows)] 11 | extern crate pdcurses; 12 | 13 | use std::ffi::CString; 14 | use std::ptr; 15 | 16 | #[cfg(windows)] 17 | use pdcurses as curses; 18 | #[cfg(windows)] 19 | pub use pdcurses::{chtype, mmask_t, MEVENT, SCREEN}; 20 | #[cfg(windows)] 21 | type ScrPtr = *mut SCREEN; 22 | #[cfg(windows)] 23 | type FILE = *mut curses::FILE; 24 | 25 | #[cfg(unix)] 26 | use ncurses::ll as curses; 27 | #[cfg(unix)] 28 | pub use ncurses::ll::{chtype, mmask_t, MEVENT, SCREEN}; 29 | #[cfg(unix)] 30 | type ScrPtr = SCREEN; 31 | #[cfg(unix)] 32 | type FILE = curses::FILE_p; 33 | 34 | mod input; 35 | pub use self::input::*; 36 | 37 | mod attributes; 38 | pub use self::attributes::*; 39 | 40 | pub mod colorpair; 41 | pub use crate::colorpair::ColorPair; 42 | 43 | #[cfg(windows)] 44 | mod windows; 45 | #[cfg(windows)] 46 | pub use self::windows::constants::*; 47 | #[cfg(windows)] 48 | use self::windows as platform_specific; 49 | 50 | #[cfg(unix)] 51 | mod unix; 52 | #[cfg(unix)] 53 | pub use self::unix::constants::*; 54 | #[cfg(unix)] 55 | use self::unix as platform_specific; 56 | 57 | pub const OK: i32 = 0; 58 | pub const ERR: i32 = -1; 59 | 60 | mod window; 61 | pub use crate::window::Window; 62 | 63 | pub trait ToChtype { 64 | fn to_chtype(&self) -> chtype; 65 | } 66 | 67 | impl ToChtype for char { 68 | fn to_chtype(&self) -> chtype { 69 | *self as chtype 70 | } 71 | } 72 | 73 | impl ToChtype for chtype { 74 | fn to_chtype(&self) -> chtype { 75 | *self 76 | } 77 | } 78 | 79 | /// Return the output speed of the terminal. On Windows it simply returns `INT_MAX` 80 | pub fn baudrate() -> i32 { 81 | unsafe { curses::baudrate() } 82 | } 83 | 84 | /// Sounds the audible bell on the terminal, if possible; if not, it calls flash(). 85 | pub fn beep() -> i32 { 86 | unsafe { curses::beep() } 87 | } 88 | 89 | /// Indicates if the terminal has the capability to change the definition of its colors. 90 | pub fn can_change_color() -> bool { 91 | unsafe { curses::can_change_color() != 0 } 92 | } 93 | 94 | /// Set cbreak mode. 95 | /// 96 | /// In cbreak mode, characters typed by the user are made available immediately, and erase/kill 97 | /// character processing is not performed. In nocbreak mode, typed characters are buffered until 98 | /// a newline or carriage return. Interrupt and flow control characters are unaffected by this 99 | /// mode. 100 | pub fn cbreak() -> i32 { 101 | unsafe { curses::cbreak() } 102 | } 103 | 104 | /// Maximum number of colors the terminal is capable of displaying. 105 | pub fn COLORS() -> i32 { 106 | platform_specific::_COLORS() 107 | } 108 | 109 | /// Maximum number of color-pairs the terminal is capable of displaying. 110 | pub fn COLOR_PAIRS() -> i32 { 111 | platform_specific::_COLOR_PAIRS() 112 | } 113 | 114 | /// This routine gives programmers a way to find the intensity of the red, green, and blue (RGB) 115 | /// components in a color. It takes the color number as an argument and returns three values 116 | /// that tell you the amounts of red, green, and blue components in the given color. The argument 117 | /// must be a legal color value, i.e., 0 through COLORS()-1, inclusive. The values that are returned 118 | /// are in the range 0 (no component) through 1000 (maximum amount of component), inclusive. 119 | /// 120 | /// ```rust 121 | /// use pancurses::{can_change_color, color_content, endwin, init_color, initscr, start_color}; 122 | /// 123 | /// initscr(); 124 | /// start_color(); 125 | /// if can_change_color() { 126 | /// init_color(8, 35, 502, 1000); 127 | /// let (r, g, b) = color_content(8); 128 | /// assert_eq!(35, r); 129 | /// assert_eq!(502, g); 130 | /// assert_eq!(1000, b); 131 | /// } 132 | /// endwin(); 133 | /// ``` 134 | pub fn color_content(color_number: i16) -> (i16, i16, i16) { 135 | let mut r: i16 = 0; 136 | let mut g: i16 = 0; 137 | let mut b: i16 = 0; 138 | unsafe { 139 | curses::color_content(color_number, &mut r, &mut g, &mut b); 140 | } 141 | (r, g, b) 142 | } 143 | 144 | /// Alters the appearance of the cursor. 145 | /// 146 | /// A visibility of 0 makes it disappear; 1 makes it appear "normal" (usually an underline) and 2 147 | /// makes it "highly visible" (usually a block). 148 | pub fn curs_set(visibility: i32) -> i32 { 149 | unsafe { curses::curs_set(visibility) } 150 | } 151 | 152 | /// Save the current terminal modes as the "program" (in curses) state for use by the 153 | /// `reset_prog_mode()` and `reset_shell_mode()` functions. This is done automatically by initscr(). 154 | pub fn def_prog_mode() -> i32 { 155 | unsafe { curses::def_prog_mode() } 156 | } 157 | 158 | /// Save the current terminal modes as the "shell" (not in curses) state for use by the 159 | /// `reset_prog_mode()` and `reset_shell_mode()` functions. This is done automatically by initscr(). 160 | pub fn def_shell_mode() -> i32 { 161 | unsafe { curses::def_shell_mode() } 162 | } 163 | 164 | /// Inserts an 'milliseconds' millisecond pause in output. This routine should not be used extensively 165 | /// because padding characters are used rather than a CPU pause. If no padding character is 166 | /// specified, this uses napms to perform the delay. 167 | pub fn delay_output(milliseconds: i32) -> i32 { 168 | unsafe { curses::delay_output(milliseconds) } 169 | } 170 | 171 | /// Frees storage associated with the SCREEN data structure. 172 | /// 173 | /// The endwin routine does not do this, so delscreen should be called after endwin if a particular 174 | /// SCREEN is no longer needed. 175 | /// 176 | /// In PDCurses, the parameter must be the value of SP, and delscreen() sets SP to NULL. 177 | pub fn delscreen(screen: ScrPtr) { 178 | unsafe { curses::delscreen(screen) } 179 | } 180 | 181 | /// Compares the virtual screen to the physical screen and performs an update of the physical 182 | /// screen. 183 | pub fn doupdate() -> i32 { 184 | unsafe { curses::doupdate() } 185 | } 186 | 187 | /// Enabled echoing typed characters. 188 | /// 189 | /// Initially, input characters are echoed. Subsequent calls to echo() and noecho() do not flush 190 | /// type-ahead. 191 | pub fn echo() -> i32 { 192 | unsafe { curses::echo() } 193 | } 194 | 195 | /// Should be called before exiting or escaping from curses mode temporarily. 196 | /// 197 | /// It will restore tty modes, move the cursor to the lower left corner of the screen and reset the 198 | /// terminal into the proper non-visual mode. To resume curses after a temporary escape, call 199 | /// refresh() or doupdate(). 200 | pub fn endwin() -> i32 { 201 | unsafe { curses::endwin() } 202 | } 203 | 204 | /// Flashes the screen, if possible; if not, it calls beep(). 205 | pub fn flash() -> i32 { 206 | unsafe { curses::flash() } 207 | } 208 | 209 | /// Throws away any type-ahead that has been typed by the user and has not yet been read by the 210 | /// program. 211 | pub fn flushinp() -> i32 { 212 | unsafe { curses::flushinp() } 213 | } 214 | 215 | /// Returns the current mouse status in an MEVENT struct. 216 | pub fn getmouse() -> Result { 217 | platform_specific::_getmouse() 218 | } 219 | 220 | /// Similar to cbreak(), but allows for a time limit to be specified, in tenths of a second. 221 | /// 222 | /// This causes getch() to block for that period before returning None if no key has been received. 223 | /// tenths must be between 1 and 255. 224 | pub fn half_delay(tenths: i32) -> i32 { 225 | unsafe { curses::halfdelay(tenths) } 226 | } 227 | 228 | /// Indicates if the terminal supports, and can maniplulate color. 229 | pub fn has_colors() -> bool { 230 | unsafe { curses::has_colors() > 0 } 231 | } 232 | 233 | /// Initialize the curses system, this must be the first function that is called. 234 | /// 235 | /// Returns a Window struct that is used to access Window specific functions. 236 | pub fn initscr() -> Window { 237 | platform_specific::pre_init(); 238 | let window_pointer = unsafe { curses::initscr() }; 239 | window::new_window(window_pointer, true) 240 | } 241 | 242 | /// Changes the definition of a color. It takes four arguments: the number of the color to be 243 | /// changed followed by three RGB values (for the amounts of red, green, and blue components). 244 | /// The first argument must be a legal color value; default colors are not allowed here. 245 | /// Each of the last three arguments must be a value in the range 0 through 1000. When `init_color` 246 | /// is used, all occurrences of that color on the screen immediately change to the new definition. 247 | pub fn init_color(color_number: i16, red: i16, green: i16, blue: i16) -> i32 { 248 | unsafe { curses::init_color(color_number, red, green, blue) } 249 | } 250 | 251 | /// Changes the definition of a color-pair. 252 | /// 253 | /// It takes three arguments: the number of the color-pair to be redefined, and the new values of 254 | /// the foreground and background colors. The pair number must be between 0 and `COLOR_PAIRS` - 1, 255 | /// inclusive. The foreground and background must be between 0 and `COLORS()` - 1, inclusive. If the 256 | /// color pair was previously initialized, the screen is refreshed, and all occurrences of that 257 | /// color-pair are changed to the new definition. 258 | pub fn init_pair(pair_index: i16, foreground_color: i16, background_color: i16) -> i32 { 259 | unsafe { curses::init_pair(pair_index, foreground_color, background_color) as i32 } 260 | } 261 | 262 | /// Sets the timeout for a mouse click. 263 | /// 264 | /// Sets the maximum time (in thousands of a second) that can elapse between press and release 265 | /// events for them to be recognized as aclick. Use mouseinterval(0) to disable click resolution. 266 | /// This function returns the previous interval value. Use mouseinterval(-1) to obtain the interval 267 | /// without altering it. The default is one sixth of a second. 268 | pub fn mouseinterval(interval: i32) -> i32 { 269 | unsafe { curses::mouseinterval(interval) } 270 | } 271 | 272 | /// Set the mouse events to be reported. 273 | /// 274 | /// By default, no mouse events are reported. The function will return a mask to indicate which of 275 | /// the specified mouse events can be reported; on complete failure it returns 0. If oldmask is 276 | /// non-NULL, this function fills the indicated location with the previous value of the given 277 | /// window's mouse event mask. 278 | /// 279 | /// As a side effect, setting a zero mousemask may turn off the mouse pointer; setting a nonzero 280 | /// mask may turn it on. Whether this happens is device-dependent. 281 | pub fn mousemask(newmask: mmask_t, oldmask: Option<&mut mmask_t>) -> mmask_t { 282 | let oldmask_ptr = oldmask.map(|x| x as *mut _).unwrap_or(std::ptr::null_mut()); 283 | unsafe { curses::mousemask(newmask, oldmask_ptr) } 284 | } 285 | 286 | /// Returns a character string corresponding to the key `code`. 287 | /// 288 | /// * Printable characters are displayed as themselves, e.g., a one-character string containing the 289 | /// key. 290 | /// * Control characters are displayed in the ^X notation. 291 | /// * DEL (character 127) is displayed as ^?. 292 | /// * Values above 128 are either meta characters (if the screen has not been initialized, or if 293 | /// meta has been called with a TRUE parameter), shown in the M-X notation, or are displayed as 294 | /// themselves. In the latter case, the values may not be printable; this follows the X/Open 295 | /// specification. 296 | /// * Values above 256 may be the names of the names of function keys. 297 | /// * Otherwise (if there is no corresponding name) the function returns `None`, to denote an 298 | /// error. X/Open also lists an "UNKNOWN KEY" return value, which some implementations return 299 | /// rather than `None`. 300 | pub fn keyname(code: i32) -> Option { 301 | platform_specific::_keyname(code) 302 | } 303 | 304 | /// Suspends the program for the specified number of milliseconds. 305 | pub fn napms(ms: i32) -> i32 { 306 | unsafe { curses::napms(ms) } 307 | } 308 | 309 | /// A program that outputs to more than one terminal should use the newterm routine for each 310 | /// terminal instead of initscr. 311 | /// 312 | /// A program that needs to inspect capabilities, so it can continue to 313 | /// run in a line-oriented mode if the terminal cannot support a screen-oriented program, would also 314 | /// use newterm. The routine newterm should be called once for each terminal. It returns a variable 315 | /// of type `ScrPtr` which should be saved as a reference to that terminal. 316 | /// 317 | /// (For the PDCurses backend it's just an alternative interface for initscr(). It always returns 318 | /// SP, or NULL.) 319 | pub fn newterm(t: Option<&str>, output: FILE, input: FILE) -> ScrPtr { 320 | let term_type = t.map(|x| CString::new(x).unwrap()); 321 | let type_ptr = match term_type { 322 | Some(ref s) => s.as_ptr(), 323 | _ => std::ptr::null(), 324 | }; 325 | unsafe { curses::newterm(type_ptr, output, input) } 326 | } 327 | 328 | /// Creates a new window with the given number of lines, nlines and columns, ncols. 329 | /// 330 | /// The upper left corner of the window is at line begy, column begx. If nlines is zero, it 331 | /// defaults to LINES - begy; ncols to COLS - begx. Create a new full-screen window by calling 332 | /// newwin(0, 0, 0, 0). 333 | pub fn newwin(nlines: i32, ncols: i32, begy: i32, begx: i32) -> Window { 334 | let window_pointer = unsafe { curses::newwin(nlines, ncols, begy, begx) }; 335 | window::new_window(window_pointer, false) 336 | } 337 | 338 | /// Enables the translation of a carriage return into a newline on input. 339 | /// 340 | /// nonl() disables this. Initially, the translation does occur. 341 | pub fn nl() -> i32 { 342 | unsafe { curses::nl() } 343 | } 344 | 345 | /// Set nocbreak mode. 346 | /// 347 | /// In cbreak mode, characters typed by the user are made available immediately, and erase/kill 348 | /// character processing is not performed. In nocbreak mode, typed characters are buffered until 349 | /// a newline or carriage return. Interrupt and flow control characters are unaffected by this 350 | /// mode. 351 | pub fn nocbreak() -> i32 { 352 | unsafe { curses::nocbreak() } 353 | } 354 | 355 | /// Disables echoing typed characters. 356 | /// 357 | /// Initially, input characters are echoed. Subsequent calls to echo() and noecho() do not flush 358 | /// type-ahead. 359 | pub fn noecho() -> i32 { 360 | unsafe { curses::noecho() } 361 | } 362 | 363 | /// Disables the translation of a carriage return into a newline on input. 364 | /// 365 | /// nl() enables this. Initially, the translation does occur. 366 | pub fn nonl() -> i32 { 367 | unsafe { curses::nonl() } 368 | } 369 | 370 | /// Disable raw mode. 371 | /// 372 | /// Raw mode is similar to cbreak mode, in that characters typed are immediately passed through to 373 | /// the user program. The difference is that in raw mode, the INTR, QUIT, SUSP, and STOP characters 374 | /// are passed through without being interpreted, and without generating a signal. 375 | pub fn noraw() -> i32 { 376 | unsafe { curses::noraw() } 377 | } 378 | 379 | /// Enable raw mode. 380 | /// 381 | /// Raw mode is similar to cbreak mode, in that characters typed are immediately passed through to 382 | /// the user program. The difference is that in raw mode, the INTR, QUIT, SUSP, and STOP characters 383 | /// are passed through without being interpreted, and without generating a signal. 384 | pub fn raw() -> i32 { 385 | unsafe { curses::raw() } 386 | } 387 | 388 | /// Restore the terminal to "program" (in curses) state. This is done 389 | /// automatically by endwin() and doupdate() after an endwin(), so this would normally not be 390 | /// called before. 391 | pub fn reset_prog_mode() -> i32 { 392 | unsafe { curses::reset_prog_mode() } 393 | } 394 | 395 | /// Restore the terminal to "shell" (not in curses) state. This is done automatically by 396 | /// endwin() and doupdate() after an endwin(), so this would normally not be called before. 397 | pub fn reset_shell_mode() -> i32 { 398 | unsafe { curses::reset_shell_mode() } 399 | } 400 | 401 | /// Attempts to resize the screen to the given size. 402 | /// 403 | /// `resize_term()` is effectively two functions: When called with nonzero values for nlines and 404 | /// ncols, it attempts to resize the screen to the given size. When called with (0, 0), it merely 405 | /// adjusts the internal structures to match the current size after the screen is resized by the 406 | /// user. If you want to support user resizing, you should check for getch() returning `KEY_RESIZE`, 407 | /// and/or call `is_termresized()` at appropriate times; if either condition occurs, call 408 | /// `resize_term(0, 0)`. Then, with either user or programmatic resizing, you'll have to resize any 409 | /// windows you've created. 410 | pub fn resize_term(nlines: i32, ncols: i32) -> i32 { 411 | platform_specific::_resize_term(nlines, ncols) 412 | } 413 | 414 | /// Toggles whether the `A_BLINK` attribute sets an actual blink mode (TRUE), or sets the background 415 | /// color to high intensity (FALSE). 416 | /// 417 | /// The default is platform-dependent (FALSE in most cases). It returns OK if it could set the 418 | /// state to match the given parameter, ERR otherwise. Current platforms also adjust the value 419 | /// of COLORS() according to this function -- 16 for FALSE, and 8 for TRUE. 420 | /// (Only supported on Windows) 421 | pub fn set_blink(enabled: bool) -> i32 { 422 | platform_specific::_set_blink(enabled) 423 | } 424 | 425 | /// Switches between different terminals. 426 | /// 427 | /// The screen reference new becomes the new current terminal. The previous terminal is returned by 428 | /// the routine. This is the only routine which manipulates ScrPtr's; all other routines affect 429 | /// only the current terminal. 430 | /// 431 | /// (Does nothing meaningful in PDCurses, but is included for compatibility with other curses 432 | /// implementations.) 433 | pub fn set_term(new: ScrPtr) -> ScrPtr { 434 | unsafe { curses::set_term(new) } 435 | } 436 | 437 | /// Sets the title of the window in which the curses program is running. This function may not do 438 | /// anything on some platforms. (Only supported on Windows) 439 | pub fn set_title(title: &str) { 440 | platform_specific::_set_title(title); 441 | } 442 | 443 | /// Initializes eight basic colors (black, red, green, yellow, blue, magenta, cyan, 444 | /// and white), and two global variables accessed through `COLORS()` and `COLOR_PAIRS()` (respectively defining the 445 | /// maximum number of colors and color-pairs the terminal is capable of displaying). 446 | pub fn start_color() -> i32 { 447 | unsafe { curses::start_color() as i32 } 448 | } 449 | 450 | /// Allows the use of -1 as a foreground or background color with `init_pair()`. 451 | /// 452 | /// Calls `assume_default_colors(-1, -1);` -1 represents the foreground or background color that 453 | /// the terminal had at startup. 454 | pub fn use_default_colors() -> i32 { 455 | unsafe { curses::use_default_colors() } 456 | } 457 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | use crate::{chtype, curses, platform_specific, ptr, Input, ToChtype, ERR}; 2 | use std::ffi::CString; 3 | 4 | #[derive(Debug)] 5 | pub struct Window { 6 | #[cfg(windows)] 7 | _window: *mut curses::WINDOW, 8 | #[cfg(unix)] 9 | _window: curses::WINDOW, 10 | _stdscr: bool, 11 | } 12 | 13 | #[cfg(windows)] 14 | type WindowPointer = *mut curses::WINDOW; 15 | #[cfg(unix)] 16 | type WindowPointer = curses::WINDOW; 17 | 18 | impl Window { 19 | /// Adds the chtype ch to the window at the current cursor position, and advances the cursor. 20 | /// 21 | /// Note that chtypes can convey both text (a single character) and attributes, including a 22 | /// color pair. 23 | pub fn addch(&self, ch: T) -> i32 { 24 | unsafe { curses::waddch(self._window, ch.to_chtype()) } 25 | } 26 | 27 | /// Write all the characters of the string to the given window. 28 | /// 29 | /// The functionality is similar to calling window.addch() once for each character in the 30 | /// string. 31 | pub fn addstr>(&self, string: T) -> i32 { 32 | let s = CString::new(string.as_ref()).unwrap(); 33 | unsafe { curses::waddstr(self._window, s.as_ptr()) } 34 | } 35 | 36 | /// Write at most length characters; if length is negative, then the entire string will be 37 | /// added. 38 | pub fn addnstr>(&self, string: T, length: usize) -> i32 { 39 | let s = CString::new(string.as_ref()).unwrap(); 40 | unsafe { curses::waddnstr(self._window, s.as_ptr(), length as i32) } 41 | } 42 | 43 | /// Retrieve attributes for the given window. 44 | /// 45 | /// ```rust 46 | /// use pancurses::{A_BOLD, initscr, endwin}; 47 | /// let window = initscr(); 48 | /// window.attron(A_BOLD); 49 | /// let (active_attributes, color_pair) = window.attrget(); 50 | /// assert_eq!(A_BOLD, active_attributes); 51 | /// endwin(); 52 | /// ``` 53 | pub fn attrget(&self) -> (chtype, i16) { 54 | let mut attributes: chtype = 0; 55 | let mut color_pair: i16 = 0; 56 | unsafe { 57 | curses::wattr_get( 58 | self._window, 59 | &mut attributes, 60 | &mut color_pair, 61 | ptr::null_mut(), 62 | ); 63 | } 64 | (attributes, color_pair) 65 | } 66 | 67 | /// Turns off the named attributes without affecting any other attributes. 68 | pub fn attroff>(&self, attributes: T) -> i32 { 69 | platform_specific::_attroff(self._window, attributes.into()) 70 | } 71 | 72 | /// Turns on the named attributes without affecting any other attributes. 73 | pub fn attron>(&self, attributes: T) -> i32 { 74 | platform_specific::_attron(self._window, attributes.into()) 75 | } 76 | 77 | /// Sets the current attributes of the given window to attributes. 78 | pub fn attrset>(&self, attributes: T) -> i32 { 79 | platform_specific::_attrset(self._window, attributes.into()) 80 | } 81 | 82 | /// Not only change the background, but apply it immediately to every cell in the window. 83 | pub fn bkgd>(&self, ch: T) -> i32 { 84 | unsafe { curses::wbkgd(self._window, ch.into()) } 85 | } 86 | 87 | /// Manipulate the background of a window. The background is a chtype consisting of any 88 | /// combination of attributes and a character; it is combined with each chtype added or 89 | /// inserted to the window by addch() or insch(). Only the attribute part is used to set 90 | /// the background of non-blank characters, while both character and attributes are used 91 | /// for blank positions. 92 | pub fn bkgdset>(&self, ch: T) { 93 | unsafe { curses::wbkgdset(self._window, ch.into()) } 94 | } 95 | 96 | /// Draw a border around the edges of the window. 97 | #[allow(clippy::too_many_arguments)] 98 | pub fn border( 99 | &self, 100 | left_side: T, 101 | right_side: T, 102 | top_side: T, 103 | bottom_side: T, 104 | top_left_corner: T, 105 | top_right_corner: T, 106 | bottom_left_corner: T, 107 | bottom_right_corner: T, 108 | ) -> i32 { 109 | unsafe { 110 | curses::wborder( 111 | self._window, 112 | left_side.to_chtype(), 113 | right_side.to_chtype(), 114 | top_side.to_chtype(), 115 | bottom_side.to_chtype(), 116 | top_left_corner.to_chtype(), 117 | top_right_corner.to_chtype(), 118 | bottom_left_corner.to_chtype(), 119 | bottom_right_corner.to_chtype(), 120 | ) 121 | } 122 | } 123 | 124 | /// Changes the attributes of a given number of characters starting at the current cursor 125 | /// location. It does not update the cursor and does not perform wrapping. A character count 126 | /// of -1 or greater than the remaining window width means to change attributes all the way 127 | /// to the end of the current line. 128 | pub fn chgat(&self, n: i32, attributes: chtype, color_pair: i16) -> i32 { 129 | unsafe { curses::wchgat(self._window, n, attributes, color_pair, ptr::null_mut()) } 130 | } 131 | 132 | /// Similar to erase(), but also calls clearok() to ensure that the the window is cleared on 133 | /// the next refresh(). 134 | pub fn clear(&self) -> i32 { 135 | unsafe { curses::wclear(self._window) } 136 | } 137 | 138 | /// With clearok(), if bf is TRUE, the next call to refresh() with 139 | /// this window will clear the screen completely and redraw the 140 | /// entire screen. 141 | pub fn clearok(&self, bf: bool) -> i32 { 142 | unsafe { curses::clearok(self._window, bf as u8) } 143 | } 144 | 145 | /// Clear the window from the current cursor position to the end of the window. 146 | pub fn clrtobot(&self) -> i32 { 147 | unsafe { curses::wclrtobot(self._window) } 148 | } 149 | 150 | /// Clear the window from the current cursor position to the end of the current line. 151 | pub fn clrtoeol(&self) -> i32 { 152 | unsafe { curses::wclrtoeol(self._window) } 153 | } 154 | 155 | /// Sets the current color of the given window to the foreground/background combination 156 | /// described by the color pair parameter. 157 | pub fn color_set(&self, color_pair: i16) -> i32 { 158 | unsafe { curses::wcolor_set(self._window, color_pair, ptr::null_mut()) } 159 | } 160 | 161 | /// Copy all text from this window to the destination window. The arguments src_tc and 162 | /// src_tr specify the top left corner of the region to be copied. dst_tc, dst_tr, dst_br, 163 | /// and dst_bc specify the region within the destination window to copy to. The argument 164 | /// "overlay", if TRUE, indicates that the copy is done non-destructively (as in overlay()); 165 | /// blanks in the source window are not copied to the destination window. When overlay is 166 | /// FALSE, blanks are copied. 167 | #[allow(clippy::too_many_arguments)] 168 | pub fn copywin( 169 | &self, 170 | destination_window: &Window, 171 | src_tr: i32, 172 | src_tc: i32, 173 | dst_tr: i32, 174 | dst_tc: i32, 175 | dst_br: i32, 176 | dst_bc: i32, 177 | overlay: bool, 178 | ) -> i32 { 179 | unsafe { 180 | curses::copywin( 181 | self._window, 182 | destination_window._window, 183 | src_tr, 184 | src_tc, 185 | dst_tr, 186 | dst_tc, 187 | dst_br, 188 | dst_bc, 189 | overlay as i32, 190 | ) 191 | } 192 | } 193 | 194 | /// Delete the character under the cursor. All characters to the right of the cursor 195 | /// on the same line are moved to the left one position and hte last character on the 196 | /// line is filled with a blank. The cursor position does not change. 197 | pub fn delch(&self) -> i32 { 198 | unsafe { curses::wdelch(self._window) } 199 | } 200 | 201 | /// Delete the line under the cursor. All lines below are moved up one line, and the 202 | /// bottom line is cleared. The cursor position does not change. 203 | pub fn deleteln(&self) -> i32 { 204 | unsafe { curses::wdeleteln(self._window) } 205 | } 206 | 207 | /// Deletes the window, freeing all associated memory. In the case of overlapping windows, 208 | /// subwindows should be deleted before the main window. 209 | pub fn delwin(self) -> i32 { 210 | let r = unsafe { curses::delwin(self._window) }; 211 | std::mem::forget(self); 212 | r 213 | } 214 | 215 | /// The same as subwin(), except that begy and begx are relative to the origin of the window 216 | /// rather than the screen. 217 | /// 218 | /// There is no difference between subwindows and derived windows. 219 | pub fn derwin(&self, nlines: i32, ncols: i32, begy: i32, begx: i32) -> Result { 220 | self.subwin( 221 | nlines, 222 | ncols, 223 | begy + self.get_beg_y(), 224 | begx + self.get_beg_x(), 225 | ) 226 | } 227 | 228 | /// Draw a border around the edge of the window. If any argument is zero, an appropriate 229 | /// default is used. 230 | pub fn draw_box(&self, verch: T, horch: T) -> i32 { 231 | platform_specific::_draw_box(self._window, verch.to_chtype(), horch.to_chtype()) 232 | } 233 | 234 | /// Creates an exact duplicate of the window. 235 | pub fn dupwin(&self) -> Window { 236 | let dup_win = unsafe { curses::dupwin(self._window) }; 237 | Window { 238 | _window: dup_win, 239 | _stdscr: false, 240 | } 241 | } 242 | 243 | /// Reports whether the given screen-relative y, x coordinates fall within the window. 244 | pub fn enclose(&self, y: i32, x: i32) -> bool { 245 | unsafe { curses::wenclose(self._window, y, x) > 0 } 246 | } 247 | 248 | /// Copies blanks (i.e. the background chtype) to every cell of the window. 249 | pub fn erase(&self) -> i32 { 250 | unsafe { curses::werase(self._window) } 251 | } 252 | 253 | /// Get the upper-left y coordinate of this window 254 | pub fn get_beg_y(&self) -> i32 { 255 | unsafe { curses::getbegy(self._window) } 256 | } 257 | 258 | // Get the upper-left x coordinate of this window 259 | pub fn get_beg_x(&self) -> i32 { 260 | unsafe { curses::getbegx(self._window) } 261 | } 262 | 263 | /// Get the upper-left y and x coordinates of this window 264 | pub fn get_beg_yx(&self) -> (i32, i32) { 265 | (self.get_beg_y(), self.get_beg_x()) 266 | } 267 | 268 | /// Returns the given window's current background character and attributes. 269 | pub fn getbkgd(&self) -> chtype { 270 | unsafe { curses::getbkgd(self._window) } 271 | } 272 | 273 | /// Read a character from the terminal associated with the window. 274 | /// 275 | /// In nodelay mode, if there is no input waiting, None is returned. In delay mode, 276 | /// the program will hang until the system passes text through to the program. Depending on 277 | /// the setting of cbreak(), this will be after one character or after the first newline. 278 | /// Unless noecho() has been set, the character will also be echoed into the designated window. 279 | /// 280 | /// If keypad() is TRUE, and a function key is pressed, the token for that function key will be 281 | /// returned instead of the raw characters. 282 | /// If nodelay(win, TRUE) has been called on the window and no input is waiting, None is 283 | /// returned. 284 | pub fn getch(&self) -> Option { 285 | platform_specific::_wgetch(self._window) 286 | } 287 | 288 | /// Return the current x coordinate of the cursor 289 | pub fn get_cur_x(&self) -> i32 { 290 | unsafe { curses::getcurx(self._window) } 291 | } 292 | 293 | /// Return the current y coordinate of the cursor 294 | pub fn get_cur_y(&self) -> i32 { 295 | unsafe { curses::getcury(self._window) } 296 | } 297 | 298 | /// Return the current y and x coordinates of the cursor 299 | pub fn get_cur_yx(&self) -> (i32, i32) { 300 | (self.get_cur_y(), self.get_cur_x()) 301 | } 302 | 303 | /// Return the maximum x value of this Window, in other words the number of columns. 304 | pub fn get_max_x(&self) -> i32 { 305 | unsafe { curses::getmaxx(self._window) } 306 | } 307 | 308 | /// Return the maximum y value of this Window, in other words the number of rows. 309 | pub fn get_max_y(&self) -> i32 { 310 | unsafe { curses::getmaxy(self._window) } 311 | } 312 | 313 | /// Return the maximum y and x value of this Window 314 | pub fn get_max_yx(&self) -> (i32, i32) { 315 | (self.get_max_y(), self.get_max_x()) 316 | } 317 | 318 | /// Draw a horizontal line using ch from the current cursor position. The line is at most 319 | /// n characters long, or as many as fit into the window. 320 | pub fn hline(&self, ch: T, n: i32) -> i32 { 321 | unsafe { curses::whline(self._window, ch.to_chtype(), n) } 322 | } 323 | 324 | /// For positive n, insert n lines into the specified window above the current line. 325 | /// The n bottom lines are lost. For negative n, delete n lines (starting with the one under 326 | /// the cursor), and move the remaining lines up. The bottom n lines are cleared. 327 | /// The current cursor position remains the same. 328 | pub fn insdelln(&self, n: i32) -> i32 { 329 | unsafe { curses::winsdelln(self._window, n) } 330 | } 331 | 332 | /// A blank line is inserted above the current line and the bottom line is lost. 333 | pub fn insertln(&self) -> i32 { 334 | unsafe { curses::winsertln(self._window) } 335 | } 336 | 337 | /// Returns true if the specified line in the specified window has been changed since the last 338 | /// call to refresh(). 339 | pub fn is_linetouched(&self, line: i32) -> bool { 340 | unsafe { curses::is_linetouched(self._window, line) > 0 } 341 | } 342 | 343 | /// Returns true if the specified window has been changed since the last call to refresh(). 344 | pub fn is_touched(&self) -> bool { 345 | unsafe { curses::is_wintouched(self._window) > 0 } 346 | } 347 | 348 | /// Controls whether getch() returns function/special keys as single key codes (e.g., the left 349 | /// arrow key as KEY_LEFT). 350 | /// 351 | /// Per X/Open, the default for keypad mode is OFF. You'll probably want it on. With keypad 352 | /// mode off, if a special key is pressed, getch() does nothing or returns ERR. 353 | pub fn keypad(&self, use_keypad: bool) -> i32 { 354 | unsafe { curses::keypad(self._window, use_keypad as u8) } 355 | } 356 | 357 | /// Insert the character ch before the character under the cursor. 358 | /// 359 | /// All characters to the right of the cursor are moved one space to the right, with the 360 | /// possibility of the rightmost character on the line being lost. The insertion operation does 361 | /// not change the cursor position. 362 | pub fn insch(&self, ch: T) -> i32 { 363 | unsafe { curses::winsch(self._window, ch.to_chtype()) } 364 | } 365 | 366 | /// Converts between screen-relative and window-relative coordinates. 367 | /// 368 | /// A to_screen parameter of true means to convert from window to screen; 369 | /// otherwise the reverse. 370 | pub fn mouse_trafo(&self, y: i32, x: i32, to_screen: bool) -> (i32, i32) { 371 | let mut mut_y = y; 372 | let mut mut_x = x; 373 | unsafe { curses::wmouse_trafo(self._window, &mut mut_y, &mut mut_x, to_screen as u8); } 374 | (mut_y, mut_x) 375 | } 376 | 377 | /// The cursor associated with the window is moved to the given location. 378 | /// 379 | /// This does not move the physical cursor of the terminal until refresh() is called. The 380 | /// position specified is relative to the upper left corner of the window, which is (0,0). 381 | pub fn mv(&self, y: i32, x: i32) -> i32 { 382 | unsafe { curses::wmove(self._window, y, x) } 383 | } 384 | 385 | /// moves the cursor to the specified position and adds ch to the specified window 386 | pub fn mvaddch(&self, y: i32, x: i32, ch: T) -> i32 { 387 | unsafe { curses::mvwaddch(self._window, y, x, ch.to_chtype()) } 388 | } 389 | 390 | /// Write all the characters of the string str to the given window. The functionality is 391 | /// similar to calling waddch() once for each character in the string. 392 | pub fn mvaddstr>(&self, y: i32, x: i32, string: T) -> i32 { 393 | let s = CString::new(string.as_ref()).unwrap(); 394 | unsafe { curses::mvwaddstr(self._window, y, x, s.as_ptr()) } 395 | } 396 | 397 | /// Write the first'n' characters of the string str to the given window. 398 | pub fn mvaddnstr>(&self, y: i32, x: i32, string: T, n: i32) -> i32 { 399 | let s = CString::new(string.as_ref()).unwrap(); 400 | unsafe { curses::mvwaddnstr(self._window, y, x, s.as_ptr(), n) } 401 | } 402 | 403 | /// Moves the cursor and changes the attributes of a given number of characters starting at the 404 | /// cursor location. It does not update the cursor and does not perform wrapping. A character count 405 | /// of -1 or greater than the remaining window width means to change attributes all the way 406 | /// to the end of the current line. 407 | pub fn mvchgat(&self, y: i32, x: i32, n: i32, attributes: chtype, color_pair: i16) -> i32 { 408 | unsafe { 409 | curses::mvwchgat( 410 | self._window, 411 | y, 412 | x, 413 | n, 414 | attributes, 415 | color_pair, 416 | ptr::null_mut(), 417 | ) 418 | } 419 | } 420 | 421 | /// Moves a derived window (or subwindow) inside its parent window. 422 | /// 423 | /// The screen-relative parameters of the window are not changed. This routine is used to 424 | /// display different parts of the parent window at the same physical position on the screen. 425 | pub fn mvderwin(&self, pary: i32, parx: i32) -> i32 { 426 | unsafe { curses::mvderwin(self._window, pary, parx) } 427 | } 428 | 429 | /// Retrieves the character and attribute from the specified window position, in the form of a 430 | /// chtype. 431 | pub fn mvinch(&self, y: i32, x: i32) -> chtype { 432 | unsafe { curses::mvwinch(self._window, y, x) } 433 | } 434 | 435 | /// Move the cursor and then insert the character ch before the character under the cursor. 436 | /// 437 | /// First performs a cursor movement using wmove, and returns an error if the position is 438 | /// outside the window. All characters to the right of the cursor are moved one space to the 439 | /// right, with the possibility of the rightmost character on the line being lost. The insertion 440 | /// operation does not change the cursor position. 441 | pub fn mvinsch(&self, y: i32, x: i32, ch: T) -> i32 { 442 | unsafe { curses::mvwinsch(self._window, y, x, ch.to_chtype()) } 443 | } 444 | 445 | /// Add a string to the window at the specified cursor position. 446 | pub fn mvprintw>(&self, y: i32, x: i32, string: T) -> i32 { 447 | let s = CString::new(string.as_ref()).unwrap(); 448 | unsafe { curses::mvwprintw(self._window, y, x, s.as_ptr()) } 449 | } 450 | 451 | /// Moves the window so that the upper left-hand corner is at position (y,x). 452 | /// 453 | /// If the move would cause the window to be off the screen, it is an error and the window is 454 | /// not moved. Moving subwindows is allowed. 455 | pub fn mvwin(&self, y: i32, x: i32) -> i32 { 456 | unsafe { curses::mvwin(self._window, y, x) } 457 | } 458 | 459 | /// Controls whether wgetch() is a non-blocking call. If the option is enabled, and 460 | /// no input is ready, wgetch() will return ERR. If disabled, wgetch() will hang until input is 461 | /// ready. 462 | pub fn nodelay(&self, enabled: bool) -> i32 { 463 | unsafe { curses::nodelay(self._window, enabled as u8) as i32 } 464 | } 465 | 466 | /// Copies the window to the virtual screen. 467 | pub fn noutrefresh(&self) -> i32 { 468 | unsafe { curses::wnoutrefresh(self._window) } 469 | } 470 | 471 | /// Overlays this window on top of destination_window. This window and destination_window are 472 | /// not required to be the same size; only text where the two windows overlap is copied. 473 | /// overlay() is non-destructive. 474 | pub fn overlay(&self, destination_window: &Window) -> i32 { 475 | unsafe { curses::overlay(self._window, destination_window._window) } 476 | } 477 | 478 | /// Overlays this window on top of destination_window. This window and destination_window are 479 | /// not required to be the same size; only text where the two windows overlap is copied. 480 | /// overwrite() is destructive. 481 | pub fn overwrite(&self, destination_window: &Window) -> i32 { 482 | unsafe { curses::overwrite(self._window, destination_window._window) } 483 | } 484 | 485 | /// Add a string to the window at the current cursor position. 486 | pub fn printw>(&self, string: T) -> i32 { 487 | let s = CString::new(string.as_ref()).unwrap(); 488 | unsafe { curses::wprintw(self._window, s.as_ptr()) } 489 | } 490 | 491 | /// Copies the named window to the physical terminal screen, taking into account what 492 | /// is already there in order to optimize cursor movement. 493 | /// 494 | /// This function must be called to get any output on the terminal, as other routines only 495 | /// manipulate data structures. Unless leaveok() has been enabled, the physical cursor of the 496 | /// terminal is left at the location of the window's cursor. 497 | pub fn refresh(&self) -> i32 { 498 | unsafe { curses::wrefresh(self._window) } 499 | } 500 | 501 | /// Resizes the window to the given dimensions. Doesn't resize subwindows on pdcurses 502 | /// so you have to resize them yourself. 503 | pub fn resize(&mut self, nlines: i32, ncols: i32) -> i32 { 504 | unsafe { curses::wresize(self._window, nlines, ncols) } 505 | } 506 | 507 | /// If enabled and a scrolling region is set with setscrreg(), any attempt to move off 508 | /// the bottom margin will cause all lines in the scrolling region to scroll up one line. 509 | pub fn scrollok(&self, bf: bool) -> i32 { 510 | unsafe { curses::scrollok(self._window, bf as u8) } 511 | } 512 | 513 | /// Sets a scrolling region in a window. 514 | /// 515 | /// "top" and "bot" are the line numbers for the top and bottom margins. 516 | pub fn setscrreg(&self, top: i32, bot: i32) -> i32 { 517 | unsafe { curses::wsetscrreg(self._window, top, bot) } 518 | } 519 | 520 | /// Creates a new subwindow within a window. 521 | /// 522 | /// The dimensions of the subwindow are nlines lines and ncols columns. The subwindow is at 523 | /// position (begy, begx) on the screen. This position is relative to the screen, and not to 524 | /// the window orig. Changes made to either window will affect both. When using this routine, 525 | /// you will often need to call touchwin() before calling refresh(). 526 | pub fn subwin(&self, nlines: i32, ncols: i32, begy: i32, begx: i32) -> Result { 527 | let new_window = unsafe { curses::subwin(self._window, nlines, ncols, begy, begx) }; 528 | if new_window.is_null() { 529 | Err(ERR) 530 | } else { 531 | Ok(Window { 532 | _window: new_window, 533 | _stdscr: false, 534 | }) 535 | } 536 | } 537 | 538 | /// Set blocking or non-blocking reads for the specified window. 539 | /// 540 | /// The delay is measured in milliseconds. If it's negative, a blocking read is used; if zero, 541 | /// then non-blocking reads are done -- if no input is waiting, ERR is returned immediately. 542 | /// If the delay is positive, the read blocks for the delay period; if the period expires, 543 | /// ERR is returned. 544 | pub fn timeout(&self, milliseconds: i32) { 545 | unsafe { curses::wtimeout(self._window, milliseconds) } 546 | } 547 | 548 | /// Throws away all information about which parts of the window have been touched, pretending 549 | /// that the entire window has been drawn on. 550 | /// 551 | /// This is sometimes necessary when using overlapping windows, since a change to one window 552 | /// will affect the other window, but the records of which lines have been changed in the other 553 | /// window will not reflect the change. 554 | pub fn touch(&self) -> i32 { 555 | unsafe { curses::touchwin(self._window) } 556 | } 557 | 558 | /// Throws away all information about which parts of the window have been touched, pretending 559 | /// that the entire window has been drawn on. 560 | /// 561 | /// This is sometimes necessary when using overlapping windows, since a change to one window 562 | /// will affect the other window, but the records of which lines have been changed in the other 563 | /// window will not reflect the change. 564 | pub fn touchline(&self, start: i32, count: i32) -> i32 { 565 | unsafe { curses::touchline(self._window, start, count) } 566 | } 567 | 568 | /// Makes n lines in the window, starting at line y, look as if they have or have not been 569 | /// changed since the last call to refresh(). 570 | pub fn touchln(&self, y: i32, n: i32, changed: bool) -> i32 { 571 | unsafe { curses::wtouchln(self._window, y, n, if changed { 1 } else { 0 }) } 572 | } 573 | 574 | /// Places ch back onto the input queue to be returned by the next call to getch(). 575 | pub fn ungetch(&self, input: &Input) -> i32 { 576 | platform_specific::_ungetch(input) 577 | } 578 | 579 | /// Marks all lines in the window as unchanged since the last call to refresh(). 580 | pub fn untouch(&self) -> i32 { 581 | unsafe { curses::untouchwin(self._window) } 582 | } 583 | 584 | /// Draw a vertical line using ch from the current cursor position. The line is at most 585 | /// n characters long, or as many as fit into the window. 586 | pub fn vline(&self, ch: T, n: i32) -> i32 { 587 | unsafe { curses::wvline(self._window, ch.to_chtype(), n) } 588 | } 589 | } 590 | 591 | pub fn new_window(window_pointer: WindowPointer, is_stdscr: bool) -> Window { 592 | Window { 593 | _window: window_pointer, 594 | _stdscr: is_stdscr, 595 | } 596 | } 597 | 598 | /// Automatically clean up window resources when dropped 599 | impl Drop for Window { 600 | fn drop(&mut self) { 601 | if !self._stdscr { 602 | unsafe { 603 | curses::delwin(self._window); 604 | } 605 | } 606 | } 607 | } 608 | --------------------------------------------------------------------------------