diff --git a/app/pom.xml b/app/pom.xml
index d3311e9..8d79b56 100644
--- a/app/pom.xml
+++ b/app/pom.xml
@@ -36,12 +36,6 @@
javafx-fxml
${javafx.version}
-
-
- org.controlsfx
- controlsfx
- 11.1.2
-
cn.navclub
nes4j-bin
diff --git a/app/src/main/java/cn/navclub/nes4j/app/control/IconPopup.java b/app/src/main/java/cn/navclub/nes4j/app/control/IconPopup.java
new file mode 100644
index 0000000..063b3e3
--- /dev/null
+++ b/app/src/main/java/cn/navclub/nes4j/app/control/IconPopup.java
@@ -0,0 +1,72 @@
+package cn.navclub.nes4j.app.control;
+
+import cn.navclub.nes4j.app.control.skin.IconPopupSkin;
+import javafx.animation.FadeTransition;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.scene.control.PopupControl;
+import javafx.scene.control.Skin;
+import javafx.scene.image.Image;
+import javafx.stage.Screen;
+import javafx.util.Duration;
+
+/**
+ * A icon popup implement.
+ *
+ * @author GZYangKui
+ */
+public class IconPopup extends PopupControl {
+ private final FadeTransition transition;
+ private final ObjectProperty image;
+
+ public IconPopup() {
+ this.setAutoFix(true);
+ this.transition = new FadeTransition();
+ this.transition.setToValue(0);
+ this.transition.setFromValue(1.0);
+ this.transition.setOnFinished(event -> this.hide());
+ this.transition.setDuration(Duration.millis(1000));
+ this.image = new SimpleObjectProperty<>(this, "image", null);
+
+ this.setOnShown(event -> {
+ this.transition.stop();
+ this.transition.setNode(this.getSkin().getNode());
+ this.transition.setDelay(Duration.millis(500));
+ this.transition.play();
+ this.calculateXY();
+ });
+ }
+
+ public IconPopup(Image image) {
+ this();
+ this.setImage(image);
+ }
+
+ @Override
+ protected Skin> createDefaultSkin() {
+ return new IconPopupSkin(this);
+ }
+
+ public Image getImage() {
+ return image.get();
+ }
+
+ public ObjectProperty imageProperty() {
+ return image;
+ }
+
+ public void setImage(Image image) {
+ this.image.set(image);
+ }
+
+ private void calculateXY() {
+ var screen = Screen.getPrimary();
+ var rect = screen.getVisualBounds();
+
+ var x = (rect.getWidth() - this.getWidth()) / 2;
+ var y = (rect.getHeight() - this.getHeight()) - 10;
+
+ this.setX(x);
+ this.setY(y);
+ }
+}
diff --git a/app/src/main/java/cn/navclub/nes4j/app/control/StatusIndicator.java b/app/src/main/java/cn/navclub/nes4j/app/control/StatusIndicator.java
deleted file mode 100644
index d577fd1..0000000
--- a/app/src/main/java/cn/navclub/nes4j/app/control/StatusIndicator.java
+++ /dev/null
@@ -1,104 +0,0 @@
-//package cn.navclub.nes4j.app.control;
-//
-//import cn.navclub.nes4j.app.assets.FXResource;
-//import javafx.animation.FadeTransition;
-//import javafx.beans.property.ObjectProperty;
-//import javafx.beans.property.SimpleObjectProperty;
-//import javafx.collections.ListChangeListener;
-//import javafx.scene.Node;
-//import javafx.scene.Parent;
-//
-//import javafx.scene.image.Image;
-//import javafx.scene.image.ImageView;
-//import javafx.scene.layout.VBox;
-//import javafx.util.Duration;
-//
-///**
-// * Custom status indicator control,current implement only support {@link Parent} and subclass.
-// *
-// * @author GZYangKui
-// */
-//public class StatusIndicator extends VBox {
-// private static final Image DEFAULT_IMAGE = FXResource.loadImage("empty.png");
-// private static final Image DEFAULT_ERROR_IMAGE = FXResource.loadImage("error.png");
-//
-// private final ImageView icon;
-// private ObjectProperty attach;
-// private final ObjectProperty type;
-// private ListChangeListener listChangeListener;
-//
-// public StatusIndicator() {
-// this.icon = new ImageView();
-// this.type = new SimpleObjectProperty<>(this, "type", null);
-//
-// this.getStyleClass().add("status-indicator");
-// this.getChildren().add(this.icon);
-//
-//
-// //listener type change
-// this.type.addListener(((observable, oldValue, newValue) -> {
-// var image = switch (newValue) {
-// case ERROR -> DEFAULT_ERROR_IMAGE;
-// case LOAD -> DEFAULT_LOAD_IMAGE;
-// default -> DEFAULT_IMAGE;
-// };
-// this.icon.setImage(image);
-// }));
-// }
-//
-// private ListChangeListener listChangeListener() {
-// return c -> {
-// var children = this.getAttach().getChildrenUnmodifiable();
-// if (children.size() > 0) {
-// transition.play();
-// } else {
-// this.setVisible(true);
-// }
-// };
-// }
-//
-// public Parent getAttach() {
-// return this.attachProperty().get();
-// }
-//
-// public ObjectProperty attachProperty() {
-// if (this.attach == null) {
-// this.attach = new SimpleObjectProperty<>(this, "attach", null);
-// }
-// return attach;
-// }
-//
-// public void setAttach(Parent attach) {
-// var oldValue = this.getAttach();
-// if (oldValue == attach) {
-// return;
-// }
-// if (oldValue != null) {
-// oldValue.getChildrenUnmodifiable().removeListener(this.listChangeListener);
-// }
-// this.listChangeListener = this.listChangeListener();
-// attach.getChildrenUnmodifiable().addListener(this.listChangeListener);
-// this.attachProperty().set(attach);
-// }
-//
-// public Type getType() {
-// return type.get();
-// }
-//
-// public ObjectProperty typeProperty() {
-// return type;
-// }
-//
-// public void setType(Type type) {
-// this.type.set(type);
-// }
-//
-// public enum Type {
-// //Empty
-// EMPTY,
-// //Load
-// LOAD,
-// //error
-// ERROR
-// }
-//}
diff --git a/app/src/main/java/cn/navclub/nes4j/app/control/skin/IconPopupSkin.java b/app/src/main/java/cn/navclub/nes4j/app/control/skin/IconPopupSkin.java
new file mode 100644
index 0000000..bc9d20f
--- /dev/null
+++ b/app/src/main/java/cn/navclub/nes4j/app/control/skin/IconPopupSkin.java
@@ -0,0 +1,44 @@
+package cn.navclub.nes4j.app.control.skin;
+
+import cn.navclub.nes4j.app.assets.FXResource;
+import cn.navclub.nes4j.app.control.IconPopup;
+import javafx.scene.Node;
+import javafx.scene.control.Skin;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.VBox;
+
+
+public class IconPopupSkin implements Skin {
+ private VBox node;
+ @SuppressWarnings("all")
+ private final ImageView icon;
+ private final IconPopup popup;
+
+ public IconPopupSkin(IconPopup popup) {
+ this.popup = popup;
+ this.node = new VBox();
+ this.icon = new ImageView();
+
+ this.icon.imageProperty().bind(popup.imageProperty());
+
+ this.node.getChildren().add(this.icon);
+ this.node.getStyleClass().add("text-popup");
+ this.node.getStylesheets().add(FXResource.loadStyleSheet("TextPopup.css"));
+ }
+
+ @Override
+ public IconPopup getSkinnable() {
+ return this.popup;
+ }
+
+ @Override
+ public Node getNode() {
+ return node;
+ }
+
+ @Override
+ public void dispose() {
+ this.node = null;
+ this.icon.imageProperty().unbind();
+ }
+}
diff --git a/app/src/main/java/cn/navclub/nes4j/app/view/GameWorld.java b/app/src/main/java/cn/navclub/nes4j/app/view/GameWorld.java
index 02707ff..c300aaa 100644
--- a/app/src/main/java/cn/navclub/nes4j/app/view/GameWorld.java
+++ b/app/src/main/java/cn/navclub/nes4j/app/view/GameWorld.java
@@ -3,6 +3,7 @@
import cn.navclub.nes4j.app.assets.FXResource;
import cn.navclub.nes4j.app.INes;
import cn.navclub.nes4j.app.audio.JavaXAudio;
+import cn.navclub.nes4j.app.control.IconPopup;
import cn.navclub.nes4j.app.service.TaskService;
import cn.navclub.nes4j.app.dialog.DHandle;
import cn.navclub.nes4j.app.event.FPSTracer;
@@ -24,6 +25,7 @@
import javafx.scene.control.*;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.WritableImage;
+import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
@@ -56,16 +58,17 @@ public class GameWorld extends Stage {
private volatile int fps;
private Debugger debugger;
private TaskService service;
+ private final IconPopup speedPopup;
public GameWorld() {
var scene = new Scene(FXResource.loadFXML(this));
this.scale = 3;
-
this.ctx = canvas.getGraphicsContext2D();
this.eventQueue = new LinkedBlockingDeque<>();
this.tracer = new FPSTracer(it -> this.fps = it);
+ this.speedPopup = new IconPopup(FXResource.loadImage("speed.png"));
this.intBuffer = IntBuffer.allocate(this.scale * this.scale);
this.image = new WritableImage(this.scale * Frame.width, this.scale * Frame.height);
@@ -74,7 +77,7 @@ public GameWorld() {
this.setHeight(600);
this.setScene(scene);
this.setResizable(false);
- this.getScene().getStylesheets().add(FXResource.loadStyleSheet("common.css"));
+ this.getScene().getStylesheets().add(FXResource.loadStyleSheet("Common.css"));
this.setOnCloseRequest(event -> this.dispose(null));
@@ -94,6 +97,15 @@ public GameWorld() {
}
}
}
+
+ //Change emulator speed
+ if (code == KeyCode.ADD || code == KeyCode.SUBTRACT) {
+ if (log.isDebugEnabled()) {
+ log.debug("Change ppu output frame action:{}", code);
+ }
+ this.instance.speed(code == KeyCode.ADD ? -1 : 1);
+ this.speedPopup.show(this);
+ }
});
}
diff --git a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/common.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/Common.css
similarity index 99%
rename from app/src/main/resources/cn/navclub/nes4j/app/assets/css/common.css
rename to app/src/main/resources/cn/navclub/nes4j/app/assets/css/Common.css
index 43bbb0a..b418faf 100644
--- a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/common.css
+++ b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/Common.css
@@ -16,7 +16,7 @@
/**Game hall text color**/
-nes4j-game-hall-text-fill: #000000;
/**Game hall list-cell select background color**/
- -nes4j-game-hall-list-cell-active: linear-gradient(to right, #D6E0F0, #DDE4EF, #E6EAEF, #EAEBED);
+ -nes4j-game-hall-list-cell-active: linear-gradient(to right, #b4cbf3, #c6d6ef, #cfddef, #e4e7ef);
/**Game hall list-cell select text fill*/
-nes4j-game-hall-list-cell-text: #5185fd;
/**Default list view select row background color**/
diff --git a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DException.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DException.css
index f7c724f..937dc3f 100644
--- a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DException.css
+++ b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DException.css
@@ -1,4 +1,4 @@
-@import "common.css";
+@import "Common.css";
.text-area {
-fx-padding: 0;
diff --git a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DHandle.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DHandle.css
index f8a9713..e5e15a0 100644
--- a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DHandle.css
+++ b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DHandle.css
@@ -1,4 +1,4 @@
-@import "common.css";
+@import "Common.css";
.box, .content {
-fx-spacing: 2em;
diff --git a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DNesHeaderStyle.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DNesHeaderStyle.css
index 2db39f1..128b9c2 100644
--- a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DNesHeaderStyle.css
+++ b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DNesHeaderStyle.css
@@ -1,4 +1,4 @@
-@import "common.css";
+@import "Common.css";
GridPane {
-fx-hgap: 1em;
diff --git a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DebuggerStyle.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DebuggerStyle.css
index 097056c..3acd8d3 100644
--- a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DebuggerStyle.css
+++ b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DebuggerStyle.css
@@ -1,4 +1,4 @@
-@import "common.css";
+@import "Common.css";
GridPane {
-fx-vgap: 1em;
diff --git a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/GameHallStyle.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/GameHallStyle.css
index b7fdd7c..bf76958 100644
--- a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/GameHallStyle.css
+++ b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/GameHallStyle.css
@@ -1,4 +1,4 @@
-@import "common.css";
+@import "Common.css";
.navbar *, .assort, .flow-pane .game-tray .label, .empty .label {
-fx-font-size: 1.4em;
@@ -53,6 +53,7 @@
}
.assort {
+ -fx-padding: 0;
-fx-background-insets: 0;
}
@@ -68,12 +69,12 @@
-fx-shape: 'M970.666667 213.333333H546.586667a10.573333 10.573333 0 0 1-7.54-3.126666L429.793333 100.953333A52.986667 52.986667 0 0 0 392.08 85.333333H96a53.393333 53.393333 0 0 0-53.333333 53.333334v704a53.393333 53.393333 0 0 0 53.333333 53.333333h874.666667a53.393333 53.393333 0 0 0 53.333333-53.333333V266.666667a53.393333 53.393333 0 0 0-53.333333-53.333334z m-275.866667 374.82c-25.486667 33.926667-71.333333 74.92-148.666667 132.913334a21.333333 21.333333 0 0 1-25.6 0c-77.333333-58-123.18-98.986667-148.666666-132.913334S341.333333 528.273333 341.333333 497.233333C341.333333 434.793333 392.126667 384 454.566667 384A112.893333 112.893333 0 0 1 533.333333 415.9 112.893333 112.893333 0 0 1 612.1 384c62.44 0 113.233333 50.793333 113.233333 113.233333 0 31.04-5.106667 57.08-30.533333 90.92z';
}
-.assort .list-cell {
+.assort .tree-cell {
-fx-background-color: -nes4j-game-hall-list-view;
-fx-focus-traversable: false;
}
-.assort .list-cell:selected {
+.assort .tree-cell:selected {
-fx-text-fill: -nes4j-game-hall-list-cell-text;
-fx-background-color: -nes4j-game-hall-list-cell-active;
}
diff --git a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/Nes4j.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/Nes4j.css
index 87b9082..e24a9ea 100644
--- a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/Nes4j.css
+++ b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/Nes4j.css
@@ -1,4 +1,4 @@
-@import "common.css";
+@import "Common.css";
.left-box, .right-box {
-fx-spacing: .5em;
diff --git a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/SystemPalette.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/SystemPalette.css
index 3098a08..0e1940f 100644
--- a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/SystemPalette.css
+++ b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/SystemPalette.css
@@ -1,4 +1,4 @@
-@import "common.css";
+@import "Common.css";
VBox {
-fx-spacing: 1em;
diff --git a/app/src/main/resources/cn/navclub/nes4j/app/assets/css/TextPopup.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/TextPopup.css
new file mode 100644
index 0000000..1405096
--- /dev/null
+++ b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/TextPopup.css
@@ -0,0 +1,9 @@
+@import "Common.css";
+
+.text-popup {
+ -fx-padding: 2em 3em;
+ -fx-alignment: CENTER;
+ -fx-border-radius: .5em;
+ -fx-background-radius: .5em;
+ -fx-background-color: rgba(0, 0, 0, .8);
+}
\ No newline at end of file
diff --git a/app/src/main/resources/cn/navclub/nes4j/app/assets/img/speed.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/speed.png
new file mode 100644
index 0000000..2910fa3
Binary files /dev/null and b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/speed.png differ
diff --git a/bin/src/main/java/cn/navclub/nes4j/bin/NES.java b/bin/src/main/java/cn/navclub/nes4j/bin/NES.java
index 3535a50..2f2793f 100644
--- a/bin/src/main/java/cn/navclub/nes4j/bin/NES.java
+++ b/bin/src/main/java/cn/navclub/nes4j/bin/NES.java
@@ -32,7 +32,6 @@ public class NES {
private final TCallback gameLoopCallback;
//cpu stall cycle
private int stall;
- @Setter
private int speed;
//APU mute
@Setter
@@ -157,7 +156,7 @@ public void stop() {
}
/**
- * 在debug模式下用于执行下一个断点所用
+ * Execute next break line
*/
public synchronized void release() {
if (this.thread == null) {
@@ -166,6 +165,20 @@ public synchronized void release() {
LockSupport.unpark(this.thread);
}
+ /**
+ * Manual change emulator speed
+ *
+ * @param span offset value
+ */
+ public int speed(int span) {
+ var temp = this.speed + span;
+ if (temp < 0) {
+ temp = 0;
+ }
+ this.speed = temp;
+ return this.speed;
+ }
+
public static class NESBuilder {
private File file;
private byte[] buffer;
diff --git a/build/icon/nes4j.ico b/build/icon/nes4j.ico
new file mode 100644
index 0000000..18e0e5e
Binary files /dev/null and b/build/icon/nes4j.ico differ
diff --git a/build/jpackage.sh b/build/jpackage.sh
index 41a9e29..b07cf3f 100644
--- a/build/jpackage.sh
+++ b/build/jpackage.sh
@@ -19,6 +19,7 @@ fi
PROGRAM_NAME="nes4j"
PROGRAM_ICON="icon/nes4j.png"
MAIN_CLASS="cn.navclub.nes4j.app/cn.navclub.nes4j.app.Launcher"
-
+CMD="jpackage -n $PROGRAM_NAME -p $1 -m $MAIN_CLASS --icon $PROGRAM_ICON --license-file ../LICENSE --linux-package-name $PROGRAM_NAME --linux-app-category game"
+echo "Jpackage command:$CMD"
# shellcheck disable=SC2091
-$(jpackage -n "$PROGRAM_NAME" -p "$1" -m "${MAIN_CLASS}" --icon "$PROGRAM_ICON" --license-file ../LICENSE --linux-package-name "$PROGRAM_NAME")
+$($CMD)