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)