Lanterna

Swingライクなコンポーネントベースアプローチを採用した純粋Java TUIライブラリ。ネイティブ依存なしで、開発環境でのSwingターミナルエミュレータを提供。

TUITerminalJavaPure-JavaComponent-based

GitHub概要

mabe02/lanterna

Java library for creating text-based GUIs

スター2,442
ウォッチ65
フォーク260
作成日:2015年6月29日
言語:Java
ライセンス:GNU Lesser General Public License v3.0

トピックス

なし

スター履歴

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

Lanterna

Lanternaは、テキスト環境でセミグラフィカルなユーザーインターフェースを作成するためのJavaライブラリです。C言語のcursesライブラリに似ていますが、より多くの機能を提供します。100%純粋Javaで実装されており、ネイティブライブラリに依存しません。

特徴

3層アーキテクチャ

  • Terminal層: 最も低レベルの抽象化層
  • Screen層: ダブルバッファリングを提供する中間層
  • GUI層: 完全なGUIツールキットを提供する最上位層

プラットフォームサポート

  • 純粋Java: ネイティブライブラリ依存なし
  • Swingエミュレータ: GUI環境での開発用ターミナル
  • クロスプラットフォーム: Linux、Windows、macOS対応
  • 自動検出: 最適なターミナル実装を自動選択

GUIコンポーネント

  • ウィンドウシステム: モーダル/マルチウィンドウ対応
  • 基本コンポーネント: Button、Label、TextBox、ComboBox
  • レイアウトマネージャー: LinearLayout、GridLayout
  • ダイアログ: MessageDialog、FileDialog

入出力機能

  • キーボード入力: 特殊キー、修飾キー対応
  • マウスサポート: クリック、ドラッグイベント
  • カラーサポート: 256色、RGBカラー対応
  • Unicodeサポート: 完全なUTF-8サポート

基本的な使用方法

インストール

<!-- Maven -->
<dependency>
    <groupId>com.googlecode.lanterna</groupId>
    <artifactId>lanterna</artifactId>
    <version>3.1.1</version>
</dependency>
// Gradle
implementation 'com.googlecode.lanterna:lanterna:3.1.1'

Hello World(Terminal層)

import com.googlecode.lanterna.terminal.*;
import com.googlecode.lanterna.TerminalSize;
import java.io.IOException;

public class HelloWorldTerminal {
    public static void main(String[] args) throws IOException {
        // ターミナルの作成
        Terminal terminal = new DefaultTerminalFactory().createTerminal();
        
        // プライベートモードに入る
        terminal.enterPrivateMode();
        terminal.clearScreen();
        
        // カーソルを移動してテキストを出力
        terminal.setCursorPosition(10, 5);
        terminal.putCharacter('H');
        terminal.putCharacter('e');
        terminal.putCharacter('l');
        terminal.putCharacter('l');
        terminal.putCharacter('o');
        terminal.putCharacter(',');
        terminal.putCharacter(' ');
        terminal.putCharacter('W');
        terminal.putCharacter('o');
        terminal.putCharacter('r');
        terminal.putCharacter('l');
        terminal.putCharacter('d');
        terminal.putCharacter('!');
        
        terminal.flush();
        
        // キー入力を待つ
        terminal.readInput();
        
        // プライベートモードを終了
        terminal.exitPrivateMode();
        terminal.close();
    }
}

Screen層の使用

import com.googlecode.lanterna.screen.*;
import com.googlecode.lanterna.terminal.*;
import com.googlecode.lanterna.graphics.*;
import com.googlecode.lanterna.TextColor;
import java.io.IOException;

public class ScreenExample {
    public static void main(String[] args) throws IOException {
        // ターミナルとスクリーンの作成
        Terminal terminal = new DefaultTerminalFactory().createTerminal();
        Screen screen = new TerminalScreen(terminal);
        
        screen.startScreen();
        screen.clear();
        
        // TextGraphicsを使用した描画
        TextGraphics textGraphics = screen.newTextGraphics();
        textGraphics.setForegroundColor(TextColor.ANSI.YELLOW);
        textGraphics.setBackgroundColor(TextColor.ANSI.BLUE);
        
        // テキストの描画
        textGraphics.putString(5, 3, "Lanterna Screen Example");
        
        // ボックスの描画
        textGraphics.drawRectangle(
            new TerminalPosition(3, 2),
            new TerminalSize(25, 5),
            '█'
        );
        
        // 画面を更新
        screen.refresh();
        
        // キー入力を待つ
        screen.readInput();
        
        screen.stopScreen();
        terminal.close();
    }
}

