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.

2327 lines
76KB

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