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.

2417 lines
79KB

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