Audio plugin host https://kx.studio/carla
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.

1179 lines
40KB

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