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.

2080 lines
66KB

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