Mouse support is back! :-D

This commit is contained in:
Brendan Robert 2015-03-15 01:53:56 -05:00
parent 2ad9c15ce2
commit 555f125a3b
4 changed files with 112 additions and 122 deletions

View File

@ -48,10 +48,12 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javafx.embed.swing.SwingFXUtils; import javafx.embed.swing.SwingFXUtils;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage; import javafx.stage.Stage;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@ -386,8 +388,12 @@ public class EmulatorUILogic implements Reconfigurable {
} }
static final Map<Object, Set<Label>> indicators = new HashMap<>(); static final Map<Object, Set<Label>> indicators = new HashMap<>();
static public void addIndicator(Object owner, Label icon) { static public void addIndicator(Object owner, Label icon) {
addIndicator(owner, icon, 250);
}
static public void addIndicator(Object owner, Label icon, long TTL) {
if (JaceApplication.singleton == null) return;
synchronized (indicators) { synchronized (indicators) {
Set<Label> ind = indicators.get(owner); Set<Label> ind = indicators.get(owner);
if (ind == null) { if (ind == null) {
@ -400,6 +406,7 @@ public class EmulatorUILogic implements Reconfigurable {
} }
static public void removeIndicator(Object owner, Label icon) { static public void removeIndicator(Object owner, Label icon) {
if (JaceApplication.singleton == null) return;
synchronized (indicators) { synchronized (indicators) {
Set<Label> ind = indicators.get(owner); Set<Label> ind = indicators.get(owner);
if (ind != null) { if (ind != null) {
@ -410,6 +417,7 @@ public class EmulatorUILogic implements Reconfigurable {
} }
static public void removeIndicators(Object owner) { static public void removeIndicators(Object owner) {
if (JaceApplication.singleton == null) return;
synchronized (indicators) { synchronized (indicators) {
Set<Label> ind = indicators.get(owner); Set<Label> ind = indicators.get(owner);
if (ind == null) { if (ind == null) {
@ -421,7 +429,19 @@ public class EmulatorUILogic implements Reconfigurable {
indicators.remove(owner); indicators.remove(owner);
} }
} }
static public void addMouseListener(EventHandler<MouseEvent> handler) {
if (JaceApplication.singleton != null) {
JaceApplication.singleton.controller.addMouseListener(handler);
}
}
static public void removeMouseListener(EventHandler<MouseEvent> handler) {
if (JaceApplication.singleton != null) {
JaceApplication.singleton.controller.removeMouseListener(handler);
}
}
@Override @Override
public String getName() { public String getName() {
return "Jace User Interface"; return "Jace User Interface";

View File

@ -19,13 +19,10 @@ import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
@ -33,16 +30,17 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.input.DragEvent; import javafx.scene.input.DragEvent;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode; import javafx.scene.input.TransferMode;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
@ -181,6 +179,9 @@ public class JaceUIController {
Map<Label, Long> iconTTL = new ConcurrentHashMap<>(); Map<Label, Long> iconTTL = new ConcurrentHashMap<>();
void addIndicator(Label icon) { void addIndicator(Label icon) {
addIndicator(icon, 250);
}
void addIndicator(Label icon, long TTL) {
if (!iconTTL.containsKey(icon)) { if (!iconTTL.containsKey(icon)) {
Application.invokeLater(()->{ Application.invokeLater(()->{
if (!notificationBox.getChildren().contains(icon)) { if (!notificationBox.getChildren().contains(icon)) {
@ -188,7 +189,7 @@ public class JaceUIController {
} }
}); });
} }
trackTTL(icon); trackTTL(icon, TTL);
} }
void removeIndicator(Label icon) { void removeIndicator(Label icon) {
@ -200,8 +201,8 @@ public class JaceUIController {
ScheduledExecutorService notificationExecutor = Executors.newSingleThreadScheduledExecutor(); ScheduledExecutorService notificationExecutor = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture ttlCleanupTask = null; ScheduledFuture ttlCleanupTask = null;
private void trackTTL(Label icon) { private void trackTTL(Label icon, long TTL) {
iconTTL.put(icon, System.currentTimeMillis()+250L); iconTTL.put(icon, System.currentTimeMillis()+TTL);
if (ttlCleanupTask == null || ttlCleanupTask.isCancelled()) { if (ttlCleanupTask == null || ttlCleanupTask.isCancelled()) {
ttlCleanupTask = notificationExecutor.scheduleWithFixedDelay(this::processTTL, 1, 100, TimeUnit.MILLISECONDS); ttlCleanupTask = notificationExecutor.scheduleWithFixedDelay(this::processTTL, 1, 100, TimeUnit.MILLISECONDS);
@ -210,15 +211,26 @@ public class JaceUIController {
private void processTTL() { private void processTTL() {
Long now = System.currentTimeMillis(); Long now = System.currentTimeMillis();
for (Iterator<Label> iterator = iconTTL.keySet().iterator(); iterator.hasNext();) { iconTTL.keySet().stream()
Label icon = iterator.next(); .filter((icon) -> (iconTTL.get(icon) <= now))
if (iconTTL.get(icon) <= now) { .forEach((icon) -> {
removeIndicator(icon); removeIndicator(icon);
} });
}
if (iconTTL.isEmpty()) { if (iconTTL.isEmpty()) {
ttlCleanupTask.cancel(true); ttlCleanupTask.cancel(true);
ttlCleanupTask = null; ttlCleanupTask = null;
} }
} }
public void addMouseListener(EventHandler<MouseEvent> handler) {
appleScreen.addEventHandler(MouseEvent.MOUSE_MOVED, handler);
appleScreen.addEventHandler(MouseEvent.MOUSE_PRESSED, handler);
appleScreen.addEventHandler(MouseEvent.MOUSE_RELEASED, handler);
}
public void removeMouseListener(EventHandler<MouseEvent> handler) {
appleScreen.removeEventHandler(MouseEvent.MOUSE_MOVED, handler);
appleScreen.removeEventHandler(MouseEvent.MOUSE_PRESSED, handler);
appleScreen.removeEventHandler(MouseEvent.MOUSE_RELEASED, handler);
}
} }

View File

@ -18,7 +18,7 @@
*/ */
package jace.hardware; package jace.hardware;
import jace.Emulator; import jace.EmulatorUILogic;
import jace.apple2e.MOS65C02; import jace.apple2e.MOS65C02;
import jace.apple2e.RAM128k; import jace.apple2e.RAM128k;
import jace.config.ConfigurableField; import jace.config.ConfigurableField;
@ -30,13 +30,14 @@ import jace.core.RAMEvent;
import jace.core.RAMEvent.TYPE; import jace.core.RAMEvent.TYPE;
import jace.state.Stateful; import jace.state.Stateful;
import jace.core.Utility; import jace.core.Utility;
import java.awt.Component; import javafx.event.EventHandler;
import java.awt.Dimension; import javafx.geometry.Bounds;
import java.awt.MouseInfo; import javafx.scene.Node;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
/** /**
* Apple Mouse interface implementation. This is fully compatible with several * Apple Mouse interface implementation. This is fully compatible with several
@ -46,8 +47,8 @@ import javafx.scene.control.Label;
*/ */
@Stateful @Stateful
@Name("Apple Mouse") @Name("Apple Mouse")
public class CardAppleMouse extends Card implements MouseListener { public class CardAppleMouse extends Card {
@Stateful @Stateful
public int mode; public int mode;
@Stateful @Stateful
@ -73,18 +74,18 @@ public class CardAppleMouse extends Card implements MouseListener {
@Stateful @Stateful
public int statusByte; public int statusByte;
@Stateful @Stateful
public Point lastMouseLocation; public Point2D lastMouseLocation;
@Stateful @Stateful
public Point clampMin = new Point(0, 0x03ff); public Rectangle2D clampWindow = new Rectangle2D(0, 0, 0x03ff, 0x03ff);
@Stateful
public Point clampMax = new Point(0, 0x03ff);
// By default, update 60 times a second -- roughly every VBL period (in theory) // By default, update 60 times a second -- roughly every VBL period (in theory)
@ConfigurableField(name = "Update frequency", shortName = "updateFreq", category = "Mouse", description = "# of CPU cycles between updates; affects polling and interrupt-based routines") @ConfigurableField(name = "Update frequency", shortName = "updateFreq", category = "Mouse", description = "# of CPU cycles between updates; affects polling and interrupt-based routines")
public static int CYCLES_PER_UPDATE = (int) (1020484L / 60L); public static int CYCLES_PER_UPDATE = (int) (1020484L / 60L);
@ConfigurableField(name = "Fullscreen fix", shortName = "fsfix", category = "Mouse", description = "If the mouse pointer is a little off when in fullscreen, this should fix it.") @ConfigurableField(name = "Fullscreen fix", shortName = "fsfix", category = "Mouse", description = "If the mouse pointer is a little off when in fullscreen, this should fix it.")
public boolean fullscreenFix = true; public boolean fullscreenFix = true;
Label mouseActive = Utility.loadIconLabel("input-mouse.png"); Label mouseActive = Utility.loadIconLabel("input-mouse.png");
public boolean movedSinceLastTick = false;
public boolean movedSinceLastRead = false;
public CardAppleMouse(Computer computer) { public CardAppleMouse(Computer computer) {
super(computer); super(computer);
} }
@ -100,6 +101,30 @@ public class CardAppleMouse extends Card implements MouseListener {
deactivateMouse(); deactivateMouse();
} }
EventHandler<MouseEvent> mouseHandler = this::processMouseEvent;
private void processMouseEvent(MouseEvent event) {
if (event.getEventType() == MouseEvent.MOUSE_MOVED) {
Node source = (Node) event.getSource();
updateLocation(event.getSceneX(), event.getSceneY(), source.getBoundsInLocal());
event.consume();
} else if (event.getEventType() == MouseEvent.MOUSE_PRESSED) {
mousePressed(event);
event.consume();
} else if (event.getEventType() == MouseEvent.MOUSE_RELEASED) {
mouseReleased(event);
event.consume();
}
}
private void updateLocation(double x, double y, Bounds bounds) {
double scaledX = x / bounds.getWidth();
double scaledY = y / bounds.getHeight();
lastMouseLocation = new Point2D(scaledX, scaledY);
movedSinceLastTick = true;
movedSinceLastRead = true;
}
/* /*
* Coded against this information * Coded against this information
* http://stason.org/TULARC/pc/apple2/programmer/012-How-do-I-write-programs-which-use-the-mouse.html * http://stason.org/TULARC/pc/apple2/programmer/012-How-do-I-write-programs-which-use-the-mouse.html
@ -346,7 +371,7 @@ public class CardAppleMouse extends Card implements MouseListener {
*/ */
private void initMouse() { private void initMouse() {
mouseActive.setText("Active"); mouseActive.setText("Active");
// Emulator.getFrame().addIndicator(this, mouseActive, 2000); EmulatorUILogic.addIndicator(this, mouseActive, 2000);
setClampWindowX(0, 0x3ff); setClampWindowX(0, 0x3ff);
setClampWindowY(0, 0x3ff); setClampWindowY(0, 0x3ff);
clearMouse(); clearMouse();
@ -376,7 +401,7 @@ public class CardAppleMouse extends Card implements MouseListener {
* Screen hole positions are updated * Screen hole positions are updated
*/ */
private void homeMouse() { private void homeMouse() {
lastMouseLocation = new Point(0, 0); lastMouseLocation = new Point2D(0, 0);
updateMouseState(); updateMouseState();
getCPU().C = 0; getCPU().C = 0;
} }
@ -386,10 +411,7 @@ public class CardAppleMouse extends Card implements MouseListener {
*/ */
private void activateMouse() { private void activateMouse() {
active = true; active = true;
Component drawingArea = Emulator.getScreen(); EmulatorUILogic.addMouseListener(mouseHandler);
if (drawingArea != null) {
drawingArea.addMouseListener(this);
}
} }
/* /*
@ -401,10 +423,7 @@ public class CardAppleMouse extends Card implements MouseListener {
interruptOnMove = false; interruptOnMove = false;
interruptOnPress = false; interruptOnPress = false;
interruptOnVBL = false; interruptOnVBL = false;
Component drawingArea = Emulator.getScreen(); EmulatorUILogic.removeMouseListener(mouseHandler);
if (drawingArea != null) {
drawingArea.removeMouseListener(this);
}
} }
@Override @Override
@ -437,12 +456,12 @@ public class CardAppleMouse extends Card implements MouseListener {
} }
} }
if (interruptOnMove) { if (interruptOnMove) {
Point currentMouseLocation = MouseInfo.getPointerInfo().getLocation(); if (movedSinceLastTick) {
if (!currentMouseLocation.equals(lastMouseLocation)) {
isInterrupt = true; isInterrupt = true;
getCPU().generateInterrupt(); getCPU().generateInterrupt();
} }
} }
movedSinceLastTick = false;
} }
@Override @Override
@ -456,58 +475,15 @@ public class CardAppleMouse extends Card implements MouseListener {
} }
private void updateMouseState() { private void updateMouseState() {
Component drawingArea = Emulator.getScreen(); double x = lastMouseLocation.getX();
if (drawingArea == null) { x *= clampWindow.getWidth();
return; x += clampWindow.getMinX();
} x = Math.min(Math.max(x, clampWindow.getMinX()), clampWindow.getMaxX());
// Graphics2D screen = (Graphics2D) computer.getVideo().getScreen();
// Point currentMouseLocation = MouseInfo.getPointerInfo().getLocation();
// Point topLeft = drawingArea.getLocationOnScreen();
Point currentMouseLocation = null; // = Emulator.getFrame().getContentPane().getMousePosition();
// if (currentMouseLocation == null) return;
// Point topLeft = drawingArea.getLocationOnScreen();
Point topLeft = new Point(0,0);
Dimension d = drawingArea.getBounds().getSize(); double y = lastMouseLocation.getY();
// if (screen.getTransform() != null) { y *= clampWindow.getHeight();
// d = new Dimension((int) (screen.getTransform().getScaleX() * d.width), y += clampWindow.getMinY();
// (int) (screen.getTransform().getScaleY() * d.height)); y = Math.min(Math.max(y, clampWindow.getMinY()), clampWindow.getMaxY());
// topLeft.x += screen.getTransform().getTranslateX();
// topLeft.y += screen.getTransform().getTranslateY();
// }
if (fullscreenFix) {
// if (Emulator.getFrame().isFullscreenActive()) {
// Toolkit t = Toolkit.getDefaultToolkit();
// topLeft.y -= t.getScreenInsets(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()).top;
// }
}
// Scale X and Y to the clamping range of the mouse (will this work for most software?)
double width = clampMax.x - clampMin.x;
double x = 0;
// double x = currentMouseLocation.getX() - topLeft.x;
x *= width;
x /= d.width;
x += clampMin.x;
if (x < clampMin.x) {
x = clampMin.x;
}
if (x > clampMax.x) {
x = clampMax.x;
}
double height = clampMax.y - clampMin.y;
double y = 0;
// double y = currentMouseLocation.getY() - topLeft.y;
y *= height;
y /= d.height;
y += clampMin.y;
if (y < clampMin.y) {
y = clampMin.y;
}
if (y > clampMax.y) {
y = clampMax.y;
}
PagedMemory m = ((RAM128k) computer.getMemory()).getMainMemory(); PagedMemory m = ((RAM128k) computer.getMemory()).getMainMemory();
int s = getSlot(); int s = getSlot();
@ -541,11 +517,10 @@ public class CardAppleMouse extends Card implements MouseListener {
* `----------------- Currently, button 0 is up (0) or down (1) * `----------------- Currently, button 0 is up (0) or down (1)
*/ */
int status = 0; int status = 0;
boolean mouseMoved = !currentMouseLocation.equals(lastMouseLocation);
if (button1pressLast) { if (button1pressLast) {
status |= 1; status |= 1;
} }
if (interruptOnMove && mouseMoved) { if (interruptOnMove && movedSinceLastRead) {
status |= 2; status |= 2;
} }
if (interruptOnPress && (button0press != button0pressLast || button1press != button1pressLast)) { if (interruptOnPress && (button0press != button0pressLast || button1press != button1pressLast)) {
@ -557,7 +532,7 @@ public class CardAppleMouse extends Card implements MouseListener {
if (button1press) { if (button1press) {
status |= 16; status |= 16;
} }
if (mouseMoved) { if (movedSinceLastRead) {
status |= 32; status |= 32;
} }
if (button0pressLast) { if (button0pressLast) {
@ -576,52 +551,37 @@ public class CardAppleMouse extends Card implements MouseListener {
*/ */
m.writeByte(0x07F8 + s, (byte) (mode)); m.writeByte(0x07F8 + s, (byte) (mode));
lastMouseLocation = currentMouseLocation;
button0pressLast = button0press; button0pressLast = button0press;
button1pressLast = button1press; button1pressLast = button1press;
movedSinceLastRead = false;
} }
@Override
public void mousePressed(MouseEvent me) { public void mousePressed(MouseEvent me) {
int button = me.getButton(); MouseButton button = me.getButton();
if (button == 1 || button == 2) { if (button == MouseButton.PRIMARY || button == MouseButton.MIDDLE) {
button0press = true; button0press = true;
} }
if (button == 2 || button == 3) { if (button == MouseButton.SECONDARY || button == MouseButton.MIDDLE) {
button1press = true; button1press = true;
} }
} }
@Override
public void mouseReleased(MouseEvent me) { public void mouseReleased(MouseEvent me) {
int button = me.getButton(); MouseButton button = me.getButton();
if (button == 1 || button == 2) { if (button == MouseButton.PRIMARY || button == MouseButton.MIDDLE) {
button0press = false; button0press = false;
} }
if (button == 2 || button == 3) { if (button == MouseButton.SECONDARY || button == MouseButton.MIDDLE) {
button1press = false; button1press = false;
} }
} }
@Override
public void mouseClicked(MouseEvent me) {
}
@Override
public void mouseEntered(MouseEvent me) {
}
@Override
public void mouseExited(MouseEvent me) {
}
private void setClampWindowX(int min, int max) { private void setClampWindowX(int min, int max) {
// Fix for GEOS clamping funkiness // Fix for GEOS clamping funkiness
if (max == 32767) { if (max == 32767) {
max = 560; max = 560;
} }
clampMin.x = min; clampWindow = new Rectangle2D(min, clampWindow.getMinY(), max, clampWindow.getMaxY());
clampMax.x = max;
} }
private void setClampWindowY(int min, int max) { private void setClampWindowY(int min, int max) {
@ -629,8 +589,7 @@ public class CardAppleMouse extends Card implements MouseListener {
if (max == 32767) { if (max == 32767) {
max = 192; max = 192;
} }
clampMin.y = min; clampWindow = new Rectangle2D(clampWindow.getMinX(), min, clampWindow.getMaxX(), max);
clampMax.y = max;
} }
@Override @Override

View File

@ -18,6 +18,7 @@
*/ */
package jace.hardware; package jace.hardware;
import jace.Emulator;
import jace.EmulatorUILogic; import jace.EmulatorUILogic;
import jace.config.ConfigurableField; import jace.config.ConfigurableField;
import jace.config.Name; import jace.config.Name;
@ -81,9 +82,7 @@ public class CardDiskII extends Card implements Reconfigurable, MediaConsumerPar
currentDrive = drive1; currentDrive = drive1;
drive1.reset(); drive1.reset();
drive2.reset(); drive2.reset();
// if (Emulator.getFrame() != null) { EmulatorUILogic.removeIndicators(this);
// Emulator.getFrame().removeIndicators(this);
// }
// Motherboard.cancelSpeedRequest(this); // Motherboard.cancelSpeedRequest(this);
} }