Notcurses

高度なグラフィックスとマルチメディアサポートを備えた強力なTUIライブラリ。24ビットRGBカラー、画像・動画の表示、レイヤー機能などを提供。

TUITerminalGraphicsMultimediaC++

GitHub概要

dankamongmen/notcurses

blingful character graphics/TUI library. definitely not curses.

スター4,009
ウォッチ33
フォーク127
作成日:2019年11月17日
言語:C
ライセンス:Other

トピックス

cclincursesterminalterminal-emulators

スター履歴

dankamongmen/notcurses Star History
データ取得日時: 2025/7/25 11:08

Notcurses

Notcursesは、モダンなターミナル機能を活用した強力なTUIライブラリです、24ビットRGBカラー、グラフィックス、動画再生、レイヤー機能など、従来のNCursesを大幅に拡張した機能を提供します。Cで書かれたAPIに加え、C++ラッパーも提供されています。

特徴

グラフィックス・マルチメディア

  • 24ビットRGB: True Color対応
  • 画像表示: PNG、JPEGなどの表示
  • 動画再生: ビデオファイルの再生
  • ブレイル文字: 高解像度グラフィックス

高度なレンダリング

  • プレーン: レイヤー化された描画面
  • Zオーダー: 深度管理
  • アルファブレンディング: 透明度サポート
  • セルスタイル: 細かい制御

パフォーマンス

  • 最適化: 高速レンダリング
  • 非同期入力: ノンブロッキングI/O
  • マルチスレッド: スレッドセーフ
  • 差分更新: 効率的な画面更新

ポータビリティ

  • Linux/Unix: 完全サポート
  • Windows: Windows Terminal対応
  • macOS: ネイティブサポート
  • terminfo: ターミナル自動検出

基本的な使用方法

インストール

# Ubuntu/Debian
sudo apt-get install libnotcurses-dev

# macOS
brew install notcurses

# ソースからビルド
mkdir build && cd build
cmake ..
make -j
sudo make install

# コンパイル
g++ -o myapp myapp.cpp -lnotcurses-core -lnotcurses

Hello World (C++)

#include <notcurses/notcurses.h>
#include <iostream>

int main() {
    // オプション設定
    notcurses_options opts = {};
    opts.flags = NCOPTION_SUPPRESS_BANNERS;
    
    // Notcursesの初期化
    notcurses* nc = notcurses_core_init(&opts, nullptr);
    if (!nc) {
        return 1;
    }
    
    // 標準プレーンを取得
    ncplane* stdplane = notcurses_stdplane(nc);
    
    // テキストを表示
    ncplane_putstr_yx(stdplane, 2, 2, "Hello, Notcurses!");
    
    // RGBカラーでテキストを表示
    ncplane_set_fg_rgb8(stdplane, 255, 100, 100);
    ncplane_putstr_yx(stdplane, 4, 2, "RGB Colored Text");
    
    // レンダリング
    notcurses_render(nc);
    
    // キー入力待ち
    notcurses_getc_blocking(nc, nullptr);
    
    // クリーンアップ
    notcurses_stop(nc);
    
    return 0;
}

プレーンとグラフィックス

#include <notcurses/notcurses.h>
#include <cmath>

int main() {
    notcurses_options opts = {};
    notcurses* nc = notcurses_core_init(&opts, nullptr);
    ncplane* stdplane = notcurses_stdplane(nc);
    
    // プレーンのサイズを取得
    unsigned y, x;
    ncplane_dim_yx(stdplane, &y, &x);
    
    // 新しいプレーンを作成
    ncplane_options nopts = {};
    nopts.y = 5;
    nopts.x = 10;
    nopts.rows = 20;
    nopts.cols = 40;
    
    ncplane* plane = ncplane_create(stdplane, &nopts);
    
    // ボーダーを描画
    uint64_t channels = 0;
    ncchannel_set_rgb8(&channels, 0, 255, 0);
    ncplane_perimeter(plane, &ncplane_box_chars, 0, channels, 
                      NCBOXMASK_TOP | NCBOXMASK_RIGHT | 
                      NCBOXMASK_BOTTOM | NCBOXMASK_LEFT);
    
    // グラデーション描画
    for (int i = 0; i < 18; i++) {
        for (int j = 0; j < 38; j++) {
            uint32_t r = (i * 255) / 18;
            uint32_t g = (j * 255) / 38;
            uint32_t b = 128;
            
            ncplane_set_fg_rgb8(plane, r, g, b);
            ncplane_set_bg_rgb8(plane, 0, 0, 0);
            ncplane_putchar_yx(plane, i + 1, j + 1, '█');
        }
    }
    
    notcurses_render(nc);
    notcurses_getc_blocking(nc, nullptr);
    
    ncplane_destroy(plane);
    notcurses_stop(nc);
    
    return 0;
}

