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.

1251 lines
41KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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 defined (__IPHONE_13_0)
  19. #define JUCE_HAS_IOS_POINTER_SUPPORT 1
  20. #else
  21. #define JUCE_HAS_IOS_POINTER_SUPPORT 0
  22. #endif
  23. namespace juce
  24. {
  25. class UIViewComponentPeer;
  26. static UIInterfaceOrientation getWindowOrientation()
  27. {
  28. UIApplication* sharedApplication = [UIApplication sharedApplication];
  29. #if (defined (__IPHONE_13_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0)
  30. for (UIScene* scene in [sharedApplication connectedScenes])
  31. if ([scene isKindOfClass: [UIWindowScene class]])
  32. return [(UIWindowScene*) scene interfaceOrientation];
  33. return UIInterfaceOrientationPortrait;
  34. #else
  35. return [sharedApplication statusBarOrientation];
  36. #endif
  37. }
  38. namespace Orientations
  39. {
  40. static Desktop::DisplayOrientation convertToJuce (UIInterfaceOrientation orientation)
  41. {
  42. switch (orientation)
  43. {
  44. case UIInterfaceOrientationPortrait: return Desktop::upright;
  45. case UIInterfaceOrientationPortraitUpsideDown: return Desktop::upsideDown;
  46. case UIInterfaceOrientationLandscapeLeft: return Desktop::rotatedClockwise;
  47. case UIInterfaceOrientationLandscapeRight: return Desktop::rotatedAntiClockwise;
  48. case UIInterfaceOrientationUnknown:
  49. default: jassertfalse; // unknown orientation!
  50. }
  51. return Desktop::upright;
  52. }
  53. static UIInterfaceOrientation convertFromJuce (Desktop::DisplayOrientation orientation)
  54. {
  55. switch (orientation)
  56. {
  57. case Desktop::upright: return UIInterfaceOrientationPortrait;
  58. case Desktop::upsideDown: return UIInterfaceOrientationPortraitUpsideDown;
  59. case Desktop::rotatedClockwise: return UIInterfaceOrientationLandscapeLeft;
  60. case Desktop::rotatedAntiClockwise: return UIInterfaceOrientationLandscapeRight;
  61. case Desktop::allOrientations:
  62. default: jassertfalse; // unknown orientation!
  63. }
  64. return UIInterfaceOrientationPortrait;
  65. }
  66. static NSUInteger getSupportedOrientations()
  67. {
  68. NSUInteger allowed = 0;
  69. auto& d = Desktop::getInstance();
  70. if (d.isOrientationEnabled (Desktop::upright)) allowed |= UIInterfaceOrientationMaskPortrait;
  71. if (d.isOrientationEnabled (Desktop::upsideDown)) allowed |= UIInterfaceOrientationMaskPortraitUpsideDown;
  72. if (d.isOrientationEnabled (Desktop::rotatedClockwise)) allowed |= UIInterfaceOrientationMaskLandscapeLeft;
  73. if (d.isOrientationEnabled (Desktop::rotatedAntiClockwise)) allowed |= UIInterfaceOrientationMaskLandscapeRight;
  74. return allowed;
  75. }
  76. }
  77. enum class MouseEventFlags
  78. {
  79. none,
  80. down,
  81. up,
  82. upAndCancel,
  83. };
  84. //==============================================================================
  85. } // namespace juce
  86. using namespace juce;
  87. @interface JuceUIView : UIView <UITextViewDelegate>
  88. {
  89. @public
  90. UIViewComponentPeer* owner;
  91. UITextView* hiddenTextView;
  92. }
  93. - (JuceUIView*) initWithOwner: (UIViewComponentPeer*) owner withFrame: (CGRect) frame;
  94. - (void) dealloc;
  95. - (void) drawRect: (CGRect) r;
  96. - (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event;
  97. - (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event;
  98. - (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event;
  99. - (void) touchesCancelled: (NSSet*) touches withEvent: (UIEvent*) event;
  100. #if JUCE_HAS_IOS_POINTER_SUPPORT
  101. - (void) onHover: (UIHoverGestureRecognizer*) gesture API_AVAILABLE (ios (13.0));
  102. - (void) onScroll: (UIPanGestureRecognizer*) gesture;
  103. #endif
  104. - (BOOL) becomeFirstResponder;
  105. - (BOOL) resignFirstResponder;
  106. - (BOOL) canBecomeFirstResponder;
  107. - (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text;
  108. - (BOOL) isAccessibilityElement;
  109. - (id) accessibilityElementAtIndex: (NSInteger) index;
  110. - (NSInteger) accessibilityElementCount;
  111. - (NSInteger) indexOfAccessibilityElement: (id) element;
  112. @end
  113. //==============================================================================
  114. @interface JuceUIViewController : UIViewController
  115. {
  116. }
  117. - (JuceUIViewController*) init;
  118. - (NSUInteger) supportedInterfaceOrientations;
  119. - (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation;
  120. - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration;
  121. - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation;
  122. - (void) viewWillTransitionToSize: (CGSize) size withTransitionCoordinator: (id<UIViewControllerTransitionCoordinator>) coordinator;
  123. - (BOOL) prefersStatusBarHidden;
  124. - (UIStatusBarStyle) preferredStatusBarStyle;
  125. - (void) viewDidLoad;
  126. - (void) viewWillAppear: (BOOL) animated;
  127. - (void) viewDidAppear: (BOOL) animated;
  128. - (void) viewWillLayoutSubviews;
  129. - (void) viewDidLayoutSubviews;
  130. @end
  131. //==============================================================================
  132. @interface JuceUIWindow : UIWindow
  133. {
  134. @private
  135. UIViewComponentPeer* owner;
  136. }
  137. - (void) setOwner: (UIViewComponentPeer*) owner;
  138. - (void) becomeKeyWindow;
  139. @end
  140. //==============================================================================
  141. //==============================================================================
  142. namespace juce
  143. {
  144. struct UIViewPeerControllerReceiver
  145. {
  146. virtual ~UIViewPeerControllerReceiver() = default;
  147. virtual void setViewController (UIViewController*) = 0;
  148. };
  149. class UIViewComponentPeer : public ComponentPeer,
  150. public FocusChangeListener,
  151. public UIViewPeerControllerReceiver
  152. {
  153. public:
  154. UIViewComponentPeer (Component&, int windowStyleFlags, UIView* viewToAttachTo);
  155. ~UIViewComponentPeer() override;
  156. //==============================================================================
  157. void* getNativeHandle() const override { return view; }
  158. void setVisible (bool shouldBeVisible) override;
  159. void setTitle (const String& title) override;
  160. void setBounds (const Rectangle<int>&, bool isNowFullScreen) override;
  161. void setViewController (UIViewController* newController) override
  162. {
  163. jassert (controller == nullptr);
  164. controller = [newController retain];
  165. }
  166. Rectangle<int> getBounds() const override { return getBounds (! isSharedWindow); }
  167. Rectangle<int> getBounds (bool global) const;
  168. Point<float> localToGlobal (Point<float> relativePosition) override;
  169. Point<float> globalToLocal (Point<float> screenPosition) override;
  170. using ComponentPeer::localToGlobal;
  171. using ComponentPeer::globalToLocal;
  172. void setAlpha (float newAlpha) override;
  173. void setMinimised (bool) override {}
  174. bool isMinimised() const override { return false; }
  175. void setFullScreen (bool shouldBeFullScreen) override;
  176. bool isFullScreen() const override { return fullScreen; }
  177. bool contains (Point<int> localPos, bool trueIfInAChildWindow) const override;
  178. BorderSize<int> getFrameSize() const override { return BorderSize<int>(); }
  179. bool setAlwaysOnTop (bool alwaysOnTop) override;
  180. void toFront (bool makeActiveWindow) override;
  181. void toBehind (ComponentPeer* other) override;
  182. void setIcon (const Image& newIcon) override;
  183. StringArray getAvailableRenderingEngines() override { return StringArray ("CoreGraphics Renderer"); }
  184. void drawRect (CGRect);
  185. bool canBecomeKeyWindow();
  186. //==============================================================================
  187. void viewFocusGain();
  188. void viewFocusLoss();
  189. bool isFocused() const override;
  190. void grabFocus() override;
  191. void textInputRequired (Point<int>, TextInputTarget&) override;
  192. BOOL textViewReplaceCharacters (Range<int>, const String&);
  193. void updateHiddenTextContent (TextInputTarget*);
  194. void globalFocusChanged (Component*) override;
  195. void updateScreenBounds();
  196. void handleTouches (UIEvent*, MouseEventFlags);
  197. #if JUCE_HAS_IOS_POINTER_SUPPORT
  198. API_AVAILABLE (ios (13.0)) void onHover (UIHoverGestureRecognizer*);
  199. void onScroll (UIPanGestureRecognizer*);
  200. #endif
  201. //==============================================================================
  202. void repaint (const Rectangle<int>& area) override;
  203. void performAnyPendingRepaintsNow() override;
  204. //==============================================================================
  205. UIWindow* window = nil;
  206. JuceUIView* view = nil;
  207. UIViewController* controller = nil;
  208. const bool isSharedWindow, isAppex;
  209. bool fullScreen = false, insideDrawRect = false;
  210. static int64 getMouseTime (NSTimeInterval timestamp) noexcept
  211. {
  212. return (Time::currentTimeMillis() - Time::getMillisecondCounter())
  213. + (int64) (timestamp * 1000.0);
  214. }
  215. static int64 getMouseTime (UIEvent* e) noexcept
  216. {
  217. return getMouseTime ([e timestamp]);
  218. }
  219. static MultiTouchMapper<UITouch*> currentTouches;
  220. private:
  221. //==============================================================================
  222. class AsyncRepaintMessage : public CallbackMessage
  223. {
  224. public:
  225. UIViewComponentPeer* const peer;
  226. const Rectangle<int> rect;
  227. AsyncRepaintMessage (UIViewComponentPeer* const p, const Rectangle<int>& r)
  228. : peer (p), rect (r)
  229. {
  230. }
  231. void messageCallback() override
  232. {
  233. if (ComponentPeer::isValidPeer (peer))
  234. peer->repaint (rect);
  235. }
  236. };
  237. //==============================================================================
  238. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer)
  239. };
  240. static void sendScreenBoundsUpdate (JuceUIViewController* c)
  241. {
  242. JuceUIView* juceView = (JuceUIView*) [c view];
  243. if (juceView != nil && juceView->owner != nullptr)
  244. juceView->owner->updateScreenBounds();
  245. }
  246. static bool isKioskModeView (JuceUIViewController* c)
  247. {
  248. JuceUIView* juceView = (JuceUIView*) [c view];
  249. if (juceView == nil || juceView->owner == nullptr)
  250. {
  251. jassertfalse;
  252. return false;
  253. }
  254. return Desktop::getInstance().getKioskModeComponent() == &(juceView->owner->getComponent());
  255. }
  256. MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches;
  257. } // namespace juce
  258. //==============================================================================
  259. //==============================================================================
  260. @implementation JuceUIViewController
  261. - (JuceUIViewController*) init
  262. {
  263. self = [super init];
  264. return self;
  265. }
  266. - (NSUInteger) supportedInterfaceOrientations
  267. {
  268. return Orientations::getSupportedOrientations();
  269. }
  270. - (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation
  271. {
  272. return Desktop::getInstance().isOrientationEnabled (Orientations::convertToJuce (interfaceOrientation));
  273. }
  274. - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation
  275. duration: (NSTimeInterval) duration
  276. {
  277. ignoreUnused (toInterfaceOrientation, duration);
  278. [UIView setAnimationsEnabled: NO]; // disable this because it goes the wrong way and looks like crap.
  279. }
  280. - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation
  281. {
  282. ignoreUnused (fromInterfaceOrientation);
  283. sendScreenBoundsUpdate (self);
  284. [UIView setAnimationsEnabled: YES];
  285. }
  286. - (void) viewWillTransitionToSize: (CGSize) size withTransitionCoordinator: (id<UIViewControllerTransitionCoordinator>) coordinator
  287. {
  288. [super viewWillTransitionToSize: size withTransitionCoordinator: coordinator];
  289. [coordinator animateAlongsideTransition: nil completion: ^void (id<UIViewControllerTransitionCoordinatorContext>)
  290. {
  291. sendScreenBoundsUpdate (self);
  292. }];
  293. }
  294. - (BOOL) prefersStatusBarHidden
  295. {
  296. if (isKioskModeView (self))
  297. return true;
  298. return [[[NSBundle mainBundle] objectForInfoDictionaryKey: @"UIStatusBarHidden"] boolValue];
  299. }
  300. #if defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0
  301. - (BOOL) prefersHomeIndicatorAutoHidden
  302. {
  303. return isKioskModeView (self);
  304. }
  305. #endif
  306. - (UIStatusBarStyle) preferredStatusBarStyle
  307. {
  308. return UIStatusBarStyleDefault;
  309. }
  310. - (void) viewDidLoad
  311. {
  312. sendScreenBoundsUpdate (self);
  313. [super viewDidLoad];
  314. }
  315. - (void) viewWillAppear: (BOOL) animated
  316. {
  317. sendScreenBoundsUpdate (self);
  318. [super viewWillAppear:animated];
  319. }
  320. - (void) viewDidAppear: (BOOL) animated
  321. {
  322. sendScreenBoundsUpdate (self);
  323. [super viewDidAppear:animated];
  324. }
  325. - (void) viewWillLayoutSubviews
  326. {
  327. sendScreenBoundsUpdate (self);
  328. }
  329. - (void) viewDidLayoutSubviews
  330. {
  331. sendScreenBoundsUpdate (self);
  332. }
  333. @end
  334. @implementation JuceUIView
  335. - (JuceUIView*) initWithOwner: (UIViewComponentPeer*) peer
  336. withFrame: (CGRect) frame
  337. {
  338. [super initWithFrame: frame];
  339. owner = peer;
  340. hiddenTextView = [[UITextView alloc] initWithFrame: CGRectZero];
  341. [self addSubview: hiddenTextView];
  342. hiddenTextView.delegate = self;
  343. hiddenTextView.autocapitalizationType = UITextAutocapitalizationTypeNone;
  344. hiddenTextView.autocorrectionType = UITextAutocorrectionTypeNo;
  345. hiddenTextView.inputAssistantItem.leadingBarButtonGroups = @[];
  346. hiddenTextView.inputAssistantItem.trailingBarButtonGroups = @[];
  347. #if JUCE_HAS_IOS_POINTER_SUPPORT
  348. if (@available (iOS 13.4, *))
  349. {
  350. auto hoverRecognizer = [[[UIHoverGestureRecognizer alloc] initWithTarget: self action: @selector (onHover:)] autorelease];
  351. [hoverRecognizer setCancelsTouchesInView: NO];
  352. [hoverRecognizer setRequiresExclusiveTouchType: YES];
  353. [self addGestureRecognizer: hoverRecognizer];
  354. auto panRecognizer = [[[UIPanGestureRecognizer alloc] initWithTarget: self action: @selector (onScroll:)] autorelease];
  355. [panRecognizer setCancelsTouchesInView: NO];
  356. [panRecognizer setRequiresExclusiveTouchType: YES];
  357. [panRecognizer setAllowedScrollTypesMask: UIScrollTypeMaskAll];
  358. [panRecognizer setMaximumNumberOfTouches: 0];
  359. [self addGestureRecognizer: panRecognizer];
  360. }
  361. #endif
  362. return self;
  363. }
  364. - (void) dealloc
  365. {
  366. [hiddenTextView removeFromSuperview];
  367. [hiddenTextView release];
  368. [super dealloc];
  369. }
  370. //==============================================================================
  371. - (void) drawRect: (CGRect) r
  372. {
  373. if (owner != nullptr)
  374. owner->drawRect (r);
  375. }
  376. //==============================================================================
  377. - (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
  378. {
  379. ignoreUnused (touches);
  380. if (owner != nullptr)
  381. owner->handleTouches (event, MouseEventFlags::down);
  382. }
  383. - (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event
  384. {
  385. ignoreUnused (touches);
  386. if (owner != nullptr)
  387. owner->handleTouches (event, MouseEventFlags::none);
  388. }
  389. - (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event
  390. {
  391. ignoreUnused (touches);
  392. if (owner != nullptr)
  393. owner->handleTouches (event, MouseEventFlags::up);
  394. }
  395. - (void) touchesCancelled: (NSSet*) touches withEvent: (UIEvent*) event
  396. {
  397. if (owner != nullptr)
  398. owner->handleTouches (event, MouseEventFlags::upAndCancel);
  399. [self touchesEnded: touches withEvent: event];
  400. }
  401. #if JUCE_HAS_IOS_POINTER_SUPPORT
  402. - (void) onHover: (UIHoverGestureRecognizer*) gesture
  403. {
  404. if (owner != nullptr)
  405. owner->onHover (gesture);
  406. }
  407. - (void) onScroll: (UIPanGestureRecognizer*) gesture
  408. {
  409. if (owner != nullptr)
  410. owner->onScroll (gesture);
  411. }
  412. #endif
  413. //==============================================================================
  414. - (BOOL) becomeFirstResponder
  415. {
  416. if (owner != nullptr)
  417. owner->viewFocusGain();
  418. return true;
  419. }
  420. - (BOOL) resignFirstResponder
  421. {
  422. if (owner != nullptr)
  423. owner->viewFocusLoss();
  424. return [super resignFirstResponder];
  425. }
  426. - (BOOL) canBecomeFirstResponder
  427. {
  428. return owner != nullptr && owner->canBecomeKeyWindow();
  429. }
  430. - (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text
  431. {
  432. ignoreUnused (textView);
  433. return owner->textViewReplaceCharacters (Range<int> ((int) range.location, (int) (range.location + range.length)),
  434. nsStringToJuce (text));
  435. }
  436. - (BOOL) isAccessibilityElement
  437. {
  438. return NO;
  439. }
  440. - (id) accessibilityElementAtIndex: (NSInteger) index
  441. {
  442. if (owner != nullptr)
  443. if (auto* handler = owner->getComponent().getAccessibilityHandler())
  444. return (id) handler->getNativeImplementation();
  445. return nil;
  446. }
  447. - (NSInteger) accessibilityElementCount
  448. {
  449. return owner != nullptr ? 1 : 0;
  450. }
  451. - (NSInteger) indexOfAccessibilityElement: (id) element
  452. {
  453. return 0;
  454. }
  455. @end
  456. //==============================================================================
  457. @implementation JuceUIWindow
  458. - (void) setOwner: (UIViewComponentPeer*) peer
  459. {
  460. owner = peer;
  461. }
  462. - (void) becomeKeyWindow
  463. {
  464. [super becomeKeyWindow];
  465. if (owner != nullptr)
  466. owner->grabFocus();
  467. }
  468. @end
  469. //==============================================================================
  470. //==============================================================================
  471. namespace juce
  472. {
  473. bool KeyPress::isKeyCurrentlyDown (int)
  474. {
  475. return false;
  476. }
  477. Point<float> juce_lastMousePos;
  478. //==============================================================================
  479. UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags, UIView* viewToAttachTo)
  480. : ComponentPeer (comp, windowStyleFlags),
  481. isSharedWindow (viewToAttachTo != nil),
  482. isAppex (SystemStats::isRunningInAppExtensionSandbox())
  483. {
  484. CGRect r = convertToCGRect (component.getBounds());
  485. view = [[JuceUIView alloc] initWithOwner: this withFrame: r];
  486. view.multipleTouchEnabled = YES;
  487. view.hidden = true;
  488. view.opaque = component.isOpaque();
  489. view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0];
  490. #if JUCE_COREGRAPHICS_DRAW_ASYNC
  491. if (! getComponentAsyncLayerBackedViewDisabled (component))
  492. {
  493. [[view layer] setDrawsAsynchronously: YES];
  494. }
  495. #endif
  496. if (isSharedWindow)
  497. {
  498. window = [viewToAttachTo window];
  499. [viewToAttachTo addSubview: view];
  500. }
  501. else
  502. {
  503. r = convertToCGRect (component.getBounds());
  504. r.origin.y = [UIScreen mainScreen].bounds.size.height - (r.origin.y + r.size.height);
  505. window = [[JuceUIWindow alloc] initWithFrame: r];
  506. [((JuceUIWindow*) window) setOwner: this];
  507. controller = [[JuceUIViewController alloc] init];
  508. controller.view = view;
  509. window.rootViewController = controller;
  510. window.hidden = true;
  511. window.opaque = component.isOpaque();
  512. window.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0];
  513. if (component.isAlwaysOnTop())
  514. window.windowLevel = UIWindowLevelAlert;
  515. view.frame = CGRectMake (0, 0, r.size.width, r.size.height);
  516. }
  517. setTitle (component.getName());
  518. setVisible (component.isVisible());
  519. Desktop::getInstance().addFocusChangeListener (this);
  520. }
  521. UIViewComponentPeer::~UIViewComponentPeer()
  522. {
  523. currentTouches.deleteAllTouchesForPeer (this);
  524. Desktop::getInstance().removeFocusChangeListener (this);
  525. view->owner = nullptr;
  526. [view removeFromSuperview];
  527. [view release];
  528. [controller release];
  529. if (! isSharedWindow)
  530. {
  531. [((JuceUIWindow*) window) setOwner: nil];
  532. #if defined (__IPHONE_13_0)
  533. if (@available (iOS 13.0, *))
  534. window.windowScene = nil;
  535. #endif
  536. [window release];
  537. }
  538. }
  539. //==============================================================================
  540. void UIViewComponentPeer::setVisible (bool shouldBeVisible)
  541. {
  542. if (! isSharedWindow)
  543. window.hidden = ! shouldBeVisible;
  544. view.hidden = ! shouldBeVisible;
  545. }
  546. void UIViewComponentPeer::setTitle (const String&)
  547. {
  548. // xxx is this possible?
  549. }
  550. void UIViewComponentPeer::setBounds (const Rectangle<int>& newBounds, const bool isNowFullScreen)
  551. {
  552. fullScreen = isNowFullScreen;
  553. if (isSharedWindow)
  554. {
  555. CGRect r = convertToCGRect (newBounds);
  556. if (view.frame.size.width != r.size.width || view.frame.size.height != r.size.height)
  557. [view setNeedsDisplay];
  558. view.frame = r;
  559. }
  560. else
  561. {
  562. window.frame = convertToCGRect (newBounds);
  563. view.frame = CGRectMake (0, 0, (CGFloat) newBounds.getWidth(), (CGFloat) newBounds.getHeight());
  564. handleMovedOrResized();
  565. }
  566. }
  567. Rectangle<int> UIViewComponentPeer::getBounds (const bool global) const
  568. {
  569. auto r = view.frame;
  570. if (global)
  571. {
  572. if (view.window != nil)
  573. {
  574. r = [view convertRect: r toView: view.window];
  575. r = [view.window convertRect: r toWindow: nil];
  576. }
  577. else if (window != nil)
  578. {
  579. r.origin.x += window.frame.origin.x;
  580. r.origin.y += window.frame.origin.y;
  581. }
  582. }
  583. return convertToRectInt (r);
  584. }
  585. Point<float> UIViewComponentPeer::localToGlobal (Point<float> relativePosition)
  586. {
  587. return relativePosition + getBounds (true).getPosition().toFloat();
  588. }
  589. Point<float> UIViewComponentPeer::globalToLocal (Point<float> screenPosition)
  590. {
  591. return screenPosition - getBounds (true).getPosition().toFloat();
  592. }
  593. void UIViewComponentPeer::setAlpha (float newAlpha)
  594. {
  595. [view.window setAlpha: (CGFloat) newAlpha];
  596. }
  597. void UIViewComponentPeer::setFullScreen (bool shouldBeFullScreen)
  598. {
  599. if (! isSharedWindow)
  600. {
  601. auto r = shouldBeFullScreen ? Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea
  602. : lastNonFullscreenBounds;
  603. if ((! shouldBeFullScreen) && r.isEmpty())
  604. r = getBounds();
  605. // (can't call the component's setBounds method because that'll reset our fullscreen flag)
  606. if (! r.isEmpty())
  607. setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, r), shouldBeFullScreen);
  608. component.repaint();
  609. }
  610. }
  611. void UIViewComponentPeer::updateScreenBounds()
  612. {
  613. auto& desktop = Desktop::getInstance();
  614. auto oldArea = component.getBounds();
  615. auto oldDesktop = desktop.getDisplays().getPrimaryDisplay()->userArea;
  616. forceDisplayUpdate();
  617. if (fullScreen)
  618. {
  619. fullScreen = false;
  620. setFullScreen (true);
  621. }
  622. else if (! isSharedWindow)
  623. {
  624. auto newDesktop = desktop.getDisplays().getPrimaryDisplay()->userArea;
  625. if (newDesktop != oldDesktop)
  626. {
  627. // this will re-centre the window, but leave its size unchanged
  628. auto centreRelX = oldArea.getCentreX() / (float) oldDesktop.getWidth();
  629. auto centreRelY = oldArea.getCentreY() / (float) oldDesktop.getHeight();
  630. auto x = ((int) (newDesktop.getWidth() * centreRelX)) - (oldArea.getWidth() / 2);
  631. auto y = ((int) (newDesktop.getHeight() * centreRelY)) - (oldArea.getHeight() / 2);
  632. component.setBounds (oldArea.withPosition (x, y));
  633. }
  634. }
  635. [view setNeedsDisplay];
  636. }
  637. bool UIViewComponentPeer::contains (Point<int> localPos, bool trueIfInAChildWindow) const
  638. {
  639. if (! ScalingHelpers::scaledScreenPosToUnscaled (component, component.getLocalBounds()).contains (localPos))
  640. return false;
  641. UIView* v = [view hitTest: convertToCGPoint (localPos)
  642. withEvent: nil];
  643. if (trueIfInAChildWindow)
  644. return v != nil;
  645. return v == view;
  646. }
  647. bool UIViewComponentPeer::setAlwaysOnTop (bool alwaysOnTop)
  648. {
  649. if (! isSharedWindow)
  650. window.windowLevel = alwaysOnTop ? UIWindowLevelAlert : UIWindowLevelNormal;
  651. return true;
  652. }
  653. void UIViewComponentPeer::toFront (bool makeActiveWindow)
  654. {
  655. if (isSharedWindow)
  656. [[view superview] bringSubviewToFront: view];
  657. if (makeActiveWindow && window != nil && component.isVisible())
  658. [window makeKeyAndVisible];
  659. }
  660. void UIViewComponentPeer::toBehind (ComponentPeer* other)
  661. {
  662. if (auto* otherPeer = dynamic_cast<UIViewComponentPeer*> (other))
  663. {
  664. if (isSharedWindow)
  665. [[view superview] insertSubview: view belowSubview: otherPeer->view];
  666. }
  667. else
  668. {
  669. jassertfalse; // wrong type of window?
  670. }
  671. }
  672. void UIViewComponentPeer::setIcon (const Image& /*newIcon*/)
  673. {
  674. // to do..
  675. }
  676. //==============================================================================
  677. static float getMaximumTouchForce (UITouch* touch) noexcept
  678. {
  679. if ([touch respondsToSelector: @selector (maximumPossibleForce)])
  680. return (float) touch.maximumPossibleForce;
  681. return 0.0f;
  682. }
  683. static float getTouchForce (UITouch* touch) noexcept
  684. {
  685. if ([touch respondsToSelector: @selector (force)])
  686. return (float) touch.force;
  687. return 0.0f;
  688. }
  689. void UIViewComponentPeer::handleTouches (UIEvent* event, MouseEventFlags mouseEventFlags)
  690. {
  691. NSArray* touches = [[event touchesForView: view] allObjects];
  692. for (unsigned int i = 0; i < [touches count]; ++i)
  693. {
  694. UITouch* touch = [touches objectAtIndex: i];
  695. auto maximumForce = getMaximumTouchForce (touch);
  696. if ([touch phase] == UITouchPhaseStationary && maximumForce <= 0)
  697. continue;
  698. auto pos = convertToPointFloat ([touch locationInView: view]);
  699. juce_lastMousePos = pos + getBounds (true).getPosition().toFloat();
  700. auto time = getMouseTime (event);
  701. auto touchIndex = currentTouches.getIndexOfTouch (this, touch);
  702. auto modsToSend = ModifierKeys::currentModifiers;
  703. auto isUp = [] (MouseEventFlags m)
  704. {
  705. return m == MouseEventFlags::up || m == MouseEventFlags::upAndCancel;
  706. };
  707. if (mouseEventFlags == MouseEventFlags::down)
  708. {
  709. if ([touch phase] != UITouchPhaseBegan)
  710. continue;
  711. ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
  712. modsToSend = ModifierKeys::currentModifiers;
  713. // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
  714. handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, modsToSend.withoutMouseButtons(),
  715. MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation, time, {}, touchIndex);
  716. if (! isValidPeer (this)) // (in case this component was deleted by the event)
  717. return;
  718. }
  719. else if (isUp (mouseEventFlags))
  720. {
  721. if (! ([touch phase] == UITouchPhaseEnded || [touch phase] == UITouchPhaseCancelled))
  722. continue;
  723. modsToSend = modsToSend.withoutMouseButtons();
  724. currentTouches.clearTouch (touchIndex);
  725. if (! currentTouches.areAnyTouchesActive())
  726. mouseEventFlags = MouseEventFlags::upAndCancel;
  727. }
  728. if (mouseEventFlags == MouseEventFlags::upAndCancel)
  729. {
  730. currentTouches.clearTouch (touchIndex);
  731. modsToSend = ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons();
  732. }
  733. // NB: some devices return 0 or 1.0 if pressure is unknown, so we'll clip our value to a believable range:
  734. auto pressure = maximumForce > 0 ? jlimit (0.0001f, 0.9999f, getTouchForce (touch) / maximumForce)
  735. : MouseInputSource::invalidPressure;
  736. handleMouseEvent (MouseInputSource::InputSourceType::touch,
  737. pos, modsToSend, pressure, MouseInputSource::invalidOrientation, time, { }, touchIndex);
  738. if (! isValidPeer (this)) // (in case this component was deleted by the event)
  739. return;
  740. if (isUp (mouseEventFlags))
  741. {
  742. handleMouseEvent (MouseInputSource::InputSourceType::touch, MouseInputSource::offscreenMousePos, modsToSend,
  743. MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation, time, {}, touchIndex);
  744. if (! isValidPeer (this))
  745. return;
  746. }
  747. }
  748. }
  749. #if JUCE_HAS_IOS_POINTER_SUPPORT
  750. void UIViewComponentPeer::onHover (UIHoverGestureRecognizer* gesture)
  751. {
  752. auto pos = convertToPointFloat ([gesture locationInView: view]);
  753. juce_lastMousePos = pos + getBounds (true).getPosition().toFloat();
  754. handleMouseEvent (MouseInputSource::InputSourceType::touch,
  755. pos,
  756. ModifierKeys::currentModifiers,
  757. MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation,
  758. UIViewComponentPeer::getMouseTime ([[NSProcessInfo processInfo] systemUptime]),
  759. {});
  760. }
  761. void UIViewComponentPeer::onScroll (UIPanGestureRecognizer* gesture)
  762. {
  763. const auto offset = [gesture translationInView: view];
  764. const auto scale = 0.5f / 256.0f;
  765. MouseWheelDetails details;
  766. details.deltaX = scale * (float) offset.x;
  767. details.deltaY = scale * (float) offset.y;
  768. details.isReversed = false;
  769. details.isSmooth = true;
  770. details.isInertial = false;
  771. handleMouseWheel (MouseInputSource::InputSourceType::touch,
  772. convertToPointFloat ([gesture locationInView: view]),
  773. UIViewComponentPeer::getMouseTime ([[NSProcessInfo processInfo] systemUptime]),
  774. details);
  775. }
  776. #endif
  777. //==============================================================================
  778. static UIViewComponentPeer* currentlyFocusedPeer = nullptr;
  779. void UIViewComponentPeer::viewFocusGain()
  780. {
  781. if (currentlyFocusedPeer != this)
  782. {
  783. if (ComponentPeer::isValidPeer (currentlyFocusedPeer))
  784. currentlyFocusedPeer->handleFocusLoss();
  785. currentlyFocusedPeer = this;
  786. handleFocusGain();
  787. }
  788. }
  789. void UIViewComponentPeer::viewFocusLoss()
  790. {
  791. if (currentlyFocusedPeer == this)
  792. {
  793. currentlyFocusedPeer = nullptr;
  794. handleFocusLoss();
  795. }
  796. }
  797. bool UIViewComponentPeer::isFocused() const
  798. {
  799. if (isAppex)
  800. return true;
  801. return isSharedWindow ? this == currentlyFocusedPeer
  802. : (window != nil && [window isKeyWindow]);
  803. }
  804. void UIViewComponentPeer::grabFocus()
  805. {
  806. if (window != nil)
  807. {
  808. [window makeKeyWindow];
  809. viewFocusGain();
  810. }
  811. }
  812. void UIViewComponentPeer::textInputRequired (Point<int>, TextInputTarget&)
  813. {
  814. }
  815. static UIKeyboardType getUIKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept
  816. {
  817. switch (type)
  818. {
  819. case TextInputTarget::textKeyboard: return UIKeyboardTypeAlphabet;
  820. case TextInputTarget::numericKeyboard: return UIKeyboardTypeNumbersAndPunctuation;
  821. case TextInputTarget::decimalKeyboard: return UIKeyboardTypeNumbersAndPunctuation;
  822. case TextInputTarget::urlKeyboard: return UIKeyboardTypeURL;
  823. case TextInputTarget::emailAddressKeyboard: return UIKeyboardTypeEmailAddress;
  824. case TextInputTarget::phoneNumberKeyboard: return UIKeyboardTypePhonePad;
  825. default: jassertfalse; break;
  826. }
  827. return UIKeyboardTypeDefault;
  828. }
  829. void UIViewComponentPeer::updateHiddenTextContent (TextInputTarget* target)
  830. {
  831. view->hiddenTextView.keyboardType = getUIKeyboardType (target->getKeyboardType());
  832. view->hiddenTextView.text = juceStringToNS (target->getTextInRange (Range<int> (0, target->getHighlightedRegion().getStart())));
  833. view->hiddenTextView.selectedRange = NSMakeRange ((NSUInteger) target->getHighlightedRegion().getStart(), 0);
  834. }
  835. BOOL UIViewComponentPeer::textViewReplaceCharacters (Range<int> range, const String& text)
  836. {
  837. if (auto* target = findCurrentTextInputTarget())
  838. {
  839. auto currentSelection = target->getHighlightedRegion();
  840. if (range.getLength() == 1 && text.isEmpty()) // (detect backspace)
  841. if (currentSelection.isEmpty())
  842. target->setHighlightedRegion (currentSelection.withStart (currentSelection.getStart() - 1));
  843. WeakReference<Component> deletionChecker (dynamic_cast<Component*> (target));
  844. if (text == "\r" || text == "\n" || text == "\r\n")
  845. handleKeyPress (KeyPress::returnKey, text[0]);
  846. else
  847. target->insertTextAtCaret (text);
  848. if (deletionChecker != nullptr)
  849. updateHiddenTextContent (target);
  850. }
  851. return NO;
  852. }
  853. void UIViewComponentPeer::globalFocusChanged (Component*)
  854. {
  855. if (auto* target = findCurrentTextInputTarget())
  856. {
  857. if (auto* comp = dynamic_cast<Component*> (target))
  858. {
  859. auto pos = component.getLocalPoint (comp, Point<int>());
  860. view->hiddenTextView.frame = CGRectMake (pos.x, pos.y, 0, 0);
  861. updateHiddenTextContent (target);
  862. [view->hiddenTextView becomeFirstResponder];
  863. }
  864. }
  865. else
  866. {
  867. [view->hiddenTextView resignFirstResponder];
  868. }
  869. }
  870. //==============================================================================
  871. void UIViewComponentPeer::drawRect (CGRect r)
  872. {
  873. if (r.size.width < 1.0f || r.size.height < 1.0f)
  874. return;
  875. CGContextRef cg = UIGraphicsGetCurrentContext();
  876. if (! component.isOpaque())
  877. CGContextClearRect (cg, CGContextGetClipBoundingBox (cg));
  878. CGContextConcatCTM (cg, CGAffineTransformMake (1, 0, 0, -1, 0, getComponent().getHeight()));
  879. CoreGraphicsContext g (cg, getComponent().getHeight());
  880. insideDrawRect = true;
  881. handlePaint (g);
  882. insideDrawRect = false;
  883. }
  884. bool UIViewComponentPeer::canBecomeKeyWindow()
  885. {
  886. return (getStyleFlags() & juce::ComponentPeer::windowIgnoresKeyPresses) == 0;
  887. }
  888. //==============================================================================
  889. void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, bool /*allowMenusAndBars*/)
  890. {
  891. displays->refresh();
  892. if (auto* peer = kioskModeComp->getPeer())
  893. {
  894. if (auto* uiViewPeer = dynamic_cast<UIViewComponentPeer*> (peer))
  895. [uiViewPeer->controller setNeedsStatusBarAppearanceUpdate];
  896. peer->setFullScreen (enableOrDisable);
  897. }
  898. }
  899. void Desktop::allowedOrientationsChanged()
  900. {
  901. // if the current orientation isn't allowed anymore then switch orientations
  902. if (! isOrientationEnabled (getCurrentOrientation()))
  903. {
  904. auto newOrientation = [this]
  905. {
  906. for (auto orientation : { upright, upsideDown, rotatedClockwise, rotatedAntiClockwise })
  907. if (isOrientationEnabled (orientation))
  908. return orientation;
  909. // you need to support at least one orientation
  910. jassertfalse;
  911. return upright;
  912. }();
  913. NSNumber* value = [NSNumber numberWithInt: (int) Orientations::convertFromJuce (newOrientation)];
  914. [[UIDevice currentDevice] setValue:value forKey:@"orientation"];
  915. [value release];
  916. }
  917. }
  918. //==============================================================================
  919. void UIViewComponentPeer::repaint (const Rectangle<int>& area)
  920. {
  921. if (insideDrawRect || ! MessageManager::getInstance()->isThisTheMessageThread())
  922. (new AsyncRepaintMessage (this, area))->post();
  923. else
  924. [view setNeedsDisplayInRect: convertToCGRect (area)];
  925. }
  926. void UIViewComponentPeer::performAnyPendingRepaintsNow()
  927. {
  928. }
  929. ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo)
  930. {
  931. return new UIViewComponentPeer (*this, styleFlags, (UIView*) windowToAttachTo);
  932. }
  933. //==============================================================================
  934. const int KeyPress::spaceKey = ' ';
  935. const int KeyPress::returnKey = 0x0d;
  936. const int KeyPress::escapeKey = 0x1b;
  937. const int KeyPress::backspaceKey = 0x7f;
  938. const int KeyPress::leftKey = 0x1000;
  939. const int KeyPress::rightKey = 0x1001;
  940. const int KeyPress::upKey = 0x1002;
  941. const int KeyPress::downKey = 0x1003;
  942. const int KeyPress::pageUpKey = 0x1004;
  943. const int KeyPress::pageDownKey = 0x1005;
  944. const int KeyPress::endKey = 0x1006;
  945. const int KeyPress::homeKey = 0x1007;
  946. const int KeyPress::deleteKey = 0x1008;
  947. const int KeyPress::insertKey = -1;
  948. const int KeyPress::tabKey = 9;
  949. const int KeyPress::F1Key = 0x2001;
  950. const int KeyPress::F2Key = 0x2002;
  951. const int KeyPress::F3Key = 0x2003;
  952. const int KeyPress::F4Key = 0x2004;
  953. const int KeyPress::F5Key = 0x2005;
  954. const int KeyPress::F6Key = 0x2006;
  955. const int KeyPress::F7Key = 0x2007;
  956. const int KeyPress::F8Key = 0x2008;
  957. const int KeyPress::F9Key = 0x2009;
  958. const int KeyPress::F10Key = 0x200a;
  959. const int KeyPress::F11Key = 0x200b;
  960. const int KeyPress::F12Key = 0x200c;
  961. const int KeyPress::F13Key = 0x200d;
  962. const int KeyPress::F14Key = 0x200e;
  963. const int KeyPress::F15Key = 0x200f;
  964. const int KeyPress::F16Key = 0x2010;
  965. const int KeyPress::F17Key = 0x2011;
  966. const int KeyPress::F18Key = 0x2012;
  967. const int KeyPress::F19Key = 0x2013;
  968. const int KeyPress::F20Key = 0x2014;
  969. const int KeyPress::F21Key = 0x2015;
  970. const int KeyPress::F22Key = 0x2016;
  971. const int KeyPress::F23Key = 0x2017;
  972. const int KeyPress::F24Key = 0x2018;
  973. const int KeyPress::F25Key = 0x2019;
  974. const int KeyPress::F26Key = 0x201a;
  975. const int KeyPress::F27Key = 0x201b;
  976. const int KeyPress::F28Key = 0x201c;
  977. const int KeyPress::F29Key = 0x201d;
  978. const int KeyPress::F30Key = 0x201e;
  979. const int KeyPress::F31Key = 0x201f;
  980. const int KeyPress::F32Key = 0x2020;
  981. const int KeyPress::F33Key = 0x2021;
  982. const int KeyPress::F34Key = 0x2022;
  983. const int KeyPress::F35Key = 0x2023;
  984. const int KeyPress::numberPad0 = 0x30020;
  985. const int KeyPress::numberPad1 = 0x30021;
  986. const int KeyPress::numberPad2 = 0x30022;
  987. const int KeyPress::numberPad3 = 0x30023;
  988. const int KeyPress::numberPad4 = 0x30024;
  989. const int KeyPress::numberPad5 = 0x30025;
  990. const int KeyPress::numberPad6 = 0x30026;
  991. const int KeyPress::numberPad7 = 0x30027;
  992. const int KeyPress::numberPad8 = 0x30028;
  993. const int KeyPress::numberPad9 = 0x30029;
  994. const int KeyPress::numberPadAdd = 0x3002a;
  995. const int KeyPress::numberPadSubtract = 0x3002b;
  996. const int KeyPress::numberPadMultiply = 0x3002c;
  997. const int KeyPress::numberPadDivide = 0x3002d;
  998. const int KeyPress::numberPadSeparator = 0x3002e;
  999. const int KeyPress::numberPadDecimalPoint = 0x3002f;
  1000. const int KeyPress::numberPadEquals = 0x30030;
  1001. const int KeyPress::numberPadDelete = 0x30031;
  1002. const int KeyPress::playKey = 0x30000;
  1003. const int KeyPress::stopKey = 0x30001;
  1004. const int KeyPress::fastForwardKey = 0x30002;
  1005. const int KeyPress::rewindKey = 0x30003;
  1006. } // namespace juce