package iron.system; import kha.input.KeyCode; class Input { public static var occupied = false; static var mouse: Mouse = null; static var pen: Pen = null; static var keyboard: Keyboard = null; static var gamepads: Array = []; static var sensor: Sensor = null; static var registered = false; public static var virtualButtons: Map = null; // Button name public static function reset() { occupied = false; if (mouse != null) mouse.reset(); if (pen != null) pen.reset(); if (keyboard != null) keyboard.reset(); for (gamepad in gamepads) gamepad.reset(); } public static function endFrame() { if (mouse != null) mouse.endFrame(); if (pen != null) pen.endFrame(); if (keyboard != null) keyboard.endFrame(); for (gamepad in gamepads) gamepad.endFrame(); if (virtualButtons != null) { for (vb in virtualButtons) vb.started = vb.released = false; } } public static function getMouse(): Mouse { if (!registered) register(); if (mouse == null) mouse = new Mouse(); return mouse; } public static function getPen(): Pen { if (!registered) register(); if (pen == null) pen = new Pen(); return pen; } public static function getSurface(): Surface { if (!registered) register(); // Map to mouse for now.. return getMouse(); } /** Get the Keyboard object. If it is not registered yet then register a new Keyboard. **/ public static function getKeyboard(): Keyboard { if (!registered) register(); if (keyboard == null) keyboard = new Keyboard(); return keyboard; } public static function getGamepad(i = 0): Gamepad { if (i >= 4) return null; if (!registered) register(); while (gamepads.length <= i) gamepads.push(new Gamepad(gamepads.length)); return gamepads[i].connected ? gamepads[i] : null; } public static function getSensor(): Sensor { if (!registered) register(); if (sensor == null) sensor = new Sensor(); return sensor; } public static function getVirtualButton(virtual: String): VirtualButton { if (!registered) register(); if (virtualButtons == null) return null; return virtualButtons.get(virtual); } static inline function register() { registered = true; App.notifyOnEndFrame(endFrame); App.notifyOnReset(reset); // Reset mouse delta on foreground kha.System.notifyOnApplicationState(function() { getMouse().reset(); }, null, null, null, null); } } class VirtualButton { public var started = false; public var released = false; public var down = false; public function new() {} } class VirtualInput { var virtualButtons: Map = null; // Button id public function setVirtual(virtual: String, button: String) { if (Input.virtualButtons == null) Input.virtualButtons = new Map(); var vb = Input.virtualButtons.get(virtual); if (vb == null) { vb = new VirtualButton(); Input.virtualButtons.set(virtual, vb); } if (virtualButtons == null) virtualButtons = new Map(); virtualButtons.set(button, vb); } function downVirtual(button: String) { if (virtualButtons != null) { var vb = virtualButtons.get(button); if (vb != null) { vb.down = true; vb.started = true; } } } function upVirtual(button: String) { if (virtualButtons != null) { var vb = virtualButtons.get(button); if (vb != null) { vb.down = false; vb.released = true; } } } } typedef Surface = Mouse; class Mouse extends VirtualInput { public static var buttons = ["left", "right", "middle", "side1", "side2"]; var buttonsDown = [false, false, false, false, false]; var buttonsStarted = [false, false, false, false, false]; var buttonsReleased = [false, false, false, false, false]; public var x(default, null) = 0.0; public var y(default, null) = 0.0; public var viewX(get, null) = 0.0; public var viewY(get, null) = 0.0; public var moved(default, null) = false; public var movementX(default, null) = 0.0; public var movementY(default, null) = 0.0; public var wheelDelta(default, null) = 0; public var locked(default, null) = false; public var hidden(default, null) = false; public var lastX = -1.0; public var lastY = -1.0; public function new() { kha.input.Mouse.get().notify(downListener, upListener, moveListener, wheelListener); #if (kha_android || kha_ios) if (kha.input.Surface.get() != null) kha.input.Surface.get().notify(onTouchDown, onTouchUp, onTouchMove); #end } public function endFrame() { buttonsStarted[0] = buttonsStarted[1] = buttonsStarted[2] = buttonsStarted[3] = buttonsStarted[4] = false; buttonsReleased[0] = buttonsReleased[1] = buttonsReleased[2] = buttonsReleased[3] = buttonsReleased[4] = false; moved = false; movementX = 0; movementY = 0; wheelDelta = 0; } public function reset() { buttonsDown[0] = buttonsDown[1] = buttonsDown[2] = buttonsDown[3] = buttonsDown[4] = false; endFrame(); } function buttonIndex(button: String): Int { for (i in 0...buttons.length) if (buttons[i] == button) return i; return 0; } public function down(button = "left"): Bool { return buttonsDown[buttonIndex(button)]; } public function started(button = "left"): Bool { return buttonsStarted[buttonIndex(button)]; } public function released(button = "left"): Bool { return buttonsReleased[buttonIndex(button)]; } public function lock() { if (kha.input.Mouse.get().canLock()) { kha.input.Mouse.get().lock(); locked = true; hidden = true; } } public function unlock() { if (kha.input.Mouse.get().canLock()) { kha.input.Mouse.get().unlock(); locked = false; hidden = false; } } public function hide() { kha.input.Mouse.get().hideSystemCursor(); hidden = true; } public function show() { kha.input.Mouse.get().showSystemCursor(); hidden = false; } function downListener(index: Int, x: Int, y: Int) { if (Input.getPen().inUse) return; buttonsDown[index] = true; buttonsStarted[index] = true; this.x = x; this.y = y; #if (kha_android || kha_ios || kha_webgl) // For movement delta using touch if (index == 0) { lastX = x; lastY = y; } #end downVirtual(buttons[index]); } function upListener(index: Int, x: Int, y: Int) { if (Input.getPen().inUse) return; buttonsDown[index] = false; buttonsReleased[index] = true; this.x = x; this.y = y; upVirtual(buttons[index]); } function moveListener(x: Int, y: Int, movementX: Int, movementY: Int) { if (lastX == -1.0 && lastY == -1.0) { // First frame init lastX = x; lastY = y; } if (locked) { // Can be called multiple times per frame this.movementX += movementX; this.movementY += movementY; } else { this.movementX += x - lastX; this.movementY += y - lastY; } lastX = x; lastY = y; this.x = x; this.y = y; moved = true; } function wheelListener(delta: Int) { wheelDelta = delta; } #if (kha_android || kha_ios) public function onTouchDown(index: Int, x: Int, y: Int) { if (index == 1) { // Two fingers down - right mouse button buttonsDown[0] = false; downListener(1, Std.int(this.x), Std.int(this.y)); pinchStarted = true; pinchTotal = 0.0; pinchDistance = 0.0; } else if (index == 2) { // Three fingers down - middle mouse button buttonsDown[1] = false; downListener(2, Std.int(this.x), Std.int(this.y)); } } public function onTouchUp(index: Int, x: Int, y: Int) { if (index == 1) upListener(1, Std.int(this.x), Std.int(this.y)); else if (index == 2) upListener(2, Std.int(this.x), Std.int(this.y)); } var pinchDistance = 0.0; var pinchTotal = 0.0; var pinchStarted = false; public function onTouchMove(index: Int, x: Int, y: Int) { // Pinch to zoom - mouse wheel if (index == 1) { var lastDistance = pinchDistance; var dx = this.x - x; var dy = this.y - y; pinchDistance = Math.sqrt(dx * dx + dy * dy); pinchTotal += lastDistance != 0 ? lastDistance - pinchDistance : 0; if (!pinchStarted) { wheelDelta = Std.int(pinchTotal / 10); if (wheelDelta != 0) { pinchTotal = 0.0; } } pinchStarted = false; } } #end inline function get_viewX(): Float { return x - iron.App.x(); } inline function get_viewY(): Float { return y - iron.App.y(); } } class Pen extends VirtualInput { static var buttons = ["tip"]; var buttonsDown = [false]; var buttonsStarted = [false]; var buttonsReleased = [false]; public var x(default, null) = 0.0; public var y(default, null) = 0.0; public var viewX(get, null) = 0.0; public var viewY(get, null) = 0.0; public var moved(default, null) = false; public var movementX(default, null) = 0.0; public var movementY(default, null) = 0.0; public var pressure(default, null) = 0.0; public var connected = false; public var inUse = false; var lastX = -1.0; var lastY = -1.0; public function new() { var pen = kha.input.Pen.get(); if (pen != null) pen.notify(downListener, upListener, moveListener); } public function endFrame() { buttonsStarted[0] = false; buttonsReleased[0] = false; moved = false; movementX = 0; movementY = 0; inUse = false; } public function reset() { buttonsDown[0] = false; endFrame(); } function buttonIndex(button: String): Int { return 0; } public function down(button = "tip"): Bool { return buttonsDown[buttonIndex(button)]; } public function started(button = "tip"): Bool { return buttonsStarted[buttonIndex(button)]; } public function released(button = "tip"): Bool { return buttonsReleased[buttonIndex(button)]; } function downListener(x: Int, y: Int, pressure: Float) { buttonsDown[0] = true; buttonsStarted[0] = true; this.x = x; this.y = y; this.pressure = pressure; #if (!kha_android && !kha_ios) @:privateAccess Input.getMouse().downListener(0, x, y); #end } function upListener(x: Int, y: Int, pressure: Float) { #if (!kha_android && !kha_ios) if (buttonsStarted[0]) { buttonsStarted[0] = false; inUse = true; return; } #end buttonsDown[0] = false; buttonsReleased[0] = true; this.x = x; this.y = y; this.pressure = pressure; #if (!kha_android && !kha_ios) @:privateAccess Input.getMouse().upListener(0, x, y); inUse = true; // On pen release, additional mouse down & up events are fired at once - filter those out #end } function moveListener(x: Int, y: Int, pressure: Float) { if (lastX == -1.0 && lastY == -1.0) { // First frame init lastX = x; lastY = y; } this.movementX = x - lastX; this.movementY = y - lastY; lastX = x; lastY = y; this.x = x; this.y = y; moved = true; this.pressure = pressure; connected = true; } inline function get_viewX(): Float { return x - iron.App.x(); } inline function get_viewY(): Float { return y - iron.App.y(); } } class Keyboard extends VirtualInput { public static var keys = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "space", "backspace", "tab", "enter", "shift", "control", "alt", "capslock", "win", "escape", "delete", "up", "down", "left", "right", "back", ",", ".", ":", ";", "<", "=", ">", "?", "!", '"', "#", "$", "%", "&", "_", "(", ")", "*", "|", "{", "}", "[", "]", "~", "`", "/", "\\", "@", "+", "-", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12"]; var keysDown = new Map(); var keysStarted = new Map(); var keysReleased = new Map(); var keysFrame: Array = []; var repeatKey = false; var repeatTime = 0.0; public function new() { reset(); kha.input.Keyboard.get().notify(downListener, upListener, pressListener); } public function endFrame() { if (keysFrame.length > 0) { for (s in keysFrame) { keysStarted.set(s, false); keysReleased.set(s, false); } keysFrame.splice(0, keysFrame.length); } if (kha.Scheduler.time() - repeatTime > 0.05) { repeatTime = kha.Scheduler.time(); repeatKey = true; } else repeatKey = false; } public function reset() { // Use Map for now.. for (s in keys) { keysDown.set(s, false); keysStarted.set(s, false); keysReleased.set(s, false); } endFrame(); } /** Check if a key is currently pressed. @param key A String representing the physical keyboard key to check. @return Bool. Returns true or false depending on the keyboard state. **/ public function down(key: String): Bool { return keysDown.get(key); } /** Check if a key has started being pressed down. Will only be run once until the key is released and pressed again. @param key A String representing the physical keyboard key to check. @return Bool. Returns true or false depending on the keyboard state. **/ public function started(key: String): Bool { return keysStarted.get(key); } /** Check if a key has been released from being pressed down. Will only be run once until the key is pressed again and release again. @param key A String representing the physical keyboard key to check. @return Bool. Returns true or false depending on the keyboard state. **/ public function released(key: String): Bool { return keysReleased.get(key); } /** Check every repeat period if a key is currently pressed. @param key A String representing the physical keyboard key to check. @return Bool. Returns true or false depending on the keyboard state. **/ public function repeat(key: String): Bool { return keysStarted.get(key) || (repeatKey && keysDown.get(key)); } public static function keyCode(key: KeyCode): String { return switch(key) { case KeyCode.Space: "space"; case KeyCode.Backspace: "backspace"; case KeyCode.Tab: "tab"; case KeyCode.Return: "enter"; case KeyCode.Shift: "shift"; case KeyCode.Control: "control"; #if kha_darwin case KeyCode.Meta: "control"; #end case KeyCode.Alt: "alt"; case KeyCode.CapsLock: "capslock"; case KeyCode.Win: "win"; case KeyCode.Escape: "escape"; case KeyCode.Delete: "delete"; case KeyCode.Up: "up"; case KeyCode.Down: "down"; case KeyCode.Left: "left"; case KeyCode.Right: "right"; case KeyCode.Back: "back"; case KeyCode.Comma: ","; case KeyCode.Period: "."; case KeyCode.Colon: ":"; case KeyCode.Semicolon: ";"; case KeyCode.LessThan: "<"; case KeyCode.Equals: "="; case KeyCode.GreaterThan: ">"; case KeyCode.QuestionMark: "?"; case KeyCode.Exclamation: "!"; case KeyCode.DoubleQuote: '"'; case KeyCode.Hash: "#"; case KeyCode.Dollar: "$"; case KeyCode.Percent: "%"; case KeyCode.Ampersand: "&"; case KeyCode.Underscore: "_"; case KeyCode.OpenParen: "("; case KeyCode.CloseParen: ")"; case KeyCode.Asterisk: "*"; case KeyCode.Pipe: "|"; case KeyCode.OpenCurlyBracket: "{"; case KeyCode.CloseCurlyBracket: "}"; case KeyCode.OpenBracket: "["; case KeyCode.CloseBracket: "]"; case KeyCode.Tilde: "~"; case KeyCode.BackQuote: "`"; case KeyCode.Slash: "/"; case KeyCode.BackSlash: "\\"; case KeyCode.At: "@"; case KeyCode.Add: "+"; case KeyCode.Plus: "+"; case KeyCode.Subtract: "-"; case KeyCode.HyphenMinus: "-"; case KeyCode.Multiply: "*"; case KeyCode.Divide: "/"; case KeyCode.Decimal: "."; case KeyCode.Zero: "0"; case KeyCode.Numpad0: "0"; case KeyCode.One: "1"; case KeyCode.Numpad1: "1"; case KeyCode.Two: "2"; case KeyCode.Numpad2: "2"; case KeyCode.Three: "3"; case KeyCode.Numpad3: "3"; case KeyCode.Four: "4"; case KeyCode.Numpad4: "4"; case KeyCode.Five: "5"; case KeyCode.Numpad5: "5"; case KeyCode.Six: "6"; case KeyCode.Numpad6: "6"; case KeyCode.Seven: "7"; case KeyCode.Numpad7: "7"; case KeyCode.Eight: "8"; case KeyCode.Numpad8: "8"; case KeyCode.Nine: "9"; case KeyCode.Numpad9: "9"; case KeyCode.F1: "f1"; case KeyCode.F2: "f2"; case KeyCode.F3: "f3"; case KeyCode.F4: "f4"; case KeyCode.F5: "f5"; case KeyCode.F6: "f6"; case KeyCode.F7: "f7"; case KeyCode.F8: "f8"; case KeyCode.F9: "f9"; case KeyCode.F10: "f10"; case KeyCode.F11: "f11"; case KeyCode.F12: "f12"; default: String.fromCharCode(cast key).toLowerCase(); } } function downListener(code: KeyCode) { var s = keyCode(code); keysFrame.push(s); keysStarted.set(s, true); keysDown.set(s, true); repeatTime = kha.Scheduler.time() + 0.4; #if kha_android_rmb // Detect right mouse button on Android.. if (code == KeyCode.Back) { var m = Input.getMouse(); @:privateAccess if (!m.buttonsDown[1]) m.downListener(1, Std.int(m.x), Std.int(m.y)); } #end downVirtual(s); } function upListener(code: KeyCode) { var s = keyCode(code); keysFrame.push(s); keysReleased.set(s, true); keysDown.set(s, false); #if kha_android_rmb if (code == KeyCode.Back) { var m = Input.getMouse(); @:privateAccess m.upListener(1, Std.int(m.x), Std.int(m.y)); } #end upVirtual(s); } function pressListener(char: String) {} } class GamepadStick { public var x = 0.0; public var y = 0.0; public var lastX = 0.0; public var lastY = 0.0; public var moved = false; public var movementX = 0.0; public var movementY = 0.0; public function new() {} } class Gamepad extends VirtualInput { public static var buttonsPS = ["cross", "circle", "square", "triangle", "l1", "r1", "l2", "r2", "share", "options", "l3", "r3", "up", "down", "left", "right", "home", "touchpad"]; public static var buttonsXBOX = ["a", "b", "x", "y", "l1", "r1", "l2", "r2", "share", "options", "l3", "r3", "up", "down", "left", "right", "home", "touchpad"]; public static var buttons = buttonsPS; public var id(get, never): String; inline function get_id() return kha.input.Gamepad.get(num).id; var buttonsDown: Array = []; // Intensity 0 - 1 var buttonsStarted: Array = []; var buttonsReleased: Array = []; var buttonsFrame: Array = []; public var leftStick = new GamepadStick(); public var rightStick = new GamepadStick(); public var connected = false; var num = 0; public function new(i: Int, virtual = false) { for (s in buttons) { buttonsDown.push(0.0); buttonsStarted.push(false); buttonsReleased.push(false); } num = i; reset(); virtual ? connected = true : connect(); } var connects = 0; function connect() { var gamepad = kha.input.Gamepad.get(num); if (gamepad == null) { // if (connects < 10) leenkx.system.Tween.timer(1, connect); // connects++; return; } connected = true; gamepad.notify(axisListener, buttonListener); } public function endFrame() { if (buttonsFrame.length > 0) { for (i in buttonsFrame) { buttonsStarted[i] = false; buttonsReleased[i] = false; } buttonsFrame.splice(0, buttonsFrame.length); } leftStick.moved = false; leftStick.movementX = 0; leftStick.movementY = 0; rightStick.moved = false; rightStick.movementX = 0; rightStick.movementY = 0; } public function reset() { for (i in 0...buttonsDown.length) { buttonsDown[i] = 0.0; buttonsStarted[i] = false; buttonsReleased[i] = false; } endFrame(); } public static function keyCode(button: Int): String { return buttons[button]; } function buttonIndex(button: String): Int { for (i in 0...buttons.length) if (buttons[i] == button) return i; return 0; } public function down(button: String): Float { return buttonsDown[buttonIndex(button)]; } public function started(button: String): Bool { return buttonsStarted[buttonIndex(button)]; } public function released(button: String): Bool { return buttonsReleased[buttonIndex(button)]; } function axisListener(axis: Int, value: Float) { var stick = axis <= 1 ? leftStick : rightStick; if (axis == 0 || axis == 2) { // X stick.lastX = stick.x; stick.x = value; stick.movementX = stick.x - stick.lastX; } else if (axis == 1 || axis == 3) { // Y stick.lastY = stick.y; stick.y = value; stick.movementY = stick.y - stick.lastY; } stick.moved = true; } function buttonListener(button: Int, value: Float) { buttonsFrame.push(button); buttonsDown[button] = value; if (value > 0) buttonsStarted[button] = true; // Will trigger L2/R2 multiple times.. else buttonsReleased[button] = true; if (value == 0.0) upVirtual(buttons[button]); else if (value == 1.0) downVirtual(buttons[button]); } } class Sensor { public var x = 0.0; public var y = 0.0; public var z = 0.0; public function new() { kha.input.Sensor.get(kha.input.SensorType.Accelerometer).notify(listener); } function listener(x: Float, y: Float, z: Float) { this.x = x; this.y = y; this.z = z; } }