Notcurses

TUITerminalGraphicsMultimediaHigh-performance

GitHub Overview

dankamongmen/notcurses

blingful character graphics/TUI library. definitely not curses.

Stars4,009
Watchers33
Forks127
Created:November 17, 2019
Language:C
License:Other

Topics

cclincursesterminalterminal-emulators

Star History

dankamongmen/notcurses Star History
Data as of: 7/25/2025, 11:08 AM

Notcurses

Notcurses is a high-performance TUI library that maximizes the use of modern terminal features. It supports graphics, video playback, and multimedia content display, providing expressive capabilities beyond traditional TUI library limitations. It's available as Rust bindings to the C library.

Features

Advanced Graphics Capabilities

  • Pixel Manipulation: True pixel-level drawing
  • Image Display: Support for JPEG, PNG, GIF, WebP, and other formats
  • Video Playback: Video playback within terminals
  • Transparency: Alpha channel support
  • Blending: Advanced color mixing

Performance Optimization

  • Double Buffering: Flicker-free rendering
  • Differential Rendering: Only redraw changed portions
  • Parallel Processing: Utilize multi-core CPUs
  • Memory Efficiency: Optimized memory usage
  • Hardware Acceleration: GPU utilization when available

Rich Feature Set

  • Full Unicode Support: Emojis, combining characters, RTL languages
  • 24-bit Color: True color support
  • Mouse Support: High-precision mouse tracking
  • Sound: Audio playback capabilities
  • Statistics: Rendering statistics and debug information

Terminal Compatibility

  • Wide Support: Compatible with major terminal emulators
  • Feature Detection: Automatic terminal capability detection
  • Graceful Degradation: Fallback for unavailable features
  • Sixel/Kitty: Graphics protocol support

Basic Usage

Installation

[dependencies]
notcurses = "3.0"

Basic Initialization and Rendering

use notcurses::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize Notcurses
    let mut nc = Notcurses::new()?;
    
    // Get the standard plane
    let mut stdplane = nc.stdplane();
    
    // Draw text
    stdplane.putstr("Hello, Notcurses!")?;
    
    // Set colors and draw
    stdplane.set_fg_rgb(0xFF0000)?; // Red
    stdplane.set_bg_rgb(0x000000)?; // Black
    stdplane.putstr_yx(2, 0, "Colored text")?;
    
    // Render
    nc.render()?;
    
    // Wait for key input
    nc.get_blocking(None)?;
    
    Ok(())
}

Plane Manipulation

use notcurses::*;

fn create_window(nc: &mut Notcurses) -> Result<NcPlane, NcError> {
    let opts = NcPlaneOptions {
        y: 5,
        x: 10,
        rows: 10,
        cols: 40,
        userptr: std::ptr::null_mut(),
        name: Some("mywindow".to_string()),
        resizecb: None,
        flags: 0,
        margin_b: 0,
        margin_r: 0,
    };
    
    // Create new plane
    let mut plane = NcPlane::new(nc, &opts)?;
    
    // Draw border
    plane.perimeter_rounded(0, 0, 0)?;
    
    // Add title
    plane.putstr_yx(0, 2, " Window Title ")?;
    
    // Add content
    plane.putstr_yx(2, 2, "This is a window!")?;
    
    Ok(plane)
}

Image Display

use notcurses::*;

fn display_image(
    nc: &mut Notcurses,
    path: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut stdplane = nc.stdplane();
    
    // Create visual
    let mut visual = NcVisual::from_file(path)?;
    
    // Scaling options
    let vopts = NcVisualOptions {
        n: Some(&mut stdplane),
        scaling: NcScale::Scale,
        y: 0,
        x: 0,
        begy: 0,
        begx: 0,
        leny: 0,
        lenx: 0,
        blitter: NcBlitter::Default,
        flags: 0,
        transcolor: 0,
        pxoffy: 0,
        pxoffx: 0,
    };
    
    // Render image
    visual.blit(nc, &vopts)?;
    nc.render()?;
    
    Ok(())
}

Animation

use notcurses::*;
use std::time::Duration;
use std::thread;

