Lanterna
A pure Java TUI library with a Swing-like component-based approach. No native dependencies, includes a Swing terminal emulator for development.
GitHub Overview
mabe02/lanterna
Java library for creating text-based GUIs
Topics
Star History
Lanterna
Lanterna is a Java library for creating semi-graphical user interfaces in text-only environments, similar to the C library curses but with more functionality. It's implemented in 100% pure Java with no native library dependencies.
Features
Three-Layer Architecture
- Terminal Layer: Lowest abstraction layer
- Screen Layer: Middle layer providing double-buffering
- GUI Layer: Top layer providing complete GUI toolkit
Platform Support
- Pure Java: No native library dependencies
- Swing Emulator: Terminal emulator for GUI environments
- Cross-Platform: Linux, Windows, macOS support
- Auto-Detection: Automatic selection of optimal terminal implementation
GUI Components
- Window System: Modal/multi-window support
- Basic Components: Button, Label, TextBox, ComboBox
- Layout Managers: LinearLayout, GridLayout
- Dialogs: MessageDialog, FileDialog
Input/Output Features
- Keyboard Input: Special keys, modifier keys support
- Mouse Support: Click and drag events
- Color Support: 256 colors, RGB color support
- Unicode Support: Full UTF-8 support
Basic Usage
Installation
<!-- 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 Layer)
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 {
// Create terminal
Terminal terminal = new DefaultTerminalFactory().createTerminal();
// Enter private mode
terminal.enterPrivateMode();
terminal.clearScreen();
// Move cursor and output text
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();
// Wait for key input
terminal.readInput();
// Exit private mode
terminal.exitPrivateMode();
terminal.close();
}
}
Using Screen Layer
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 {
// Create terminal and screen
Terminal terminal = new DefaultTerminalFactory().createTerminal();
Screen screen = new TerminalScreen(terminal);
screen.startScreen();
screen.clear();
// Drawing with TextGraphics
TextGraphics textGraphics = screen.newTextGraphics();
textGraphics.setForegroundColor(TextColor.ANSI.YELLOW);
textGraphics.setBackgroundColor(TextColor.ANSI.BLUE);
// Draw text
textGraphics.putString(5, 3, "Lanterna Screen Example");
// Draw rectangle
textGraphics.drawRectangle(
new TerminalPosition(3, 2),
new TerminalSize(25, 5),
'█'
);
// Refresh screen
screen.refresh();
// Wait for key input
screen.readInput();
screen.stopScreen();
terminal.close();
}
}
Using GUI Layer
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 {
// Initialize terminal and GUI
Terminal terminal = new DefaultTerminalFactory().createTerminal();
Screen screen = new TerminalScreen(terminal);
screen.startScreen();
// Create GUI
MultiWindowTextGUI gui = new MultiWindowTextGUI(screen);
// Create window
BasicWindow window = new BasicWindow("Lanterna GUI Example");
window.setHints(Arrays.asList(Window.Hint.CENTERED));
// Create panel
Panel panel = new Panel();
panel.setLayoutManager(new GridLayout(2));
// Add components
panel.addComponent(new Label("Name:"));
TextBox nameBox = new TextBox();
panel.addComponent(nameBox);
panel.addComponent(new Label("Age:"));
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,
"Input",
"Name: " + nameBox.getText() + "\n" +
"Age: " + ageBox.getText()
);
}
});
panel.addComponent(submitButton);
// Set panel to window
window.setComponent(panel);
// Show GUI
gui.addWindowAndWait(window);
// Cleanup
screen.stopScreen();
terminal.close();
}
}
Custom Component
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);
// Draw progress bar
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, '░');
}
// Draw label
if (!component.label.isEmpty()) {
String text = component.label + " " +
(int)(component.progress * 100) + "%";
int textX = (width - text.length()) / 2;
graphics.putString(textX, 0, text);
}
}
};
}
}
Theme Customization
import com.googlecode.lanterna.gui2.*;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.graphics.*;
public class CustomTheme extends SimpleTheme {
public CustomTheme() {
// Window style
setWindowBackgroundColor(TextColor.ANSI.BLUE);
setWindowForegroundColor(TextColor.ANSI.WHITE);
// Button style
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());
}
// Draw button border
graphics.drawRectangle(
TerminalPosition.TOP_LEFT_CORNER,
graphics.getSize(),
'█'
);
// Draw label centered
graphics.putString(
2, 1,
button.getLabel()
);
}
});
}
}
Advanced Features
Multi-Window Application
// Create non-modal windows
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));
// Add both windows
gui.addWindow(window1);
gui.addWindow(window2);
Non-blocking Input
// Non-blocking input
KeyStroke keyStroke = screen.pollInput();
if (keyStroke != null) {
if (keyStroke.getKeyType() == KeyType.Escape) {
// ESC key pressed
}
}
Animation
public class AnimatedLabel extends Label {
private String fullText;
private int currentIndex = 0;
public AnimatedLabel(String text) {
super("");
this.fullText = text;
// Animation timer
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (currentIndex <= fullText.length()) {
setText(fullText.substring(0, currentIndex));
currentIndex++;
} else {
currentIndex = 0;
}
}
}, 0, 100);
}
}
Ecosystem
Supported Terminals
- Linux: xterm, gnome-terminal, konsole
- Windows: cmd.exe, PowerShell, Windows Terminal
- macOS: Terminal.app, iTerm2
- Emulators: PuTTY, MinTTY
Related Projects
- CHARVA: Uses Lanterna as backend
- Game Development: Used for roguelike games
- Tool Development: TUI-based development tools
Advantages
- Pure Java: No native dependencies for high portability
- Three-Layer Architecture: Flexible abstraction levels
- Swing-like: Familiar API for Java developers
- Development Environment: Easy debugging in GUI environments
- Active Development: Continuous maintenance
Limitations
- Performance: May be slower than native implementations
- Limited Features: Advanced graphics features are limited
- Learning Curve: Understanding three-layer architecture required
- Documentation: Limited non-English resources
Comparison with Other Libraries
Feature | Lanterna | Jexer | CHARVA |
---|---|---|---|
Pure Java | Yes | Yes | No (JNI) |
API Style | Swing-like | Turbo Vision-like | Swing compatible |
Features | Moderate | Advanced | Basic |
Development Activity | High | Medium | Low |
Learning Cost | Medium | High | Low |
Summary
Lanterna is an excellent library for developing TUI applications in Java. Its pure Java implementation provides high portability, the three-layer architecture offers flexibility, and the Swing-like API makes it easy to learn. The ability to use the same code from GUI development environments to headless server deployment is a significant advantage.