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.

juce_ios_UIViewComponentPeer.mm 37KB

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