Skip to content
40 changes: 33 additions & 7 deletions PlayTools/Controls/ActionDispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public enum ActionDispatchPriority: Int {
public class ActionDispatcher {
static private let keymapVersion = "2.0."
static private var actions = [Action]()
static private var buttonHandlers: [String: [(Bool) -> Void]] = [:]
static private var buttonHandlers: [String: [ButtonPressHandler]] = [:]
static private var pressedKeys = Set<String>()

static private let priorityCount = 3
// You can't put more than 8 cameras or 8 joysticks in a keymap right?
Expand All @@ -35,6 +36,7 @@ public class ActionDispatcher {
invalidateActions()
actions = []
buttonHandlers.removeAll(keepingCapacity: true)
pressedKeys.removeAll(keepingCapacity: true)
directionPadHandlers.forEach({ handlers in
handlers.forEach({ handler in
handler.store(.EMPTY, ordering: .relaxed)
Expand Down Expand Up @@ -96,11 +98,17 @@ public class ActionDispatcher {
}

static public func register(key: String, handler: @escaping (Bool) -> Void) {
register(key: key, modifierKeys: [], handler: handler)
}

static public func register(key: String,
modifierKeys: [String],
handler: @escaping (Bool) -> Void) {
// this function is called when setting up `button` type of mapping
if buttonHandlers[key] == nil {
buttonHandlers[key] = []
}
buttonHandlers[key]!.append(handler)
buttonHandlers[key]!.append(ButtonPressHandler(modifierKeys: modifierKeys, handle: handler))
}

static public func register(key: String,
Expand Down Expand Up @@ -188,18 +196,31 @@ public class ActionDispatcher {
}

static public func dispatch(key: String, pressed: Bool) -> Bool {
if pressed {
pressedKeys.insert(key)
} else {
pressedKeys.remove(key)
}
guard let handlers = buttonHandlers[key] else {
return false
}
var mapped = false
for handler in handlers {
let modifiedHandlers = handlers.filter { handler in
!handler.modifierKeys.isEmpty && isPressed(anyOf: handler.modifierKeys)
}
let selectedHandlers = modifiedHandlers.isEmpty ?
handlers.filter { $0.modifierKeys.isEmpty } :
modifiedHandlers
for handler in selectedHandlers {
PlayInput.touchQueue.async(qos: .userInteractive, execute: {
handler(pressed)
handler.handle(pressed)
})
mapped = true
}
// return value matters. A false value makes a beep sound
return mapped
return !selectedHandlers.isEmpty
}

static public func isPressed(anyOf keys: [String]) -> Bool {
keys.contains { pressedKeys.contains($0) }
}

static public func dispatch(key: String, valueX: CGFloat, valueY: CGFloat) -> Bool {
Expand All @@ -217,6 +238,11 @@ public class ActionDispatcher {
}
}

private struct ButtonPressHandler {
let modifierKeys: [String]
let handle: (Bool) -> Void
}

private final class AtomicHandler: AtomicReference {
static fileprivate let EMPTY = AtomicHandler("", {_, _ in })
let key: String
Expand Down
82 changes: 69 additions & 13 deletions PlayTools/Controls/Backend/Action/PlayAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,79 +13,133 @@ protocol Action {

class ButtonAction: Action {
func invalidate() {
isPressed = false
Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id,
actionName: "Button", keyName: keyName)
}

let keyCode: Int
let keyName: String
let modifierKeyCode: Int?
let modifierKeyName: String?
private let modifierKeys: [String]
let point: CGPoint
var id: Int?
private var isPressed = false

init(keyCode: Int, keyName: String, point: CGPoint) {
init(keyCode: Int,
keyName: String,
modifierKeyCode: Int? = nil,
modifierKeyName: String? = nil,
point: CGPoint) {
self.keyCode = keyCode
self.keyName = keyName
self.modifierKeyCode = modifierKeyCode
self.modifierKeyName = modifierKeyName
self.modifierKeys = Self.dispatchNames(code: modifierKeyCode, name: modifierKeyName)
self.point = point
let code = keyCode
let codeName = KeyCodeNames.keyCodes[code] ?? "Btn"
// TODO: set both key names in draggable button, so as to depracate key code
ActionDispatcher.register(key: code == KeyCodeNames.defaultCode ? keyName: codeName, handler: self.update)
for key in Self.dispatchNames(code: keyCode, name: keyName) {
ActionDispatcher.register(key: key, modifierKeys: modifierKeys, handler: self.update)
}
for key in modifierKeys {
ActionDispatcher.register(key: key, handler: self.updateModifier)
}
}

convenience init(data: Button) {
let keyCode = data.keyCode
self.init(
keyCode: keyCode,
keyName: data.keyName,
modifierKeyCode: data.modifierKeyCode,
modifierKeyName: data.modifierKeyName,
point: CGPoint(
x: data.transform.xCoord.absoluteX,
y: data.transform.yCoord.absoluteY))
}

func update(pressed: Bool) {
if pressed {
guard !isPressed else {
return
}
isPressed = true
Toucher.touchcam(point: point, phase: UITouch.Phase.began, tid: &id,
actionName: "Button", keyName: keyName)
} else {
guard isPressed else {
return
}
isPressed = false
Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id,
actionName: "Button", keyName: keyName)
}
}

private func updateModifier(pressed: Bool) {
if !pressed && id != nil {
isPressed = false
Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id,
actionName: "Button", keyName: keyName)
}
}

private static func dispatchNames(code: Int?, name: String?) -> [String] {
guard let name = name, !name.isEmpty else {
return []
}

let resolvedCode = KeyCodeNames.keyCodeByName[name] ?? code
if let resolvedCode = resolvedCode, resolvedCode != KeyCodeNames.defaultCode {
return KeyCodeNames.dispatchNames(for: resolvedCode, fallback: name)
}
return [name]
}
}

class DraggableButtonAction: ButtonAction {
var releasePoint: CGPoint

override init(keyCode: Int, keyName: String, point: CGPoint) {
override init(keyCode: Int,
keyName: String,
modifierKeyCode: Int? = nil,
modifierKeyName: String? = nil,
point: CGPoint) {
self.releasePoint = point
super.init(keyCode: keyCode, keyName: keyName, point: point)
super.init(
keyCode: keyCode,
keyName: keyName,
modifierKeyCode: modifierKeyCode,
modifierKeyName: modifierKeyName,
point: point)
}

override func update(pressed: Bool) {
if pressed {
Toucher.touchcam(point: point, phase: UITouch.Phase.began, tid: &id,
actionName: "DraggableButton", keyName: keyName)
self.releasePoint = point
ActionDispatcher.register(key: KeyCodeNames.mouseMove,
ActionDispatcher.register(key: keyName,
handler: self.onMouseMoved,
priority: .DRAGGABLE)
if !mode.cursorHidden() {
if keyName == KeyCodeNames.mouseMove && !mode.cursorHidden() {
AKInterface.shared!.hideCursor()
}
} else {
Toucher.touchcam(point: releasePoint, phase: UITouch.Phase.ended, tid: &id,
actionName: "DraggableButton", keyName: keyName)
if id == nil {
ActionDispatcher.unregister(key: KeyCodeNames.mouseMove)
if !mode.cursorHidden() {
ActionDispatcher.unregister(key: keyName)
if keyName == KeyCodeNames.mouseMove && !mode.cursorHidden() {
AKInterface.shared!.unhideCursor()
}
}
}
}

override func invalidate() {
ActionDispatcher.unregister(key: KeyCodeNames.mouseMove)
ActionDispatcher.unregister(key: keyName)
super.invalidate()
}

Expand Down Expand Up @@ -189,8 +243,10 @@ class JoystickAction: Action {
self.mode = mode
for index in 0..<keys.count {
let key = keys[index]
ActionDispatcher.register(key: KeyCodeNames.keyCodes[key]!,
handler: self.getPressedHandler(index: index))
for keyName in KeyCodeNames.dispatchNames(for: key) {
ActionDispatcher.register(key: keyName,
handler: self.getPressedHandler(index: index))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public class EditorControllerEventAdapter: ControllerEventAdapter {
public func handleValueChanged(_ profile: GCExtendedGamepad, _ element: GCControllerElement) {
// This is the index of controller buttons, which is String, not Int
var alias: String = element.aliases.first!
if let buttonElement = element as? GCControllerButtonInput, !buttonElement.isPressed {
return
}
if alias == "Direction Pad" {
guard let dpadElement = element as? GCControllerDirectionPad else {
Toast.showOver(msg: "cannot map direction pad: element type not recognizable")
Expand Down
41 changes: 36 additions & 5 deletions PlayTools/Controls/MenuController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@
EditorController.shared.focusedControl?.resize(down: true)
}

@objc
func captureElementModifierKey(_ sender: AnyObject) {
EditorController.shared.captureModifierKey()
}

@objc
func clearElementModifierKey(_ sender: AnyObject) {
EditorController.shared.clearModifierKey()
}

// put a mark in the toucher log, so as to align with tester description
@objc
func markToucherLog(_ sender: AnyObject) {
Expand Down Expand Up @@ -141,6 +151,10 @@
value: "Upsize selected element", comment: ""),
NSLocalizedString("menu.keymapping.downsizeElement", tableName: "Playtools",
value: "Downsize selected element", comment: ""),
NSLocalizedString("menu.keymapping.captureModifier", tableName: "Playtools",
value: "Set selected button modifier", comment: ""),
NSLocalizedString("menu.keymapping.clearModifier", tableName: "Playtools",
value: "Clear selected button modifier", comment: ""),
NSLocalizedString("menu.keymapping.toggleDebug", tableName: "Playtools",
value: "Toggle Debug Overlay", comment: ""),
NSLocalizedString("menu.keymapping.hide.pointer", tableName: "Playtools",
Expand All @@ -155,7 +169,8 @@
UIImage(systemName: "trash.fill"),
UIImage(systemName: "square.resize.up"),
UIImage(systemName: "square.resize.down"),
UIImage(systemName: "rectangle.landscape.rotate"),
UIImage(systemName: "command"),
UIImage(systemName: "command.circle"),
UIImage(systemName: "wrench.and.screwdriver"),
UIImage(systemName: "pointer.arrow.slash"),
UIImage(systemName: "arrow.down.square"),
Expand All @@ -165,7 +180,8 @@
#selector(UIApplication.removeElement(_:)),
#selector(UIApplication.upscaleElement(_:)),
#selector(UIApplication.downscaleElement(_:)),
#selector(UIApplication.rotateView(_:)),
#selector(UIApplication.captureElementModifierKey(_:)),
#selector(UIApplication.clearElementModifierKey(_:)),
#selector(UIApplication.toggleDebugOverlay(_:)),
#selector(UIApplication.hideCursor(_:)),
#selector(UIApplication.previousKeymap(_:)),
Expand Down Expand Up @@ -229,23 +245,38 @@
children: arrowKeyChildrenCommands)])
}

class func keymappingMenu() -> UIMenu {

Check failure on line 248 in PlayTools/Controls/MenuController.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Function Body Length Violation: Function body should span 50 lines or less excluding comments and whitespace: currently spans 63 lines (function_body_length)
let keyCommands = [
"K", // Toggle keymap editor
UIKeyCommand.inputDelete, // Remove keymap element
UIKeyCommand.inputUpArrow, // Increase keymap element size
UIKeyCommand.inputDownArrow, // Decrease keymap element size
"M", // Set button modifier
"M", // Clear button modifier
"D", // Toggle debug overlay
".", // Hide cursor until move
"[", // Previous keymap
"]" // Next keymap
]
let arrowKeyChildrenCommands = zip(zip(keyCommands, keymapping), iconsSelctor).map { (arg0, image) in
let (command, btn) = arg0
let keyModifiers: [UIKeyModifierFlags] = [
.command,
.command,
.command,
.command,
.command,
[.command, .shift],
.command,
.command,
.command,
.command
]
let arrowKeyChildrenCommands = zip(zip(zip(keyCommands, keymapping), iconsSelctor), keyModifiers)
.map { (arg0, modifierFlags) in
let ((command, btn), image) = arg0
return UIKeyCommand(title: btn, image: image,
action: keymappingSelectors[keymapping.firstIndex(of: btn)!],
input: command,
modifierFlags: .command,
modifierFlags: modifierFlags,
propertyList: [CommandsList.KeymappingToolbox: btn]
)
}
Expand Down
Loading
Loading