* Fix Duy's deploy and install stuffs * Fix Makefile error and move external_include to external/zink * Bug fix: version ordering when types are disabled This commit fixes #293 * Update README.md * Add option to disable symlink to /var/mobile/Documents/.pojavlauncher (off by default) * Reset preferences option, switch to POJAV_HOME * Update bug_report.yml Update pojavlauncher directory location * Mitigation for the Apache Log4j RCE in 1.8.x-1.18 * Experimental things * Multitouch handling, allows one of these actions below to work with moving camera at the same time. - Switching hotbar slot; might not work properly - Destroying block (left mouse button) with another finger long-pressed (without pressing the PRI button) - Right mouse trigger with another finger tapped onto the screen (without pressing the SEC button); not working properly yet * Sub-pixel cursor support; not yet checked * Completely fix Log4J RCE (1.7-1.16.5) (not yet tested) TODO: checker with SHA1 * Log4J RCE patch config: disable XML format output This applies for Minecraft 1.12+ Disable XML formatting for now until we have a log viewer. * Create newsfeed.html * Update newsfeed.html * Delete newsfeed.html * WIP Demo mode stuff Sign in with a Microsoft account that doesn't own the game, demo_user will be made. This is very much WIP as my iPhone 7 doesn't feel like launching PojavLauncher. * New toggle option: Restart before launching game This option allows the launcher to restart itself before launching the game, therefore all changes are applied such as JVM Arguments and Java version. The restart mechanism doesn't always work, so a toggle is here for now. * A few UI changes Disable the Play button until version list finished loading Disable ability to go back while a version is downloading Display loading alert while logging in * Possible fix for broken Mojang login * Fix a NullPointerException when launching ≤ 1.6.4 * Bug fix: NPE when setting inherited fields Unable to insert id=null java.lang.NoSuchFieldException: id at java.lang.Class.getDeclaredField(Class.java:2070) at net.kdt.pojavlaunch.Tools.insertSafety(Tools.java:544) at net.kdt.pojavlaunch.Tools.getVersionInfo(Tools.java:466) at net.kdt.pojavlaunch.PLaunchApp.launchMinecraft(PLaunchApp.java:61) at net.kdt.pojavlaunch.uikit.UIKit.callback_SurfaceViewController_launchMi necraft(UIKit.java:104) at net.kdt.pojavlaunch.uikit.UIKit.launchUI(Native Method) at net.kdt.pojavlaunch.PLaunchApp.main(PLaunchApp.java:40) * Keyboard + Mouse input handling; some cleanups * Keyboard input: KeyboardInput.m - Requires iOS 13.4. Used pressesBegan and pressesEnded. - Not all keys are handled, these will be handled later. * Mouse input - Requires iOS 13.0 for hovering. Used UIHoverGestureRecognizer. - Requires iOS 14.0 for L/R mouse buttons and scroll handling. Used GCMouse for now. We will use iOS 13.4(?)'s API later. - Pointer lock is not yet handled. These above apply to iPadOS aswell, not sure about tvOS. iPhone users need to disable "Perform Touch Gestures" in the AssistiveTouch Accessibility settings in order to get the pointer hovering to work. * Cleanups - Use asprintf instead of sprintf when possible - CallbackBridge functions: removed unnecessary JNI fields. - Condition cleanup (managing virtual mouse position). * Use the old jvm arguments parser back for now There’s no handling for backslash yet, or ignoring spaces inside delimiters. * Remove duplicated code * Support for input cursor movement from keyboard New class: TrackedTextField. This class keeps track of the pointer in the text field. The hidden text field now has 40 placeholder spaces and the pointer is placed at the middle. When typing, placeholder spaces won't get removed as before. Autocorrect seem to be broken a bit for now. Additional changes: - Added missing GLFW functions (array version of other functions) - Hotbar handling: doubled barWidth and barHeight Bug fix: - Touching onto the hotbar breaks the handling of on-screen tap/long-press gestures * Bug fix: inputStringLength is not set back to 40 This caused a bug that clears the text when pressing backspace after the keyboard is displayed again. * Consolidate some code * Fix undeclared identifier * Consolidate some more code, fix my dumb mistake * Added VGPU renderer Commit: https://github.com/PojavLauncherTeam/VGPU/commit/9f4ded5934b24c62368d70 1647855c9c9077d903 * Update LauncherPreferencesViewController.m This line was mistakenly removed * Various bug fixes - Input cursor movement seems too fast, attempt to reduce it by setting larger font size in the hidden text field. - latestlog.txt doesn't log in 1.12+ since Log4j patch. Fixed by adding some config lines. - Makefile: building is broken when run make from a path with space(s). Fixed by handling spaces with backslashes. - text2speech is not overridden properly. Fixed by excluding it from download list and adding launcher.jar itself to the classpath. - XML log output in < 1.12 since Log4j patch. Fixed by using our own configuration file. Some more changes: - Commented out a debug line in LWJGLX - Fix a typo. - JavaLauncher: use (ObjC)BOOL instead of (std)bool. * Better click handling * Finish make ipa Still requires proper codesigning afterwards, and until I figure out the depends folder you'll need to download a copy of the Azul JDK artifact on our android-openjdk-build-multiarch repository and extract it to depends/jre. Tested and working on my MacBook Pro, although I need to find out why libjsig.dylib is defined twice (this issue doesn't hinder the build though, install_name_tool just doesn't apply changes). * Added readable MSA errors * Merge unjailbroken changes with main (#313) * fix that lol * IPABuilder moment * typo moment * Hooking changes Now hook only dladdr and realpath, JRE works now. However, need to obtain libffi. * Sole more changes Add RPATH to libraries Hook dlopen since loading libawt_headless somehow point to Frameworks directory. * Update development.yml * Update development.yml Co-authored-by: Duy Tran Khanh <[email protected]> Co-authored-by: Duy Tran Khanh <[email protected]> * Fix issue with text alignment * Makefile: multi-jobs building native code * Bump to Xcode 13.2.1 * Bump to Xcode 13.2.1 pt 2 * use gnproc and nproc (for procursus on macos) on the road development is bumpy * oops * [Custom controls] Added color picker for iOS < 14 Might aswell add an option to optionally use this instead of the system color picker on iOS 14+. Used library: Alderis Other changes - [Custom controls] The editor is now fullscreen due to the presentation of the color picker. - Fixes scroll wheel input is always int * I’m too tired of getting dpkg exits, so it’s here * Fix broken share log * Bug fix: Fabric 1.17+ crash at startup(fixes #304) Microsoft account now set accountType to msa. Minor change in custom control view controller. * Add Arc capes * Minor bug fix for Arc capes switch * Change how the login progress is displayed Login progress now displays on the title bar instead of a dialog. This will prevent infinite loading in case a connection error occurs. Closes #315 * Bug fix: Arc DNS injector breaks on Java 16+ This is caused by a missing function in our class loader. Error: System class loader does not define the appendToClassPathForInstrumentation method Unable to add /Applications/PojavLauncher.app/libs/arc_dns_injector.jar to system class path - the system class loader does not define the appendToClassPathForInstrumentation method or the method failed FATAL ERROR in native method: processing of -javaagent failed, appending to system class path failed * Why did I add set -e * Add changelog for 2.1 release * FAQ changes * Workflow changes and typo fix * Workflow changes part 2 * Fixes swipeable button state handling * Update informational.yml * Add A7 warning * Remove codesign from Alderis + thin lib * Only load Alderis.framework when needed * CC editor: view controller now follow bounds(#326) * Update README.md (#324) * Update LauncherPreferencesViewController.m Co-authored-by: Duy Tran Khanh <[email protected]> Co-authored-by: Duy Tran Khanh <[email protected]> Co-authored-by: Silly Sock <[email protected]>
888 lines
34 KiB
Objective-C
888 lines
34 KiB
Objective-C
#import <GameController/GameController.h>
|
|
|
|
#import "LauncherPreferences.h"
|
|
#import "SurfaceViewController.h"
|
|
#import "egl_bridge.h"
|
|
#import "ios_uikit_bridge.h"
|
|
|
|
#import "customcontrols/ControlButton.h"
|
|
#import "customcontrols/ControlDrawer.h"
|
|
#import "customcontrols/ControlSubButton.h"
|
|
#import "customcontrols/CustomControlsUtils.h"
|
|
|
|
#import "KeyboardInput.h"
|
|
|
|
#include "glfw_keycodes.h"
|
|
#include "utils.h"
|
|
|
|
#include "EGL/egl.h"
|
|
|
|
#include "GLES2/gl2.h"
|
|
#include "GLES2/gl2ext.h"
|
|
|
|
// Debugging only
|
|
// #define DEBUG_VISIBLE_TEXT_FIELD
|
|
// #define DEBUG_VISIBLE_TOUCH
|
|
|
|
#define INPUT_SPACE_CHAR @" "
|
|
#define INPUT_FULL_LENGTH 40
|
|
#define INPUT_SPACE_LENGTH 20
|
|
|
|
#ifdef DEBUG_VISIBLE_TEXT_FIELD
|
|
UILabel *inputLengthView;
|
|
#endif
|
|
int inputTextLength;
|
|
|
|
int notchOffset;
|
|
|
|
int currentHotbarSlot = -1;
|
|
BOOL slideableHotbar;
|
|
|
|
// TODO: key modifiers impl
|
|
|
|
#pragma mark Class GameSurfaceView
|
|
@implementation GameSurfaceView
|
|
const void * _CGDataProviderGetBytePointerCallbackOSMESA(void *info) {
|
|
return gbuffer;
|
|
}
|
|
|
|
- (void)displayLayer {
|
|
CGDataProviderRef bitmapProvider = CGDataProviderCreateDirect(NULL, savedWidth * savedHeight * 4, &callbacks);
|
|
CGImageRef bitmap = CGImageCreate(savedWidth, savedHeight, 8, 32, 4 * savedWidth, colorSpace, kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder16Little, bitmapProvider, NULL, FALSE, kCGRenderingIntentDefault);
|
|
|
|
self.layer.contents = (__bridge id) bitmap;
|
|
CGImageRelease(bitmap);
|
|
CGDataProviderRelease(bitmapProvider);
|
|
// CGColorSpaceRelease(colorSpace);
|
|
}
|
|
|
|
- (id)initWithFrame:(CGRect)frame {
|
|
self = [super initWithFrame:frame];
|
|
|
|
if ([getPreference(@"renderer") hasPrefix:@"libOSMesaOverride"]) {
|
|
self.layer.opaque = YES;
|
|
|
|
colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
|
|
callbacks.version = 0;
|
|
callbacks.getBytePointer = _CGDataProviderGetBytePointerCallbackOSMESA;
|
|
callbacks.releaseBytePointer = _CGDataProviderReleaseBytePointerCallback;
|
|
callbacks.getBytesAtPosition = NULL;
|
|
callbacks.releaseInfo = NULL;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
+ (Class)layerClass {
|
|
if ([getPreference(@"renderer") hasPrefix:@"libOSMesa"]) {
|
|
return CALayer.class;
|
|
} else {
|
|
return CAMetalLayer.class;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
#pragma mark Class TrackedTextField
|
|
@interface TrackedTextField : UITextField
|
|
@property int lastPosition;
|
|
@property UITextPosition* lockPos;
|
|
@end
|
|
|
|
@implementation TrackedTextField
|
|
- (UITextPosition *)closestPositionToPoint:(CGPoint)point {
|
|
UITextPosition *position = [super closestPositionToPoint:point];
|
|
int start = [self offsetFromPosition:self.beginningOfDocument toPosition:position];
|
|
if (start - self.lastPosition != 0) {
|
|
int key = (start - self.lastPosition > 0) ? GLFW_KEY_DPAD_RIGHT : GLFW_KEY_DPAD_LEFT;
|
|
CallbackBridge_nativeSendKey(key, 0, 1, 0);
|
|
CallbackBridge_nativeSendKey(key, 0, 0, 0);
|
|
}
|
|
self.lastPosition = start;
|
|
return [self positionFromPosition:self.beginningOfDocument offset:clamp(start, 20, self.text.length - 20)];
|
|
}
|
|
|
|
- (void)setText:(NSString *)text {
|
|
[super setText:text];
|
|
self.lockPos = [self positionFromPosition:self.beginningOfDocument offset:20];
|
|
self.selectedTextRange = [self textRangeFromPosition:self.lockPos toPosition:self.lockPos];
|
|
}
|
|
@end
|
|
|
|
|
|
#pragma mark Class SurfaceViewController
|
|
@interface SurfaceViewController ()<UITextFieldDelegate, UIPointerInteractionDelegate, UIGestureRecognizerDelegate> {
|
|
}
|
|
|
|
@property TrackedTextField *inputView;
|
|
@property(nonatomic, strong) NSMutableDictionary* cc_dictionary;
|
|
@property(nonatomic, strong) NSMutableArray* swipeableButtons;
|
|
@property(nonatomic, strong) NSMutableArray* togglableVisibleButtons;
|
|
@property ControlButton* swipingButton;
|
|
@property UITouch *primaryTouch, *hotbarTouch;
|
|
@property id mouseConnectCallback, mouseDisconnectCallback;
|
|
|
|
@property CGRect clickRange;
|
|
@property BOOL shouldTriggerClick;
|
|
|
|
@end
|
|
|
|
@implementation SurfaceViewController
|
|
|
|
- (void)viewDidLoad
|
|
{
|
|
[super viewDidLoad];
|
|
viewController = self;
|
|
isControlModifiable = NO;
|
|
|
|
setPreference(@"internal_selected_account", nil);
|
|
|
|
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
|
|
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
|
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
|
|
[self.navigationItem setHidesBackButton:YES animated:YES];
|
|
[self.navigationController setNavigationBarHidden:YES animated:YES];
|
|
|
|
CGRect screenBounds = [[UIScreen mainScreen] bounds];
|
|
CGFloat screenScale = [[UIScreen mainScreen] scale];
|
|
|
|
UIEdgeInsets insets = UIApplication.sharedApplication.windows.firstObject.safeAreaInsets;
|
|
|
|
resolutionScale = ((NSNumber *)getPreference(@"resolution")).floatValue / 100.0;
|
|
slideableHotbar = [getPreference(@"slideable_hotbar") boolValue];
|
|
|
|
int width = (int) roundf(screenBounds.size.width);
|
|
int height = (int) roundf(screenBounds.size.height);
|
|
|
|
savedWidth = roundf(width * screenScale);
|
|
savedHeight = roundf(height * screenScale);
|
|
|
|
self.surfaceView = [[GameSurfaceView alloc] initWithFrame:self.view.frame];
|
|
self.surfaceView.multipleTouchEnabled = YES;
|
|
self.surfaceView.layer.contentsScale = screenScale * resolutionScale;
|
|
self.surfaceView.layer.magnificationFilter = self.surfaceView.layer.minificationFilter = kCAFilterNearest;
|
|
[self.view addSubview:self.surfaceView];
|
|
|
|
// Enable support for desktop GLSL
|
|
if ([getPreference(@"disable_gl4es_shaderconv") boolValue]) {
|
|
setenv("LIBGL_NOSHADERCONV", "1", 1);
|
|
}
|
|
|
|
notchOffset = insets.left;
|
|
width = width - notchOffset * 2;
|
|
CGFloat buttonScale = [getPreference(@"button_scale") floatValue] / 100.0;
|
|
|
|
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]
|
|
initWithTarget:self action:@selector(surfaceOnClick:)];
|
|
tapGesture.allowedTouchTypes = @[@(UITouchTypeDirect)];
|
|
tapGesture.delegate = self;
|
|
tapGesture.numberOfTapsRequired = 1;
|
|
tapGesture.numberOfTouchesRequired = 1;
|
|
tapGesture.cancelsTouchesInView = NO;
|
|
[self.surfaceView addGestureRecognizer:tapGesture];
|
|
|
|
UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc]
|
|
initWithTarget:self action:@selector(surfaceOnDoubleClick:)];
|
|
doubleTapGesture.allowedTouchTypes = @[@(UITouchTypeDirect)];
|
|
doubleTapGesture.delegate = self;
|
|
doubleTapGesture.numberOfTapsRequired = 2;
|
|
doubleTapGesture.numberOfTouchesRequired = 1;
|
|
doubleTapGesture.cancelsTouchesInView = NO;
|
|
[self.surfaceView addGestureRecognizer:doubleTapGesture];
|
|
|
|
if (@available(iOS 13.0, *)) {
|
|
UIHoverGestureRecognizer *hoverGesture = [[UIHoverGestureRecognizer alloc]
|
|
initWithTarget:self action:@selector(surfaceOnHover:)];
|
|
[self.surfaceView addGestureRecognizer:hoverGesture];
|
|
}
|
|
|
|
UILongPressGestureRecognizer *longpressGesture = [[UILongPressGestureRecognizer alloc]
|
|
initWithTarget:self action:@selector(surfaceOnLongpress:)];
|
|
longpressGesture.allowedTouchTypes = @[@(UITouchTypeDirect)];
|
|
longpressGesture.cancelsTouchesInView = NO;
|
|
longpressGesture.delegate = self;
|
|
longpressGesture.minimumPressDuration = [getPreference(@"time_longPressTrigger") floatValue] / 1000;
|
|
[self.surfaceView addGestureRecognizer:longpressGesture];
|
|
|
|
self.scrollPanGesture = [[UIPanGestureRecognizer alloc]
|
|
initWithTarget:self action:@selector(surfaceOnTouchesScroll:)];
|
|
self.scrollPanGesture.allowedTouchTypes = @[@(UITouchTypeDirect)];
|
|
self.scrollPanGesture.delegate = self;
|
|
self.scrollPanGesture.minimumNumberOfTouches = 2;
|
|
self.scrollPanGesture.maximumNumberOfTouches = 2;
|
|
|
|
if (@available(iOS 13.4, *)) {
|
|
[self.surfaceView addInteraction:[[UIPointerInteraction alloc] initWithDelegate:self]];
|
|
}
|
|
|
|
// Virtual mouse
|
|
virtualMouseFrame = CGRectMake(screenBounds.size.width / 2, screenBounds.size.height / 2, 18, 27);
|
|
self.mousePointerView = [[UIImageView alloc] initWithFrame:virtualMouseFrame];
|
|
self.mousePointerView.hidden = YES;
|
|
self.mousePointerView.image = [UIImage imageNamed:@"mouse_pointer.png"];
|
|
self.mousePointerView.userInteractionEnabled = NO;
|
|
[self.view addSubview:self.mousePointerView];
|
|
|
|
#ifndef DEBUG_VISIBLE_TEXT_FIELD
|
|
self.inputView = [[TrackedTextField alloc] initWithFrame:CGRectMake(0, -1, 1, 1)];
|
|
#else
|
|
self.inputView = [[TrackedTextField alloc] initWithFrame:CGRectMake(5 * 2 + 160.0, 5 * 2 + 30.0, 200.0, 30.0)];
|
|
self.inputView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.5f];
|
|
self.inputView.font = [self.inputView.font fontWithSize:20];
|
|
|
|
inputLengthView = [[UILabel alloc] initWithFrame:CGRectMake(5 * 2 + 80.0, 5 * 2 + 30.0, 80.0, 30.0)];
|
|
inputLengthView.text = @"length=?";
|
|
inputLengthView.backgroundColor = [UIColor colorWithWhite:1.0f alpha:0.6f];
|
|
[self.view addSubview:inputLengthView];
|
|
#endif
|
|
self.inputView.delegate = self;
|
|
self.inputView.lastPosition = 20;
|
|
[self.inputView addTarget:self action:@selector(inputViewDidChange) forControlEvents:UIControlEventEditingChanged];
|
|
|
|
NSString *controlFilePath = [NSString stringWithFormat:@"%s/%@", getenv("POJAV_PATH_CONTROL"), (NSString *)getPreference(@"default_ctrl")];
|
|
|
|
NSError *cc_error;
|
|
NSString *cc_data = [NSString stringWithContentsOfFile:controlFilePath encoding:NSUTF8StringEncoding error:&cc_error];
|
|
|
|
self.swipeableButtons = [[NSMutableArray alloc] init];
|
|
self.togglableVisibleButtons = [[NSMutableArray alloc] init];
|
|
if (cc_error) {
|
|
NSLog(@"Error: could not read %@: %@", controlFilePath, cc_error.localizedDescription);
|
|
showDialog(self, @"Error", [NSString stringWithFormat:@"Could not read %@: %@", controlFilePath, cc_error.localizedDescription]);
|
|
} else {
|
|
NSData* cc_objc_data = [cc_data dataUsingEncoding:NSUTF8StringEncoding];
|
|
self.cc_dictionary = [NSJSONSerialization JSONObjectWithData:cc_objc_data options:NSJSONReadingMutableContainers error:&cc_error];
|
|
if (cc_error != nil) {
|
|
showDialog(self, @"Error parsing JSON", cc_error.localizedDescription);
|
|
} else {
|
|
CGFloat currentScale = [self.cc_dictionary[@"scaledAt"] floatValue];
|
|
CGFloat savedScale = [getPreference(@"button_scale") floatValue];
|
|
loadControlObject(self.view, self.cc_dictionary, ^void(ControlButton* button) {
|
|
BOOL isSwipeable = [button.properties[@"isSwipeable"] boolValue];
|
|
|
|
BOOL isToggleCtrlBtn = NO;
|
|
for (int i = 0; i < 4; i++) {
|
|
int keycodeInt = [button.properties[@"keycodes"][i] intValue];
|
|
if (keycodeInt == SPECIALBTN_TOGGLECTRL) {
|
|
isToggleCtrlBtn = YES;
|
|
}
|
|
}
|
|
|
|
[button addTarget:self action:@selector(executebtn_down:) forControlEvents:UIControlEventTouchDown];
|
|
[button addTarget:self action:@selector(executebtn_up:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];
|
|
|
|
if (isSwipeable) {
|
|
UIPanGestureRecognizer *panRecognizerButton = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(executebtn_swipe:)];
|
|
panRecognizerButton.delegate = self;
|
|
[button addGestureRecognizer:panRecognizerButton];
|
|
[self.swipeableButtons addObject:button];
|
|
}
|
|
if (!isToggleCtrlBtn) {
|
|
[self.togglableVisibleButtons addObject:button];
|
|
}
|
|
});
|
|
|
|
self.cc_dictionary[@"scaledAt"] = @(savedScale);
|
|
}
|
|
}
|
|
|
|
[KeyboardInput initKeycodeTable];
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
self.mouseConnectCallback = [[NSNotificationCenter defaultCenter] addObserverForName:GCMouseDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
NSLog(@"Input: Mouse connected!");
|
|
GCMouse* mouse = note.object;
|
|
[self registerMouseCallbacks: mouse];
|
|
[self setNeedsUpdateOfPrefersPointerLocked];
|
|
}];
|
|
self.mouseDisconnectCallback = [[NSNotificationCenter defaultCenter] addObserverForName:GCMouseDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
NSLog(@"Input: Mouse disconnected!");
|
|
GCMouse* mouse = note.object;
|
|
mouse.mouseInput.mouseMovedHandler = nil;
|
|
mouse.mouseInput.leftButton.pressedChangedHandler = nil;
|
|
mouse.mouseInput.middleButton.pressedChangedHandler = nil;
|
|
mouse.mouseInput.rightButton.pressedChangedHandler = nil;
|
|
[self setNeedsUpdateOfPrefersPointerLocked];
|
|
}];
|
|
if (GCMouse.current != nil) {
|
|
[self registerMouseCallbacks: GCMouse.current];
|
|
}
|
|
}
|
|
|
|
[self.view addSubview:self.inputView];
|
|
|
|
[self executebtn_special_togglebtn:0];
|
|
|
|
// [self setPreferredFramesPerSecond:1000];
|
|
|
|
callback_SurfaceViewController_launchMinecraft(savedWidth * resolutionScale, savedHeight * resolutionScale);
|
|
}
|
|
|
|
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
|
|
return UIRectEdgeBottom;
|
|
}
|
|
|
|
- (BOOL)prefersHomeIndicatorAutoHidden {
|
|
return NO;
|
|
}
|
|
|
|
#pragma mark - Input: send touch utilities
|
|
|
|
|
|
- (BOOL)isTouchInactive:(UITouch *)touch {
|
|
return touch == nil || touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled;
|
|
}
|
|
|
|
|
|
- (void)sendTouchPoint:(CGPoint)location withEvent:(int)event
|
|
{
|
|
CGFloat screenScale = [[UIScreen mainScreen] scale];
|
|
if (!isGrabbing) {
|
|
screenScale *= resolutionScale;
|
|
if (virtualMouseEnabled) {
|
|
if (event == ACTION_MOVE) {
|
|
virtualMouseFrame.origin.x += location.x - lastVirtualMousePoint.x;
|
|
virtualMouseFrame.origin.y += location.y - lastVirtualMousePoint.y;
|
|
} else if (event == ACTION_MOVE_MOTION) {
|
|
event = ACTION_MOVE;
|
|
virtualMouseFrame.origin.x += location.x;
|
|
virtualMouseFrame.origin.y += location.y;
|
|
}
|
|
virtualMouseFrame.origin.x = clamp(virtualMouseFrame.origin.x, 0, self.view.frame.size.width);
|
|
virtualMouseFrame.origin.y = clamp(virtualMouseFrame.origin.y, 0, self.view.frame.size.height);
|
|
lastVirtualMousePoint = location;
|
|
self.mousePointerView.frame = virtualMouseFrame;
|
|
callback_SurfaceViewController_onTouch(event, virtualMouseFrame.origin.x * screenScale, virtualMouseFrame.origin.y * screenScale);
|
|
return;
|
|
}
|
|
lastVirtualMousePoint = location;
|
|
}
|
|
callback_SurfaceViewController_onTouch(event, location.x * screenScale, location.y * screenScale);
|
|
}
|
|
|
|
#pragma mark - Input: on-surface functions
|
|
|
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
|
return YES;
|
|
}
|
|
|
|
- (void)sendTouchEvent:(UITouch *)touchEvent withUIEvent:(UIEvent *)uievent withEvent:(int)event
|
|
{
|
|
CGPoint locationInView = [touchEvent locationInView:self.view];
|
|
|
|
if (touchEvent.view == self.surfaceView) {
|
|
switch (event) {
|
|
case ACTION_DOWN:
|
|
self.clickRange = CGRectMake(locationInView.x - 2, locationInView.y - 2, 5, 5);
|
|
self.shouldTriggerClick = YES;
|
|
|
|
break;
|
|
|
|
case ACTION_MOVE:
|
|
if (self.shouldTriggerClick && !CGRectContainsPoint(self.clickRange, locationInView)) {
|
|
self.shouldTriggerClick = NO;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (touchEvent == self.hotbarTouch && slideableHotbar && ![self isTouchInactive:self.hotbarTouch]) {
|
|
CGFloat screenScale = [[UIScreen mainScreen] scale];
|
|
int slot = callback_SurfaceViewController_touchHotbar(locationInView.x * screenScale, locationInView.y * screenScale);
|
|
|
|
if (slot != -1 && currentHotbarSlot != slot && (event == ACTION_DOWN || currentHotbarSlot != -1)) {
|
|
currentHotbarSlot = slot;
|
|
CallbackBridge_nativeSendKey(slot, 0, 1, 0);
|
|
CallbackBridge_nativeSendKey(slot, 0, 0, 0);
|
|
return;
|
|
} /* else if ((event == ACTION_MOVE || event == ACTION_UP) && slot == -1 && currentHotbarSlot != -1) {
|
|
return;
|
|
} */
|
|
|
|
if (event == ACTION_DOWN && slot == -1) {
|
|
currentHotbarSlot = -1;
|
|
}
|
|
/*
|
|
if (currentHotbarSlot != -1) {
|
|
return;
|
|
}
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (touchEvent == self.primaryTouch) {
|
|
if ([self isTouchInactive:self.primaryTouch]) return; // FIXME: should be? ACTION_UP will never be sent
|
|
[self sendTouchPoint:locationInView withEvent:event];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
|
|
BOOL handled = NO;
|
|
|
|
if (@available(iOS 13.4, *)) {
|
|
for (UIPress *press in presses) {
|
|
if (press.key != nil && [KeyboardInput sendKeyEvent:press.key down:YES]) {
|
|
handled = YES;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!handled) {
|
|
[super pressesBegan:presses withEvent:event];
|
|
}
|
|
}
|
|
|
|
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
|
|
BOOL handled = NO;
|
|
|
|
if (@available(iOS 13.4, *)) {
|
|
for (UIPress *press in presses) {
|
|
if (press.key != nil && [KeyboardInput sendKeyEvent:press.key down:NO]) {
|
|
handled = YES;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!handled) {
|
|
[super pressesEnded:presses withEvent:event];
|
|
}
|
|
}
|
|
|
|
- (void)registerMouseCallbacks:(GCMouse *)mouse API_AVAILABLE(ios(14.0)) {
|
|
NSLog(@"Input: Got mouse %@", mouse);
|
|
mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
|
|
CallbackBridge_nativeSendMouseButton(GLFW_MOUSE_BUTTON_LEFT, pressed, 0);
|
|
};
|
|
mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
|
|
CallbackBridge_nativeSendMouseButton(GLFW_MOUSE_BUTTON_MIDDLE, pressed, 0);
|
|
};
|
|
mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
|
|
CallbackBridge_nativeSendMouseButton(GLFW_MOUSE_BUTTON_RIGHT, pressed, 0);
|
|
};
|
|
|
|
mouse.mouseInput.scroll.xAxis.valueChangedHandler = ^(GCControllerAxisInput * _Nonnull axis, float value) {
|
|
CallbackBridge_nativeSendScroll(value, 0);
|
|
};
|
|
mouse.mouseInput.scroll.yAxis.valueChangedHandler = ^(GCControllerAxisInput * _Nonnull axis, float value) {
|
|
CallbackBridge_nativeSendScroll(0, value);
|
|
};
|
|
|
|
}
|
|
|
|
- (void)surfaceOnClick:(UITapGestureRecognizer *)sender {
|
|
if (!self.shouldTriggerClick) return;
|
|
|
|
if (sender.state == UIGestureRecognizerStateRecognized) {
|
|
if (currentHotbarSlot == -1) {
|
|
self.inputView.text = INPUT_SPACE_CHAR;
|
|
inputTextLength = 0;
|
|
|
|
CallbackBridge_nativeSendMouseButton(
|
|
isGrabbing == JNI_TRUE ? GLFW_MOUSE_BUTTON_RIGHT : GLFW_MOUSE_BUTTON_LEFT, 1, 0);
|
|
CallbackBridge_nativeSendMouseButton(
|
|
isGrabbing == JNI_TRUE ? GLFW_MOUSE_BUTTON_RIGHT : GLFW_MOUSE_BUTTON_LEFT, 0, 0);
|
|
} else {
|
|
CallbackBridge_nativeSendKey(currentHotbarSlot, 0, 1, 0);
|
|
CallbackBridge_nativeSendKey(currentHotbarSlot, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)surfaceOnDoubleClick:(UITapGestureRecognizer *)sender {
|
|
if (sender.state == UIGestureRecognizerStateRecognized && isGrabbing) {
|
|
CGFloat screenScale = [[UIScreen mainScreen] scale];
|
|
CGPoint location = [sender locationInView:[sender.view superview]];
|
|
int hotbarSlot = callback_SurfaceViewController_touchHotbar(location.x * screenScale * resolutionScale, location.y * screenScale * resolutionScale);
|
|
if (hotbarSlot != -1 && currentHotbarSlot == hotbarSlot) {
|
|
CallbackBridge_nativeSendKey(GLFW_KEY_F, 0, 1, 0);
|
|
CallbackBridge_nativeSendKey(GLFW_KEY_F, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)surfaceOnHover:(UIHoverGestureRecognizer *)sender API_AVAILABLE(ios(13.0)) {
|
|
if (@available(iOS 13.0, *)) {
|
|
CGPoint point = [sender locationInView:self.surfaceView];
|
|
// NSLog(@"Mouse move!!");
|
|
// NSLog(@"Mouse pos = %f, %f", point.x, point.y);
|
|
switch (sender.state) {
|
|
case UIGestureRecognizerStateBegan:
|
|
[self sendTouchPoint:point withEvent:ACTION_DOWN];
|
|
break;
|
|
case UIGestureRecognizerStateChanged:
|
|
[self sendTouchPoint:point withEvent:ACTION_MOVE];
|
|
break;
|
|
case UIGestureRecognizerStateEnded:
|
|
case UIGestureRecognizerStateCancelled:
|
|
[self sendTouchPoint:point withEvent:ACTION_UP];
|
|
break;
|
|
default:
|
|
// point = CGPointMake(-1, -1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void)surfaceOnLongpress:(UILongPressGestureRecognizer *)sender
|
|
{
|
|
if (!slideableHotbar) {
|
|
CGFloat screenScale = [[UIScreen mainScreen] scale];
|
|
CGPoint location = [sender locationInView:[sender.view superview]];
|
|
currentHotbarSlot = callback_SurfaceViewController_touchHotbar(location.x * screenScale * resolutionScale, location.y * screenScale * resolutionScale);
|
|
}
|
|
if (sender.state == UIGestureRecognizerStateBegan) {
|
|
self.shouldTriggerClick = NO;
|
|
if (currentHotbarSlot == -1) {
|
|
self.inputView.text = INPUT_SPACE_CHAR;
|
|
inputTextLength = 0;
|
|
|
|
CallbackBridge_nativeSendMouseButton(GLFW_MOUSE_BUTTON_LEFT, 1, 0);
|
|
} else {
|
|
CallbackBridge_nativeSendKey(GLFW_KEY_Q, 0, 1, 0);
|
|
}
|
|
} else if (sender.state == UIGestureRecognizerStateChanged) {
|
|
// Nothing to do here, already handled in touchesMoved
|
|
} else {
|
|
if (sender.state == UIGestureRecognizerStateCancelled
|
|
|| sender.state == UIGestureRecognizerStateFailed
|
|
|| sender.state == UIGestureRecognizerStateEnded)
|
|
{
|
|
if (currentHotbarSlot == -1) {
|
|
self.inputView.text = INPUT_SPACE_CHAR;
|
|
inputTextLength = 0;
|
|
|
|
CallbackBridge_nativeSendMouseButton(GLFW_MOUSE_BUTTON_LEFT, 0, 0);
|
|
} else {
|
|
|
|
CallbackBridge_nativeSendKey(GLFW_KEY_Q, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)surfaceOnTouchesScroll:(UIPanGestureRecognizer *)sender {
|
|
if (isGrabbing) return;
|
|
if (sender.state == UIGestureRecognizerStateBegan ||
|
|
sender.state == UIGestureRecognizerStateChanged ||
|
|
sender.state == UIGestureRecognizerStateEnded) {
|
|
CGPoint velocity = [sender velocityInView:self.view];
|
|
if (velocity.x != 0.0f || velocity.y != 0.0f) {
|
|
CallbackBridge_nativeSendScroll((CGFloat) (velocity.x/10.0), (CGFloat) (velocity.y/10.0));
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME: incomplete
|
|
- (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4)) API_AVAILABLE(ios(13.4)) API_AVAILABLE(ios(13.4)){
|
|
NSLog(@"regionForRequest called");
|
|
return nil;
|
|
}
|
|
|
|
- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4)){
|
|
NSLog(@"styleForRegion called");
|
|
return [UIPointerStyle hiddenPointerStyle];
|
|
}
|
|
|
|
#pragma mark - Input view stuff
|
|
|
|
NSString* inputStringBefore;
|
|
int inputStringLength = INPUT_FULL_LENGTH;
|
|
-(void)inputViewDidChange {
|
|
int typedLength = (int)self.inputView.text.length - inputStringLength;
|
|
if (typedLength < 0) {
|
|
for (int i = 0; i < -typedLength; i++) {
|
|
if (self.inputView.text.length < INPUT_FULL_LENGTH) {
|
|
self.inputView.text = [@" " stringByAppendingString:self.inputView.text];
|
|
}
|
|
CallbackBridge_nativeSendKey(GLFW_KEY_BACKSPACE, 0, 1, 0);
|
|
CallbackBridge_nativeSendKey(GLFW_KEY_BACKSPACE, 0, 0, 0);
|
|
if (inputTextLength > 0) {
|
|
--inputTextLength;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_VISIBLE_TEXT_FIELD
|
|
inputLengthView.text = [@"length=" stringByAppendingFormat:@"%i",
|
|
inputTextLength];
|
|
#endif
|
|
} else if (typedLength > 0) {
|
|
int index = [self.inputView offsetFromPosition:self.inputView.beginningOfDocument toPosition:self.inputView.selectedTextRange.start];
|
|
NSString *newText = [self.inputView.text substringWithRange:NSMakeRange(index - typedLength, typedLength)];
|
|
int charLength = (int) [newText length];
|
|
for (int i = 0; i < charLength; i++) {
|
|
// Directly convert unichar to jchar since both are in UTF-16 encoding.
|
|
|
|
jchar theChar = (jchar) [newText characterAtIndex:i];
|
|
if (isUseStackQueueCall) {
|
|
CallbackBridge_nativeSendCharMods(theChar, /* mods */ 0);
|
|
} else {
|
|
CallbackBridge_nativeSendChar(theChar);
|
|
}
|
|
|
|
++inputTextLength;
|
|
#ifdef DEBUG_VISIBLE_TEXT_FIELD
|
|
inputLengthView.text = [@"length=" stringByAppendingFormat:@"%i",
|
|
inputTextLength];
|
|
#endif
|
|
}
|
|
inputStringBefore = self.inputView.text;
|
|
// [self.inputView.text substringFromIndex:inputTextLength - 1];
|
|
} else {
|
|
#ifdef DEBUG_VISIBLE_TEXT_FIELD
|
|
NSLog(@"Compare \"%@\" vs \"%@\"", self.inputView.text, inputStringBefore);
|
|
#endif
|
|
for (int i = 0; i < INPUT_FULL_LENGTH; i++) {
|
|
if ([self.inputView.text characterAtIndex:i] != [inputStringBefore characterAtIndex:i]) {
|
|
NSString *inputStringNow = [self.inputView.text substringFromIndex:i];
|
|
/*
|
|
self.inputView.text = [self.inputView.text substringToIndex:i];
|
|
// self notify
|
|
[self inputViewDidChange];
|
|
|
|
self.inputView.text = [self.inputView.text stringByAppendingString:inputStringNow];
|
|
// self notify
|
|
[self inputViewDidChange];
|
|
*/
|
|
|
|
for (int i2 = 0; i2 < [inputStringNow length]; i2++) {
|
|
CallbackBridge_nativeSendKey(GLFW_KEY_BACKSPACE, 0, 1, 0);
|
|
CallbackBridge_nativeSendKey(GLFW_KEY_BACKSPACE, 0, 0, 0);
|
|
}
|
|
|
|
for (int i2 = 0; i2 < [inputStringNow length]; i2++) {
|
|
CallbackBridge_nativeSendCharMods((jchar) [inputStringNow characterAtIndex:i2], /* mods */ 0);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_VISIBLE_TEXT_FIELD
|
|
inputLengthView.text = @"length =";
|
|
#endif
|
|
inputStringBefore = self.inputView.text;
|
|
}
|
|
|
|
inputStringLength = (int)self.inputView.text.length;
|
|
|
|
// Reset to default value
|
|
// self.inputView.text = INPUT_SPACE_CHAR;
|
|
}
|
|
|
|
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
|
|
CallbackBridge_nativeSendKey(GLFW_KEY_ENTER, 0, 1, 0);
|
|
CallbackBridge_nativeSendKey(GLFW_KEY_ENTER, 0, 0, 0);
|
|
return YES;
|
|
}
|
|
|
|
#pragma mark - On-screen button functions
|
|
|
|
int currentVisibility = 1;
|
|
- (void)executebtn:(UIButton *)sender withAction:(int)action {
|
|
ControlButton *button = (ControlButton *)sender;
|
|
int held = action == ACTION_DOWN;
|
|
for (int i = 0; i < 4; i++) {
|
|
int keycode = ((NSNumber *)button.properties[@"keycodes"][i]).intValue;
|
|
if (keycode < 0) {
|
|
switch (keycode) {
|
|
case SPECIALBTN_KEYBOARD:
|
|
if (held == 0) {
|
|
if (self.inputView.isFirstResponder) {
|
|
[self.inputView resignFirstResponder];
|
|
self.inputView.alpha = 1.0f;
|
|
self.inputView.text = @"";
|
|
} else {
|
|
[self.inputView becomeFirstResponder];
|
|
// Empty the input field so that user will no longer able to select text inside.
|
|
self.inputView.text = INPUT_SPACE_CHAR;
|
|
inputStringLength = INPUT_FULL_LENGTH;
|
|
|
|
#ifndef DEBUG_VISIBLE_TEXT_FIELD
|
|
self.inputView.alpha = 0.0f;
|
|
#endif
|
|
inputTextLength = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SPECIALBTN_MOUSEPRI:
|
|
CallbackBridge_nativeSendMouseButton(GLFW_MOUSE_BUTTON_LEFT, held, 0);
|
|
break;
|
|
|
|
case SPECIALBTN_MOUSESEC:
|
|
CallbackBridge_nativeSendMouseButton(GLFW_MOUSE_BUTTON_RIGHT, held, 0);
|
|
break;
|
|
|
|
case SPECIALBTN_MOUSEMID:
|
|
CallbackBridge_nativeSendMouseButton(GLFW_MOUSE_BUTTON_MIDDLE, held, 0);
|
|
break;
|
|
|
|
case SPECIALBTN_TOGGLECTRL:
|
|
[self executebtn_special_togglebtn:held];
|
|
break;
|
|
|
|
case SPECIALBTN_SCROLLDOWN:
|
|
if (!held) {
|
|
CallbackBridge_nativeSendScroll(0.0, 1.0);
|
|
}
|
|
break;
|
|
|
|
case SPECIALBTN_SCROLLUP:
|
|
if (!held) {
|
|
CallbackBridge_nativeSendScroll(0.0, -1.0);
|
|
}
|
|
|
|
case SPECIALBTN_VIRTUALMOUSE:
|
|
if (!isGrabbing && !held) {
|
|
virtualMouseEnabled = !virtualMouseEnabled;
|
|
self.mousePointerView.hidden = !virtualMouseEnabled;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
NSLog(@"Warning: button %@ sent unknown special keycode: %d", button.titleLabel.text, keycode);
|
|
break;
|
|
}
|
|
} else if (keycode > 0) {
|
|
// there's no key id 0, but we accidentally used -1 as a special key id, so we had to do that
|
|
// if (keycode == 0) { keycode = -1; }
|
|
// at the moment, send unknown keycode does nothing, may even cause performance issue, so ignore it
|
|
|
|
CallbackBridge_nativeSendKey(keycode, 0, held, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)executebtn_down:(ControlButton *)sender
|
|
{
|
|
[self executebtn:sender withAction:ACTION_DOWN];
|
|
if ([self.swipeableButtons containsObject:sender]) {
|
|
self.swipingButton = sender;
|
|
}
|
|
}
|
|
|
|
- (void)executebtn_swipe:(UIPanGestureRecognizer *)sender
|
|
{
|
|
CGPoint location = [sender locationInView:self.view];
|
|
if (sender.state == UIGestureRecognizerStateCancelled || sender.state == UIGestureRecognizerStateEnded) {
|
|
[self executebtn_up:self.swipingButton];
|
|
return;
|
|
}
|
|
for (ControlButton *button in self.swipeableButtons) {
|
|
if (CGRectContainsPoint(button.frame, location) && (ControlButton *)self.swipingButton != button) {
|
|
[self executebtn:self.swipingButton withAction:ACTION_UP];
|
|
self.swipingButton = (ControlButton *)button;
|
|
[self executebtn:self.swipingButton withAction:ACTION_DOWN];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)executebtn_up:(ControlButton *)sender
|
|
{
|
|
if (self.swipingButton == sender) {
|
|
[self executebtn:self.swipingButton withAction:ACTION_UP];
|
|
self.swipingButton = nil;
|
|
} else {
|
|
[self executebtn:sender withAction:ACTION_UP];
|
|
}
|
|
}
|
|
|
|
- (void)executebtn_special_togglebtn:(int)held {
|
|
if (held == 0) {
|
|
currentVisibility = !currentVisibility;
|
|
for (ControlButton *button in self.togglableVisibleButtons) {
|
|
if (!currentVisibility && ![button isKindOfClass:[ControlSubButton class]]) {
|
|
button.hidden = currentVisibility;
|
|
if ([button isKindOfClass:[ControlDrawer class]]) {
|
|
[(ControlDrawer *)button restoreButtonVisibility];
|
|
}
|
|
} else if (currentVisibility) {
|
|
button.hidden = currentVisibility;
|
|
}
|
|
}
|
|
|
|
#ifndef DEBUG_VISIBLE_TEXT_FIELD
|
|
self.inputView.hidden = currentVisibility;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#pragma mark - Input: On-screen touch events
|
|
|
|
int touchesMovedCount;
|
|
// Equals to Android ACTION_DOWN
|
|
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
[super touchesBegan:touches withEvent:event];
|
|
int i = 0;
|
|
for (UITouch *touch in touches) {
|
|
if (@available(iOS 14.0, *)) { // 13.4
|
|
if (touch.type == UITouchTypeIndirectPointer) {
|
|
continue; // handle this in a different place
|
|
}
|
|
}
|
|
CGPoint locationInView = [touch locationInView:self.view];
|
|
CGFloat screenScale = [[UIScreen mainScreen] scale];
|
|
currentHotbarSlot = callback_SurfaceViewController_touchHotbar(locationInView.x * screenScale, locationInView.y * screenScale);
|
|
if ([self isTouchInactive:self.hotbarTouch] && currentHotbarSlot != -1) {
|
|
self.hotbarTouch = touch;
|
|
}
|
|
if ([self isTouchInactive:self.primaryTouch] && currentHotbarSlot == -1) {
|
|
self.primaryTouch = touch;
|
|
}
|
|
[self sendTouchEvent:touch withUIEvent:event withEvent:ACTION_DOWN];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Equals to Android ACTION_MOVE
|
|
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
[super touchesMoved:touches withEvent:event];
|
|
|
|
for (UITouch *touch in touches) {
|
|
if (@available(iOS 14.0, *)) { // 13.4
|
|
if (touch.type == UITouchTypeIndirectPointer) {
|
|
continue; // handle this in a different place
|
|
}
|
|
}
|
|
if (self.hotbarTouch != touch && [self isTouchInactive:self.primaryTouch]) {
|
|
// Replace the inactive touch with the current active touch
|
|
self.primaryTouch = touch;
|
|
[self sendTouchEvent:touch withUIEvent:event withEvent:ACTION_DOWN];
|
|
}
|
|
[self sendTouchEvent:touch withUIEvent:event withEvent:ACTION_MOVE];
|
|
}
|
|
}
|
|
|
|
// For ACTION_UP and ACTION_CANCEL
|
|
- (void)touchesEndedGlobal:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
for (UITouch *touch in touches) {
|
|
if (@available(iOS 14.0, *)) { // 13.4
|
|
if (touch.type == UITouchTypeIndirectPointer) {
|
|
continue; // handle this in a different place
|
|
}
|
|
}
|
|
[self sendTouchEvent:touch withUIEvent:event withEvent:ACTION_UP];
|
|
}
|
|
}
|
|
|
|
// Equals to Android ACTION_UP
|
|
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
[super touchesEnded:touches withEvent:event];
|
|
[self touchesEndedGlobal:touches withEvent:event];
|
|
}
|
|
|
|
// Equals to Android ACTION_CANCEL
|
|
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
[super touchesCancelled:touches withEvent:event];
|
|
[self touchesEndedGlobal:touches withEvent:event];
|
|
}
|
|
|
|
@end
|