mirror of
https://github.com/libretro/RetroArch
synced 2025-01-30 12:32:52 +00:00
[iOS] New iOS 13 project to support Swift; Custom keyboard and touch mouse support (#13435)
* Support for Swift, added emulator keyboard * fixed toggle key handling using magic number hack for now * fixed keyboard transparency slider for now with suboptimal fix; add gesture recognizer to hide/show keyboard * Support CocoaView extensions in Swift; move keyboard delegate impl and setup to swift extension * moved keyboard view model creation out of EmulatorKeyboard * implement key pressed delegate in swift extension * added input method for directly sending RETROK_* codes to support a touchscreen keyboard; assign keyboard model delegates; updated keyboard layout (added F1-F12 keys); change shift, control and alt keys to be modifiers * enable focus mode when custom keyboard is shown; enable/disable overlay when custom keyboard is toggled * Specify -DHAVE_OPENGLES2 instead of -DHAVE_OPENGLES3 since glsym_es3.h does not compile in iOS 14.5 Fix tvOS build using compiler flags * Create new project for iOS 13 deploy target; add check for deploy target to conditionally compile for new iOS 13 specific feature (custom keyboard) * force disable core info caching for iOS, use opengl es2 for debug * Add flag for iOS custom keyboard - 3-finger swipe up to show, 3-finger swipe down to hide * use OpenGLES2 instead; using ES3 results in compile time errors on iOS 14.5 * code cleanup * Updated references to -DDONT_WANT_ARM_ASM_OPTIMIZATIONS flag * Add JIT support for non-jailbroken devices * iOS: Add support for touch mouse handler * Added a HAVE_IOS_TOUCHMOUSE preprocessor macro so that it builds under the iOS11_Metal xcode project * Changed click-and-drag behavior to double tap hold and drag * Visual improvements to the emulator keyboard: updated colors, improved key-press effect
This commit is contained in:
parent
3d54d952a5
commit
bc02f8319e
@ -41,6 +41,20 @@ float cocoa_screen_get_backing_scale_factor(void);
|
||||
#endif
|
||||
|
||||
static bool apple_key_state[MAX_KEYS];
|
||||
|
||||
// Send keyboard inputs directly using RETROK_* codes
|
||||
// Used by the iOS custom keyboard implementation
|
||||
void apple_direct_input_keyboard_event(bool down,
|
||||
unsigned code, uint32_t character, uint32_t mod, unsigned device)
|
||||
{
|
||||
int appleKey = rarch_keysym_lut[code];
|
||||
apple_key_state[appleKey] = down;
|
||||
input_keyboard_event(down,
|
||||
code,
|
||||
character, (enum retro_mod)mod, device);
|
||||
}
|
||||
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
/* TODO/FIXME - static globals */
|
||||
static bool small_keyboard_active = false;
|
||||
@ -433,8 +447,12 @@ static int16_t cocoa_input_state(
|
||||
return apple->window_pos_x * cocoa_screen_get_backing_scale_factor();
|
||||
#endif
|
||||
}
|
||||
#ifdef IOS
|
||||
val = apple->mouse_rel_x;
|
||||
#else
|
||||
val = apple->window_pos_x - apple->mouse_x_last;
|
||||
apple->mouse_x_last = apple->window_pos_x;
|
||||
#endif
|
||||
return val;
|
||||
case RETRO_DEVICE_ID_MOUSE_Y:
|
||||
if (device == RARCH_DEVICE_MOUSE_SCREEN)
|
||||
@ -445,8 +463,12 @@ static int16_t cocoa_input_state(
|
||||
return apple->window_pos_y * cocoa_screen_get_backing_scale_factor();
|
||||
#endif
|
||||
}
|
||||
#ifdef IOS
|
||||
val = apple->mouse_rel_y;
|
||||
#else
|
||||
val = apple->window_pos_y - apple->mouse_y_last;
|
||||
apple->mouse_y_last = apple->window_pos_y;
|
||||
#endif
|
||||
return val;
|
||||
case RETRO_DEVICE_ID_MOUSE_LEFT:
|
||||
return apple->mouse_buttons & 1;
|
||||
|
@ -169,6 +169,9 @@ RETRO_BEGIN_DECLS
|
||||
void apple_input_keyboard_event(bool down,
|
||||
unsigned code, uint32_t character, uint32_t mod, unsigned device);
|
||||
|
||||
void apple_direct_input_keyboard_event(bool down,
|
||||
unsigned code, uint32_t character, uint32_t mod, unsigned device);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
|
@ -2487,6 +2487,7 @@ static int action_ok_playlist_entry_collection(const char *path,
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifndef IOS
|
||||
core_info = playlist_entry_get_core_info(entry);
|
||||
|
||||
if (core_info && !string_is_empty(core_info->path))
|
||||
@ -2498,6 +2499,10 @@ static int action_ok_playlist_entry_collection(const char *path,
|
||||
strlcpy(core_path, entry->core_path, sizeof(core_path));
|
||||
playlist_resolve_path(PLAYLIST_LOAD, true, core_path, sizeof(core_path));
|
||||
}
|
||||
#else
|
||||
strlcpy(core_path, entry->core_path, sizeof(core_path));
|
||||
playlist_resolve_path(PLAYLIST_LOAD, true, core_path, sizeof(core_path));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
233
pkg/apple/CocoaView+KeyboardSupport.swift
Normal file
233
pkg/apple/CocoaView+KeyboardSupport.swift
Normal file
@ -0,0 +1,233 @@
|
||||
//
|
||||
// CocoaView+KeyboardSupport.swift
|
||||
// RetroArchiOS11
|
||||
//
|
||||
// Created by Yoshi Sugawara on 2/25/21.
|
||||
// Copyright © 2021 RetroArch. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension CocoaView {
|
||||
var leftKeyboardModel: EmulatorKeyboardViewModel {
|
||||
return EmulatorKeyboardViewModel(keys: [
|
||||
[
|
||||
EmulatorKeyboardKey(label: "1", code: Int(RETROK_1.rawValue)),
|
||||
EmulatorKeyboardKey(label: "2", code: Int(RETROK_2.rawValue)),
|
||||
EmulatorKeyboardKey(label: "3", code: Int(RETROK_3.rawValue)),
|
||||
EmulatorKeyboardKey(label: "4", code: Int(RETROK_4.rawValue)),
|
||||
EmulatorKeyboardKey(label: "5", code: Int(RETROK_5.rawValue)),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "q", code: Int(RETROK_q.rawValue)),
|
||||
EmulatorKeyboardKey(label: "w", code: Int(RETROK_w.rawValue)),
|
||||
EmulatorKeyboardKey(label: "e", code: Int(RETROK_e.rawValue)),
|
||||
EmulatorKeyboardKey(label: "r", code: Int(RETROK_r.rawValue)),
|
||||
EmulatorKeyboardKey(label: "t", code: Int(RETROK_t.rawValue)),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "a", code: Int(RETROK_a.rawValue)),
|
||||
EmulatorKeyboardKey(label: "s", code: Int(RETROK_s.rawValue)),
|
||||
EmulatorKeyboardKey(label: "d", code: Int(RETROK_d.rawValue)),
|
||||
EmulatorKeyboardKey(label: "f", code: Int(RETROK_f.rawValue)),
|
||||
EmulatorKeyboardKey(label: "g", code: Int(RETROK_g.rawValue)),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "z", code: Int(RETROK_z.rawValue)),
|
||||
EmulatorKeyboardKey(label: "x", code: Int(RETROK_x.rawValue)),
|
||||
EmulatorKeyboardKey(label: "c", code: Int(RETROK_c.rawValue)),
|
||||
EmulatorKeyboardKey(label: "v", code: Int(RETROK_v.rawValue)),
|
||||
EmulatorKeyboardKey(label: "b", code: Int(RETROK_b.rawValue)),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "SHIFT", code: Int(RETROK_LSHIFT.rawValue), keySize: .standard, isModifier: true, imageName: "shift", imageNameHighlighted: "shift.fill"),
|
||||
EmulatorKeyboardKey(label: "Fn", code: 9000, keySize: .standard, imageName: "fn"),
|
||||
EmulatorKeyboardKey(label: "CTRL", code: Int(RETROK_LCTRL.rawValue), isModifier: true, imageName: "control"),
|
||||
EmulatorKeyboardKey(label: "Space", code: Int(RETROK_SPACE.rawValue), keySize: .wide)
|
||||
]
|
||||
],
|
||||
alternateKeys: [
|
||||
[
|
||||
EmulatorKeyboardKey(label: "ESC", code: Int(RETROK_ESCAPE.rawValue), imageName: "escape"),
|
||||
SliderKey(keySize: .standard)
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "F1", code: Int(RETROK_F1.rawValue)),
|
||||
EmulatorKeyboardKey(label: "F2", code: Int(RETROK_F2.rawValue)),
|
||||
EmulatorKeyboardKey(label: "F3", code: Int(RETROK_F3.rawValue)),
|
||||
EmulatorKeyboardKey(label: "F4", code: Int(RETROK_F4.rawValue)),
|
||||
EmulatorKeyboardKey(label: "F5", code: Int(RETROK_F5.rawValue)),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "-", code: Int(RETROK_MINUS.rawValue)),
|
||||
EmulatorKeyboardKey(label: "=", code: Int(RETROK_EQUALS.rawValue)),
|
||||
EmulatorKeyboardKey(label: "/", code: Int(RETROK_SLASH.rawValue)),
|
||||
EmulatorKeyboardKey(label: "[", code: Int(RETROK_LEFTBRACKET.rawValue)),
|
||||
EmulatorKeyboardKey(label: "]", code: Int(RETROK_RIGHTBRACKET.rawValue)),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: ";", code: Int(RETROK_SEMICOLON.rawValue)),
|
||||
EmulatorKeyboardKey(label: "~", code: Int(RETROK_TILDE.rawValue)),
|
||||
EmulatorKeyboardKey(label: ":", code: Int(RETROK_COLON.rawValue)),
|
||||
EmulatorKeyboardKey(label: "?", code: Int(RETROK_QUESTION.rawValue)),
|
||||
EmulatorKeyboardKey(label: "!", code: Int(RETROK_EXCLAIM.rawValue)),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "SHIFT", code: Int(RETROK_LSHIFT.rawValue), keySize: .standard, isModifier: true, imageName: "shift", imageNameHighlighted: "shift.fill"),
|
||||
EmulatorKeyboardKey(label: "Fn", code: 9000, keySize: .standard, imageName: "fn"),
|
||||
EmulatorKeyboardKey(label: "CTRL", code: Int(RETROK_LCTRL.rawValue), isModifier: true, imageName: "control"),
|
||||
EmulatorKeyboardKey(label: "Space", code: Int(RETROK_SPACE.rawValue), keySize: .wide)
|
||||
]
|
||||
])
|
||||
}
|
||||
|
||||
var rightKeyboardModel: EmulatorKeyboardViewModel {
|
||||
EmulatorKeyboardViewModel(keys: [
|
||||
[
|
||||
EmulatorKeyboardKey(label: "6", code: Int(RETROK_6.rawValue)),
|
||||
EmulatorKeyboardKey(label: "7", code: Int(RETROK_7.rawValue)),
|
||||
EmulatorKeyboardKey(label: "8", code: Int(RETROK_8.rawValue)),
|
||||
EmulatorKeyboardKey(label: "9", code: Int(RETROK_9.rawValue)),
|
||||
EmulatorKeyboardKey(label: "0", code: Int(RETROK_0.rawValue))
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "y", code: Int(RETROK_y.rawValue)),
|
||||
EmulatorKeyboardKey(label: "u", code: Int(RETROK_u.rawValue)),
|
||||
EmulatorKeyboardKey(label: "i", code: Int(RETROK_i.rawValue)),
|
||||
EmulatorKeyboardKey(label: "o", code: Int(RETROK_o.rawValue)),
|
||||
EmulatorKeyboardKey(label: "p", code: Int(RETROK_p.rawValue)),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "h", code: Int(RETROK_h.rawValue)),
|
||||
EmulatorKeyboardKey(label: "j", code: Int(RETROK_j.rawValue)),
|
||||
EmulatorKeyboardKey(label: "k", code: Int(RETROK_k.rawValue)),
|
||||
EmulatorKeyboardKey(label: "l", code: Int(RETROK_l.rawValue)),
|
||||
EmulatorKeyboardKey(label: "'", code: Int(RETROK_QUOTE.rawValue))
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "n", code: Int(RETROK_n.rawValue)),
|
||||
EmulatorKeyboardKey(label: "m", code: Int(RETROK_m.rawValue)),
|
||||
EmulatorKeyboardKey(label: ",", code: Int(RETROK_COMMA.rawValue)),
|
||||
EmulatorKeyboardKey(label: ".", code: Int(RETROK_PERIOD.rawValue)),
|
||||
EmulatorKeyboardKey(label: "BKSPC", code: Int(RETROK_BACKSPACE.rawValue), imageName: "delete.left", imageNameHighlighted: "delete.left.fill")
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "Alt", code: Int(RETROK_LALT.rawValue), isModifier: true, imageName: "alt"),
|
||||
EmulatorKeyboardKey(label: "tab", code: Int(RETROK_TAB.rawValue), imageName: "arrow.right.to.line"),
|
||||
EmulatorKeyboardKey(label: "RETURN", code: Int(RETROK_RETURN.rawValue), keySize: .wide)
|
||||
],
|
||||
],
|
||||
alternateKeys: [
|
||||
[
|
||||
EmulatorKeyboardKey(label: "F6", code: Int(RETROK_F6.rawValue)),
|
||||
EmulatorKeyboardKey(label: "F7", code: Int(RETROK_F7.rawValue)),
|
||||
EmulatorKeyboardKey(label: "F8", code: Int(RETROK_F8.rawValue)),
|
||||
EmulatorKeyboardKey(label: "F9", code: Int(RETROK_F9.rawValue)),
|
||||
EmulatorKeyboardKey(label: "F10", code: Int(RETROK_F10.rawValue)),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "PAGEUP", code: Int(RETROK_PAGEUP.rawValue), imageName: "arrow.up.doc"),
|
||||
EmulatorKeyboardKey(label: "HOME", code: Int(RETROK_HOME.rawValue), imageName: "house"),
|
||||
EmulatorKeyboardKey(label: "INS", code: Int(RETROK_INSERT.rawValue), imageName: "text.insert"),
|
||||
EmulatorKeyboardKey(label: "END", code: Int(RETROK_END.rawValue)),
|
||||
EmulatorKeyboardKey(label: "PAGEDWN", code: Int(RETROK_PAGEDOWN.rawValue), imageName: "arrow.down.doc"),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "F11", code: Int(RETROK_F11.rawValue)),
|
||||
EmulatorKeyboardKey(label: "⬆️", code: Int(RETROK_UP.rawValue), imageName: "arrow.up"),
|
||||
SpacerKey(),
|
||||
SpacerKey(),
|
||||
EmulatorKeyboardKey(label: "F12", code: Int(RETROK_F12.rawValue)),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "⬅️", code: Int(RETROK_LEFT.rawValue), imageName: "arrow.left"),
|
||||
EmulatorKeyboardKey(label: "⬇️", code: Int(RETROK_DOWN.rawValue), imageName: "arrow.down"),
|
||||
EmulatorKeyboardKey(label: "➡️", code: Int(RETROK_RIGHT.rawValue), imageName: "arrow.right"),
|
||||
SpacerKey(),
|
||||
EmulatorKeyboardKey(label: "DEL", code: Int(RETROK_DELETE.rawValue), imageName: "clear", imageNameHighlighted: "clear.fill"),
|
||||
],
|
||||
[
|
||||
EmulatorKeyboardKey(label: "RETURN", code: Int(RETROK_RETURN.rawValue), keySize: .wide)
|
||||
]
|
||||
])
|
||||
}
|
||||
|
||||
@objc func setupEmulatorKeyboard() {
|
||||
keyboardController = EmulatorKeyboardController(leftKeyboardModel: leftKeyboardModel, rightKeyboardModel: rightKeyboardModel)
|
||||
keyboardController.leftKeyboardModel.delegate = self;
|
||||
keyboardController.rightKeyboardModel.delegate = self;
|
||||
addChild(keyboardController)
|
||||
keyboardController.didMove(toParent: self)
|
||||
keyboardController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(keyboardController.view)
|
||||
keyboardController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
keyboardController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
keyboardController.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
||||
keyboardController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
|
||||
keyboardController.leftKeyboardModel.delegate = self
|
||||
keyboardController.rightKeyboardModel.delegate = self
|
||||
keyboardController.leftKeyboardModel.modifierDelegate = self
|
||||
keyboardController.rightKeyboardModel.modifierDelegate = self
|
||||
keyboardController.view.isHidden = true
|
||||
keyboardModifierState = 0
|
||||
}
|
||||
}
|
||||
|
||||
extension CocoaView: EmulatorKeyboardKeyPressedDelegate {
|
||||
func keyUp(_ key: KeyCoded) {
|
||||
print("keyUp: code=\(key.keyCode) keyboardModifierState = \(keyboardModifierState)")
|
||||
apple_direct_input_keyboard_event(false, UInt32(key.keyCode), 0, keyboardModifierState, UInt32(RETRO_DEVICE_KEYBOARD))
|
||||
}
|
||||
|
||||
func keyDown(_ key: KeyCoded) {
|
||||
print("keyDown: code=\(key.keyCode) keyboardModifierState = \(keyboardModifierState)")
|
||||
apple_direct_input_keyboard_event(true, UInt32(key.keyCode), 0, keyboardModifierState, UInt32(RETRO_DEVICE_KEYBOARD))
|
||||
}
|
||||
}
|
||||
|
||||
extension CocoaView: EmulatorKeyboardModifierPressedDelegate {
|
||||
func modifierPressedWithKey(_ key: KeyCoded, enable: Bool) {
|
||||
switch UInt32(key.keyCode) {
|
||||
case RETROK_LSHIFT.rawValue:
|
||||
if enable {
|
||||
keyboardModifierState |= RETROKMOD_SHIFT.rawValue
|
||||
apple_direct_input_keyboard_event(true, UInt32(RETROK_LSHIFT.rawValue), 0, keyboardModifierState, UInt32(RETRO_DEVICE_KEYBOARD))
|
||||
} else {
|
||||
keyboardModifierState &= ~RETROKMOD_SHIFT.rawValue
|
||||
apple_direct_input_keyboard_event(false, UInt32(RETROK_LSHIFT.rawValue), 0, keyboardModifierState, UInt32(RETRO_DEVICE_KEYBOARD))
|
||||
}
|
||||
case RETROK_LCTRL.rawValue:
|
||||
if enable {
|
||||
keyboardModifierState |= RETROKMOD_CTRL.rawValue
|
||||
apple_direct_input_keyboard_event(true, UInt32(RETROK_LCTRL.rawValue), 0, keyboardModifierState, UInt32(RETRO_DEVICE_KEYBOARD))
|
||||
} else {
|
||||
keyboardModifierState &= ~RETROKMOD_CTRL.rawValue
|
||||
apple_direct_input_keyboard_event(false, UInt32(RETROK_LCTRL.rawValue), 0, keyboardModifierState, UInt32(RETRO_DEVICE_KEYBOARD))
|
||||
}
|
||||
case RETROK_LALT.rawValue:
|
||||
if enable {
|
||||
keyboardModifierState |= RETROKMOD_ALT.rawValue
|
||||
apple_direct_input_keyboard_event(true, UInt32(RETROK_LALT.rawValue), 0, keyboardModifierState, UInt32(RETRO_DEVICE_KEYBOARD))
|
||||
} else {
|
||||
keyboardModifierState &= ~RETROKMOD_ALT.rawValue
|
||||
apple_direct_input_keyboard_event(false, UInt32(RETROK_LALT.rawValue), 0, keyboardModifierState, UInt32(RETRO_DEVICE_KEYBOARD))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func isModifierEnabled(key: KeyCoded) -> Bool {
|
||||
switch UInt32(key.keyCode) {
|
||||
case RETROK_LSHIFT.rawValue:
|
||||
return (keyboardModifierState & RETROKMOD_SHIFT.rawValue) != 0
|
||||
case RETROK_LCTRL.rawValue:
|
||||
return (keyboardModifierState & RETROKMOD_CTRL.rawValue) != 0
|
||||
case RETROK_LALT.rawValue:
|
||||
return (keyboardModifierState & RETROKMOD_ALT.rawValue) != 0
|
||||
default:
|
||||
break
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
601
pkg/apple/EmulatorKeyboard.swift
Normal file
601
pkg/apple/EmulatorKeyboard.swift
Normal file
@ -0,0 +1,601 @@
|
||||
//
|
||||
// EmulatorKeyboard.swift
|
||||
//
|
||||
// Created by Yoshi Sugawara on 7/30/20.
|
||||
//
|
||||
|
||||
// TODO: shift key should change the label of the keys to uppercase (need callback mechanism?)
|
||||
// pan gesture to outer edges of keyboard view for better dragging
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class KeyboardButton: UIButton {
|
||||
let key: KeyCoded
|
||||
var toggleState = false
|
||||
|
||||
// MARK: - Functions
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
let newArea = CGRect(
|
||||
x: self.bounds.origin.x - 5.0,
|
||||
y: self.bounds.origin.y - 5.0,
|
||||
width: self.bounds.size.width + 20.0,
|
||||
height: self.bounds.size.height + 20.0
|
||||
)
|
||||
return newArea.contains(point)
|
||||
}
|
||||
|
||||
private func updateColors() {
|
||||
backgroundColor = isHighlighted ? EmulatorKeyboardView.keyPressedBackgroundColor : isSelected ? EmulatorKeyboardView.keySelectedBackgroundColor : EmulatorKeyboardView.keyNormalBackgroundColor
|
||||
layer.borderColor = (isHighlighted ? EmulatorKeyboardView.keyPressedBorderColor : isSelected ? EmulatorKeyboardView.keySelectedBorderColor : EmulatorKeyboardView.keyNormalBorderColor).cgColor
|
||||
titleLabel?.textColor = isHighlighted ? EmulatorKeyboardView.keyPressedTextColor : isSelected ? EmulatorKeyboardView.keySelectedTextColor : EmulatorKeyboardView.keyNormalTextColor
|
||||
titleLabel?.tintColor = titleLabel?.textColor
|
||||
}
|
||||
|
||||
override open var isHighlighted: Bool {
|
||||
didSet {
|
||||
updateColors()
|
||||
}
|
||||
}
|
||||
|
||||
override open var isSelected: Bool {
|
||||
didSet {
|
||||
updateColors()
|
||||
}
|
||||
}
|
||||
|
||||
required init(key: KeyCoded) {
|
||||
self.key = key
|
||||
super.init(frame: .zero)
|
||||
updateColors()
|
||||
}
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
@objc protocol EmulatorKeyboardKeyPressedDelegate: AnyObject {
|
||||
func keyDown(_ key: KeyCoded)
|
||||
func keyUp(_ key: KeyCoded)
|
||||
}
|
||||
|
||||
@objc protocol EmulatorKeyboardModifierPressedDelegate: AnyObject {
|
||||
func modifierPressedWithKey(_ key: KeyCoded, enable: Bool)
|
||||
func isModifierEnabled(key: KeyCoded) -> Bool
|
||||
}
|
||||
|
||||
protocol EmulatorKeyboardViewDelegate: AnyObject {
|
||||
func toggleAlternateKeys()
|
||||
func refreshModifierStates()
|
||||
func updateTransparency(toAlpha alpha: Float)
|
||||
}
|
||||
|
||||
class EmulatorKeyboardView: UIView {
|
||||
|
||||
static var keyboardBackgroundColor = UIColor.systemGray6.withAlphaComponent(0.5)
|
||||
static var keyboardCornerRadius = 6.0
|
||||
static var keyboardDragColor = UIColor.systemGray
|
||||
|
||||
static var keyCornerRadius = 6.0
|
||||
static var keyBorderWidth = 1.0
|
||||
|
||||
static var rowSpacing = 12.0
|
||||
static var keySpacing = 8.0
|
||||
|
||||
static var keyNormalFont = UIFont.systemFont(ofSize: 12)
|
||||
static var keyPressedFont = UIFont.boldSystemFont(ofSize: 24)
|
||||
|
||||
static var keyNormalBackgroundColor = UIColor.systemGray4.withAlphaComponent(0.5)
|
||||
static var keyNormalBorderColor = keyNormalBackgroundColor
|
||||
static var keyNormalTextColor = UIColor.label
|
||||
|
||||
static var keyPressedBackgroundColor = UIColor.systemGray2
|
||||
static var keyPressedBorderColor = keyPressedBackgroundColor
|
||||
static var keyPressedTextColor = UIColor.label
|
||||
|
||||
static var keySelectedBackgroundColor = UIColor.systemGray2.withAlphaComponent(0.8)
|
||||
static var keySelectedBorderColor = keySelectedBackgroundColor
|
||||
static var keySelectedTextColor = UIColor.label
|
||||
|
||||
var viewModel = EmulatorKeyboardViewModel(keys: [[KeyCoded]]()) {
|
||||
didSet {
|
||||
setupWithModel(viewModel)
|
||||
}
|
||||
}
|
||||
var modifierButtons = Set<KeyboardButton>()
|
||||
|
||||
weak var delegate: EmulatorKeyboardViewDelegate?
|
||||
|
||||
private lazy var keyRowsStackView: UIStackView = {
|
||||
let stackView = UIStackView()
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
stackView.distribution = .equalCentering
|
||||
stackView.spacing = Self.rowSpacing
|
||||
return stackView
|
||||
}()
|
||||
|
||||
private lazy var alternateKeyRowsStackView: UIStackView = {
|
||||
let stackView = UIStackView()
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
stackView.distribution = .equalCentering
|
||||
stackView.spacing = Self.rowSpacing
|
||||
stackView.isHidden = true
|
||||
return stackView
|
||||
}()
|
||||
|
||||
let dragMeView: UIView = {
|
||||
let view = UIView(frame: .zero)
|
||||
view.backgroundColor = EmulatorKeyboardView.keyboardDragColor
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.widthAnchor.constraint(equalToConstant: 80).isActive = true
|
||||
view.heightAnchor.constraint(equalToConstant: 2).isActive = true
|
||||
let outerView = UIView(frame: .zero)
|
||||
outerView.backgroundColor = .clear
|
||||
outerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
outerView.addSubview(view)
|
||||
view.centerXAnchor.constraint(equalTo: outerView.centerXAnchor).isActive = true
|
||||
view.centerYAnchor.constraint(equalTo: outerView.centerYAnchor).isActive = true
|
||||
outerView.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
outerView.widthAnchor.constraint(equalToConstant: 100).isActive = true
|
||||
return outerView
|
||||
}()
|
||||
|
||||
private var pressedKeyViews = [UIControl: UIView]()
|
||||
|
||||
convenience init() {
|
||||
self.init(frame: CGRect.zero)
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
backgroundColor = Self.keyboardBackgroundColor
|
||||
layer.cornerRadius = Self.keyboardCornerRadius
|
||||
layoutMargins = UIEdgeInsets(top: 16, left: 4, bottom: 16, right: 4)
|
||||
insetsLayoutMarginsFromSafeArea = false
|
||||
addSubview(keyRowsStackView)
|
||||
keyRowsStackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
|
||||
keyRowsStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 4.0).isActive = true
|
||||
keyRowsStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: -4.0).isActive = true
|
||||
addSubview(alternateKeyRowsStackView)
|
||||
alternateKeyRowsStackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
|
||||
alternateKeyRowsStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 4.0).isActive = true
|
||||
alternateKeyRowsStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: -4.0).isActive = true
|
||||
addSubview(dragMeView)
|
||||
dragMeView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
|
||||
dragMeView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
|
||||
@objc private func keyPressed(_ sender: KeyboardButton) {
|
||||
if sender.key.keyCode == 9000 { // hack for now
|
||||
return
|
||||
}
|
||||
if !sender.key.isModifier {
|
||||
// make a "stand-in" for our key, and scale up key
|
||||
let view = UIView()
|
||||
view.backgroundColor = EmulatorKeyboardView.keyPressedBackgroundColor
|
||||
view.layer.cornerRadius = EmulatorKeyboardView.keyCornerRadius
|
||||
view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
view.frame = sender.convert(sender.bounds, to: self)
|
||||
addSubview(view)
|
||||
|
||||
var tx = 0.0
|
||||
let ty = sender.bounds.height * -1.20
|
||||
|
||||
if let window = self.window {
|
||||
let rect = sender.convert(sender.bounds, to:window)
|
||||
|
||||
if rect.maxX > window.bounds.width * 0.9 {
|
||||
tx = sender.bounds.width * -0.5
|
||||
}
|
||||
if rect.minX < window.bounds.width * 0.1 {
|
||||
tx = sender.bounds.width * 0.5
|
||||
}
|
||||
}
|
||||
|
||||
sender.superview!.bringSubviewToFront(sender)
|
||||
sender.transform = CGAffineTransform(translationX:tx, y:ty).scaledBy(x:2, y:2)
|
||||
|
||||
pressedKeyViews[sender] = view
|
||||
}
|
||||
viewModel.keyPressed(sender.key)
|
||||
}
|
||||
|
||||
@objc private func keyCancelled(_ sender: KeyboardButton) {
|
||||
sender.transform = .identity
|
||||
if let view = pressedKeyViews[sender] {
|
||||
view.removeFromSuperview()
|
||||
pressedKeyViews.removeValue(forKey: sender)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func keyReleased(_ sender: KeyboardButton) {
|
||||
sender.transform = .identity
|
||||
if sender.key.keyCode == 9000 {
|
||||
delegate?.toggleAlternateKeys()
|
||||
return
|
||||
}
|
||||
if let view = pressedKeyViews[sender] {
|
||||
view.removeFromSuperview()
|
||||
pressedKeyViews.removeValue(forKey: sender)
|
||||
}
|
||||
sender.isSelected = viewModel.modifierKeyToggleStateForKey(sender.key)
|
||||
viewModel.keyReleased(sender.key)
|
||||
self.delegate?.refreshModifierStates()
|
||||
}
|
||||
|
||||
func setupWithModel(_ model: EmulatorKeyboardViewModel) {
|
||||
for row in model.keys {
|
||||
let keysInRow = createKeyRow(keys: row)
|
||||
keyRowsStackView.addArrangedSubview(keysInRow)
|
||||
}
|
||||
if let altKeys = model.alternateKeys {
|
||||
for row in altKeys {
|
||||
let keysInRow = createKeyRow(keys: row)
|
||||
alternateKeyRowsStackView.addArrangedSubview(keysInRow)
|
||||
}
|
||||
}
|
||||
if !model.isDraggable {
|
||||
dragMeView.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
func toggleKeysStackView() {
|
||||
if viewModel.alternateKeys != nil {
|
||||
keyRowsStackView.isHidden.toggle()
|
||||
alternateKeyRowsStackView.isHidden.toggle()
|
||||
refreshModifierStates()
|
||||
}
|
||||
}
|
||||
|
||||
func refreshModifierStates() {
|
||||
modifierButtons.forEach{ button in
|
||||
button.isSelected = viewModel.modifierKeyToggleStateForKey(button.key)
|
||||
}
|
||||
}
|
||||
|
||||
private func createKey(_ keyCoded: KeyCoded) -> UIButton {
|
||||
let key = KeyboardButton(key: keyCoded)
|
||||
if let imageName = keyCoded.keyImageName {
|
||||
key.tintColor = EmulatorKeyboardView.keyNormalTextColor
|
||||
key.setImage(UIImage(systemName: imageName), for: .normal)
|
||||
if let highlightedImageName = keyCoded.keyImageNameHighlighted {
|
||||
key.setImage(UIImage(systemName: highlightedImageName), for: .highlighted)
|
||||
key.setImage(UIImage(systemName: highlightedImageName), for: .selected)
|
||||
}
|
||||
} else {
|
||||
key.setTitle(keyCoded.keyLabel, for: .normal)
|
||||
key.titleLabel?.font = EmulatorKeyboardView.keyNormalFont
|
||||
key.setTitleColor(EmulatorKeyboardView.keyNormalTextColor, for: .normal)
|
||||
key.setTitleColor(EmulatorKeyboardView.keySelectedTextColor, for: .selected)
|
||||
key.setTitleColor(EmulatorKeyboardView.keyPressedTextColor, for: .highlighted)
|
||||
}
|
||||
|
||||
key.translatesAutoresizingMaskIntoConstraints = false
|
||||
key.widthAnchor.constraint(equalToConstant: (25 * CGFloat(keyCoded.keySize.rawValue))).isActive = true
|
||||
key.heightAnchor.constraint(equalToConstant: 35).isActive = true
|
||||
key.backgroundColor = EmulatorKeyboardView.keyNormalBackgroundColor
|
||||
key.layer.borderWidth = EmulatorKeyboardView.keyBorderWidth
|
||||
key.layer.borderColor = EmulatorKeyboardView.keyNormalBorderColor.cgColor
|
||||
key.layer.cornerRadius = EmulatorKeyboardView.keyCornerRadius
|
||||
key.addTarget(self, action: #selector(keyPressed(_:)), for: .touchDown)
|
||||
key.addTarget(self, action: #selector(keyReleased(_:)), for: .touchUpInside)
|
||||
key.addTarget(self, action: #selector(keyReleased(_:)), for: .touchUpOutside)
|
||||
key.addTarget(self, action: #selector(keyCancelled(_:)), for: .touchCancel)
|
||||
if keyCoded.isModifier {
|
||||
modifierButtons.update(with: key)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
private func createKeyRow(keys: [KeyCoded]) -> UIStackView {
|
||||
let subviews: [UIView] = keys.enumerated().map { index, keyCoded -> UIView in
|
||||
if keyCoded is SpacerKey {
|
||||
let spacer = UIView()
|
||||
spacer.widthAnchor.constraint(equalToConstant: 25.0 * CGFloat(keyCoded.keySize.rawValue)).isActive = true
|
||||
spacer.heightAnchor.constraint(equalToConstant: 25.0).isActive = true
|
||||
return spacer
|
||||
} else if let sliderKey = keyCoded as? SliderKey {
|
||||
sliderKey.keyboardView = self
|
||||
return sliderKey.createView()
|
||||
}
|
||||
return createKey(keyCoded)
|
||||
}
|
||||
let stack = UIStackView(arrangedSubviews: subviews)
|
||||
stack.axis = .horizontal
|
||||
stack.distribution = .fill
|
||||
stack.spacing = 8
|
||||
return stack
|
||||
}
|
||||
}
|
||||
|
||||
@objc enum KeySize: Int {
|
||||
case standard = 1, wide, wider
|
||||
}
|
||||
|
||||
// represents a key that has an underlying code that gets sent to the emulator
|
||||
@objc protocol KeyCoded: AnyObject {
|
||||
var keyLabel: String { get }
|
||||
var keyImageName: String? { get }
|
||||
var keyImageNameHighlighted: String? { get }
|
||||
var keyCode: Int { get }
|
||||
var keySize: KeySize { get }
|
||||
var isModifier: Bool { get }
|
||||
}
|
||||
|
||||
protocol KeyRowsDataSource {
|
||||
func keyForPositionAt(_ position: KeyPosition) -> KeyCoded?
|
||||
}
|
||||
|
||||
@objc class EmulatorKeyboardKey: NSObject, KeyCoded {
|
||||
let keyLabel: String
|
||||
var keyImageName: String?
|
||||
var keyImageNameHighlighted: String?
|
||||
let keyCode: Int
|
||||
let keySize: KeySize
|
||||
let isModifier: Bool
|
||||
|
||||
override var description: String {
|
||||
return String(format: "\(keyLabel) (%02X)", keyCode)
|
||||
}
|
||||
init(label: String, code: Int, keySize: KeySize = .standard, isModifier: Bool = false, imageName: String? = nil, imageNameHighlighted: String? = nil) {
|
||||
self.keyLabel = label
|
||||
self.keyCode = code
|
||||
self.keySize = keySize
|
||||
self.isModifier = isModifier
|
||||
self.keyImageName = imageName
|
||||
self.keyImageNameHighlighted = imageNameHighlighted
|
||||
}
|
||||
}
|
||||
|
||||
class SpacerKey: KeyCoded {
|
||||
let keyLabel = ""
|
||||
let keyCode = 0
|
||||
let keySize: KeySize
|
||||
let isModifier = false
|
||||
let keyImageName: String? = nil
|
||||
let keyImageNameHighlighted: String? = nil
|
||||
init(keySize: KeySize = .standard) {
|
||||
self.keySize = keySize
|
||||
}
|
||||
}
|
||||
|
||||
class SliderKey: KeyCoded {
|
||||
let keyLabel = ""
|
||||
let keyCode = 0
|
||||
let keySize: KeySize
|
||||
let isModifier = false
|
||||
let keyImageName: String? = nil
|
||||
let keyImageNameHighlighted: String? = nil
|
||||
weak var keyboardView: EmulatorKeyboardView?
|
||||
|
||||
init(keySize: KeySize = .standard) {
|
||||
self.keySize = keySize
|
||||
}
|
||||
|
||||
func createView() -> UIView {
|
||||
let slider = UISlider(frame: .zero)
|
||||
slider.minimumValue = 0.1
|
||||
slider.maximumValue = 1.0
|
||||
slider.addTarget(self, action: #selector(adjustKeyboardAlpha(_:)), for: .valueChanged)
|
||||
slider.value = 1.0
|
||||
let size = CGSize(width:EmulatorKeyboardView.keyNormalFont.pointSize, height:EmulatorKeyboardView.keyNormalFont.pointSize)
|
||||
slider.setThumbImage(UIImage.dot(size:size, color:EmulatorKeyboardView.keyNormalTextColor), for: .normal)
|
||||
return slider
|
||||
}
|
||||
@objc func adjustKeyboardAlpha(_ sender: UISlider) {
|
||||
keyboardView?.delegate?.updateTransparency(toAlpha: sender.value)
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyPosition {
|
||||
let row: Int
|
||||
let column: Int
|
||||
}
|
||||
|
||||
@objc class EmulatorKeyboardViewModel: NSObject, KeyRowsDataSource {
|
||||
var keys = [[KeyCoded]]()
|
||||
var alternateKeys: [[KeyCoded]]?
|
||||
var modifiers: [Int16: KeyCoded]?
|
||||
|
||||
var isDraggable = true
|
||||
|
||||
@objc weak var delegate: EmulatorKeyboardKeyPressedDelegate?
|
||||
@objc weak var modifierDelegate: EmulatorKeyboardModifierPressedDelegate?
|
||||
|
||||
init(keys: [[KeyCoded]], alternateKeys: [[KeyCoded]]? = nil) {
|
||||
self.keys = keys
|
||||
self.alternateKeys = alternateKeys
|
||||
}
|
||||
|
||||
func createView() -> EmulatorKeyboardView {
|
||||
let view = EmulatorKeyboardView()
|
||||
view.viewModel = self
|
||||
return view
|
||||
}
|
||||
|
||||
func keyForPositionAt(_ position: KeyPosition) -> KeyCoded? {
|
||||
guard position.row < keys.count else {
|
||||
return nil
|
||||
}
|
||||
let row = keys[position.row]
|
||||
guard position.column < row.count else {
|
||||
return nil
|
||||
}
|
||||
return row[position.column]
|
||||
}
|
||||
|
||||
func modifierKeyToggleStateForKey(_ key: KeyCoded) -> Bool {
|
||||
return key.isModifier && (modifierDelegate?.isModifierEnabled(key: key) ?? false)
|
||||
}
|
||||
|
||||
func keyPressed(_ key: KeyCoded) {
|
||||
if key.isModifier {
|
||||
let isPressed = modifierDelegate?.isModifierEnabled(key: key) ?? false
|
||||
modifierDelegate?.modifierPressedWithKey(key, enable: !isPressed)
|
||||
return
|
||||
}
|
||||
delegate?.keyDown(key)
|
||||
}
|
||||
|
||||
func keyReleased(_ key: KeyCoded) {
|
||||
if key.isModifier {
|
||||
return
|
||||
}
|
||||
delegate?.keyUp(key)
|
||||
}
|
||||
|
||||
// KeyCoded can support a shifted key label
|
||||
// view can update with shifted key labels?
|
||||
// cluster can support alternate keys and view can swap them out?
|
||||
}
|
||||
|
||||
@objc class EmulatorKeyboardController: UIViewController {
|
||||
|
||||
class EmulatorKeyboardPassthroughView: UIView {
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let hitView = super.hitTest(point, with: event)
|
||||
if hitView == self {
|
||||
return nil
|
||||
}
|
||||
return hitView
|
||||
}
|
||||
}
|
||||
|
||||
@objc let leftKeyboardModel: EmulatorKeyboardViewModel
|
||||
@objc let rightKeyboardModel: EmulatorKeyboardViewModel
|
||||
|
||||
@objc lazy var leftKeyboardView: EmulatorKeyboardView = {
|
||||
let view = leftKeyboardModel.createView()
|
||||
view.delegate = self
|
||||
return view
|
||||
}()
|
||||
@objc lazy var rightKeyboardView: EmulatorKeyboardView = {
|
||||
let view = rightKeyboardModel.createView()
|
||||
view.delegate = self
|
||||
return view
|
||||
}()
|
||||
var keyboardConstraints = [NSLayoutConstraint]()
|
||||
|
||||
init(leftKeyboardModel: EmulatorKeyboardViewModel, rightKeyboardModel: EmulatorKeyboardViewModel) {
|
||||
self.leftKeyboardModel = leftKeyboardModel
|
||||
self.rightKeyboardModel = rightKeyboardModel
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
view = EmulatorKeyboardPassthroughView()
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupView()
|
||||
|
||||
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(draggedView(_:)))
|
||||
leftKeyboardView.dragMeView.isUserInteractionEnabled = true
|
||||
leftKeyboardView.dragMeView.addGestureRecognizer(panGesture)
|
||||
let panGestureRightKeyboard = UIPanGestureRecognizer(target: self, action: #selector(draggedView(_:)))
|
||||
rightKeyboardView.dragMeView.isUserInteractionEnabled = true
|
||||
rightKeyboardView.dragMeView.addGestureRecognizer(panGestureRightKeyboard)
|
||||
}
|
||||
|
||||
func setupView() {
|
||||
NSLayoutConstraint.deactivate(keyboardConstraints)
|
||||
keyboardConstraints.removeAll()
|
||||
leftKeyboardView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(leftKeyboardView)
|
||||
leftKeyboardView.heightAnchor.constraint(equalToConstant: 270).isActive = true
|
||||
leftKeyboardView.widthAnchor.constraint(equalToConstant: 180).isActive = true
|
||||
keyboardConstraints.append(contentsOf: [
|
||||
leftKeyboardView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
|
||||
leftKeyboardView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
|
||||
])
|
||||
rightKeyboardView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(rightKeyboardView)
|
||||
keyboardConstraints.append(contentsOf: [
|
||||
rightKeyboardView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
|
||||
rightKeyboardView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
|
||||
])
|
||||
rightKeyboardView.heightAnchor.constraint(equalToConstant: 270).isActive = true
|
||||
rightKeyboardView.widthAnchor.constraint(equalToConstant: 180).isActive = true
|
||||
NSLayoutConstraint.activate(keyboardConstraints)
|
||||
}
|
||||
|
||||
func setupViewFrames() {
|
||||
// initial placement on the bottom corners
|
||||
// since we don't know the frame of this view yet until layout time,
|
||||
// assume it's taking the full screen
|
||||
let screenFrame = UIScreen.main.bounds
|
||||
let keyboardHeight: CGFloat = 250.0
|
||||
let keyboardWidth: CGFloat = 180.0
|
||||
let bottomLeftFrame = CGRect(
|
||||
x: 0,
|
||||
y: screenFrame.size.height - 40 - keyboardHeight - 20,
|
||||
width: keyboardWidth, height: keyboardHeight)
|
||||
let bottomRightFrame = CGRect(
|
||||
x: screenFrame.size.width - 20 - keyboardWidth,
|
||||
y:screenFrame.size.height - 40 - keyboardHeight - 20,
|
||||
width: keyboardWidth, height: keyboardHeight
|
||||
)
|
||||
view.addSubview(leftKeyboardView)
|
||||
view.addSubview(rightKeyboardView)
|
||||
leftKeyboardView.frame = bottomLeftFrame
|
||||
rightKeyboardView.frame = bottomRightFrame
|
||||
}
|
||||
|
||||
func setupKeyModels() {
|
||||
leftKeyboardView.setupWithModel(leftKeyboardModel)
|
||||
rightKeyboardView.setupWithModel(rightKeyboardModel)
|
||||
}
|
||||
|
||||
@objc func draggedView(_ sender:UIPanGestureRecognizer){
|
||||
guard let keyboardView = sender.view?.superview else {
|
||||
return
|
||||
}
|
||||
let translation = sender.translation(in: self.view)
|
||||
keyboardView.center = CGPoint(x: keyboardView.center.x + translation.x, y: keyboardView.center.y + translation.y)
|
||||
sender.setTranslation(CGPoint.zero, in: self.view)
|
||||
}
|
||||
}
|
||||
|
||||
extension EmulatorKeyboardController: EmulatorKeyboardViewDelegate {
|
||||
func toggleAlternateKeys() {
|
||||
for keyboard in [leftKeyboardView, rightKeyboardView] {
|
||||
keyboard.toggleKeysStackView()
|
||||
}
|
||||
}
|
||||
func refreshModifierStates() {
|
||||
for keyboard in [leftKeyboardView, rightKeyboardView] {
|
||||
keyboard.refreshModifierStates()
|
||||
}
|
||||
}
|
||||
func updateTransparency(toAlpha alpha: Float) {
|
||||
for keyboard in [leftKeyboardView, rightKeyboardView] {
|
||||
keyboard.alpha = CGFloat(alpha)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension UIImage {
|
||||
static func dot(size:CGSize, color:UIColor) -> UIImage {
|
||||
return UIGraphicsImageRenderer(size: size).image { context in
|
||||
context.cgContext.setFillColor(color.cgColor)
|
||||
context.cgContext.fillEllipse(in: CGRect(origin:.zero, size:size))
|
||||
}
|
||||
}
|
||||
}
|
175
pkg/apple/EmulatorTouchMouse.swift
Normal file
175
pkg/apple/EmulatorTouchMouse.swift
Normal file
@ -0,0 +1,175 @@
|
||||
//
|
||||
// EmulatorTouchMouse.swift
|
||||
// RetroArchiOS
|
||||
//
|
||||
// Created by Yoshi Sugawara on 12/27/21.
|
||||
// Copyright © 2021 RetroArch. All rights reserved.
|
||||
//
|
||||
|
||||
/**
|
||||
Touch mouse behavior:
|
||||
- Mouse movement: Pan finger around screen
|
||||
- Left click: Tap with one finger
|
||||
- Right click: Tap with two fingers (or hold with one finger and tap with another)
|
||||
- Click-and-drag: Double tap and hold for 1 second, then pan finger around screen to drag mouse
|
||||
|
||||
Code adapted from iDOS/dospad: https://github.com/litchie/dospad
|
||||
*/
|
||||
|
||||
import Combine
|
||||
import UIKit
|
||||
|
||||
@objc protocol EmulatorTouchMouseHandlerDelegate: AnyObject {
|
||||
func handleMouseClick(isLeftClick: Bool, isPressed: Bool)
|
||||
func handleMouseMove(x: CGFloat, y: CGFloat)
|
||||
}
|
||||
|
||||
@objcMembers public class EmulatorTouchMouseHandler: NSObject {
|
||||
enum MouseHoldState {
|
||||
case notHeld, wait, held
|
||||
}
|
||||
|
||||
struct MouseClick {
|
||||
var isRightClick = false
|
||||
var isPressed = false
|
||||
}
|
||||
|
||||
struct TouchInfo {
|
||||
let touch: UITouch
|
||||
let origin: CGPoint
|
||||
let holdState: MouseHoldState
|
||||
}
|
||||
|
||||
let view: UIView
|
||||
weak var delegate: EmulatorTouchMouseHandlerDelegate?
|
||||
|
||||
private let positionChangeThreshold: CGFloat = 20.0
|
||||
private let mouseHoldInterval: TimeInterval = 1.0
|
||||
|
||||
private var pendingMouseEvents = [MouseClick]()
|
||||
private var mouseEventPublisher: AnyPublisher<MouseClick, Never> {
|
||||
mouseEventSubject.eraseToAnyPublisher()
|
||||
}
|
||||
private let mouseEventSubject = PassthroughSubject<MouseClick, Never>()
|
||||
private var subscription: AnyCancellable?
|
||||
|
||||
private var primaryTouch: TouchInfo?
|
||||
private var secondaryTouch: TouchInfo?
|
||||
|
||||
private let mediumHaptic = UIImpactFeedbackGenerator(style: .medium)
|
||||
|
||||
public init(view: UIView) {
|
||||
self.view = view
|
||||
super.init()
|
||||
setup()
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
subscription = mouseEventPublisher
|
||||
.sink(receiveValue: {[weak self] value in
|
||||
self?.pendingMouseEvents.append(value)
|
||||
self?.processMouseEvents()
|
||||
})
|
||||
}
|
||||
|
||||
private func processMouseEvents() {
|
||||
guard let event = pendingMouseEvents.first else {
|
||||
return
|
||||
}
|
||||
delegate?.handleMouseClick(isLeftClick: !event.isRightClick, isPressed: event.isPressed)
|
||||
if event.isPressed {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||
self?.mouseEventSubject.send(MouseClick(isRightClick: event.isRightClick, isPressed: false))
|
||||
}
|
||||
}
|
||||
pendingMouseEvents.removeFirst()
|
||||
processMouseEvents()
|
||||
}
|
||||
|
||||
@objc private func beginHold() {
|
||||
guard let primaryTouch = primaryTouch, primaryTouch.holdState == .wait else {
|
||||
return
|
||||
}
|
||||
self.primaryTouch = TouchInfo(touch: primaryTouch.touch, origin: primaryTouch.origin, holdState: .held)
|
||||
mediumHaptic.impactOccurred()
|
||||
delegate?.handleMouseClick(isLeftClick: true, isPressed: true)
|
||||
}
|
||||
|
||||
private func endHold() {
|
||||
guard let primaryTouch = primaryTouch else { return }
|
||||
if primaryTouch.holdState == .notHeld {
|
||||
return
|
||||
}
|
||||
if primaryTouch.holdState == .wait {
|
||||
Thread.cancelPreviousPerformRequests(withTarget: self, selector: #selector(beginHold), object: self)
|
||||
} else {
|
||||
delegate?.handleMouseClick(isLeftClick: true, isPressed: false)
|
||||
}
|
||||
self.primaryTouch = TouchInfo(touch: primaryTouch.touch, origin: primaryTouch.origin, holdState: .notHeld)
|
||||
}
|
||||
|
||||
public func touchesBegan(touches: Set<UITouch>) {
|
||||
guard let touch = touches.first else {
|
||||
return
|
||||
}
|
||||
if primaryTouch == nil {
|
||||
primaryTouch = TouchInfo(touch: touch, origin: touch.location(in: view), holdState: .wait)
|
||||
if touch.tapCount == 2 {
|
||||
self.perform(#selector(beginHold), with: nil, afterDelay: mouseHoldInterval)
|
||||
}
|
||||
} else if secondaryTouch == nil {
|
||||
secondaryTouch = TouchInfo(touch: touch, origin: touch.location(in: view), holdState: .notHeld)
|
||||
}
|
||||
}
|
||||
|
||||
public func touchesEnded(touches: Set<UITouch>) {
|
||||
for touch in touches {
|
||||
if touch == primaryTouch?.touch {
|
||||
if touch.tapCount > 0 {
|
||||
for _ in 1...touch.tapCount {
|
||||
mouseEventSubject.send(MouseClick(isRightClick: false, isPressed: true))
|
||||
}
|
||||
}
|
||||
endHold()
|
||||
primaryTouch = nil
|
||||
secondaryTouch = nil
|
||||
} else if touch == secondaryTouch?.touch {
|
||||
if touch.tapCount > 0 {
|
||||
mouseEventSubject.send(MouseClick(isRightClick: true, isPressed: true))
|
||||
endHold()
|
||||
}
|
||||
secondaryTouch = nil
|
||||
}
|
||||
}
|
||||
delegate?.handleMouseMove(x: 0, y: 0)
|
||||
}
|
||||
|
||||
public func touchesMoved(touches: Set<UITouch>) {
|
||||
for touch in touches {
|
||||
if touch == primaryTouch?.touch {
|
||||
let a = touch.previousLocation(in: view)
|
||||
let b = touch.location(in: view)
|
||||
if primaryTouch?.holdState == .wait && (distanceBetween(pointA: a, pointB: b) > positionChangeThreshold) {
|
||||
endHold()
|
||||
}
|
||||
delegate?.handleMouseMove(x: b.x-a.x, y: b.y-a.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func touchesCancelled(touches: Set<UITouch>) {
|
||||
for touch in touches {
|
||||
if touch == primaryTouch?.touch {
|
||||
endHold()
|
||||
}
|
||||
}
|
||||
primaryTouch = nil
|
||||
secondaryTouch = nil
|
||||
}
|
||||
|
||||
func distanceBetween(pointA: CGPoint, pointB: CGPoint) -> CGFloat {
|
||||
let dx = pointA.x - pointB.x
|
||||
let dy = pointA.y - pointB.y
|
||||
return sqrt(dx*dx*dy*dy)
|
||||
}
|
||||
}
|
17
pkg/apple/JITSupport.h
Normal file
17
pkg/apple/JITSupport.h
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// JITSupport.h
|
||||
// RetroArchiOS
|
||||
//
|
||||
// Created by Yoshi Sugawara on 9/25/21.
|
||||
// Copyright © 2021 RetroArch. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef JITSupport_h
|
||||
#define JITSupport_h
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
static bool jb_has_debugger_attached(void);
|
||||
bool jb_enable_ptrace_hack(void);
|
||||
|
||||
#endif /* JITSupport_h */
|
76
pkg/apple/JITSupport.m
Normal file
76
pkg/apple/JITSupport.m
Normal file
@ -0,0 +1,76 @@
|
||||
//
|
||||
// JITSupport.m
|
||||
// RetroArchiOS
|
||||
//
|
||||
// Created by Yoshi Sugawara on 9/25/21.
|
||||
// Copyright © 2021 RetroArch. All rights reserved.
|
||||
//
|
||||
// Copied from UTMApp, original author: osy
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/getsect.h>
|
||||
#include <pthread.h>
|
||||
|
||||
extern int csops(pid_t pid, unsigned int ops, void * useraddr, size_t usersize);
|
||||
extern boolean_t exc_server(mach_msg_header_t *, mach_msg_header_t *);
|
||||
extern int ptrace(int request, pid_t pid, caddr_t addr, int data);
|
||||
|
||||
#define CS_OPS_STATUS 0 /* return status */
|
||||
#define CS_KILL 0x00000200 /* kill process if it becomes invalid */
|
||||
#define CS_DEBUGGED 0x10000000 /* process is currently or has previously been debugged and allowed to run with invalid pages */
|
||||
#define PT_TRACE_ME 0 /* child declares it's being traced */
|
||||
#define PT_SIGEXC 12 /* signals as exceptions for current_proc */
|
||||
|
||||
static void *exception_handler(void *argument) {
|
||||
mach_port_t port = *(mach_port_t *)argument;
|
||||
mach_msg_server(exc_server, 2048, port, 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool jb_has_debugger_attached(void) {
|
||||
int flags;
|
||||
return !csops(getpid(), CS_OPS_STATUS, &flags, sizeof(flags)) && flags & CS_DEBUGGED;
|
||||
}
|
||||
|
||||
bool jb_enable_ptrace_hack(void) {
|
||||
bool debugged = jb_has_debugger_attached();
|
||||
|
||||
// Thanks to this comment: https://news.ycombinator.com/item?id=18431524
|
||||
// We use this hack to allow mmap with PROT_EXEC (which usually requires the
|
||||
// dynamic-codesigning entitlement) by tricking the process into thinking
|
||||
// that Xcode is debugging it. We abuse the fact that JIT is needed to
|
||||
// debug the process.
|
||||
if (ptrace(PT_TRACE_ME, 0, NULL, 0) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ptracing ourselves confuses the kernel and will cause bad things to
|
||||
// happen to the system (hangs…) if an exception or signal occurs. Setup
|
||||
// some "safety nets" so we can cause the process to exit in a somewhat sane
|
||||
// state. We only need to do this if the debugger isn't attached. (It'll do
|
||||
// this itself, and if we do it we'll interfere with its normal operation
|
||||
// anyways.)
|
||||
if (!debugged) {
|
||||
// First, ensure that signals are delivered as Mach software exceptions…
|
||||
ptrace(PT_SIGEXC, 0, NULL, 0);
|
||||
|
||||
// …then ensure that this exception goes through our exception handler.
|
||||
// I think it's OK to just watch for EXC_SOFTWARE because the other
|
||||
// exceptions (e.g. EXC_BAD_ACCESS, EXC_BAD_INSTRUCTION, and friends)
|
||||
// will end up being delivered as signals anyways, and we can get them
|
||||
// once they're resent as a software exception.
|
||||
mach_port_t port = MACH_PORT_NULL;
|
||||
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
|
||||
mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
|
||||
task_set_exception_ports(mach_task_self(), EXC_MASK_SOFTWARE, port, EXCEPTION_DEFAULT, THREAD_STATE_NONE);
|
||||
pthread_t thread;
|
||||
pthread_create(&thread, NULL, exception_handler, (void *)&port);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
16
pkg/apple/RetroArch-Bridging-Header.h
Normal file
16
pkg/apple/RetroArch-Bridging-Header.h
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#ifndef HAVE_COCOATOUCH
|
||||
#define HAVE_COCOATOUCH
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_IOS_CUSTOMKEYBOARD
|
||||
#define HAVE_IOS_CUSTOMKEYBOARD
|
||||
#endif
|
||||
|
||||
#include "libretro-common/include/libretro.h"
|
||||
#import "../ui/drivers/cocoa/cocoa_common.h"
|
||||
#include "../../input/drivers_keyboard/keyboard_event_apple.h"
|
||||
#include "../../input/input_keymaps.h"
|
@ -80,6 +80,7 @@
|
||||
92CC05C321FE3C6D00FF79F0 /* WebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC05C121FE3C6D00FF79F0 /* WebServer.m */; };
|
||||
92CC05C521FEDC9F00FF79F0 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92CC05C421FEDC9F00FF79F0 /* CFNetwork.framework */; };
|
||||
92CC05C721FEDD0B00FF79F0 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92CC05C621FEDD0B00FF79F0 /* MobileCoreServices.framework */; };
|
||||
92DAF342277EC52900FE2A9E /* JITSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 92DAF341277EC52900FE2A9E /* JITSupport.m */; };
|
||||
92E5DCD4231A5786006491BF /* modules in Resources */ = {isa = PBXBuildFile; fileRef = 92E5DCD3231A5786006491BF /* modules */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@ -332,6 +333,8 @@
|
||||
92CC05C121FE3C6D00FF79F0 /* WebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebServer.m; sourceTree = "<group>"; };
|
||||
92CC05C421FEDC9F00FF79F0 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
|
||||
92CC05C621FEDD0B00FF79F0 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
|
||||
92DAF340277EC52900FE2A9E /* JITSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JITSupport.h; sourceTree = "<group>"; };
|
||||
92DAF341277EC52900FE2A9E /* JITSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JITSupport.m; sourceTree = "<group>"; };
|
||||
92E5DCD3231A5786006491BF /* modules */ = {isa = PBXFileReference; lastKnownFileType = folder; path = modules; sourceTree = "<group>"; };
|
||||
96366C5416C9AC3300D64A22 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
|
||||
96366C5816C9ACF500D64A22 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
|
||||
@ -410,6 +413,8 @@
|
||||
children = (
|
||||
92B9EC9324E0536200E6CFB2 /* core */,
|
||||
92B9EAE524E0518600E6CFB2 /* libretro-common */,
|
||||
92DAF340277EC52900FE2A9E /* JITSupport.h */,
|
||||
92DAF341277EC52900FE2A9E /* JITSupport.m */,
|
||||
);
|
||||
name = Sources;
|
||||
sourceTree = "<group>";
|
||||
@ -1102,7 +1107,7 @@
|
||||
ORGANIZATIONNAME = RetroArch;
|
||||
TargetAttributes = {
|
||||
9204BE091D319EF300BD49DB = {
|
||||
DevelopmentTeam = UK699V5ZS8;
|
||||
DevelopmentTeam = R72X3BF4KE;
|
||||
DevelopmentTeamName = RetroArch;
|
||||
};
|
||||
926C77D621FD1E6500103EDE = {
|
||||
@ -1217,6 +1222,7 @@
|
||||
92CC05BA21FE3C1700FF79F0 /* GCDWebServerFileRequest.m in Sources */,
|
||||
92CC05AE21FE3C1700FF79F0 /* GCDWebServerFileResponse.m in Sources */,
|
||||
9210C2F624B3A32D00E6FE7C /* griffin_cpp.cpp in Sources */,
|
||||
92DAF342277EC52900FE2A9E /* JITSupport.m in Sources */,
|
||||
92CC05B221FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m in Sources */,
|
||||
92CC05A221FE3C1700FF79F0 /* GCDWebServerResponse.m in Sources */,
|
||||
);
|
||||
@ -1276,7 +1282,7 @@
|
||||
CODE_SIGN_RESOURCE_RULES_PATH = "$(SDKROOT)/ResourceRules.plist";
|
||||
CURRENT_PROJECT_VERSION = 1.10.0;
|
||||
DEPS_DIR = "$(SRCBASE)/deps";
|
||||
DEVELOPMENT_TEAM = UK699V5ZS8;
|
||||
DEVELOPMENT_TEAM = R72X3BF4KE;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = NO;
|
||||
GCC_PREFIX_HEADER = "";
|
||||
@ -1392,7 +1398,7 @@
|
||||
CODE_SIGN_RESOURCE_RULES_PATH = "$(SDKROOT)/ResourceRules.plist";
|
||||
CURRENT_PROJECT_VERSION = 1.10.0;
|
||||
DEPS_DIR = "$(SRCBASE)/deps";
|
||||
DEVELOPMENT_TEAM = UK699V5ZS8;
|
||||
DEVELOPMENT_TEAM = R72X3BF4KE;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = NO;
|
||||
GCC_PREFIX_HEADER = "";
|
||||
|
2079
pkg/apple/RetroArch_iOS13.xcodeproj/project.pbxproj
Normal file
2079
pkg/apple/RetroArch_iOS13.xcodeproj/project.pbxproj
Normal file
File diff suppressed because it is too large
Load Diff
7
pkg/apple/RetroArch_iOS13.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
pkg/apple/RetroArch_iOS13.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:/Users/yoshi/Code/personal/RetroArch-yoshi/pkg/apple/RetroArch_iOS13.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDESourceControlProjectFavoriteDictionaryKey</key>
|
||||
<false/>
|
||||
<key>IDESourceControlProjectIdentifier</key>
|
||||
<string>6D7FAF49-495D-480E-B0C9-8C39AC77CE3C</string>
|
||||
<key>IDESourceControlProjectName</key>
|
||||
<string>RetroArch_iOS</string>
|
||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||
<dict>
|
||||
<key>6B9F0B13E5864452B91F13C09B7ED9EB989E82AD</key>
|
||||
<string>https://github.com/libretro/retroarch-joypad-autoconfig.git</string>
|
||||
<key>76200F0D6584D865E96F58DE862E738E88B23A3C</key>
|
||||
<string>https://github.com/libretro/libretro-super.git</string>
|
||||
<key>A267D9543F572B4C32EC6E1B876E3B9BFE4DE8F6</key>
|
||||
<string>https://github.com/libretro/retroarch-assets.git</string>
|
||||
<key>C3AEE01BDA902108663DB5DB9CD7916436919463</key>
|
||||
<string>https://github.com/libretro/libretro-database.git</string>
|
||||
<key>C7C12374C7051F8843B3EFA1ACCAF2907102CCF7</key>
|
||||
<string>https://github.com/libretro/RetroArch.git</string>
|
||||
<key>EF363D58F01B3FB341FA6C851870E60E4F080E97</key>
|
||||
<string>https://github.com/libretro/common-overlays.git</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectPath</key>
|
||||
<string>apple/iOS/RetroArch_iOS.xcodeproj</string>
|
||||
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
||||
<dict>
|
||||
<key>6B9F0B13E5864452B91F13C09B7ED9EB989E82AD</key>
|
||||
<string>../../../..media/autoconfig</string>
|
||||
<key>76200F0D6584D865E96F58DE862E738E88B23A3C</key>
|
||||
<string>../../../../..</string>
|
||||
<key>A267D9543F572B4C32EC6E1B876E3B9BFE4DE8F6</key>
|
||||
<string>../../../..media/assets</string>
|
||||
<key>C3AEE01BDA902108663DB5DB9CD7916436919463</key>
|
||||
<string>../../../..media/libretrodb</string>
|
||||
<key>C7C12374C7051F8843B3EFA1ACCAF2907102CCF7</key>
|
||||
<string>../../../..</string>
|
||||
<key>EF363D58F01B3FB341FA6C851870E60E4F080E97</key>
|
||||
<string>../../../..media/overlays</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectURL</key>
|
||||
<string>https://github.com/libretro/RetroArch.git</string>
|
||||
<key>IDESourceControlProjectVersion</key>
|
||||
<integer>111</integer>
|
||||
<key>IDESourceControlProjectWCCIdentifier</key>
|
||||
<string>C7C12374C7051F8843B3EFA1ACCAF2907102CCF7</string>
|
||||
<key>IDESourceControlProjectWCConfigurations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>76200F0D6584D865E96F58DE862E738E88B23A3C</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>A267D9543F572B4C32EC6E1B876E3B9BFE4DE8F6</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>assets</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>6B9F0B13E5864452B91F13C09B7ED9EB989E82AD</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>autoconfig</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>C3AEE01BDA902108663DB5DB9CD7916436919463</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>libretrodb</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>EF363D58F01B3FB341FA6C851870E60E4F080E97</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>overlays</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>C7C12374C7051F8843B3EFA1ACCAF2907102CCF7</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>retroarch</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,30 @@
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "C7C12374C7051F8843B3EFA1ACCAF2907102CCF7",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
|
||||
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
|
||||
"76200F0D6584D865E96F58DE862E738E88B23A3C" : 9223372036854775807,
|
||||
"C7C12374C7051F8843B3EFA1ACCAF2907102CCF7" : 9223372036854775807
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "8BA6E269-73BB-4AAE-8F4A-967CCF8ACA14",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
|
||||
"76200F0D6584D865E96F58DE862E738E88B23A3C" : "",
|
||||
"C7C12374C7051F8843B3EFA1ACCAF2907102CCF7" : "retroarch\/"
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "RetroArch_iOS10",
|
||||
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
|
||||
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "pkg\/apple\/RetroArch_iOS10.xcodeproj",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/libretro\/libretro-super.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "76200F0D6584D865E96F58DE862E738E88B23A3C"
|
||||
},
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/libretro\/RetroArch.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "C7C12374C7051F8843B3EFA1ACCAF2907102CCF7"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1010"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
|
||||
BuildableName = "RetroArch.app"
|
||||
BlueprintName = "RetroArchiOS"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
|
||||
BuildableName = "RetroArch.app"
|
||||
BlueprintName = "RetroArchiOS"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
|
||||
BuildableName = "RetroArch.app"
|
||||
BlueprintName = "RetroArchiOS"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
|
||||
BuildableName = "RetroArch.app"
|
||||
BlueprintName = "RetroArchiOS"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1010"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
|
||||
BuildableName = "RetroArch.app"
|
||||
BlueprintName = "RetroArchiOS"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
|
||||
BuildableName = "RetroArch.app"
|
||||
BlueprintName = "RetroArchiOS"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
|
||||
BuildableName = "RetroArch.app"
|
||||
BlueprintName = "RetroArchiOS"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
|
||||
BuildableName = "RetroArch.app"
|
||||
BlueprintName = "RetroArchiOS"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1010"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
|
||||
BuildableName = "RetroArchTV.app"
|
||||
BlueprintName = "RetroArchTV"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
|
||||
BuildableName = "RetroArchTV.app"
|
||||
BlueprintName = "RetroArchTV"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
|
||||
BuildableName = "RetroArchTV.app"
|
||||
BlueprintName = "RetroArchTV"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
|
||||
BuildableName = "RetroArchTV.app"
|
||||
BlueprintName = "RetroArchTV"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1010"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
|
||||
BuildableName = "RetroArchTV.app"
|
||||
BlueprintName = "RetroArchTV"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
|
||||
BuildableName = "RetroArchTV.app"
|
||||
BlueprintName = "RetroArchTV"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
|
||||
BuildableName = "RetroArchTV.app"
|
||||
BlueprintName = "RetroArchTV"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
|
||||
BuildableName = "RetroArchTV.app"
|
||||
BlueprintName = "RetroArchTV"
|
||||
ReferencedContainer = "container:RetroArch_iOS13.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<string>RetroArch</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
@ -42,10 +42,18 @@
|
||||
#endif
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
@class EmulatorKeyboardController;
|
||||
|
||||
@interface CocoaView : UIViewController
|
||||
#elif TARGET_OS_TV
|
||||
@interface CocoaView : GCEventViewController
|
||||
#endif
|
||||
|
||||
#if TARGET_OS_IOS && defined(HAVE_IOS_CUSTOMKEYBOARD)
|
||||
@property(nonatomic,strong) EmulatorKeyboardController *keyboardController;
|
||||
@property(nonatomic,assign) unsigned int keyboardModifierState;
|
||||
#endif
|
||||
|
||||
+ (CocoaView*)get;
|
||||
@end
|
||||
|
||||
|
@ -26,20 +26,33 @@
|
||||
#ifdef HAVE_COCOATOUCH
|
||||
#import "../../../pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.h"
|
||||
#import "WebServer.h"
|
||||
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MIN_REQUIRED >= 130000
|
||||
#import "RetroArch-Swift.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "../../../configuration.h"
|
||||
#include "../../../retroarch.h"
|
||||
#include "../../../verbosity.h"
|
||||
|
||||
#include "../../input/drivers/cocoa_input.h"
|
||||
#include "../../input/drivers_keyboard/keyboard_event_apple.h"
|
||||
|
||||
|
||||
static CocoaView* g_instance;
|
||||
|
||||
#ifdef HAVE_COCOATOUCH
|
||||
void *glkitview_init(void);
|
||||
|
||||
@interface CocoaView()<GCDWebUploaderDelegate> {
|
||||
|
||||
@interface CocoaView()<GCDWebUploaderDelegate, UIGestureRecognizerDelegate
|
||||
#ifdef HAVE_IOS_TOUCHMOUSE
|
||||
,EmulatorTouchMouseHandlerDelegate> {
|
||||
EmulatorTouchMouseHandler *mouseHandler;
|
||||
}
|
||||
#else
|
||||
>
|
||||
#endif
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
@ -76,23 +89,12 @@ void *glkitview_init(void);
|
||||
#if defined(HAVE_COCOA)
|
||||
ui_window_cocoa_t cocoa_view;
|
||||
cocoa_view.data = (CocoaView*)self;
|
||||
#elif defined(HAVE_COCOATOUCH)
|
||||
#if defined(HAVE_COCOA_METAL)
|
||||
self.view = [UIView new];
|
||||
#else
|
||||
self.view = (BRIDGE GLKView*)glkitview_init();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(OSX)
|
||||
video_driver_display_type_set(RARCH_DISPLAY_OSX);
|
||||
video_driver_display_set(0);
|
||||
video_driver_display_userdata_set((uintptr_t)self);
|
||||
#elif TARGET_OS_IOS
|
||||
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(showNativeMenu)];
|
||||
swipe.numberOfTouchesRequired = 4;
|
||||
swipe.direction = UISwipeGestureRecognizerDirectionDown;
|
||||
[self.view addGestureRecognizer:swipe];
|
||||
#endif
|
||||
|
||||
return self;
|
||||
@ -152,6 +154,35 @@ void *glkitview_init(void);
|
||||
});
|
||||
}
|
||||
|
||||
-(void) showCustomKeyboard
|
||||
{
|
||||
#ifdef HAVE_IOS_CUSTOMKEYBOARD
|
||||
[self.keyboardController.view setHidden:false];
|
||||
[self updateOverlayAndFocus];
|
||||
#endif
|
||||
}
|
||||
|
||||
-(void) hideCustomKeyboard
|
||||
{
|
||||
#ifdef HAVE_IOS_CUSTOMKEYBOARD
|
||||
[self.keyboardController.view setHidden:true];
|
||||
[self updateOverlayAndFocus];
|
||||
#endif
|
||||
}
|
||||
|
||||
-(void) updateOverlayAndFocus
|
||||
{
|
||||
#ifdef HAVE_IOS_CUSTOMKEYBOARD
|
||||
int cmdData = self.keyboardController.view.isHidden ? 0 : 1;
|
||||
command_event(CMD_EVENT_GAME_FOCUS_TOGGLE, &cmdData);
|
||||
if ( self.keyboardController.view.isHidden ) {
|
||||
command_event(CMD_EVENT_OVERLAY_INIT, NULL);
|
||||
} else {
|
||||
command_event(CMD_EVENT_OVERLAY_DEINIT, NULL);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
-(BOOL)prefersHomeIndicatorAutoHidden { return YES; }
|
||||
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
|
||||
{
|
||||
@ -227,12 +258,15 @@ void *glkitview_init(void);
|
||||
}
|
||||
|
||||
[self adjustViewFrameForSafeArea];
|
||||
#ifdef HAVE_IOS_CUSTOMKEYBOARD
|
||||
[self.view bringSubviewToFront:self.keyboardController.view];
|
||||
#endif
|
||||
}
|
||||
|
||||
/* NOTE: This version runs on iOS6+. */
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
return (NSUInteger)apple_frontend_settings.orientation_flags;
|
||||
return (UIInterfaceOrientationMask)apple_frontend_settings.orientation_flags;
|
||||
}
|
||||
|
||||
/* NOTE: This version runs on iOS2-iOS5, but not iOS6+. */
|
||||
@ -265,6 +299,50 @@ void *glkitview_init(void);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_COCOATOUCH
|
||||
|
||||
#pragma mark - UIViewController Lifecycle
|
||||
|
||||
-(void)loadView {
|
||||
#if defined(HAVE_COCOA_METAL)
|
||||
self.view = [UIView new];
|
||||
#else
|
||||
self.view = (BRIDGE GLKView*)glkitview_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
-(void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
#if TARGET_OS_IOS
|
||||
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(showNativeMenu)];
|
||||
swipe.numberOfTouchesRequired = 4;
|
||||
swipe.delegate = self;
|
||||
swipe.direction = UISwipeGestureRecognizerDirectionDown;
|
||||
[self.view addGestureRecognizer:swipe];
|
||||
#ifdef HAVE_IOS_TOUCHMOUSE
|
||||
mouseHandler = [[EmulatorTouchMouseHandler alloc] initWithView:self.view];
|
||||
mouseHandler.delegate = self;
|
||||
#endif
|
||||
#ifdef HAVE_IOS_CUSTOMKEYBOARD
|
||||
[self setupEmulatorKeyboard];
|
||||
UISwipeGestureRecognizer *showKeyboardSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(showCustomKeyboard)];
|
||||
showKeyboardSwipe.numberOfTouchesRequired = 3;
|
||||
showKeyboardSwipe.direction = UISwipeGestureRecognizerDirectionUp;
|
||||
showKeyboardSwipe.delegate = self;
|
||||
[self.view addGestureRecognizer:showKeyboardSwipe];
|
||||
UISwipeGestureRecognizer *hideKeyboardSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(hideCustomKeyboard)];
|
||||
hideKeyboardSwipe.numberOfTouchesRequired = 3;
|
||||
hideKeyboardSwipe.direction = UISwipeGestureRecognizerDirectionDown;
|
||||
hideKeyboardSwipe.delegate = self;
|
||||
[self.view addGestureRecognizer:hideKeyboardSwipe];
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
#if TARGET_OS_IOS
|
||||
@ -282,6 +360,47 @@ void *glkitview_init(void);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if TARGET_OS_IOS && HAVE_IOS_TOUCHMOUSE
|
||||
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
[mouseHandler touchesBeganWithTouches:touches];
|
||||
}
|
||||
|
||||
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
[mouseHandler touchesMovedWithTouches:touches];
|
||||
}
|
||||
|
||||
-(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
[mouseHandler touchesCancelledWithTouches:touches];
|
||||
}
|
||||
|
||||
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
[mouseHandler touchesEndedWithTouches:touches];
|
||||
}
|
||||
|
||||
#pragma mark EmulatorTouchMouseHandlerDelegate
|
||||
-(void)handleMouseClickWithIsLeftClick:(BOOL)isLeftClick isPressed:(BOOL)isPressed {
|
||||
cocoa_input_data_t *apple = (cocoa_input_data_t*) input_state_get_ptr()->current_data;
|
||||
if (apple == NULL) {
|
||||
return;
|
||||
}
|
||||
NSUInteger buttonIndex = isLeftClick ? 0 : 1;
|
||||
if (isPressed) {
|
||||
apple->mouse_buttons |= (1 << buttonIndex);
|
||||
} else {
|
||||
apple->mouse_buttons &= ~(1 << buttonIndex);
|
||||
}
|
||||
}
|
||||
|
||||
-(void)handleMouseMoveWithX:(CGFloat)x y:(CGFloat)y {
|
||||
cocoa_input_data_t *apple = (cocoa_input_data_t*) input_state_get_ptr()->current_data;
|
||||
if (apple == NULL) {
|
||||
return;
|
||||
}
|
||||
apple->mouse_rel_x = (int16_t)x;
|
||||
apple->mouse_rel_y = (int16_t)y;
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma mark GCDWebServerDelegate
|
||||
- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server
|
||||
{
|
||||
@ -309,6 +428,7 @@ void *glkitview_init(void);
|
||||
}];
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
@ -41,6 +41,9 @@
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#if defined(HAVE_COCOA_METAL) || defined(HAVE_COCOATOUCH)
|
||||
#if TARGET_OS_IOS
|
||||
#import "JITSupport.h"
|
||||
#endif
|
||||
id<ApplePlatform> apple_platform;
|
||||
#else
|
||||
static id apple_platform;
|
||||
@ -539,6 +542,13 @@ enum
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#if TARGET_OS_IOS
|
||||
if (jb_enable_ptrace_hack()) {
|
||||
NSLog(@"Ptrace hack complete, JIT support is enabled");
|
||||
} else {
|
||||
NSLog(@"Ptrace hack NOT available; Please use an app like Jitterbug.");
|
||||
}
|
||||
#endif
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, NSStringFromClass([RApplication class]), NSStringFromClass([RetroArch_iOS class]));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user