refactor config
This commit is contained in:
@@ -55,7 +55,7 @@ public record AppConfig(
|
|||||||
AppConfig defaults = defaults();
|
AppConfig defaults = defaults();
|
||||||
Path path = configPath();
|
Path path = configPath();
|
||||||
if (!Files.isRegularFile(path)) {
|
if (!Files.isRegularFile(path)) {
|
||||||
writeDefaultConfig(path, defaults);
|
save(path, defaults);
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,10 +195,6 @@ public record AppConfig(
|
|||||||
return Map.copyOf(parsed);
|
return Map.copyOf(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writeDefaultConfig(Path path, AppConfig defaults) {
|
|
||||||
save(path, defaults);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void save(Path path, AppConfig config) {
|
private static void save(Path path, AppConfig config) {
|
||||||
try {
|
try {
|
||||||
Path parent = path.getParent();
|
Path parent = path.getParent();
|
||||||
@@ -340,15 +336,7 @@ public record AppConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int intValue(TomlTable table, String key, int fallback) {
|
private static int intValue(TomlTable table, String key, int fallback) {
|
||||||
TomlPrimitive primitive = primitive(table, key);
|
return (int) longValue(table, key, fallback);
|
||||||
if (primitive == null) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return primitive.asInteger();
|
|
||||||
} catch (RuntimeException ex) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long longValue(TomlTable table, String key, long fallback) {
|
private static long longValue(TomlTable table, String key, long fallback) {
|
||||||
|
|||||||
@@ -134,29 +134,11 @@ public final class Compositor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void toggleFloating() {
|
public void toggleFloating() {
|
||||||
if (isEmpty()) {
|
mutateCurrentTab(() -> currentTab().toggleFloating());
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTab().toggleFloating();
|
|
||||||
layoutVersion++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createPane() {
|
public void createPane() {
|
||||||
if (isEmpty()) {
|
mutateCurrentTab(() -> currentTab().createPane());
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTab().createPane();
|
|
||||||
layoutVersion++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Opens a new floating pane, makes it active, and returns it (null when no tab exists). */
|
|
||||||
public TerminalPane openFloatingPane() {
|
|
||||||
if (isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
TerminalPane pane = currentTab().createFloatingPane();
|
|
||||||
layoutVersion++;
|
|
||||||
return pane;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -173,18 +155,20 @@ public final class Compositor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void nextFloatingPane() {
|
public void nextFloatingPane() {
|
||||||
if (isEmpty()) {
|
mutateCurrentTab(() -> currentTab().nextFloatingPane());
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTab().nextFloatingPane();
|
|
||||||
layoutVersion++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggleActiveFloating() {
|
public void toggleActiveFloating() {
|
||||||
|
mutateCurrentTab(() -> currentTab().toggleActiveFloating());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a structural change on the current tab and bump the layout version so the next frame
|
||||||
|
// recomposites. No-op when no tab is left.
|
||||||
|
private void mutateCurrentTab(Runnable change) {
|
||||||
if (isEmpty()) {
|
if (isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentTab().toggleActiveFloating();
|
change.run();
|
||||||
layoutVersion++;
|
layoutVersion++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -259,12 +259,6 @@ final class Tab implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void closeActivePane() {
|
|
||||||
if (active != null) {
|
|
||||||
closePane(active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes {@code closing} (the active pane on a key-bound close, or any pane whose process just
|
* Closes {@code closing} (the active pane on a key-bound close, or any pane whose process just
|
||||||
* exited) and re-selects the active pane only when the one that closed was active. Returns
|
* exited) and re-selects the active pane only when the one that closed was active. Returns
|
||||||
@@ -329,8 +323,8 @@ final class Tab implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TerminalPane createFloatingPane() {
|
private void createFloatingPane() {
|
||||||
return addFloating(openPane(true));
|
addFloating(openPane(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -41,24 +41,17 @@ abstract class TerminalRenderer {
|
|||||||
void release() {
|
void release() {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void clipRect(GraphicsContext gc, double x, double y, double width, double height) {
|
|
||||||
gc.beginPath();
|
|
||||||
gc.rect(x, y, width, height);
|
|
||||||
gc.clip();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clip to {@code region} if given (the pane's rect minus the panes covering it, computed by
|
* Clip to {@code region} if given (the pane's rect minus the panes covering it, computed by
|
||||||
* {@code Shape.subtract} at layout), otherwise to the plain rect. The region is a rectilinear
|
* {@code Shape.subtract} at layout), otherwise to the plain rect. The region is a rectilinear
|
||||||
* path, so it replays onto the canvas as move/line/close segments.
|
* path, so it replays onto the canvas as move/line/close segments.
|
||||||
*/
|
*/
|
||||||
protected static void clip(GraphicsContext gc, double x, double y, double width, double height, Shape region) {
|
protected static void clip(GraphicsContext gc, double x, double y, double width, double height, Shape region) {
|
||||||
if (region == null) {
|
|
||||||
clipRect(gc, x, y, width, height);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var elements = ((Path) region).getElements();
|
|
||||||
gc.beginPath();
|
gc.beginPath();
|
||||||
|
if (region == null) {
|
||||||
|
gc.rect(x, y, width, height);
|
||||||
|
} else {
|
||||||
|
var elements = ((Path) region).getElements();
|
||||||
if (elements.isEmpty()) {
|
if (elements.isEmpty()) {
|
||||||
gc.rect(x, y, 0.0, 0.0); // fully covered: clip to nothing
|
gc.rect(x, y, 0.0, 0.0); // fully covered: clip to nothing
|
||||||
}
|
}
|
||||||
@@ -71,6 +64,7 @@ abstract class TerminalRenderer {
|
|||||||
gc.closePath();
|
gc.closePath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
gc.clip();
|
gc.clip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ import javafx.stage.Stage;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One top-level terminal window: its own {@link Stage}, {@link Compositor}, config/metrics, render
|
* One top-level terminal window: its own {@link Stage}, {@link Compositor}, config/metrics, render
|
||||||
@@ -34,6 +36,9 @@ final class TerminalWindow {
|
|||||||
private final Compositor compositor;
|
private final Compositor compositor;
|
||||||
private final Stage stage;
|
private final Stage stage;
|
||||||
private final AnimationTimer renderLoop;
|
private final AnimationTimer renderLoop;
|
||||||
|
// Key-bound actions by their config keybinding name, checked in this order on each key press
|
||||||
|
// (the keys must match AppConfig's keybinding keys).
|
||||||
|
private final Map<String, Runnable> keyActions = new LinkedHashMap<>();
|
||||||
private AppConfig config;
|
private AppConfig config;
|
||||||
private boolean closed;
|
private boolean closed;
|
||||||
|
|
||||||
@@ -51,6 +56,23 @@ final class TerminalWindow {
|
|||||||
// The last pane closing closes this window (not the JVM); see teardown().
|
// The last pane closing closes this window (not the JVM); see teardown().
|
||||||
compositor.setOnEmpty(this::teardown);
|
compositor.setOnEmpty(this::teardown);
|
||||||
|
|
||||||
|
keyActions.put("navigate_left", () -> compositor.navigate(Direction.LEFT));
|
||||||
|
keyActions.put("navigate_down", () -> compositor.navigate(Direction.DOWN));
|
||||||
|
keyActions.put("navigate_up", () -> compositor.navigate(Direction.UP));
|
||||||
|
keyActions.put("navigate_right", () -> compositor.navigate(Direction.RIGHT));
|
||||||
|
keyActions.put("toggle_floating", compositor::toggleFloating);
|
||||||
|
keyActions.put("new_pane", compositor::createPane);
|
||||||
|
keyActions.put("next_floating", compositor::nextFloatingPane);
|
||||||
|
keyActions.put("promote_floating", compositor::toggleActiveFloating);
|
||||||
|
// Closing the last pane closes this window, via the compositor's onEmpty hook.
|
||||||
|
keyActions.put("close_pane", compositor::closeActivePane);
|
||||||
|
keyActions.put("new_tab", compositor::newTab);
|
||||||
|
keyActions.put("previous_tab", compositor::previousTab);
|
||||||
|
keyActions.put("next_tab", compositor::nextTab);
|
||||||
|
keyActions.put("open_font_selector", this::openFontSelector);
|
||||||
|
keyActions.put("open_scrollback", this::openScrollbackInEditor);
|
||||||
|
keyActions.put("paste", this::pasteFromClipboard);
|
||||||
|
|
||||||
StackPane root = new StackPane(compositor.canvas(), compositor.imageOverlay());
|
StackPane root = new StackPane(compositor.canvas(), compositor.imageOverlay());
|
||||||
compositor.canvas().widthProperty().bind(root.widthProperty());
|
compositor.canvas().widthProperty().bind(root.widthProperty());
|
||||||
compositor.canvas().heightProperty().bind(root.heightProperty());
|
compositor.canvas().heightProperty().bind(root.heightProperty());
|
||||||
@@ -107,60 +129,19 @@ final class TerminalWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handlePressed(KeyEvent event) {
|
private void handlePressed(KeyEvent event) {
|
||||||
if (config.keybindings().get("navigate_left").matches(event)) {
|
for (Map.Entry<String, Runnable> action : keyActions.entrySet()) {
|
||||||
compositor.navigate(Direction.LEFT);
|
if (config.keybindings().get(action.getKey()).matches(event)) {
|
||||||
|
action.getValue().run();
|
||||||
event.consume();
|
event.consume();
|
||||||
} else if (config.keybindings().get("navigate_down").matches(event)) {
|
return;
|
||||||
compositor.navigate(Direction.DOWN);
|
}
|
||||||
event.consume();
|
}
|
||||||
} else if (config.keybindings().get("navigate_up").matches(event)) {
|
|
||||||
compositor.navigate(Direction.UP);
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("navigate_right").matches(event)) {
|
|
||||||
compositor.navigate(Direction.RIGHT);
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("toggle_floating").matches(event)) {
|
|
||||||
compositor.toggleFloating();
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("new_pane").matches(event)) {
|
|
||||||
compositor.createPane();
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("next_floating").matches(event)) {
|
|
||||||
compositor.nextFloatingPane();
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("promote_floating").matches(event)) {
|
|
||||||
compositor.toggleActiveFloating();
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("close_pane").matches(event)) {
|
|
||||||
// Closing the last pane closes this window, via the compositor's onEmpty hook.
|
|
||||||
compositor.closeActivePane();
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("new_tab").matches(event)) {
|
|
||||||
compositor.newTab();
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("previous_tab").matches(event)) {
|
|
||||||
compositor.previousTab();
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("next_tab").matches(event)) {
|
|
||||||
compositor.nextTab();
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("open_font_selector").matches(event)) {
|
|
||||||
openFontSelector();
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("open_scrollback").matches(event)) {
|
|
||||||
openScrollbackInEditor();
|
|
||||||
event.consume();
|
|
||||||
} else if (config.keybindings().get("paste").matches(event)) {
|
|
||||||
pasteFromClipboard();
|
|
||||||
event.consume();
|
|
||||||
} else {
|
|
||||||
String encoded = KeyEncoder.encode(event);
|
String encoded = KeyEncoder.encode(event);
|
||||||
if (encoded != null) {
|
if (encoded != null) {
|
||||||
compositor.activePane().send(encoded);
|
compositor.activePane().send(encoded);
|
||||||
event.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void handleTyped(KeyEvent event) {
|
private void handleTyped(KeyEvent event) {
|
||||||
if (event.isAltDown() || event.isControlDown() || event.isMetaDown()) {
|
if (event.isAltDown() || event.isControlDown() || event.isMetaDown()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user