Backported changes from Lawless Legends app experience; namely better boot behavior and also UI Controls Overlay. :)
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<KeyEvent> 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<Double>() {
|
||||
@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<KeyEvent> 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<MouseEvent> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<Card> c : getMemory().getAllCards()) {
|
||||
c.ifPresent(Card::reset);
|
||||
try {
|
||||
for (Optional<Card> 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<RAMListener> hints = new ArrayList<>();
|
||||
|
||||
List<Runnable> 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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) -> {
|
||||
|
@ -134,7 +134,7 @@ public class MetaCheat extends Cheats {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerListeners() {
|
||||
public void registerListeners() {
|
||||
}
|
||||
|
||||
public void addCheat(DynamicCheat cheat) {
|
||||
|
@ -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);
|
||||
|
@ -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<ConfigNode> 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);
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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<Class> 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<Image> 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<ButtonType> 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> {
|
||||
|
||||
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<InvokableAction, Runnable> allActions = null;
|
||||
|
||||
public static Map<InvokableAction, Runnable> 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<InvokableAction, Runnable> actions = getAllInvokableActions();
|
||||
List<InvokableAction> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,188 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.image.*?>
|
||||
<?import javafx.scene.canvas.*?>
|
||||
<?import java.lang.*?>
|
||||
<?import java.util.*?>
|
||||
<?import javafx.scene.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Slider?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.TilePane?>
|
||||
|
||||
<AnchorPane id="AnchorPane" fx:id="rootPane" prefHeight="384.0" prefWidth="560.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jace.JaceUIController">
|
||||
<AnchorPane id="AnchorPane" fx:id="rootPane" prefHeight="384.0" prefWidth="560.0" style="-fx-background-color: black;" stylesheets="@../styles/style.css" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jace.JaceUIController">
|
||||
<children>
|
||||
<StackPane fx:id="stackPane" prefHeight="384.0" prefWidth="560.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<children>
|
||||
<ImageView fx:id="appleScreen" fitHeight="384.0" fitWidth="560.0" pickOnBounds="true" />
|
||||
<ImageView fx:id="appleScreen" fitHeight="384.0" fitWidth="560.0" pickOnBounds="true" style="-fx-background-color: BLACK;" />
|
||||
<HBox fx:id="notificationBox" alignment="BOTTOM_RIGHT" blendMode="SCREEN" fillHeight="false" maxHeight="45.0" mouseTransparent="true" prefHeight="45.0" prefWidth="560.0" StackPane.alignment="BOTTOM_CENTER" />
|
||||
<AnchorPane fx:id="menuButtonPane" prefHeight="200.0" prefWidth="200.0">
|
||||
<children>
|
||||
<Button fx:id="menuButton" layoutX="494.0" layoutY="14.0" mnemonicParsing="false" styleClass="menuButton" text="☰" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="14.0" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<BorderPane fx:id="controlOverlay" visible="false">
|
||||
<center>
|
||||
<HBox maxHeight="64.0" prefHeight="64.0" styleClass="uiSpeedSlider" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<Pane prefHeight="200.0" prefWidth="200.0" HBox.hgrow="SOMETIMES" />
|
||||
<AnchorPane>
|
||||
<children>
|
||||
<ImageView layoutX="2.0" layoutY="2.0">
|
||||
<image>
|
||||
<Image url="@../styles/icons/slow.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<Slider fx:id="speedSlider" blockIncrement="1.0" majorTickUnit="5.0" max="10.0" minorTickCount="5" prefHeight="64.0" prefWidth="300.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" value="1.0" HBox.hgrow="ALWAYS">
|
||||
<HBox.margin>
|
||||
<Insets />
|
||||
</HBox.margin>
|
||||
<padding>
|
||||
<Insets top="18.0" />
|
||||
</padding>
|
||||
</Slider>
|
||||
<AnchorPane>
|
||||
<children>
|
||||
<ImageView layoutX="2.0" layoutY="2.0">
|
||||
<image>
|
||||
<Image url="@../styles/icons/fast.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<Pane prefHeight="200.0" prefWidth="200.0" HBox.hgrow="SOMETIMES" />
|
||||
</children>
|
||||
</HBox>
|
||||
</center>
|
||||
<top>
|
||||
<HBox fillHeight="false" nodeOrientation="LEFT_TO_RIGHT" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<TilePane hgap="5.0" nodeOrientation="LEFT_TO_RIGHT" vgap="5.0" HBox.hgrow="SOMETIMES">
|
||||
<children>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Info">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/info.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Config">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/config.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
</TilePane>
|
||||
<TilePane alignment="TOP_RIGHT" hgap="5.0" vgap="5.0" HBox.hgrow="ALWAYS">
|
||||
<children>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="IDE">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/ide.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Inspect">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/inspect.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button alignment="TOP_LEFT" contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Plug-in" TilePane.alignment="TOP_RIGHT">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/plugin.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
</TilePane>
|
||||
</children>
|
||||
</HBox>
|
||||
</top>
|
||||
<bottom>
|
||||
<HBox fillHeight="false" nodeOrientation="LEFT_TO_RIGHT" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<TilePane hgap="5.0" nodeOrientation="LEFT_TO_RIGHT" vgap="5.0" HBox.hgrow="SOMETIMES">
|
||||
<children>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Fullscreen">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/fullscreen.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Aspect">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/aspect.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Gfx Mode">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/screenshot.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
</TilePane>
|
||||
<TilePane alignment="TOP_RIGHT" hgap="5.0" vgap="5.0" HBox.hgrow="ALWAYS">
|
||||
<children>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Play">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/play.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button alignment="TOP_LEFT" contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Restart" TilePane.alignment="TOP_RIGHT">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/reboot.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
</TilePane>
|
||||
</children>
|
||||
</HBox>
|
||||
</bottom>
|
||||
<StackPane.margin>
|
||||
<Insets />
|
||||
</StackPane.margin>
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</padding>
|
||||
</BorderPane>
|
||||
</children>
|
||||
</StackPane>
|
||||
</children>
|
||||
|
BIN
src/main/resources/styles/icons/aspect.png
Normal file
After Width: | Height: | Size: 724 B |
BIN
src/main/resources/styles/icons/brun.png
Normal file
After Width: | Height: | Size: 976 B |
BIN
src/main/resources/styles/icons/config.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/main/resources/styles/icons/fast.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src/main/resources/styles/icons/fullscreen.png
Normal file
After Width: | Height: | Size: 914 B |
BIN
src/main/resources/styles/icons/ide.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/main/resources/styles/icons/info.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/main/resources/styles/icons/inspect.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/main/resources/styles/icons/paste.png
Normal file
After Width: | Height: | Size: 800 B |
BIN
src/main/resources/styles/icons/play.png
Normal file
After Width: | Height: | Size: 567 B |
BIN
src/main/resources/styles/icons/plugin.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/main/resources/styles/icons/reboot.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/main/resources/styles/icons/rewind.png
Normal file
After Width: | Height: | Size: 701 B |
BIN
src/main/resources/styles/icons/screenshot.png
Normal file
After Width: | Height: | Size: 959 B |
BIN
src/main/resources/styles/icons/slow.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
src/main/resources/styles/icons/sound.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
@ -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
|
||||
}
|