Backported changes from Lawless Legends app experience; namely better boot behavior and also UI Controls Overlay. :)

This commit is contained in:
Brendan Robert 2018-01-12 23:27:56 -06:00
parent dba57e6e89
commit 0c0b2c107c
33 changed files with 670 additions and 116 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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";
}
}
}

View File

@ -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;

View File

@ -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) -> {

View File

@ -134,7 +134,7 @@ public class MetaCheat extends Cheats {
}
@Override
void registerListeners() {
public void registerListeners() {
}
public void addCheat(DynamicCheat cheat) {

View File

@ -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);

View File

@ -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);
});

View File

@ -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);

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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
}