fn animate_text(nc: &mut Notcurses) -> Result<(), NcError> {
    let mut plane = nc.stdplane();
    let text = "Animated Text";
    let max_x = plane.dim_x() as i32 - text.len() as i32;
    
    for x in 0..max_x {
        plane.erase();
        
        // Rainbow gradient
        let hue = (x as f32 / max_x as f32) * 360.0;
        let (r, g, b) = hsl_to_rgb(hue, 1.0, 0.5);
        plane.set_fg_rgb8(r, g, b)?;
        
        plane.putstr_yx(5, x, text)?;
        nc.render()?;
        
        thread::sleep(Duration::from_millis(50));
    }
    
    Ok(())
}

fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) {
    let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
    let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
    let m = l - c / 2.0;
    
    let (r, g, b) = match (h / 60.0) as u8 {
        0 => (c, x, 0.0),
        1 => (x, c, 0.0),
        2 => (0.0, c, x),
        3 => (0.0, x, c),
        4 => (x, 0.0, c),
        _ => (c, 0.0, x),
    };
    
    (
        ((r + m) * 255.0) as u8,
        ((g + m) * 255.0) as u8,
        ((b + m) * 255.0) as u8,
    )
}

Advanced Features

Menu System

use notcurses::*;

fn create_menu(nc: &mut Notcurses) -> Result<NcMenu, NcError> {
    let sections = vec![
        NcMenuSection {
            name: "File".to_string(),
            items: vec![
                NcMenuItem {
                    desc: "New".to_string(),
                    shortcut: Some("Ctrl+N".to_string()),
                },
                NcMenuItem {
                    desc: "Open".to_string(),
                    shortcut: Some("Ctrl+O".to_string()),
                },
                NcMenuItem {
                    desc: "Save".to_string(),
                    shortcut: Some("Ctrl+S".to_string()),
                },
            ],
        },
        NcMenuSection {
            name: "Edit".to_string(),
            items: vec![
                NcMenuItem {
                    desc: "Copy".to_string(),
                    shortcut: Some("Ctrl+C".to_string()),
                },
                NcMenuItem {
                    desc: "Paste".to_string(),
                    shortcut: Some("Ctrl+V".to_string()),
                },
            ],
        },
    ];
    
    let opts = NcMenuOptions {
        sections,
        ..Default::default()
    };
    
    NcMenu::new(&mut nc.stdplane(), &opts)
}

Graphs and Charts

use notcurses::*;

fn draw_chart(
    plane: &mut NcPlane,
    data: &[f64],
) -> Result<(), NcError> {
    let height = plane.dim_y() as usize;
    let width = plane.dim_x() as usize;
    
    // Normalize data
    let max_val = data.iter().cloned().fold(f64::MIN, f64::max);
    let min_val = data.iter().cloned().fold(f64::MAX, f64::min);
    let range = max_val - min_val;
    
    // Draw graph
    for (i, &value) in data.iter().enumerate() {
        let x = (i * width) / data.len();
        let normalized = (value - min_val) / range;
        let y = height - (normalized * height as f64) as usize - 1;
        
        // Draw bars
        for bar_y in y..height {
            plane.putchar_yx(bar_y as i32, x as i32, '█')?;
        }
    }
    
    Ok(())
}

Ecosystem

Related Projects

  • notcurses-rs: Official Rust bindings
  • ncplayer: Media player
  • ncneofetch: System information display tool
  • nctetris: Tetris game implementation

Tools and Utilities

  • notcurses-demo: Feature demonstration
  • ncls: Enhanced ls command
  • ncplayer: Terminal media player

Advantages

  • Cutting-edge Features: Full utilization of modern terminal capabilities
  • High Performance: Optimized rendering engine
  • Rich Expression: Support for images, videos, and graphics
  • Professional Quality: Commercial-grade library
  • Active Development: Continuous feature additions and improvements

Limitations

  • Complexity: Steep learning curve due to rich features
  • Dependencies: Depends on C language library
  • Terminal Requirements: Latest features require compatible terminals
  • Resource Usage: High memory and CPU usage
  • Portability: Some features are platform-specific

Comparison with Other Libraries

FeatureNotcursesRatatuiCursive
GraphicsFull SupportText OnlyText Only
PerformanceVery HighHighHigh
Learning CurveVery HighMediumLow-Medium
Feature RichnessHighestMediumMedium
PortabilityMediumHighHigh

Summary

Notcurses is a revolutionary library that maximizes the potential of terminal applications. It enables image display, video playback, and advanced graphics expressions that were impossible with traditional TUI libraries. While the learning curve is steep, it's the optimal choice for developers who want to create professional terminal applications or visually impressive TUIs.