refactor config
This commit is contained in:
@@ -55,7 +55,7 @@ public record AppConfig(
|
||||
AppConfig defaults = defaults();
|
||||
Path path = configPath();
|
||||
if (!Files.isRegularFile(path)) {
|
||||
writeDefaultConfig(path, defaults);
|
||||
save(path, defaults);
|
||||
return defaults;
|
||||
}
|
||||
|
||||
@@ -195,10 +195,6 @@ public record AppConfig(
|
||||
return Map.copyOf(parsed);
|
||||
}
|
||||
|
||||
private static void writeDefaultConfig(Path path, AppConfig defaults) {
|
||||
save(path, defaults);
|
||||
}
|
||||
|
||||
private static void save(Path path, AppConfig config) {
|
||||
try {
|
||||
Path parent = path.getParent();
|
||||
@@ -340,15 +336,7 @@ public record AppConfig(
|
||||
}
|
||||
|
||||
private static int intValue(TomlTable table, String key, int fallback) {
|
||||
TomlPrimitive primitive = primitive(table, key);
|
||||
if (primitive == null) {
|
||||
return fallback;
|
||||
}
|
||||
try {
|
||||
return primitive.asInteger();
|
||||
} catch (RuntimeException ex) {
|
||||
return fallback;
|
||||
}
|
||||
return (int) longValue(table, key, fallback);
|
||||
}
|
||||
|
||||
private static long longValue(TomlTable table, String key, long fallback) {
|
||||
|
||||
@@ -134,29 +134,11 @@ public final class Compositor {
|
||||
}
|
||||
|
||||
public void toggleFloating() {
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
currentTab().toggleFloating();
|
||||
layoutVersion++;
|
||||
mutateCurrentTab(() -> currentTab().toggleFloating());
|
||||
}
|
||||
|
||||
public void createPane() {
|
||||
if (isEmpty()) {
|
||||
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;
|
||||
mutateCurrentTab(() -> currentTab().createPane());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,18 +155,20 @@ public final class Compositor {
|
||||
}
|
||||
|
||||
public void nextFloatingPane() {
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
currentTab().nextFloatingPane();
|
||||
layoutVersion++;
|
||||
mutateCurrentTab(() -> currentTab().nextFloatingPane());
|
||||
}
|
||||
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
currentTab().toggleActiveFloating();
|
||||
change.run();
|
||||
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
|
||||
* 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() {
|
||||
return addFloating(openPane(true));
|
||||
private void createFloatingPane() {
|
||||
addFloating(openPane(true));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,24 +41,17 @@ abstract class TerminalRenderer {
|
||||
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
|
||||
* {@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.
|
||||
*/
|
||||
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();
|
||||
if (region == null) {
|
||||
gc.rect(x, y, width, height);
|
||||
} else {
|
||||
var elements = ((Path) region).getElements();
|
||||
if (elements.isEmpty()) {
|
||||
gc.rect(x, y, 0.0, 0.0); // fully covered: clip to nothing
|
||||
}
|
||||
@@ -71,6 +64,7 @@ abstract class TerminalRenderer {
|
||||
gc.closePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
gc.clip();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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 Stage stage;
|
||||
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 boolean closed;
|
||||
|
||||
@@ -51,6 +56,23 @@ final class TerminalWindow {
|
||||
// The last pane closing closes this window (not the JVM); see 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());
|
||||
compositor.canvas().widthProperty().bind(root.widthProperty());
|
||||
compositor.canvas().heightProperty().bind(root.heightProperty());
|
||||
@@ -107,60 +129,19 @@ final class TerminalWindow {
|
||||
}
|
||||
|
||||
private void handlePressed(KeyEvent event) {
|
||||
if (config.keybindings().get("navigate_left").matches(event)) {
|
||||
compositor.navigate(Direction.LEFT);
|
||||
for (Map.Entry<String, Runnable> action : keyActions.entrySet()) {
|
||||
if (config.keybindings().get(action.getKey()).matches(event)) {
|
||||
action.getValue().run();
|
||||
event.consume();
|
||||
} else if (config.keybindings().get("navigate_down").matches(event)) {
|
||||
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 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
String encoded = KeyEncoder.encode(event);
|
||||
if (encoded != null) {
|
||||
compositor.activePane().send(encoded);
|
||||
event.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTyped(KeyEvent event) {
|
||||
if (event.isAltDown() || event.isControlDown() || event.isMetaDown()) {
|
||||
|
||||
Reference in New Issue
Block a user