GUI層の使用

import com.googlecode.lanterna.gui2.*;
import com.googlecode.lanterna.screen.*;
import com.googlecode.lanterna.terminal.*;
import java.io.IOException;
import java.util.Arrays;

public class GUIExample {
    public static void main(String[] args) throws IOException {
        // ターミナルとGUIの初期化
        Terminal terminal = new DefaultTerminalFactory().createTerminal();
        Screen screen = new TerminalScreen(terminal);
        screen.startScreen();
        
        // GUIの作成
        MultiWindowTextGUI gui = new MultiWindowTextGUI(screen);
        
        // ウィンドウの作成
        BasicWindow window = new BasicWindow("Lanterna GUI Example");
        window.setHints(Arrays.asList(Window.Hint.CENTERED));
        
        // パネルの作成
        Panel panel = new Panel();
        panel.setLayoutManager(new GridLayout(2));
        
        // コンポーネントの追加
        panel.addComponent(new Label("名前:"));
        TextBox nameBox = new TextBox();
        panel.addComponent(nameBox);
        
        panel.addComponent(new Label("年齢:"));
        TextBox ageBox = new TextBox().setValidationPattern("[0-9]*");
        panel.addComponent(ageBox);
        
        panel.addComponent(new EmptySpace());
        Button submitButton = new Button("Submit", new Runnable() {
            @Override
            public void run() {
                MessageDialog.showMessageDialog(
                    gui,
                    "入力内容",
                    "名前: " + nameBox.getText() + "\n" +
                    "年齢: " + ageBox.getText()
                );
            }
        });
        panel.addComponent(submitButton);
        
        // ウィンドウにパネルを設定
        window.setComponent(panel);
        
        // GUIを表示
        gui.addWindowAndWait(window);
        
        // クリーンアップ
        screen.stopScreen();
        terminal.close();
    }
}

カスタムコンポーネント

import com.googlecode.lanterna.gui2.*;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.TextColor;

public class ProgressBar extends AbstractComponent<ProgressBar> {
    private double progress = 0.0;
    private String label = "";
    
    public ProgressBar() {
        setPreferredSize(new TerminalSize(20, 1));
    }
    
    public void setProgress(double progress) {
        this.progress = Math.max(0.0, Math.min(1.0, progress));
        invalidate();
    }
    
    public void setLabel(String label) {
        this.label = label;
        invalidate();
    }
    
    @Override
    protected ComponentRenderer<ProgressBar> createDefaultRenderer() {
        return new ComponentRenderer<ProgressBar>() {
            @Override
            public TerminalSize getPreferredSize(ProgressBar component) {
                return new TerminalSize(20, 1);
            }
            
            @Override
            public void drawComponent(TextGUIGraphics graphics, ProgressBar component) {
                TerminalSize size = graphics.getSize();
                int width = size.getColumns();
                int filledWidth = (int)(width * component.progress);
                
                // 進捗バーの描画
                graphics.setForegroundColor(TextColor.ANSI.GREEN);
                for (int i = 0; i < filledWidth; i++) {
                    graphics.setCharacter(i, 0, '█');
                }
                
                graphics.setForegroundColor(TextColor.ANSI.WHITE);
                for (int i = filledWidth; i < width; i++) {
                    graphics.setCharacter(i, 0, '░');
                }
                
                // ラベルの描画
                if (!component.label.isEmpty()) {
                    String text = component.label + " " + 
                                 (int)(component.progress * 100) + "%";
                    int textX = (width - text.length()) / 2;
                    graphics.putString(textX, 0, text);
                }
            }
        };
    }
}

テーマのカスタマイズ

import com.googlecode.lanterna.gui2.*;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.graphics.*;

