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