mirror of
https://github.com/Xyverle/neutuino.git
synced 2026-06-26 22:23:14 -04:00
0.3.0, I really need to start breaking up these updates
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4,4 +4,4 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "neutuino"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "neutuino"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
edition = "2024"
|
||||
repository = "https://github.com/Xyverle/neutuino"
|
||||
description = "A minimal zero-dependancy pure-rust cross-platform TUI library"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Neutuino
|
||||
Zero dependancy cross-platform pure-rust simple TUI library
|
||||
|
||||
Supported OSes: Windows 10+, MacOS (untested), Linux
|
||||
Supported OSes: Windows 10+, MacOS (untested), and Linux
|
||||
|
||||
This project is still highly work in progress and it will be a decent while until feature completeness
|
||||
|
||||
## Todo
|
||||
## Roadmap
|
||||
- [x] Output (Unix)
|
||||
- [x] Output (Windows)
|
||||
- [x] Input (Unix) (Appears to work, more testing needed)
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
#![warn(clippy::all, clippy::pedantic)]
|
||||
|
||||
use neutuino::ansi::{
|
||||
COLORS_BG, COLORS_FG, STYLE_BOLD, STYLE_ITALIC, STYLE_RESET, STYLE_UNDERLINE,
|
||||
move_cursor_to_column, set_window_title,
|
||||
};
|
||||
use neutuino::input::{Event, KeyEvent, poll_input};
|
||||
use neutuino::os::{disable_raw_mode, enable_ansi, enable_raw_mode, get_terminal_size};
|
||||
use neutuino::prelude::*;
|
||||
use std::{io, time::Duration};
|
||||
|
||||
fn print_line_style_reset(string: &str) {
|
||||
@@ -16,13 +11,13 @@ fn main() -> io::Result<()> {
|
||||
let all_styles = format!("{STYLE_BOLD}{STYLE_ITALIC}{STYLE_UNDERLINE}");
|
||||
|
||||
enable_ansi()?;
|
||||
enable_raw_mode()?;
|
||||
let _raw_terminal = RawModeHandler::new()?;
|
||||
|
||||
println!("q to quit{}", move_cursor_to_column(0));
|
||||
let next = |x: usize| (x + 1) % COLORS_FG.len();
|
||||
|
||||
let terminal_size = get_terminal_size()?;
|
||||
let terminal_size_str = format!("{terminal_size:?}");
|
||||
let terminal_size_str = format!("{:?}", terminal_size);
|
||||
print!("{}", set_window_title(terminal_size_str).unwrap());
|
||||
|
||||
let mut counter = 0;
|
||||
@@ -45,6 +40,5 @@ fn main() -> io::Result<()> {
|
||||
}
|
||||
counter = next(counter);
|
||||
}
|
||||
disable_raw_mode()?;
|
||||
Ok(())
|
||||
}
|
||||
34
examples/simple.rs
Normal file
34
examples/simple.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
#![warn(clippy::all, clippy::pedantic)]
|
||||
|
||||
use neutuino::prelude::*;
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
thread, time,
|
||||
};
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
enable_ansi()?;
|
||||
|
||||
// makes the terminal raw until this value is dropped
|
||||
let _raw_terminal = RawModeHandler::new()?;
|
||||
let _alt_screen = AltScreenHandler::new()?;
|
||||
|
||||
// gets the size of the terminal
|
||||
let terminal_size = get_terminal_size()?;
|
||||
let middle = (terminal_size.0 / 2, terminal_size.1 / 2);
|
||||
|
||||
let string = "Hello, World!";
|
||||
|
||||
let adjusted_middle = (middle.0 - ((string.len() / 2) as u16), middle.1);
|
||||
|
||||
print!(
|
||||
"{COLOR_RED_BG}{}{string}",
|
||||
move_cursor_to_position(adjusted_middle.0, adjusted_middle.1)
|
||||
);
|
||||
io::stdout().flush()?; // VERY IMPORTANT!
|
||||
|
||||
thread::sleep(time::Duration::new(3, 0));
|
||||
|
||||
// no flush needed here as the program is about to end and it will be auto flushed
|
||||
Ok(())
|
||||
}
|
||||
96
src/ansi.rs
96
src/ansi.rs
@@ -4,21 +4,21 @@
|
||||
//!
|
||||
//! For these to work on Windows you need to run the `enable_ansi` function in the os module
|
||||
|
||||
/// Sets the terminal to an arbitrary 12-bit/truecolor color
|
||||
use std::io::{self, Write};
|
||||
|
||||
/// Sets the terminal to an arbitrary 12-bit/truecolor color in the foreground when printed
|
||||
#[must_use]
|
||||
pub fn rgb_color_code_fg(red: u8, green: u8, blue: u8) -> String {
|
||||
format!("\x1b[38;2;{red};{green};{blue}m")
|
||||
}
|
||||
|
||||
/// Sets the terminal to an arbitrary 12-bit/truecolor color
|
||||
/// Sets the terminal to an arbitrary 12-bit/truecolor color in the background when printed
|
||||
#[must_use]
|
||||
pub fn rgb_color_code_bg(red: u8, green: u8, blue: u8) -> String {
|
||||
format!("\x1b[48;2;{red};{green};{blue}m")
|
||||
}
|
||||
|
||||
/// Sets the title of the window
|
||||
///
|
||||
/// The title must be only in ASCII characters or **weird** things will happen
|
||||
/// Sets the title of the window when printed
|
||||
#[must_use]
|
||||
pub fn set_window_title<T: Into<String>>(title: T) -> Option<String> {
|
||||
let title = title.into();
|
||||
@@ -28,31 +28,31 @@ pub fn set_window_title<T: Into<String>>(title: T) -> Option<String> {
|
||||
Some(format!("\x1b]0;{title}\x1b\x5c"))
|
||||
}
|
||||
|
||||
/// Moves the cursor up {num} characters
|
||||
/// Moves the cursor up {num} characters when printed
|
||||
#[must_use]
|
||||
pub fn move_cursor_up(num: u16) -> String {
|
||||
format!("\x1b[{num}A")
|
||||
}
|
||||
|
||||
/// Moves the cursor down {num} characters
|
||||
/// Moves the cursor down {num} characters when printed
|
||||
#[must_use]
|
||||
pub fn move_cursor_down(num: u16) -> String {
|
||||
format!("\x1b[{num}B")
|
||||
}
|
||||
|
||||
/// Moves the cursor right {num} characters
|
||||
/// Moves the cursor right {num} characters when printed
|
||||
#[must_use]
|
||||
pub fn move_cursor_right(num: u16) -> String {
|
||||
format!("\x1b[{num}C")
|
||||
}
|
||||
|
||||
/// Moves the cursor left {num} characters
|
||||
/// Moves the cursor left {num} characters when printed
|
||||
#[must_use]
|
||||
pub fn move_cursor_left(num: u16) -> String {
|
||||
format!("\x1b[{num}A")
|
||||
}
|
||||
|
||||
/// Moves the cursor to {row}
|
||||
/// Moves the cursor to {row} when printed
|
||||
///
|
||||
/// Origin is 0, 0
|
||||
#[must_use]
|
||||
@@ -60,7 +60,7 @@ pub fn move_cursor_to_row(line: u16) -> String {
|
||||
format!("\x1b[{}d", line.saturating_add(1))
|
||||
}
|
||||
|
||||
/// Moves the cursor to {column}
|
||||
/// Moves the cursor to {column} when printed
|
||||
///
|
||||
/// Origin is 0, 0
|
||||
#[must_use]
|
||||
@@ -68,7 +68,7 @@ pub fn move_cursor_to_column(column: u16) -> String {
|
||||
format!("\x1b[{}G", column.saturating_add(1))
|
||||
}
|
||||
|
||||
/// Moves the cursor to Position {x}, {y}
|
||||
/// Moves the cursor to Position {x}, {y} when printed
|
||||
///
|
||||
/// Origin is 0, 0
|
||||
#[must_use]
|
||||
@@ -250,3 +250,75 @@ pub const COLORS: [(&str, &str); 9] = [
|
||||
(COLOR_WHITE_FG, COLOR_WHITE_BG),
|
||||
(COLOR_DEFAULT_FG, COLOR_DEFAULT_BG),
|
||||
];
|
||||
|
||||
/// Struct that prints `ALT_SCREEN_ENTER` on construction
|
||||
/// and `ALT_SCREEN_EXIT` on destruction
|
||||
///
|
||||
/// Prefered over function as it prints `ALT_SCREEN_EXIT` on panic
|
||||
pub struct AltScreenHandler {
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl AltScreenHandler {
|
||||
/// Creates a new instance and sets the terminal into the alternate screen
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If it fails to print or flush the output
|
||||
pub fn new() -> io::Result<Self> {
|
||||
print!("{ALT_SCREEN_ENTER}");
|
||||
io::stdout().flush()?;
|
||||
Ok(Self { enabled: true })
|
||||
}
|
||||
/// Enables raw mode
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Never errors if the alt screen is already enabled
|
||||
///
|
||||
/// If it fails to print or flush the output
|
||||
pub fn enable(&mut self) -> io::Result<()> {
|
||||
self.set(true)
|
||||
}
|
||||
/// Disables raw mode
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Never errors if the alt screen is already disabled
|
||||
///
|
||||
/// If it fails to print or flush the output
|
||||
pub fn disable(&mut self) -> io::Result<()> {
|
||||
self.set(false)
|
||||
}
|
||||
/// Sets raw mode
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Never errors if the alt screen is in the same state as the boolean
|
||||
///
|
||||
/// If it fails to print or flush the output
|
||||
pub fn set(&mut self, alt: bool) -> io::Result<()> {
|
||||
if self.enabled == alt {
|
||||
return Ok(());
|
||||
}
|
||||
if alt {
|
||||
print!("{ALT_SCREEN_ENTER}");
|
||||
} else {
|
||||
print!("{ALT_SCREEN_EXIT}");
|
||||
}
|
||||
io::stdout().flush()?;
|
||||
self.enabled = alt;
|
||||
Ok(())
|
||||
}
|
||||
/// Gets if the alt screen is enabled
|
||||
#[must_use]
|
||||
pub fn get(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AltScreenHandler {
|
||||
fn drop(&mut self) {
|
||||
self.disable().expect("Failed to disable alternate screen");
|
||||
}
|
||||
}
|
||||
|
||||
69
src/input.rs
69
src/input.rs
@@ -0,0 +1,69 @@
|
||||
//! Various input functions, structs, etc.
|
||||
//!
|
||||
//! Very incomplete currently
|
||||
|
||||
/// Different events that can happen through the terminal
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Event {
|
||||
/// An event that happens upon a key being pressed
|
||||
Key(KeyEvent),
|
||||
/// An event that happens upon focus to the terminal window being gained
|
||||
FocusGained,
|
||||
/// An event that happens upon focus to the terminal window being lost
|
||||
FocusLost,
|
||||
}
|
||||
|
||||
/// An event that happens upon a key being pressed
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum KeyEvent {
|
||||
/// The Backspace key
|
||||
Backspace,
|
||||
/// The Up arrow key
|
||||
Up,
|
||||
/// The Down arrow key
|
||||
Down,
|
||||
/// The Left arrow key
|
||||
Left,
|
||||
/// The Right arrow key
|
||||
Right,
|
||||
/// The Home key
|
||||
Home,
|
||||
/// The End key
|
||||
End,
|
||||
/// The PageUp key
|
||||
PageUp,
|
||||
/// The PageDown key
|
||||
PageDown,
|
||||
/// The Tab key
|
||||
Tab,
|
||||
/// Shift + Tab key
|
||||
ShiftTab,
|
||||
/// The delete key
|
||||
Delete,
|
||||
/// The insert key
|
||||
Insert,
|
||||
/// The f1-f12 keys
|
||||
F(u8),
|
||||
/// Any character inputted by the keyboard
|
||||
Char(char),
|
||||
/// Ctrl + Char
|
||||
Ctrl(char),
|
||||
/// The Escape key
|
||||
Escape,
|
||||
/// A null byte sent to the terminal
|
||||
///
|
||||
/// Can mean several different things
|
||||
Null,
|
||||
}
|
||||
|
||||
impl From<KeyEvent> for Event {
|
||||
fn from(value: KeyEvent) -> Self {
|
||||
Self::Key(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use crate::unix::input::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use crate::windows::input::*;
|
||||
|
||||
77
src/lib.rs
77
src/lib.rs
@@ -1,62 +1,35 @@
|
||||
#![warn(clippy::all, clippy::pedantic)]
|
||||
|
||||
pub mod ansi;
|
||||
// this lint has way too many false positives
|
||||
#![allow(clippy::doc_markdown)]
|
||||
//! This crate is a simple and minimal TUI library that supports the following OSes:
|
||||
//! - Windows 10+
|
||||
//! - MacOS (currently untested)
|
||||
//! - Linux
|
||||
//!
|
||||
//! ## Roadmap
|
||||
//! - [x] Output (Unix)
|
||||
//! - [x] Output (Windows)
|
||||
//! - [x] Input (Unix) (Appears to work, more testing needed)
|
||||
//! - [ ] Input (Windows) (WIP)
|
||||
//! - [ ] Events (Focus reporting, Bracketed-paste) (Unix)
|
||||
//! - [ ] Events (Focus reporting, Bracketed-paste) (Windows)
|
||||
//! - [ ] Mouse input (Unix)
|
||||
//! - [ ] Mouse input (Windows)
|
||||
//! - [ ] Feature completeness / API cleanup
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use crate::unix::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use crate::windows::*;
|
||||
pub mod ansi;
|
||||
pub mod input;
|
||||
pub mod os;
|
||||
|
||||
pub mod input {
|
||||
//! Various input functions, structs, etc.
|
||||
//!
|
||||
//! Very incomplete currently
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Event {
|
||||
Key(KeyEvent),
|
||||
FocusGained,
|
||||
FocusLost,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum KeyEvent {
|
||||
Backspace,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Home,
|
||||
End,
|
||||
PageUp,
|
||||
PageDown,
|
||||
Tab,
|
||||
BackTab,
|
||||
Delete,
|
||||
Insert,
|
||||
F(u8),
|
||||
Char(char),
|
||||
Ctrl(char),
|
||||
Escape,
|
||||
Null,
|
||||
}
|
||||
|
||||
impl From<KeyEvent> for Event {
|
||||
fn from(value: KeyEvent) -> Self {
|
||||
Self::Key(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use crate::unix::input::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use crate::windows::input::*;
|
||||
pub mod prelude {
|
||||
//! Covenience re-export of common members
|
||||
pub use crate::ansi::*;
|
||||
pub use crate::input::*;
|
||||
pub use crate::os::*;
|
||||
}
|
||||
|
||||
88
src/os.rs
88
src/os.rs
@@ -1,5 +1,89 @@
|
||||
//! Collection of functions that help control the terminal
|
||||
//!
|
||||
//! These are built to work at least on these platforms:
|
||||
//! Windows, Linux, and Mac, but are likely to work on more
|
||||
//! These are built to work on Windows, Linux, and MacOS
|
||||
|
||||
use std::io;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use crate::unix::os::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use crate::windows::os::*;
|
||||
|
||||
/// Struct that calls `enable_raw_mode` on construction
|
||||
/// and `disable_raw_mode` on destruction
|
||||
///
|
||||
/// Prefered over function as it calls `disable_raw_mode` on panic
|
||||
pub struct RawModeHandler {
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl RawModeHandler {
|
||||
/// Creates a new instance and sets the terminal to raw mode
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If there is no stdin,
|
||||
/// stdin is not a tty,
|
||||
/// or it fails to change terminal settings
|
||||
pub fn new() -> io::Result<Self> {
|
||||
enable_raw_mode()?;
|
||||
Ok(Self { enabled: true })
|
||||
}
|
||||
/// Enables raw mode
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Never errors if raw mode is already enabled
|
||||
///
|
||||
/// If there is no stdin,
|
||||
/// stdin is not a tty,
|
||||
/// or it fails to change terminal settings
|
||||
pub fn enable(&mut self) -> io::Result<()> {
|
||||
self.set(true)
|
||||
}
|
||||
/// Disables raw mode
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Never errors if raw mode is already disabled
|
||||
///
|
||||
/// If there is no stdin,
|
||||
/// stdin is not a tty,
|
||||
/// or it fails to change terminal settings
|
||||
pub fn disable(&mut self) -> io::Result<()> {
|
||||
self.set(false)
|
||||
}
|
||||
/// Sets raw mode
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Never errors if raw mode is in the same state as the boolean
|
||||
///
|
||||
/// If there is no stdin,
|
||||
/// stdin is not a tty,
|
||||
/// or it fails to change terminal settings
|
||||
pub fn set(&mut self, raw: bool) -> io::Result<()> {
|
||||
if self.enabled == raw {
|
||||
return Ok(());
|
||||
}
|
||||
if raw {
|
||||
enable_raw_mode()?;
|
||||
} else {
|
||||
disable_raw_mode()?;
|
||||
}
|
||||
self.enabled = raw;
|
||||
Ok(())
|
||||
}
|
||||
/// Gets if raw mode is enabled
|
||||
#[must_use]
|
||||
pub fn get(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RawModeHandler {
|
||||
fn drop(&mut self) {
|
||||
self.disable().expect("Failed to disable terminal raw mode");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ unsafe extern "C" {
|
||||
|
||||
const STDIN_FILENO: c_int = 0;
|
||||
const STDOUT_FILENO: c_int = 1;
|
||||
pub const POLLIN: c_short = 1;
|
||||
const POLLIN: c_short = 1;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
const TIOCGWINSZ: c_ulong = 0x5413;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
const NCCS: usize = 0x20;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -276,7 +276,7 @@ pub mod input {
|
||||
Some(Ok(b'B')) => Some(Event::Key(KeyEvent::Down)),
|
||||
Some(Ok(b'H')) => Some(Event::Key(KeyEvent::Home)),
|
||||
Some(Ok(b'F')) => Some(Event::Key(KeyEvent::End)),
|
||||
Some(Ok(b'Z')) => Some(Event::Key(KeyEvent::BackTab)),
|
||||
Some(Ok(b'Z')) => Some(Event::Key(KeyEvent::ShiftTab)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ pub mod input {
|
||||
0x08 => KeyEvent::Backspace, // VK_BACK
|
||||
0x09 => {
|
||||
if shift {
|
||||
KeyEvent::BackTab
|
||||
KeyEvent::ShiftTab
|
||||
} else {
|
||||
KeyEvent::Tab
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user