0.3.0, I really need to start breaking up these updates

This commit is contained in:
2025-05-23 06:02:12 -04:00
parent 2abc5fba5e
commit d27f92137d
11 changed files with 310 additions and 84 deletions

2
Cargo.lock generated
View File

@@ -4,4 +4,4 @@ version = 4
[[package]]
name = "neutuino"
version = "0.2.0"
version = "0.3.0"

View File

@@ -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"

View File

@@ -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)

View File

@@ -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
View 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(())
}

View File

@@ -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");
}
}

View File

@@ -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::*;

View File

@@ -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::*;
}

View File

@@ -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");
}
}

View File

@@ -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,
}
}

View File

@@ -241,7 +241,7 @@ pub mod input {
0x08 => KeyEvent::Backspace, // VK_BACK
0x09 => {
if shift {
KeyEvent::BackTab
KeyEvent::ShiftTab
} else {
KeyEvent::Tab
}