Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Multi-keymap #181

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions PlayTools/Controls/ActionDispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,32 +54,32 @@ public class ActionDispatcher {
// in future, keymap format will be upgraded.
// PlayTools would maintain limited backwards compatibility.
// Meanwhile, keymap format upgrade would be rare.
if !keymap.keymapData.version.hasPrefix(keymapVersion) {
if !keymap.currentKeymap.version.hasPrefix(keymapVersion) {
DispatchQueue.main.asyncAfter(
deadline: .now() + .seconds(5)) {
Toast.showHint(title: "Keymap format too new",
text: ["Current keymap version \(keymap.keymapData.version)" +
text: ["Current keymap version \(keymap.currentKeymap.version)" +
" is too new and cannot be recognized\n" +
"For protection of your data, keymap is not loaded\n" +
"Please upgrade PlayCover, " +
"For protection of your data, keymap is not loaded\n" +
"Please upgrade PlayCover, " +
"or import an older version of keymap (requires \(keymapVersion)x"])
}
return
}

for button in keymap.keymapData.buttonModels {
for button in keymap.currentKeymap.buttonModels {
actions.append(ButtonAction(data: button))
}

for draggableButton in keymap.keymapData.draggableButtonModels {
for draggableButton in keymap.currentKeymap.draggableButtonModels {
actions.append(DraggableButtonAction(data: draggableButton))
}

for mouse in keymap.keymapData.mouseAreaModel {
for mouse in keymap.currentKeymap.mouseAreaModel {
actions.append(CameraAction(data: mouse))
}

for joystick in keymap.keymapData.joystickModel {
for joystick in keymap.currentKeymap.joystickModel {
// Left Thumbstick, Right Thumbstick, Mouse
if JoystickModel.isAnalog(joystick) {
actions.append(ContinuousJoystickAction(data: joystick))
Expand Down
49 changes: 44 additions & 5 deletions PlayTools/Controls/MenuController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,32 @@
DebugController.instance.toggleDebugOverlay()
}

@objc func hideCursor(_ sender: AnyObject) {
@objc
func hideCursor(_ sender: AnyObject) {
AKInterface.shared!.hideCursorMove()
}

@objc
func nextKeymap(_ sender: AnyObject) {
if mode == .EDITOR {
ModeAutomaton.onCmdK()
}

keymap.nextKeymap()
Toast.showHint(title: "Switched to next keymap: \(keymap.currentKeymapName)")
ActionDispatcher.build()
}

@objc
func previousKeymap(_ sender: AnyObject) {
if mode == .EDITOR {
ModeAutomaton.onCmdK()
}

keymap.previousKeymap()
Toast.showHint(title: "Switched to previous keymap: \(keymap.currentKeymapName)")
ActionDispatcher.build()
}
}

extension UIViewController {
Expand Down Expand Up @@ -107,15 +130,22 @@
NSLocalizedString("menu.keymapping.toggleDebug", tableName: "Playtools",
value: "Toggle Debug Overlay", comment: ""),
NSLocalizedString("menu.keymapping.hide.pointer", tableName: "Playtools",
value: "Hide Mouse Pointer", comment: "")
value: "Hide Mouse Pointer", comment: ""),
NSLocalizedString("menu.keymapping.nextKeymap", tableName: "Playtools",
value: "Next Keymap", comment: ""),
NSLocalizedString("menu.keymapping.previousKeymap", tableName: "Playtools",
value: "Previous Keymap", comment: "")
]
var keymappingSelectors = [#selector(UIApplication.switchEditorMode(_:)),
#selector(UIApplication.removeElement(_:)),
#selector(UIApplication.upscaleElement(_:)),
#selector(UIApplication.downscaleElement(_:)),
#selector(UIApplication.rotateView(_:)),
#selector(UIApplication.toggleDebugOverlay(_:)),
#selector(UIApplication.hideCursor(_:))
#selector(UIApplication.hideCursor(_:)),
#selector(UIApplication.nextKeymap(_:)),
#selector(UIApplication.previousKeymap(_:)),

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

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Comma Violation: Collection literals should not have trailing commas (trailing_comma)

]

class MenuController {
Expand Down Expand Up @@ -156,8 +186,17 @@
}

class func keymappingMenu() -> UIMenu {
let keyCommands = [ "K", UIKeyCommand.inputDelete,
UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R", "D", "."]
let keyCommands = [
"K", // Toggle keymap editor
UIKeyCommand.inputDelete, // Remove keymap element
UIKeyCommand.inputUpArrow, // Increase keymap element size
UIKeyCommand.inputDownArrow, // Decrease keymap element size
"R", // Rotate display
"D", // Toggle debug overlay
".", // Hide cursor until move
"N", // Next keymap
"B", // Previous keymap

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

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Comma Violation: Collection literals should not have trailing commas (trailing_comma)
]
let arrowKeyChildrenCommands = zip(keyCommands, keymapping).map { (command, btn) in
UIKeyCommand(title: btn,
image: nil,
Expand Down
10 changes: 5 additions & 5 deletions PlayTools/Editor/Controllers/EditorController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,20 +96,20 @@ class EditorController {
}

func showButtons() {
for button in keymap.keymapData.draggableButtonModels {
for button in keymap.currentKeymap.draggableButtonModels {
let ctrl = DraggableButtonModel(data: button)
addControlToView(control: ctrl)
}
for joystick in keymap.keymapData.joystickModel {
for joystick in keymap.currentKeymap.joystickModel {
let ctrl = JoystickModel(data: joystick)
addControlToView(control: ctrl)
}
for mouse in keymap.keymapData.mouseAreaModel {
for mouse in keymap.currentKeymap.mouseAreaModel {
let ctrl =
MouseAreaModel(data: mouse)
addControlToView(control: ctrl)
}
for button in keymap.keymapData.buttonModels {
for button in keymap.currentKeymap.buttonModels {
let ctrl = ButtonModel(data: button)
addControlToView(control: ctrl)
}
Expand All @@ -132,7 +132,7 @@ class EditorController {
break
}
}
keymap.keymapData = keymapData
keymap.currentKeymap = keymapData
controls = []
view.subviews.forEach { $0.removeFromSuperview() }
}
Expand Down
178 changes: 152 additions & 26 deletions PlayTools/Keymap/Keymapping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,55 +13,177 @@ class Keymapping {
static let shared = Keymapping()

let bundleIdentifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? ""
var keymapUrl: URL
var keymapData: KeymappingData {
didSet {
encode()

private var keymapIdx: Int
public var currentKeymap: KeymappingData {
get {
getKeymap(name: currentKeymapName)
}
set {
setKeymap(name: currentKeymapName, map: newValue)
}
}

private let baseKeymapURL: URL
private let configURL: URL
private var keymapURLs: [String: URL]

public var keymapConfig: KeymapConfig {
get {
do {
let data = try Data(contentsOf: configURL)
return try PropertyListDecoder().decode(KeymapConfig.self, from: data)
} catch {
print("[PlayTools] Failed to decode config url.\n%@")
return resetConfig()
}
}
set {
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml

do {
let data = try encoder.encode(newValue)
try data.write(to: configURL)
} catch {
print("[PlayTools] Keymapping encode failed.\n%@")
}
}
}

public var currentKeymapName: String {
Array(keymapURLs.keys)[keymapIdx]
}

init() {
keymapUrl = URL(fileURLWithPath: "/Users/\(NSUserName())/Library/Containers/io.playcover.PlayCover")
baseKeymapURL = URL(fileURLWithPath: "/Users/\(NSUserName())/Library/Containers/io.playcover.PlayCover")
.appendingPathComponent("Keymapping")
if !FileManager.default.fileExists(atPath: keymapUrl.path) {
.appendingPathComponent(bundleIdentifier)

configURL = baseKeymapURL.appendingPathComponent(".config").appendingPathExtension("plist")
keymapURLs = [:]

keymapIdx = 0

do {
let data = try Data(contentsOf: configURL)
keymapConfig = try PropertyListDecoder().decode(KeymapConfig.self, from: data)
} catch {
print("[PlayTools] Failed to decode config url.\n%@")
keymapConfig = KeymapConfig(defaultKm: "default")
resetConfig()
}

loadKeymapData()
}

private func loadKeymapData() {
if !FileManager.default.fileExists(atPath: baseKeymapURL.path) {
do {
try FileManager.default.createDirectory(
atPath: keymapUrl.path,
atPath: baseKeymapURL.path,
withIntermediateDirectories: true,
attributes: [:])
} catch {
print("[PlayTools] Failed to create Keymapping directory.\n%@")
}
}
keymapUrl.appendPathComponent("\(bundleIdentifier).plist")
keymapData = KeymappingData(bundleIdentifier: bundleIdentifier)
if !decode() {
encode()

reloadKeymapCache()

if let defaultKmIdx = keymapURLs.keys.firstIndex(of: keymapConfig.defaultKm) {
keymapIdx = keymapURLs.distance(from: keymapURLs.startIndex, to: defaultKmIdx)
}
}

func encode() {
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
public func reloadKeymapCache() {
keymapURLs = [:]

do {
let data = try encoder.encode(keymapData)
try data.write(to: keymapUrl)
let directoryContents = try FileManager.default
.contentsOfDirectory(at: baseKeymapURL, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])

if directoryContents.count > 0 {
for keymap in directoryContents where keymap.pathExtension.contains("plist") {
keymapURLs[keymap.deletingPathExtension().lastPathComponent] = keymap
}

return
}
} catch {
print("[PlayTools] Keymapping encode failed.\n%@")
print("[PlayTools] Failed to get keymapping directory.\n%@")
}

setKeymap(name: "default", map: KeymappingData(bundleIdentifier: bundleIdentifier))
reloadKeymapCache()
}

func decode() -> Bool {
do {
let data = try Data(contentsOf: keymapUrl)
keymapData = try PropertyListDecoder().decode(KeymappingData.self, from: data)
return true
} catch {
keymapData = KeymappingData(bundleIdentifier: bundleIdentifier)
print("[PlayTools] Keymapping decode failed.\n%@")
return false
private func getKeymap(name: String) -> KeymappingData {
if let keymapURL = keymapURLs[name] {
do {
let data = try Data(contentsOf: keymapURL)
let map = try PropertyListDecoder().decode(KeymappingData.self, from: data)
return map
} catch {
print("[PlayTools] Keymapping decode failed.\n%@")
}
} else {
print("[PlayTools] Unable to find keymap with name \(name).\n%@")
}

return resetKeymap(name: name)
}

private func setKeymap(name: String, map: KeymappingData) {
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml

if !keymapURLs.keys.contains(name) {
let mapURL = baseKeymapURL.appendingPathComponent(name).appendingPathExtension("plist")

keymapURLs[name] = mapURL
}

if let keymapURL = keymapURLs[name] {
do {
let data = try encoder.encode(map)
try data.write(to: keymapURL)
} catch {
print("[PlayTools] Keymapping encode failed.\n%@")
}
} else {
print("[PlayTools] Unable to find keymap with name \(name).\n%@")
}
}

public func nextKeymap() {
keymapIdx = (keymapIdx + 1) % keymapURLs.count
}

public func previousKeymap() {
keymapIdx = (keymapIdx - 1 + keymapURLs.count) % keymapURLs.count
}

@discardableResult
public func resetKeymap(name: String) -> KeymappingData {
setKeymap(name: name, map: KeymappingData(bundleIdentifier: bundleIdentifier))
return getKeymap(name: name)
}

@discardableResult
private func resetConfig() -> KeymapConfig {
let defaultKm = keymapURLs.keys.contains("default") ? "default" : keymapURLs.keys.first

guard let defaultKm = defaultKm else {
reloadKeymapCache()
return resetConfig()
}

keymapConfig = KeymapConfig(defaultKm: defaultKm)

return keymapConfig
}

}

struct KeymappingData: Codable {
Expand All @@ -72,3 +194,7 @@ struct KeymappingData: Codable {
var bundleIdentifier: String
var version = "2.0.0"
}

struct KeymapConfig: Codable {
var defaultKm: String
}