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.

1146 lines
38KB

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