feat: Terminal rendering
Now finally we can render cube on a screen with up arrow
This commit is contained in:
@@ -6,12 +6,12 @@ import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
|
||||
import com.googlecode.lanterna.terminal.MouseCaptureMode;
|
||||
import cz.jzitnik.annotations.Dependency;
|
||||
import cz.jzitnik.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.events.KeyboardPressEvent;
|
||||
import cz.jzitnik.events.MouseAction;
|
||||
import cz.jzitnik.events.RerenderScreen;
|
||||
import cz.jzitnik.events.TerminalResizeEvent;
|
||||
import cz.jzitnik.states.RunningState;
|
||||
import cz.jzitnik.utils.StateManager;
|
||||
import cz.jzitnik.states.TerminalState;
|
||||
import cz.jzitnik.utils.events.EventManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -23,8 +23,11 @@ public class Cli implements Runnable {
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectDependency
|
||||
private StateManager stateManager;
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectState
|
||||
private RunningState runningState;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -33,15 +36,21 @@ public class Cli implements Runnable {
|
||||
try (TerminalScreen terminal = new DefaultTerminalFactory()
|
||||
.setMouseCaptureMode(MouseCaptureMode.CLICK_RELEASE_DRAG_MOVE)
|
||||
.createScreen()) {
|
||||
stateManager.registerManually(terminal);
|
||||
terminalState.setTerminalScreen(terminal);
|
||||
terminalState.setTextGraphics(terminal.newTextGraphics());
|
||||
|
||||
terminal.setCursorPosition(null);
|
||||
terminal.doResizeIfNecessary();
|
||||
terminal.getTerminal().enterPrivateMode();
|
||||
|
||||
terminal.getTerminal().addResizeListener((_, terminalSize) -> eventManager.emitEvent(new TerminalResizeEvent(terminalSize)));
|
||||
terminal.getTerminal().addResizeListener((_, terminalSize) -> {
|
||||
terminal.doResizeIfNecessary();
|
||||
eventManager.emitEvent(new TerminalResizeEvent(terminalSize));
|
||||
});
|
||||
|
||||
terminal.startScreen();
|
||||
|
||||
eventManager.emitEvent(new TerminalResizeEvent(terminal.getTerminalSize()));
|
||||
|
||||
RunningState runningState = stateManager.getOrThrow(RunningState.class);
|
||||
while (runningState.isRunning()) {
|
||||
KeyStroke keyStroke = terminal.readInput();
|
||||
if (keyStroke != null) {
|
||||
@@ -53,8 +62,6 @@ public class Cli implements Runnable {
|
||||
eventManager.emitEvent(new KeyboardPressEvent(keyStroke));
|
||||
}
|
||||
}
|
||||
|
||||
eventManager.emitEvent(RerenderScreen.full(terminal.getTerminalSize()));
|
||||
} catch (IOException e) {
|
||||
log.error("Terminal error occurred, shutting down CLI thread.", e);
|
||||
throw new RuntimeException(e);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cz.jzitnik.events;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import cz.jzitnik.utils.events.Event;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -8,12 +9,16 @@ import lombok.Data;
|
||||
import java.awt.*;
|
||||
|
||||
public record RerenderScreen(ScreenPart[] parts) implements Event {
|
||||
public RerenderScreen(ScreenPart part) {
|
||||
this(new ScreenPart[] { part });
|
||||
}
|
||||
|
||||
public static RerenderScreen full(TerminalSize terminalSize) {
|
||||
return new RerenderScreen(
|
||||
new ScreenPart[]{
|
||||
new ScreenPart(
|
||||
new Point(0, 0),
|
||||
new Point(terminalSize.getRows() - 1, terminalSize.getColumns() - 1)
|
||||
new TerminalPosition(0, 0),
|
||||
new TerminalPosition(terminalSize.getColumns() - 1, terminalSize.getRows() - 1)
|
||||
)
|
||||
}
|
||||
);
|
||||
@@ -22,7 +27,7 @@ public record RerenderScreen(ScreenPart[] parts) implements Event {
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class ScreenPart {
|
||||
private Point start;
|
||||
private Point end;
|
||||
private TerminalPosition start;
|
||||
private TerminalPosition end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,25 @@ package cz.jzitnik.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TextCharacter;
|
||||
import com.googlecode.lanterna.TextColor;
|
||||
import com.googlecode.lanterna.screen.TerminalScreen;
|
||||
import com.googlecode.lanterna.graphics.TextGraphics;
|
||||
import com.googlecode.lanterna.screen.Screen;
|
||||
import cz.jzitnik.annotations.EventHandler;
|
||||
import cz.jzitnik.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.events.RerenderScreen;
|
||||
import cz.jzitnik.states.ScreenBuffer;
|
||||
import cz.jzitnik.states.TerminalState;
|
||||
import cz.jzitnik.ui.pixels.Empty;
|
||||
import cz.jzitnik.ui.pixels.Pixel;
|
||||
import cz.jzitnik.utils.DependencyManager;
|
||||
import cz.jzitnik.utils.events.AbstractEventHandler;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
|
||||
@EventHandler(RerenderScreen.class)
|
||||
public class CliHandler extends AbstractEventHandler<RerenderScreen> {
|
||||
@InjectState
|
||||
private TerminalScreen terminalScreen;
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
@@ -30,25 +33,32 @@ public class CliHandler extends AbstractEventHandler<RerenderScreen> {
|
||||
public void handle(RerenderScreen event) {
|
||||
var parts = event.parts();
|
||||
var buffer = screenBuffer.getBuffer();
|
||||
var terminalScreen = terminalState.getTerminalScreen();
|
||||
var tg = terminalState.getTextGraphics();
|
||||
|
||||
for (RerenderScreen.ScreenPart part : parts) {
|
||||
Point a = part.getStart();
|
||||
Point b = part.getEnd();
|
||||
var start = part.getStart();
|
||||
var end = part.getEnd();
|
||||
|
||||
for (double y = a.getY(); y < b.getY(); y++) {
|
||||
for (double x = a.getX(); x < b.getX(); x++) {
|
||||
Pixel pixel = buffer[(int) y][(int) x];
|
||||
terminalScreen.setCharacter(
|
||||
(int) x,
|
||||
(int) y,
|
||||
new TextCharacter(
|
||||
' ',
|
||||
TextColor.ANSI.DEFAULT,
|
||||
pixel.getClass().equals(Empty.class) ? TextColor.ANSI.DEFAULT : pixel.getColor()
|
||||
)
|
||||
);
|
||||
for (int y = start.getRow(); y <= end.getRow(); y++) {
|
||||
for (int x = start.getColumn(); x <= end.getColumn(); x++) {
|
||||
Pixel pixel = buffer[y][x];
|
||||
TextColor color = pixel.getClass().equals(Empty.class) ? TextColor.ANSI.BLACK : pixel.getColor();
|
||||
|
||||
drawPixel(tg, x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
terminalScreen.refresh();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawPixel(TextGraphics tg, int x, int y, TextColor color) {
|
||||
tg.setForegroundColor(color);
|
||||
tg.setCharacter(x, y, '█'); // full block character
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,5 +18,6 @@ public class ExitEventHandler extends AbstractEventHandler<ExitEvent> {
|
||||
StateManager stateManager = dm.getDependencyOrThrow(StateManager.class);
|
||||
RunningState runningState = stateManager.getOrThrow(RunningState.class);
|
||||
runningState.setRunning(false);
|
||||
System.exit(0); // Pls don't blame me
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
package cz.jzitnik.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TerminalPosition;
|
||||
import com.googlecode.lanterna.TextColor;
|
||||
import com.googlecode.lanterna.input.KeyStroke;
|
||||
import cz.jzitnik.annotations.EventHandler;
|
||||
import cz.jzitnik.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.events.ExitEvent;
|
||||
import cz.jzitnik.events.KeyboardPressEvent;
|
||||
import cz.jzitnik.events.RerenderScreen;
|
||||
import cz.jzitnik.states.ScreenBuffer;
|
||||
import cz.jzitnik.ui.pixels.ColoredPixel;
|
||||
import cz.jzitnik.ui.pixels.Pixel;
|
||||
import cz.jzitnik.utils.DependencyManager;
|
||||
import cz.jzitnik.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.utils.events.EventManager;
|
||||
@@ -14,13 +21,29 @@ public class KeyboardPressEventHandler extends AbstractEventHandler<KeyboardPres
|
||||
super(dm);
|
||||
}
|
||||
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
|
||||
@Override
|
||||
public void handle(KeyboardPressEvent event) {
|
||||
EventManager eventManager = dm.getDependencyOrThrow(EventManager.class);
|
||||
|
||||
KeyStroke keyStroke = event.getKeyStroke();
|
||||
Pixel[][] buffer = screenBuffer.getBuffer();
|
||||
|
||||
switch (keyStroke.getKeyType()) {
|
||||
case ArrowUp:
|
||||
var start = new TerminalPosition(0, 0);
|
||||
var end = new TerminalPosition(20, 10);
|
||||
for (int y = start.getRow(); y < end.getRow(); y++) {
|
||||
for (int x = start.getColumn(); x < end.getColumn(); x++) {
|
||||
buffer[y][x] = new ColoredPixel(TextColor.ANSI.CYAN);
|
||||
}
|
||||
}
|
||||
|
||||
eventManager.emitEvent(new RerenderScreen(new RerenderScreen.ScreenPart(start, end)));
|
||||
|
||||
break;
|
||||
case Escape:
|
||||
eventManager.emitEvent(new ExitEvent());
|
||||
break;
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
package cz.jzitnik.events.handlers;
|
||||
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import cz.jzitnik.annotations.EventHandler;
|
||||
import cz.jzitnik.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.events.RerenderScreen;
|
||||
import cz.jzitnik.events.TerminalResizeEvent;
|
||||
import cz.jzitnik.states.ScreenBuffer;
|
||||
import cz.jzitnik.ui.pixels.Empty;
|
||||
import cz.jzitnik.ui.pixels.Pixel;
|
||||
import cz.jzitnik.utils.DependencyManager;
|
||||
import cz.jzitnik.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.utils.events.EventManager;
|
||||
|
||||
@EventHandler(TerminalResizeEvent.class)
|
||||
public class TerminalResizeEventHandler extends AbstractEventHandler<TerminalResizeEvent> {
|
||||
@@ -11,8 +19,26 @@ public class TerminalResizeEventHandler extends AbstractEventHandler<TerminalRes
|
||||
super(dm);
|
||||
}
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectState
|
||||
private ScreenBuffer screenBuffer;
|
||||
|
||||
@Override
|
||||
public void handle(TerminalResizeEvent event) {
|
||||
System.out.println("NEWSIZE: " + event.getNewSize());
|
||||
TerminalSize size = event.getNewSize();
|
||||
int width = size.getColumns();
|
||||
int height = size.getRows();
|
||||
|
||||
Pixel[][] buffer = new Pixel[height][width];
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
buffer[y][x] = new Empty();
|
||||
}
|
||||
}
|
||||
screenBuffer.setBuffer(buffer);
|
||||
|
||||
eventManager.emitEvent(RerenderScreen.full(size));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ import lombok.Data;
|
||||
@Data
|
||||
@State
|
||||
public class ScreenBuffer {
|
||||
private Pixel[][] buffer;
|
||||
private Pixel[][] buffer = new Pixel[][] {};
|
||||
}
|
||||
|
||||
13
src/main/java/cz/jzitnik/states/TerminalState.java
Normal file
13
src/main/java/cz/jzitnik/states/TerminalState.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package cz.jzitnik.states;
|
||||
|
||||
import com.googlecode.lanterna.graphics.TextGraphics;
|
||||
import com.googlecode.lanterna.screen.TerminalScreen;
|
||||
import cz.jzitnik.annotations.State;
|
||||
import lombok.Data;
|
||||
|
||||
@State
|
||||
@Data
|
||||
public class TerminalState {
|
||||
private TerminalScreen terminalScreen;
|
||||
private TextGraphics textGraphics;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import cz.jzitnik.annotations.Dependency;
|
||||
import cz.jzitnik.annotations.injectors.InjectConfig;
|
||||
import cz.jzitnik.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.events.handlers.CliHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
@@ -96,48 +97,53 @@ public class DependencyManager {
|
||||
}
|
||||
}
|
||||
|
||||
for (Object instance : data.values()) {
|
||||
inject(instance);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void inject(Object instance) {
|
||||
StateManager stateManager = (StateManager) data.get(StateManager.class);
|
||||
for (Object instance: data.values()) {
|
||||
Class<?> clazz = instance.getClass();
|
||||
Class<?> clazz = instance.getClass();
|
||||
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(InjectDependency.class)) {
|
||||
field.setAccessible(true);
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(InjectDependency.class)) {
|
||||
field.setAccessible(true);
|
||||
|
||||
if (!data.containsKey(field.getType())) continue;
|
||||
if (!data.containsKey(field.getType())) continue;
|
||||
|
||||
Object dependency = field.getType() == getClass() ? this : data.get(field.getType());
|
||||
Object dependency = field.getType() == getClass() ? this : data.get(field.getType());
|
||||
|
||||
if (!field.getType().isAssignableFrom(dependency.getClass())) continue;
|
||||
if (!field.getType().isAssignableFrom(dependency.getClass())) continue;
|
||||
|
||||
try {
|
||||
field.set(instance, dependency);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else if (field.isAnnotationPresent(InjectState.class)) {
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
field.set(instance, dependency);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else if (field.isAnnotationPresent(InjectState.class)) {
|
||||
field.setAccessible(true);
|
||||
|
||||
Optional<?> stateOptional = stateManager.get(field.getType());
|
||||
Optional<?> stateOptional = stateManager.get(field.getType());
|
||||
|
||||
if (stateOptional.isEmpty()) continue;
|
||||
if (stateOptional.isEmpty()) continue;
|
||||
|
||||
try {
|
||||
field.set(instance, stateOptional.get());
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else if (field.isAnnotationPresent(InjectConfig.class)) {
|
||||
field.setAccessible(true);
|
||||
Optional<?> config = Optional.ofNullable(configs.get(field.getType()));
|
||||
try {
|
||||
field.set(instance, stateOptional.get());
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else if (field.isAnnotationPresent(InjectConfig.class)) {
|
||||
field.setAccessible(true);
|
||||
Optional<?> config = Optional.ofNullable(configs.get(field.getType()));
|
||||
|
||||
if (config.isEmpty()) continue;
|
||||
if (config.isEmpty()) continue;
|
||||
|
||||
try {
|
||||
field.set(instance, config.get());
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
try {
|
||||
field.set(instance, config.get());
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public class EventManager extends Thread {
|
||||
private ExecutorService eventExecutor;
|
||||
private final HashMap<Class<? extends Event>, AbstractEventHandler<? extends Event>> handlers = new HashMap<>();
|
||||
private final BlockingQueue<Event> eventQueue = new LinkedBlockingQueue<>();
|
||||
private final DependencyManager dependencyManager;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Event> AbstractEventHandler<T> getHandler(Class<T> type) {
|
||||
@@ -38,6 +39,7 @@ public class EventManager extends Thread {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public EventManager(Reflections reflections, DependencyManager dependencyManager) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
setDaemon(true);
|
||||
|
||||
var classes = reflections.getTypesAnnotatedWith(EventHandler.class);
|
||||
@@ -56,6 +58,10 @@ public class EventManager extends Thread {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (Object instance : handlers.values()) {
|
||||
dependencyManager.inject(instance);
|
||||
}
|
||||
|
||||
eventExecutor = Executors.newFixedThreadPool(eventThreadPoolConfig.getThreadCount());
|
||||
while (runningState.isRunning()) {
|
||||
try {
|
||||
@@ -76,7 +82,13 @@ public class EventManager extends Thread {
|
||||
AbstractEventHandler<T> handler = getHandler((Class<T>) event.getClass());
|
||||
|
||||
if (handler != null) {
|
||||
eventExecutor.submit(() -> handler.handle(typedEvent));
|
||||
eventExecutor.submit(() -> {
|
||||
try {
|
||||
handler.handle(typedEvent);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user