From 0c0b2c107cca325d0bc3dc5046e95f75a0ae15c9 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Fri, 12 Jan 2018 23:27:56 -0600 Subject: [PATCH] Backported changes from Lawless Legends app experience; namely better boot behavior and also UI Controls Overlay. :) --- src/main/java/jace/Emulator.java | 2 +- src/main/java/jace/EmulatorUILogic.java | 94 +++++--- src/main/java/jace/JaceApplication.java | 13 +- src/main/java/jace/JaceUIController.java | 218 ++++++++++++++++-- src/main/java/jace/apple2e/Apple2e.java | 86 +++++-- src/main/java/jace/apple2e/VideoNTSC.java | 6 +- src/main/java/jace/cheat/Cheats.java | 4 +- src/main/java/jace/cheat/MetaCheat.java | 2 +- .../jace/cheat/MontezumasRevengeCheats.java | 2 +- src/main/java/jace/config/Configuration.java | 13 +- src/main/java/jace/core/Computer.java | 20 +- src/main/java/jace/core/Keyboard.java | 5 + src/main/java/jace/core/TimedDevice.java | 4 +- src/main/java/jace/core/Utility.java | 93 +++++++- src/main/java/jace/core/Video.java | 8 +- src/main/resources/fxml/JaceUI.fxml | 187 ++++++++++++++- src/main/resources/styles/icons/aspect.png | Bin 0 -> 724 bytes src/main/resources/styles/icons/brun.png | Bin 0 -> 976 bytes src/main/resources/styles/icons/config.png | Bin 0 -> 1354 bytes src/main/resources/styles/icons/fast.png | Bin 0 -> 1998 bytes .../resources/styles/icons/fullscreen.png | Bin 0 -> 914 bytes src/main/resources/styles/icons/ide.png | Bin 0 -> 1238 bytes src/main/resources/styles/icons/info.png | Bin 0 -> 1079 bytes src/main/resources/styles/icons/inspect.png | Bin 0 -> 1406 bytes src/main/resources/styles/icons/paste.png | Bin 0 -> 800 bytes src/main/resources/styles/icons/play.png | Bin 0 -> 567 bytes src/main/resources/styles/icons/plugin.png | Bin 0 -> 1290 bytes src/main/resources/styles/icons/reboot.png | Bin 0 -> 1239 bytes src/main/resources/styles/icons/rewind.png | Bin 0 -> 701 bytes .../resources/styles/icons/screenshot.png | Bin 0 -> 959 bytes src/main/resources/styles/icons/slow.png | Bin 0 -> 3019 bytes src/main/resources/styles/icons/sound.png | Bin 0 -> 1331 bytes src/main/resources/styles/style.css | 29 ++- 33 files changed, 670 insertions(+), 116 deletions(-) create mode 100644 src/main/resources/styles/icons/aspect.png create mode 100644 src/main/resources/styles/icons/brun.png create mode 100644 src/main/resources/styles/icons/config.png create mode 100644 src/main/resources/styles/icons/fast.png create mode 100644 src/main/resources/styles/icons/fullscreen.png create mode 100644 src/main/resources/styles/icons/ide.png create mode 100644 src/main/resources/styles/icons/info.png create mode 100644 src/main/resources/styles/icons/inspect.png create mode 100644 src/main/resources/styles/icons/paste.png create mode 100644 src/main/resources/styles/icons/play.png create mode 100644 src/main/resources/styles/icons/plugin.png create mode 100644 src/main/resources/styles/icons/reboot.png create mode 100644 src/main/resources/styles/icons/rewind.png create mode 100644 src/main/resources/styles/icons/screenshot.png create mode 100644 src/main/resources/styles/icons/slow.png create mode 100644 src/main/resources/styles/icons/sound.png diff --git a/src/main/java/jace/Emulator.java b/src/main/java/jace/Emulator.java index c34a18b..10c5587 100644 --- a/src/main/java/jace/Emulator.java +++ b/src/main/java/jace/Emulator.java @@ -18,8 +18,8 @@ */ package jace; -import jace.apple2e.Apple2e; import jace.config.Configuration; +import jace.apple2e.Apple2e; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; diff --git a/src/main/java/jace/EmulatorUILogic.java b/src/main/java/jace/EmulatorUILogic.java index a5d9710..543eef2 100644 --- a/src/main/java/jace/EmulatorUILogic.java +++ b/src/main/java/jace/EmulatorUILogic.java @@ -18,10 +18,10 @@ */ package jace; -import com.sun.javafx.tk.quantum.OverlayWarning; import jace.apple2e.MOS65C02; import jace.apple2e.RAM128k; import jace.apple2e.SoftSwitches; +import jace.config.ConfigurableField; import jace.config.ConfigurationUIController; import jace.config.InvokableAction; import jace.config.Reconfigurable; @@ -82,6 +82,12 @@ public class EmulatorUILogic implements Reconfigurable { }; } + @ConfigurableField( + category = "General", + name = "Show Drives" + ) + public boolean showDrives = false; + public static void updateCPURegisters(MOS65C02 cpu) { // DebuggerPanel debuggerPanel = Emulator.getFrame().getDebuggerPanel(); // debuggerPanel.valueA.setText(Integer.toHexString(cpu.A)); @@ -246,7 +252,7 @@ public class EmulatorUILogic implements Reconfigurable { name = "Toggle Debug", category = "debug", description = "Show/hide the debug panel", - alternatives = "Show Debug;Hide Debug", + alternatives = "Show Debug;Hide Debug;Inspect", defaultKeyMapping = "ctrl+shift+d") public static void toggleDebugPanel() { // AbstractEmulatorFrame frame = Emulator.getFrame(); @@ -262,13 +268,14 @@ public class EmulatorUILogic implements Reconfigurable { name = "Toggle fullscreen", category = "general", description = "Activate/deactivate fullscreen mode", - alternatives = "fullscreen,maximize", + alternatives = "fullscreen;maximize", defaultKeyMapping = "ctrl+shift+f") public static void toggleFullscreen() { Platform.runLater(() -> { Stage stage = JaceApplication.getApplication().primaryStage; stage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH); stage.setFullScreen(!stage.isFullScreen()); + JaceApplication.getApplication().controller.setAspectRatioEnabled(stage.isFullScreen()); }); } @@ -276,7 +283,7 @@ public class EmulatorUILogic implements Reconfigurable { name = "Save Raw Screenshot", category = "general", description = "Save raw (RAM) format of visible screen", - alternatives = "screendump, raw screenshot", + alternatives = "screendump;raw screenshot", defaultKeyMapping = "ctrl+shift+z") public static void saveScreenshotRaw() throws FileNotFoundException, IOException { SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss"); @@ -317,7 +324,7 @@ public class EmulatorUILogic implements Reconfigurable { name = "Save Screenshot", category = "general", description = "Save image of visible screen", - alternatives = "Save image,save framebuffer,screenshot", + alternatives = "Save image;save framebuffer;screenshot", defaultKeyMapping = "ctrl+shift+s") public static void saveScreenshot() throws IOException { FileChooser select = new FileChooser(); @@ -346,7 +353,7 @@ public class EmulatorUILogic implements Reconfigurable { name = "Configuration", category = "general", description = "Edit emulator configuraion", - alternatives = "Reconfigure,Preferences,Settings", + alternatives = "Reconfigure;Preferences;Settings;Config", defaultKeyMapping = {"f4", "ctrl+shift+c"}) public static void showConfig() { FXMLLoader fxmlLoader = new FXMLLoader(EmulatorUILogic.class.getResource("/fxml/Configuration.fxml")); @@ -368,7 +375,7 @@ public class EmulatorUILogic implements Reconfigurable { name = "Open IDE", category = "development", description = "Open new IDE window for Basic/Assembly/Plasma coding", - alternatives = "dev,development,acme,assembler,editor", + alternatives = "IDE;dev;development;acme;assembler;editor", defaultKeyMapping = {"ctrl+shift+i"}) public static void showIDE() { FXMLLoader fxmlLoader = new FXMLLoader(EmulatorUILogic.class.getResource("/fxml/editor.fxml")); @@ -392,44 +399,61 @@ public class EmulatorUILogic implements Reconfigurable { name = "Resize window", category = "general", description = "Resize the screen to 1x/1.5x/2x/3x video size", - alternatives = "Adjust screen;Adjust window size;Adjust aspect ratio;Fix screen;Fix window size;Fix aspect ratio;Correct aspect ratio;", + alternatives = "Aspect;Adjust screen;Adjust window size;Adjust aspect ratio;Fix screen;Fix window size;Fix aspect ratio;Correct aspect ratio;", defaultKeyMapping = {"ctrl+shift+a"}) public static void scaleIntegerRatio() { Platform.runLater(() -> { - JaceApplication.getApplication().primaryStage.setFullScreen(false); + if (JaceApplication.getApplication() == null + || JaceApplication.getApplication().primaryStage == null) { + return; + } + Stage stage = JaceApplication.getApplication().primaryStage; size++; if (size > 3) { size = 0; } - int width = 0, height = 0; - switch (size) { - case 0: // 1x - width = 560; - height = 384; - break; - case 1: // 1.5x - width = 840; - height = 576; - break; - case 2: // 2x - width = 560*2; - height = 384*2; - break; - case 3: // 3x (retina) 2880x1800 - width = 560*3; - height = 384*3; - break; - default: // 2x - width = 560*2; - height = 384*2; + if (stage.isFullScreen()) { + JaceApplication.getApplication().controller.toggleAspectRatio(); + } else { + int width = 0, height = 0; + switch (size) { + case 0: // 1x + width = 560; + height = 384; + break; + case 1: // 1.5x + width = 840; + height = 576; + break; + case 2: // 2x + width = 560 * 2; + height = 384 * 2; + break; + case 3: // 3x (retina) 2880x1800 + width = 560 * 3; + height = 384 * 3; + break; + default: // 2x + width = 560 * 2; + height = 384 * 2; + } + double vgap = stage.getScene().getY(); + double hgap = stage.getScene().getX(); + stage.setWidth(hgap * 2 + width); + stage.setHeight(vgap + height); } - Stage stage = JaceApplication.getApplication().primaryStage; - double vgap = stage.getScene().getY(); - double hgap = stage.getScene().getX(); - stage.setWidth(hgap*2 + width); - stage.setHeight(vgap + height); }); } + + @InvokableAction( + name = "About", + category = "general", + description = "Display about window", + alternatives = "info;credits", + defaultKeyMapping = {"ctrl+shift+."}) + public static void showAboutWindow() { + //TODO: Implement + } public static boolean confirm(String message) { // return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(Emulator.getFrame(), message); diff --git a/src/main/java/jace/JaceApplication.java b/src/main/java/jace/JaceApplication.java index f8703c2..c74097a 100644 --- a/src/main/java/jace/JaceApplication.java +++ b/src/main/java/jace/JaceApplication.java @@ -54,14 +54,15 @@ public class JaceApplication extends Application { } primaryStage.show(); - Emulator emulator = new Emulator(getParameters().getRaw()); - javafx.application.Platform.runLater(() -> { + new Thread(() -> { + new Emulator(getParameters().getRaw()); + reconnectUIHooks(); + EmulatorUILogic.scaleIntegerRatio(); while (Emulator.computer.getVideo() == null || Emulator.computer.getVideo().getFrameBuffer() == null) { Thread.yield(); } - controller.connectComputer(Emulator.computer, primaryStage); bootWatchdog(); - }); + }).start(); primaryStage.setOnCloseRequest(event -> { Emulator.computer.deactivate(); Platform.exit(); @@ -69,6 +70,10 @@ public class JaceApplication extends Application { }); } + public void reconnectUIHooks() { + controller.connectComputer(Emulator.computer, primaryStage); + } + public static JaceApplication getApplication() { return singleton; } diff --git a/src/main/java/jace/JaceUIController.java b/src/main/java/jace/JaceUIController.java index 7b5c3c1..c10243b 100644 --- a/src/main/java/jace/JaceUIController.java +++ b/src/main/java/jace/JaceUIController.java @@ -6,10 +6,10 @@ package jace; import com.sun.glass.ui.Application; -import jace.cheat.MetaCheat; import jace.core.Card; import jace.core.Computer; -import jace.core.Keyboard; +import jace.core.Motherboard; +import jace.core.Utility; import jace.library.MediaCache; import jace.library.MediaConsumer; import jace.library.MediaConsumerParent; @@ -30,10 +30,22 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; +import javafx.animation.FadeTransition; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.beans.binding.NumberBinding; +import javafx.beans.binding.When; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.control.Button; import javafx.scene.control.Label; +import javafx.scene.control.Slider; import javafx.scene.effect.DropShadow; import javafx.scene.image.ImageView; import javafx.scene.input.DragEvent; @@ -43,11 +55,14 @@ import javafx.scene.input.TransferMode; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.CornerRadii; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.stage.Stage; +import javafx.util.Duration; +import javafx.util.StringConverter; /** * @@ -70,29 +85,197 @@ public class JaceUIController { @FXML private ImageView appleScreen; + @FXML + private BorderPane controlOverlay; + + @FXML + private Slider speedSlider; + + @FXML + private AnchorPane menuButtonPane; + + @FXML + private Button menuButton; + Computer computer; + private final BooleanProperty aspectRatioCorrectionEnabled = new SimpleBooleanProperty(false); + @FXML void initialize() { assert rootPane != null : "fx:id=\"rootPane\" was not injected: check your FXML file 'JaceUI.fxml'."; assert stackPane != null : "fx:id=\"stackPane\" was not injected: check your FXML file 'JaceUI.fxml'."; assert notificationBox != null : "fx:id=\"notificationBox\" was not injected: check your FXML file 'JaceUI.fxml'."; assert appleScreen != null : "fx:id=\"appleScreen\" was not injected: check your FXML file 'JaceUI.fxml'."; - appleScreen.fitWidthProperty().bind(rootPane.widthProperty()); + controlOverlay.setVisible(false); + menuButtonPane.setVisible(false); + NumberBinding aspectCorrectedWidth = rootPane.heightProperty().multiply(3.0).divide(2.0); + NumberBinding width = new When( + aspectRatioCorrectionEnabled.and(aspectCorrectedWidth.lessThan(rootPane.widthProperty())) + ).then(aspectCorrectedWidth).otherwise(rootPane.widthProperty()); + appleScreen.fitWidthProperty().bind(width); appleScreen.fitHeightProperty().bind(rootPane.heightProperty()); + appleScreen.setVisible(false); rootPane.setOnDragEntered(this::processDragEnteredEvent); rootPane.setOnDragExited(this::processDragExitedEvent); + rootPane.setBackground(new Background(new BackgroundFill(Color.BLACK, null, null))); + rootPane.setOnMouseMoved(this::showMenuButton); + rootPane.setOnMouseExited(this::hideControlOverlay); + menuButton.setOnMouseClicked(this::showControlOverlay); + controlOverlay.setOnMouseClicked(this::hideControlOverlay); + delayTimer.getKeyFrames().add(new KeyFrame(Duration.millis(3000), evt -> { + hideControlOverlay(null); + })); + } + + private void showMenuButton(MouseEvent evt) { + if (!evt.isPrimaryButtonDown() && !evt.isSecondaryButtonDown() && !controlOverlay.isVisible()) { + resetMenuButtonTimer(); + if (!menuButtonPane.isVisible()) { + menuButtonPane.setVisible(true); + FadeTransition ft = new FadeTransition(Duration.millis(500), menuButtonPane); + ft.setFromValue(0.0); + ft.setToValue(1.0); + ft.play(); + } + } + } + + Timeline delayTimer = new Timeline(); + private void resetMenuButtonTimer() { + delayTimer.playFromStart(); + } + + private void showControlOverlay(MouseEvent evt) { + if (!evt.isPrimaryButtonDown() && !evt.isSecondaryButtonDown()) { + delayTimer.stop(); + menuButtonPane.setVisible(false); + controlOverlay.setVisible(true); + FadeTransition ft = new FadeTransition(Duration.millis(500), controlOverlay); + ft.setFromValue(0.0); + ft.setToValue(1.0); + ft.play(); + } + } + + private void hideControlOverlay(MouseEvent evt) { + if (menuButtonPane.isVisible()) { + FadeTransition ft1 = new FadeTransition(Duration.millis(500), menuButtonPane); + ft1.setFromValue(1.0); + ft1.setToValue(0.0); + ft1.setOnFinished(evt1 -> menuButtonPane.setVisible(false)); + ft1.play(); + } + if (controlOverlay.isVisible()) { + FadeTransition ft2 = new FadeTransition(Duration.millis(500), controlOverlay); + ft2.setFromValue(1.0); + ft2.setToValue(0.0); + ft2.setOnFinished(evt1 -> controlOverlay.setVisible(false)); + ft2.play(); + } + } + + private double convertSpeedToRatio(Double setting) { + if (setting < 1.0) { + return 0.5; + } else if (setting == 1.0) { + return 1.0; + } else if (setting >= 10) { + return Double.MAX_VALUE; + } else { + double val = Math.pow(2.0, (setting - 1.0) / 1.5); + val = Math.floor(val * 2.0) / 2.0; + if (val > 2.0) { + val = Math.floor(val); + } + return val; + } + } + + private void connectControls(Stage primaryStage) { + connectButtons(controlOverlay); + if (computer.getKeyboard() != null) { + EventHandler keyboardHandler = computer.getKeyboard().getListener(); + primaryStage.setOnShowing(evt -> computer.getKeyboard().resetState()); + rootPane.setOnKeyPressed(keyboardHandler); + rootPane.setOnKeyReleased(keyboardHandler); + rootPane.setFocusTraversable(true); + } + speedSlider.setValue(1.0); + speedSlider.setMinorTickCount(0); + speedSlider.setMajorTickUnit(1); + speedSlider.setLabelFormatter(new StringConverter() { + @Override + public String toString(Double val) { + if (val < 1.0) { + return "Half"; + } else if (val >= 10.0) { + return "∞"; + } + double v = convertSpeedToRatio(val); + if (v != Math.floor(v)) { + return String.valueOf(v) + "x"; + } else { + return String.valueOf((int) v) + "x"; + } + } + + @Override + public Double fromString(String string) { + return 1.0; + } + }); + speedSlider.valueProperty().addListener((val, oldValue, newValue) -> setSpeed(newValue.doubleValue())); + } + + private void connectButtons(Node n) { + if (n instanceof Button) { + Button button = (Button) n; + Runnable action = Utility.getNamedInvokableAction(button.getText()); + button.setOnMouseClicked(evt -> action.run()); + } else if (n instanceof Parent) { + for (Node child : ((Parent) n).getChildrenUnmodifiable()) { + connectButtons(child); + } + } + } + + private void setSpeed(double speed) { + double speedRatio = convertSpeedToRatio(speed); + if (speedRatio > 100.0) { + Emulator.computer.getMotherboard().maxspeed = true; + Motherboard.cpuPerClock = 3; + } else { + if (speedRatio > 25) { + Motherboard.cpuPerClock = 2; + } else { + Motherboard.cpuPerClock = 1; + } + Emulator.computer.getMotherboard().maxspeed = false; + Emulator.computer.getMotherboard().speedRatio = (int) (speedRatio * 100); + } + Emulator.computer.getMotherboard().reconfigure(); + } + + public void toggleAspectRatio() { + setAspectRatioEnabled(aspectRatioCorrectionEnabled.not().get()); + } + + public void setAspectRatioEnabled(boolean enabled) { + aspectRatioCorrectionEnabled.set(enabled); } public void connectComputer(Computer computer, Stage primaryStage) { + if (computer == null) { + return; + } this.computer = computer; - appleScreen.setImage(computer.getVideo().getFrameBuffer()); - EventHandler keyboardHandler = computer.getKeyboard().getListener(); - primaryStage.setOnShowing(evt -> computer.getKeyboard().resetState()); - rootPane.setFocusTraversable(true); - rootPane.setOnKeyPressed(keyboardHandler); - rootPane.setOnKeyReleased(keyboardHandler); - rootPane.requestFocus(); + Platform.runLater(() -> { + connectControls(primaryStage); + appleScreen.setImage(computer.getVideo().getFrameBuffer()); + appleScreen.setVisible(true); + rootPane.requestFocus(); + }); } private void processDragEnteredEvent(DragEvent evt) { @@ -245,24 +428,25 @@ public class JaceUIController { public void removeMouseListener(EventHandler handler) { appleScreen.removeEventHandler(MouseEvent.ANY, handler); } - + Label currentNotification = null; + public void displayNotification(String message) { Label oldNotification = currentNotification; Label notification = new Label(message); currentNotification = notification; notification.setEffect(new DropShadow(2.0, Color.BLACK)); notification.setTextFill(Color.WHITE); - notification.setBackground(new Background(new BackgroundFill(Color.rgb(0,0,80, 0.7), new CornerRadii(5.0), new Insets(-5.0)))); - Application.invokeLater(() -> { + notification.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 80, 0.7), new CornerRadii(5.0), new Insets(-5.0)))); + Application.invokeLater(() -> { stackPane.getChildren().remove(oldNotification); stackPane.getChildren().add(notification); }); - - notificationExecutor.schedule(()->{ - Application.invokeLater(() -> { + + notificationExecutor.schedule(() -> { + Application.invokeLater(() -> { stackPane.getChildren().remove(notification); - }); + }); }, 4, TimeUnit.SECONDS); } } diff --git a/src/main/java/jace/apple2e/Apple2e.java b/src/main/java/jace/apple2e/Apple2e.java index 3164b7b..95e6f4b 100644 --- a/src/main/java/jace/apple2e/Apple2e.java +++ b/src/main/java/jace/apple2e/Apple2e.java @@ -19,6 +19,8 @@ package jace.apple2e; import jace.Emulator; +import jace.JaceApplication; +import jace.apple2e.softswitch.VideoSoftSwitch; import jace.cheat.Cheats; import jace.config.ClassSelection; import jace.config.ConfigurableField; @@ -40,21 +42,22 @@ import jace.hardware.massStorage.CardMassStorage; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; /** - * Apple2e is a computer with a 65c02 CPU, 128k of bankswitched ram, - * double-hires graphics, and up to seven peripheral I/O cards installed. Pause - * and resume are implemented by the Motherboard class. This class provides - * overall configuration of the computer, but the actual operation of the - * computer and its timing characteristics are managed in the Motherboard class. + * Apple2e is a computer with a 65c02 CPU, 128k of bankswitched ram, double-hires graphics, and up to seven peripheral + * I/O cards installed. Pause and resume are implemented by the Motherboard class. This class provides overall + * configuration of the computer, but the actual operation of the computer and its timing characteristics are managed in + * the Motherboard class. * * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ @@ -121,7 +124,7 @@ public class Apple2e extends Computer { return "Computer (Apple //e)"; } - private void reinitMotherboard() { + protected void reinitMotherboard() { if (motherboard != null && motherboard.isRunning()) { motherboard.suspend(); } @@ -134,16 +137,25 @@ public class Apple2e extends Computer { public void coldStart() { pause(); reinitMotherboard(); + RAM128k ram = (RAM128k) getMemory(); + ram.initMemoryPattern(ram.mainMemory); + ram.initMemoryPattern(ram.getAuxMemory()); for (SoftSwitches s : SoftSwitches.values()) { s.getSwitch().reset(); } getMemory().configureActiveMemory(); getVideo().configureVideoMode(); - for (Optional c : getMemory().getAllCards()) { - c.ifPresent(Card::reset); + try { + for (Optional c : getMemory().getAllCards()) { + c.ifPresent(Card::reset); + waitForVBL(); + } + } catch (InterruptedException ex) { + Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); + } finally { + getCpu().resume(); + reboot(); } - reboot(); - resume(); } public void reboot() { @@ -158,7 +170,9 @@ public class Apple2e extends Computer { public void warmStart() { boolean restart = pause(); for (SoftSwitches s : SoftSwitches.values()) { - s.getSwitch().reset(); + if (!(s.getSwitch() instanceof VideoSoftSwitch)) { + s.getSwitch().reset(); + } } getMemory().configureActiveMemory(); getVideo().configureVideoMode(); @@ -194,11 +208,11 @@ public class Apple2e extends Computer { @Override public final void reconfigure() { boolean restart = pause(); - + if (Utility.isHeadlessMode()) { joy1enabled = false; joy2enabled = false; - + } super.reconfigure(); @@ -216,9 +230,7 @@ public class Apple2e extends Computer { if (getMemory() == null) { try { currentMemory = (RAM128k) ramCard.getValue().getConstructor(Computer.class).newInstance(this); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException ex) { - Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); - } catch (IllegalArgumentException | InvocationTargetException ex) { + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException ex) { Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); } try { @@ -291,6 +303,9 @@ public class Apple2e extends Computer { getVideo().configureVideoMode(); getVideo().reconfigure(); Emulator.resizeVideo(); + if (JaceApplication.getApplication() != null) { + JaceApplication.getApplication().reconnectUIHooks(); + } getVideo().resume(); } catch (InstantiationException | IllegalAccessException ex) { Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); @@ -377,6 +392,35 @@ public class Apple2e extends Computer { // } private List hints = new ArrayList<>(); + List vblCallbacks = Collections.synchronizedList(new ArrayList<>()); + + public void waitForVBL() throws InterruptedException { + waitForVBL(0); + } + + public void waitForVBL(int count) throws InterruptedException { + Semaphore s = new Semaphore(0); + onNextVBL(s::release); + s.acquire(); + if (count > 1) { + waitForVBL(count - 1); + } + } + + public void onNextVBL(Runnable r) { + vblCallbacks.add(r); + } + + @Override + public void notifyVBLStateChanged(boolean state) { + super.notifyVBLStateChanged(state); + if (state) { + while (vblCallbacks != null && !vblCallbacks.isEmpty()) { + vblCallbacks.remove(0).run(); + } + } + } + ScheduledExecutorService animationTimer = new ScheduledThreadPoolExecutor(1); Runnable drawHints = () -> { if (getCpu().getProgramCounter() >> 8 != 0x0c6) { @@ -438,10 +482,10 @@ public class Apple2e extends Computer { private void enableHints() { if (hints.isEmpty()) { - hints.add(getMemory().observe(RAMEvent.TYPE.EXECUTE, 0x0FB63, (e)->{ - animationTimer.schedule(drawHints, 1, TimeUnit.SECONDS); - animationSchedule = - animationTimer.scheduleAtFixedRate(doAnimation, 1250, 100, TimeUnit.MILLISECONDS); + hints.add(getMemory().observe(RAMEvent.TYPE.EXECUTE, 0x0FB63, (e) -> { + animationTimer.schedule(drawHints, 1, TimeUnit.SECONDS); + animationSchedule + = animationTimer.scheduleAtFixedRate(doAnimation, 1250, 100, TimeUnit.MILLISECONDS); })); // Latch to the PRODOS SYNTAX CHECK parser /* @@ -475,4 +519,4 @@ public class Apple2e extends Computer { public String getShortName() { return "computer"; } -} \ No newline at end of file +} diff --git a/src/main/java/jace/apple2e/VideoNTSC.java b/src/main/java/jace/apple2e/VideoNTSC.java index 37c651f..172d497 100644 --- a/src/main/java/jace/apple2e/VideoNTSC.java +++ b/src/main/java/jace/apple2e/VideoNTSC.java @@ -57,14 +57,14 @@ public class VideoNTSC extends VideoDHGR { public boolean enableVideo7 = true; // Scanline represents 560 bits, divided up into 28-bit words int[] scanline = new int[20]; - static int[] divBy28 = new int[560]; + static public int[] divBy28 = new int[560]; static { for (int i = 0; i < 560; i++) { divBy28[i] = i / 28; } } - boolean[] colorActive = new boolean[80]; + protected boolean[] colorActive = new boolean[80]; int rowStart = 0; public VideoNTSC(Computer computer) { @@ -89,7 +89,7 @@ public class VideoNTSC extends VideoDHGR { static int currentMode = -1; @InvokableAction(name = "Toggle video mode", category = "video", - alternatives = "mode,color,b&w,monochrome", + alternatives = "Gfx mode;color;b&w;monochrome", defaultKeyMapping = {"ctrl+shift+g"}) public static void changeVideoMode() { VideoNTSC thiss = (VideoNTSC) Emulator.computer.video; diff --git a/src/main/java/jace/cheat/Cheats.java b/src/main/java/jace/cheat/Cheats.java index fb8f34c..27db3fc 100644 --- a/src/main/java/jace/cheat/Cheats.java +++ b/src/main/java/jace/cheat/Cheats.java @@ -41,7 +41,7 @@ public abstract class Cheats extends Device { super(computer); } - @InvokableAction(name = "Toggle Cheats", alternatives = "cheat", defaultKeyMapping = "ctrl+shift+m") + @InvokableAction(name = "Toggle Cheats", alternatives = "cheat;Plug-in", defaultKeyMapping = "ctrl+shift+m") public void toggleCheats() { cheatsActive = !cheatsActive; if (cheatsActive) { @@ -97,7 +97,7 @@ public abstract class Cheats extends Device { super.detach(); } - abstract void registerListeners(); + public abstract void registerListeners(); protected void unregisterListeners() { listeners.stream().forEach((l) -> { diff --git a/src/main/java/jace/cheat/MetaCheat.java b/src/main/java/jace/cheat/MetaCheat.java index 384b019..162f51c 100644 --- a/src/main/java/jace/cheat/MetaCheat.java +++ b/src/main/java/jace/cheat/MetaCheat.java @@ -134,7 +134,7 @@ public class MetaCheat extends Cheats { } @Override - void registerListeners() { + public void registerListeners() { } public void addCheat(DynamicCheat cheat) { diff --git a/src/main/java/jace/cheat/MontezumasRevengeCheats.java b/src/main/java/jace/cheat/MontezumasRevengeCheats.java index abd04b7..ecd9c32 100644 --- a/src/main/java/jace/cheat/MontezumasRevengeCheats.java +++ b/src/main/java/jace/cheat/MontezumasRevengeCheats.java @@ -70,7 +70,7 @@ public class MontezumasRevengeCheats extends Cheats { }; @Override - void registerListeners() { + public void registerListeners() { RAM memory = Emulator.computer.memory; if (repulsiveHack) { addCheat(RAMEvent.TYPE.WRITE, this::repulsiveBehavior, 0x1508, 0x1518); diff --git a/src/main/java/jace/config/Configuration.java b/src/main/java/jace/config/Configuration.java index 16fd22e..c45119d 100644 --- a/src/main/java/jace/config/Configuration.java +++ b/src/main/java/jace/config/Configuration.java @@ -49,6 +49,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Stream; import javafx.collections.ObservableList; import javafx.scene.control.TreeItem; import javafx.scene.image.Image; @@ -255,6 +256,12 @@ public class Configuration implements Reconfigurable { getChangedIcon().ifPresent(this::setGraphic); } } + + public Stream getTreeAsStream() { + return Stream.concat( + Stream.of(this), + children.stream().flatMap(ConfigNode::getTreeAsStream)); + } } public static ConfigNode BASE; public static EmulatorUILogic ui = Emulator.logic; @@ -301,7 +308,7 @@ public class Configuration implements Reconfigurable { node.setRawFieldValue(f.getName(), (Serializable) o); } continue; - } + } if (o == null) { continue; } @@ -494,7 +501,9 @@ public class Configuration implements Reconfigurable { newRoot.getChildren().stream().forEach((child) -> { String childName = child.toString(); ConfigNode oldChild = oldRoot.findChild(childName); - if (oldChild == null) {oldChild = oldRoot.findChild(child.id);} + if (oldChild == null) { + oldChild = oldRoot.findChild(child.id); + } // System.out.println("Applying settings for " + childName); applyConfigTree(child, oldChild); }); diff --git a/src/main/java/jace/core/Computer.java b/src/main/java/jace/core/Computer.java index 4c99ea1..cbce122 100644 --- a/src/main/java/jace/core/Computer.java +++ b/src/main/java/jace/core/Computer.java @@ -128,10 +128,18 @@ public abstract class Computer implements Reconfigurable { } public void deactivate() { - cpu.suspend(); - motherboard.suspend(); - video.suspend(); - mixer.detach(); + if (cpu != null) { + cpu.suspend(); + } + if (motherboard != null) { + motherboard.suspend(); + } + if (video != null) { + video.suspend(); + } + if (mixer != null) { + mixer.detach(); + } } @InvokableAction( @@ -161,7 +169,7 @@ public abstract class Computer implements Reconfigurable { name = "Warm boot", description = "Process user-initatiated reboot (ctrl+apple+reset)", category = "general", - alternatives = "reboot;reset;three-finger-salute", + alternatives = "reboot;reset;three-finger-salute;restart", defaultKeyMapping = {"Ctrl+Ignore Alt+Ignore Meta+Backspace", "Ctrl+Ignore Alt+Ignore Meta+Delete"}) public void invokeWarmStart() { warmStart(); @@ -185,7 +193,7 @@ public abstract class Computer implements Reconfigurable { return result; } - @InvokableAction(name = "Resume", description = "Resumes the computer if it was previously paused", alternatives = "unpause;unfreeze;resume", defaultKeyMapping = {"meta+shift+pause", "alt+shift+pause"}) + @InvokableAction(name = "Resume", description = "Resumes the computer if it was previously paused", alternatives = "unpause;unfreeze;resume;play", defaultKeyMapping = {"meta+shift+pause", "alt+shift+pause"}) public void resume() { doResume(); getRunningProperty().set(true); diff --git a/src/main/java/jace/core/Keyboard.java b/src/main/java/jace/core/Keyboard.java index e8c8fa5..e7cfb5c 100644 --- a/src/main/java/jace/core/Keyboard.java +++ b/src/main/java/jace/core/Keyboard.java @@ -18,6 +18,7 @@ */ package jace.core; +import jace.Emulator; import jace.apple2e.SoftSwitches; import jace.config.InvokableAction; import jace.config.Reconfigurable; @@ -68,6 +69,7 @@ public class Keyboard implements Reconfigurable { return "kbd"; } static byte currentKey = 0; + public boolean shiftPressed = false; public static void clearStrobe() { currentKey = (byte) (currentKey & 0x07f); @@ -102,6 +104,7 @@ public class Keyboard implements Reconfigurable { registerKeyHandler(new KeyHandler(code) { @Override public boolean handleKeyUp(KeyEvent e) { + Emulator.computer.getKeyboard().shiftPressed = e.isShiftDown(); if (action == null || !action.notifyOnRelease()) { return false; } @@ -125,6 +128,7 @@ public class Keyboard implements Reconfigurable { @Override public boolean handleKeyDown(KeyEvent e) { // System.out.println("Key down: "+method.toString()); + Emulator.computer.getKeyboard().shiftPressed = e.isShiftDown(); Object returnValue = null; try { if (method.getParameterCount() > 0) { @@ -248,6 +252,7 @@ public class Keyboard implements Reconfigurable { default: } + Emulator.computer.getKeyboard().shiftPressed = e.isShiftDown(); if (e.isShiftDown()) { c = fixShiftedChar(c); } diff --git a/src/main/java/jace/core/TimedDevice.java b/src/main/java/jace/core/TimedDevice.java index e2be07d..5aed2de 100644 --- a/src/main/java/jace/core/TimedDevice.java +++ b/src/main/java/jace/core/TimedDevice.java @@ -36,7 +36,8 @@ public abstract class TimedDevice extends Device { super(computer); setSpeed(cyclesPerSecond); } - @ConfigurableField(name = "Speed", description = "(in hertz)") + @ConfigurableField(name = "Speed", description = "(Percentage)") + public int speedRatio = 100; public long cyclesPerSecond = defaultCyclesPerSecond(); @ConfigurableField(name = "Max speed") public boolean maxspeed = false; @@ -170,6 +171,7 @@ public abstract class TimedDevice extends Device { @Override public void reconfigure() { + cyclesPerSecond = defaultCyclesPerSecond() * speedRatio / 100; if (cyclesPerSecond == 0) { cyclesPerSecond = defaultCyclesPerSecond(); } diff --git a/src/main/java/jace/core/Utility.java b/src/main/java/jace/core/Utility.java index e933542..1cc9162 100644 --- a/src/main/java/jace/core/Utility.java +++ b/src/main/java/jace/core/Utility.java @@ -18,6 +18,8 @@ */ package jace.core; +import jace.config.Configuration; +import jace.config.InvokableAction; import java.io.File; import java.io.InputStream; import java.lang.reflect.Field; @@ -36,15 +38,18 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import java.util.stream.Stream; import javafx.application.Platform; import javafx.geometry.Pos; import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; import javafx.scene.control.ContentDisplay; import javafx.scene.control.Label; import javafx.scene.effect.DropShadow; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.paint.Color; + /** * This is a set of helper functions which do not belong anywhere else. * Functions vary from introspection, discovery, and string/pattern matching. @@ -52,7 +57,9 @@ import javafx.scene.paint.Color; * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public class Utility { + static Reflections reflections = new Reflections("jace"); + public static Set findAllSubclasses(Class clazz) { return reflections.getSubTypesOf(clazz); } @@ -65,11 +72,11 @@ public class Utility { * * @param s * @param t - * @return Distance (higher is better) + * @return Distance (lower means a closer match, zero is identical) */ public static int levenshteinDistance(String s, String t) { if (s == null || t == null || s.length() == 0 || t.length() == 0) { - return -1; + return Integer.MAX_VALUE; } s = s.toLowerCase().replaceAll("[^a-zA-Z0-9\\s]", ""); @@ -95,8 +102,19 @@ public class Utility { } } } - return Math.max(m, n) - dist[m][n]; + return dist[m][n]; } + + /** + * Normalize distance based on longest string + * @param s + * @param t + * @return Similarity ranking, higher is better + */ + public static int adjustedLevenshteinDistance(String s, String t) { + return Math.max(s.length(), t.length()) - levenshteinDistance(s, t); + } + /** * Compare strings based on a tally of similar patterns found, using a fixed @@ -107,7 +125,7 @@ public class Utility { * @param c1 * @param c2 * @param width Search window size - * @return Overall similarity score (higher is beter) + * @return Overall similarity score (higher is better) */ public static double rankMatch(String c1, String c2, int width) { double score = 0; @@ -135,6 +153,7 @@ public class Utility { } private static boolean isHeadless = false; + public static void setHeadlessMode(boolean headless) { isHeadless = headless; } @@ -142,7 +161,7 @@ public class Utility { public static boolean isHeadlessMode() { return isHeadless; } - + public static Optional loadIcon(String filename) { if (isHeadless) { return Optional.empty(); @@ -181,6 +200,20 @@ public class Utility { return Optional.of(label); } + public static void confirm(String title, String message, Runnable accept) { + Platform.runLater(() -> { + Alert confirm = new Alert(Alert.AlertType.CONFIRMATION); + confirm.setContentText(message); + confirm.setTitle(title); + Optional response = confirm.showAndWait(); + response.ifPresent(b -> { + if (b.getButtonData().isDefaultButton()) { + (new Thread(accept)).start(); + } + }); + }); + } + // public static void runModalProcess(String title, final Runnable runnable) { //// final JDialog frame = new JDialog(Emulator.getFrame()); // final JProgressBar progressBar = new JProgressBar(); @@ -201,7 +234,6 @@ public class Utility { // frame.dispose(); // }).start(); // } - public static class RankingComparator implements Comparator { String match; @@ -215,8 +247,8 @@ public class Utility { @Override public int compare(String o1, String o2) { - double s1 = levenshteinDistance(match, o1); - double s2 = levenshteinDistance(match, o2); + double s1 = adjustedLevenshteinDistance(match, o1); + double s2 = adjustedLevenshteinDistance(match, o2); if (s2 == s1) { s1 = rankMatch(o1, match, 3) + rankMatch(o1, match, 2); s2 = rankMatch(o2, match, 3) + rankMatch(o2, match, 2); @@ -256,7 +288,7 @@ public class Utility { // System.out.println(match + "->" + c + ":" + l + " -- "+ m2 + "," + m3 + "," + "(" + (m2 + m3) + ")"); // } // double score = rankMatch(match, candidates.get(0), 2); - double score = levenshteinDistance(match, candidates.get(0)); + double score = adjustedLevenshteinDistance(match, candidates.get(0)); if (score > 1) { return candidates.get(0); } @@ -450,4 +482,47 @@ public class Utility { } return setChild(object, paths[paths.length - 1], value, hex); } + + static Map allActions = null; + + public static Map getAllInvokableActions() { + if (allActions == null) { + allActions = new HashMap<>(); + Configuration.BASE.getTreeAsStream().forEach((Configuration.ConfigNode node) -> { + for (Method m : node.subject.getClass().getMethods()) { + if (m.isAnnotationPresent(InvokableAction.class)) { + allActions.put(m.getAnnotation(InvokableAction.class), () -> { + try { + m.invoke(node.subject); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + Logger.getLogger(Utility.class.getName()).log(Level.SEVERE, null, ex); + } + }); + } + } + }); + } + return allActions; + } + + public static Runnable getNamedInvokableAction(String action) { + Map actions = getAllInvokableActions(); + List actionsList = new ArrayList(actions.keySet()); + actionsList.sort((a,b) -> Integer.compare(getActionNameMatch(action, a), getActionNameMatch(action, b))); +// for (InvokableAction a : actionsList) { +// String actionName = a.alternatives() == null ? a.name() : (a.name() + ";" + a.alternatives()); +// System.out.println("Score for " + action + " evaluating " + a.name() + ": " + getActionNameMatch(action, a)); +// } + return actions.get(actionsList.get(0)); + } + + private static int getActionNameMatch(String str, InvokableAction action) { + int nameMatch = levenshteinDistance(str, action.name()); + if (action.alternatives() != null) { + for (String alt : action.alternatives().split(";")) { + nameMatch = Math.min(nameMatch, levenshteinDistance(str, alt)); + } + } + return nameMatch; + } } diff --git a/src/main/java/jace/core/Video.java b/src/main/java/jace/core/Video.java index 1867a06..695bdae 100644 --- a/src/main/java/jace/core/Video.java +++ b/src/main/java/jace/core/Video.java @@ -132,13 +132,15 @@ public abstract class Video extends Device { public static int MIN_SCREEN_REFRESH = 15; Runnable redrawScreen = () -> { - if (computer.getRunningProperty().get()) { - visible.getPixelWriter().setPixels(0, 0, 560, 192, video.getPixelReader(), 0, 0); + if (visible != null && video != null) { +// if (computer.getRunningProperty().get()) { + screenDirty = false; + visible.getPixelWriter().setPixels(0, 0, 560, 192, video.getPixelReader(), 0, 0); +// } } }; public void redraw() { - screenDirty = false; javafx.application.Platform.runLater(redrawScreen); } diff --git a/src/main/resources/fxml/JaceUI.fxml b/src/main/resources/fxml/JaceUI.fxml index 11b09e6..d6580bf 100644 --- a/src/main/resources/fxml/JaceUI.fxml +++ b/src/main/resources/fxml/JaceUI.fxml @@ -1,19 +1,188 @@ - - - - - - - + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/styles/icons/aspect.png b/src/main/resources/styles/icons/aspect.png new file mode 100644 index 0000000000000000000000000000000000000000..67c025dc00f18530021c84e242ae99c7f43c33a3 GIT binary patch literal 724 zcmV;_0xSKAP)~4CAZ`Fi ztp7;ZjDRbU`WlFMMTWEz5t1K)ZRDTv%-GOyD}fOXY^ z&}INo147#YKm~*x06+#FwinW3otG%$fwzh9UwnYY#;m2?Ox90VcvizzwrEE3(vG;1+noyhECu z=8NDNc|Idt*#ghQ&7_a%`Z?gD0jAO=57$K{WgQ5OEQj}rjbh05m<)=bg$k=d+ar#o z2k@`jS`OPDk^zL$p5PopWoPx6RoN7^Jpk%A8IMf0y27^mtU{O`#!h-wHbob*-Iw$m zcmsSnoTaq-`;tJeYWuUnN_7CD4MeL05N#k@9e`*9(fR{+fgr5Ao6Efb0000126oEP)OgPG^oG{@mP2HUp3v0*vJtZ<5}Q3n<}+6w@az?lWXcfdp_1AqzP z@UJ>&z@(J{L~HME2P7N7IxytR0ANVQY;;g-7wEMyU@0Zp8t@9}%gO+tPsXUFthbbC z05Av~bxHaQ@YuBhz+XIyHz;_n-3TDvfwn=d}12#!0|wN#>3_V(|}q4BB9XIY8vn<0Fh9*)M`wE1HdV6 zC!9RSFW~l-NlV|^SWl-3h7n`HW)8$}0%K`)6#lGm`4cE7K|AqN6mc1( zsErUsZiTtoo1y7M1AuwpJO^a6igRE-)&PogLgW--fFgYQO|-O{2D}eIBor>S8k1m={u&#(G6`y}zXPi&9gV{_(FF|MVnS-U!m4PE z(AEbO`|oiXsJItM$RNeOL`DWE?nP2Y@#0=4XB4i=S8X5oNi-Wj1u8bgd>J6)RD|2M zaNQT~)ucQh_X~72fYw^M;u43#pA{|*2u&a~N&`HM(Fl}l1s?`{4wP#J9|n8~lxv0Z y*4ZqJ1Bie{B0;t|0T#0s4PZi*GGjT?~`LEK2y3R004F*F}k!G)WmF8Yz?*(BrQoM)de z_uhBD?#=Bp1Ahp)XU@$0@A;Z@=b@@p0pKIxZ$P`C34BnMUt*mrRYc@|;Ac;@F;$%_ z!=HBPE8BpT`zq4$mZ}K2CqQRQaxXHZ&IF7l_aZ}r2oRA`5!vaXQYrx=GA<%vQ^s>8 z7vOcwO0B@Bz-tKt(OCvwNJaoK0DR%-@H@bu)!Pmn2^{_{ zTms%}jWA#X=0Md=ZyDGZO#o*8x)^N7_rU3hp!4uGaNeW+BCt1>fVWEsq)J=|o(v)Y zc%=pbq(BpRA}`L&T)^(BQlo{9FE0FFjGBb7Z?TptU(Z=_!StAJqSGxEY%c!NbV#u#bYdAe3}#g>gMdbC@NFbCO7d}D>>sIR5!k4z%@m!9 z$Pgx_O|eD(N6PvzGMy@T0NdAes{Y!gECSATZ&Hgs*9fTU+hA>_ECP1iBz>+Cu(l0i z&djN00N9_fzx|XDJm#3pVa&1c|8^8sU{X{S5gFk|GPIc+NzrD^2gy*WLox*B1^))j zVG^M^Oj2~pO(FKeT@Ac=uDQ)Z5I0y_1P(lBGRv_OI=$!PJx4z zvkjGrx00*@nEAg@gMdP?CyD@I7;~Yx+JS`P5~k?dp8!mraIywL#9|8A5JJGA8aST{ zlV01Ph^zvx}IOZx0Od9$=W-ks6 z`YI+9-ER9F1m1PjJpuG5A;21czJP=U;KdxhnBt1v1uvwi3jIsq7N@ve39yC_u=4us z7YeGefRP9SMhp6V0JxB$a|~OupPT?|xY0HePSB+e&+1$Z9R9flj<(-n;B(u)iTOEC zp-wab*6`gp*5KIo-vtnG)}#N4qR~=>pvwnUz2K=b3q0(pGvnG&Re$j8`(LT*+u&?~ zjxzxx>2y?pz03@ECO}n%hXaE2J M07*qoM6N<$f{I3D8~^|S literal 0 HcmV?d00001 diff --git a/src/main/resources/styles/icons/fast.png b/src/main/resources/styles/icons/fast.png new file mode 100644 index 0000000000000000000000000000000000000000..7219fca50e09b57acc59435b0470e0a6774e520a GIT binary patch literal 1998 zcmbVN4NTNl9KS;m#o<61K?qwdagoSfd*58g@pXsZsfWjL4%`x5X&+FzyVkbAK{SFI zqnkK%x;Vrs<7+blGBF5l!iwCZV`+xtt z#g?)#z(34iB9R1G%qF`qCx~O}6ybeEYB;0z$?Wx_R6qb90ds3?{%4EXD8??S@1~Jd@7j4X~i; zm4-BvvGnqmuQ=txp3RCMbb&O1i9Ey%Xtu#qx z6}}9rQDYk23s50pNSw!C2_^|Fi)LN8U@qmtvjM`mvth{VM9fG#X;u&{Xs3F9-(ob{ zSUQVx3J08hVG?9XHfj}mtyZR#D@N6|S}}{8<8e0uEG7dixJFJNTGkdZ1CAV3z0>rn4~6HVla2{3V={Ga!ivXoGOxbwAn#Ih7K2QyQg5Yxi( z!bt~sau;tdgqlcrm9%}t;?T)#* zbFcSroxbL6>aU^ut7_hJEL+z7Mbb4172;w55^~m<7VZ~2d=)J3A*on5^f><3RG7aK>MpLV(ZzZ>XJs{Qt>j2^Alq}sX5^R)b(xL?+lReGi# zCVsRZJ#|UC{FA%Jx`dTam%bmXo`KfyYTLeB?R%p%sjPpXCgt)4UFU|t$hLItjeWis z;>!ZNZWb%@H=M~_RC0A=e00>qEsuM89JaITww39K46iMfwBCamQj=?c@b~nB^Gm}qpmF%58XD73>*Bexols`ZV z=mY0~8hy?~&6b2gl8uP;fwxZO_j3?10G>LPzi2oWw#-+@+K+RX0UN+6(DbOE{0val zGoS}dJ?bYf06OLWM z$LE?(P#YLguhTOII^p;(a9MgxY|s_(Tfp!J@H-22$|b~xHG&^4!dI3M8`ubbyQJ776tsng&!p+!CXLq|IGjJvm_t77WrC}7peu9N`m+y(eD0ZSzW z`0-tUuOi@z1d3Z%UHB2ujt#nwfSH8XcMV=uk#M3O8B@%hQ>XeS@p>A}0v>z>WCIWO z0w%z2F7RM0U_x=_zZuqakClMgmCp(u>;&wqYV;?&o5boiI0brIyu+JStY$zHI1rJp zQN3G>pyd>ZITA? o&awV`$J%Cb{rB|P4Ue+V9|yguqTv|iSpWb407*qoM6N<$f{8(jE&u=k literal 0 HcmV?d00001 diff --git a/src/main/resources/styles/icons/ide.png b/src/main/resources/styles/icons/ide.png new file mode 100644 index 0000000000000000000000000000000000000000..435b62052b8e9d33c7707b39643e8ba60e93b5d4 GIT binary patch literal 1238 zcmV;{1S$K8P)n+a8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H11XD>wK~#90?VC-AmQ@tTe`lJ~9KjK^nJmHzB2%cKO(|h!Ld#r= zeXZKH5CTC0QG!~9&?4GckR-GS%-W@yZk%x%!O4+YWHp5fVL?Utbz0o})}8y_=lkP6 zZ_OX>=05j1bAJE#-urx=5mn{?tfANm#MXiVu@i``1p{Iy5L?4}28qZRa363}v*`r& zGjLW_uQw81Rr?w+0=x=*3j73UVNY3-d9_+u&Ooe!Dd26&|da9Qm`U^oE31hYi4 z&hIlpR230f3ru0wkE?6IVc;}oSv0c|rE4j$8u%LcKvl2#14LvSa3E*A?|_Y}dcN0j zGX~ywYZiDUZNuqO19k=&GG_w7Gl2m=0Lx=TWdJV)#wwby5@2uO;O5wXIpB}mSvp?9 zg!chH_By|h4VV+&U8-eGxCG{A!R00HaQSHx0;uA4`ExGvRq) znb+4n19HHh&45odA@H}lWk8MaxlHKPD=`rn!Mucd-0406JdH8t2(YGL*gt^3(gt*5 z23Ww)3IFo90bd1j&1zF*JkxZ-fLQRIFdzr~OjGdD4Zys~i3T6l0K%`adhjbt!k;Pv zAI*Rq@Mqe9k77Uq{J9wLsx99Kh{zJ)6JTTD@CM9veqF(^Q}x!=3`3Qq^Co1ZmuW1o(@B^e$V&G6BJg^^l8Mq&KwoFoO8n8=5 zPTTg8e^5m31`c}NlO*r3bBo%jxdH|LO-4kx}5K zI+BY1N=!uV#XJ(b@Dn7!TP>!h+}oT1u9k_&3z(|2-TXTe33V7^*~ zhYldk zg+rCBTg%2BghI(t;6dO?OvSpd1FE{cbacnzK`52=+Z+`ik^RkO5Q?RH?Hn1U^Mwp> zjOco~{0-nu;MO|!7O~LtWWUqz03O9$b8qh()UK{z3L~b0_f+**U4erJ^bLypFEf{c zSyjE#MDSb&{Fl|GhoLB1Fd%jUv9(}8>;z)#Z+arED|*5+MF0Q*07*qoM6N<$g3q@; ANdN!< literal 0 HcmV?d00001 diff --git a/src/main/resources/styles/icons/info.png b/src/main/resources/styles/icons/info.png new file mode 100644 index 0000000000000000000000000000000000000000..444affed314642ba0fffef222c2c63afe7d5b83b GIT binary patch literal 1079 zcmV-71jze|P)!E3PP;XdMT1J{u76ljbJCD zl5`^J6F-_%0UH}X{sAEnO%-CJrD-(8pd^haC>DXd6f+n1_V(`fV`lff^#`vxZfD+m zGdufc-Ww$%xI`&63{;Ga0Yg9u7&HQKX`}{R07t+fa3~_@PW5x)0HxG(U;?NDBPp9Z z19pHd5jjY!TN5H;MbM+V1e^fkNKQ;2C2M+HGW-pC`nobaC^ge(YDc6oTmdr~8y@C} z8O*DknFD}PU@u3*gR%#VCb1>~NkSf}Smb7$$S?w2WYm@^E#+Fb*9iWsN zMStpgg;iry6HN{<@60;MzVq#P?C8Hy=m*wA*MpEx&Dv9Hpk^g@_!~M?cLrH%w-e98UOV z<0s4dgvqjh4X_>W|IYdOC{zJFb8esZ==we~qLdo;(2-YBsXRCUeF#}p#yoVcuBZ%o z=n&X8We07#p!A5l46RLGo2|D$5c(k^UmWz6hCzT^vI#%4JQj~Cx30s@uE#?cCKQ#+ z2M3@J2#QM0L)YXKl?#u~>FQqQ$U|3S6_vvW2Z*l2JIh;>95@q^^Lik(ldCHEumg7o zk6Sq^k{4TG93Z*^^E6j=vgK6jRj0lU$P)H886Sainv>zc=BRAiw58utQ;`&QQ)831 zjQ8m`X-T*6)R@Vbu63S0k@#b&9T#n`waX5#p;O4c$`ye@Zg7C{%wm=rWz3>~^dUU| z_8!>JM4fEeN88pwlLJJrI8*4_+g{-Ym=b?Ej3M+$OhitBdDmKH$h@(siI!)AqN}b~ z5_ae!+my7;6=_pJ-nLxcl$C7}KYRQa_>8?QZy9(={F;FsU?Obl|0hBK(noh)j)*FI zM5q{#8im;?m(Nlnm&?ZqMbloG$V%*Am*qE~@iD)4bZmKbGQ6zzk zlelxGC=@bj&O)+9ix31iR+zJ>a1ltfi7ZkI0?WlzAQe-%sEBT4CM7aC=`_teEuQz< z?J{%kbIv{IeQ);|vLQFa5hJX>^K41+nl>c1=RDfTa$`8O95&1T1 zAl7(e0C3K&1GWKA01p5zz|2M99pE(Zxy1f5OG!k63$PXV3XsU>$v2iGKvoC< zpbEUw1xYuT_kfi}0O-c#>*WI?%o6RIeXL;TfS-XHa5JLaV@Uv@170V6{tMs*;2~fY z`GTv#{s3@4a1b5g=R5GVGb#XdstXzI7l2d1z1~5;Lg0H0mm@zlvT#5Lv-*Hxh38vwA*!^yeWeck}!b&vSA``FG0=N8^er5t#wb8mn)$+Rf0@TZ`Vc8cir`4Az{;(Bo>0&RC5m)LFEN zx05v|GIR{E=zFWtR1%SE=v?1U)||{O;BRzv?FL;o)ERWnRV>D=0Io=V&M05M8tP=g zpv72*URSkK(F>nevc|H^QYl04=i13cvmmWD)-!ckD}dXaa|1@lsbv8$D3k*8TJ+4!_GPD z02E!$xhlGEv1rn2HxqrCanVv2-Itf8J;0sDx@G|Nt>zJI+ss*DZ3$c9KH!ou#wj1$ z^#SFyk=HWdsF`j&J&S#V^QxESoUlWmrC871#tZ!VBiQcfZXY*heJUxEyfZxd!H{az~LZYi#R{; z=~&Qn`&AJV??bQEeeVTpKwm5X8_)+yz7VF-qie65ncQa;cnbSe*IXuop)V28j=(?! zJ-U7~x3T1i46Hy;Z(l?ABccB4Ev;vT! zud!kWnYwsEXxBGaqOY;aPAVA$%K%jYj&(s&Ng()))H{j$#H}kJ_)b^D=!2x96R80c zDIoYy8#*}d!f%-xT5bS`$rm}mGqE9f184xT9DV3`H9_Jtz)2Qa+sB3w44@&PeqcNB z26`IXN#aw$KH6+)f&H-{MET87UHQ4)CUk|@_@k*o{(G7`o5QN12bhzM=7b5EmBc6~Gl4EF>8 z0O#Bo`U4Liz27qcAPB-G%RSrwMj~(^b literal 0 HcmV?d00001 diff --git a/src/main/resources/styles/icons/paste.png b/src/main/resources/styles/icons/paste.png new file mode 100644 index 0000000000000000000000000000000000000000..d7e13866e610fb493b5705be87316a4ea6a69e63 GIT binary patch literal 800 zcmV+*1K<3KP)iTf9LHG3Pd!DjgW0@q9CF09TA~J!4#YD z4@f9PquxTN(8yK_Jslz>8jV7B3nU~8A|a88h=^H7@pfd5%+C8b_no=V{Ux`U_i@g7 zzs$SuW9}VP6`%{u13v*B)Akj30PIjztXluzy$`NT`vPpS5|9B`iHr5X1*>2gFl`mA zLCk9IEVWU*1MUJntHeXt2%H7hjM#3KRl*_%4{%OZA6q3|2@&}STy7#4IsX4MLR)&DT{>T zM(P8+3-Bfh@GiicSOx4Bk;4{&Vim9ncqk$>7Qwuq@9VT4FjMD!K$#mM3vL2;D(p80 zOb%JM2%J~d+f@oGb0cKIOI5vBb|(?}w!-QO;FgF`MMA87z#x4IrTc_f1-t@24J@l9 zAyxrzfuk5mh*bcpx`2U%Xa#_cgs25TeF@PEfO-;IApojNXpI1g-^Ax)dxB;AsJvhUCTc2fo9*~U9ueDRAE5W^}(8AIR%1|1b7$VO%mW; zfHz5icLClc0p108lLUAd;7t5@c;k-8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H10kla(K~#90<=efD)ld+C(POl9K}1AA2}lqUPy}V{R`7_)D1Z)# zA)^Cw0s=%t2POe&wGz^M_|6}DrOE8Ezau%ceczokxWY%gKKGv0gC}^0ANYA^zT+(( zU$parhd;;ud#;NBzTA3;zs=_;fR}e3l!}07XT<-Rr|0f{;NQ<_+qUZi_g{J*MO=FX zfP94I=O_Tf5mo>&7-1CvLlIU2Fc4ui0K*U>02qW21;7x5NB{;PL<7(qAp?NM2w4C$ zMaTr8Awo6)%@8U8XoOG&Kof*Y02&}v15k|60f0h;E&voEbON9Np&J0n2ps`PMCb}Y z5<+JH5)iH;moGY}ZQI~$#PSIMaKeX(#oa%F+viop(n$azXA;1@{seCDK4S3%fSY~a z-$X6%4iFkoZ-DT4`U0fH(-R;qo_+wS@$>>nkEahnNjyCO%HpX9D2=BUpgf*BfEMx8 z0JMoGAD~q{xd83r$pdH^PYyuac;W$C#}fJzW_#ztbWF)mc0M~002ovPDHLk FV1m;j-HiYM literal 0 HcmV?d00001 diff --git a/src/main/resources/styles/icons/plugin.png b/src/main/resources/styles/icons/plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..f6a01ef6cfb047dab10a061045739a1e0d360aad GIT binary patch literal 1290 zcmV+l1@-!gP)cD|4nIPwJiuO)?!iVqD4^` zA}T6w+$b)Tg1U8~QbZKGbYT^<6BVT3qKl}AB5A4GT0yN=TWzX|No$q zAv5#dz2|i@G#?!1!QA_O?|k##dH3GA&m?gnqoWK`GwU_8)n;~i8T_7bGr-JlGqW9L zHUS(5_5tH&_LrFrnc2-=TCR|k6u@F&2cRZ#78nFNQZ+pzHTh29qa4Z4jOSaOw5I`E z3MMaL8d%rr#H*271MZ9H(*fL(sOyGk1I%n8uqv|q+E`t0q+9|0gc=ZoOA>Tlkz&9h zLS>J^t^{3IL>nOKH1Km|_u*JwZ=@E%uOj*!0X|LCbwjEFTO;~BCFxY6t{ajJkn|<6 zy+D`uBz=&g=?%*UH1Pyx*8|@GJx!X912;%Ink49qh8i^4H5Zcy-vyBq4Zi!{QO6Z72AG)v8yhrP?V(c! zysm)ffNL9kf0LQbnF-KL81M|RANUR!k(7IUZ)P396ToXUH**4bMAFAOxx0aDfF(0N zmjHv3+-@l&EK*c4O{nIhz*zQKdprp&BmClzQvCAX2YdpI0H=W~>cpkl`PE`T(k}%y z>!Q|xxsFpnMIO)Y45ciKmxx22$wT`Y@Vhr*yBRR%P1tS*jCvE!8{kb?GUjgqZ^C&4 zya`K&`z_E;Bf&G07I>4k(-qKTW*4*p0870IySxcY#(Ln#W{t9G`Uc=eU=3k9{WY)~ z7?O0(>Q&MI_gfhUfr}dKS1{lK!rDeXjvs(kGy1eVsiN^Vuwa(t*8n?na{LXfZ&w2Z z>;WDmtPtG^ya7xV1PBYsQS1a)l_*|0j zGNZ86DNyTx&z$tUVyA}zTMO(e>n4ApIv;T?)qoFy&VpO(`Up$ptxjIR(bSRP7D>}Z zI!HP~u{{N4VtkywvPc64BlTX+!F@Yn&d`YGbKpu312#q^-$~eH z8KZY^PK}ER8@cxq258m(zX7iTU0#!+Z4m}Mo1nKgLazecTK&am`98^jDPVbllUYW% zk7M;V#nzpAkvb0O0$$3~Fxy4)Ca^F;@4AG(v%+IZ>|ryzD@VI02?Z(z?gl{GEl`bv zGM9OuAuRk+b>aT}a8`ki?Gho=FJ#`njZ zG%9Ac*oh19GhjE3-vSv1{&CXkcj5tp0jVc83fwR0-+B!sjT4ri>b7YD0e=vdyB9@r z%nHk5DPeZi2V9-SRW--8&+n7KTfmEw{>&w2HUMk}Hf5TXjORoaW58kH*DMZaF(PR) zHpzzPR2>dBZD>{@n9-A+(D5=(=zPOvyL>z2Kb-F&`W`PRGynhq07*qoM6N<$f|<)d Ad;kCd literal 0 HcmV?d00001 diff --git a/src/main/resources/styles/icons/reboot.png b/src/main/resources/styles/icons/reboot.png new file mode 100644 index 0000000000000000000000000000000000000000..93f228a3eab305f567da4162461555342770a387 GIT binary patch literal 1239 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 DF(-ss literal 0 HcmV?d00001 diff --git a/src/main/resources/styles/icons/sound.png b/src/main/resources/styles/icons/sound.png new file mode 100644 index 0000000000000000000000000000000000000000..c8e743de47326e2ceef05a7fb60ac7f9e53ddd27 GIT binary patch literal 1331 zcmV-31H)M`N(VqhdPU?e5$Vh$ zUa0_x$PVBH@Ck5CLJOxCg7ccp$(=-A+1H7)PlS$*2=?u7znaKn60lR_E zMPy0RpiKiHA~g|Nks-v_z~PAY+khWLq(4zqRc+`2Tn@YsOaTfU1S%nvwirV}N&OjJp8X2~0*n*bB^Q8Gwl771)+jAFF`xBOvSps!az_CwT>)&#|vL zz^f5`?P)527|ACB0CWLsfX+I0&qegNsVM+rCLbFBuqklE8wJ+av41k8zw^M#h5|^C zd@KOKeBe*R_fue5gx|v%8Mv1&)M(yFNDnDU(kEU@ou&xDXNRGd=wFH zAjEHLi2dqJ05T?@7(k%kN3m~klfb%=OW|9?{*BZCa!5Wk0KfkYz`sV!bHJ5`{r!gR zUqF|tQWcRdyhlgBpIklZ0T#!wOC=&Azzx8#s$Ov0Rdt_;oWq-7NucR6U;yv556XVL zcZr^Wt`k@f3<7K?+rOFH835b~9QAE}12$sZ<$YW0^>-ZTHf#qC+e3aEV+8^*WY`V? z3qmf%V}|_}!*-2fcOIBW3z9bh;1y@W=J^MKd4|t@hV4n9!?BYPSgQ+D5>wSF;LVWc zwZJYzzaQ^)IVe{F_d2l^_`vYLr7!?|c?@^~uX^OlLn5*?s8H35z*~mxazl6A(5)&I z09Bn*)u(_vfWMvQ3h;#C_O_v`8MU+Q`N8K$ViygbOz=UJBl$l}ySXfv9zIg_+;E|BFK~)_N zDnz6TJYd-VXy}$0x^JKylcA4+xs8|%LGh=<2k@&mjhYS@wIDJAY=|h0=z zK>%i}2_wATiS6cCbZks0@WioHc9A%rBGq&g|fCaDAv+IO2=ObG40 zQ8EUE_TMV&K$xAIrJcdofE5}1E5M;TiH`&QN#d62i;-J_FC$EbfQ_npJW1R_6-DdF z31+VBfLA!#46IF>Jd_B4s*VD$@IQnx;Bla)s;_B^?}ea5A5Be*|MApv;44*im(Y{P pD;t2kr8Jj7qvR5RRu7=n@*nSb2LpM*2yFlW002ovPDHLkV1hq(PmTZp literal 0 HcmV?d00001 diff --git a/src/main/resources/styles/style.css b/src/main/resources/styles/style.css index 884c262..4b40374 100644 --- a/src/main/resources/styles/style.css +++ b/src/main/resources/styles/style.css @@ -3,7 +3,7 @@ } .setting-row { - -fx-padding: 5 0 0 4; + -fx-padding: 5 0 0 4; } .setting-label, .setting-keyboard-shortcut { @@ -19,3 +19,30 @@ -fx-font-size: 9pt; -fx-font-family: "Courier New"; } + +.menuButton { + -fx-font-size:16pt; + -fx-border-radius: 10px; + -fx-background-radius: 10px; +} + +.menuButton, .uiActionButton, .uiSpeedSlider ImageView, .uiSpeedSlider Slider, .uiSpeedSlider AnchorPane { + -fx-background-color: rgba(0, 0, 0, 0.75); + -fx-text-fill: #a0FFa0 +} + +.uiActionButton ImageView, .uiSpeedSlider ImageView { + -fx-effect: dropshadow(gaussian , rgba(128,255,128,0.75) , 2,1.0,0,0); +} + +.uiSpeedSlider AnchorPane { + -fx-padding: 0 5 0 5 +} + +.uiSpeedSlider Slider { + -fx-padding: 18 0 10 0 +} + +.uiSpeedSlider Slider NumberAxis { + -fx-tick-label-fill: #80ff80 +} \ No newline at end of file