画像表示

#include <notcurses/notcurses.h>
#include <notcurses/direct.h>

void display_image(notcurses* nc, const char* filename) {
    ncplane* stdplane = notcurses_stdplane(nc);
    
    // ビジュアルを作成
    ncvisual_options vopts = {};
    vopts.n = stdplane;
    vopts.y = 2;
    vopts.x = 2;
    vopts.scaling = NCSCALE_STRETCH;
    vopts.blitter = NCBLIT_PIXEL;  // ピクセルブリッター
    
    // 画像を読み込み
    ncvisual* ncv = ncvisual_from_file(filename);
    if (!ncv) {
        return;
    }
    
    // 画像をレンダリング
    ncplane* ncp = ncvisual_blit(nc, ncv, &vopts);
    
    notcurses_render(nc);
    
    // クリーンアップ
    ncvisual_destroy(ncv);
}

int main() {
    notcurses_options opts = {};
    opts.flags = NCOPTION_NO_ALTERNATE_SCREEN;
    
    notcurses* nc = notcurses_core_init(&opts, nullptr);
    
    // 画像を表示
    display_image(nc, "image.png");
    
    notcurses_getc_blocking(nc, nullptr);
    notcurses_stop(nc);
    
    return 0;
}

アニメーション

#include <notcurses/notcurses.h>
#include <thread>
#include <chrono>

class Animation {
private:
    notcurses* nc;
    ncplane* plane;
    int frame;
    
public:
    Animation(notcurses* nc) : nc(nc), frame(0) {
        ncplane_options nopts = {};
        nopts.y = 5;
        nopts.x = 10;
        nopts.rows = 10;
        nopts.cols = 30;
        
        plane = ncplane_create(notcurses_stdplane(nc), &nopts);
    }
    
    ~Animation() {
        ncplane_destroy(plane);
    }
    
    void update() {
        ncplane_erase(plane);
        
        // フレームに基づいてアニメーション
        int offset = frame % 20;
        
        // 移動するボール
        ncplane_set_fg_rgb8(plane, 255, 255, 0);
        ncplane_putchar_yx(plane, 5, offset, '●');
        
        // トレイル効果
        for (int i = 1; i < 5 && offset - i >= 0; i++) {
            int alpha = 255 - (i * 50);
            ncplane_set_fg_rgb8(plane, alpha, alpha, 0);
            ncplane_putchar_yx(plane, 5, offset - i, '·');
        }
        
        frame++;
    }
    
    void render() {
        notcurses_render(nc);
    }
};

