Keyboard.java
package swingtree.input;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import swingtree.UI;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* This is a simple Singleton class representing the current state of the keyboard.
* <b>It is not a keyboard shortcut implementation</b> but merely a fast API to check if
* a certain key on the keyboard has been pressed...
* The keyboard class <b>can not be used to hook up lambda callbacks</b>, but merely ought
* to serve as <b>a convenient way to query the current state of the keyboard</b>!
*/
public final class Keyboard
{
private final static Keyboard INSTANCE = new Keyboard();
private static final Logger log = LoggerFactory.getLogger(Keyboard.class);
/**
* Exposes the singleton instance of the {@link Keyboard} class.
*
* @return The singleton instance of the {@link Keyboard} class.
*/
public static Keyboard get() {
if ( !UI.thisIsUIThread() )
log.warn(
"Keyboard.get() should only be called from the UI thread (Swing Event Dispatch Thread).\n" +
"Encountered thread '{}' instead!", Thread.currentThread().getName(),
new Throwable() // Stack trace for debugging purposes
);
return INSTANCE;
}
/**
* This enum represents all the keys on the keyboard which can be queried for their current state.
* This is a mapping of the {@link KeyEvent} constants to a more readable and type safe enum.
*/
public enum Key
{
/**
* This value is used to indicate that the keyCode is unknown.
* KEY_TYPED events do not have a keyCode value; this value
* is used instead.
* See {@link KeyEvent#VK_UNDEFINED} for more information.
*/
NONE(KeyEvent.VK_UNDEFINED),
/** Constant for the ENTER virtual key. See {@link KeyEvent#VK_ENTER} for the underlying key-code. */
ENTER(KeyEvent.VK_ENTER),
/** Constant for the BACK_SPACE virtual key. See {@link KeyEvent#VK_BACK_SPACE} for the underlying key-code.*/
BACK_SPACE(KeyEvent.VK_BACK_SPACE),
/** Constant for the TAB virtual key. See {@link KeyEvent#VK_TAB} for the underlying key-code.*/
TAB(KeyEvent.VK_TAB),
/** Constant for the CANCEL virtual key. See {@link KeyEvent#VK_CANCEL} for the underlying key-code.*/
CANCEL(KeyEvent.VK_CANCEL),
/** Constant for the CLEAR virtual key. See {@link KeyEvent#VK_CLEAR} for the underlying key-code.*/
CLEAR(KeyEvent.VK_CLEAR),
/** Constant for the SHIFT virtual key. See {@link KeyEvent#VK_SHIFT} for the underlying key-code.*/
SHIFT(KeyEvent.VK_SHIFT),
/** Constant for the CONTROL virtual key. See {@link KeyEvent#VK_CONTROL} for the underlying key-code.*/
CONTROL(KeyEvent.VK_CONTROL),
/** Constant for the ALT virtual key. See {@link KeyEvent#VK_ALT} for the underlying key-code.*/
ALT(KeyEvent.VK_ALT),
/** Constant for the PAUSE virtual key. See {@link KeyEvent#VK_PAUSE} for the underlying key-code.*/
PAUSE(KeyEvent.VK_PAUSE),
/** Constant for the CAPS_LOCK virtual key. See {@link KeyEvent#VK_CAPS_LOCK} for the underlying key-code.*/
CAPS_LOCK(KeyEvent.VK_CAPS_LOCK),
/** Constant for the ESCAPE virtual key. See {@link KeyEvent#VK_ESCAPE} for the underlying key-code.*/
ESCAPE(KeyEvent.VK_ESCAPE),
/** Constant for the SPACE virtual key. See {@link KeyEvent#VK_SPACE} for the underlying key-code.*/
SPACE(KeyEvent.VK_SPACE),
/** Constant for the PAGE_UP virtual key. See {@link KeyEvent#VK_PAGE_UP} for the underlying key-code.*/
PAGE_UP(KeyEvent.VK_PAGE_UP),
/** Constant for the PAGE_DOWN virtual key. See {@link KeyEvent#VK_PAGE_DOWN} for the underlying key-code.*/
PAGE_DOWN(KeyEvent.VK_PAGE_DOWN),
/** Constant for the END virtual key. See {@link KeyEvent#VK_END} for the underlying key-code.*/
END(KeyEvent.VK_END),
/** Constant for the HOME virtual key. See {@link KeyEvent#VK_HOME} for the underlying key-code.*/
HOME(KeyEvent.VK_HOME),
/**
* Constant for the non-numpad <b>left</b> arrow key. See {@link KeyEvent#VK_LEFT} for the underlying key-code.
* @see #LEFT
*/
LEFT(KeyEvent.VK_LEFT),
/**
* Constant for the non-numpad <b>up</b> arrow key.See {@link KeyEvent#VK_U} for the underlying key-code.
* @see #UP
*/
UP(KeyEvent.VK_UP),
/**
* Constant for the non-numpad <b>right</b> arrow key. See {@link KeyEvent#VK_RIGHT} for the underlying key-code.
* @see #RIGHT
*/
RIGHT(KeyEvent.VK_RIGHT),
/**
* Constant for the non-numpad <b>down</b> arrow key.See {@link KeyEvent#VK_DOWN} for the underlying key-code.
* @see #DOWN
*/
DOWN(KeyEvent.VK_DOWN),
/**
* Constant for the comma key, ","
*/
COMMA(KeyEvent.VK_COMMA),
/**
* Constant for the minus key, "-"
*/
MINUS(KeyEvent.VK_MINUS),
/**
* Constant for the period key, "."
*/
PERIOD(KeyEvent.VK_PERIOD),
/**
* Constant for the forward slash key, "/"
*/
SLASH(KeyEvent.VK_SLASH),
/** Constant for the "0" key. */
ZERO(KeyEvent.VK_0),
/** Constant for the "1" key. */
ONE(KeyEvent.VK_1),
/** Constant for the "2" key. */
TWO(KeyEvent.VK_2),
/** Constant for the "3" key. */
THREE(KeyEvent.VK_3),
/** Constant for the "4" key. */
FOUR(KeyEvent.VK_4),
/** Constant for the "5" key. */
FIVE(KeyEvent.VK_5),
/** Constant for the "6" key. */
SIX(KeyEvent.VK_6),
/** Constant for the "7" key. */
SEVEN(KeyEvent.VK_7),
/** Constant for the "8" key. */
EIGHT(KeyEvent.VK_8),
/** Constant for the "9" key. */
NINE(KeyEvent.VK_9),
/**
* Constant for the semicolon key, ";"
*/
SEMICOLON(KeyEvent.VK_SEMICOLON),
/**
* Constant for the equals key, "="
*/
EQUALS(KeyEvent.VK_EQUALS),
A(KeyEvent.VK_A), B(KeyEvent.VK_B), C(KeyEvent.VK_C), D(KeyEvent.VK_D), E(KeyEvent.VK_E), F(KeyEvent.VK_F),
G(KeyEvent.VK_G), H(KeyEvent.VK_H), I(KeyEvent.VK_I), J(KeyEvent.VK_J), K(KeyEvent.VK_K), L(KeyEvent.VK_L),
M(KeyEvent.VK_M), N(KeyEvent.VK_N), O(KeyEvent.VK_O), P(KeyEvent.VK_P), Q(KeyEvent.VK_Q), R(KeyEvent.VK_R),
S(KeyEvent.VK_S), T(KeyEvent.VK_T), U(KeyEvent.VK_U), V(KeyEvent.VK_V), W(KeyEvent.VK_W), X(KeyEvent.VK_X),
Y(KeyEvent.VK_Y), Z(KeyEvent.VK_Z),
OPEN_BRACKET(KeyEvent.VK_OPEN_BRACKET),
BACK_SLASH(KeyEvent.VK_BACK_SLASH),
CLOSE_BRACKET(KeyEvent.VK_CLOSE_BRACKET),
NUMPAD_0(KeyEvent.VK_NUMPAD0), NUMPAD_1(KeyEvent.VK_NUMPAD1), NUMPAD_2(KeyEvent.VK_NUMPAD2),
NUMPAD_3(KeyEvent.VK_NUMPAD3), NUMPAD_4(KeyEvent.VK_NUMPAD4), NUMPAD_5(KeyEvent.VK_NUMPAD5),
NUMPAD_6(KeyEvent.VK_NUMPAD6), NUMPAD_7(KeyEvent.VK_NUMPAD7), NUMPAD_8(KeyEvent.VK_NUMPAD8),
NUMPAD_9(KeyEvent.VK_NUMPAD9),
MULTIPLY(KeyEvent.VK_MULTIPLY), ADD(KeyEvent.VK_ADD), SEPARATOR(KeyEvent.VK_SEPARATOR),
SUBTRACT(KeyEvent.VK_SUBTRACT), DECIMAL(KeyEvent.VK_DECIMAL), DIVIDE(KeyEvent.VK_DIVIDE),
DELETE(KeyEvent.VK_DELETE), NUM_LOCK(KeyEvent.VK_NUM_LOCK), SCROLL_LOCK(KeyEvent.VK_SCROLL_LOCK),
F1(KeyEvent.VK_F1), F2(KeyEvent.VK_F2), F3(KeyEvent.VK_F3), F4(KeyEvent.VK_F4), F5(KeyEvent.VK_F5),
F6(KeyEvent.VK_F6), F7(KeyEvent.VK_F7), F8(KeyEvent.VK_F8), F9(KeyEvent.VK_F9), F10(KeyEvent.VK_F10),
F11(KeyEvent.VK_F11), F12(KeyEvent.VK_F12), F13(KeyEvent.VK_F13), F14(KeyEvent.VK_F14), F15(KeyEvent.VK_F15),
F16(KeyEvent.VK_F16), F17(KeyEvent.VK_F17), F18(KeyEvent.VK_F18), F19(KeyEvent.VK_F19), F20(KeyEvent.VK_F20),
F21(KeyEvent.VK_F21), F22(KeyEvent.VK_F22), F23(KeyEvent.VK_F23), F24(KeyEvent.VK_F24),
PRINTSCREEN(KeyEvent.VK_PRINTSCREEN),
INSERT(KeyEvent.VK_INSERT),
HELP(KeyEvent.VK_HELP), META(KeyEvent.VK_META), BACK_QUOTE(KeyEvent.VK_BACK_QUOTE),
QUOTE(KeyEvent.VK_QUOTE);
public final int code;
Key( int keyCode ) { this.code = keyCode; }
/**
* This method checks if this key is currently pressed on the keyboard
* by checking if it is in a list of currently pressed keys.
*
* @return The truth value determining if this key is currently pressed on the keyboard.
*/
public boolean isPressed() {
return Keyboard.get().isPressed(this);
}
}
private final List<Key> _pressed = new ArrayList<>();
/**
* This method checks if the supplied {@link Key} is currently pressed on the keyboard.
*
* @param key An instance of the {@link Key} enum representing a keyboard key.
* @return The truth value determining if the specified {@link Key} type is currently pressed by the user.
* @throws NullPointerException If the supplied {@link Key} is null,
* use {@link Key#NONE} instead to check if no key is currently pressed.
*/
public boolean isPressed( Key key ) {
Objects.requireNonNull(key);
synchronized ( this ) {
if ( key == Key.NONE )
return _pressed.isEmpty();
return _pressed.contains(key);
}
}
private Keyboard() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
@Override
public boolean dispatchKeyEvent(KeyEvent ke) {
synchronized (this) {
switch ( ke.getID() ) {
case KeyEvent.KEY_PRESSED: {
Key k = _fromKeyEvent(ke);
if ( k != Key.NONE && !_pressed.contains(k) )
_pressed.add(k);
}
break;
case KeyEvent.KEY_RELEASED: {
Key k = _fromKeyEvent(ke);
if ( k != Key.NONE )
_pressed.remove(k);
}
break;
}
return false;
}
}
});
}
private static Key _fromKeyEvent( KeyEvent keyEvent ) {
for ( Key key : Key.values() ) {
if ( key.code == keyEvent.getKeyCode() ) {
return key;
}
}
return Key.NONE;
}
}