diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index ad8f2234..39587a67 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -15,6 +15,9 @@ 6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CBA287ABFD5000BEE9D /* UIKit.framework */; }; 6E84A15028D0F97500BF7495 /* AKInterface.bundle in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 951D8275299D097C00D35B20 /* Playtools.strings in Resources */ = {isa = PBXBuildFile; fileRef = 951D8277299D097C00D35B20 /* Playtools.strings */; }; + 9555ED2F2C058E08006E469C /* DebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9555ED2E2C058E08006E469C /* DebugView.swift */; }; + 9555ED312C058E28006E469C /* DebugModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9555ED302C058E28006E469C /* DebugModel.swift */; }; + 9555ED332C058E36006E469C /* DebugController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9555ED322C058E36006E469C /* DebugController.swift */; }; 954389C22B38922400B063BB /* MouseArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954389C12B38922400B063BB /* MouseArea.swift */; }; 954389C42B38968C00B063BB /* Joystick.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954389C32B38968C00B063BB /* Joystick.swift */; }; 954389C62B3896E600B063BB /* ChildButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954389C52B3896E600B063BB /* ChildButton.swift */; }; @@ -104,6 +107,9 @@ 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AKInterface.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 951D8276299D097C00D35B20 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Playtools.strings; sourceTree = ""; }; 951D8278299D098000D35B20 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Playtools.strings"; sourceTree = ""; }; + 9555ED2E2C058E08006E469C /* DebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugView.swift; sourceTree = ""; }; + 9555ED302C058E28006E469C /* DebugModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugModel.swift; sourceTree = ""; }; + 9555ED322C058E36006E469C /* DebugController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugController.swift; sourceTree = ""; }; 954389C12B38922400B063BB /* MouseArea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseArea.swift; sourceTree = ""; }; 954389C32B38968C00B063BB /* Joystick.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Joystick.swift; sourceTree = ""; }; 954389C52B3896E600B063BB /* ChildButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChildButton.swift; sourceTree = ""; }; @@ -204,6 +210,14 @@ name = AKInterface; sourceTree = ""; }; + 9555ED2D2C058DE3006E469C /* DebugOverlay */ = { + isa = PBXGroup; + children = ( + 9555ED2E2C058E08006E469C /* DebugView.swift */, + 9555ED302C058E28006E469C /* DebugModel.swift */, + 9555ED322C058E36006E469C /* DebugController.swift */, + ); + path = DebugOverlay; 954389BE2B37C75C00B063BB /* Models */ = { isa = PBXGroup; children = ( @@ -362,6 +376,7 @@ 9562D1712AB51B57002C329D /* Backend */ = { isa = PBXGroup; children = ( + 9555ED2D2C058DE3006E469C /* DebugOverlay */, 9562D17A2AB6E5ED002C329D /* Action */, AA719724287A480C00623C15 /* Toucher.swift */, ); @@ -713,8 +728,10 @@ AA7197AA287A481500623C15 /* Toast.swift in Sources */, AA719789287A480D00623C15 /* PlayInput.swift in Sources */, AA71970B287A44D200623C15 /* PlayLoader.m in Sources */, + 9555ED332C058E36006E469C /* DebugController.swift in Sources */, 9562D1512AB484C7002C329D /* EventAdapters.swift in Sources */, AA7197AF287A481500623C15 /* KeyCodeNames.swift in Sources */, + 9555ED2F2C058E08006E469C /* DebugView.swift in Sources */, AA71978A287A480D00623C15 /* ControlMode.swift in Sources */, AA7197AD287A481500623C15 /* EditorController.swift in Sources */, 9562D16B2AB505AD002C329D /* MouseEventAdapter.swift in Sources */, @@ -722,6 +739,7 @@ 9562D1772AB52550002C329D /* EditorControllerEventAdapter.swift in Sources */, AA7197A3287A481500623C15 /* CircleMenuButton.swift in Sources */, 9562D1582AB4FB9B002C329D /* TouchscreenKeyboardEventAdapter.swift in Sources */, + 9555ED312C058E28006E469C /* DebugModel.swift in Sources */, AA7197A9287A481500623C15 /* PlayInfo.swift in Sources */, AA71986A287A81A000623C15 /* PTFakeMetaTouch.m in Sources */, 9562D15E2AB4FCB8002C329D /* TouchscreenControllerEventAdapter.swift in Sources */, diff --git a/PlayTools/Controls/Backend/Action/PlayAction.swift b/PlayTools/Controls/Backend/Action/PlayAction.swift index 51894009..1b768a8b 100644 --- a/PlayTools/Controls/Backend/Action/PlayAction.swift +++ b/PlayTools/Controls/Backend/Action/PlayAction.swift @@ -12,7 +12,8 @@ protocol Action { class ButtonAction: Action { func invalidate() { - Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id) + Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id, + actionName: "Button", keyName: keyName) } let keyCode: Int @@ -42,9 +43,11 @@ class ButtonAction: Action { func update(pressed: Bool) { if pressed { - Toucher.touchcam(point: point, phase: UITouch.Phase.began, tid: &id) + Toucher.touchcam(point: point, phase: UITouch.Phase.began, tid: &id, + actionName: "Button", keyName: keyName) } else { - Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id) + Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id, + actionName: "Button", keyName: keyName) } } } @@ -59,7 +62,8 @@ class DraggableButtonAction: ButtonAction { override func update(pressed: Bool) { if pressed { - Toucher.touchcam(point: point, phase: UITouch.Phase.began, tid: &id) + Toucher.touchcam(point: point, phase: UITouch.Phase.began, tid: &id, + actionName: "DraggableButton", keyName: keyName) self.releasePoint = point ActionDispatcher.register(key: KeyCodeNames.mouseMove, handler: self.onMouseMoved, @@ -68,7 +72,8 @@ class DraggableButtonAction: ButtonAction { AKInterface.shared!.hideCursor() } } else { - Toucher.touchcam(point: releasePoint, phase: UITouch.Phase.ended, tid: &id) + 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() { @@ -86,7 +91,8 @@ class DraggableButtonAction: ButtonAction { func onMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { self.releasePoint.x += deltaX self.releasePoint.y -= deltaY - Toucher.touchcam(point: self.releasePoint, phase: UITouch.Phase.moved, tid: &id) + Toucher.touchcam(point: self.releasePoint, phase: UITouch.Phase.moved, tid: &id, + actionName: "DraggableButton", keyName: keyName) } } @@ -117,13 +123,16 @@ class ContinuousJoystickAction: Action { if dis < 16 { if begun { begun = false - Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id) + Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id, + actionName: "ControllerJoystick", keyName: key) } } else if !begun { begun = true - Toucher.touchcam(point: point, phase: UITouch.Phase.began, tid: &id) + Toucher.touchcam(point: point, phase: UITouch.Phase.began, tid: &id, + actionName: "ControllerJoystick", keyName: key) } else { - Toucher.touchcam(point: point, phase: UITouch.Phase.moved, tid: &id) + Toucher.touchcam(point: point, phase: UITouch.Phase.moved, tid: &id, + actionName: "ControllerJoystick", keyName: key) } } @@ -140,7 +149,8 @@ class ContinuousJoystickAction: Action { } func invalidate() { - Toucher.touchcam(point: CGPoint(x: 10, y: 10), phase: UITouch.Phase.ended, tid: &id) + Toucher.touchcam(point: CGPoint(x: 10, y: 10), phase: UITouch.Phase.ended, tid: &id, + actionName: "ControllerJoystick", keyName: key) } } @@ -178,7 +188,8 @@ class JoystickAction: Action { } func invalidate() { - Toucher.touchcam(point: center, phase: UITouch.Phase.ended, tid: &id) + Toucher.touchcam(point: center, phase: UITouch.Phase.ended, tid: &id, + actionName: "KeyboardJoystick", keyName: "Keyboard") } func getPressedHandler(index: Int) -> (Bool) -> Void { @@ -228,11 +239,13 @@ class JoystickAction: Action { let moving = id != nil if atCenter() { if moving { - Toucher.touchcam(point: touch, phase: UITouch.Phase.ended, tid: &id) + Toucher.touchcam(point: touch, phase: UITouch.Phase.ended, tid: &id, + actionName: "KeyboardJoystick", keyName: "Keyboard") } } else { if moving { - Toucher.touchcam(point: touch, phase: UITouch.Phase.moved, tid: &id) + Toucher.touchcam(point: touch, phase: UITouch.Phase.moved, tid: &id, + actionName: "KeyboardJoystick", keyName: "Keyboard") } else { begin() } @@ -241,26 +254,29 @@ class JoystickAction: Action { func handleFree() { handleCommon { - Toucher.touchcam(point: self.center, phase: UITouch.Phase.began, tid: &id) + Toucher.touchcam(point: self.center, phase: UITouch.Phase.began, tid: &id, + actionName: "KeyboardJoystick", keyName: "Keyboard") PlayInput.touchQueue.asyncAfter(deadline: .now() + 0.04, qos: .userInitiated) { if self.id == nil { return } - Toucher.touchcam(point: self.touch, phase: UITouch.Phase.moved, tid: &self.id) + Toucher.touchcam(point: self.touch, phase: UITouch.Phase.moved, tid: &self.id, + actionName: "KeyboardJoystick", keyName: "Keyboard") } // end closure } } func handleFixed() { handleCommon { - Toucher.touchcam(point: self.touch, phase: UITouch.Phase.began, tid: &id) + Toucher.touchcam(point: self.touch, phase: UITouch.Phase.began, tid: &id, + actionName: "KeyboardJoystick", keyName: "Keyboard") } } } class CameraAction: Action { var swipeMove, swipeScale1, swipeScale2: SwipeAction - static var swipeDrag = SwipeAction() + static var swipeDrag = SwipeAction(actionName: "Drag", keyName: "ScrollWheel") var key: String! var center: CGPoint @@ -269,9 +285,9 @@ class CameraAction: Action { let centerX = data.transform.xCoord.absoluteX let centerY = data.transform.yCoord.absoluteY center = CGPoint(x: centerX, y: centerY) - swipeMove = SwipeAction() - swipeScale1 = SwipeAction() - swipeScale2 = SwipeAction() + swipeMove = SwipeAction(actionName: "Camera", keyName: key) + swipeScale1 = SwipeAction(actionName: "Zoom1", keyName: "ScrollWheel") + swipeScale2 = SwipeAction(actionName: "Zoom2", keyName: "ScrollWheel") ActionDispatcher.register(key: key, handler: self.moveUpdated, priority: .CAMERA) ActionDispatcher.register(key: KeyCodeNames.scrollWheelScale, @@ -316,7 +332,10 @@ class SwipeAction: Action { var location: CGPoint = CGPoint.zero private var id: Int? let timer = DispatchSource.makeTimerSource(flags: [], queue: PlayInput.touchQueue) - init() { + private let actionName: String, keyName: String; + init(actionName: String, keyName: String) { + self.actionName = actionName + self.keyName = keyName timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.1, leeway: DispatchTimeInterval.milliseconds(50)) timer.setEventHandler(qos: .userInteractive, handler: self.checkEnded) timer.activate() @@ -385,7 +404,8 @@ class SwipeAction: Action { guard let start = from() else {return} location = start counter = 0 - Toucher.touchcam(point: location, phase: UITouch.Phase.began, tid: &id) + Toucher.touchcam(point: location, phase: UITouch.Phase.began, tid: &id, + actionName: actionName, keyName: keyName) timer.resume() } else { if shouldEdgeReset { @@ -394,7 +414,8 @@ class SwipeAction: Action { } // 1. Put location update after touch action, so that final `end` touch has different location // 2. If `began` touched, do not `move` touch at the same time, otherwise the two may conflict - Toucher.touchcam(point: self.location, phase: UITouch.Phase.moved, tid: &id) + Toucher.touchcam(point: self.location, phase: UITouch.Phase.moved, tid: &id, + actionName: actionName, keyName: keyName) } // Scale movement down, so that an edge reset won't cause a too short touch sequence var scaledDeltaX = deltaX @@ -431,7 +452,8 @@ class SwipeAction: Action { if id == nil { return } - Toucher.touchcam(point: self.location, phase: UITouch.Phase.ended, tid: &id) + Toucher.touchcam(point: self.location, phase: UITouch.Phase.ended, tid: &id, + actionName: actionName, keyName: keyName) // Touch might somehow fail to end if id == nil { timer.suspend() @@ -461,7 +483,8 @@ class FakeMouseAction: Action { // DispatchQueue.main.async { // Toast.showHint(title: "Fake mouse pressed", text: ["\(self.pos)"]) // } - Toucher.touchcam(point: pos, phase: UITouch.Phase.began, tid: &id) + Toucher.touchcam(point: pos, phase: UITouch.Phase.began, tid: &id, + actionName: "FakeMouse", keyName: "FakeMouse") ActionDispatcher.register(key: KeyCodeNames.fakeMouse, handler: movementHandler, priority: .DRAGGABLE) @@ -475,7 +498,8 @@ class FakeMouseAction: Action { // DispatchQueue.main.async { // Toast.showHint(title: " lift Fake mouse", text: ["\(self.pos)"]) // } - Toucher.touchcam(point: pos, phase: UITouch.Phase.ended, tid: &id) + Toucher.touchcam(point: pos, phase: UITouch.Phase.ended, tid: &id, + actionName: "FakeMouse", keyName: "FakeMouse") if id == nil { ActionDispatcher.unregister(key: KeyCodeNames.fakeMouse) } @@ -484,13 +508,15 @@ class FakeMouseAction: Action { func movementHandler(xValue: CGFloat, yValue: CGFloat) { pos.x = xValue pos.y = yValue - Toucher.touchcam(point: pos, phase: UITouch.Phase.moved, tid: &id) + Toucher.touchcam(point: pos, phase: UITouch.Phase.moved, tid: &id, + actionName: "FakeMouse", keyName: "FakeMouse") } func invalidate() { ActionDispatcher.unregister(key: KeyCodeNames.fakeMouse) - Toucher.touchcam(point: pos, - phase: UITouch.Phase.ended, tid: &self.id) + Toucher.touchcam(point: pos ?? CGPoint(x: 10, y: 10), + phase: UITouch.Phase.ended, tid: &self.id, + actionName: "FakeMouse", keyName: "FakeMouse") } } diff --git a/PlayTools/Controls/Backend/DebugOverlay/DebugController.swift b/PlayTools/Controls/Backend/DebugOverlay/DebugController.swift new file mode 100644 index 00000000..c532d7cc --- /dev/null +++ b/PlayTools/Controls/Backend/DebugOverlay/DebugController.swift @@ -0,0 +1,37 @@ +// +// DebugController.swift +// PlayTools +// +// Created by 许沂聪 on 2024/5/28. +// + +import Foundation + +class DebugController { + static let instance = DebugController() + private init() { + + } + + private var debugView = DebugContainer.instance + + public func toggleDebugOverlay() { + let window = screen.keyWindow + let controller = window!.rootViewController + let view = controller!.view + if debugView.superview == nil { + view!.addSubview(debugView) + view!.bringSubviewToFront(debugView) + debugView.isHidden = false + debugView.isUserInteractionEnabled = false + PlayInput.touchQueue.async { + DebugModel.instance.enabled = true + } + } else { + debugView.removeFromSuperview() + PlayInput.touchQueue.async { + DebugModel.instance.enabled = false + } + } + } +} diff --git a/PlayTools/Controls/Backend/DebugOverlay/DebugModel.swift b/PlayTools/Controls/Backend/DebugOverlay/DebugModel.swift new file mode 100644 index 00000000..1af8e5b3 --- /dev/null +++ b/PlayTools/Controls/Backend/DebugOverlay/DebugModel.swift @@ -0,0 +1,45 @@ +// +// DebugModel.swift +// PlayTools +// +// Created by 许沂聪 on 2024/5/28. +// + +import Foundation + +class DebugModel { + static let instance = DebugModel() + private init() { + touches = [] + } + struct TouchPoint { + var point: CGPoint + var phase: UITouch.Phase + var description: String + } + + public var touches: [TouchPoint] + public var enabled = false + public func record(point: CGPoint, phase: UITouch.Phase, tid: Int, description: String) { + // If debug screen not enabled, do not record + if !enabled { + return + } + // Run in main thread, because `touches` is not thread safe + DispatchQueue.main.async { + while self.touches.count < tid { + // report error + self.touches.append(TouchPoint( + point: CGPoint(x: 100, y: 100), + phase: UITouch.Phase.cancelled, + description: "Error recording debug info: point id exceeds record array" + )) + } + if self.touches.count == tid { + self.touches.append(TouchPoint(point: point, phase: phase, description: description)) + } else { + self.touches[tid] = TouchPoint(point: point, phase: phase, description: description) + } + } + } +} diff --git a/PlayTools/Controls/Backend/DebugOverlay/DebugView.swift b/PlayTools/Controls/Backend/DebugOverlay/DebugView.swift new file mode 100644 index 00000000..7850ecae --- /dev/null +++ b/PlayTools/Controls/Backend/DebugOverlay/DebugView.swift @@ -0,0 +1,128 @@ +// +// DebugView.swift +// PlayTools +// +// Created by 许沂聪 on 2024/5/28. +// + +import Foundation +import UIKit + +class RingView: UIView { + + var ringColor: UIColor = .blue { + didSet { + shapeLayer.strokeColor = ringColor.cgColor + } + } + + var ringWidth: CGFloat = 10 { + didSet { + shapeLayer.lineWidth = ringWidth + } + } + + var text: String? { + didSet { + label.text = text + setNeedsLayout() + } + } + + private let shapeLayer = CAShapeLayer() + private let label = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + commonInit() + } + + private func commonInit() { + shapeLayer.lineWidth = ringWidth + shapeLayer.fillColor = nil + shapeLayer.strokeColor = ringColor.cgColor + layer.addSublayer(shapeLayer) + + label.textAlignment = .center + label.textColor = .black + addSubview(label) + } + + public func setData(position: CGPoint, description: String, phase: UITouch.Phase) { + center = position + text = description + let colorMap: [UIColor] = [.green, .red, .brown, .blue, .purple, .cyan, .gray, .darkGray, .black] + ringColor = colorMap[phase.rawValue] + } + + override func draw(_ rect: CGRect) { + let center = CGPoint(x: rect.midX, y: rect.midY) + let radius = CGFloat(floatLiteral: 20) + + let startAngle = -CGFloat.pi / 2 + let endAngle = startAngle + 2 * CGFloat.pi + + let path = UIBezierPath(arcCenter: center, radius: radius, + startAngle: startAngle, endAngle: endAngle, clockwise: true) + + shapeLayer.path = path.cgPath + } + + override func layoutSubviews() { + super.layoutSubviews() + // Put label 30 pixels down the ring + label.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 20) + label.center = CGPoint(x: bounds.midX, y: bounds.midY + 30) + } +} + +class DebugContainer: UIView { + static let instance = DebugContainer() + private init() { + super.init(frame: CGRect(x: 0, y: 0, width: screen.width, height: screen.height)) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + // This is called once when the view shows + override func draw(_ rect: CGRect) { + let data = DebugModel.instance.touches + while subviews.count < data.count { + addSubview(RingView(frame: CGRect(x: 100, y: 100, width: 300, height: 300))) + } + while subviews.count > data.count { + subviews.last?.removeFromSuperview() + } + var iter = data.makeIterator() + for view in subviews { + guard let ring = view as? RingView else { + continue + } + guard let point = iter.next() else { + continue + } + let description = point.description + ": " + point.phase.name() + ring.setData(position: point.point, description: description, phase: point.phase) + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + if self.superview == nil { + return + } + self.draw(CGRect()) + } + } +} + +extension UITouch.Phase { + public func name() -> String { + let nameMap = ["Began", "Moved", "Stationary", "Ended", "Cancelled", + "regionEntered", "regionMoved", "regionExited"] + return nameMap[self.rawValue] + } +} diff --git a/PlayTools/Controls/Backend/Toucher.swift b/PlayTools/Controls/Backend/Toucher.swift index a4dce82d..94bd55f9 100644 --- a/PlayTools/Controls/Backend/Toucher.swift +++ b/PlayTools/Controls/Backend/Toucher.swift @@ -19,7 +19,9 @@ class Toucher { on invocations with phase "began", an int id is allocated, which can be used later to refer to this touch point. on invocations with phase "ended", id is set to nil representing the touch point is no longer valid. */ - static func touchcam(point: CGPoint, phase: UITouch.Phase, tid: inout Int?) { + static func touchcam(point: CGPoint, phase: UITouch.Phase, tid: inout Int?, + // Name info for debug use + actionName: String, keyName: String) { if phase == UITouch.Phase.began { if tid != nil { return @@ -30,12 +32,17 @@ class Toucher { } else if tid == nil { return } + var recordId = tid! tid = PTFakeMetaTouch.fakeTouchId(tid!, at: point, with: phase, in: keyWindow, on: keyView) writeLog(logMessage: "\(phase.rawValue.description) \(tid!.description) \(point.debugDescription)") if tid! < 0 { tid = nil + } else { + recordId = tid! } + DebugModel.instance.record(point: point, phase: phase, tid: recordId, + description: actionName + "(" + keyName + ")") } static func setupLogfile() { diff --git a/PlayTools/Controls/MenuController.swift b/PlayTools/Controls/MenuController.swift index 19db6674..7fc187e0 100644 --- a/PlayTools/Controls/MenuController.swift +++ b/PlayTools/Controls/MenuController.swift @@ -61,11 +61,16 @@ extension UIApplication { rootViewController.rotateView(sender) } } - + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2, execute: { Toast.showHint(title: "Rotated") }) } + + @objc + func toggleDebugOverlay(_ sender: AnyObject) { + DebugController.instance.toggleDebugOverlay() + } } extension UIViewController { @@ -94,13 +99,16 @@ var keymapping = [ NSLocalizedString("menu.keymapping.downsizeElement", tableName: "Playtools", value: "Downsize selected element", comment: ""), NSLocalizedString("menu.keymapping.rotateDisplay", tableName: "Playtools", - value: "Rotate display area", comment: "") + value: "Rotate display area", comment: ""), + NSLocalizedString("menu.keymapping.toggleDebug", tableName: "Playtools", + value: "Toggle Debug Overlay", comment: ""), ] var keymappingSelectors = [#selector(UIApplication.switchEditorMode(_:)), #selector(UIApplication.removeElement(_:)), #selector(UIApplication.upscaleElement(_:)), #selector(UIApplication.downscaleElement(_:)), - #selector(UIApplication.rotateView(_:)) + #selector(UIApplication.rotateView(_:)), + #selector(UIApplication.toggleDebugOverlay(_:)) ] class MenuController { @@ -142,7 +150,7 @@ class MenuController { class func keymappingMenu() -> UIMenu { let keyCommands = [ "K", UIKeyCommand.inputDelete, - UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R", "L"] + UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R", "D"] let arrowKeyChildrenCommands = zip(keyCommands, keymapping).map { (command, btn) in UIKeyCommand(title: btn, image: nil, diff --git a/PlayTools/en.lproj/Playtools.strings b/PlayTools/en.lproj/Playtools.strings index 0311d2a5..14af8643 100644 Binary files a/PlayTools/en.lproj/Playtools.strings and b/PlayTools/en.lproj/Playtools.strings differ