// // UIComputerView.m // Moonlight // // Created by Diego Waxemberg on 10/22/14. // Copyright (c) 2014 Moonlight Stream. All rights reserved. // #import "UIComputerView.h" @implementation UIComputerView { TemporaryHost* _host; UIImageView* _hostIcon; UILabel* _hostLabel; UIImageView* _hostOverlay; UIActivityIndicatorView* _hostSpinner; id _callback; CGSize _labelSize; } static const float REFRESH_CYCLE = 2.0f; #if TARGET_OS_TV static const int ITEM_PADDING = 50; static const int LABEL_DY = 40; #else static const int ITEM_PADDING = 0; static const int LABEL_DY = 20; #endif - (id) init { self = [super init]; #if TARGET_OS_TV self.frame = CGRectMake(0, 0, 400, 400); #else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { self.frame = CGRectMake(0, 0, 200, 200); } else { self.frame = CGRectMake(0, 0, 100, 100); } #endif _hostIcon = [[UIImageView alloc] initWithFrame:self.frame]; [_hostIcon setImage:[UIImage imageNamed:@"Computer"]]; self.layer.shadowColor = [[UIColor blackColor] CGColor]; self.layer.shadowOffset = CGSizeMake(5,8); self.layer.shadowOpacity = 0.3; [self addTarget:self action:@selector(hostButtonSelected:) forControlEvents:UIControlEventTouchDown]; [self addTarget:self action:@selector(hostButtonDeselected:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchDragExit]; _hostLabel = [[UILabel alloc] init]; _hostLabel.textColor = [UIColor whiteColor]; _hostOverlay = [[UIImageView alloc] initWithFrame:CGRectMake(self.frame.size.width / 3, _hostIcon.frame.size.height / 4, _hostIcon.frame.size.width / 3, self.frame.size.height / 3)]; _hostSpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; [_hostSpinner setFrame:_hostOverlay.frame]; _hostSpinner.userInteractionEnabled = NO; _hostSpinner.hidesWhenStopped = YES; [self addSubview:_hostLabel]; [self addSubview:_hostIcon]; #if TARGET_OS_TV _hostIcon.clipsToBounds = NO; _hostIcon.adjustsImageWhenAncestorFocused = YES; _hostIcon.masksFocusEffectToContents = YES; self.adjustsImageWhenHighlighted = NO; _hostOverlay.masksFocusEffectToContents = YES; _hostOverlay.adjustsImageWhenAncestorFocused = NO; [_hostIcon.overlayContentView addSubview:_hostOverlay]; [_hostIcon.overlayContentView addSubview:_hostSpinner]; #else [self addSubview:_hostOverlay]; [self addSubview:_hostSpinner]; if (@available(iOS 13.4.1, *)) { // Allow the button style to change when moused over self.pointerInteractionEnabled = YES; } #endif return self; } - (void) hostButtonSelected:(id)sender { _hostIcon.layer.opacity = 0.5f; _hostSpinner.layer.opacity = 0.5f; _hostOverlay.layer.opacity = 0.5f; } - (void) hostButtonDeselected:(id)sender { _hostIcon.layer.opacity = 1.0f; _hostSpinner.layer.opacity = 1.0f; _hostOverlay.layer.opacity = 1.0f; } - (id) initForAddWithCallback:(id)callback { self = [self init]; _callback = callback; [self addTarget:self action:@selector(addClicked) forControlEvents:UIControlEventPrimaryActionTriggered]; [_hostLabel setText:@"Add Host Manually"]; [_hostLabel sizeToFit]; [_hostOverlay setImage:[UIImage imageNamed:@"AddOverlayIcon"]]; [self updateBounds]; return self; } - (id) initWithComputer:(TemporaryHost*)host andCallback:(id)callback { self = [self init]; _host = host; _callback = callback; // Use UIContextMenuInteraction on iOS 13.0+ and a standard UILongPressGestureRecognizer // for tvOS devices and iOS prior to 13.0. #if !TARGET_OS_TV if (@available(iOS 13.0, *)) { UIContextMenuInteraction* rightClickInteraction = [[UIContextMenuInteraction alloc] initWithDelegate:self]; [self addInteraction:rightClickInteraction]; } else #endif { UILongPressGestureRecognizer* longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(hostLongClicked:)]; [self addGestureRecognizer:longPressRecognizer]; } [self addTarget:self action:@selector(hostClicked) forControlEvents:UIControlEventPrimaryActionTriggered]; [self updateContentsForHost:host]; return self; } - (void)didMoveToSuperview { // Start our update loop when we are added to our cell if (self.superview != nil && _host != nil) { [self updateLoop]; } } - (void) updateBounds { float x = FLT_MAX; float y = FLT_MAX; float width = 0; float height; float iconX = _hostIcon.frame.origin.x + _hostIcon.frame.size.width / 2; _hostLabel.center = CGPointMake(iconX, _hostIcon.frame.origin.y + _hostIcon.frame.size.height + LABEL_DY); x = MIN(x, _hostIcon.frame.origin.x); x = MIN(x, _hostLabel.frame.origin.x); y = MIN(y, _hostIcon.frame.origin.y); y = MIN(y, _hostLabel.frame.origin.y); width = MAX(width, _hostIcon.frame.size.width); width = MAX(width, _hostLabel.frame.size.width); height = _hostIcon.frame.size.height + _hostLabel.frame.size.height + LABEL_DY / 2; self.bounds = CGRectMake(x - ITEM_PADDING, y - ITEM_PADDING, width + 2 * ITEM_PADDING, height + 2 * ITEM_PADDING); } - (void) updateContentsForHost:(TemporaryHost*)host { _hostLabel.text = _host.name; [_hostLabel sizeToFit]; if (host.state == StateOnline) { [_hostSpinner stopAnimating]; if (host.pairState == PairStateUnpaired) { [_hostOverlay setImage:[UIImage imageNamed:@"LockedOverlayIcon"]]; } else { [_hostOverlay setImage:nil]; } } else if (host.state == StateOffline) { [_hostSpinner stopAnimating]; [_hostOverlay setImage:[UIImage imageNamed:@"ErrorOverlayIcon"]]; } else { [_hostSpinner startAnimating]; } [self updateBounds]; } - (void) updateLoop { // Stop immediately if the view has been detached if (self.superview == nil) { return; } [self updateContentsForHost:_host]; // Queue the next refresh cycle [self performSelector:@selector(updateLoop) withObject:self afterDelay:REFRESH_CYCLE]; } - (void) hostLongClicked:(UILongPressGestureRecognizer*)gesture { if (gesture.state == UIGestureRecognizerStateBegan) { [_callback hostLongClicked:_host view:self]; } } #if !TARGET_OS_TV - (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location { // We don't want to trigger the primary action at this point, so cancel // tracking touch on this view now. This will also have the (intended) // effect of removing the touch highlight on this view. [self cancelTrackingWithEvent:nil]; [_callback hostLongClicked:_host view:self]; return nil; } #endif - (void) hostClicked { [_callback hostClicked:_host view:self]; } - (void) addClicked { [_callback addHostClicked]; } @end