summaryrefslogtreecommitdiff
path: root/src/ios.m
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-03-07 08:15:57 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-03-07 08:15:57 +0200
commit4562609999fb880f66ee0eccef409d69a64a4ce0 (patch)
tree8fcdc34cbde9e837136f29259da4cdde7884bd5d /src/ios.m
parent5b80af0e983db5e3d09e207c9fce6790b53dea4d (diff)
iOS: Haptic tap when long-pressing
Diffstat (limited to 'src/ios.m')
-rw-r--r--src/ios.m123
1 files changed, 114 insertions, 9 deletions
diff --git a/src/ios.m b/src/ios.m
index 4d1aac37..75824117 100644
--- a/src/ios.m
+++ b/src/ios.m
@@ -22,14 +22,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "ios.h" 23#include "ios.h"
24#include "app.h" 24#include "app.h"
25#include "ui/command.h"
25#include "ui/window.h" 26#include "ui/window.h"
26#include <SDL_events.h> 27#include <SDL_events.h>
27#include <SDL_syswm.h> 28#include <SDL_syswm.h>
28 29
29#import <UIKit/UIKit.h> 30#import <UIKit/UIKit.h>
31#import <CoreHaptics/CoreHaptics.h>
30 32
31static iBool isSystemDarkMode_ = iFalse; 33static iBool isSystemDarkMode_ = iFalse;
32static iBool isPhone_ = iFalse; 34static iBool isPhone_ = iFalse;
33 35
34static UIViewController *viewController_(iWindow *window) { 36static UIViewController *viewController_(iWindow *window) {
35 SDL_SysWMinfo wm; 37 SDL_SysWMinfo wm;
@@ -41,11 +43,104 @@ static UIViewController *viewController_(iWindow *window) {
41 return NULL; 43 return NULL;
42} 44}
43 45
44@interface KeyboardObserver : NSObject 46/*----------------------------------------------------------------------------------------------*/
45-(void)keyboardOnScreen:(NSNotification *)notification; 47
48API_AVAILABLE(ios(13.0))
49@interface HapticState : NSObject
50@property (nonatomic, strong) CHHapticEngine *engine;
51@property (nonatomic, strong) NSDictionary *tapDef;
52@end
53
54@implementation HapticState
55
56-(void)setup {
57 NSError *error;
58 self.engine = [[CHHapticEngine alloc] initAndReturnError:&error];
59 __weak HapticState *hs = self;
60 [self.engine setResetHandler:^{
61 NSLog(@"Haptic engine reset");
62 NSError *startupError;
63 [hs.engine startAndReturnError:&startupError];
64 if (startupError) {
65 NSLog(@"Engine couldn't restart");
66 }
67 else {
68 // TODO: Create pattern players.
69 }
70 }];
71 [self.engine setStoppedHandler:^(CHHapticEngineStoppedReason reason){
72 NSLog(@"Haptic engine stopped");
73 switch (reason) {
74 case CHHapticEngineStoppedReasonAudioSessionInterrupt:
75 break;
76 case CHHapticEngineStoppedReasonApplicationSuspended:
77 break;
78 case CHHapticEngineStoppedReasonIdleTimeout:
79 break;
80 case CHHapticEngineStoppedReasonSystemError:
81 break;
82 default:
83 break;
84 }
85 }];
86 self.tapDef = @{
87 CHHapticPatternKeyPattern:
88 @[
89 @{
90 CHHapticPatternKeyEvent: @{
91 CHHapticPatternKeyEventType: CHHapticEventTypeHapticTransient,
92 CHHapticPatternKeyTime: @0.0,
93 CHHapticPatternKeyEventDuration:@0.1
94 },
95 },
96 ],
97 };
98}
99
100-(void)playTapEffect {
101 NSError *error = nil;
102 CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:self.tapDef
103 error:&error];
104 // TODO: Check the error.
105 id<CHHapticPatternPlayer> player = [self.engine createPlayerWithPattern:pattern error:&error];
106 // TODO: Check the error.
107 [self.engine startWithCompletionHandler:^(NSError *err){
108 if (err == nil) {
109 [self.engine notifyWhenPlayersFinished:^(NSError * _Nullable error) {
110 return CHHapticEngineFinishedActionStopEngine;
111 }];
112 NSError *startError = nil;
113 [player startAtTime:0.0 error:&startError];
114 }
115 }];
116}
117
118@end
119
120/*----------------------------------------------------------------------------------------------*/
121
122@interface AppState : NSObject
123@property (nonatomic, assign) BOOL isHapticsAvailable;
124@property (nonatomic, strong) NSObject *haptic;
46@end 125@end
47 126
48@implementation KeyboardObserver 127static AppState *appState_;
128
129@implementation AppState
130
131-(void)setupHaptics {
132 if (@available(iOS 13.0, *)) {
133 self.isHapticsAvailable = CHHapticEngine.capabilitiesForHardware.supportsHaptics;
134 if (self.isHapticsAvailable) {
135 HapticState *hs = [[HapticState alloc] init];
136 [hs setup];
137 self.haptic = hs;
138 }
139 } else {
140 self.isHapticsAvailable = NO;
141 }
142}
143
49-(void)keyboardOnScreen:(NSNotification *)notification { 144-(void)keyboardOnScreen:(NSNotification *)notification {
50 NSDictionary *info = notification.userInfo; 145 NSDictionary *info = notification.userInfo;
51 NSValue *value = info[UIKeyboardFrameEndUserInfoKey]; 146 NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
@@ -70,21 +165,20 @@ static void enableMouse_(iBool yes) {
70 SDL_EventState(SDL_MOUSEBUTTONUP, yes); 165 SDL_EventState(SDL_MOUSEBUTTONUP, yes);
71} 166}
72 167
73KeyboardObserver *keyObs_;
74
75void setupApplication_iOS(void) { 168void setupApplication_iOS(void) {
76 enableMouse_(iFalse); 169 enableMouse_(iFalse);
77 NSString *deviceModel = [[UIDevice currentDevice] model]; 170 NSString *deviceModel = [[UIDevice currentDevice] model];
78 if ([deviceModel isEqualToString:@"iPhone"]) { 171 if ([deviceModel isEqualToString:@"iPhone"]) {
79 isPhone_ = iTrue; 172 isPhone_ = iTrue;
80 } 173 }
81 keyObs_ = [[KeyboardObserver alloc] init]; 174 appState_ = [[AppState alloc] init];
175 [appState_ setupHaptics];
82 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 176 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
83 [center addObserver:keyObs_ 177 [center addObserver:appState_
84 selector:@selector(keyboardOnScreen:) 178 selector:@selector(keyboardOnScreen:)
85 name:UIKeyboardWillShowNotification 179 name:UIKeyboardWillShowNotification
86 object:nil]; 180 object:nil];
87 [center addObserver:keyObs_ 181 [center addObserver:appState_
88 selector:@selector(keyboardOffScreen:) 182 selector:@selector(keyboardOffScreen:)
89 name:UIKeyboardWillHideNotification 183 name:UIKeyboardWillHideNotification
90 object:nil]; 184 object:nil];
@@ -128,6 +222,17 @@ void setupWindow_iOS(iWindow *window) {
128 postCommandf_App("~os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0); 222 postCommandf_App("~os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0);
129} 223}
130 224
225void playHapticEffect_iOS(enum iHapticEffect effect) {
226 if (@available(iOS 13.0, *)) {
227 HapticState *hs = (HapticState *) appState_.haptic;
228 switch(effect) {
229 case tap_HapticEffect:
230 [hs playTapEffect];
231 break;
232 }
233 }
234}
235
131iBool processEvent_iOS(const SDL_Event *ev) { 236iBool processEvent_iOS(const SDL_Event *ev) {
132 if (ev->type == SDL_WINDOWEVENT) { 237 if (ev->type == SDL_WINDOWEVENT) {
133 if (ev->window.event == SDL_WINDOWEVENT_RESTORED) { 238 if (ev->window.event == SDL_WINDOWEVENT_RESTORED) {