The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2402 lines
78KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include "juce_CGMetalLayerRenderer_mac.h"
  19. #if TARGET_OS_SIMULATOR && JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
  20. #warning JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS uses parts of the Metal API that are currently unsupported in the simulator - falling back to JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS=0
  21. #undef JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
  22. #endif
  23. #if defined (__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
  24. #define JUCE_HAS_IOS_POINTER_SUPPORT 1
  25. #else
  26. #define JUCE_HAS_IOS_POINTER_SUPPORT 0
  27. #endif
  28. #if defined (__IPHONE_13_4) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_4
  29. #define JUCE_HAS_IOS_HARDWARE_KEYBOARD_SUPPORT 1
  30. #else
  31. #define JUCE_HAS_IOS_HARDWARE_KEYBOARD_SUPPORT 0
  32. #endif
  33. namespace juce
  34. {
  35. //==============================================================================
  36. static NSArray* getContainerAccessibilityElements (AccessibilityHandler& handler)
  37. {
  38. const auto children = handler.getChildren();
  39. NSMutableArray* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
  40. for (auto* childHandler : children)
  41. {
  42. id accessibleElement = [&childHandler]
  43. {
  44. id native = static_cast<id> (childHandler->getNativeImplementation());
  45. if (! childHandler->getChildren().empty())
  46. return [native accessibilityContainer];
  47. return native;
  48. }();
  49. if (accessibleElement != nil)
  50. [accessibleChildren addObject: accessibleElement];
  51. }
  52. [accessibleChildren addObject: static_cast<id> (handler.getNativeImplementation())];
  53. return accessibleChildren;
  54. }
  55. class UIViewComponentPeer;
  56. namespace iOSGlobals
  57. {
  58. class KeysCurrentlyDown
  59. {
  60. public:
  61. bool isDown (int x) const { return down.find (x) != down.cend(); }
  62. void setDown (int x, bool b)
  63. {
  64. if (b)
  65. down.insert (x);
  66. else
  67. down.erase (x);
  68. }
  69. private:
  70. std::set<int> down;
  71. };
  72. static KeysCurrentlyDown keysCurrentlyDown;
  73. static UIViewComponentPeer* currentlyFocusedPeer = nullptr;
  74. } // namespace iOSGlobals
  75. static UIInterfaceOrientation getWindowOrientation()
  76. {
  77. UIApplication* sharedApplication = [UIApplication sharedApplication];
  78. #if defined (__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
  79. if (@available (iOS 13.0, *))
  80. {
  81. for (UIScene* scene in [sharedApplication connectedScenes])
  82. if ([scene isKindOfClass: [UIWindowScene class]])
  83. return [(UIWindowScene*) scene interfaceOrientation];
  84. }
  85. #endif
  86. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  87. return [sharedApplication statusBarOrientation];
  88. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  89. }
  90. struct Orientations
  91. {
  92. static Desktop::DisplayOrientation convertToJuce (UIInterfaceOrientation orientation)
  93. {
  94. switch (orientation)
  95. {
  96. case UIInterfaceOrientationPortrait: return Desktop::upright;
  97. case UIInterfaceOrientationPortraitUpsideDown: return Desktop::upsideDown;
  98. case UIInterfaceOrientationLandscapeLeft: return Desktop::rotatedClockwise;
  99. case UIInterfaceOrientationLandscapeRight: return Desktop::rotatedAntiClockwise;
  100. case UIInterfaceOrientationUnknown:
  101. default: jassertfalse; // unknown orientation!
  102. }
  103. return Desktop::upright;
  104. }
  105. static UIInterfaceOrientation convertFromJuce (Desktop::DisplayOrientation orientation)
  106. {
  107. switch (orientation)
  108. {
  109. case Desktop::upright: return UIInterfaceOrientationPortrait;
  110. case Desktop::upsideDown: return UIInterfaceOrientationPortraitUpsideDown;
  111. case Desktop::rotatedClockwise: return UIInterfaceOrientationLandscapeLeft;
  112. case Desktop::rotatedAntiClockwise: return UIInterfaceOrientationLandscapeRight;
  113. case Desktop::allOrientations:
  114. default: jassertfalse; // unknown orientation!
  115. }
  116. return UIInterfaceOrientationPortrait;
  117. }
  118. static UIInterfaceOrientationMask getSupportedOrientations()
  119. {
  120. NSUInteger allowed = 0;
  121. auto& d = Desktop::getInstance();
  122. if (d.isOrientationEnabled (Desktop::upright)) allowed |= UIInterfaceOrientationMaskPortrait;
  123. if (d.isOrientationEnabled (Desktop::upsideDown)) allowed |= UIInterfaceOrientationMaskPortraitUpsideDown;
  124. if (d.isOrientationEnabled (Desktop::rotatedClockwise)) allowed |= UIInterfaceOrientationMaskLandscapeLeft;
  125. if (d.isOrientationEnabled (Desktop::rotatedAntiClockwise)) allowed |= UIInterfaceOrientationMaskLandscapeRight;
  126. return allowed;
  127. }
  128. };
  129. enum class MouseEventFlags
  130. {
  131. none,
  132. down,
  133. up,
  134. upAndCancel,
  135. };
  136. //==============================================================================
  137. } // namespace juce
  138. using namespace juce;
  139. @interface JuceUITextPosition : UITextPosition
  140. {
  141. @public
  142. int index;
  143. }
  144. @end
  145. @implementation JuceUITextPosition
  146. + (instancetype) withIndex: (int) indexIn
  147. {
  148. auto* result = [[JuceUITextPosition alloc] init];
  149. result->index = indexIn;
  150. return [result autorelease];
  151. }
  152. @end
  153. //==============================================================================
  154. @interface JuceUITextRange : UITextRange
  155. {
  156. @public
  157. int from, to;
  158. }
  159. @end
  160. @implementation JuceUITextRange
  161. + (instancetype) withRange: (juce::Range<int>) range
  162. {
  163. return [JuceUITextRange from: range.getStart() to: range.getEnd()];
  164. }
  165. + (instancetype) from: (int) from to: (int) to
  166. {
  167. auto* result = [[JuceUITextRange alloc] init];
  168. result->from = from;
  169. result->to = to;
  170. return [result autorelease];
  171. }
  172. - (UITextPosition*) start
  173. {
  174. return [JuceUITextPosition withIndex: from];
  175. }
  176. - (UITextPosition*) end
  177. {
  178. return [JuceUITextPosition withIndex: to];
  179. }
  180. - (Range<int>) range
  181. {
  182. return Range<int>::between (from, to);
  183. }
  184. - (BOOL) isEmpty
  185. {
  186. return from == to;
  187. }
  188. @end
  189. //==============================================================================
  190. // UITextInputStringTokenizer doesn't handle 'line' granularities correctly by default, hence
  191. // this subclass.
  192. @interface JuceTextInputTokenizer : UITextInputStringTokenizer
  193. {
  194. UIViewComponentPeer* peer;
  195. }
  196. - (instancetype) initWithPeer: (UIViewComponentPeer*) peer;
  197. @end
  198. //==============================================================================
  199. @interface JuceUITextSelectionRect : UITextSelectionRect
  200. {
  201. CGRect _rect;
  202. }
  203. @end
  204. @implementation JuceUITextSelectionRect
  205. + (instancetype) withRect: (CGRect) rect
  206. {
  207. auto* result = [[JuceUITextSelectionRect alloc] init];
  208. result->_rect = rect;
  209. return [result autorelease];
  210. }
  211. - (CGRect) rect { return _rect; }
  212. - (NSWritingDirection) writingDirection { return NSWritingDirectionNatural; }
  213. - (BOOL) containsStart { return NO; }
  214. - (BOOL) containsEnd { return NO; }
  215. - (BOOL) isVertical { return NO; }
  216. @end
  217. //==============================================================================
  218. struct CADisplayLinkDeleter
  219. {
  220. void operator() (CADisplayLink* displayLink) const noexcept
  221. {
  222. [displayLink invalidate];
  223. [displayLink release];
  224. }
  225. };
  226. @interface JuceTextView : UIView <UITextInput>
  227. {
  228. @public
  229. UIViewComponentPeer* owner;
  230. id<UITextInputDelegate> delegate;
  231. }
  232. - (instancetype) initWithOwner: (UIViewComponentPeer*) owner;
  233. @end
  234. @interface JuceUIView : UIView<CALayerDelegate>
  235. {
  236. @public
  237. UIViewComponentPeer* owner;
  238. std::unique_ptr<CADisplayLink, CADisplayLinkDeleter> displayLink;
  239. }
  240. - (JuceUIView*) initWithOwner: (UIViewComponentPeer*) owner withFrame: (CGRect) frame;
  241. - (void) dealloc;
  242. + (Class) layerClass;
  243. - (void) displayLinkCallback: (CADisplayLink*) dl;
  244. - (void) drawRect: (CGRect) r;
  245. - (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event;
  246. - (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event;
  247. - (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event;
  248. - (void) touchesCancelled: (NSSet*) touches withEvent: (UIEvent*) event;
  249. #if JUCE_HAS_IOS_POINTER_SUPPORT
  250. - (void) onHover: (UIHoverGestureRecognizer*) gesture API_AVAILABLE (ios (13.0));
  251. - (void) onScroll: (UIPanGestureRecognizer*) gesture;
  252. #endif
  253. - (BOOL) becomeFirstResponder;
  254. - (BOOL) resignFirstResponder;
  255. - (BOOL) canBecomeFirstResponder;
  256. - (void) traitCollectionDidChange: (UITraitCollection*) previousTraitCollection;
  257. - (BOOL) isAccessibilityElement;
  258. - (CGRect) accessibilityFrame;
  259. - (NSArray*) accessibilityElements;
  260. @end
  261. //==============================================================================
  262. @interface JuceUIViewController : UIViewController
  263. {
  264. }
  265. - (JuceUIViewController*) init;
  266. - (NSUInteger) supportedInterfaceOrientations;
  267. - (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation;
  268. - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration;
  269. - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation;
  270. - (void) viewWillTransitionToSize: (CGSize) size withTransitionCoordinator: (id<UIViewControllerTransitionCoordinator>) coordinator;
  271. - (BOOL) prefersStatusBarHidden;
  272. - (UIStatusBarStyle) preferredStatusBarStyle;
  273. - (void) viewDidLoad;
  274. - (void) viewWillAppear: (BOOL) animated;
  275. - (void) viewDidAppear: (BOOL) animated;
  276. - (void) viewWillLayoutSubviews;
  277. - (void) viewDidLayoutSubviews;
  278. @end
  279. //==============================================================================
  280. @interface JuceUIWindow : UIWindow
  281. {
  282. @private
  283. UIViewComponentPeer* owner;
  284. }
  285. - (void) setOwner: (UIViewComponentPeer*) owner;
  286. - (void) becomeKeyWindow;
  287. @end
  288. //==============================================================================
  289. //==============================================================================
  290. namespace juce
  291. {
  292. struct UIViewPeerControllerReceiver
  293. {
  294. virtual ~UIViewPeerControllerReceiver() = default;
  295. virtual void setViewController (UIViewController*) = 0;
  296. };
  297. //==============================================================================
  298. class UIViewComponentPeer : public ComponentPeer,
  299. public UIViewPeerControllerReceiver
  300. {
  301. public:
  302. UIViewComponentPeer (Component&, int windowStyleFlags, UIView* viewToAttachTo);
  303. ~UIViewComponentPeer() override;
  304. //==============================================================================
  305. void* getNativeHandle() const override { return view; }
  306. void setVisible (bool shouldBeVisible) override;
  307. void setTitle (const String& title) override;
  308. void setBounds (const Rectangle<int>&, bool isNowFullScreen) override;
  309. void setViewController (UIViewController* newController) override
  310. {
  311. jassert (controller == nullptr);
  312. controller = [newController retain];
  313. }
  314. Rectangle<int> getBounds() const override { return getBounds (! isSharedWindow); }
  315. Rectangle<int> getBounds (bool global) const;
  316. Point<float> localToGlobal (Point<float> relativePosition) override;
  317. Point<float> globalToLocal (Point<float> screenPosition) override;
  318. using ComponentPeer::localToGlobal;
  319. using ComponentPeer::globalToLocal;
  320. void setAlpha (float newAlpha) override;
  321. void setMinimised (bool) override {}
  322. bool isMinimised() const override { return false; }
  323. void setFullScreen (bool shouldBeFullScreen) override;
  324. bool isFullScreen() const override { return fullScreen; }
  325. bool contains (Point<int> localPos, bool trueIfInAChildWindow) const override;
  326. OptionalBorderSize getFrameSizeIfPresent() const override { return {}; }
  327. BorderSize<int> getFrameSize() const override { return BorderSize<int>(); }
  328. bool setAlwaysOnTop (bool alwaysOnTop) override;
  329. void toFront (bool makeActiveWindow) override;
  330. void toBehind (ComponentPeer* other) override;
  331. void setIcon (const Image& newIcon) override;
  332. StringArray getAvailableRenderingEngines() override { return StringArray ("CoreGraphics Renderer"); }
  333. void displayLinkCallback();
  334. void drawRect (CGRect);
  335. void drawRectWithContext (CGContextRef, CGRect);
  336. bool canBecomeKeyWindow();
  337. //==============================================================================
  338. void viewFocusGain();
  339. void viewFocusLoss();
  340. bool isFocused() const override;
  341. void grabFocus() override;
  342. void textInputRequired (Point<int>, TextInputTarget&) override;
  343. void dismissPendingTextInput() override;
  344. void closeInputMethodContext() override;
  345. void updateScreenBounds();
  346. void handleTouches (UIEvent*, MouseEventFlags);
  347. #if JUCE_HAS_IOS_POINTER_SUPPORT
  348. API_AVAILABLE (ios (13.0)) void onHover (UIHoverGestureRecognizer*);
  349. void onScroll (UIPanGestureRecognizer*);
  350. #endif
  351. Range<int> getMarkedTextRange() const
  352. {
  353. return Range<int>::withStartAndLength (startOfMarkedTextInTextInputTarget,
  354. stringBeingComposed.length());
  355. }
  356. enum class UnderlineRegion
  357. {
  358. none,
  359. underCompositionRange
  360. };
  361. void replaceMarkedRangeWithText (TextInputTarget* target,
  362. const String& text,
  363. UnderlineRegion underline)
  364. {
  365. if (stringBeingComposed.isNotEmpty())
  366. target->setHighlightedRegion (getMarkedTextRange());
  367. target->insertTextAtCaret (text);
  368. const auto underlineRanges = underline == UnderlineRegion::underCompositionRange
  369. ? Array { Range<int>::withStartAndLength (startOfMarkedTextInTextInputTarget,
  370. text.length()) }
  371. : Array<Range<int>>{};
  372. target->setTemporaryUnderlining (underlineRanges);
  373. stringBeingComposed = text;
  374. }
  375. //==============================================================================
  376. void repaint (const Rectangle<int>& area) override;
  377. void performAnyPendingRepaintsNow() override;
  378. //==============================================================================
  379. UIWindow* window = nil;
  380. JuceUIView* view = nil;
  381. UIViewController* controller = nil;
  382. const bool isSharedWindow, isAppex;
  383. String stringBeingComposed;
  384. int startOfMarkedTextInTextInputTarget = 0;
  385. bool fullScreen = false, insideDrawRect = false;
  386. NSUniquePtr<JuceTextView> hiddenTextInput { [[JuceTextView alloc] initWithOwner: this] };
  387. NSUniquePtr<JuceTextInputTokenizer> tokenizer { [[JuceTextInputTokenizer alloc] initWithPeer: this] };
  388. static int64 getMouseTime (NSTimeInterval timestamp) noexcept
  389. {
  390. return (Time::currentTimeMillis() - Time::getMillisecondCounter())
  391. + (int64) (timestamp * 1000.0);
  392. }
  393. static int64 getMouseTime (UIEvent* e) noexcept
  394. {
  395. return getMouseTime ([e timestamp]);
  396. }
  397. static NSString* getDarkModeNotificationName()
  398. {
  399. return @"ViewDarkModeChanged";
  400. }
  401. static MultiTouchMapper<UITouch*> currentTouches;
  402. static UIKeyboardType getUIKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept
  403. {
  404. switch (type)
  405. {
  406. case TextInputTarget::textKeyboard: return UIKeyboardTypeDefault;
  407. case TextInputTarget::numericKeyboard: return UIKeyboardTypeNumbersAndPunctuation;
  408. case TextInputTarget::decimalKeyboard: return UIKeyboardTypeNumbersAndPunctuation;
  409. case TextInputTarget::urlKeyboard: return UIKeyboardTypeURL;
  410. case TextInputTarget::emailAddressKeyboard: return UIKeyboardTypeEmailAddress;
  411. case TextInputTarget::phoneNumberKeyboard: return UIKeyboardTypePhonePad;
  412. case TextInputTarget::passwordKeyboard: return UIKeyboardTypeASCIICapable;
  413. }
  414. jassertfalse;
  415. return UIKeyboardTypeDefault;
  416. }
  417. #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
  418. std::unique_ptr<CoreGraphicsMetalLayerRenderer> metalRenderer;
  419. #endif
  420. RectangleList<float> deferredRepaints;
  421. private:
  422. void appStyleChanged() override
  423. {
  424. [controller setNeedsStatusBarAppearanceUpdate];
  425. }
  426. //==============================================================================
  427. class AsyncRepaintMessage : public CallbackMessage
  428. {
  429. public:
  430. UIViewComponentPeer* const peer;
  431. const Rectangle<int> rect;
  432. AsyncRepaintMessage (UIViewComponentPeer* const p, const Rectangle<int>& r)
  433. : peer (p), rect (r)
  434. {
  435. }
  436. void messageCallback() override
  437. {
  438. if (ComponentPeer::isValidPeer (peer))
  439. peer->repaint (rect);
  440. }
  441. };
  442. //==============================================================================
  443. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer)
  444. };
  445. static UIViewComponentPeer* getViewPeer (JuceUIViewController* c)
  446. {
  447. if (JuceUIView* juceView = (JuceUIView*) [c view])
  448. return juceView->owner;
  449. jassertfalse;
  450. return nullptr;
  451. }
  452. static void sendScreenBoundsUpdate (JuceUIViewController* c)
  453. {
  454. if (auto* peer = getViewPeer (c))
  455. peer->updateScreenBounds();
  456. }
  457. static bool isKioskModeView (JuceUIViewController* c)
  458. {
  459. if (auto* peer = getViewPeer (c))
  460. return Desktop::getInstance().getKioskModeComponent() == &(peer->getComponent());
  461. return false;
  462. }
  463. MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches;
  464. } // namespace juce
  465. //==============================================================================
  466. //==============================================================================
  467. @implementation JuceUIViewController
  468. - (JuceUIViewController*) init
  469. {
  470. self = [super init];
  471. return self;
  472. }
  473. - (NSUInteger) supportedInterfaceOrientations
  474. {
  475. return Orientations::getSupportedOrientations();
  476. }
  477. - (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation
  478. {
  479. return Desktop::getInstance().isOrientationEnabled (Orientations::convertToJuce (interfaceOrientation));
  480. }
  481. - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation
  482. duration: (NSTimeInterval) duration
  483. {
  484. ignoreUnused (toInterfaceOrientation, duration);
  485. [UIView setAnimationsEnabled: NO]; // disable this because it goes the wrong way and looks like crap.
  486. }
  487. - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation
  488. {
  489. ignoreUnused (fromInterfaceOrientation);
  490. sendScreenBoundsUpdate (self);
  491. [UIView setAnimationsEnabled: YES];
  492. }
  493. - (void) viewWillTransitionToSize: (CGSize) size withTransitionCoordinator: (id<UIViewControllerTransitionCoordinator>) coordinator
  494. {
  495. [super viewWillTransitionToSize: size withTransitionCoordinator: coordinator];
  496. [coordinator animateAlongsideTransition: nil completion: ^void (id<UIViewControllerTransitionCoordinatorContext>)
  497. {
  498. sendScreenBoundsUpdate (self);
  499. }];
  500. }
  501. - (BOOL) prefersStatusBarHidden
  502. {
  503. if (isKioskModeView (self))
  504. return true;
  505. return [[[NSBundle mainBundle] objectForInfoDictionaryKey: @"UIStatusBarHidden"] boolValue];
  506. }
  507. - (BOOL) prefersHomeIndicatorAutoHidden
  508. {
  509. return isKioskModeView (self);
  510. }
  511. - (UIStatusBarStyle) preferredStatusBarStyle
  512. {
  513. #if defined (__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
  514. if (@available (iOS 13.0, *))
  515. {
  516. if (auto* peer = getViewPeer (self))
  517. {
  518. switch (peer->getAppStyle())
  519. {
  520. case ComponentPeer::Style::automatic:
  521. return UIStatusBarStyleDefault;
  522. case ComponentPeer::Style::light:
  523. return UIStatusBarStyleDarkContent;
  524. case ComponentPeer::Style::dark:
  525. return UIStatusBarStyleLightContent;
  526. }
  527. }
  528. }
  529. #endif
  530. return UIStatusBarStyleDefault;
  531. }
  532. - (void) viewDidLoad
  533. {
  534. sendScreenBoundsUpdate (self);
  535. [super viewDidLoad];
  536. }
  537. - (void) viewWillAppear: (BOOL) animated
  538. {
  539. sendScreenBoundsUpdate (self);
  540. [super viewWillAppear:animated];
  541. }
  542. - (void) viewDidAppear: (BOOL) animated
  543. {
  544. sendScreenBoundsUpdate (self);
  545. [super viewDidAppear:animated];
  546. }
  547. - (void) viewWillLayoutSubviews
  548. {
  549. sendScreenBoundsUpdate (self);
  550. }
  551. - (void) viewDidLayoutSubviews
  552. {
  553. sendScreenBoundsUpdate (self);
  554. }
  555. @end
  556. @implementation JuceUIView
  557. - (JuceUIView*) initWithOwner: (UIViewComponentPeer*) peer
  558. withFrame: (CGRect) frame
  559. {
  560. [super initWithFrame: frame];
  561. owner = peer;
  562. #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
  563. if (@available (iOS 13.0, *))
  564. {
  565. auto* layer = (CAMetalLayer*) [self layer];
  566. layer.device = MTLCreateSystemDefaultDevice();
  567. layer.framebufferOnly = NO;
  568. layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
  569. if (owner != nullptr)
  570. layer.opaque = owner->getComponent().isOpaque();
  571. layer.presentsWithTransaction = YES;
  572. layer.needsDisplayOnBoundsChange = true;
  573. layer.presentsWithTransaction = true;
  574. layer.delegate = self;
  575. layer.allowsNextDrawableTimeout = NO;
  576. }
  577. #endif
  578. displayLink.reset ([CADisplayLink displayLinkWithTarget: self
  579. selector: @selector (displayLinkCallback:)]);
  580. [displayLink.get() addToRunLoop: [NSRunLoop mainRunLoop]
  581. forMode: NSDefaultRunLoopMode];
  582. [self addSubview: owner->hiddenTextInput.get()];
  583. #if JUCE_HAS_IOS_POINTER_SUPPORT
  584. if (@available (iOS 13.4, *))
  585. {
  586. auto hoverRecognizer = [[[UIHoverGestureRecognizer alloc] initWithTarget: self action: @selector (onHover:)] autorelease];
  587. [hoverRecognizer setCancelsTouchesInView: NO];
  588. [hoverRecognizer setRequiresExclusiveTouchType: YES];
  589. [self addGestureRecognizer: hoverRecognizer];
  590. auto panRecognizer = [[[UIPanGestureRecognizer alloc] initWithTarget: self action: @selector (onScroll:)] autorelease];
  591. [panRecognizer setCancelsTouchesInView: NO];
  592. [panRecognizer setRequiresExclusiveTouchType: YES];
  593. [panRecognizer setAllowedScrollTypesMask: UIScrollTypeMaskAll];
  594. [panRecognizer setMaximumNumberOfTouches: 0];
  595. [self addGestureRecognizer: panRecognizer];
  596. }
  597. #endif
  598. return self;
  599. }
  600. - (void) dealloc
  601. {
  602. [owner->hiddenTextInput.get() removeFromSuperview];
  603. displayLink = nullptr;
  604. [super dealloc];
  605. }
  606. //==============================================================================
  607. + (Class) layerClass
  608. {
  609. #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
  610. if (@available (iOS 13, *))
  611. return [CAMetalLayer class];
  612. #endif
  613. return [CALayer class];
  614. }
  615. - (void) displayLinkCallback: (CADisplayLink*) dl
  616. {
  617. if (owner != nullptr)
  618. owner->displayLinkCallback();
  619. }
  620. #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
  621. - (CALayer*) makeBackingLayer
  622. {
  623. auto* layer = [CAMetalLayer layer];
  624. layer.device = MTLCreateSystemDefaultDevice();
  625. layer.framebufferOnly = NO;
  626. layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
  627. if (owner != nullptr)
  628. layer.opaque = owner->getComponent().isOpaque();
  629. layer.presentsWithTransaction = YES;
  630. layer.needsDisplayOnBoundsChange = true;
  631. layer.presentsWithTransaction = true;
  632. layer.delegate = self;
  633. layer.allowsNextDrawableTimeout = NO;
  634. return layer;
  635. }
  636. - (void) displayLayer: (CALayer*) layer
  637. {
  638. if (owner != nullptr)
  639. {
  640. owner->deferredRepaints = owner->metalRenderer->drawRectangleList (static_cast<CAMetalLayer*> (layer),
  641. (float) [self contentScaleFactor],
  642. [self] (auto&&... args) { owner->drawRectWithContext (args...); },
  643. std::move (owner->deferredRepaints),
  644. false);
  645. }
  646. }
  647. #endif
  648. //==============================================================================
  649. - (void) drawRect: (CGRect) r
  650. {
  651. if (owner != nullptr)
  652. owner->drawRect (r);
  653. }
  654. //==============================================================================
  655. - (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
  656. {
  657. ignoreUnused (touches);
  658. if (owner != nullptr)
  659. owner->handleTouches (event, MouseEventFlags::down);
  660. }
  661. - (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event
  662. {
  663. ignoreUnused (touches);
  664. if (owner != nullptr)
  665. owner->handleTouches (event, MouseEventFlags::none);
  666. }
  667. - (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event
  668. {
  669. ignoreUnused (touches);
  670. if (owner != nullptr)
  671. owner->handleTouches (event, MouseEventFlags::up);
  672. }
  673. - (void) touchesCancelled: (NSSet*) touches withEvent: (UIEvent*) event
  674. {
  675. if (owner != nullptr)
  676. owner->handleTouches (event, MouseEventFlags::upAndCancel);
  677. [self touchesEnded: touches withEvent: event];
  678. }
  679. #if JUCE_HAS_IOS_POINTER_SUPPORT
  680. - (void) onHover: (UIHoverGestureRecognizer*) gesture
  681. {
  682. if (owner != nullptr)
  683. owner->onHover (gesture);
  684. }
  685. - (void) onScroll: (UIPanGestureRecognizer*) gesture
  686. {
  687. if (owner != nullptr)
  688. owner->onScroll (gesture);
  689. }
  690. #endif
  691. static std::optional<int> getKeyCodeForSpecialCharacterString (StringRef characters)
  692. {
  693. static const auto map = [&]
  694. {
  695. std::map<String, int> result
  696. {
  697. { nsStringToJuce (UIKeyInputUpArrow), KeyPress::upKey },
  698. { nsStringToJuce (UIKeyInputDownArrow), KeyPress::downKey },
  699. { nsStringToJuce (UIKeyInputLeftArrow), KeyPress::leftKey },
  700. { nsStringToJuce (UIKeyInputRightArrow), KeyPress::rightKey },
  701. { nsStringToJuce (UIKeyInputEscape), KeyPress::escapeKey },
  702. #if JUCE_HAS_IOS_HARDWARE_KEYBOARD_SUPPORT
  703. // These symbols are available on iOS 8, but only declared in the headers for iOS 13.4+
  704. { nsStringToJuce (UIKeyInputPageUp), KeyPress::pageUpKey },
  705. { nsStringToJuce (UIKeyInputPageDown), KeyPress::pageDownKey },
  706. #endif
  707. };
  708. #if JUCE_HAS_IOS_HARDWARE_KEYBOARD_SUPPORT
  709. if (@available (iOS 13.4, *))
  710. {
  711. result.insert ({ { nsStringToJuce (UIKeyInputHome), KeyPress::homeKey },
  712. { nsStringToJuce (UIKeyInputEnd), KeyPress::endKey },
  713. { nsStringToJuce (UIKeyInputF1), KeyPress::F1Key },
  714. { nsStringToJuce (UIKeyInputF2), KeyPress::F2Key },
  715. { nsStringToJuce (UIKeyInputF3), KeyPress::F3Key },
  716. { nsStringToJuce (UIKeyInputF4), KeyPress::F4Key },
  717. { nsStringToJuce (UIKeyInputF5), KeyPress::F5Key },
  718. { nsStringToJuce (UIKeyInputF6), KeyPress::F6Key },
  719. { nsStringToJuce (UIKeyInputF7), KeyPress::F7Key },
  720. { nsStringToJuce (UIKeyInputF8), KeyPress::F8Key },
  721. { nsStringToJuce (UIKeyInputF9), KeyPress::F9Key },
  722. { nsStringToJuce (UIKeyInputF10), KeyPress::F10Key },
  723. { nsStringToJuce (UIKeyInputF11), KeyPress::F11Key },
  724. { nsStringToJuce (UIKeyInputF12), KeyPress::F12Key } });
  725. }
  726. #endif
  727. #if defined (__IPHONE_15_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0
  728. if (@available (iOS 15.0, *))
  729. {
  730. result.insert ({ { nsStringToJuce (UIKeyInputDelete), KeyPress::deleteKey } });
  731. }
  732. #endif
  733. return result;
  734. }();
  735. const auto iter = map.find (characters);
  736. return iter != map.cend() ? std::make_optional (iter->second) : std::nullopt;
  737. }
  738. static int getKeyCodeForCharacters (StringRef unmodified)
  739. {
  740. return getKeyCodeForSpecialCharacterString (unmodified).value_or (unmodified[0]);
  741. }
  742. static int getKeyCodeForCharacters (NSString* characters)
  743. {
  744. return getKeyCodeForCharacters (nsStringToJuce (characters));
  745. }
  746. #if JUCE_HAS_IOS_HARDWARE_KEYBOARD_SUPPORT
  747. static void updateModifiers (const UIKeyModifierFlags flags)
  748. {
  749. const auto convert = [&flags] (UIKeyModifierFlags f, int result) { return (flags & f) != 0 ? result : 0; };
  750. const auto juceFlags = convert (UIKeyModifierAlphaShift, 0) // capslock modifier currently not implemented
  751. | convert (UIKeyModifierShift, ModifierKeys::shiftModifier)
  752. | convert (UIKeyModifierControl, ModifierKeys::ctrlModifier)
  753. | convert (UIKeyModifierAlternate, ModifierKeys::altModifier)
  754. | convert (UIKeyModifierCommand, ModifierKeys::commandModifier)
  755. | convert (UIKeyModifierNumericPad, 0); // numpad modifier currently not implemented
  756. ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withOnlyMouseButtons().withFlags (juceFlags);
  757. }
  758. API_AVAILABLE (ios(13.4))
  759. static int getKeyCodeForKey (UIKey* key)
  760. {
  761. return getKeyCodeForCharacters ([key charactersIgnoringModifiers]);
  762. }
  763. API_AVAILABLE (ios(13.4))
  764. static bool attemptToConsumeKeys (JuceUIView* view, NSSet<UIPress*>* presses)
  765. {
  766. auto used = false;
  767. for (UIPress* press in presses)
  768. {
  769. if (auto* key = [press key])
  770. {
  771. const auto code = getKeyCodeForKey (key);
  772. const auto handleCodepoint = [view, &used, code] (juce_wchar codepoint)
  773. {
  774. // These both need to fire; no short-circuiting!
  775. used |= view->owner->handleKeyUpOrDown (true);
  776. used |= view->owner->handleKeyPress (code, codepoint);
  777. };
  778. if (getKeyCodeForSpecialCharacterString (nsStringToJuce ([key charactersIgnoringModifiers])).has_value())
  779. handleCodepoint (0);
  780. else
  781. for (const auto codepoint : nsStringToJuce ([key characters]))
  782. handleCodepoint (codepoint);
  783. }
  784. }
  785. return used;
  786. }
  787. - (void) pressesBegan:(NSSet<UIPress*>*) presses withEvent:(UIPressesEvent*) event
  788. {
  789. const auto handledEvent = [&]
  790. {
  791. if (@available (iOS 13.4, *))
  792. {
  793. auto isEscape = false;
  794. updateModifiers ([event modifierFlags]);
  795. for (UIPress* press in presses)
  796. {
  797. if (auto* key = [press key])
  798. {
  799. const auto code = getKeyCodeForKey (key);
  800. isEscape |= code == KeyPress::escapeKey;
  801. iOSGlobals::keysCurrentlyDown.setDown (code, true);
  802. }
  803. }
  804. return ((isEscape && owner->stringBeingComposed.isEmpty())
  805. || owner->findCurrentTextInputTarget() == nullptr)
  806. && attemptToConsumeKeys (self, presses);
  807. }
  808. return false;
  809. }();
  810. if (! handledEvent)
  811. [super pressesBegan: presses withEvent: event];
  812. }
  813. /* Returns true if we handled the event. */
  814. static bool doKeysUp (UIViewComponentPeer* owner, NSSet<UIPress*>* presses, UIPressesEvent* event)
  815. {
  816. if (@available (iOS 13.4, *))
  817. {
  818. updateModifiers ([event modifierFlags]);
  819. for (UIPress* press in presses)
  820. if (auto* key = [press key])
  821. iOSGlobals::keysCurrentlyDown.setDown (getKeyCodeForKey (key), false);
  822. return owner->findCurrentTextInputTarget() == nullptr && owner->handleKeyUpOrDown (false);
  823. }
  824. return false;
  825. }
  826. - (void) pressesEnded:(NSSet<UIPress*>*) presses withEvent:(UIPressesEvent*) event
  827. {
  828. if (! doKeysUp (owner, presses, event))
  829. [super pressesEnded: presses withEvent: event];
  830. }
  831. - (void) pressesCancelled:(NSSet<UIPress*>*) presses withEvent:(UIPressesEvent*) event
  832. {
  833. if (! doKeysUp (owner, presses, event))
  834. [super pressesCancelled: presses withEvent: event];
  835. }
  836. #endif
  837. //==============================================================================
  838. - (BOOL) becomeFirstResponder
  839. {
  840. if (owner != nullptr)
  841. owner->viewFocusGain();
  842. return true;
  843. }
  844. - (BOOL) resignFirstResponder
  845. {
  846. if (owner != nullptr)
  847. owner->viewFocusLoss();
  848. return [super resignFirstResponder];
  849. }
  850. - (BOOL) canBecomeFirstResponder
  851. {
  852. return owner != nullptr && owner->canBecomeKeyWindow();
  853. }
  854. - (void) traitCollectionDidChange: (UITraitCollection*) previousTraitCollection
  855. {
  856. [super traitCollectionDidChange: previousTraitCollection];
  857. if (@available (iOS 12.0, *))
  858. {
  859. const auto wasDarkModeActive = ([previousTraitCollection userInterfaceStyle] == UIUserInterfaceStyleDark);
  860. if (wasDarkModeActive != Desktop::getInstance().isDarkModeActive())
  861. [[NSNotificationCenter defaultCenter] postNotificationName: UIViewComponentPeer::getDarkModeNotificationName()
  862. object: nil];
  863. }
  864. }
  865. - (BOOL) isAccessibilityElement
  866. {
  867. return NO;
  868. }
  869. - (CGRect) accessibilityFrame
  870. {
  871. if (owner != nullptr)
  872. if (auto* handler = owner->getComponent().getAccessibilityHandler())
  873. return convertToCGRect (handler->getComponent().getScreenBounds());
  874. return CGRectZero;
  875. }
  876. - (NSArray*) accessibilityElements
  877. {
  878. if (owner != nullptr)
  879. if (auto* handler = owner->getComponent().getAccessibilityHandler())
  880. return getContainerAccessibilityElements (*handler);
  881. return nil;
  882. }
  883. @end
  884. //==============================================================================
  885. @implementation JuceUIWindow
  886. - (void) setOwner: (UIViewComponentPeer*) peer
  887. {
  888. owner = peer;
  889. }
  890. - (void) becomeKeyWindow
  891. {
  892. [super becomeKeyWindow];
  893. if (owner != nullptr)
  894. owner->grabFocus();
  895. }
  896. @end
  897. /** see https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/LowerLevelText-HandlingTechnologies/LowerLevelText-HandlingTechnologies.html */
  898. @implementation JuceTextView
  899. - (TextInputTarget*) getTextInputTarget
  900. {
  901. if (owner != nullptr)
  902. return owner->findCurrentTextInputTarget();
  903. return nullptr;
  904. }
  905. - (instancetype) initWithOwner: (UIViewComponentPeer*) ownerIn
  906. {
  907. [super init];
  908. owner = ownerIn;
  909. delegate = nil;
  910. // The frame must have a finite size, otherwise some accessibility events will be ignored
  911. self.frame = CGRectMake (0.0, 0.0, 1.0, 1.0);
  912. return self;
  913. }
  914. - (BOOL) canPerformAction: (SEL) action withSender: (id) sender
  915. {
  916. if (auto* target = [self getTextInputTarget])
  917. {
  918. if (action == @selector (paste:))
  919. {
  920. if (@available (iOS 10, *))
  921. return [[UIPasteboard generalPasteboard] hasStrings];
  922. return [[UIPasteboard generalPasteboard] string] != nil;
  923. }
  924. }
  925. return [super canPerformAction: action withSender: sender];
  926. }
  927. - (void) cut: (id) sender
  928. {
  929. [self copy: sender];
  930. if (auto* target = [self getTextInputTarget])
  931. {
  932. if (delegate != nil)
  933. [delegate textWillChange: self];
  934. target->insertTextAtCaret ("");
  935. if (delegate != nil)
  936. [delegate textDidChange: self];
  937. }
  938. }
  939. - (void) copy: (id) sender
  940. {
  941. if (auto* target = [self getTextInputTarget])
  942. [[UIPasteboard generalPasteboard] setString: juceStringToNS (target->getTextInRange (target->getHighlightedRegion()))];
  943. }
  944. - (void) paste: (id) sender
  945. {
  946. if (auto* target = [self getTextInputTarget])
  947. {
  948. if (auto* string = [[UIPasteboard generalPasteboard] string])
  949. {
  950. if (delegate != nil)
  951. [delegate textWillChange: self];
  952. target->insertTextAtCaret (nsStringToJuce (string));
  953. if (delegate != nil)
  954. [delegate textDidChange: self];
  955. }
  956. }
  957. }
  958. - (void) selectAll: (id) sender
  959. {
  960. if (auto* target = [self getTextInputTarget])
  961. target->setHighlightedRegion ({ 0, target->getTotalNumChars() });
  962. }
  963. - (void) deleteBackward
  964. {
  965. auto* target = [self getTextInputTarget];
  966. if (target == nullptr)
  967. return;
  968. const auto range = target->getHighlightedRegion();
  969. const auto rangeToDelete = range.isEmpty() ? range.withStartAndLength (jmax (range.getStart() - 1, 0),
  970. range.getStart() != 0 ? 1 : 0)
  971. : range;
  972. const auto start = rangeToDelete.getStart();
  973. // This ensures that the cursor is at the beginning, rather than the end, of the selection
  974. target->setHighlightedRegion ({ start, start });
  975. target->setHighlightedRegion (rangeToDelete);
  976. target->insertTextAtCaret ("");
  977. }
  978. - (void) insertText: (NSString*) text
  979. {
  980. if (owner == nullptr)
  981. return;
  982. if (auto* target = owner->findCurrentTextInputTarget())
  983. {
  984. // If we're in insertText, it's because there's a focused TextInputTarget,
  985. // and key presses from pressesBegan and pressesEnded have been composed
  986. // into a string that is now ready for insertion.
  987. // Because JUCE has been passing key events to the system for composition, it
  988. // won't have been processing those key presses itself, so it may not have had
  989. // a chance to process keys like return/tab/etc.
  990. // It's not possible to filter out these keys during pressesBegan, because they
  991. // may form part of a longer composition sequence.
  992. // e.g. when entering Japanese text, the return key may be used to select an option
  993. // from the IME menu, and in this situation the return key should not be propagated
  994. // to the JUCE view.
  995. // If we receive a special character (return/tab/etc.) in insertText, it can
  996. // only be because the composition has finished, so we can turn the event into
  997. // a KeyPress and trust the current TextInputTarget to process it correctly.
  998. const auto redirectKeyPresses = [&] (juce_wchar codepoint)
  999. {
  1000. target->setTemporaryUnderlining ({});
  1001. // Simulate a key down
  1002. const auto code = getKeyCodeForCharacters (String::charToString (codepoint));
  1003. iOSGlobals::keysCurrentlyDown.setDown (code, true);
  1004. owner->handleKeyUpOrDown (true);
  1005. owner->handleKeyPress (code, codepoint);
  1006. // Simulate a key up
  1007. iOSGlobals::keysCurrentlyDown.setDown (code, false);
  1008. owner->handleKeyUpOrDown (false);
  1009. };
  1010. using UR = UIViewComponentPeer::UnderlineRegion;
  1011. if ([text isEqual: @"\n"] || [text isEqual: @"\r"])
  1012. redirectKeyPresses ('\r');
  1013. else if ([text isEqual: @"\t"])
  1014. redirectKeyPresses ('\t');
  1015. else
  1016. owner->replaceMarkedRangeWithText (target, nsStringToJuce (text), UR::none);
  1017. }
  1018. owner->stringBeingComposed.clear();
  1019. owner->startOfMarkedTextInTextInputTarget = 0;
  1020. }
  1021. - (BOOL) hasText
  1022. {
  1023. if (auto* target = [self getTextInputTarget])
  1024. return target->getTextInRange ({ 0, 1 }).isNotEmpty();
  1025. return NO;
  1026. }
  1027. - (BOOL) accessibilityElementsHidden
  1028. {
  1029. return NO;
  1030. }
  1031. - (UITextRange*) selectedTextRangeForTarget: (TextInputTarget*) target
  1032. {
  1033. if (target != nullptr)
  1034. return [JuceUITextRange withRange: target->getHighlightedRegion()];
  1035. return nil;
  1036. }
  1037. - (UITextRange*) selectedTextRange
  1038. {
  1039. return [self selectedTextRangeForTarget: [self getTextInputTarget]];
  1040. }
  1041. - (void) setSelectedTextRange: (JuceUITextRange*) range
  1042. {
  1043. if (auto* target = [self getTextInputTarget])
  1044. target->setHighlightedRegion (range != nil ? [range range] : Range<int>());
  1045. }
  1046. - (UITextRange*) markedTextRange
  1047. {
  1048. if (owner != nullptr && owner->stringBeingComposed.isNotEmpty())
  1049. if (auto* target = owner->findCurrentTextInputTarget())
  1050. return [JuceUITextRange withRange: owner->getMarkedTextRange()];
  1051. return nil;
  1052. }
  1053. - (void) setMarkedText: (NSString*) markedText
  1054. selectedRange: (NSRange) selectedRange
  1055. {
  1056. if (owner == nullptr)
  1057. return;
  1058. const auto newMarkedText = nsStringToJuce (markedText);
  1059. const ScopeGuard scope { [&] { owner->stringBeingComposed = newMarkedText; } };
  1060. auto* target = owner->findCurrentTextInputTarget();
  1061. if (target == nullptr)
  1062. return;
  1063. if (owner->stringBeingComposed.isEmpty())
  1064. owner->startOfMarkedTextInTextInputTarget = target->getHighlightedRegion().getStart();
  1065. using UR = UIViewComponentPeer::UnderlineRegion;
  1066. owner->replaceMarkedRangeWithText (target, newMarkedText, UR::underCompositionRange);
  1067. const auto newSelection = nsRangeToJuce (selectedRange) + owner->startOfMarkedTextInTextInputTarget;
  1068. target->setHighlightedRegion (newSelection);
  1069. }
  1070. - (void) unmarkText
  1071. {
  1072. if (owner == nullptr)
  1073. return;
  1074. auto* target = owner->findCurrentTextInputTarget();
  1075. if (target == nullptr)
  1076. return;
  1077. using UR = UIViewComponentPeer::UnderlineRegion;
  1078. owner->replaceMarkedRangeWithText (target, owner->stringBeingComposed, UR::none);
  1079. owner->stringBeingComposed.clear();
  1080. owner->startOfMarkedTextInTextInputTarget = 0;
  1081. }
  1082. - (NSDictionary<NSAttributedStringKey, id>*) markedTextStyle
  1083. {
  1084. return nil;
  1085. }
  1086. - (void) setMarkedTextStyle: (NSDictionary<NSAttributedStringKey, id>*) dict
  1087. {
  1088. }
  1089. - (UITextPosition*) beginningOfDocument
  1090. {
  1091. return [JuceUITextPosition withIndex: 0];
  1092. }
  1093. - (UITextPosition*) endOfDocument
  1094. {
  1095. if (auto* target = [self getTextInputTarget])
  1096. return [JuceUITextPosition withIndex: target->getTotalNumChars()];
  1097. return [JuceUITextPosition withIndex: 0];
  1098. }
  1099. - (id<UITextInputTokenizer>) tokenizer
  1100. {
  1101. return owner->tokenizer.get();
  1102. }
  1103. - (NSWritingDirection) baseWritingDirectionForPosition: (UITextPosition*) position
  1104. inDirection: (UITextStorageDirection) direction
  1105. {
  1106. return NSWritingDirectionNatural;
  1107. }
  1108. - (CGRect) caretRectForPosition: (JuceUITextPosition*) position
  1109. {
  1110. if (position == nil)
  1111. return CGRectZero;
  1112. // Currently we ignore the requested position and just return the text editor's caret position
  1113. if (auto* target = [self getTextInputTarget])
  1114. {
  1115. if (auto* comp = dynamic_cast<Component*> (target))
  1116. {
  1117. const auto areaOnDesktop = comp->localAreaToGlobal (target->getCaretRectangle());
  1118. return convertToCGRect (detail::ScalingHelpers::scaledScreenPosToUnscaled (areaOnDesktop));
  1119. }
  1120. }
  1121. return CGRectZero;
  1122. }
  1123. - (UITextRange*) characterRangeByExtendingPosition: (JuceUITextPosition*) position
  1124. inDirection: (UITextLayoutDirection) direction
  1125. {
  1126. const auto newPosition = [self indexFromPosition: position inDirection: direction offset: 1];
  1127. return [JuceUITextRange from: position->index to: newPosition];
  1128. }
  1129. - (int) closestIndexToPoint: (CGPoint) point
  1130. {
  1131. if (auto* target = [self getTextInputTarget])
  1132. {
  1133. if (auto* comp = dynamic_cast<Component*> (target))
  1134. {
  1135. const auto pointOnDesktop = detail::ScalingHelpers::unscaledScreenPosToScaled (convertToPointFloat (point));
  1136. return target->getCharIndexForPoint (comp->getLocalPoint (nullptr, pointOnDesktop).roundToInt());
  1137. }
  1138. }
  1139. return -1;
  1140. }
  1141. - (UITextRange*) characterRangeAtPoint: (CGPoint) point
  1142. {
  1143. const auto index = [self closestIndexToPoint: point];
  1144. const auto result = index != -1 ? [JuceUITextRange from: index to: index] : nil;
  1145. jassert (result != nullptr);
  1146. return result;
  1147. }
  1148. - (UITextPosition*) closestPositionToPoint: (CGPoint) point
  1149. {
  1150. const auto index = [self closestIndexToPoint: point];
  1151. const auto result = index != -1 ? [JuceUITextPosition withIndex: index] : nil;
  1152. jassert (result != nullptr);
  1153. return result;
  1154. }
  1155. - (UITextPosition*) closestPositionToPoint: (CGPoint) point
  1156. withinRange: (JuceUITextRange*) range
  1157. {
  1158. const auto index = [self closestIndexToPoint: point];
  1159. const auto result = index != -1 ? [JuceUITextPosition withIndex: [range range].clipValue (index)] : nil;
  1160. jassert (result != nullptr);
  1161. return result;
  1162. }
  1163. - (NSComparisonResult) comparePosition: (JuceUITextPosition*) position
  1164. toPosition: (JuceUITextPosition*) other
  1165. {
  1166. const auto a = position != nil ? makeOptional (position->index) : nullopt;
  1167. const auto b = other != nil ? makeOptional (other ->index) : nullopt;
  1168. if (a < b)
  1169. return NSOrderedAscending;
  1170. if (b < a)
  1171. return NSOrderedDescending;
  1172. return NSOrderedSame;
  1173. }
  1174. - (NSInteger) offsetFromPosition: (JuceUITextPosition*) from
  1175. toPosition: (JuceUITextPosition*) toPosition
  1176. {
  1177. if (from != nil && toPosition != nil)
  1178. return toPosition->index - from->index;
  1179. return 0;
  1180. }
  1181. - (int) indexFromPosition: (JuceUITextPosition*) position
  1182. inDirection: (UITextLayoutDirection) direction
  1183. offset: (NSInteger) offset
  1184. {
  1185. switch (direction)
  1186. {
  1187. case UITextLayoutDirectionLeft:
  1188. case UITextLayoutDirectionRight:
  1189. return position->index + (int) (offset * (direction == UITextLayoutDirectionLeft ? -1 : 1));
  1190. case UITextLayoutDirectionUp:
  1191. case UITextLayoutDirectionDown:
  1192. {
  1193. if (auto* target = [self getTextInputTarget])
  1194. {
  1195. const auto originalRectangle = target->getCaretRectangleForCharIndex (position->index);
  1196. auto testIndex = position->index;
  1197. for (auto lineOffset = 0; lineOffset < offset; ++lineOffset)
  1198. {
  1199. const auto caretRect = target->getCaretRectangleForCharIndex (testIndex);
  1200. const auto newY = direction == UITextLayoutDirectionUp ? caretRect.getY() - 1
  1201. : caretRect.getBottom() + 1;
  1202. testIndex = target->getCharIndexForPoint ({ originalRectangle.getX(), newY });
  1203. }
  1204. return testIndex;
  1205. }
  1206. }
  1207. }
  1208. return position->index;
  1209. }
  1210. - (UITextPosition*) positionFromPosition: (JuceUITextPosition*) position
  1211. inDirection: (UITextLayoutDirection) direction
  1212. offset: (NSInteger) offset
  1213. {
  1214. return [JuceUITextPosition withIndex: [self indexFromPosition: position inDirection: direction offset: offset]];
  1215. }
  1216. - (UITextPosition*) positionFromPosition: (JuceUITextPosition*) position
  1217. offset: (NSInteger) offset
  1218. {
  1219. if (position != nil)
  1220. {
  1221. if (auto* target = [self getTextInputTarget])
  1222. {
  1223. const auto newIndex = position->index + (int) offset;
  1224. if (isPositiveAndBelow (newIndex, target->getTotalNumChars() + 1))
  1225. return [JuceUITextPosition withIndex: newIndex];
  1226. }
  1227. }
  1228. return nil;
  1229. }
  1230. - (CGRect) firstRectForRange: (JuceUITextRange*) range
  1231. {
  1232. if (auto* target = [self getTextInputTarget])
  1233. {
  1234. if (auto* comp = dynamic_cast<Component*> (target))
  1235. {
  1236. const auto list = target->getTextBounds ([range range]);
  1237. if (! list.isEmpty())
  1238. {
  1239. const auto areaOnDesktop = comp->localAreaToGlobal (list.getRectangle (0));
  1240. return convertToCGRect (detail::ScalingHelpers::scaledScreenPosToUnscaled (areaOnDesktop));
  1241. }
  1242. }
  1243. }
  1244. return {};
  1245. }
  1246. - (NSArray<UITextSelectionRect*>*) selectionRectsForRange: (JuceUITextRange*) range
  1247. {
  1248. if (auto* target = [self getTextInputTarget])
  1249. {
  1250. if (auto* comp = dynamic_cast<Component*> (target))
  1251. {
  1252. const auto list = target->getTextBounds ([range range]);
  1253. auto* result = [NSMutableArray arrayWithCapacity: (NSUInteger) list.getNumRectangles()];
  1254. for (const auto& rect : list)
  1255. {
  1256. const auto areaOnDesktop = comp->localAreaToGlobal (rect);
  1257. const auto nativeArea = convertToCGRect (detail::ScalingHelpers::scaledScreenPosToUnscaled (areaOnDesktop));
  1258. [result addObject: [JuceUITextSelectionRect withRect: nativeArea]];
  1259. }
  1260. return result;
  1261. }
  1262. }
  1263. return nil;
  1264. }
  1265. - (UITextPosition*) positionWithinRange: (JuceUITextRange*) range
  1266. farthestInDirection: (UITextLayoutDirection) direction
  1267. {
  1268. return direction == UITextLayoutDirectionUp || direction == UITextLayoutDirectionLeft
  1269. ? [range start]
  1270. : [range end];
  1271. }
  1272. - (void) replaceRange: (JuceUITextRange*) range
  1273. withText: (NSString*) text
  1274. {
  1275. if (owner == nullptr)
  1276. return;
  1277. owner->stringBeingComposed.clear();
  1278. if (auto* target = owner->findCurrentTextInputTarget())
  1279. {
  1280. target->setHighlightedRegion ([range range]);
  1281. target->insertTextAtCaret (nsStringToJuce (text));
  1282. }
  1283. }
  1284. - (void) setBaseWritingDirection: (NSWritingDirection) writingDirection
  1285. forRange: (UITextRange*) range
  1286. {
  1287. }
  1288. - (NSString*) textInRange: (JuceUITextRange*) range
  1289. {
  1290. if (auto* target = [self getTextInputTarget])
  1291. return juceStringToNS (target->getTextInRange ([range range]));
  1292. return nil;
  1293. }
  1294. - (UITextRange*) textRangeFromPosition: (JuceUITextPosition*) fromPosition
  1295. toPosition: (JuceUITextPosition*) toPosition
  1296. {
  1297. const auto from = fromPosition != nil ? fromPosition->index : 0;
  1298. const auto to = toPosition != nil ? toPosition ->index : 0;
  1299. return [JuceUITextRange withRange: Range<int>::between (from, to)];
  1300. }
  1301. - (void) setInputDelegate: (id<UITextInputDelegate>) delegateIn
  1302. {
  1303. delegate = delegateIn;
  1304. }
  1305. - (id<UITextInputDelegate>) inputDelegate
  1306. {
  1307. return delegate;
  1308. }
  1309. - (UIKeyboardType) keyboardType
  1310. {
  1311. if (auto* target = [self getTextInputTarget])
  1312. return UIViewComponentPeer::getUIKeyboardType (target->getKeyboardType());
  1313. return UIKeyboardTypeDefault;
  1314. }
  1315. - (UITextAutocapitalizationType) autocapitalizationType
  1316. {
  1317. return UITextAutocapitalizationTypeNone;
  1318. }
  1319. - (UITextAutocorrectionType) autocorrectionType
  1320. {
  1321. return UITextAutocorrectionTypeNo;
  1322. }
  1323. - (UITextSpellCheckingType) spellCheckingType
  1324. {
  1325. return UITextSpellCheckingTypeNo;
  1326. }
  1327. - (BOOL) canBecomeFirstResponder
  1328. {
  1329. return YES;
  1330. }
  1331. @end
  1332. //==============================================================================
  1333. @implementation JuceTextInputTokenizer
  1334. - (instancetype) initWithPeer: (UIViewComponentPeer*) peerIn
  1335. {
  1336. [super initWithTextInput: peerIn->hiddenTextInput.get()];
  1337. peer = peerIn;
  1338. return self;
  1339. }
  1340. - (UITextRange*) rangeEnclosingPosition: (JuceUITextPosition*) position
  1341. withGranularity: (UITextGranularity) granularity
  1342. inDirection: (UITextDirection) direction
  1343. {
  1344. if (granularity != UITextGranularityLine)
  1345. return [super rangeEnclosingPosition: position withGranularity: granularity inDirection: direction];
  1346. auto* target = peer->findCurrentTextInputTarget();
  1347. if (target == nullptr)
  1348. return nullptr;
  1349. const auto numChars = target->getTotalNumChars();
  1350. if (! isPositiveAndBelow (position->index, numChars))
  1351. return nullptr;
  1352. const auto allText = target->getTextInRange ({ 0, numChars });
  1353. const auto begin = AccessibilityTextHelpers::makeCharPtrIteratorAdapter (allText.begin());
  1354. const auto end = AccessibilityTextHelpers::makeCharPtrIteratorAdapter (allText.end());
  1355. const auto positionIter = begin + position->index;
  1356. const auto nextNewlineIter = std::find (positionIter, end, '\n');
  1357. const auto lastNewlineIter = std::find (std::make_reverse_iterator (positionIter),
  1358. std::make_reverse_iterator (begin),
  1359. '\n').base();
  1360. const auto from = std::distance (begin, lastNewlineIter);
  1361. const auto to = std::distance (begin, nextNewlineIter);
  1362. return [JuceUITextRange from: from to: to];
  1363. }
  1364. @end
  1365. //==============================================================================
  1366. //==============================================================================
  1367. namespace juce
  1368. {
  1369. bool KeyPress::isKeyCurrentlyDown (int keyCode)
  1370. {
  1371. #if JUCE_HAS_IOS_HARDWARE_KEYBOARD_SUPPORT
  1372. return iOSGlobals::keysCurrentlyDown.isDown (keyCode)
  1373. || ('A' <= keyCode && keyCode <= 'Z' && iOSGlobals::keysCurrentlyDown.isDown ((int) CharacterFunctions::toLowerCase ((juce_wchar) keyCode)))
  1374. || ('a' <= keyCode && keyCode <= 'z' && iOSGlobals::keysCurrentlyDown.isDown ((int) CharacterFunctions::toUpperCase ((juce_wchar) keyCode)));
  1375. #else
  1376. return false;
  1377. #endif
  1378. }
  1379. Point<float> juce_lastMousePos;
  1380. //==============================================================================
  1381. UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags, UIView* viewToAttachTo)
  1382. : ComponentPeer (comp, windowStyleFlags),
  1383. isSharedWindow (viewToAttachTo != nil),
  1384. isAppex (SystemStats::isRunningInAppExtensionSandbox())
  1385. {
  1386. CGRect r = convertToCGRect (component.getBounds());
  1387. view = [[JuceUIView alloc] initWithOwner: this withFrame: r];
  1388. view.multipleTouchEnabled = YES;
  1389. view.hidden = true;
  1390. view.opaque = component.isOpaque();
  1391. view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0];
  1392. #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
  1393. if (@available (iOS 13, *))
  1394. {
  1395. metalRenderer = CoreGraphicsMetalLayerRenderer::create();
  1396. jassert (metalRenderer != nullptr);
  1397. }
  1398. #endif
  1399. if ((windowStyleFlags & ComponentPeer::windowRequiresSynchronousCoreGraphicsRendering) == 0)
  1400. [[view layer] setDrawsAsynchronously: YES];
  1401. if (isSharedWindow)
  1402. {
  1403. window = [viewToAttachTo window];
  1404. [viewToAttachTo addSubview: view];
  1405. }
  1406. else
  1407. {
  1408. r = convertToCGRect (component.getBounds());
  1409. r.origin.y = [UIScreen mainScreen].bounds.size.height - (r.origin.y + r.size.height);
  1410. window = [[JuceUIWindow alloc] initWithFrame: r];
  1411. [((JuceUIWindow*) window) setOwner: this];
  1412. controller = [[JuceUIViewController alloc] init];
  1413. controller.view = view;
  1414. window.rootViewController = controller;
  1415. window.hidden = true;
  1416. window.opaque = component.isOpaque();
  1417. window.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0];
  1418. if (component.isAlwaysOnTop())
  1419. window.windowLevel = UIWindowLevelAlert;
  1420. view.frame = CGRectMake (0, 0, r.size.width, r.size.height);
  1421. }
  1422. setTitle (component.getName());
  1423. setVisible (component.isVisible());
  1424. }
  1425. UIViewComponentPeer::~UIViewComponentPeer()
  1426. {
  1427. if (iOSGlobals::currentlyFocusedPeer == this)
  1428. iOSGlobals::currentlyFocusedPeer = nullptr;
  1429. currentTouches.deleteAllTouchesForPeer (this);
  1430. view->owner = nullptr;
  1431. [view removeFromSuperview];
  1432. [view release];
  1433. [controller release];
  1434. if (! isSharedWindow)
  1435. {
  1436. [((JuceUIWindow*) window) setOwner: nil];
  1437. #if defined (__IPHONE_13_0)
  1438. if (@available (iOS 13.0, *))
  1439. window.windowScene = nil;
  1440. #endif
  1441. [window release];
  1442. }
  1443. }
  1444. //==============================================================================
  1445. void UIViewComponentPeer::setVisible (bool shouldBeVisible)
  1446. {
  1447. if (! isSharedWindow)
  1448. window.hidden = ! shouldBeVisible;
  1449. view.hidden = ! shouldBeVisible;
  1450. }
  1451. void UIViewComponentPeer::setTitle (const String&)
  1452. {
  1453. // xxx is this possible?
  1454. }
  1455. void UIViewComponentPeer::setBounds (const Rectangle<int>& newBounds, const bool isNowFullScreen)
  1456. {
  1457. fullScreen = isNowFullScreen;
  1458. if (isSharedWindow)
  1459. {
  1460. CGRect r = convertToCGRect (newBounds);
  1461. if (! approximatelyEqual (view.frame.size.width, r.size.width)
  1462. || ! approximatelyEqual (view.frame.size.height, r.size.height))
  1463. {
  1464. [view setNeedsDisplay];
  1465. }
  1466. view.frame = r;
  1467. }
  1468. else
  1469. {
  1470. window.frame = convertToCGRect (newBounds);
  1471. view.frame = CGRectMake (0, 0, (CGFloat) newBounds.getWidth(), (CGFloat) newBounds.getHeight());
  1472. handleMovedOrResized();
  1473. }
  1474. }
  1475. Rectangle<int> UIViewComponentPeer::getBounds (const bool global) const
  1476. {
  1477. auto r = view.frame;
  1478. if (global)
  1479. {
  1480. if (view.window != nil)
  1481. {
  1482. r = [view convertRect: r toView: view.window];
  1483. r = [view.window convertRect: r toWindow: nil];
  1484. }
  1485. else if (window != nil)
  1486. {
  1487. r.origin.x += window.frame.origin.x;
  1488. r.origin.y += window.frame.origin.y;
  1489. }
  1490. }
  1491. return convertToRectInt (r);
  1492. }
  1493. Point<float> UIViewComponentPeer::localToGlobal (Point<float> relativePosition)
  1494. {
  1495. return relativePosition + getBounds (true).getPosition().toFloat();
  1496. }
  1497. Point<float> UIViewComponentPeer::globalToLocal (Point<float> screenPosition)
  1498. {
  1499. return screenPosition - getBounds (true).getPosition().toFloat();
  1500. }
  1501. void UIViewComponentPeer::setAlpha (float newAlpha)
  1502. {
  1503. [view.window setAlpha: (CGFloat) newAlpha];
  1504. }
  1505. void UIViewComponentPeer::setFullScreen (bool shouldBeFullScreen)
  1506. {
  1507. if (! isSharedWindow)
  1508. {
  1509. auto r = shouldBeFullScreen ? Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea
  1510. : lastNonFullscreenBounds;
  1511. if ((! shouldBeFullScreen) && r.isEmpty())
  1512. r = getBounds();
  1513. // (can't call the component's setBounds method because that'll reset our fullscreen flag)
  1514. if (! r.isEmpty())
  1515. setBounds (detail::ScalingHelpers::scaledScreenPosToUnscaled (component, r), shouldBeFullScreen);
  1516. component.repaint();
  1517. }
  1518. }
  1519. void UIViewComponentPeer::updateScreenBounds()
  1520. {
  1521. auto& desktop = Desktop::getInstance();
  1522. auto oldArea = component.getBounds();
  1523. auto oldDesktop = desktop.getDisplays().getPrimaryDisplay()->userArea;
  1524. forceDisplayUpdate();
  1525. if (fullScreen)
  1526. {
  1527. fullScreen = false;
  1528. setFullScreen (true);
  1529. }
  1530. else if (! isSharedWindow)
  1531. {
  1532. auto newDesktop = desktop.getDisplays().getPrimaryDisplay()->userArea;
  1533. if (newDesktop != oldDesktop)
  1534. {
  1535. // this will re-centre the window, but leave its size unchanged
  1536. auto centreRelX = oldArea.getCentreX() / (float) oldDesktop.getWidth();
  1537. auto centreRelY = oldArea.getCentreY() / (float) oldDesktop.getHeight();
  1538. auto x = ((int) (newDesktop.getWidth() * centreRelX)) - (oldArea.getWidth() / 2);
  1539. auto y = ((int) (newDesktop.getHeight() * centreRelY)) - (oldArea.getHeight() / 2);
  1540. component.setBounds (oldArea.withPosition (x, y));
  1541. }
  1542. }
  1543. [view setNeedsDisplay];
  1544. }
  1545. bool UIViewComponentPeer::contains (Point<int> localPos, bool trueIfInAChildWindow) const
  1546. {
  1547. if (! detail::ScalingHelpers::scaledScreenPosToUnscaled (component, component.getLocalBounds()).contains (localPos))
  1548. return false;
  1549. UIView* v = [view hitTest: convertToCGPoint (localPos)
  1550. withEvent: nil];
  1551. if (trueIfInAChildWindow)
  1552. return v != nil;
  1553. return v == view;
  1554. }
  1555. bool UIViewComponentPeer::setAlwaysOnTop (bool alwaysOnTop)
  1556. {
  1557. if (! isSharedWindow)
  1558. window.windowLevel = alwaysOnTop ? UIWindowLevelAlert : UIWindowLevelNormal;
  1559. return true;
  1560. }
  1561. void UIViewComponentPeer::toFront (bool makeActiveWindow)
  1562. {
  1563. if (isSharedWindow)
  1564. [[view superview] bringSubviewToFront: view];
  1565. if (makeActiveWindow && window != nil && component.isVisible())
  1566. [window makeKeyAndVisible];
  1567. }
  1568. void UIViewComponentPeer::toBehind (ComponentPeer* other)
  1569. {
  1570. if (auto* otherPeer = dynamic_cast<UIViewComponentPeer*> (other))
  1571. {
  1572. if (isSharedWindow)
  1573. [[view superview] insertSubview: view belowSubview: otherPeer->view];
  1574. }
  1575. else
  1576. {
  1577. jassertfalse; // wrong type of window?
  1578. }
  1579. }
  1580. void UIViewComponentPeer::setIcon (const Image& /*newIcon*/)
  1581. {
  1582. // to do..
  1583. }
  1584. //==============================================================================
  1585. static float getMaximumTouchForce (UITouch* touch) noexcept
  1586. {
  1587. if ([touch respondsToSelector: @selector (maximumPossibleForce)])
  1588. return (float) touch.maximumPossibleForce;
  1589. return 0.0f;
  1590. }
  1591. static float getTouchForce (UITouch* touch) noexcept
  1592. {
  1593. if ([touch respondsToSelector: @selector (force)])
  1594. return (float) touch.force;
  1595. return 0.0f;
  1596. }
  1597. void UIViewComponentPeer::handleTouches (UIEvent* event, MouseEventFlags mouseEventFlags)
  1598. {
  1599. if (event == nullptr)
  1600. return;
  1601. #if JUCE_HAS_IOS_HARDWARE_KEYBOARD_SUPPORT
  1602. if (@available (iOS 13.4, *))
  1603. {
  1604. updateModifiers ([event modifierFlags]);
  1605. }
  1606. #endif
  1607. NSArray* touches = [[event touchesForView: view] allObjects];
  1608. for (unsigned int i = 0; i < [touches count]; ++i)
  1609. {
  1610. UITouch* touch = [touches objectAtIndex: i];
  1611. auto maximumForce = getMaximumTouchForce (touch);
  1612. if ([touch phase] == UITouchPhaseStationary && maximumForce <= 0)
  1613. continue;
  1614. auto pos = convertToPointFloat ([touch locationInView: view]);
  1615. juce_lastMousePos = pos + getBounds (true).getPosition().toFloat();
  1616. auto time = getMouseTime (event);
  1617. auto touchIndex = currentTouches.getIndexOfTouch (this, touch);
  1618. auto modsToSend = ModifierKeys::currentModifiers;
  1619. auto isUp = [] (MouseEventFlags m)
  1620. {
  1621. return m == MouseEventFlags::up || m == MouseEventFlags::upAndCancel;
  1622. };
  1623. if (mouseEventFlags == MouseEventFlags::down)
  1624. {
  1625. if ([touch phase] != UITouchPhaseBegan)
  1626. continue;
  1627. ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
  1628. modsToSend = ModifierKeys::currentModifiers;
  1629. // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
  1630. handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, modsToSend.withoutMouseButtons(),
  1631. MouseInputSource::defaultPressure, MouseInputSource::defaultOrientation, time, {}, touchIndex);
  1632. if (! isValidPeer (this)) // (in case this component was deleted by the event)
  1633. return;
  1634. }
  1635. else if (isUp (mouseEventFlags))
  1636. {
  1637. if (! ([touch phase] == UITouchPhaseEnded || [touch phase] == UITouchPhaseCancelled))
  1638. continue;
  1639. modsToSend = modsToSend.withoutMouseButtons();
  1640. currentTouches.clearTouch (touchIndex);
  1641. if (! currentTouches.areAnyTouchesActive())
  1642. mouseEventFlags = MouseEventFlags::upAndCancel;
  1643. }
  1644. if (mouseEventFlags == MouseEventFlags::upAndCancel)
  1645. {
  1646. currentTouches.clearTouch (touchIndex);
  1647. modsToSend = ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons();
  1648. }
  1649. // NB: some devices return 0 or 1.0 if pressure is unknown, so we'll clip our value to a believable range:
  1650. auto pressure = maximumForce > 0 ? jlimit (0.0001f, 0.9999f, getTouchForce (touch) / maximumForce)
  1651. : MouseInputSource::defaultPressure;
  1652. handleMouseEvent (MouseInputSource::InputSourceType::touch,
  1653. pos, modsToSend, pressure, MouseInputSource::defaultOrientation, time, { }, touchIndex);
  1654. if (! isValidPeer (this)) // (in case this component was deleted by the event)
  1655. return;
  1656. if (isUp (mouseEventFlags))
  1657. {
  1658. handleMouseEvent (MouseInputSource::InputSourceType::touch, MouseInputSource::offscreenMousePos, modsToSend,
  1659. MouseInputSource::defaultPressure, MouseInputSource::defaultOrientation, time, {}, touchIndex);
  1660. if (! isValidPeer (this))
  1661. return;
  1662. }
  1663. }
  1664. }
  1665. #if JUCE_HAS_IOS_POINTER_SUPPORT
  1666. void UIViewComponentPeer::onHover (UIHoverGestureRecognizer* gesture)
  1667. {
  1668. auto pos = convertToPointFloat ([gesture locationInView: view]);
  1669. juce_lastMousePos = pos + getBounds (true).getPosition().toFloat();
  1670. handleMouseEvent (MouseInputSource::InputSourceType::touch,
  1671. pos,
  1672. ModifierKeys::currentModifiers,
  1673. MouseInputSource::defaultPressure, MouseInputSource::defaultOrientation,
  1674. UIViewComponentPeer::getMouseTime ([[NSProcessInfo processInfo] systemUptime]),
  1675. {});
  1676. }
  1677. void UIViewComponentPeer::onScroll (UIPanGestureRecognizer* gesture)
  1678. {
  1679. const auto offset = [gesture translationInView: view];
  1680. const auto scale = 0.5f / 256.0f;
  1681. MouseWheelDetails details;
  1682. details.deltaX = scale * (float) offset.x;
  1683. details.deltaY = scale * (float) offset.y;
  1684. details.isReversed = false;
  1685. details.isSmooth = true;
  1686. details.isInertial = false;
  1687. const auto reconstructedMousePosition = convertToPointFloat ([gesture locationInView: view]) - convertToPointFloat (offset);
  1688. handleMouseWheel (MouseInputSource::InputSourceType::touch,
  1689. reconstructedMousePosition,
  1690. UIViewComponentPeer::getMouseTime ([[NSProcessInfo processInfo] systemUptime]),
  1691. details);
  1692. }
  1693. #endif
  1694. //==============================================================================
  1695. void UIViewComponentPeer::viewFocusGain()
  1696. {
  1697. if (iOSGlobals::currentlyFocusedPeer != this)
  1698. {
  1699. if (ComponentPeer::isValidPeer (iOSGlobals::currentlyFocusedPeer))
  1700. iOSGlobals::currentlyFocusedPeer->handleFocusLoss();
  1701. iOSGlobals::currentlyFocusedPeer = this;
  1702. handleFocusGain();
  1703. }
  1704. }
  1705. void UIViewComponentPeer::viewFocusLoss()
  1706. {
  1707. if (iOSGlobals::currentlyFocusedPeer == this)
  1708. {
  1709. iOSGlobals::currentlyFocusedPeer = nullptr;
  1710. handleFocusLoss();
  1711. }
  1712. }
  1713. bool UIViewComponentPeer::isFocused() const
  1714. {
  1715. if (isAppex)
  1716. return true;
  1717. return isSharedWindow ? this == iOSGlobals::currentlyFocusedPeer
  1718. : (window != nil && [window isKeyWindow]);
  1719. }
  1720. void UIViewComponentPeer::grabFocus()
  1721. {
  1722. if (window != nil)
  1723. {
  1724. [window makeKeyWindow];
  1725. viewFocusGain();
  1726. }
  1727. }
  1728. void UIViewComponentPeer::textInputRequired (Point<int>, TextInputTarget&)
  1729. {
  1730. // We need to restart the text input session so that the keyboard can change types if necessary.
  1731. if ([hiddenTextInput.get() isFirstResponder])
  1732. [hiddenTextInput.get() resignFirstResponder];
  1733. [hiddenTextInput.get() becomeFirstResponder];
  1734. }
  1735. void UIViewComponentPeer::closeInputMethodContext()
  1736. {
  1737. if (auto* input = hiddenTextInput.get())
  1738. {
  1739. if (auto* delegate = [input inputDelegate])
  1740. {
  1741. [delegate selectionWillChange: input];
  1742. [delegate selectionDidChange: input];
  1743. }
  1744. }
  1745. }
  1746. void UIViewComponentPeer::dismissPendingTextInput()
  1747. {
  1748. closeInputMethodContext();
  1749. [hiddenTextInput.get() resignFirstResponder];
  1750. }
  1751. //==============================================================================
  1752. void UIViewComponentPeer::displayLinkCallback()
  1753. {
  1754. vBlankListeners.call ([] (auto& l) { l.onVBlank(); });
  1755. if (deferredRepaints.isEmpty())
  1756. return;
  1757. for (const auto& r : deferredRepaints)
  1758. [view setNeedsDisplayInRect: convertToCGRect (r)];
  1759. }
  1760. //==============================================================================
  1761. void UIViewComponentPeer::drawRect (CGRect r)
  1762. {
  1763. if (r.size.width < 1.0f || r.size.height < 1.0f)
  1764. return;
  1765. drawRectWithContext (UIGraphicsGetCurrentContext(), r);
  1766. }
  1767. void UIViewComponentPeer::drawRectWithContext (CGContextRef cg, CGRect)
  1768. {
  1769. if (! component.isOpaque())
  1770. CGContextClearRect (cg, CGContextGetClipBoundingBox (cg));
  1771. CGContextConcatCTM (cg, CGAffineTransformMake (1, 0, 0, -1, 0, getComponent().getHeight()));
  1772. CoreGraphicsContext g (cg, getComponent().getHeight());
  1773. insideDrawRect = true;
  1774. handlePaint (g);
  1775. insideDrawRect = false;
  1776. }
  1777. bool UIViewComponentPeer::canBecomeKeyWindow()
  1778. {
  1779. return (getStyleFlags() & juce::ComponentPeer::windowIgnoresKeyPresses) == 0;
  1780. }
  1781. //==============================================================================
  1782. void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, bool /*allowMenusAndBars*/)
  1783. {
  1784. displays->refresh();
  1785. if (auto* peer = kioskModeComp->getPeer())
  1786. {
  1787. if (auto* uiViewPeer = dynamic_cast<UIViewComponentPeer*> (peer))
  1788. [uiViewPeer->controller setNeedsStatusBarAppearanceUpdate];
  1789. peer->setFullScreen (enableOrDisable);
  1790. }
  1791. }
  1792. void Desktop::allowedOrientationsChanged()
  1793. {
  1794. #if defined (__IPHONE_16_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0
  1795. if (@available (iOS 16.0, *))
  1796. {
  1797. UIApplication* sharedApplication = [UIApplication sharedApplication];
  1798. const NSUniquePtr<UIWindowSceneGeometryPreferencesIOS> preferences { [UIWindowSceneGeometryPreferencesIOS alloc] };
  1799. [preferences.get() initWithInterfaceOrientations: Orientations::getSupportedOrientations()];
  1800. for (UIScene* scene in [sharedApplication connectedScenes])
  1801. {
  1802. if ([scene isKindOfClass: [UIWindowScene class]])
  1803. {
  1804. auto* windowScene = static_cast<UIWindowScene*> (scene);
  1805. for (UIWindow* window in [windowScene windows])
  1806. if (auto* vc = [window rootViewController])
  1807. [vc setNeedsUpdateOfSupportedInterfaceOrientations];
  1808. [windowScene requestGeometryUpdateWithPreferences: preferences.get()
  1809. errorHandler: ^([[maybe_unused]] NSError* error)
  1810. {
  1811. // Failed to set the new set of supported orientations.
  1812. // You may have hit this assertion because you're trying to restrict the supported orientations
  1813. // of an app that allows multitasking (i.e. the app does not require fullscreen, and supports
  1814. // all orientations).
  1815. // iPadOS apps that allow multitasking must support all interface orientations,
  1816. // so attempting to change the set of supported orientations will fail.
  1817. // If you hit this assertion in an application that requires fullscreen, it may be because the
  1818. // set of supported orientations declared in the app's plist doesn't have any entries in common
  1819. // with the orientations passed to Desktop::setOrientationsEnabled.
  1820. DBG (nsStringToJuce ([error localizedDescription]));
  1821. jassertfalse;
  1822. }];
  1823. }
  1824. }
  1825. return;
  1826. }
  1827. #endif
  1828. // if the current orientation isn't allowed anymore then switch orientations
  1829. if (! isOrientationEnabled (getCurrentOrientation()))
  1830. {
  1831. auto newOrientation = [this]
  1832. {
  1833. for (auto orientation : { upright, upsideDown, rotatedClockwise, rotatedAntiClockwise })
  1834. if (isOrientationEnabled (orientation))
  1835. return orientation;
  1836. // you need to support at least one orientation
  1837. jassertfalse;
  1838. return upright;
  1839. }();
  1840. NSNumber* value = [NSNumber numberWithInt: (int) Orientations::convertFromJuce (newOrientation)];
  1841. [[UIDevice currentDevice] setValue:value forKey:@"orientation"];
  1842. [value release];
  1843. }
  1844. }
  1845. //==============================================================================
  1846. void UIViewComponentPeer::repaint (const Rectangle<int>& area)
  1847. {
  1848. if (insideDrawRect || ! MessageManager::getInstance()->isThisTheMessageThread())
  1849. {
  1850. (new AsyncRepaintMessage (this, area))->post();
  1851. return;
  1852. }
  1853. deferredRepaints.add (area.toFloat());
  1854. }
  1855. void UIViewComponentPeer::performAnyPendingRepaintsNow()
  1856. {
  1857. }
  1858. ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo)
  1859. {
  1860. return new UIViewComponentPeer (*this, styleFlags, (UIView*) windowToAttachTo);
  1861. }
  1862. //==============================================================================
  1863. const int KeyPress::spaceKey = ' ';
  1864. const int KeyPress::returnKey = 0x0d;
  1865. const int KeyPress::escapeKey = 0x1b;
  1866. const int KeyPress::backspaceKey = 0x7f;
  1867. const int KeyPress::leftKey = 0x1000;
  1868. const int KeyPress::rightKey = 0x1001;
  1869. const int KeyPress::upKey = 0x1002;
  1870. const int KeyPress::downKey = 0x1003;
  1871. const int KeyPress::pageUpKey = 0x1004;
  1872. const int KeyPress::pageDownKey = 0x1005;
  1873. const int KeyPress::endKey = 0x1006;
  1874. const int KeyPress::homeKey = 0x1007;
  1875. const int KeyPress::deleteKey = 0x1008;
  1876. const int KeyPress::insertKey = -1;
  1877. const int KeyPress::tabKey = 9;
  1878. const int KeyPress::F1Key = 0x2001;
  1879. const int KeyPress::F2Key = 0x2002;
  1880. const int KeyPress::F3Key = 0x2003;
  1881. const int KeyPress::F4Key = 0x2004;
  1882. const int KeyPress::F5Key = 0x2005;
  1883. const int KeyPress::F6Key = 0x2006;
  1884. const int KeyPress::F7Key = 0x2007;
  1885. const int KeyPress::F8Key = 0x2008;
  1886. const int KeyPress::F9Key = 0x2009;
  1887. const int KeyPress::F10Key = 0x200a;
  1888. const int KeyPress::F11Key = 0x200b;
  1889. const int KeyPress::F12Key = 0x200c;
  1890. const int KeyPress::F13Key = 0x200d;
  1891. const int KeyPress::F14Key = 0x200e;
  1892. const int KeyPress::F15Key = 0x200f;
  1893. const int KeyPress::F16Key = 0x2010;
  1894. const int KeyPress::F17Key = 0x2011;
  1895. const int KeyPress::F18Key = 0x2012;
  1896. const int KeyPress::F19Key = 0x2013;
  1897. const int KeyPress::F20Key = 0x2014;
  1898. const int KeyPress::F21Key = 0x2015;
  1899. const int KeyPress::F22Key = 0x2016;
  1900. const int KeyPress::F23Key = 0x2017;
  1901. const int KeyPress::F24Key = 0x2018;
  1902. const int KeyPress::F25Key = 0x2019;
  1903. const int KeyPress::F26Key = 0x201a;
  1904. const int KeyPress::F27Key = 0x201b;
  1905. const int KeyPress::F28Key = 0x201c;
  1906. const int KeyPress::F29Key = 0x201d;
  1907. const int KeyPress::F30Key = 0x201e;
  1908. const int KeyPress::F31Key = 0x201f;
  1909. const int KeyPress::F32Key = 0x2020;
  1910. const int KeyPress::F33Key = 0x2021;
  1911. const int KeyPress::F34Key = 0x2022;
  1912. const int KeyPress::F35Key = 0x2023;
  1913. const int KeyPress::numberPad0 = 0x30020;
  1914. const int KeyPress::numberPad1 = 0x30021;
  1915. const int KeyPress::numberPad2 = 0x30022;
  1916. const int KeyPress::numberPad3 = 0x30023;
  1917. const int KeyPress::numberPad4 = 0x30024;
  1918. const int KeyPress::numberPad5 = 0x30025;
  1919. const int KeyPress::numberPad6 = 0x30026;
  1920. const int KeyPress::numberPad7 = 0x30027;
  1921. const int KeyPress::numberPad8 = 0x30028;
  1922. const int KeyPress::numberPad9 = 0x30029;
  1923. const int KeyPress::numberPadAdd = 0x3002a;
  1924. const int KeyPress::numberPadSubtract = 0x3002b;
  1925. const int KeyPress::numberPadMultiply = 0x3002c;
  1926. const int KeyPress::numberPadDivide = 0x3002d;
  1927. const int KeyPress::numberPadSeparator = 0x3002e;
  1928. const int KeyPress::numberPadDecimalPoint = 0x3002f;
  1929. const int KeyPress::numberPadEquals = 0x30030;
  1930. const int KeyPress::numberPadDelete = 0x30031;
  1931. const int KeyPress::playKey = 0x30000;
  1932. const int KeyPress::stopKey = 0x30001;
  1933. const int KeyPress::fastForwardKey = 0x30002;
  1934. const int KeyPress::rewindKey = 0x30003;
  1935. } // namespace juce