tuinix/lib.rs
1//! A library for building terminal user interface (TUI) applications on Unix systems with minimum dependencies.
2//!
3//! `tuinix` provides a lightweight foundation for building terminal-based user interfaces with minimal
4//! dependencies (only `libc` is required). The library offers a clean API for:
5//!
6//! - Managing terminal state (raw mode, alternate screen)
7//! - Capturing and processing keyboard input
8//! - Drawing styled text with ANSI colors
9//! - Handling terminal resize events
10//! - Creating efficient terminal frames with differential updates
11//!
12//! ## Basic Example
13//!
14//! This example demonstrates basic terminal UI functionality including initializing the terminal,
15//! drawing styled text, processing keyboard events, and handling terminal resizing.
16//!
17//! ```no_run
18//! use std::{fmt::Write, time::Duration};
19//!
20//! use tuinix::{Terminal, TerminalColor, TerminalEvent, TerminalFrame, TerminalInput, TerminalStyle};
21//!
22//! fn main() -> Result<(), Box<dyn std::error::Error>> {
23//! // Initialize terminal
24//! let mut terminal = Terminal::new()?;
25//!
26//! // Create a frame with the terminal's dimensions
27//! let mut frame: TerminalFrame = TerminalFrame::new(terminal.size());
28//!
29//! // Add styled content to the frame
30//! let title_style = TerminalStyle::new().bold().fg_color(TerminalColor::GREEN);
31//!
32//! writeln!(
33//! frame,
34//! "{}Welcome to tuinix!{}",
35//! title_style,
36//! TerminalStyle::RESET
37//! )?;
38//! writeln!(frame, "\nPress any key ('q' to quit)")?;
39//!
40//! // Draw the frame to the terminal
41//! terminal.draw(frame)?;
42//!
43//! // Process input events with a timeout
44//! loop {
45//! match terminal.poll_event(&[], &[], Some(Duration::from_millis(100)))? {
46//! Some(TerminalEvent::Input(input)) => {
47//! let TerminalInput::Key(input) = input else {
48//! continue; // Skip mouse events
49//! };
50//!
51//! // Check if 'q' was pressed
52//! if let tuinix::KeyCode::Char('q') = input.code {
53//! break;
54//! }
55//!
56//! // Display the input
57//! let mut frame: TerminalFrame = TerminalFrame::new(terminal.size());
58//! writeln!(frame, "Key pressed: {:?}", input)?;
59//! writeln!(frame, "\nPress any key ('q' to quit)")?;
60//! terminal.draw(frame)?;
61//! }
62//! Some(TerminalEvent::Resize(size)) => {
63//! // Terminal was resized, update UI if needed
64//! let mut frame: TerminalFrame = TerminalFrame::new(size);
65//! writeln!(frame, "Terminal resized to {}x{}", size.cols, size.rows)?;
66//! writeln!(frame, "\nPress any key ('q' to quit)")?;
67//! terminal.draw(frame)?;
68//! }
69//! Some(TerminalEvent::FdReady { .. }) => unreachable!(),
70//! None => {
71//! // Timeout elapsed, no events to process
72//! }
73//! }
74//! }
75//!
76//! Ok(())
77//! }
78//! ```
79//!
80//! For integration with external event loop libraries like `mio`, see the [nonblocking.rs] example.
81//!
82//! [nonblocking.rs]: https://github.com/sile/tuinix/blob/main/examples/nonblocking.rs
83#![warn(missing_docs)]
84use std::{io::ErrorKind, os::fd::RawFd};
85
86mod frame;
87mod geometry;
88mod input;
89mod style;
90mod terminal;
91
92pub use frame::{EstimateCharWidth, FixedCharWidthEstimator, TerminalFrame};
93pub use geometry::{TerminalPosition, TerminalRegion, TerminalSize};
94pub use input::{KeyCode, KeyInput, MouseEvent, MouseInput, TerminalInput};
95pub use style::{TerminalColor, TerminalStyle};
96pub use terminal::{Terminal, TerminalEvent};
97
98/// Sets a file descriptor to non-blocking mode.
99///
100/// This function modifies the flags of the given file descriptor (`fd`) to
101/// include the `O_NONBLOCK` flag, which makes operations on the file descriptor
102/// non-blocking.
103///
104/// When a file descriptor is in non-blocking mode, operations that would normally
105/// block until data is available (such as `read`) or until resources are ready
106/// (such as `write`) will instead immediately return with [`std::io::ErrorKind::WouldBlock`]
107/// if the operation cannot be completed without blocking. This allows the calling
108/// thread to continue execution and check for availability later, which is
109/// particularly useful in asynchronous I/O patterns.
110pub fn set_nonblocking(fd: RawFd) -> std::io::Result<()> {
111 unsafe {
112 let flags = libc::fcntl(fd, libc::F_GETFL, 0);
113 if flags < 0 {
114 return Err(std::io::Error::last_os_error());
115 }
116 if libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) < 0 {
117 return Err(std::io::Error::last_os_error());
118 }
119 Ok(())
120 }
121}
122
123/// Handles the result of a non-blocking I/O operation by converting [`ErrorKind::WouldBlock`] errors to `Ok(None)`.
124///
125/// This utility function is designed to work with non-blocking I/O operations (typically used after
126/// calling [`set_nonblocking()`] on [`Terminal::input_fd()`] and [`Terminal::signal_fd()`]). When a non-blocking operation returns a
127/// [`ErrorKind::WouldBlock`] error, indicating that the operation would need to block to complete, this function
128/// converts it to `Ok(None)` for easier handling in caller code.
129pub fn try_nonblocking<T>(result: std::io::Result<T>) -> std::io::Result<Option<T>> {
130 match result {
131 Err(e) if e.kind() == ErrorKind::WouldBlock => Ok(None),
132 Err(e) => Err(e),
133 Ok(v) => Ok(Some(v)),
134 }
135}
136
137/// Handles the result of an I/O operation that might be interrupted by converting [`ErrorKind::Interrupted`] errors to `Ok(None)`.
138///
139/// This utility function manages system calls that can be interrupted by signals. When an I/O operation
140/// returns an [`ErrorKind::Interrupted`] error, indicating that a system call was interrupted by a signal
141/// before it could complete, this function converts it to `Ok(None)` for easier handling in caller code.
142///
143/// This is particularly useful in scenarios where you want to retry operations that were interrupted,
144/// rather than propagating the error.
145pub fn try_uninterrupted<T>(result: std::io::Result<T>) -> std::io::Result<Option<T>> {
146 match result {
147 Err(e) if e.kind() == ErrorKind::Interrupted => Ok(None),
148 Err(e) => Err(e),
149 Ok(v) => Ok(Some(v)),
150 }
151}