termal_core 5.0.0

This library contains implementation for the termal library
Documentation
//! Core library of termal, contains the implementation.

#![cfg_attr(docsrs, feature(doc_cfg))]

mod rgb;

use std::{
    io::{self, Write},
    panic,
};

use minlin::Cast;

pub use self::{err::*, rgb::*};

pub mod codes;
mod err;
#[cfg(feature = "term_image")]
pub mod image;
#[cfg(feature = "proc")]
pub mod proc;
#[cfg(feature = "progress")]
pub mod progress;
#[cfg(feature = "raw")]
pub mod raw;
#[cfg(feature = "term_text")]
pub mod term_text;

/// Appends linear gradient to the given string.
///
/// The gradient consists of characters given by `s`. `s_len` is the length of
/// `s` in characters. The result is written to `res`. `start` and `end` are
/// the starting and ending colors of the gradient.
///
/// If you don't know the length of the string and you would like to allocate
/// new string for the resulting gradient, consider using [`gradient`].
///
/// # Example
/// ```no_run
/// use termal_core::{codes, write_gradient};
///
/// let mut buf = codes::CLEAR.to_string();
///
/// let text = "gradient";
/// write_gradient(
///     &mut buf,
///     text,
///     text.len(),
///     (0xFD, 0xB9, 0x75),
///     (0x57, 0x9B, 0xDF)
/// );
///
/// println!("{buf}");
/// ```
///
/// ## Result in terminal
/// ![](https://raw.githubusercontent.com/BonnyAD9/termal/refs/heads/master/assets/write_gradient.png)
pub fn write_gradient(
    res: &mut String,
    s: impl AsRef<str>,
    s_len: usize,
    start: impl Into<Rgb>,
    end: impl Into<Rgb>,
) {
    let len = s_len as f32 - 1.;
    let start: Rgb<f32> = start.into().cast();
    let end: Rgb<f32> = end.into().cast();

    let step = if s_len == 1 {
        Rgb::<f32>::ZERO
    } else {
        (end - start) / len
    };

    for (i, c) in s.as_ref().chars().take(s_len).enumerate() {
        let col: Rgb<u8> = (start + step * i as f32).cast();
        res.push_str(&fg!(col.r(), col.g(), col.b()));
        res.push(c);
    }
}

/// Generates linear color gradient with the given text.
///
/// The gradient will be generated with characters in the string `s` and its
/// color will start with `start` and end with `end`.
///
/// If you want more granular control and possibly more efficient results in
/// some usecases, you can use [`write_gradient`].
///
/// # Example
/// ```no_run
/// use termal_core::{codes, gradient};
///
/// let mut buf = codes::CLEAR.to_string();
/// buf += &gradient("gradient", (0xFD, 0xB9, 0x75), (0x57, 0x9B, 0xDF));
/// println!("{buf}");
/// ```
///
/// ## Result in terminal
/// ![](https://raw.githubusercontent.com/BonnyAD9/termal/refs/heads/master/assets/write_gradient.png)
pub fn gradient(
    s: impl AsRef<str>,
    start: impl Into<Rgb>,
    end: impl Into<Rgb>,
) -> String {
    let mut res = String::new();
    let len = s.as_ref().chars().count();
    write_gradient(&mut res, s, len, start, end);
    res
}

/// Resets terminal modes. This should in most cases restore terminal to state
/// before your app started. Useful for example in case of panic.
///
/// The reset works on best-effort bases - it may not be fully reliable in all
/// cases, but it should work in most cases as long as you use this crate to
/// enable the terminal features.
///
/// What this doesn't do:
/// - Doesn't clear the screen and buffer.
/// - Doesn't move the cursor (it will be at the same position after this
///   call).
///
/// What this does:
/// - Disable raw mode (if feature `raw` is enabled on this crate).
/// - Reset text modes.
/// - Show cursor.
/// - Disable mouse tracking and extensions.
/// - Disable focus events.
/// - Reset scroll region.
/// - Disable alternative buffer.
/// - Disable reverse color mode.
/// - Disable bracketed paste.
/// - Reset colors to their defaults (codes, fg, bg, cursor).
///
/// Note that this function internally uses codes [`codes::CUR_SAVE`] and
/// [`codes::CUR_LOAD`] so the last saved position will not be preserved.
pub fn reset_terminal() {
    #[cfg(feature = "raw")]
    if raw::is_raw_mode_enabled() {
        _ = raw::disable_raw_mode();
    }
    let s = [
        codes::RESET,
        codes::ENABLE_LINE_WRAP,
        codes::SHOW_CURSOR,
        codes::DISABLE_MOUSE_XY_UTF8_EXT,
        codes::DISABLE_MOUSE_XY_EXT,
        codes::DISABLE_MOUSE_XY_URXVT_EXT,
        codes::DISABLE_MOUSE_XY_PIX_EXT,
        codes::DISABLE_MOUSE_XY_TRACKING,
        codes::DISABLE_MOUSE_XY_PR_TRACKING,
        codes::DISABLE_MOUSE_XY_DRAG_TRACKING,
        codes::DISABLE_MOUSE_XY_ALL_TRACKING,
        codes::DISABLE_FOCUS_EVENT,
        codes::CUR_SAVE,
        codes::RESET_SCROLL_REGION,
        codes::CUR_LOAD,
        codes::DISABLE_ALTERNATIVE_BUFFER,
        codes::DISABLE_REVERSE_COLOR,
        codes::DISABLE_BRACKETED_PASTE_MODE,
        codes::RESET_ALL_COLOR_CODES,
        codes::RESET_DEFAULT_FG_COLOR,
        codes::RESET_DEFAULT_BG_COLOR,
        codes::RESET_CURSOR_COLOR,
    ]
    .concat();
    print!("{s}");
    _ = io::stdout().flush();
}

/// Registers panic hook that will prepend terminal reset before the current
/// panic hook. Useful for tui apps.
///
/// This will make sure that the terminal is set to reasonable state even when
/// your app panics.
///
/// It will use the function [`reset_terminal`]. See that for more info.
pub fn register_reset_on_panic() {
    let hook = panic::take_hook();
    panic::set_hook(Box::new(move |pci| {
        reset_terminal();
        hook(pci)
    }));
}