int main() {
    notcurses_options opts = {};
    notcurses* nc = notcurses_core_init(&opts, nullptr);
    
    Animation anim(nc);
    
    // アニメーションループ
    for (int i = 0; i < 100; i++) {
        anim.update();
        anim.render();
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
    
    notcurses_stop(nc);
    
    return 0;
}

ウィジェットと入力

#include <notcurses/notcurses.h>
#include <string>

class InputField {
private:
    ncplane* plane;
    std::string text;
    size_t cursor_pos;
    
public:
    InputField(ncplane* parent, int y, int x, int width) 
        : cursor_pos(0) {
        ncplane_options nopts = {};
        nopts.y = y;
        nopts.x = x;
        nopts.rows = 3;
        nopts.cols = width;
        
        plane = ncplane_create(parent, &nopts);
    }
    
    ~InputField() {
        ncplane_destroy(plane);
    }
    
    void draw() {
        ncplane_erase(plane);
        
        // ボーダー描画
        uint64_t channels = 0;
        ncchannel_set_rgb8(&channels, 100, 100, 255);
        ncplane_perimeter(plane, &ncplane_box_chars, 0, channels, 
                         NCBOXMASK_TOP | NCBOXMASK_RIGHT | 
                         NCBOXMASK_BOTTOM | NCBOXMASK_LEFT);
        
        // テキスト表示
        ncplane_putstr_yx(plane, 1, 1, text.c_str());
        
        // カーソル
        if (cursor_pos <= text.length()) {
            ncplane_set_bg_rgb8(plane, 255, 255, 255);
            ncplane_set_fg_rgb8(plane, 0, 0, 0);
            char ch = (cursor_pos < text.length()) ? text[cursor_pos] : ' ';
            ncplane_putchar_yx(plane, 1, 1 + cursor_pos, ch);
        }
    }
    
    void handle_input(const ncinput* ni) {
        if (ni->id == 'a' && ni->id <= 'z') {
            // 文字入力
            text.insert(cursor_pos, 1, ni->id);
            cursor_pos++;
        } else if (ni->id == NCKEY_BACKSPACE) {
            // バックスペース
            if (cursor_pos > 0) {
                text.erase(cursor_pos - 1, 1);
                cursor_pos--;
            }
        } else if (ni->id == NCKEY_LEFT) {
            // 左移動
            if (cursor_pos > 0) {
                cursor_pos--;
            }
        } else if (ni->id == NCKEY_RIGHT) {
            // 右移動
            if (cursor_pos < text.length()) {
                cursor_pos++;
            }
        }
    }
    
    const std::string& get_text() const {
        return text;
    }
};

int main() {
    notcurses_options opts = {};
    notcurses* nc = notcurses_core_init(&opts, nullptr);
    ncplane* stdplane = notcurses_stdplane(nc);
    
    ncplane_putstr_yx(stdplane, 2, 2, "Input Field Demo (ESC to exit)");
    
    InputField input(stdplane, 5, 5, 30);
    
    ncinput ni;
    while (true) {
        input.draw();
        notcurses_render(nc);
        
        notcurses_getc_blocking(nc, &ni);
        
        if (ni.id == NCKEY_ESC) {
            break;
        }
        
        input.handle_input(&ni);
    }
    
    notcurses_stop(nc);
    
    printf("入力: %s\n", input.get_text().c_str());
    
    return 0;
}

高度な機能

マルチメディア再生

// 動画ファイルの再生
ncvisual* ncv = ncvisual_from_file("video.mp4");

// ループでフレームを表示
while (ncvisual_decode(ncv) == 0) {
    ncvisual_blit(nc, ncv, &vopts);
    notcurses_render(nc);
}

レイヤーとアルファブレンディング

// 透明度を持つプレーン
ncplane_set_fg_alpha(plane, NCALPHA_BLEND);
ncplane_set_bg_alpha(plane, NCALPHA_TRANSPARENT);

パフォーマンス統計

// 統計情報を取得
ncstats stats;
notcurses_stats_alloc(nc, &stats);
// レンダリング後
notcurses_stats(nc, &stats);
printf("Renders: %ju, Failed renders: %ju\n", 
       stats.renders, stats.failed_renders);

エコシステム

関連ツール

  • ncls: lsコマンドの拡張版
  • ncplayer: メディアプレイヤー
  • ncneofetch: システム情報表示
  • notcurses-demo: 機能デモ

言語バインディング

  • Python: notcurses-python
  • Rust: notcurses-rs
  • Go: notcurses-go
  • Zig: notcurses-zig

利点

  • 最新機能: True Color、マルチメディア対応
  • 高パフォーマンス: 最適化されたレンダリング
  • 豊富なAPI: 幅広い機能セット
  • アクティブ開発: 継続的な改善
  • クロスプラットフォーム: 良好な互換性

制約事項

  • 新しい: NCursesと比べて歴史が浅い
  • 複雑性: APIが大規模で学習コストが高い
  • 依存関係: マルチメディア機能には追加ライブラリが必要
  • ターミナル要件: 最新機能にはモダンなターミナルが必要

他のライブラリとの比較

項目NotcursesncursesFTXUI
レベル中-低
機能非常に豊富基本的豊富
マルチメディア完全サポートなしなし
True Colorネイティブ限定的サポート
学習コスト

まとめ

Notcursesは、モダンなターミナルの機能を最大限に活用したTUIライブラリです。True Color、マルチメディア、レイヤー機能など、従来のNCursesでは不可能だった機能を提供します。学習コストは高いですが、リッチなターミナルアプリケーションを開発したい開発者にとっては、非常に強力な選択肢となるでしょう。