Skip to content

Commit

Permalink
[#314] ZX Keyboard inside draw canvas
Browse files Browse the repository at this point in the history
  • Loading branch information
vbmacher committed May 20, 2024
1 parent bf2e396 commit 739a32b
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -253,18 +253,18 @@ public String toString() {
}

@Override
public void onKeyEvent(KeyEvent e) {
public boolean onKeyEvent(KeyEvent e) {
boolean pressed = e.getID() == KEY_PRESSED;
if (!pressed && e.getID() != KEY_RELEASED) {
return;
return false;
}
BiConsumer<Byte, Byte> keySet = pressed ? this::andKeyMap : this::orKeyMap;
BiConsumer<Byte, Byte> keyUnset = pressed ? this::orKeyMap : this::andKeyMap;

// shift / alt / ctrl are visible in modifiersEx only if pressed = true
boolean symShift = (e.getModifiersEx() & (KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)) != 0;
boolean shift = (e.getModifiersEx() & (KeyEvent.SHIFT_DOWN_MASK)) != 0;

Byte[] command = CHAR_MAPPING.get(e.getKeyCode());
if (command != null) {
if (command[2] == 1 || (command[2] == -1 && shift)) {
Expand All @@ -291,6 +291,7 @@ public void onKeyEvent(KeyEvent e) {
keyUnset.accept(KEY_SYM_SHIFT[0], KEY_SYM_SHIFT[1]);
}
}
return true;
}

private void andKeyMap(byte key, byte value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ public class DisplayCanvas extends Canvas implements AutoCloseable {

private final ULA ula;
private final PaintCycle paintCycle = new PaintCycle();
private final KeyboardCanvas keyboardCanvas;

public DisplayCanvas(ULA ula) {
public DisplayCanvas(ULA ula, KeyboardCanvas keyboardCanvas) {
this.ula = Objects.requireNonNull(ula);
this.keyboardCanvas = Objects.requireNonNull(keyboardCanvas);
this.screenImage.setAccelerationPriority(1.0f);
this.screenImageData = ((DataBufferInt) this.screenImage.getRaster().getDataBuffer()).getData();
}
Expand Down Expand Up @@ -185,6 +187,13 @@ protected void paint() {
graphics.drawImage(
screenImage, MARGIN, MARGIN,
(int) (SCREEN_IMAGE_WIDTH * ZOOM), (int) (SCREEN_IMAGE_HEIGHT * ZOOM), null);

Color color = graphics.getColor();
graphics.setColor(new Color(0, 0, 0, 127));
graphics.translate(0, SCREEN_IMAGE_HEIGHT * ZOOM - KeyboardCanvas.KEYBOARD_HEIGHT + MARGIN);
keyboardCanvas.paint(graphics);
graphics.setColor(color);

graphics.dispose();

} while (strategy.contentsRestored());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@
import static net.emustudio.plugins.device.zxspectrum.ula.ZxParameters.SCREEN_IMAGE_WIDTH;

public class DisplayWindow extends JDialog {
private final DisplayCanvas canvas;
private final KeyboardCanvas keyboardCanvas;

public final static int MARGIN = 30;

private final static int BOUND_X = (int) (DisplayCanvas.ZOOM * SCREEN_IMAGE_WIDTH + 2 * MARGIN);
private final static int BOUND_Y = (int) (DisplayCanvas.ZOOM * SCREEN_IMAGE_HEIGHT + 2 * MARGIN);

private final DisplayCanvas canvas;

public DisplayWindow(JFrame parent, ULA ula) {
super(parent);
this.canvas = new DisplayCanvas(ula);
this.keyboardCanvas = new KeyboardCanvas();
KeyboardCanvas keyboardCanvas = new KeyboardCanvas(70);
this.canvas = new DisplayCanvas(ula, keyboardCanvas);

initComponents();
setLocationRelativeTo(parent);
Expand Down Expand Up @@ -66,23 +68,17 @@ public void destroy() {
private void initComponents() {
setTitle("ZX Spectrum48K");
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
canvas.setBounds(
MARGIN, MARGIN,
(int) (DisplayCanvas.ZOOM * SCREEN_IMAGE_WIDTH + 2 * MARGIN),
(int) (DisplayCanvas.ZOOM * SCREEN_IMAGE_HEIGHT + 2 * MARGIN));
canvas.setBounds(MARGIN, MARGIN, BOUND_X, BOUND_Y);

GroupLayout layout = new GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(canvas)
.addComponent(keyboardCanvas));
.addComponent(canvas));
layout.setVerticalGroup(
layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(canvas, GroupLayout.DEFAULT_SIZE, 407, Short.MAX_VALUE)
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
.addComponent(keyboardCanvas, GroupLayout.DEFAULT_SIZE, KeyboardCanvas.KEYBOARD_HEIGHT + 3, Short.MAX_VALUE)
.addContainerGap()));
pack();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,25 @@
* Host-ZX Keyboard mapping visual representation.
*/
public class KeyboardCanvas extends JComponent implements KeyboardDispatcher.OnKeyListener {
private final static int bw = 45; // button width
private final static int bw = 42; // button width
private final static int bh = 33; // button height
private final static int bsw = 70; // backspace width
private final static int tabw = 60; // tab width
private final static int lshiftw = 55; // left shift width
private final static int brakew = 300; // break width
private final static int brakew = 270; // break width
private final static int s = 5; // space between buttons
private final static double sHalf = s / 2.0; // space between buttons
private final static int arc = 15; // arc radius
private final static int margin = 10;
private final static int rshiftw = 3 * bw - 3 * s - margin; // right shift width
private final static int rshiftw = 3 * bw - 3 * s - margin + (int)sHalf; // right shift width

public final static int KEYBOARD_WIDTH = 13 * (bw + s) + 10 + bsw + 10;
public final static int KEYBOARD_HEIGHT = 5 * (bh + s) + 2 * margin;
public final static int KEYBOARD_WIDTH = 13 * (bw + s) + bsw + 10 + 10;
public final static int KEYBOARD_HEIGHT = 5 * (bh + s) + 2 * margin - s;

private final static int X_SHIFT = (int) ((ZOOM * SCREEN_IMAGE_WIDTH + 2 * MARGIN - KEYBOARD_WIDTH) / 2.0);
private final static int X_SHIFT_L = X_SHIFT + margin;
private final static int Y_SHIFT_T = margin;

private final static Color USABLE_BUTTON_COLOR = Color.LIGHT_GRAY;

private final static int STROKE_WIDTH = 3;

private final static double[][] KEY_MAP = new double[][]{
new double[]{bw + s + bw / 2.0, bh}, // 1
Expand All @@ -47,7 +46,7 @@ public class KeyboardCanvas extends JComponent implements KeyboardDispatcher.OnK
new double[]{bw + s, 0}, // 8
new double[]{bw + s, 0}, // 9
new double[]{bw + s, 0}, // 0,
new double[]{-(11 * bw + s) + tabw + s, bh + s}, // Q
new double[]{-(11 * bw + s) + tabw + sHalf, bh + s}, // Q
new double[]{bw + s, 0}, // W
new double[]{bw + s, 0}, // E
new double[]{bw + s, 0}, // R
Expand All @@ -57,7 +56,7 @@ public class KeyboardCanvas extends JComponent implements KeyboardDispatcher.OnK
new double[]{bw + s, 0}, // I
new double[]{bw + s, 0}, // O
new double[]{bw + s, 0}, // P
new double[]{-(10 * bw + s) - tabw + bsw + s, bh + s}, // A
new double[]{-(10 * bw + s) - tabw + bsw + sHalf, bh + s}, // A
new double[]{bw + s, 0}, // S
new double[]{bw + s, 0}, // D
new double[]{bw + s, 0}, // F
Expand Down Expand Up @@ -101,25 +100,35 @@ public class KeyboardCanvas extends JComponent implements KeyboardDispatcher.OnK
"SHIFT", ":", "£", "?", "/", "*", ",", ".", "SHIFT", "SYM", "SYM"
};

private final BasicStroke outlineStroke = new BasicStroke(3.0f);
private final BasicStroke outlineStroke = new BasicStroke(STROKE_WIDTH);
private final Color usableButtonColor;
private final Color outlineColor;
private final Color brightColor;

private boolean symShift = false;
private boolean shift = false;

public KeyboardCanvas() {
public KeyboardCanvas(int alpha) {
setDoubleBuffered(true);
this.usableButtonColor = new Color(
Color.LIGHT_GRAY.getRed(),
Color.LIGHT_GRAY.getGreen(),
Color.LIGHT_GRAY.getBlue(), alpha);
this.outlineColor = new Color(0, 0, 0, alpha);
this.brightColor = new Color(255, 255, 255, alpha);
}

@Override
public void onKeyEvent(KeyEvent e) {
public boolean onKeyEvent(KeyEvent e) {
boolean pressed = e.getID() == KEY_PRESSED;
if (!pressed && e.getID() != KEY_RELEASED) {
return;
return false;
}

symShift = (e.getModifiersEx() & (KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)) != 0;
shift = (e.getModifiersEx() & (KeyEvent.SHIFT_DOWN_MASK)) != 0;
repaint();
return true;
}

public void paint(Graphics g) {
Expand All @@ -133,7 +142,7 @@ public void paint(Graphics g) {

g2d.setFont(new Font("SansSerif", Font.PLAIN, 11));
g2d.setStroke(outlineStroke);
g2d.setColor(Color.WHITE);
g2d.setColor(brightColor);
g2d.translate(X_SHIFT_L, 0);

for (int i = 0; i < KEY_MAP.length; i++) {
Expand All @@ -152,7 +161,7 @@ public void paint(Graphics g) {
int sw = g2d.getFontMetrics().stringWidth(text);

g2d.translate(KEY_MAP[i][0] - sw / 2.0, KEY_MAP[i][1]);
g2d.setColor(Color.BLACK);
g2d.setColor(outlineColor);
g2d.fill(textShape);
g2d.translate(sw / 2.0, 0);
}
Expand All @@ -163,14 +172,15 @@ private void drawKeyboard(Graphics2D g) {

// keyboard shape
g.setStroke(stroke);
g.drawRoundRect(X_SHIFT, 0, KEYBOARD_WIDTH, KEYBOARD_HEIGHT, arc, arc);
g.setColor(outlineColor);
g.drawRoundRect(X_SHIFT, -STROKE_WIDTH, KEYBOARD_WIDTH, KEYBOARD_HEIGHT, arc, arc);

// top row
for (int i = 0; i < 13; i++) {
if (i >= 1 && i <= 10) {
g.setColor(USABLE_BUTTON_COLOR);
g.setColor(usableButtonColor);
g.fillRoundRect(X_SHIFT_L + i * (bw + s), Y_SHIFT_T, bw, bh, arc, arc);
g.setColor(Color.BLACK);
g.setColor(outlineColor);
}
g.drawRoundRect(X_SHIFT_L + i * (bw + s), Y_SHIFT_T, bw, bh, arc, arc);
}
Expand All @@ -183,9 +193,9 @@ private void drawKeyboard(Graphics2D g) {
g.drawRoundRect(X_SHIFT_L, y1, tabw, bh, arc, arc);
for (int i = 0; i < 12; i++) {
if (i < 10) {
g.setColor(USABLE_BUTTON_COLOR);
g.setColor(usableButtonColor);
g.fillRoundRect(X_SHIFT_L + i * (bw + s) + tabw + s, y1, bw, bh, arc, arc);
g.setColor(Color.BLACK);
g.setColor(outlineColor);
}
g.drawRoundRect(X_SHIFT_L + i * (bw + s) + tabw + s, y1, bw, bh, arc, arc);
}
Expand All @@ -194,66 +204,66 @@ private void drawKeyboard(Graphics2D g) {
int x0 = X_SHIFT_L + 12 * (bw + s) + tabw + s;
int y0 = Y_SHIFT_T + bh + s;
Polygon enterPolygon = new Polygon(
new int[]{x0, x0 + tabw - s, x0 + tabw - s, x0 + 2 * s, x0 + 2 * s, x0},
new int[]{x0, x0 + tabw - 2 * s, x0 + tabw - 2 * s, x0 + 2 * s, x0 + 2 * s, x0},
new int[]{y0, y0, y0 + 2 * bh + s, y0 + 2 * bh + s, y0 + bh, y0 + bh},
6
);

g.setColor(USABLE_BUTTON_COLOR);
g.setColor(usableButtonColor);
g.fillPolygon(enterPolygon);
g.setColor(Color.BLACK);
g.setColor(outlineColor);
g.drawPolygon(enterPolygon);

// caps lock
int y2 = Y_SHIFT_T + (bh + s) * 2;
g.drawRoundRect(X_SHIFT_L, y2, bsw, bh, arc, arc);
for (int i = 0; i < 12; i++) {
if (i < 9) {
g.setColor(USABLE_BUTTON_COLOR);
g.setColor(usableButtonColor);
g.fillRoundRect(X_SHIFT_L + i * (bw + s) + bsw + s, y2, bw, bh, arc, arc);
g.setColor(Color.BLACK);
g.setColor(outlineColor);
}
g.drawRoundRect(X_SHIFT_L + i * (bw + s) + bsw + s, y2, bw, bh, arc, arc);
}

// l shift
int y3 = Y_SHIFT_T + (bh + s) * 3;

g.setColor(USABLE_BUTTON_COLOR);
g.setColor(usableButtonColor);
g.fillRoundRect(X_SHIFT_L, y3, lshiftw, bh, arc, arc);
g.setColor(Color.BLACK);
g.setColor(outlineColor);
g.drawRoundRect(X_SHIFT_L, y3, lshiftw, bh, arc, arc);
for (int i = 0; i < 11; i++) {
if (i >= 1 && i < 8) {
g.setColor(USABLE_BUTTON_COLOR);
g.setColor(usableButtonColor);
g.fillRoundRect(X_SHIFT_L + i * (bw + s) + lshiftw + s, y3, bw, bh, arc, arc);
g.setColor(Color.BLACK);
g.setColor(outlineColor);
}
g.drawRoundRect(X_SHIFT_L + i * (bw + s) + lshiftw + s, y3, bw, bh, arc, arc);
}
g.setColor(USABLE_BUTTON_COLOR);
g.setColor(usableButtonColor);
g.fillRoundRect(X_SHIFT_L + 11 * (bw + s) + lshiftw + s, y3, rshiftw, bh, arc, arc);
g.setColor(Color.BLACK);
g.setColor(outlineColor);
g.drawRoundRect(X_SHIFT_L + 11 * (bw + s) + lshiftw + s, y3, rshiftw, bh, arc, arc);

// l ctrl
int y4 = Y_SHIFT_T + (bh + s) * 4;
g.setColor(USABLE_BUTTON_COLOR);
g.setColor(usableButtonColor);
g.fillRoundRect(X_SHIFT_L, y4, tabw, bh, arc, arc);
g.setColor(Color.BLACK);
g.setColor(outlineColor);
g.drawRoundRect(X_SHIFT_L, y4, tabw, bh, arc, arc);
g.drawRoundRect(X_SHIFT_L + tabw + s, y4, bw, bh, arc, arc);
g.drawRoundRect(X_SHIFT_L + tabw + bw + 2 * s, y4, tabw, bh, arc, arc);

g.setColor(USABLE_BUTTON_COLOR);
g.setColor(usableButtonColor);
g.fillRoundRect(X_SHIFT_L + 2 * (tabw + s) + bw + 2 * s, y4, brakew, bh, arc, arc);
g.setColor(Color.BLACK);
g.setColor(outlineColor);
g.drawRoundRect(X_SHIFT_L + 2 * (tabw + s) + bw + 2 * s, y4, brakew, bh, arc, arc);
g.drawRoundRect(X_SHIFT_L + 2 * (tabw + s) + bw + 4 * s + brakew, y4, tabw, bh, arc, arc);

g.setColor(USABLE_BUTTON_COLOR);
g.setColor(usableButtonColor);
g.fillRoundRect(X_SHIFT_L + 3 * (tabw + s) + bw + 4 * s + brakew, y4, bw, bh, arc, arc);
g.setColor(Color.BLACK);
g.setColor(outlineColor);
g.drawRoundRect(X_SHIFT_L + 3 * (tabw + s) + bw + 4 * s + brakew, y4, bw, bh, arc, arc); // RCTRL
g.drawRoundRect(X_SHIFT_L + 3 * (tabw + s) + 2 * bw + 5 * s + brakew, y4, bw, bh, arc, arc);
g.drawRoundRect(X_SHIFT_L + 3 * (tabw + s) + 3 * bw + 6 * s + brakew, y4, tabw, bh, arc, arc);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,21 @@ public class KeyboardDispatcher implements AutoCloseable, KeyEventDispatcher {

public interface OnKeyListener {

void onKeyEvent(KeyEvent e);
boolean onKeyEvent(KeyEvent e);
}

@Override
public boolean dispatchKeyEvent(KeyEvent e) {
boolean isConsumed = false;
if (!e.isConsumed()) {
onKeyListeners.forEach(c -> c.onKeyEvent(e));
e.consume();
isConsumed = true;
boolean consumed = false;
for (OnKeyListener listener : onKeyListeners) {
consumed |= listener.onKeyEvent(e);
}
if (consumed) {
e.consume();
isConsumed = true;
}
}
return isConsumed;
}
Expand Down

0 comments on commit 739a32b

Please sign in to comment.