mirror of
https://github.com/badvision/lawless-legends.git
synced 2024-06-09 23:29:37 +00:00
Merge branch 'master' of https://github.com/badvision/lawless-legends
This commit is contained in:
commit
bdefe148a8
|
@ -29,6 +29,14 @@
|
|||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.8bitbunch</groupId>
|
||||
<artifactId>lawlesslegends</artifactId>
|
||||
<version>3.0-SNAPSHOT</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<annotationProcessors>jace.config.InvokableActionAnnotationProcessor</annotationProcessors>
|
||||
</configuration>
|
||||
<version>3.11.0</version>
|
||||
</plugin>
|
||||
|
@ -113,18 +121,6 @@
|
|||
}
|
||||
</moduleInfoSource>
|
||||
</module>
|
||||
<module>
|
||||
<artifact>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<version>0.10.2</version>
|
||||
</artifact>
|
||||
<moduleInfoSource>
|
||||
module reflections {
|
||||
exports org.reflections;
|
||||
}
|
||||
</moduleInfoSource>
|
||||
</module>
|
||||
</modules>
|
||||
<overwriteExistingFiles>true</overwriteExistingFiles>
|
||||
</configuration>
|
||||
|
|
|
@ -72,6 +72,14 @@ public class Emulator {
|
|||
return getInstance().computer;
|
||||
}
|
||||
|
||||
public static void whileSuspended(Consumer<LawlessComputer> action) {
|
||||
withComputer(c->c.getMotherboard().whileSuspended(()->action.accept(c)));
|
||||
}
|
||||
|
||||
public static <T> T whileSuspended(Function<LawlessComputer, T> action, T defaultValue) {
|
||||
return withComputer(c->c.getMotherboard().whileSuspended(()->action.apply(c), defaultValue), defaultValue);
|
||||
}
|
||||
|
||||
public static void withComputer(Consumer<LawlessComputer> c) {
|
||||
LawlessComputer computer = getComputer();
|
||||
if (computer != null) {
|
||||
|
|
|
@ -209,14 +209,13 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
alternatives = "Execute program;Load binary;Load program;Load rom;Play single-load game",
|
||||
defaultKeyMapping = "ctrl+shift+b")
|
||||
public static void runFile() {
|
||||
Emulator.withComputer(c->
|
||||
c.getMotherboard().whileSuspended(()->{
|
||||
FileChooser select = new FileChooser();
|
||||
File binary = select.showOpenDialog(LawlessLegends.getApplication().primaryStage);
|
||||
if (binary != null) {
|
||||
runFileNamed(binary);
|
||||
}
|
||||
}));
|
||||
Emulator.whileSuspended(c-> {
|
||||
FileChooser select = new FileChooser();
|
||||
File binary = select.showOpenDialog(LawlessLegends.getApplication().primaryStage);
|
||||
if (binary != null) {
|
||||
runFileNamed(binary);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void runFileNamed(File binary) {
|
||||
|
@ -244,13 +243,13 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
in.read(data);
|
||||
}
|
||||
|
||||
Emulator.withComputer(c->c.getMotherboard().whileSuspended(()->{
|
||||
Emulator.whileSuspended(c-> {
|
||||
RAM ram = c.getMemory();
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
ram.write(address + i, data[i], false, true);
|
||||
}
|
||||
c.getCpu().setProgramCounter(address);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@InvokableAction(
|
||||
|
@ -311,22 +310,20 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
}
|
||||
File outFile = new File("screen_" + type + "_a" + Integer.toHexString(start) + "_" + timestamp);
|
||||
try (FileOutputStream out = new FileOutputStream(outFile)) {
|
||||
Emulator.withComputer(c -> {
|
||||
Emulator.whileSuspended(c -> {
|
||||
RAM128k ram = (RAM128k) c.getMemory();
|
||||
c.getMotherboard().whileSuspended(() -> {
|
||||
try {
|
||||
if (dres) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
out.write(ram.getAuxVideoMemory().readByte(start + i));
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (dres) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
out.write(ram.getMainMemory().readByte(start + i));
|
||||
out.write(ram.getAuxVideoMemory().readByte(start + i));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logger.getLogger(EmulatorUILogic.class.getName()).log(Level.SEVERE, "Error writing screenshot", e);
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < len; i++) {
|
||||
out.write(ram.getMainMemory().readByte(start + i));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logger.getLogger(EmulatorUILogic.class.getName()).log(Level.SEVERE, "Error writing screenshot", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
System.out.println("Wrote screenshot to " + outFile.getAbsolutePath());
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.concurrent.Executors;
|
|||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
@ -259,8 +260,8 @@ public class JaceUIController {
|
|||
|
||||
private void connectButtons(Node n) {
|
||||
if (n instanceof Button button) {
|
||||
Runnable action = Utility.getNamedInvokableAction(button.getText());
|
||||
button.setOnMouseClicked(evt -> action.run());
|
||||
Function<Boolean, Boolean> action = Utility.getNamedInvokableAction(button.getText());
|
||||
button.setOnMouseClicked(evt -> action.apply(false));
|
||||
} else if (n instanceof Parent parent) {
|
||||
parent.getChildrenUnmodifiable().forEach(child -> connectButtons(child));
|
||||
}
|
||||
|
@ -378,17 +379,15 @@ public class JaceUIController {
|
|||
});
|
||||
icon.setOnDragDropped(event -> {
|
||||
System.out.println("Dropping media on " + icon.getText());
|
||||
Emulator.withComputer(c -> {
|
||||
c.getMotherboard().whileSuspended(() -> {
|
||||
try {
|
||||
consumer.insertMedia(media, media.files.get(0));
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(JaceUIController.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
event.setDropCompleted(true);
|
||||
event.consume();
|
||||
Emulator.whileSuspended(c-> {
|
||||
try {
|
||||
consumer.insertMedia(media, media.files.get(0));
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(JaceUIController.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
});
|
||||
event.setDropCompleted(true);
|
||||
event.consume();
|
||||
endDragEvent();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -426,7 +426,7 @@ public class VideoDHGR extends Video {
|
|||
|
||||
protected void displayDoubleHires(WritableImage screen, int xOffset, int y, int rowAddress) {
|
||||
// Skip odd columns since this does two at once
|
||||
if ((xOffset & 0x01) == 1) {
|
||||
if ((xOffset & 0x01) == 1 || xOffset < 0) {
|
||||
return;
|
||||
}
|
||||
int b1 = ((RAM128k) computer.getMemory()).getAuxVideoMemory().readByte(rowAddress + xOffset );
|
||||
|
@ -452,7 +452,7 @@ public class VideoDHGR extends Video {
|
|||
|
||||
protected void displayHires(WritableImage screen, int xOffset, int y, int rowAddress) {
|
||||
// Skip odd columns since this does two at once
|
||||
if ((xOffset & 0x01) == 1) {
|
||||
if ((xOffset & 0x01) == 1 || xOffset < 0) {
|
||||
return;
|
||||
}
|
||||
int b1 = 0x0ff & ((RAM128k) computer.getMemory()).getMainMemory().readByte(rowAddress + xOffset);
|
||||
|
@ -465,6 +465,7 @@ public class VideoDHGR extends Video {
|
|||
}
|
||||
|
||||
protected void displayLores(WritableImage screen, int xOffset, int y, int rowAddress) {
|
||||
if (xOffset < 0) return;
|
||||
int c1 = ((RAM128k) computer.getMemory()).getMainMemory().readByte(rowAddress + xOffset) & 0x0FF;
|
||||
if ((y & 7) < 4) {
|
||||
c1 &= 15;
|
||||
|
@ -492,6 +493,7 @@ public class VideoDHGR extends Video {
|
|||
}
|
||||
|
||||
protected void displayDoubleLores(WritableImage screen, int xOffset, int y, int rowAddress) {
|
||||
if (xOffset < 0) return;
|
||||
int c1 = ((RAM128k) computer.getMemory()).getAuxVideoMemory().readByte(rowAddress + xOffset) & 0x0FF;
|
||||
int c2 = ((RAM128k) computer.getMemory()).getMainMemory().readByte(rowAddress + xOffset) & 0x0FF;
|
||||
if ((y & 7) < 4) {
|
||||
|
@ -554,7 +556,7 @@ public class VideoDHGR extends Video {
|
|||
|
||||
protected void displayText(WritableImage screen, int xOffset, int y, int rowAddress) {
|
||||
// Skip odd columns since this does two at once
|
||||
if ((xOffset & 0x01) == 1) {
|
||||
if ((xOffset & 0x01) == 1 || xOffset < 0) {
|
||||
return;
|
||||
}
|
||||
int yOffset = y & 7;
|
||||
|
@ -571,7 +573,7 @@ public class VideoDHGR extends Video {
|
|||
|
||||
protected void displayText80(WritableImage screen, int xOffset, int y, int rowAddress) {
|
||||
// Skip odd columns since this does two at once
|
||||
if ((xOffset & 0x01) == 1) {
|
||||
if ((xOffset & 0x01) == 1 || xOffset < 0) {
|
||||
return;
|
||||
}
|
||||
int yOffset = y & 7;
|
||||
|
|
|
@ -143,6 +143,7 @@ public class VideoNTSC extends VideoDHGR {
|
|||
|
||||
@Override
|
||||
protected void showBW(WritableImage screen, int x, int y, int dhgrWord) {
|
||||
if (x < 0) return;
|
||||
int pos = divBy28[x];
|
||||
if (rowStart < 0) {
|
||||
rowStart = pos;
|
||||
|
@ -153,6 +154,7 @@ public class VideoNTSC extends VideoDHGR {
|
|||
|
||||
@Override
|
||||
protected void showDhgr(WritableImage screen, int x, int y, int dhgrWord) {
|
||||
if (x < 0) return;
|
||||
int pos = divBy28[x];
|
||||
if (rowStart < 0) {
|
||||
rowStart = pos;
|
||||
|
@ -163,6 +165,7 @@ public class VideoNTSC extends VideoDHGR {
|
|||
|
||||
@Override
|
||||
protected void displayLores(WritableImage screen, int xOffset, int y, int rowAddress) {
|
||||
if (xOffset < 0) return;
|
||||
int data = ((RAM128k) computer.getMemory()).getMainMemory().readByte(rowAddress + xOffset) & 0x0FF;
|
||||
int pos = xOffset >> 1;
|
||||
if (rowStart < 0) {
|
||||
|
@ -193,6 +196,7 @@ public class VideoNTSC extends VideoDHGR {
|
|||
|
||||
@Override
|
||||
protected void displayDoubleLores(WritableImage screen, int xOffset, int y, int rowAddress) {
|
||||
if (xOffset < 0) return;
|
||||
int pos = xOffset >> 1;
|
||||
if (rowStart < 0) {
|
||||
rowStart = pos;
|
||||
|
@ -257,6 +261,7 @@ public class VideoNTSC extends VideoDHGR {
|
|||
// Reset scanline position
|
||||
int byteCounter = 0;
|
||||
for (int s = rowStart; s < 20; s++) {
|
||||
if (s < 0) continue;
|
||||
int add = 0;
|
||||
int bits;
|
||||
if (hiresMode) {
|
||||
|
|
|
@ -150,7 +150,7 @@ public class ApplesoftProgram {
|
|||
}
|
||||
|
||||
public void run() {
|
||||
Emulator.withComputer(c->c.getMotherboard().whileSuspended(()->{
|
||||
Emulator.whileSuspended(c-> {
|
||||
int programStart = c.getMemory().readWordRaw(START_OF_PROG_POINTER);
|
||||
int programEnd = programStart + getProgramSize();
|
||||
if (isProgramRunning()) {
|
||||
|
@ -162,7 +162,7 @@ public class ApplesoftProgram {
|
|||
injectProgram();
|
||||
clearVariables(programEnd);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private void injectProgram() {
|
||||
|
|
|
@ -30,7 +30,6 @@ import java.io.ObjectStreamException;
|
|||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
|
@ -43,13 +42,13 @@ import java.util.Map;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.EmulatorUILogic;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Keyboard;
|
||||
import jace.core.Utility;
|
||||
import javafx.collections.FXCollections;
|
||||
|
@ -64,15 +63,6 @@ import javafx.scene.image.ImageView;
|
|||
*/
|
||||
public class Configuration implements Reconfigurable {
|
||||
|
||||
private static Method findAnyMethodByName(Class<? extends Reconfigurable> aClass, String m) {
|
||||
for (Method method : aClass.getMethods()) {
|
||||
if (method.getName().equals(m)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static ConfigurableField getConfigurableFieldInfo(Reconfigurable subject, String settingName) {
|
||||
Field f;
|
||||
try {
|
||||
|
@ -88,15 +78,6 @@ public class Configuration implements Reconfigurable {
|
|||
return (f != null && !f.shortName().equals("")) ? f.shortName() : longName;
|
||||
}
|
||||
|
||||
public static InvokableAction getInvokableActionInfo(Reconfigurable subject, String actionName) {
|
||||
for (Method m : subject.getClass().getMethods()) {
|
||||
if (m.getName().equals(actionName) && m.isAnnotationPresent(InvokableAction.class)) {
|
||||
return m.getAnnotation(InvokableAction.class);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Optional<ImageView> getChangedIcon() {
|
||||
return Utility.loadIcon("icon_exclaim.gif").map(ImageView::new);
|
||||
}
|
||||
|
@ -301,13 +282,13 @@ public class Configuration implements Reconfigurable {
|
|||
return;
|
||||
}
|
||||
|
||||
for (Method m : node.subject.getClass().getMethods()) {
|
||||
if (!m.isAnnotationPresent(InvokableAction.class)) {
|
||||
continue;
|
||||
}
|
||||
InvokableAction action = m.getDeclaredAnnotation(InvokableAction.class);
|
||||
node.hotkeys.put(m.getName(), action.defaultKeyMapping());
|
||||
}
|
||||
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
|
||||
registry.getStaticMethodNames(node.subject.getClass()).stream().forEach((name) ->
|
||||
node.hotkeys.put(name, registry.getStaticMethodInfo(name).defaultKeyMapping())
|
||||
);
|
||||
registry.getInstanceMethodNames(node.subject.getClass()).stream().forEach((name) ->
|
||||
node.hotkeys.put(name, registry.getInstanceMethodInfo(name).defaultKeyMapping())
|
||||
);
|
||||
|
||||
for (Field f : node.subject.getClass().getFields()) {
|
||||
// System.out.println("Evaluating field " + f.getName());
|
||||
|
@ -337,6 +318,8 @@ public class Configuration implements Reconfigurable {
|
|||
if (child == null || !child.subject.equals(o)) {
|
||||
child = new ConfigNode(node, r);
|
||||
node.putChild(f.getName(), child);
|
||||
} else {
|
||||
Logger.getLogger(Configuration.class.getName()).severe("Unable to find child named %s for node %s".formatted(r.getName(), node.name));
|
||||
}
|
||||
buildTree(child, visited);
|
||||
} else if (o.getClass().isArray()) {
|
||||
|
@ -470,6 +453,34 @@ public class Configuration implements Reconfigurable {
|
|||
return new File(System.getProperty("user.dir"), ".jace.conf");
|
||||
}
|
||||
|
||||
public static void registerKeyHandlers() {
|
||||
registerKeyHandlers(BASE, true);
|
||||
}
|
||||
|
||||
public static void registerKeyHandlers(ConfigNode node, boolean recursive) {
|
||||
Keyboard.unregisterAllHandlers(node.subject);
|
||||
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
|
||||
node.hotkeys.keySet().stream().forEach((name) -> {
|
||||
InvokableAction action = registry.getStaticMethodInfo(name);
|
||||
if (action != null) {
|
||||
for (String code : node.hotkeys.get(name)) {
|
||||
Keyboard.registerInvokableAction(action, node.subject, registry.getStaticFunction(name), code);
|
||||
}
|
||||
}
|
||||
action = registry.getInstanceMethodInfo(name);
|
||||
if (action != null) {
|
||||
for (String code : node.hotkeys.get(name)) {
|
||||
Keyboard.registerInvokableAction(action, node.subject, registry.getInstanceFunction(name), code);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (recursive) {
|
||||
node.getChildren().stream().forEach((child) -> {
|
||||
registerKeyHandlers(child, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply settings from node tree to the object model This also calls
|
||||
* "reconfigure" on objects in sequence
|
||||
|
@ -479,31 +490,26 @@ public class Configuration implements Reconfigurable {
|
|||
* descendants
|
||||
*/
|
||||
public static boolean applySettings(ConfigNode node) {
|
||||
boolean resume = false;
|
||||
if (node == BASE) {
|
||||
resume = Emulator.withComputer(c->c.pause(), false);
|
||||
}
|
||||
boolean hasChanged = false;
|
||||
if (node.changed) {
|
||||
doApply(node);
|
||||
hasChanged = true;
|
||||
}
|
||||
AtomicBoolean hasChanged = new AtomicBoolean(false);
|
||||
|
||||
// Now that the object structure reflects the current configuration,
|
||||
// process reconfiguration from the children, etc.
|
||||
for (ConfigNode child : node.getChildren()) {
|
||||
hasChanged |= applySettings(child);
|
||||
}
|
||||
Emulator.whileSuspended(c-> {
|
||||
if (node.changed) {
|
||||
doApply(node);
|
||||
hasChanged.set(true);
|
||||
}
|
||||
|
||||
if (node.equals(BASE) && hasChanged) {
|
||||
// Now that the object structure reflects the current configuration,
|
||||
// process reconfiguration from the children, etc.
|
||||
for (ConfigNode child : node.getChildren()) {
|
||||
if (applySettings(child)) hasChanged.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
if (node.equals(BASE) && hasChanged.get()) {
|
||||
buildTree();
|
||||
}
|
||||
|
||||
if (resume) {
|
||||
Emulator.withComputer(Computer::resume);
|
||||
}
|
||||
|
||||
return hasChanged;
|
||||
return hasChanged.get();
|
||||
}
|
||||
|
||||
private static void applyConfigTree(ConfigNode newRoot, ConfigNode oldRoot) {
|
||||
|
@ -529,16 +535,7 @@ public class Configuration implements Reconfigurable {
|
|||
|
||||
private static void doApply(ConfigNode node) {
|
||||
List<String> removeList = new ArrayList<>();
|
||||
Keyboard.unregisterAllHandlers(node.subject);
|
||||
node.hotkeys.keySet().stream().forEach((m) -> {
|
||||
Method method = findAnyMethodByName(node.subject.getClass(), m);
|
||||
if (method != null) {
|
||||
InvokableAction action = method.getAnnotation(InvokableAction.class);
|
||||
for (String code : node.hotkeys.get(m)) {
|
||||
Keyboard.registerInvokableAction(action, node.subject, method, code);
|
||||
}
|
||||
}
|
||||
});
|
||||
registerKeyHandlers(node, false);
|
||||
|
||||
for (String f : node.settings.keySet()) {
|
||||
try {
|
||||
|
|
|
@ -1,30 +1,38 @@
|
|||
package jace.config;
|
||||
|
||||
import jace.config.Configuration.ConfigNode;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jace.config.Configuration.ConfigNode;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
public class ConfigurationUIController {
|
||||
public static final String DELIMITER = "~!~";
|
||||
|
||||
|
@ -155,7 +163,7 @@ public class ConfigurationUIController {
|
|||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
node.hotkeys.forEach((name, values) -> settingsVbox.getChildren().add(buildKeyShortcutRow(node, name, values)));
|
||||
node.hotkeys.forEach((name, values) -> buildKeyShortcutRow(node, name, values).ifPresent(settingsVbox.getChildren()::add));
|
||||
node.settings.forEach((name, value) -> settingsVbox.getChildren().add(buildSettingRow(node, name, value)));
|
||||
}
|
||||
|
||||
|
@ -176,10 +184,14 @@ public class ConfigurationUIController {
|
|||
return row;
|
||||
}
|
||||
|
||||
private Node buildKeyShortcutRow(ConfigNode node, String actionName, String[] values) {
|
||||
InvokableAction actionInfo = Configuration.getInvokableActionInfo(node.subject, actionName);
|
||||
private Optional<Node> buildKeyShortcutRow(ConfigNode node, String actionName, String[] values) {
|
||||
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
|
||||
InvokableAction actionInfo = registry.getInstanceMethodInfo(actionName);
|
||||
if (actionInfo == null) {
|
||||
return null;
|
||||
actionInfo = registry.getStaticMethodInfo(actionName);
|
||||
}
|
||||
if (actionInfo == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
HBox row = new HBox();
|
||||
row.getStyleClass().add("setting-row");
|
||||
|
@ -194,7 +206,7 @@ public class ConfigurationUIController {
|
|||
label.setLabelFor(widget);
|
||||
row.getChildren().add(label);
|
||||
row.getChildren().add(widget);
|
||||
return row;
|
||||
return Optional.of(row);
|
||||
}
|
||||
|
||||
private void editKeyboardShortcut(ConfigNode node, String actionName, Text widget) {
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
package jace.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import javax.annotation.processing.SupportedSourceVersion;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
// Compile-time annotation processor which creates a registry of all static methods annotated with @InvokableAction.
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_17)
|
||||
@SupportedAnnotationTypes("jace.config.InvokableAction")
|
||||
public class InvokableActionAnnotationProcessor extends AbstractProcessor {
|
||||
Messager messager;
|
||||
Map<InvokableAction, ExecutableElement> staticMethods = new HashMap<>();
|
||||
Map<InvokableAction, ExecutableElement> instanceMethods = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment processingEnv) {
|
||||
super.init(processingEnv);
|
||||
this.messager = processingEnv.getMessager();
|
||||
messager.printMessage(javax.tools.Diagnostic.Kind.NOTE, "InvokableActionAnnotationProcessor init()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
messager.printMessage(javax.tools.Diagnostic.Kind.NOTE, "InvokableActionAnnotationProcessor process()");
|
||||
|
||||
// Get list of methods annotated with @InvokableAction.
|
||||
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(InvokableAction.class);
|
||||
for (Element element : elements) {
|
||||
if (element.getModifiers().contains(javax.lang.model.element.Modifier.STATIC)) {
|
||||
// If the annotation method is static, add it to the static method registry.
|
||||
trackStaticMethod(element);
|
||||
} else {
|
||||
// For non-static methods, track in a separate registry.
|
||||
trackInstanceMethod(element);
|
||||
}
|
||||
try {
|
||||
// Write class that contains static and instance methods.
|
||||
writeRegistryClass();
|
||||
} catch (IOException ex) {
|
||||
messager.printMessage(javax.tools.Diagnostic.Kind.ERROR, "Error writing InvokableActionRegistry.java: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void trackStaticMethod(Element element) {
|
||||
// Store the method in the static method registry.
|
||||
staticMethods.put(element.getAnnotation(InvokableAction.class), (ExecutableElement) element);
|
||||
}
|
||||
|
||||
private void trackInstanceMethod(Element element) {
|
||||
// Store the method in the instance method registry.
|
||||
instanceMethods.put(element.getAnnotation(InvokableAction.class), (ExecutableElement) element);
|
||||
}
|
||||
|
||||
private String serializeArrayOfStrings(String... strings) {
|
||||
return Arrays.stream(strings).map(s -> "\"" + s + "\"").collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
private void serializeInvokableAction(InvokableAction annotation, String variableName, PrintWriter writer) {
|
||||
writer.append("""
|
||||
%s = createInvokableAction("%s", "%s", "%s", "%s", %s, %s, new String[] {%s});
|
||||
""".formatted(
|
||||
variableName,
|
||||
annotation.name(),
|
||||
annotation.category(),
|
||||
annotation.description(),
|
||||
annotation.alternatives(),
|
||||
annotation.consumeKeyEvent(),
|
||||
annotation.notifyOnRelease(),
|
||||
serializeArrayOfStrings(annotation.defaultKeyMapping())
|
||||
));
|
||||
}
|
||||
|
||||
// Write the registry class.
|
||||
private void writeRegistryClass() throws IOException {
|
||||
Files.createDirectories(new File("target/generated-sources/jace/config").toPath());
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter("target/generated-sources/jace/config/InvokableActionRegistryImpl.java"))) {
|
||||
writer.write("""
|
||||
package jace.config;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class InvokableActionRegistryImpl extends InvokableActionRegistry {
|
||||
@Override
|
||||
public void init() {
|
||||
InvokableAction annotation;
|
||||
""");
|
||||
for (Map.Entry<InvokableAction, ExecutableElement> entry : staticMethods.entrySet()) {
|
||||
InvokableAction annotation = entry.getKey();
|
||||
ExecutableElement method = entry.getValue();
|
||||
String packageName = method.getEnclosingElement().getEnclosingElement().toString();
|
||||
String className = method.getEnclosingElement().getSimpleName().toString();
|
||||
String fqnClassName = packageName + "." + className;
|
||||
serializeInvokableAction(annotation, "annotation", writer);
|
||||
boolean takesBoolenParameter = method.getParameters().size() == 1 && method.getParameters().get(0).asType().toString().equalsIgnoreCase("boolean");
|
||||
boolean returnsBoolean = method.getReturnType().toString().equalsIgnoreCase("boolean");
|
||||
writer.write("""
|
||||
putStaticAction(annotation.name(), %s.class, annotation, (b) -> {
|
||||
try {
|
||||
%s %s.%s(%s);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking %s", ex);
|
||||
%s
|
||||
}
|
||||
});
|
||||
""".formatted(
|
||||
fqnClassName,
|
||||
returnsBoolean ? "return " : "",
|
||||
fqnClassName,
|
||||
method.getSimpleName(),
|
||||
takesBoolenParameter ? "b" : "",
|
||||
fqnClassName + "." + method.getSimpleName(),
|
||||
returnsBoolean ? "return false;" : ""
|
||||
));
|
||||
}
|
||||
|
||||
// Now for the instance methods, do the same except use a biconsumer which takes the instance as well as the boolean parameter.
|
||||
for (Map.Entry<InvokableAction, ExecutableElement> entry : instanceMethods.entrySet()) {
|
||||
InvokableAction annotation = entry.getKey();
|
||||
ExecutableElement method = entry.getValue();
|
||||
String packageName = method.getEnclosingElement().getEnclosingElement().toString();
|
||||
String className = method.getEnclosingElement().getSimpleName().toString();
|
||||
String fqnClassName = packageName + "." + className;
|
||||
serializeInvokableAction(annotation, "annotation", writer);
|
||||
boolean takesBoolenParameter = method.getParameters().size() == 1 && method.getParameters().get(0).asType().toString().equalsIgnoreCase("boolean");
|
||||
boolean returnsBoolean = method.getReturnType().toString().equalsIgnoreCase("boolean");
|
||||
writer.write("""
|
||||
putInstanceAction(annotation.name(), %s.class, annotation, (o, b) -> {
|
||||
try {
|
||||
%s ((%s) o).%s(%s);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking %s", ex);
|
||||
%s
|
||||
}
|
||||
});
|
||||
""".formatted(
|
||||
fqnClassName,
|
||||
returnsBoolean ? "return " : "",
|
||||
fqnClassName,
|
||||
method.getSimpleName(),
|
||||
takesBoolenParameter ? "b" : "",
|
||||
fqnClassName + "." + method.getSimpleName(),
|
||||
returnsBoolean ? "return false;" : ""
|
||||
));
|
||||
}
|
||||
writer.write("}\n}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
package jace.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public abstract class InvokableActionRegistry {
|
||||
protected static final Logger logger = Logger.getLogger(InvokableActionRegistry.class.getName());
|
||||
private final Map<Class, Set<String>> staticMethodNames = new HashMap<>();
|
||||
private final Map<String, InvokableAction> staticMethodInfo = new HashMap<>();
|
||||
private final Map<String, Function<Boolean, Boolean>> staticMethodCallers = new HashMap<>();
|
||||
private final Map<Class, Set<String>> instanceMethodNames = new HashMap<>();
|
||||
private final Map<String, InvokableAction> instanceMethodInfo = new HashMap<>();
|
||||
private final Map<String, BiFunction<Object, Boolean, Boolean>> instanceMethodCallers = new HashMap<>();
|
||||
|
||||
protected static InvokableActionRegistry instance;
|
||||
|
||||
public static InvokableActionRegistry getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new InvokableActionRegistryImpl();
|
||||
instance.init();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
abstract public void init();
|
||||
|
||||
final public void putStaticAction(String name, Class c, InvokableAction action, Consumer<Boolean> caller) {
|
||||
putStaticAction(name, c, action, (b) -> {
|
||||
caller.accept(b);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
final public void putStaticAction(String name, Class c, InvokableAction action, Function<Boolean, Boolean> caller) {
|
||||
staticMethodInfo.put(name, action);
|
||||
staticMethodCallers.put(name, caller);
|
||||
staticMethodNames.computeIfAbsent(c, k -> new TreeSet<>()).add(name);
|
||||
}
|
||||
|
||||
public void putInstanceAction(String name, Class c, InvokableAction action, BiConsumer<Object, Boolean> caller) {
|
||||
putInstanceAction(name, c, action, (o, b) -> {
|
||||
caller.accept(o, b);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public void putInstanceAction(String name, Class c, InvokableAction action, BiFunction<Object, Boolean, Boolean> caller) {
|
||||
instanceMethodInfo.put(name, action);
|
||||
instanceMethodCallers.put(name, caller);
|
||||
instanceMethodNames.computeIfAbsent(c, k -> new TreeSet<>()).add(name);
|
||||
}
|
||||
|
||||
public Set<String> getStaticMethodNames(Class c) {
|
||||
// Build a set of all the method names for this class and all its superclasses.
|
||||
Set<String> result = new TreeSet<>();
|
||||
Class current = c;
|
||||
while (current != null) {
|
||||
result.addAll(staticMethodNames.getOrDefault(current, Collections.EMPTY_SET));
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Set<String> getInstanceMethodNames(Class c) {
|
||||
// Build a set of all the method names for this class and all its superclasses.
|
||||
Set<String> result = new TreeSet<>();
|
||||
Class current = c;
|
||||
while (current != null) {
|
||||
result.addAll(instanceMethodNames.getOrDefault(current, Collections.EMPTY_SET));
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public InvokableAction getStaticMethodInfo(String name) {
|
||||
return staticMethodInfo.get(name);
|
||||
}
|
||||
|
||||
public InvokableAction getInstanceMethodInfo(String name) {
|
||||
return instanceMethodInfo.get(name);
|
||||
}
|
||||
|
||||
public Function<Boolean, Boolean> getStaticFunction(String name) {
|
||||
return staticMethodCallers.get(name);
|
||||
}
|
||||
|
||||
public BiFunction<Object, Boolean, Boolean> getInstanceFunction(String name) {
|
||||
return instanceMethodCallers.get(name);
|
||||
}
|
||||
|
||||
public Set<InvokableAction> getAllStaticActions() {
|
||||
return new HashSet<>(staticMethodInfo.values());
|
||||
}
|
||||
|
||||
protected InvokableAction createInvokableAction(String name, String category, String description, String alternatives, boolean consumeKeyEvent, boolean notifyOnRelease, String[] defaultKeyMapping) {
|
||||
return new InvokableAction() {
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String category() {
|
||||
return category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alternatives() {
|
||||
return alternatives;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean consumeKeyEvent() {
|
||||
return consumeKeyEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean notifyOnRelease() {
|
||||
return notifyOnRelease;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] defaultKeyMapping() {
|
||||
return defaultKeyMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends java.lang.annotation.Annotation> annotationType() {
|
||||
return InvokableAction.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
package jace.config;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
// NOTE: This is generated code. Do not edit.
|
||||
public class InvokableActionRegistryImpl extends InvokableActionRegistry {
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
InvokableAction annotation;
|
||||
annotation = createInvokableAction("Resize window", "general", "Resize the screen to 1x/1.5x/2x/3x video size", "Aspect;Adjust screen;Adjust window size;Adjust aspect ratio;Fix screen;Fix window size;Fix aspect ratio;Correct aspect ratio;", true, false, new String[]{"ctrl+shift+a"});
|
||||
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.EmulatorUILogic.scaleIntegerRatio();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.scaleIntegerRatio", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Rewind", "General", "Go back 1 second", "Timewarp", true, false, new String[]{"ctrl+shift+Open Bracket"});
|
||||
putStaticAction(annotation.name(), jace.state.StateManager.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.state.StateManager.beKindRewind();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.state.StateManager.beKindRewind", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Configuration", "general", "Edit emulator configuraion", "Reconfigure;Preferences;Settings;Config", true, false, new String[]{"f4", "ctrl+shift+c"});
|
||||
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.EmulatorUILogic.showConfig();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.showConfig", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Load settings", "general", "Load all configuration settings previously saved", "load preferences;revert settings;revert preferences", true, false, new String[]{"meta+ctrl+r"});
|
||||
putStaticAction(annotation.name(), jace.config.Configuration.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.config.Configuration.loadSettings();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.config.Configuration.loadSettings", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("About", "general", "Display about window", "info;credits", true, false, new String[]{"ctrl+shift+."});
|
||||
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.EmulatorUILogic.showAboutWindow();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.showAboutWindow", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Record sound", "sound", "Toggles recording (saving) sound output to a file", "", true, false, new String[]{"ctrl+shift+w"});
|
||||
putStaticAction(annotation.name(), jace.apple2e.Speaker.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.apple2e.Speaker.toggleFileOutput();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.apple2e.Speaker.toggleFileOutput", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("BRUN file", "file", "Loads a binary file in memory and executes it. File should end with #06xxxx, where xxxx is the start address in hex", "Execute program;Load binary;Load program;Load rom;Play single-load game", true, false, new String[]{"ctrl+shift+b"});
|
||||
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.EmulatorUILogic.runFile();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.runFile", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Save Raw Screenshot", "general", "Save raw (RAM) format of visible screen", "screendump;raw screenshot", true, false, new String[]{"ctrl+shift+z"});
|
||||
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.EmulatorUILogic.saveScreenshotRaw();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.saveScreenshotRaw", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Save settings", "general", "Save all configuration settings as defaults", "save preferences;save defaults", true, false, new String[]{"meta+ctrl+s"});
|
||||
putStaticAction(annotation.name(), jace.config.Configuration.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.config.Configuration.saveSettings();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.config.Configuration.saveSettings", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Toggle fullscreen", "general", "Activate/deactivate fullscreen mode", "fullscreen;maximize", true, false, new String[]{"ctrl+shift+f"});
|
||||
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.EmulatorUILogic.toggleFullscreen();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.toggleFullscreen", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Toggle Debug", "debug", "Show/hide the debug panel", "Show Debug;Hide Debug;Inspect", true, false, new String[]{"ctrl+shift+d"});
|
||||
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.EmulatorUILogic.toggleDebugPanel();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.toggleDebugPanel", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Refresh screen", "display", "Marks screen contents as changed, forcing full screen redraw", "redraw", true, false, new String[]{"ctrl+shift+r"});
|
||||
putStaticAction(annotation.name(), jace.core.Video.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.core.Video.forceRefresh();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.core.Video.forceRefresh", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Toggle video mode", "video", "", "Gfx mode;color;b&w;monochrome", true, false, new String[]{"ctrl+shift+g"});
|
||||
putStaticAction(annotation.name(), jace.apple2e.VideoNTSC.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.apple2e.VideoNTSC.changeVideoMode();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.apple2e.VideoNTSC.changeVideoMode", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Save Screenshot", "general", "Save image of visible screen", "Save image;save framebuffer;screenshot", true, false, new String[]{"ctrl+shift+s"});
|
||||
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.EmulatorUILogic.saveScreenshot();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.saveScreenshot", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Paste clipboard", "Keyboard", "", "paste", true, false, new String[]{"Ctrl+Shift+V", "Shift+Insert"});
|
||||
putStaticAction(annotation.name(), jace.core.Keyboard.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.core.Keyboard.pasteFromClipboard();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.core.Keyboard.pasteFromClipboard", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Open IDE", "development", "Open new IDE window for Basic/Assembly/Plasma coding", "IDE;dev;development;acme;assembler;editor", true, false, new String[]{"ctrl+shift+i"});
|
||||
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
|
||||
try {
|
||||
jace.EmulatorUILogic.showIDE();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.showIDE", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Up", "joystick", "", "", true, true, new String[]{"up"});
|
||||
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
|
||||
try {
|
||||
return ((jace.hardware.Joystick) o).joystickUp(b);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickUp", ex);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Open Apple Key", "Keyboard", "", "OA", false, true, new String[]{"Alt"});
|
||||
putInstanceAction(annotation.name(), jace.core.Keyboard.class, annotation, (o, b) -> {
|
||||
try {
|
||||
((jace.core.Keyboard) o).openApple(b);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.core.Keyboard.openApple", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Left", "joystick", "", "", true, true, new String[]{"left"});
|
||||
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
|
||||
try {
|
||||
return ((jace.hardware.Joystick) o).joystickLeft(b);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickLeft", ex);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Right", "joystick", "", "", true, true, new String[]{"right"});
|
||||
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
|
||||
try {
|
||||
return ((jace.hardware.Joystick) o).joystickRight(b);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickRight", ex);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Closed Apple Key", "Keyboard", "", "CA", false, true, new String[]{"Shortcut", "Meta", "Command"});
|
||||
putInstanceAction(annotation.name(), jace.core.Keyboard.class, annotation, (o, b) -> {
|
||||
try {
|
||||
((jace.core.Keyboard) o).solidApple(b);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.core.Keyboard.solidApple", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Down", "joystick", "", "", true, true, new String[]{"down"});
|
||||
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
|
||||
try {
|
||||
return ((jace.hardware.Joystick) o).joystickDown(b);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickDown", ex);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Pause", "General", "Stops the computer, allowing reconfiguration of core elements", "freeze;halt", true, false, new String[]{"meta+pause", "alt+pause"});
|
||||
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
|
||||
try {
|
||||
return ((jace.core.Computer) o).pause();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.pause", ex);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Cold boot", "general", "Process startup sequence from power-up", "Full reset;reset emulator", true, false, new String[]{"Ctrl+Shift+Backspace", "Ctrl+Shift+Delete"});
|
||||
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
|
||||
try {
|
||||
((jace.core.Computer) o).invokeColdStart();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.invokeColdStart", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Warm boot", "general", "Process user-initatiated reboot (ctrl+apple+reset)", "reboot;reset;three-finger-salute;restart", true, false, new String[]{"Ctrl+Ignore Alt+Ignore Meta+Backspace", "Ctrl+Ignore Alt+Ignore Meta+Delete"});
|
||||
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
|
||||
try {
|
||||
((jace.core.Computer) o).invokeWarmStart();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.invokeWarmStart", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Toggle Cheats", "General", "", "cheat;Plug-in", true, false, new String[]{"ctrl+shift+m"});
|
||||
putInstanceAction(annotation.name(), jace.cheat.Cheats.class, annotation, (o, b) -> {
|
||||
try {
|
||||
((jace.cheat.Cheats) o).toggleCheats();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.cheat.Cheats.toggleCheats", ex);
|
||||
}
|
||||
});
|
||||
annotation = createInvokableAction("Resume", "General", "Resumes the computer if it was previously paused", "unpause;unfreeze;resume;play", true, false, new String[]{"meta+shift+pause", "alt+shift+pause"});
|
||||
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
|
||||
try {
|
||||
((jace.core.Computer) o).resume();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.resume", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
|
||||
/**
|
||||
|
@ -102,7 +103,7 @@ public abstract class Card extends Device {
|
|||
@Override
|
||||
public void reconfigure() {
|
||||
//super.reconfigure();
|
||||
computer.motherboard.whileSuspended(() -> {
|
||||
Emulator.whileSuspended(c-> {
|
||||
unregisterListeners();
|
||||
registerListeners();
|
||||
});
|
||||
|
|
|
@ -45,9 +45,9 @@ import javafx.beans.value.ChangeListener;
|
|||
*/
|
||||
public abstract class Computer implements Reconfigurable {
|
||||
|
||||
private RAM memory;
|
||||
public RAM memory;
|
||||
public CPU cpu;
|
||||
private Video video;
|
||||
public Video video;
|
||||
public Keyboard keyboard;
|
||||
public StateManager stateManager;
|
||||
public Motherboard motherboard;
|
||||
|
|
|
@ -154,7 +154,7 @@ public abstract class Device implements Reconfigurable {
|
|||
return run;
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
public final boolean isPaused() {
|
||||
return paused;
|
||||
}
|
||||
|
||||
|
@ -163,7 +163,7 @@ public abstract class Device implements Reconfigurable {
|
|||
updateTickHandler();
|
||||
}
|
||||
|
||||
public final synchronized void setPaused(boolean paused) {
|
||||
public synchronized void setPaused(boolean paused) {
|
||||
this.paused = paused;
|
||||
updateTickHandler();
|
||||
}
|
||||
|
@ -187,22 +187,43 @@ public abstract class Device implements Reconfigurable {
|
|||
}
|
||||
}
|
||||
|
||||
public void whilePaused(Runnable r) {
|
||||
if (isRunning() && !isPaused()) {
|
||||
setPaused(true);
|
||||
r.run();
|
||||
setPaused(false);
|
||||
} else {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T whileSuspended(Supplier<T> r, T defaultValue) {
|
||||
T result;
|
||||
if (isRunning()) {
|
||||
suspend();
|
||||
T result = r.get();
|
||||
result = r.get();
|
||||
resume();
|
||||
return result;
|
||||
} else {
|
||||
return defaultValue;
|
||||
result = r.get();
|
||||
}
|
||||
return result != null ? result : defaultValue;
|
||||
}
|
||||
|
||||
public <T> T whilePaused(Supplier<T> r, T defaultValue) {
|
||||
T result;
|
||||
if (!isPaused() && isRunning()) {
|
||||
setPaused(true);
|
||||
result = r.get();
|
||||
setPaused(false);
|
||||
} else {
|
||||
result = r.get();
|
||||
}
|
||||
return result != null ? result : defaultValue;
|
||||
}
|
||||
|
||||
|
||||
public boolean suspend() {
|
||||
if (isRunning()) {
|
||||
// System.out.println(getName() + " Suspended");
|
||||
// Utility.printStackTrace();
|
||||
setRun(false);
|
||||
return true;
|
||||
}
|
||||
|
@ -213,8 +234,6 @@ public abstract class Device implements Reconfigurable {
|
|||
public void resume() {
|
||||
children.forEach(Device::resume);
|
||||
if (!isRunning()) {
|
||||
// System.out.println(getName() + " Resumed");
|
||||
// Utility.printStackTrace();
|
||||
setRun(true);
|
||||
waitCycles = 0;
|
||||
}
|
||||
|
|
|
@ -20,13 +20,12 @@ package jace.core;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
@ -96,8 +95,14 @@ public class Keyboard implements Reconfigurable {
|
|||
private static final Map<KeyCode, Set<KeyHandler>> keyHandlersByKey = new HashMap<>();
|
||||
private static final Map<Object, Set<KeyHandler>> keyHandlersByOwner = new HashMap<>();
|
||||
|
||||
public static void registerInvokableAction(InvokableAction action, Object owner, Method method, String code) {
|
||||
boolean isStatic = Modifier.isStatic(method.getModifiers());
|
||||
/**
|
||||
*
|
||||
* @param action
|
||||
* @param owner
|
||||
* @param method
|
||||
* @param code
|
||||
*/
|
||||
public static void registerInvokableAction(InvokableAction action, Object owner, Function<Boolean, Boolean> method, String code) {
|
||||
registerKeyHandler(new KeyHandler(code) {
|
||||
@Override
|
||||
public boolean handleKeyUp(KeyEvent e) {
|
||||
|
@ -105,41 +110,40 @@ public class Keyboard implements Reconfigurable {
|
|||
if (action == null || !action.notifyOnRelease()) {
|
||||
return false;
|
||||
}
|
||||
// System.out.println("Key up: "+method.toString());
|
||||
Object returnValue = null;
|
||||
try {
|
||||
if (method.getParameterCount() > 0) {
|
||||
returnValue = method.invoke(isStatic ? null : owner, false);
|
||||
} else {
|
||||
returnValue = method.invoke(isStatic ? null : owner);
|
||||
}
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
Logger.getLogger(Keyboard.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
if (returnValue != null) {
|
||||
return (Boolean) returnValue;
|
||||
}
|
||||
return action.consumeKeyEvent();
|
||||
return method.apply(false) || action.consumeKeyEvent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleKeyDown(KeyEvent e) {
|
||||
// System.out.println("Key down: "+method.toString());
|
||||
Emulator.withComputer(c -> c.getKeyboard().shiftPressed = e.isShiftDown());
|
||||
Object returnValue = null;
|
||||
try {
|
||||
if (method.getParameterCount() > 0) {
|
||||
returnValue = method.invoke(isStatic ? null : owner, true);
|
||||
} else {
|
||||
returnValue = method.invoke(isStatic ? null : owner);
|
||||
}
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
Logger.getLogger(Keyboard.class.getName()).log(Level.SEVERE, null, ex);
|
||||
if (action == null) {
|
||||
return false;
|
||||
}
|
||||
if (returnValue != null) {
|
||||
return (Boolean) returnValue;
|
||||
return method.apply(true) || action.consumeKeyEvent();
|
||||
}
|
||||
}, owner);
|
||||
}
|
||||
|
||||
public static void registerInvokableAction(InvokableAction action, Object owner, BiFunction<Object, Boolean, Boolean> method, String code) {
|
||||
registerKeyHandler(new KeyHandler(code) {
|
||||
@Override
|
||||
public boolean handleKeyUp(KeyEvent e) {
|
||||
Emulator.withComputer(c -> c.getKeyboard().shiftPressed = e.isShiftDown());
|
||||
if (action == null || !action.notifyOnRelease()) {
|
||||
return false;
|
||||
}
|
||||
return action != null ? action.consumeKeyEvent() : null;
|
||||
return method.apply(owner, false) || action.consumeKeyEvent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleKeyDown(KeyEvent e) {
|
||||
// System.out.println("Key down: "+method.toString());
|
||||
Emulator.withComputer(c -> c.getKeyboard().shiftPressed = e.isShiftDown());
|
||||
if (action == null) {
|
||||
return false;
|
||||
}
|
||||
return method.apply(owner, true) || action.consumeKeyEvent();
|
||||
}
|
||||
}, owner);
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ public abstract class TimedDevice extends Device {
|
|||
if (!isRunning()) {
|
||||
return false;
|
||||
}
|
||||
setPaused(true);
|
||||
super.setPaused(true);
|
||||
try {
|
||||
// KLUDGE: Sleeping to wait for worker thread to hit paused state. We might be inside the worker (?)
|
||||
Thread.sleep(10);
|
||||
|
@ -82,6 +82,15 @@ public abstract class TimedDevice extends Device {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPaused(boolean paused) {
|
||||
if (!isPaused() && paused) {
|
||||
pause();
|
||||
} else {
|
||||
super.setPaused(paused);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used in unit tests where we want the device
|
||||
* to act like it is resumed, but not actual free-running.
|
||||
|
@ -100,7 +109,7 @@ public abstract class TimedDevice extends Device {
|
|||
return;
|
||||
}
|
||||
worker = new Thread(() -> {
|
||||
System.out.println("Worker thread for " + getDeviceName() + " starting");
|
||||
// System.out.println("Worker thread for " + getDeviceName() + " starting");
|
||||
while (isRunning()) {
|
||||
hasStopped = false;
|
||||
doTick();
|
||||
|
@ -113,7 +122,7 @@ public abstract class TimedDevice extends Device {
|
|||
}
|
||||
}
|
||||
hasStopped = true;
|
||||
System.out.println("Worker thread for " + getDeviceName() + " stopped");
|
||||
// System.out.println("Worker thread for " + getDeviceName() + " stopped");
|
||||
});
|
||||
worker.setDaemon(false);
|
||||
worker.setPriority(Thread.MAX_PRIORITY);
|
||||
|
|
|
@ -20,8 +20,6 @@ package jace.core;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -30,12 +28,10 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jace.config.Configuration;
|
||||
import jace.config.InvokableAction;
|
||||
import jace.config.InvokableActionRegistry;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Alert;
|
||||
|
@ -406,38 +402,15 @@ public class Utility {
|
|||
return null;
|
||||
}
|
||||
|
||||
static final Map<InvokableAction, Runnable> allActions = new ConcurrentHashMap<>();
|
||||
|
||||
public static Map<InvokableAction, Runnable> getAllInvokableActions() {
|
||||
synchronized (allActions) {
|
||||
if (allActions.isEmpty()) {
|
||||
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());
|
||||
public static Function<Boolean, Boolean> getNamedInvokableAction(String action) {
|
||||
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
|
||||
List<InvokableAction> actionsList = new ArrayList(registry.getAllStaticActions());
|
||||
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));
|
||||
return registry.getStaticFunction(actionsList.get(0).name());
|
||||
}
|
||||
|
||||
private static int getActionNameMatch(String str, InvokableAction action) {
|
||||
|
|
|
@ -156,7 +156,7 @@ public abstract class Video extends Device {
|
|||
public void tick() {
|
||||
addWaitCycles(waitsPerCycle);
|
||||
addWaitCycles((int) motherboardAdjustedWaitsPerCycle);
|
||||
setScannerLocation(currentWriter.getYOffset(y));
|
||||
if (y < APPLE_SCREEN_LINES) setScannerLocation(currentWriter.getYOffset(y));
|
||||
setFloatingBus(computer.getMemory().readRaw(scannerAddress + x));
|
||||
if (hPeriod > 0) {
|
||||
hPeriod--;
|
||||
|
@ -164,7 +164,7 @@ public abstract class Video extends Device {
|
|||
x = -1;
|
||||
}
|
||||
} else {
|
||||
if (!isVblank && x < APPLE_CYCLES_PER_LINE) {
|
||||
if (!isVblank && x < APPLE_CYCLES_PER_LINE && x >= 0) {
|
||||
draw();
|
||||
}
|
||||
if (x >= APPLE_CYCLES_PER_LINE - 1) {
|
||||
|
|
|
@ -18,16 +18,18 @@
|
|||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import jace.hardware.ProdosDriver.MLI_COMMAND_TYPE;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import static jace.hardware.ProdosDriver.MLI_COMMAND;
|
||||
import jace.hardware.ProdosDriver.MLI_COMMAND_TYPE;
|
||||
import static jace.hardware.ProdosDriver.MLI_UNITNUMBER;
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,6 +15,7 @@ import jace.apple2e.SoftSwitches;
|
|||
import jace.apple2e.VideoNTSC;
|
||||
import jace.cheat.Cheats;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.Configuration;
|
||||
import jace.core.Video;
|
||||
import jace.library.MediaConsumer;
|
||||
|
||||
|
@ -79,31 +80,31 @@ public class LawlessComputer extends Apple2e {
|
|||
((VideoNTSC) getVideo()).enableVideo7 = false;
|
||||
getMemory().configureActiveMemory();
|
||||
getVideo().configureVideoMode();
|
||||
Configuration.registerKeyHandlers();
|
||||
doResume();
|
||||
|
||||
if (!performedBootAnimation) {
|
||||
try {
|
||||
performedBootAnimation = true;
|
||||
waitForVBL();
|
||||
renderWithMask(0, 0, 0, 0);
|
||||
renderWithMask(0x0, 0x10, 0, 0x40, 0, 0x01, 0, 0x4);
|
||||
renderWithMask(0x8, 0x10, 0x02, 0x40, 0, 0x01, 0x20, 0x4);
|
||||
renderWithMask(0x8, 0x11, 0x22, 0x44);
|
||||
renderWithMask(0x8, 0x19, 0x22, 0x66, 0x4c, 0x11, 0x33, 044);
|
||||
renderWithMask(0x4c, 0x19, 0x33, 0x66);
|
||||
renderWithMask(0x4c, 0x5d, 0x33, 0x77, 0x6e, 0x19, 0x3B, 0x66);
|
||||
renderWithMask(0x6e, 0x5d, 0x3B, 0x77);
|
||||
renderWithMask(0x6e, 0x7f, 0x3b, 0x7f, 0x7f, 0x5d, 0x7f, 0x77);
|
||||
renderWithMask(0x7f, 0x7f, 0x7f, 0x7f);
|
||||
renderWithMask(0x00,0x00,0x00,0x00);
|
||||
renderWithMask(0x08,0x10,0x20,0x40,0x00,0x01,0x02,0x04);
|
||||
renderWithMask(0x08,0x11,0x22,0x44);
|
||||
renderWithMask(0x0C,0x19,0x32,0x64,0x48,0x11,0x23,0x46);
|
||||
renderWithMask(0x4C,0x19,0x33,0x66);
|
||||
renderWithMask(0x4E,0x1D,0x3B,0x76,0x6C,0x59,0x33,0x67);
|
||||
renderWithMask(0x6E,0x5D,0x3B,0x77);
|
||||
renderWithMask(0x7F,0x7E,0x7D,0x7B,0x77,0x6F,0x5F,0x3F);
|
||||
renderWithMask(0x7F,0x7F,0x7F,0x7F);
|
||||
waitForVBL(230);
|
||||
renderWithMask(0x7f, 0x6e, 0x7f, 0x3b, 0x77, 0x6e, 0x5d, 0x3b);
|
||||
renderWithMask(0x77, 0x6e, 0x5d, 0x3b);
|
||||
renderWithMask(0x77, 0x66, 0x5d, 0x19, 0x33, 0x6e, 0x4c, 0x3b);
|
||||
renderWithMask(0x33, 0x66, 0x4c, 0x19);
|
||||
renderWithMask(0x33, 0x22, 0x4c, 0x8, 0x11, 0x66, 0x44, 0x19);
|
||||
renderWithMask(0x11, 0x22, 0x44, 0x8);
|
||||
renderWithMask(0x11, 0, 0x44, 0, 0, 0x22, 0, 0x8);
|
||||
renderWithMask(0, 0, 0, 0);
|
||||
renderWithMask(0x77,0x6F,0x5F,0x3F,0x7F,0x7E,0x7D,0x7B);
|
||||
renderWithMask(0x77,0x6E,0x5D,0x3B);
|
||||
renderWithMask(0x73,0x66,0x4D,0x1B,0x37,0x6E,0x5C,0x39);
|
||||
renderWithMask(0x33,0x66,0x4C,0x19);
|
||||
renderWithMask(0x31,0x62,0x44,0x09,0x13,0x26,0x4C,0x18);
|
||||
renderWithMask(0x11,0x22,0x44,0x08);
|
||||
renderWithMask(0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x00);
|
||||
renderWithMask(0x00,0x00,0x00,0x00);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(LawlessComputer.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
@ -118,6 +119,15 @@ public class LawlessComputer extends Apple2e {
|
|||
byte[] framebuffer = getBootScreen();
|
||||
int maskOffset = 0;
|
||||
for (int i = 0; i < 0x02000; i += 2) {
|
||||
int y = Video.identifyHiresRow(i + 0x02000);
|
||||
int x = i - Video.calculateHiresOffset(y);
|
||||
if (y < 0 || y >= 192 || x >= 40 || x < 0) {
|
||||
continue;
|
||||
}
|
||||
maskOffset = (y % 2) * 4;
|
||||
maskOffset += ((x / 2) % 2) * 4;
|
||||
maskOffset %= mask.length;
|
||||
|
||||
int next = (framebuffer[i] & 1) << 6;
|
||||
Byte b1 = (byte) ((framebuffer[i + 0x02000] & 0x07f) >> 1 | next);
|
||||
ram.getAuxMemory().writeByte(0x02000 + i, (byte) (b1 & mask[maskOffset] | 0x080));
|
||||
|
@ -137,9 +147,6 @@ public class LawlessComputer extends Apple2e {
|
|||
Byte b4 = (byte) ((framebuffer[i + 1] & 0x07f) >> 1 | next);
|
||||
ram.getMainMemory().writeByte(0x02001 + i, (byte) (b4 & mask[maskOffset + 3] | 0x080));
|
||||
}
|
||||
if (i % 20 != 0) {
|
||||
maskOffset = (maskOffset + 4) % mask.length;
|
||||
}
|
||||
}
|
||||
Video.forceRefresh();
|
||||
waitForVBL(5);
|
||||
|
|
|
@ -167,31 +167,27 @@ public class MetacheatUI {
|
|||
|
||||
@FXML
|
||||
void loadCheats(ActionEvent event) {
|
||||
Emulator.withComputer(c -> {
|
||||
c.getMotherboard().whileSuspended(() -> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Load cheats");
|
||||
chooser.setInitialFileName("cheat.txt");
|
||||
File saveFile = chooser.showOpenDialog(LawlessLegends.getApplication().primaryStage);
|
||||
if (saveFile != null) {
|
||||
cheatEngine.loadCheats(saveFile);
|
||||
}
|
||||
});
|
||||
Emulator.whileSuspended(c-> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Load cheats");
|
||||
chooser.setInitialFileName("cheat.txt");
|
||||
File saveFile = chooser.showOpenDialog(LawlessLegends.getApplication().primaryStage);
|
||||
if (saveFile != null) {
|
||||
cheatEngine.loadCheats(saveFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
void saveCheats(ActionEvent event) {
|
||||
Emulator.withComputer(c -> {
|
||||
c.getMotherboard().whileSuspended(() -> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Save current cheats");
|
||||
chooser.setInitialFileName("cheat.txt");
|
||||
File saveFile = chooser.showSaveDialog(LawlessLegends.getApplication().primaryStage);
|
||||
if (saveFile != null) {
|
||||
cheatEngine.saveCheats(saveFile);
|
||||
}
|
||||
});
|
||||
Emulator.whileSuspended(c-> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Save current cheats");
|
||||
chooser.setInitialFileName("cheat.txt");
|
||||
File saveFile = chooser.showSaveDialog(LawlessLegends.getApplication().primaryStage);
|
||||
if (saveFile != null) {
|
||||
cheatEngine.saveCheats(saveFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -458,46 +454,44 @@ public class MetacheatUI {
|
|||
if (cheatEngine == null) {
|
||||
return;
|
||||
}
|
||||
Emulator.withComputer(c -> {
|
||||
c.getMotherboard().whileSuspended(() -> {
|
||||
if (animationTimer == null) {
|
||||
animationTimer = new ScheduledThreadPoolExecutor(1);
|
||||
}
|
||||
Emulator.whileSuspended(c-> {
|
||||
if (animationTimer == null) {
|
||||
animationTimer = new ScheduledThreadPoolExecutor(1);
|
||||
}
|
||||
|
||||
if (animationFuture != null) {
|
||||
animationFuture.cancel(false);
|
||||
}
|
||||
if (animationFuture != null) {
|
||||
animationFuture.cancel(false);
|
||||
}
|
||||
|
||||
animationFuture = animationTimer.scheduleAtFixedRate(this::processMemoryViewUpdates, FRAME_RATE, FRAME_RATE, TimeUnit.MILLISECONDS);
|
||||
animationFuture = animationTimer.scheduleAtFixedRate(this::processMemoryViewUpdates, FRAME_RATE, FRAME_RATE, TimeUnit.MILLISECONDS);
|
||||
|
||||
cheatEngine.initMemoryView();
|
||||
int pixelsPerBlock = 16 * MEMORY_BOX_TOTAL_SIZE;
|
||||
memoryViewColumns = (int) (memoryViewPane.getWidth() / pixelsPerBlock) * 16;
|
||||
memoryViewRows = ((cheatEngine.getEndAddress() - cheatEngine.getStartAddress()) / memoryViewColumns) + 1;
|
||||
double canvasHeight = memoryViewRows * MEMORY_BOX_TOTAL_SIZE * drawScale;
|
||||
cheatEngine.initMemoryView();
|
||||
int pixelsPerBlock = 16 * MEMORY_BOX_TOTAL_SIZE;
|
||||
memoryViewColumns = (int) (memoryViewPane.getWidth() / pixelsPerBlock) * 16;
|
||||
memoryViewRows = ((cheatEngine.getEndAddress() - cheatEngine.getStartAddress()) / memoryViewColumns) + 1;
|
||||
double canvasHeight = memoryViewRows * MEMORY_BOX_TOTAL_SIZE * drawScale;
|
||||
|
||||
memoryViewContents.setPrefHeight(canvasHeight);
|
||||
memoryViewCanvas.setHeight(canvasHeight);
|
||||
GraphicsContext context = memoryViewCanvas.getGraphicsContext2D();
|
||||
context.setFill(Color.rgb(40, 40, 40));
|
||||
context.fillRect(0, 0, memoryViewCanvas.getWidth(), memoryViewCanvas.getHeight());
|
||||
for (int addr = cheatEngine.getStartAddress(); addr <= cheatEngine.getEndAddress(); addr++) {
|
||||
int col = (addr - cheatEngine.getStartAddress()) % memoryViewColumns;
|
||||
int row = (addr - cheatEngine.getStartAddress()) / memoryViewColumns;
|
||||
MemoryCell cell = cheatEngine.getMemoryCell(addr);
|
||||
cell.setRect(
|
||||
(int) (col * MEMORY_BOX_TOTAL_SIZE * drawScale),
|
||||
(int) (row * MEMORY_BOX_TOTAL_SIZE * drawScale),
|
||||
(int) (MEMORY_BOX_SIZE * drawScale),
|
||||
(int) (MEMORY_BOX_SIZE * drawScale));
|
||||
redrawNodes.add(cell);
|
||||
}
|
||||
MemoryCell.setListener((javafx.beans.value.ObservableValue<? extends jace.cheat.MemoryCell> prop, jace.cheat.MemoryCell oldCell, jace.cheat.MemoryCell newCell) -> {
|
||||
redrawNodes.add(newCell);
|
||||
});
|
||||
|
||||
setZoom(1/drawScale);
|
||||
memoryViewContents.setPrefHeight(canvasHeight);
|
||||
memoryViewCanvas.setHeight(canvasHeight);
|
||||
GraphicsContext context = memoryViewCanvas.getGraphicsContext2D();
|
||||
context.setFill(Color.rgb(40, 40, 40));
|
||||
context.fillRect(0, 0, memoryViewCanvas.getWidth(), memoryViewCanvas.getHeight());
|
||||
for (int addr = cheatEngine.getStartAddress(); addr <= cheatEngine.getEndAddress(); addr++) {
|
||||
int col = (addr - cheatEngine.getStartAddress()) % memoryViewColumns;
|
||||
int row = (addr - cheatEngine.getStartAddress()) / memoryViewColumns;
|
||||
MemoryCell cell = cheatEngine.getMemoryCell(addr);
|
||||
cell.setRect(
|
||||
(int) (col * MEMORY_BOX_TOTAL_SIZE * drawScale),
|
||||
(int) (row * MEMORY_BOX_TOTAL_SIZE * drawScale),
|
||||
(int) (MEMORY_BOX_SIZE * drawScale),
|
||||
(int) (MEMORY_BOX_SIZE * drawScale));
|
||||
redrawNodes.add(cell);
|
||||
}
|
||||
MemoryCell.setListener((javafx.beans.value.ObservableValue<? extends jace.cheat.MemoryCell> prop, jace.cheat.MemoryCell oldCell, jace.cheat.MemoryCell newCell) -> {
|
||||
redrawNodes.add(newCell);
|
||||
});
|
||||
|
||||
setZoom(1/drawScale);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ module lawlesslegends {
|
|||
requires java.desktop;
|
||||
requires java.datatransfer;
|
||||
requires java.scripting;
|
||||
requires static java.compiler;
|
||||
requires javafx.fxmlEmpty;
|
||||
requires javafx.fxml;
|
||||
requires javafx.swing;
|
||||
|
@ -46,5 +47,8 @@ module lawlesslegends {
|
|||
|
||||
uses javax.sound.sampled.SourceDataLine;
|
||||
|
||||
provides javax.annotation.processing.Processor with jace.config.InvokableActionAnnotationProcessor;
|
||||
|
||||
exports jace;
|
||||
exports jace.config;
|
||||
}
|
||||
|
|
|
@ -229,7 +229,11 @@
|
|||
"name":"jace.apple2e.VideoDHGR"
|
||||
},
|
||||
{
|
||||
"name":"jace.apple2e.VideoNTSC"
|
||||
"name":"jace.apple2e.VideoNTSC",
|
||||
"fields":[
|
||||
{"name":"enableVideo7"},
|
||||
{"name":"useTextPalette"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"jace.cheat.MetaCheat"
|
||||
|
@ -309,21 +313,33 @@
|
|||
{"name":"speedRatio"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"jace.core.Video",
|
||||
"fields":[
|
||||
{"name":"MIN_SCREEN_REFRESH"},
|
||||
{"name":"hblankOffsetX"},
|
||||
{"name":"hblankOffsetY"},
|
||||
{"name":"waitsPerCycle"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"jace.core.VideoWriter"
|
||||
},
|
||||
{
|
||||
"name":"jace.hardware.CardAppleMouse"
|
||||
"name":"jace.hardware.CardAppleMouse",
|
||||
"allPublicFields":true
|
||||
},
|
||||
{
|
||||
"name":"jace.hardware.CardDiskII",
|
||||
"allPublicFields":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":["jace.core.Computer"] }]
|
||||
},
|
||||
{
|
||||
"name":"jace.hardware.CardExt80Col"
|
||||
},
|
||||
{
|
||||
"name":"jace.hardware.CardHayesMicromodem"
|
||||
"name":"jace.hardware.CardHayesMicromodem",
|
||||
"allPublicFields":true
|
||||
},
|
||||
{
|
||||
"name":"jace.hardware.CardMockingboard",
|
||||
|
@ -331,17 +347,21 @@
|
|||
},
|
||||
{
|
||||
"name":"jace.hardware.CardRamFactor",
|
||||
"allPublicFields":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":["jace.core.Computer"] }]
|
||||
},
|
||||
{
|
||||
"name":"jace.hardware.CardRamworks",
|
||||
"allPublicFields":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":["jace.core.Computer"] }]
|
||||
},
|
||||
{
|
||||
"name":"jace.hardware.CardSSC"
|
||||
"name":"jace.hardware.CardSSC",
|
||||
"allPublicFields":true
|
||||
},
|
||||
{
|
||||
"name":"jace.hardware.CardThunderclock"
|
||||
"name":"jace.hardware.CardThunderclock",
|
||||
"allPublicFields":true
|
||||
},
|
||||
{
|
||||
"name":"jace.hardware.Joystick",
|
||||
|
@ -369,6 +389,7 @@
|
|||
},
|
||||
{
|
||||
"name":"jace.hardware.massStorage.CardMassStorage",
|
||||
"allPublicFields":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":["jace.core.Computer"] }]
|
||||
},
|
||||
{
|
||||
|
@ -393,6 +414,7 @@
|
|||
{"name":"newApplesoftBasicClicked","parameterTypes":["javafx.event.ActionEvent"] },
|
||||
{"name":"newApplesoftBasicFromMemoryClicked","parameterTypes":["javafx.event.ActionEvent"] },
|
||||
{"name":"newAssemblyListingClicked","parameterTypes":["javafx.event.ActionEvent"] },
|
||||
{"name":"newHexdataClicked","parameterTypes":["javafx.event.ActionEvent"] },
|
||||
{"name":"onCloseClicked","parameterTypes":["javafx.event.ActionEvent"] },
|
||||
{"name":"testCompileClicked","parameterTypes":["javafx.event.ActionEvent"] },
|
||||
{"name":"viewCompilerOutputClicked","parameterTypes":["javafx.event.ActionEvent"] }
|
||||
|
@ -416,6 +438,7 @@
|
|||
},
|
||||
{
|
||||
"name":"jace.lawless.LawlessVideo",
|
||||
"allPublicFields":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":["jace.core.Computer"] }]
|
||||
},
|
||||
{
|
||||
|
@ -907,7 +930,8 @@
|
|||
"methods":[
|
||||
{"name":"<init>","parameterTypes":[] },
|
||||
{"name":"setVgrow","parameterTypes":["javafx.scene.Node","javafx.scene.layout.Priority"] }
|
||||
]
|
||||
],
|
||||
"queriedMethods":[{"name":"getVgrow","parameterTypes":["javafx.scene.Node"] }]
|
||||
},
|
||||
{
|
||||
"name":"javafx.scene.shape.LineTo"
|
||||
|
|
Loading…
Reference in New Issue
Block a user