public class CustomTheme extends SimpleTheme {
    public CustomTheme() {
        // ウィンドウのスタイル
        setWindowBackgroundColor(TextColor.ANSI.BLUE);
        setWindowForegroundColor(TextColor.ANSI.WHITE);
        
        // ボタンのスタイル
        addOverride(Button.class, new ComponentRenderer<Button>() {
            @Override
            public TerminalSize getPreferredSize(Button component) {
                return new TerminalSize(
                    component.getLabel().length() + 4, 3
                );
            }
            
            @Override
            public void drawComponent(TextGUIGraphics graphics, Button button) {
                ThemeDefinition themeDefinition = button.getThemeDefinition();
                
                if (button.isFocused()) {
                    graphics.applyThemeStyle(themeDefinition.getActive());
                } else {
                    graphics.applyThemeStyle(themeDefinition.getNormal());
                }
                
                // ボタンの枠を描画
                graphics.drawRectangle(
                    TerminalPosition.TOP_LEFT_CORNER,
                    graphics.getSize(),
                    '█'
                );
                
                // ラベルを中央に描画
                graphics.putString(
                    2, 1,
                    button.getLabel()
                );
            }
        });
    }
}

高度な機能

マルチウィンドウアプリケーション

// 非モーダルウィンドウの作成
BasicWindow window1 = new BasicWindow("Window 1");
window1.setHints(Arrays.asList(
    Window.Hint.NO_DECORATIONS,
    Window.Hint.FIXED_POSITION
));
window1.setPosition(new TerminalPosition(5, 3));

BasicWindow window2 = new BasicWindow("Window 2");
window2.setHints(Arrays.asList(
    Window.Hint.NO_DECORATIONS,
    Window.Hint.FIXED_POSITION  
));
window2.setPosition(new TerminalPosition(25, 8));

// 両方のウィンドウを追加
gui.addWindow(window1);
gui.addWindow(window2);

非同期入力処理

// 非ブロッキング入力
KeyStroke keyStroke = screen.pollInput();
if (keyStroke != null) {
    if (keyStroke.getKeyType() == KeyType.Escape) {
        // ESCキーが押された
    }
}

アニメーション

public class AnimatedLabel extends Label {
    private String fullText;
    private int currentIndex = 0;
    
    public AnimatedLabel(String text) {
        super("");
        this.fullText = text;
        
        // アニメーションタイマー
        new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                if (currentIndex <= fullText.length()) {
                    setText(fullText.substring(0, currentIndex));
                    currentIndex++;
                } else {
                    currentIndex = 0;
                }
            }
        }, 0, 100);
    }
}

エコシステム

サポートされるターミナル

  • Linux: xterm、gnome-terminal、konsole
  • Windows: cmd.exe、PowerShell、Windows Terminal
  • macOS: Terminal.app、iTerm2
  • エミュレータ: PuTTY、MinTTY

関連プロジェクト

  • CHARVA: Lanternaをバックエンドとして使用
  • ゲーム開発: ローグライクゲームなどで利用
  • ツール開発: TUIベースの開発ツール

利点

  • 純粋Java: ネイティブ依存なしでポータビリティが高い
  • 3層アーキテクチャ: 柔軟な抽象化レベル
  • Swingライク: Java開発者に親しみやすいAPI
  • 開発環境: GUI環境でのデバッグが容易
  • アクティブ開発: 継続的なメンテナンス

制約事項

  • パフォーマンス: ネイティブ実装より遅い場合がある
  • 機能限定: 高度なグラフィックス機能は限定的
  • 学習曲線: 3層アーキテクチャの理解が必要
  • ドキュメント: 日本語資料が少ない

他のライブラリとの比較

項目LanternaJexerCHARVA
純粋Java×(JNI使用)
APIスタイルSwingライクTurbo VisionライクSwing互換
機能中程度高度基本的
開発の活発度
学習コスト

まとめ

Lanternaは、JavaでTUIアプリケーションを開発するための優れたライブラリです。純粋Java実装による高いポータビリティ、3層アーキテクチャによる柔軟性、SwingライクなAPIによる学習のしやすさが特徴です。特に、GUI環境での開発からヘッドレスサーバーへのデプロイまで、同じコードで対応できる点は大きな利点です。