2017-12-28 16:40:15 +00:00
|
|
|
/*
|
|
|
|
* To change this license header, choose License Headers in Project Properties.
|
|
|
|
* To change this template file, choose Tools | Templates
|
|
|
|
* and open the template in the editor.
|
|
|
|
*/
|
|
|
|
package jace;
|
|
|
|
|
|
|
|
import com.sun.glass.ui.Application;
|
|
|
|
import jace.core.Card;
|
|
|
|
import jace.core.Computer;
|
2018-01-12 03:22:00 +00:00
|
|
|
import jace.core.Motherboard;
|
2018-01-11 08:21:20 +00:00
|
|
|
import jace.core.Utility;
|
2017-12-28 16:40:15 +00:00
|
|
|
import jace.library.MediaCache;
|
|
|
|
import jace.library.MediaConsumer;
|
|
|
|
import jace.library.MediaConsumerParent;
|
|
|
|
import jace.library.MediaEntry;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.net.URI;
|
|
|
|
import java.net.URL;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Optional;
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import java.util.concurrent.Executors;
|
|
|
|
import java.util.concurrent.ScheduledExecutorService;
|
|
|
|
import java.util.concurrent.ScheduledFuture;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import java.util.logging.Level;
|
|
|
|
import java.util.logging.Logger;
|
2017-12-29 07:50:53 +00:00
|
|
|
import javafx.application.Platform;
|
2017-12-30 00:19:48 +00:00
|
|
|
import javafx.beans.binding.NumberBinding;
|
|
|
|
import javafx.beans.binding.When;
|
|
|
|
import javafx.beans.property.BooleanProperty;
|
|
|
|
import javafx.beans.property.SimpleBooleanProperty;
|
2018-01-12 00:56:01 +00:00
|
|
|
import javafx.beans.value.ChangeListener;
|
|
|
|
import javafx.beans.value.ObservableValue;
|
2017-12-28 16:40:15 +00:00
|
|
|
import javafx.event.EventHandler;
|
|
|
|
import javafx.fxml.FXML;
|
|
|
|
import javafx.geometry.Insets;
|
2018-01-11 08:21:20 +00:00
|
|
|
import javafx.scene.Node;
|
|
|
|
import javafx.scene.Parent;
|
|
|
|
import javafx.scene.control.Button;
|
2017-12-28 16:40:15 +00:00
|
|
|
import javafx.scene.control.Label;
|
2018-01-11 08:21:20 +00:00
|
|
|
import javafx.scene.control.Slider;
|
2017-12-28 16:40:15 +00:00
|
|
|
import javafx.scene.effect.DropShadow;
|
|
|
|
import javafx.scene.image.ImageView;
|
|
|
|
import javafx.scene.input.DragEvent;
|
|
|
|
import javafx.scene.input.KeyEvent;
|
|
|
|
import javafx.scene.input.MouseEvent;
|
|
|
|
import javafx.scene.input.TransferMode;
|
|
|
|
import javafx.scene.layout.AnchorPane;
|
|
|
|
import javafx.scene.layout.Background;
|
|
|
|
import javafx.scene.layout.BackgroundFill;
|
2018-01-11 08:21:20 +00:00
|
|
|
import javafx.scene.layout.BorderPane;
|
2017-12-28 16:40:15 +00:00
|
|
|
import javafx.scene.layout.CornerRadii;
|
|
|
|
import javafx.scene.layout.HBox;
|
|
|
|
import javafx.scene.layout.StackPane;
|
|
|
|
import javafx.scene.paint.Color;
|
|
|
|
import javafx.stage.Stage;
|
2018-01-12 00:56:01 +00:00
|
|
|
import javafx.util.StringConverter;
|
2017-12-28 16:40:15 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @author blurry
|
|
|
|
*/
|
|
|
|
public class JaceUIController {
|
|
|
|
|
|
|
|
@FXML
|
|
|
|
private URL location;
|
|
|
|
|
|
|
|
@FXML
|
|
|
|
private AnchorPane rootPane;
|
|
|
|
|
|
|
|
@FXML
|
|
|
|
private StackPane stackPane;
|
|
|
|
|
|
|
|
@FXML
|
|
|
|
private HBox notificationBox;
|
|
|
|
|
|
|
|
@FXML
|
|
|
|
private ImageView appleScreen;
|
|
|
|
|
2018-01-11 08:21:20 +00:00
|
|
|
@FXML
|
|
|
|
private BorderPane controlOverlay;
|
|
|
|
|
|
|
|
@FXML
|
2018-01-12 00:56:01 +00:00
|
|
|
private Slider speedSlider;
|
|
|
|
|
2017-12-28 16:40:15 +00:00
|
|
|
Computer computer;
|
|
|
|
|
2018-01-12 00:56:01 +00:00
|
|
|
private final BooleanProperty aspectRatioCorrectionEnabled = new SimpleBooleanProperty(false);
|
2017-12-30 00:19:48 +00:00
|
|
|
|
2017-12-28 16:40:15 +00:00
|
|
|
@FXML
|
|
|
|
void initialize() {
|
|
|
|
assert rootPane != null : "fx:id=\"rootPane\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
|
|
|
assert stackPane != null : "fx:id=\"stackPane\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
|
|
|
assert notificationBox != null : "fx:id=\"notificationBox\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
|
|
|
assert appleScreen != null : "fx:id=\"appleScreen\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
2018-01-11 08:21:20 +00:00
|
|
|
controlOverlay.setVisible(false);
|
2017-12-30 00:19:48 +00:00
|
|
|
NumberBinding aspectCorrectedWidth = rootPane.heightProperty().multiply(3.0).divide(2.0);
|
|
|
|
NumberBinding width = new When(
|
|
|
|
aspectRatioCorrectionEnabled.and(aspectCorrectedWidth.lessThan(rootPane.widthProperty()))
|
|
|
|
).then(aspectCorrectedWidth).otherwise(rootPane.widthProperty());
|
|
|
|
appleScreen.fitWidthProperty().bind(width);
|
2017-12-28 16:40:15 +00:00
|
|
|
appleScreen.fitHeightProperty().bind(rootPane.heightProperty());
|
2017-12-29 17:10:27 +00:00
|
|
|
appleScreen.setVisible(false);
|
2017-12-28 16:40:15 +00:00
|
|
|
rootPane.setOnDragEntered(this::processDragEnteredEvent);
|
|
|
|
rootPane.setOnDragExited(this::processDragExitedEvent);
|
2017-12-29 17:10:27 +00:00
|
|
|
rootPane.setBackground(new Background(new BackgroundFill(Color.BLACK, null, null)));
|
2018-01-11 08:21:20 +00:00
|
|
|
rootPane.setOnMouseEntered(this::showControlOverlay);
|
|
|
|
rootPane.setOnMouseExited(this::hideControlOverlay);
|
|
|
|
}
|
2018-01-12 00:56:01 +00:00
|
|
|
|
2018-01-11 08:21:20 +00:00
|
|
|
private void showControlOverlay(MouseEvent evt) {
|
|
|
|
if (!evt.isPrimaryButtonDown() && !evt.isSecondaryButtonDown()) {
|
|
|
|
controlOverlay.setVisible(true);
|
|
|
|
}
|
|
|
|
}
|
2018-01-12 00:56:01 +00:00
|
|
|
|
2018-01-11 08:21:20 +00:00
|
|
|
private void hideControlOverlay(MouseEvent evt) {
|
|
|
|
controlOverlay.setVisible(false);
|
|
|
|
}
|
2018-01-12 03:22:00 +00:00
|
|
|
|
2018-01-12 00:56:01 +00:00
|
|
|
private double convertSpeedToRatio(Double setting) {
|
|
|
|
if (setting < 1.0) {
|
|
|
|
return 0.5;
|
|
|
|
} else if (setting == 1.0) {
|
|
|
|
return 1.0;
|
|
|
|
} else if (setting >= 10) {
|
|
|
|
return Double.MAX_VALUE;
|
|
|
|
} else {
|
2018-01-12 03:22:00 +00:00
|
|
|
double val = Math.pow(2.0, (setting - 1.0) / 1.5);
|
2018-01-12 00:56:01 +00:00
|
|
|
val = Math.floor(val * 2.0) / 2.0;
|
|
|
|
if (val > 2.0) {
|
|
|
|
val = Math.floor(val);
|
|
|
|
}
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-11 08:21:20 +00:00
|
|
|
private void connectControls(Stage primaryStage) {
|
|
|
|
connectButtons(controlOverlay);
|
2018-01-12 00:56:01 +00:00
|
|
|
if (computer.getKeyboard() != null) {
|
|
|
|
EventHandler<KeyEvent> keyboardHandler = computer.getKeyboard().getListener();
|
|
|
|
primaryStage.setOnShowing(evt -> computer.getKeyboard().resetState());
|
|
|
|
rootPane.setOnKeyPressed(keyboardHandler);
|
|
|
|
rootPane.setOnKeyReleased(keyboardHandler);
|
|
|
|
rootPane.setFocusTraversable(true);
|
|
|
|
}
|
|
|
|
speedSlider.setValue(1.0);
|
|
|
|
speedSlider.setMinorTickCount(0);
|
|
|
|
speedSlider.setMajorTickUnit(1);
|
|
|
|
speedSlider.setLabelFormatter(new StringConverter<Double>() {
|
|
|
|
@Override
|
|
|
|
public String toString(Double val) {
|
|
|
|
if (val < 1.0) {
|
|
|
|
return "Half";
|
|
|
|
} else if (val >= 10.0) {
|
|
|
|
return "∞";
|
|
|
|
}
|
|
|
|
double v = convertSpeedToRatio(val);
|
|
|
|
if (v != Math.floor(v)) {
|
|
|
|
return String.valueOf(v) + "x";
|
|
|
|
} else {
|
2018-01-12 03:22:00 +00:00
|
|
|
return String.valueOf((int) v) + "x";
|
2018-01-12 00:56:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Double fromString(String string) {
|
|
|
|
return 1.0;
|
2018-01-11 08:21:20 +00:00
|
|
|
}
|
2018-01-12 00:56:01 +00:00
|
|
|
});
|
2018-01-12 03:22:00 +00:00
|
|
|
speedSlider.valueProperty().addListener((val, oldValue, newValue) -> setSpeed(newValue.doubleValue()));
|
2018-01-11 08:21:20 +00:00
|
|
|
}
|
2018-01-12 03:22:00 +00:00
|
|
|
|
2018-01-11 08:21:20 +00:00
|
|
|
private void connectButtons(Node n) {
|
|
|
|
if (n instanceof Button) {
|
|
|
|
Button button = (Button) n;
|
|
|
|
Runnable action = Utility.getNamedInvokableAction(button.getText());
|
|
|
|
button.setOnMouseClicked(evt -> action.run());
|
|
|
|
} else if (n instanceof Parent) {
|
|
|
|
for (Node child : ((Parent) n).getChildrenUnmodifiable()) {
|
|
|
|
connectButtons(child);
|
|
|
|
}
|
|
|
|
}
|
2017-12-28 16:40:15 +00:00
|
|
|
}
|
2018-01-12 03:22:00 +00:00
|
|
|
|
2018-01-12 00:56:01 +00:00
|
|
|
private void setSpeed(double speed) {
|
|
|
|
double speedRatio = convertSpeedToRatio(speed);
|
|
|
|
if (speedRatio > 100.0) {
|
|
|
|
Emulator.computer.getMotherboard().maxspeed = true;
|
2018-01-12 03:22:00 +00:00
|
|
|
Motherboard.cpuPerClock = 3;
|
2018-01-12 00:56:01 +00:00
|
|
|
} else {
|
2018-01-12 03:22:00 +00:00
|
|
|
if (speedRatio > 25) {
|
|
|
|
Motherboard.cpuPerClock = 2;
|
|
|
|
} else {
|
|
|
|
Motherboard.cpuPerClock = 1;
|
|
|
|
}
|
2018-01-12 00:56:01 +00:00
|
|
|
Emulator.computer.getMotherboard().maxspeed = false;
|
|
|
|
Emulator.computer.getMotherboard().speedRatio = (int) (speedRatio * 100);
|
|
|
|
}
|
|
|
|
Emulator.computer.getMotherboard().reconfigure();
|
|
|
|
}
|
2017-12-28 16:40:15 +00:00
|
|
|
|
2017-12-30 00:19:48 +00:00
|
|
|
public void toggleAspectRatio() {
|
|
|
|
setAspectRatioEnabled(aspectRatioCorrectionEnabled.not().get());
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setAspectRatioEnabled(boolean enabled) {
|
|
|
|
aspectRatioCorrectionEnabled.set(enabled);
|
|
|
|
}
|
|
|
|
|
2017-12-28 16:40:15 +00:00
|
|
|
public void connectComputer(Computer computer, Stage primaryStage) {
|
2017-12-29 07:50:53 +00:00
|
|
|
if (computer == null) {
|
|
|
|
return;
|
|
|
|
}
|
2017-12-28 16:40:15 +00:00
|
|
|
this.computer = computer;
|
2017-12-29 07:50:53 +00:00
|
|
|
Platform.runLater(() -> {
|
2018-01-11 08:21:20 +00:00
|
|
|
connectControls(primaryStage);
|
2017-12-29 07:50:53 +00:00
|
|
|
appleScreen.setImage(computer.getVideo().getFrameBuffer());
|
2017-12-29 17:10:27 +00:00
|
|
|
appleScreen.setVisible(true);
|
2017-12-29 07:50:53 +00:00
|
|
|
rootPane.requestFocus();
|
|
|
|
});
|
2017-12-28 16:40:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void processDragEnteredEvent(DragEvent evt) {
|
|
|
|
MediaEntry media = null;
|
|
|
|
if (evt.getDragboard().hasFiles()) {
|
|
|
|
media = MediaCache.getMediaFromFile(getDraggedFile(evt.getDragboard().getFiles()));
|
|
|
|
} else if (evt.getDragboard().hasUrl()) {
|
|
|
|
media = MediaCache.getMediaFromUrl(evt.getDragboard().getUrl());
|
|
|
|
} else if (evt.getDragboard().hasString()) {
|
|
|
|
String path = evt.getDragboard().getString();
|
|
|
|
try {
|
|
|
|
URI.create(path);
|
|
|
|
media = MediaCache.getMediaFromUrl(path);
|
|
|
|
} catch (IllegalArgumentException ex) {
|
|
|
|
File f = new File(path);
|
|
|
|
if (f.exists()) {
|
|
|
|
media = MediaCache.getMediaFromFile(f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (media != null) {
|
|
|
|
evt.acceptTransferModes(TransferMode.LINK, TransferMode.COPY);
|
|
|
|
startDragEvent(media);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void processDragExitedEvent(DragEvent evt) {
|
|
|
|
endDragEvent();
|
|
|
|
}
|
|
|
|
|
|
|
|
private File getDraggedFile(List<File> files) {
|
|
|
|
if (files == null || files.isEmpty()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
for (File f : files) {
|
|
|
|
if (f.exists()) {
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
HBox drivePanel;
|
|
|
|
|
|
|
|
private void startDragEvent(MediaEntry media) {
|
|
|
|
List<MediaConsumer> consumers = getMediaConsumers();
|
|
|
|
drivePanel = new HBox();
|
|
|
|
consumers.stream()
|
|
|
|
.filter((consumer) -> (consumer.isAccepted(media, media.files.get(0))))
|
|
|
|
.forEach((consumer) -> {
|
|
|
|
Label icon = consumer.getIcon().orElse(null);
|
|
|
|
if (icon == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
icon.setTextFill(Color.WHITE);
|
|
|
|
icon.setPadding(new Insets(2.0));
|
|
|
|
drivePanel.getChildren().add(icon);
|
|
|
|
icon.setOnDragOver(event -> {
|
|
|
|
event.acceptTransferModes(TransferMode.ANY);
|
|
|
|
event.consume();
|
|
|
|
});
|
|
|
|
icon.setOnDragDropped(event -> {
|
|
|
|
System.out.println("Dropping media on " + icon.getText());
|
|
|
|
try {
|
|
|
|
computer.pause();
|
|
|
|
consumer.insertMedia(media, media.files.get(0));
|
|
|
|
computer.resume();
|
|
|
|
event.setDropCompleted(true);
|
|
|
|
event.consume();
|
|
|
|
} catch (IOException ex) {
|
|
|
|
Logger.getLogger(JaceUIController.class.getName()).log(Level.SEVERE, null, ex);
|
|
|
|
}
|
|
|
|
endDragEvent();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
stackPane.getChildren().add(drivePanel);
|
|
|
|
drivePanel.setLayoutX(10);
|
|
|
|
drivePanel.setLayoutY(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void endDragEvent() {
|
|
|
|
stackPane.getChildren().remove(drivePanel);
|
|
|
|
drivePanel.getChildren().stream().forEach((n) -> {
|
|
|
|
n.setOnDragDropped(null);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private List<MediaConsumer> getMediaConsumers() {
|
|
|
|
List<MediaConsumer> consumers = new ArrayList<>();
|
2017-12-29 19:46:27 +00:00
|
|
|
consumers.add(Emulator.computer.getUpgradeHandler());
|
|
|
|
if (Emulator.logic.showDrives) {
|
|
|
|
for (Optional<Card> card : computer.memory.getAllCards()) {
|
|
|
|
card.filter(c -> c instanceof MediaConsumerParent).ifPresent(parent -> {
|
|
|
|
consumers.addAll(Arrays.asList(((MediaConsumerParent) parent).getConsumers()));
|
|
|
|
});
|
|
|
|
}
|
2017-12-28 16:40:15 +00:00
|
|
|
}
|
|
|
|
return consumers;
|
|
|
|
}
|
|
|
|
|
|
|
|
Map<Label, Long> iconTTL = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
void addIndicator(Label icon) {
|
|
|
|
addIndicator(icon, 250);
|
|
|
|
}
|
|
|
|
|
|
|
|
void addIndicator(Label icon, long TTL) {
|
|
|
|
if (!iconTTL.containsKey(icon)) {
|
|
|
|
Application.invokeLater(() -> {
|
|
|
|
if (!notificationBox.getChildren().contains(icon)) {
|
|
|
|
notificationBox.getChildren().add(icon);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
trackTTL(icon, TTL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void removeIndicator(Label icon) {
|
|
|
|
Application.invokeLater(() -> {
|
|
|
|
notificationBox.getChildren().remove(icon);
|
|
|
|
iconTTL.remove(icon);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
ScheduledExecutorService notificationExecutor = Executors.newSingleThreadScheduledExecutor();
|
|
|
|
ScheduledFuture ttlCleanupTask = null;
|
|
|
|
|
|
|
|
private void trackTTL(Label icon, long TTL) {
|
|
|
|
iconTTL.put(icon, System.currentTimeMillis() + TTL);
|
|
|
|
|
|
|
|
if (ttlCleanupTask == null || ttlCleanupTask.isCancelled()) {
|
|
|
|
ttlCleanupTask = notificationExecutor.scheduleWithFixedDelay(this::processTTL, 1, 100, TimeUnit.MILLISECONDS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void processTTL() {
|
|
|
|
Long now = System.currentTimeMillis();
|
|
|
|
iconTTL.keySet().stream()
|
|
|
|
.filter((icon) -> (iconTTL.get(icon) <= now))
|
|
|
|
.forEach((icon) -> {
|
|
|
|
removeIndicator(icon);
|
|
|
|
});
|
|
|
|
if (iconTTL.isEmpty()) {
|
|
|
|
ttlCleanupTask.cancel(true);
|
|
|
|
ttlCleanupTask = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void addMouseListener(EventHandler<MouseEvent> handler) {
|
|
|
|
appleScreen.addEventHandler(MouseEvent.ANY, handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void removeMouseListener(EventHandler<MouseEvent> handler) {
|
|
|
|
appleScreen.removeEventHandler(MouseEvent.ANY, handler);
|
|
|
|
}
|
2017-12-29 19:46:27 +00:00
|
|
|
|
2017-12-28 16:40:15 +00:00
|
|
|
Label currentNotification = null;
|
2017-12-29 19:46:27 +00:00
|
|
|
|
2017-12-28 16:40:15 +00:00
|
|
|
public void displayNotification(String message) {
|
|
|
|
Label oldNotification = currentNotification;
|
|
|
|
Label notification = new Label(message);
|
|
|
|
currentNotification = notification;
|
|
|
|
notification.setEffect(new DropShadow(2.0, Color.BLACK));
|
|
|
|
notification.setTextFill(Color.WHITE);
|
2017-12-29 19:46:27 +00:00
|
|
|
notification.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 80, 0.7), new CornerRadii(5.0), new Insets(-5.0))));
|
|
|
|
Application.invokeLater(() -> {
|
2017-12-28 16:40:15 +00:00
|
|
|
stackPane.getChildren().remove(oldNotification);
|
|
|
|
stackPane.getChildren().add(notification);
|
|
|
|
});
|
2017-12-29 19:46:27 +00:00
|
|
|
|
|
|
|
notificationExecutor.schedule(() -> {
|
|
|
|
Application.invokeLater(() -> {
|
2017-12-28 16:40:15 +00:00
|
|
|
stackPane.getChildren().remove(notification);
|
2017-12-29 19:46:27 +00:00
|
|
|
});
|
2017-12-28 16:40:15 +00:00
|
|
|
}, 4, TimeUnit.SECONDS);
|
|
|
|
}
|
|
|
|
}
|