Skip to content

Commit bf0e20e

Browse files
committed
feat: Implement comprehensive JLine Curses TUI library with focus indicators and repaint system
This commit implements a complete Terminal User Interface (TUI) library for JLine with: - **Essential Components**: List, Table, Tree, Input, Label, Button, TextArea - **Layout Containers**: Box, Window, Panel for organizing components - **Interactive Demo**: Complete CursesDemo showcasing all functionality - **Focus Indicators**: Yellow borders and titles for focused components - **Theme Integration**: Consistent styling through DefaultTheme - **Visual Feedback**: Clear indication of which component has focus - **Invalidation API**: Components automatically invalidate on state changes - **Efficient Updates**: Only invalid components are redrawn - **Focus-Aware**: Automatic invalidation on focus changes - **Performance**: Minimal screen updates for optimal performance - **KeyEvent System**: Comprehensive key event processing - **Keyboard Navigation**: Arrow keys, Tab, Enter, Escape support - **Modifier Keys**: Ctrl, Alt, Shift detection and handling - **Shortcut System**: Configurable keyboard shortcuts - **Comprehensive Tests**: 368+ tests across all modules - **Virtual Screen Testing**: Mock screen for component testing - **Integration Tests**: End-to-end functionality verification - **Code Quality**: Spotless formatting and clean architecture - **POSIX Commands**: Comprehensive POSIX command implementation - **Build System**: Clean Maven build with proper dependency management - **Documentation**: Updated module documentation and examples The implementation follows TUI best practices from libraries like Ratatui and Cursive, providing a modern, efficient, and user-friendly terminal interface experience.
1 parent 4f6da0e commit bf0e20e

32 files changed

+7892
-97
lines changed

curses/src/main/java/org/jline/curses/Component.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import java.util.EnumSet;
1212

13+
import org.jline.terminal.KeyEvent;
1314
import org.jline.terminal.MouseEvent;
1415

1516
public interface Component {
@@ -51,7 +52,32 @@ enum Behavior {
5152
Popup
5253
}
5354

54-
void handleMouse(MouseEvent event);
55-
56-
void handleInput(String input);
55+
boolean handleMouse(MouseEvent event);
56+
57+
/**
58+
* Handle a key event.
59+
* @param event the key event to handle
60+
* @return true if the event was handled, false otherwise
61+
*/
62+
boolean handleKey(KeyEvent event);
63+
64+
/**
65+
* Marks this component as needing to be repainted.
66+
* This will trigger a redraw on the next render cycle.
67+
*/
68+
void invalidate();
69+
70+
/**
71+
* Returns true if this component needs to be repainted.
72+
* @return true if the component is invalid and needs repainting
73+
*/
74+
boolean isInvalid();
75+
76+
/**
77+
* Gets the shortcut key for this component, if any.
78+
* @return the shortcut key, or null if none
79+
*/
80+
default String getShortcutKey() {
81+
return null;
82+
}
5783
}

curses/src/main/java/org/jline/curses/Curses.java

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ public static Box box(String title, Border border, Component component) {
9494
return new Box(title, border, component);
9595
}
9696

97+
public static BoxBuilder box(String title, Border border) {
98+
return new BoxBuilder(title, border);
99+
}
100+
97101
public interface ComponentBuilder<C extends Component> {
98102

99103
C build();
@@ -127,7 +131,7 @@ public Container build() {
127131
public static class SubMenuBuilder {
128132
private String name;
129133
private String key;
130-
List<MenuItem> contents = new ArrayList<>();
134+
java.util.List<MenuItem> contents = new ArrayList<>();
131135

132136
public SubMenuBuilder name(String name) {
133137
this.name = name;
@@ -205,9 +209,9 @@ public SubMenuBuilder add() {
205209

206210
public static class MenuBuilder implements ComponentBuilder<Menu> {
207211

208-
List<SubMenu> contents = new ArrayList<>();
212+
java.util.List<SubMenu> contents = new ArrayList<>();
209213

210-
public MenuBuilder submenu(String name, String key, List<MenuItem> menu) {
214+
public MenuBuilder submenu(String name, String key, java.util.List<MenuItem> menu) {
211215
return submenu(new SubMenu(name, key, menu));
212216
}
213217

@@ -259,4 +263,38 @@ public Window build() {
259263
return w;
260264
}
261265
}
266+
267+
public static class BoxBuilder implements ComponentBuilder<Box> {
268+
private final String title;
269+
private final Border border;
270+
private Component component;
271+
private String shortcutKey;
272+
273+
BoxBuilder(String title, Border border) {
274+
this.title = title;
275+
this.border = border;
276+
}
277+
278+
public BoxBuilder component(Component component) {
279+
this.component = component;
280+
return this;
281+
}
282+
283+
public BoxBuilder component(ComponentBuilder<?> component) {
284+
return component(component.build());
285+
}
286+
287+
public BoxBuilder key(String shortcutKey) {
288+
this.shortcutKey = shortcutKey;
289+
return this;
290+
}
291+
292+
@Override
293+
public Box build() {
294+
if (component == null) {
295+
throw new IllegalStateException("Component must be set");
296+
}
297+
return new Box(title, border, component, shortcutKey);
298+
}
299+
}
262300
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2002-2018, the original author(s).
3+
*
4+
* This software is distributable under the BSD license. See the terms of the
5+
* BSD license in the documentation provided with this software.
6+
*
7+
* https://opensource.org/licenses/BSD-3-Clause
8+
*/
9+
package org.jline.curses;
10+
11+
import org.jline.terminal.KeyEvent;
12+
13+
/**
14+
* Represents an input event that can be either a KeyEvent or a Mouse event indicator.
15+
* This is used in the KeyMap to distinguish between keyboard and mouse input.
16+
*/
17+
public class InputEvent {
18+
19+
/**
20+
* Special singleton instance to indicate mouse events.
21+
*/
22+
public static final InputEvent MOUSE = new InputEvent();
23+
24+
private final KeyEvent keyEvent;
25+
private final boolean isMouse;
26+
27+
// Private constructor for mouse indicator
28+
private InputEvent() {
29+
this.keyEvent = null;
30+
this.isMouse = true;
31+
}
32+
33+
/**
34+
* Creates an InputEvent for a KeyEvent.
35+
*/
36+
public InputEvent(KeyEvent keyEvent) {
37+
this.keyEvent = keyEvent;
38+
this.isMouse = false;
39+
}
40+
41+
/**
42+
* Returns true if this is a mouse event indicator.
43+
*/
44+
public boolean isMouse() {
45+
return isMouse;
46+
}
47+
48+
/**
49+
* Returns true if this is a key event.
50+
*/
51+
public boolean isKey() {
52+
return !isMouse;
53+
}
54+
55+
/**
56+
* Returns the KeyEvent if this is a key event, null otherwise.
57+
*/
58+
public KeyEvent getKeyEvent() {
59+
return keyEvent;
60+
}
61+
62+
@Override
63+
public String toString() {
64+
if (isMouse) {
65+
return "InputEvent[MOUSE]";
66+
} else {
67+
return "InputEvent[" + keyEvent + "]";
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)