diff --git a/Limelight/Base.lproj/iPad.storyboard b/Limelight/Base.lproj/iPad.storyboard index 0df3b82d..4e5b08ad 100644 --- a/Limelight/Base.lproj/iPad.storyboard +++ b/Limelight/Base.lproj/iPad.storyboard @@ -1,8 +1,8 @@ - + - + @@ -12,7 +12,7 @@ - + @@ -899,7 +899,7 @@ - + @@ -938,7 +938,7 @@ - + @@ -960,7 +960,7 @@ - + @@ -1163,7 +1163,7 @@ - + - + @@ -320,26 +320,26 @@ - - + + - + - + + + + - + - + + + + - + - + - + - + + + + - + - + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -477,6 +520,8 @@ + + @@ -492,7 +537,7 @@ - + @@ -1410,13 +1455,13 @@ - + - + - + diff --git a/Limelight/Input/CustomOSC/OSCProfilesManager.m b/Limelight/Input/CustomOSC/OSCProfilesManager.m index b384f3db..d284738a 100644 --- a/Limelight/Input/CustomOSC/OSCProfilesManager.m +++ b/Limelight/Input/CustomOSC/OSCProfilesManager.m @@ -241,7 +241,7 @@ static CGRect streamViewBounds; - (bool) updateSelectedProfile:(NSMutableArray *) oscButtonLayers { - NSMutableArray* buttonStatesEncoded = [self convertOnScreenControllerAndButtonsToButtonStates:oscButtonLayers]; + NSMutableArray* buttonStatesEncoded = [self convertOnScreenControllerAndWidgetsToButtonStates:oscButtonLayers]; if([self getIndexOfSelectedProfile]==0) return false; OSCProfile *newProfile = [[OSCProfile alloc] initWithName:[self getSelectedProfile].name buttonStates:buttonStatesEncoded isSelected:YES]; // create a new 'OSCProfile'. Set the array of encoded button states created above to the 'buttonStates' property of the new profile, along with a 'name'. Set 'isSelected' argument to YES which will set this saved profile as the one that will show up in the game stream view @@ -258,7 +258,7 @@ static CGRect streamViewBounds; - (void) saveProfileWithName:(NSString*)name andButtonLayers:(NSMutableArray *)oscButtonLayers { - NSMutableArray* buttonStatesEncoded = [self convertOnScreenControllerAndButtonsToButtonStates:oscButtonLayers]; + NSMutableArray* buttonStatesEncoded = [self convertOnScreenControllerAndWidgetsToButtonStates:oscButtonLayers]; OSCProfile *newProfile = [[OSCProfile alloc] initWithName:name buttonStates:buttonStatesEncoded isSelected:YES]; // create a new 'OSCProfile'. Set the array of encoded button states created above to the 'buttonStates' property of the new profile, along with a 'name'. Set 'isSelected' argument to YES which will set this saved profile as the one that will show up in the game stream view /* set all saved OSCProfiles 'isSelected' property to NO since the new profile you're adding will be set as the selected profile */ @@ -308,7 +308,7 @@ static CGRect streamViewBounds; return position; } -- (NSMutableArray* ) convertOnScreenControllerAndButtonsToButtonStates:(NSMutableArray *) oscButtonLayers { +- (NSMutableArray* ) convertOnScreenControllerAndWidgetsToButtonStates:(NSMutableArray *) oscButtonLayers { /* iterate through each OSC button the user sees on screen, create an 'OnScreenButtonState' object from each button, encode each object, and then add each object to an array */ /* NSSet *validPositionButtonNames = [NSSet setWithObjects: @@ -357,6 +357,7 @@ static CGRect streamViewBounds; buttonState.heightFactor = widgetView.heightFactor; buttonState.backgroundAlpha = widgetView.backgroundAlpha; buttonState.borderWidth = widgetView.borderWidth; + buttonState.vibrationStyle = widgetView.vibrationStyle; buttonState.sensitivityFactor = widgetView.sensitivityFactor; buttonState.stickIndicatorOffset = widgetView.stickIndicatorOffset; buttonState.widgetShape = widgetView.shape; diff --git a/Limelight/Input/CustomOSC/OnScreenButtonState.h b/Limelight/Input/CustomOSC/OnScreenButtonState.h index ebff9ce1..ac308b1d 100644 --- a/Limelight/Input/CustomOSC/OnScreenButtonState.h +++ b/Limelight/Input/CustomOSC/OnScreenButtonState.h @@ -37,6 +37,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) CGFloat oscLayerSizeFactor; // for OnScreenController CALayer @property (nonatomic, assign) CGFloat backgroundAlpha; // for OnScreenController CALayer +@property (nonatomic, assign) uint8_t vibrationStyle; // for OnScreenController CALayer // @property (nonatomic, assign) BOOL hasValidPosition; diff --git a/Limelight/Input/CustomOSC/OnScreenButtonState.m b/Limelight/Input/CustomOSC/OnScreenButtonState.m index a3f291cb..dd085904 100644 --- a/Limelight/Input/CustomOSC/OnScreenButtonState.m +++ b/Limelight/Input/CustomOSC/OnScreenButtonState.m @@ -33,6 +33,7 @@ [encoder encodeObject:self.name forKey:@"name"]; [encoder encodeObject:self.alias forKey:@"alias"]; [encoder encodeInt:self.buttonType forKey:@"buttonType"]; + [encoder encodeInt:self.vibrationStyle forKey:@"vibrationStyle"]; [encoder encodeCGPoint:self.position forKey:@"position"]; [encoder encodeBool:self.isHidden forKey:@"isHidden"]; [encoder encodeFloat:self.widthFactor forKey:@"widthFactor"]; @@ -51,6 +52,7 @@ self.name = [decoder decodeObjectForKey:@"name"]; self.alias = [decoder decodeObjectForKey:@"alias"]; self.buttonType = [decoder decodeIntForKey:@"buttonType"]; + self.vibrationStyle = [decoder decodeIntForKey:@"vibrationStyle"]; self.position = [decoder decodeCGPointForKey:@"position"]; self.isHidden = [decoder decodeBoolForKey:@"isHidden"]; self.widthFactor = [decoder decodeFloatForKey:@"widthFactor"]; diff --git a/Limelight/Input/OnScreenWidgetView.swift b/Limelight/Input/OnScreenWidgetView.swift index 423fe4cc..ac4bfa57 100644 --- a/Limelight/Input/OnScreenWidgetView.swift +++ b/Limelight/Input/OnScreenWidgetView.swift @@ -41,6 +41,7 @@ import UIKit @objc public var heightFactor: CGFloat = 1.0 @objc public var borderWidth: CGFloat = 0.0 @objc public var backgroundAlpha: CGFloat = 0.5 + @objc public var vibrationStyle: Int = 6 @objc public var latestTouchLocation: CGPoint @objc public var selfViewOnTheRight: Bool = false @objc public var shape: String = "default" @@ -50,10 +51,9 @@ import UIKit private let appWindow: UIView - - - + private var vibrationGenerator = UIImpactFeedbackGenerator(style: .light) + private var vibrationOn: Bool = false // for all touchPad or buttons hybrid with touchPads @objc public var hasStickIndicator: Bool = false @@ -124,6 +124,7 @@ import UIKit @objc public let buttonDownVisualEffectLayer = CAShapeLayer() private var buttonDownVisualEffectWidth: CGFloat + @objc init(cmdString: String, buttonLabel: String, shape:String) { self.cmdString = cmdString @@ -226,9 +227,25 @@ import UIKit fatalError("init(coder:) has not been implemented") } - // ====================================================================================================== + @objc public func setVibration(style: Int) { + if #available(iOS 13.0, *) { + vibrationOn = style < UIImpactFeedbackGenerator.FeedbackStyle.rigid.rawValue + 1 + } else { + vibrationOn = style < UIImpactFeedbackGenerator.FeedbackStyle.heavy.rawValue + 1 + }; + vibrationStyle = style; + if #available(iOS 13.0, *) { + print("rigid value \(UIImpactFeedbackGenerator.FeedbackStyle.rigid.rawValue)") + } else { + // Fallback on earlier versions + }; + if vibrationOn { + vibrationGenerator = UIImpactFeedbackGenerator(style: UIImpactFeedbackGenerator.FeedbackStyle(rawValue: style) ?? UIImpactFeedbackGenerator.FeedbackStyle.light) + } + } + @objc public func setLocation(position: CGPoint) { /* NSLayoutConstraint.activate([ @@ -437,6 +454,12 @@ import UIKit self.layer.superlayer?.addSublayer(indicatorBorder) indicatorBorder.position = CGPointMake(CGRectGetMinX(self.frame)+touchBeganLocation.x, CGRectGetMinY(self.frame)+touchBeganLocation.y) + if vibrationOn { + vibrationGenerator.prepare() + vibrationGenerator.impactOccurred() + print("vibrationInstance: \(vibrationGenerator)") + } + return indicatorBorder } @@ -787,7 +810,7 @@ import UIKit } //==== wholeButtonPress visual effect============================================= - private func buttonDownVisualEffect() { + private func buttonDownEffect() { // setupBorderLayer() CATransaction.begin() CATransaction.setDisableActions(true) @@ -795,6 +818,11 @@ import UIKit buttonDownVisualEffectLayer.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)) // update position every time we press down the button buttonDownVisualEffectLayer.borderWidth = self.buttonDownVisualEffectWidth // this will show the visual effect buttonDownVisualEffectLayer.borderColor = moonlightPurple + if vibrationOn { + vibrationGenerator.prepare() + vibrationGenerator.impactOccurred() + print("vibrationInstance: \(vibrationGenerator)") + } CATransaction.commit() } @@ -1019,7 +1047,7 @@ import UIKit // this will also deal with button events if self.widgetType == WidgetTypeEnum.button && !self.comboButtonStrings.isEmpty { - self.buttonDownVisualEffect() + self.buttonDownEffect() self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings) } @@ -1034,7 +1062,7 @@ import UIKit } // here is in edit mode: else{ - self.buttonDownVisualEffect() + self.buttonDownEffect() NotificationCenter.default.post(name: Notification.Name("OnScreenWidgetViewSelected"),object: self) // inform layout tool controller to fetch button size factors. self will be passed as the object of the notification } } diff --git a/Limelight/Input/StreamView.m b/Limelight/Input/StreamView.m index eac8cded..11dd774b 100644 --- a/Limelight/Input/StreamView.m +++ b/Limelight/Input/StreamView.m @@ -411,6 +411,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; widgetView.widthFactor = buttonState.widthFactor; widgetView.heightFactor = buttonState.heightFactor; widgetView.borderWidth = buttonState.borderWidth; + [widgetView setVibrationWithStyle:buttonState.vibrationStyle]; widgetView.sensitivityFactor = buttonState.sensitivityFactor; widgetView.stickIndicatorOffset = buttonState.stickIndicatorOffset; widgetView.minStickOffset = buttonState.minStickOffset; diff --git a/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h b/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h index 8900b2d0..4e680d29 100644 --- a/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h +++ b/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h @@ -75,6 +75,8 @@ NS_ASSUME_NONNULL_BEGIN @property (weak, nonatomic) IBOutlet UISlider *sensitivitySlider; @property (weak, nonatomic) IBOutlet UIStackView *sensitivityStack; +@property (strong, nonatomic) IBOutlet UISegmentedControl *vibrationStyleSelector; +@property (strong, nonatomic) IBOutlet UIStackView *vibrationStyleStack; diff --git a/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m b/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m index 8705c345..56a47743 100644 --- a/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m +++ b/Limelight/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m @@ -104,6 +104,7 @@ widgetView.widthFactor = buttonState.widthFactor; widgetView.heightFactor = buttonState.heightFactor; widgetView.borderWidth = buttonState.borderWidth; + [widgetView setVibrationWithStyle:buttonState.vibrationStyle]; widgetView.sensitivityFactor = buttonState.sensitivityFactor; widgetView.stickIndicatorOffset = buttonState.stickIndicatorOffset; widgetView.minStickOffset = buttonState.minStickOffset; @@ -614,6 +615,7 @@ CGRect newFrame = view.frame; newFrame.size = fittingSize; view.frame = newFrame; + if([self isIPhone]) [self updateClippedMaskForView:view]; } - (void)widgetViewTapped: (NSNotification *)notification{ @@ -663,6 +665,10 @@ [self.widgetHeightLabel setText:[LocalizationHelper localizedStringForKey:@"Height: %.2f", self->selectedWidgetView.heightFactor]]; [self.widgetAlphaLabel setText:[LocalizationHelper localizedStringForKey:@"Alpha: %.2f", self->selectedWidgetView.backgroundAlpha]]; [self.widgetBorderWidthLabel setText:[LocalizationHelper localizedStringForKey:@"Border Width: %.2f", self->selectedWidgetView.borderWidth]]; + if([self isIPhone]){ + self.vibrationStyleSelector.selectedSegmentIndex = self->selectedWidgetView.vibrationStyle; + //[self->selectedWidgetView setva] + } } - (void)setControllerCALayerSliderValues: (NSNotification *)notification{ @@ -692,62 +698,68 @@ [self.widgetAlphaLabel setText:[LocalizationHelper localizedStringForKey:@"Alpha: %.2f", alpha]]; } -- (void)widgetSizeSliderMoved{ - [self.widgetSizeLabel setText:[LocalizationHelper localizedStringForKey:@"Size: %.2f", self.widgetSizeSlider.value]]; - [self.widgetHeightLabel setText:[LocalizationHelper localizedStringForKey:@"Height: %.2f", self.widgetSizeSlider.value]]; // resizing the whole button - [self.widgetHeightSlider setValue: self.widgetSizeSlider.value]; +- (void)widgetSizeSliderMoved:(UISlider* )sender{ + [self.widgetSizeLabel setText:[LocalizationHelper localizedStringForKey:@"Size: %.2f", sender.value]]; + [self.widgetHeightLabel setText:[LocalizationHelper localizedStringForKey:@"Height: %.2f", sender.value]]; // resizing the whole button + [self.widgetHeightSlider setValue: sender.value]; if(self->selectedWidgetView != nil && self->widgetViewSelected){ self->selectedWidgetView.translatesAutoresizingMaskIntoConstraints = true; // this is mandatory to prevent unexpected key view location change // when adjusting width, the widgetView height will be syncronized - self->selectedWidgetView.widthFactor = self->selectedWidgetView.heightFactor = self.widgetSizeSlider.value; + self->selectedWidgetView.widthFactor = self->selectedWidgetView.heightFactor = sender.value; [self->selectedWidgetView resizeWidgetView]; } if(self->selectedControllerLayer != nil && self->controllerLayerSelected){ - [self.layoutOSC resizeControllerLayerWith:self->selectedControllerLayer and:self.widgetSizeSlider.value]; + [self.layoutOSC resizeControllerLayerWith:self->selectedControllerLayer and:sender.value]; } } -- (void)widgetHeightSliderMoved{ - [self.widgetHeightLabel setText:[LocalizationHelper localizedStringForKey:@"Height: %.2f", self.widgetHeightSlider.value]]; +- (void)widgetHeightSliderMoved:(UISlider* )sender{ + [self.widgetHeightLabel setText:[LocalizationHelper localizedStringForKey:@"Height: %.2f", sender.value]]; if(self->selectedWidgetView != nil && self->widgetViewSelected){ self->selectedWidgetView.translatesAutoresizingMaskIntoConstraints = true; // this is mandatory to prevent unexpected key view location change if([self->selectedWidgetView.shape isEqualToString:@"round"]) return; // don't change height for round buttons, except for dPad buttons which are in rectangle shape - self->selectedWidgetView.heightFactor = self.widgetHeightSlider.value; + self->selectedWidgetView.heightFactor = sender.value; [self->selectedWidgetView resizeWidgetView]; } } -- (void)widgetAlphaSliderMoved{ - [self.widgetAlphaLabel setText:[LocalizationHelper localizedStringForKey:@"Alpha: %.2f", self.widgetAlphaSlider.value]]; +- (void)widgetAlphaSliderMoved:(UISlider* )sender{ + [self.widgetAlphaLabel setText:[LocalizationHelper localizedStringForKey:@"Alpha: %.2f", sender.value]]; if(self->selectedWidgetView != nil && self->widgetViewSelected){ - [self->selectedWidgetView adjustTransparencyWithAlpha:self.widgetAlphaSlider.value]; + [self->selectedWidgetView adjustTransparencyWithAlpha:sender.value]; } if(self->selectedControllerLayer != nil && self->controllerLayerSelected){ - [self.layoutOSC adjustControllerLayerOpacityWith:self->selectedControllerLayer and:self.widgetAlphaSlider.value]; + [self.layoutOSC adjustControllerLayerOpacityWith:self->selectedControllerLayer and:sender.value]; } return; } -- (void)widgetBorderWidthSliderMoved{ - [self.widgetBorderWidthLabel setText:[LocalizationHelper localizedStringForKey:@"Border Width: %.2f", self.widgetBorderWidthSlider.value]]; +- (void)widgetBorderWidthSliderMoved:(UISlider* )sender{ + [self.widgetBorderWidthLabel setText:[LocalizationHelper localizedStringForKey:@"Border Width: %.2f", sender.value]]; if(self->selectedWidgetView != nil && self->widgetViewSelected){ - [self->selectedWidgetView adjustBorderWithWidth:self.widgetBorderWidthSlider.value]; + [self->selectedWidgetView adjustBorderWithWidth:sender.value]; } return; } -- (void)sensitivitySliderMoved{ - [self.sensitivityLabel setText:[LocalizationHelper localizedStringForKey:@"Sensitivity: %.2f", self.sensitivitySlider.value]]; - if(self->selectedWidgetView != nil && self->widgetViewSelected) self->selectedWidgetView.sensitivityFactor = self.sensitivitySlider.value; +- (void)vibrationStyleChanged:(UISegmentedControl* )sender{ + if(self->selectedWidgetView != nil && self->widgetViewSelected){ + [self->selectedWidgetView setVibrationWithStyle:sender.selectedSegmentIndex]; + } +} + +- (void)sensitivitySliderMoved:(UISlider* )sender{ + [self.sensitivityLabel setText:[LocalizationHelper localizedStringForKey:@"Sensitivity: %.2f", sender.value]]; + if(self->selectedWidgetView != nil && self->widgetViewSelected) self->selectedWidgetView.sensitivityFactor = sender.value; return; } -- (void)stickIndicatorOffsetSliderMoved{ - [self.stickIndicatorOffsetLabel setText:[LocalizationHelper localizedStringForKey:@"Indicator Offset: %.0f", self.stickIndicatorOffsetSlider.value]]; +- (void)stickIndicatorOffsetSliderMoved:(UISlider* )sender{ + [self.stickIndicatorOffsetLabel setText:[LocalizationHelper localizedStringForKey:@"Indicator Offset: %.0f", sender.value]]; if(self->selectedWidgetView != nil && self->widgetViewSelected){ - self->selectedWidgetView.stickIndicatorOffset = self.stickIndicatorOffsetSlider.value; + self->selectedWidgetView.stickIndicatorOffset = sender.value; [self->selectedWidgetView updateStickIndicator]; } return; @@ -790,17 +802,10 @@ self.widgetPanelStack.layer.cornerRadius = 16; self.widgetPanelStack.clipsToBounds = YES; - /* - [NSLayoutConstraint activateConstraints:@[ - [self.widgetPanelStack.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor constant:0], - [self.widgetPanelStack.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:120], - ]]; - */ - self.widgetPanelStack.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5]; [self.currentProfileLabel setText:[LocalizationHelper localizedStringForKey:@"Profile: %@",[profilesManager getSelectedProfile].name]]; - self.currentProfileLabel.layer.cornerRadius = 12; + self.currentProfileLabel.layer.cornerRadius = [self isIPhone] ? 9 : 12; self.currentProfileLabel.clipsToBounds = YES; self.widgetSizeStack.userInteractionEnabled = YES; @@ -813,40 +818,77 @@ } } - [self.widgetSizeSlider addTarget:self action:@selector(widgetSizeSliderMoved) forControlEvents:(UIControlEventValueChanged)]; + [self.widgetSizeSlider addTarget:self action:@selector(widgetSizeSliderMoved:) forControlEvents:(UIControlEventValueChanged)]; self.widgetSizeLabel.text = [LocalizationHelper localizedStringForKey:@"Size"]; self.widgetSizeStack.hidden = _quickSwitchEnabled; - [self.widgetHeightSlider addTarget:self action:@selector(widgetHeightSliderMoved) forControlEvents:(UIControlEventValueChanged)]; + [self.widgetHeightSlider addTarget:self action:@selector(widgetHeightSliderMoved:) forControlEvents:(UIControlEventValueChanged)]; self.widgetHeightLabel.text = [LocalizationHelper localizedStringForKey:@"Height"]; self.widgetHeightStack.hidden = _quickSwitchEnabled; - [self.widgetAlphaSlider addTarget:self action:@selector(widgetAlphaSliderMoved) forControlEvents:(UIControlEventValueChanged)]; + [self.widgetAlphaSlider addTarget:self action:@selector(widgetAlphaSliderMoved:) forControlEvents:(UIControlEventValueChanged)]; self.widgetAlphaLabel.text = [LocalizationHelper localizedStringForKey:@"Alpha"]; - [self.widgetBorderWidthSlider addTarget:self action:@selector(widgetBorderWidthSliderMoved) forControlEvents:(UIControlEventValueChanged)]; + [self.widgetBorderWidthSlider addTarget:self action:@selector(widgetBorderWidthSliderMoved:) forControlEvents:(UIControlEventValueChanged)]; self.widgetBorderWidthLabel.text = [LocalizationHelper localizedStringForKey:@"Border Width"]; self.borderWidthAlphaStack.hidden = _quickSwitchEnabled; - [self.sensitivitySlider addTarget:self action:@selector(sensitivitySliderMoved) forControlEvents:(UIControlEventValueChanged)]; + [self.sensitivitySlider addTarget:self action:@selector(sensitivitySliderMoved:) forControlEvents:(UIControlEventValueChanged)]; self.sensitivityLabel.text = [LocalizationHelper localizedStringForKey:@"Sensitivity"]; self.sensitivityStack.hidden = YES; // stick indicator offset slider //self.stickIndicatorOffsetSlider.hidden = YES; - [self.stickIndicatorOffsetSlider addTarget:self action:@selector(stickIndicatorOffsetSliderMoved) forControlEvents:(UIControlEventValueChanged)]; + [self.stickIndicatorOffsetSlider addTarget:self action:@selector(stickIndicatorOffsetSliderMoved:) forControlEvents:(UIControlEventValueChanged)]; self.stickIndicatorOffsetLabel.text = [LocalizationHelper localizedStringForKey:@"Indicator Offset"]; self.stickIndicatorOffsetStack.hidden = YES; + + if([self isIPhone]){ + [self.vibrationStyleSelector addTarget:self action:@selector(vibrationStyleChanged:) forControlEvents:(UIControlEventValueChanged)]; + self.vibrationStyleStack.hidden = NO; + } + [self.view bringSubviewToFront:self.toolbarRootView]; [self.view insertSubview:self.widgetPanelStack belowSubview:self.toolbarRootView]; self.widgetPanelStack.translatesAutoresizingMaskIntoConstraints = YES; + + CGRect frame = CGRectMake(0, 0, self.widgetPanelStack.frame.size.width, self.widgetPanelStack.frame.size.height); //frame.origin = CGPointMake(self.view.bounds.size.width/2, 100); frame.origin = CGPointMake(self.view.bounds.size.width/2-self.widgetPanelStack.frame.size.width/2, 100); self.widgetPanelStack.frame = frame; [self autoFitView:self.widgetPanelStack]; + + if([self isIPhone]) { + for(UIView* view in _widgetPanelStack.arrangedSubviews){ + view.transform = CGAffineTransformMakeScale(0.83, 0.83); + } + self.widgetPanelStack.layoutMargins = UIEdgeInsetsMake(3, 2, 7, 2); + + self.widgetPanelStack.clipsToBounds = YES; + + CAShapeLayer *maskLayer = [CAShapeLayer layer]; + CGRect visibleRect = CGRectInset(self.widgetPanelStack.bounds, 40, 0); // 左右各裁掉 + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:visibleRect cornerRadius:12]; + maskLayer.path = path.CGPath; + + self.widgetPanelStack.layer.mask = maskLayer; + } +} + +- (void)updateClippedMaskForView:(UIView* )view{ + CAShapeLayer *maskLayer = [CAShapeLayer layer]; + view.layer.mask = nil; + CGRect visibleRect = CGRectInset(view.bounds, 40, 0); // 左右各裁掉 20pt + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:visibleRect cornerRadius:12]; + maskLayer.path = path.CGPath; + view.layer.mask = maskLayer; +} + +- (BOOL)isIPhone{ + return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone); } - (void)handleProfileTablViewDismiss{ @@ -901,7 +943,7 @@ _oscProfilesTableViewController = [storyboard instantiateViewControllerWithIdentifier:@"OSCProfilesTableViewController"]; _oscProfilesTableViewController.streamViewBounds = self.view.bounds; - _oscProfilesTableViewController.needToUpdateOscLayoutTVC = ^() { // a block that will be called when the modally presented 'OSCProfilesTableViewController' VC is dismissed. By the time the 'OSCProfilesTableViewController' VC is dismissed the user would have potentially selected a different OSC ofile with a different layout and they want to see this layout on this 'LayoutOnScreenControlsViewController.' This block of code will load the profile and then hide/show and move each OSC button to their appropriate position + _oscProfilesTableViewController.needToUpdateOscLayoutTVC = ^() { // a block that will be called when the modally presented 'OSCProfilesTableViewController' VC is dismissed. By the time the 'OSCProfilesTableViewController' VC is dismissed the user would have potentially selected a different OSC ofile with a different layout and they want to see this layout on this 'LayoutOnScreenControlsViewController.' This block of code will load the proffile and then hide/show and move each OSC button to their appropriate position [self.layoutOSC updateControls]; // creates and saves a 'Default' OSC profile or loads the one the user selected on the previous screen [self addInnerAnalogSticksToOuterAnalogLayers]; @@ -1014,6 +1056,7 @@ // removing keyboard buttons objs // UITouch *touch = [touches anyObject]; // Get the first touch in the set + _widgetPanelStack.userInteractionEnabled = true; if(selectedWidgetView) [self.view insertSubview:selectedWidgetView belowSubview:_widgetPanelStack]; diff --git a/Limelight/ViewControllers/CustomOSCViewControl/WidgetPanelStackView.h b/Limelight/ViewControllers/CustomOSCViewControl/WidgetPanelStackView.h new file mode 100644 index 00000000..0d9971e5 --- /dev/null +++ b/Limelight/ViewControllers/CustomOSCViewControl/WidgetPanelStackView.h @@ -0,0 +1,14 @@ +// +// WidgetPanelStackView.h +// Voidex +// +// Created by True砖家 on 2025/6/19. +// Copyright © True砖家 on Bilibili. All rights reserved. +// + +#import + + +@interface WidgetPanelStackView : UIStackView + +@end diff --git a/Limelight/ViewControllers/CustomOSCViewControl/WidgetPanelStackView.m b/Limelight/ViewControllers/CustomOSCViewControl/WidgetPanelStackView.m new file mode 100644 index 00000000..38cf4c39 --- /dev/null +++ b/Limelight/ViewControllers/CustomOSCViewControl/WidgetPanelStackView.m @@ -0,0 +1,26 @@ +// +// WidgetPanelStackView.h +// Voidex +// +// Created by True砖家 on 2025/6/19. +// Copyright © True砖家 on Bilibili. All rights reserved. +// + + +#import "WidgetPanelStackView.h" + +@implementation WidgetPanelStackView + +- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event { + if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPhone) return [super pointInside:point withEvent:event]; + BOOL touchedWithinMask; + CAShapeLayer *mask = (CAShapeLayer *)self.layer.mask; + if (mask && mask.path) { + // 注意:mask 的坐标系是以 view.layer 为基准的 + touchedWithinMask = CGPathContainsPoint(mask.path, NULL, point, NO); + } + else touchedWithinMask = YES; + return touchedWithinMask; +} + +@end diff --git a/Limelight/mul.lproj/iPhone.xcstrings b/Limelight/mul.lproj/iPhone.xcstrings index b476b999..14adfc95 100644 --- a/Limelight/mul.lproj/iPhone.xcstrings +++ b/Limelight/mul.lproj/iPhone.xcstrings @@ -1831,6 +1831,186 @@ } } }, + "QRV-je-HEZ.segmentTitles[0]" : { + "comment" : "Class = \"UISegmentedControl\"; QRV-je-HEZ.segmentTitles[0] = \"light\"; ObjectID = \"QRV-je-HEZ\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "light" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "轻微" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "轻微" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "轻微" + } + } + } + }, + "QRV-je-HEZ.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; QRV-je-HEZ.segmentTitles[1] = \"medium\"; ObjectID = \"QRV-je-HEZ\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "medium" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "中等" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "中等" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "中等" + } + } + } + }, + "QRV-je-HEZ.segmentTitles[2]" : { + "comment" : "Class = \"UISegmentedControl\"; QRV-je-HEZ.segmentTitles[2] = \"heavy\"; ObjectID = \"QRV-je-HEZ\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "heavy" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "强烈" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "强烈" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "强烈" + } + } + } + }, + "QRV-je-HEZ.segmentTitles[3]" : { + "comment" : "Class = \"UISegmentedControl\"; QRV-je-HEZ.segmentTitles[3] = \"soft\"; ObjectID = \"QRV-je-HEZ\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "soft" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "柔和" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "柔和" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "柔和" + } + } + } + }, + "QRV-je-HEZ.segmentTitles[4]" : { + "comment" : "Class = \"UISegmentedControl\"; QRV-je-HEZ.segmentTitles[4] = \"rigid\"; ObjectID = \"QRV-je-HEZ\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "rigid" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "刚性" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "刚性" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "刚性" + } + } + } + }, + "QRV-je-HEZ.segmentTitles[5]" : { + "comment" : "Class = \"UISegmentedControl\"; QRV-je-HEZ.segmentTitles[5] = \"off\"; ObjectID = \"QRV-je-HEZ\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "off" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭" + } + } + } + }, "S7X-Kt-2jd.text" : { "comment" : "Class = \"UILabel\"; text = \"Show Local Keyboard Toolbar \"; ObjectID = \"S7X-Kt-2jd\";", "extractionState" : "extracted_with_value", @@ -2035,6 +2215,36 @@ } } }, + "u3w-Se-xzH.text" : { + "comment" : "Class = \"UILabel\"; text = \"Vibration Style\"; ObjectID = \"u3w-Se-xzH\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Vibration Style" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "振动模式" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "振动模式" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "振动模式" + } + } + } + }, "Uca-fD-4AC.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; Uca-fD-4AC.segmentTitles[0] = \"Align with System\"; ObjectID = \"Uca-fD-4AC\";", "extractionState" : "extracted_with_value", diff --git a/Moonlight.xcodeproj/project.pbxproj b/Moonlight.xcodeproj/project.pbxproj index df3d7d35..24410030 100644 --- a/Moonlight.xcodeproj/project.pbxproj +++ b/Moonlight.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 18341A042DEB662C0094E5F2 /* FixedTintImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 18341A032DEB662C0094E5F2 /* FixedTintImageView.m */; }; + 187F4B282E04658A00EECC59 /* WidgetPanelStackView.m in Sources */ = {isa = PBXBuildFile; fileRef = 187F4B272E04658A00EECC59 /* WidgetPanelStackView.m */; }; 189AD29A2DE7446000E24B88 /* HostCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 189AD2992DE7446000E24B88 /* HostCollectionViewController.m */; }; 840751B32BF49C050096B657 /* CustomTapGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 840751B22BF49C050096B657 /* CustomTapGestureRecognizer.m */; }; 841BAC922DE35D6B00DA639E /* ThemeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 841BAC912DE35D6B00DA639E /* ThemeManager.m */; }; @@ -131,6 +132,8 @@ /* Begin PBXFileReference section */ 18341A022DEB65AB0094E5F2 /* FixedTintImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FixedTintImageView.h; sourceTree = ""; }; 18341A032DEB662C0094E5F2 /* FixedTintImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FixedTintImageView.m; sourceTree = ""; }; + 187F4B232E04633100EECC59 /* WidgetPanelStackView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WidgetPanelStackView.h; sourceTree = ""; }; + 187F4B272E04658A00EECC59 /* WidgetPanelStackView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WidgetPanelStackView.m; sourceTree = ""; }; 189A56012DF2A99900738CAF /* Voidex v1.0.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Voidex v1.0.xcdatamodel"; sourceTree = ""; }; 189AD2962DE743B700E24B88 /* HostCollectionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HostCollectionViewController.h; sourceTree = ""; }; 189AD2992DE7446000E24B88 /* HostCollectionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HostCollectionViewController.m; sourceTree = ""; }; @@ -361,6 +364,8 @@ 84351B2E2C29AD6600A77989 /* Cells */, 84351B282C29AD6000A77989 /* LayoutOnScreenControlsViewController.h */, 84351B252C29AD6000A77989 /* LayoutOnScreenControlsViewController.m */, + 187F4B232E04633100EECC59 /* WidgetPanelStackView.h */, + 187F4B272E04658A00EECC59 /* WidgetPanelStackView.m */, 84351B262C29AD6000A77989 /* OSCProfilesTableViewController.h */, 84351B272C29AD6000A77989 /* OSCProfilesTableViewController.m */, 84351B2A2C29AD6000A77989 /* ToolBarContainerView.h */, @@ -887,6 +892,7 @@ 84351B0F2C29ACCB00A77989 /* OnScreenButtonState.m in Sources */, FB89462B19F646E200339C8A /* StreamView.m in Sources */, 840751B32BF49C050096B657 /* CustomTapGestureRecognizer.m in Sources */, + 187F4B282E04658A00EECC59 /* WidgetPanelStackView.m in Sources */, FB4678FA1A55FFAD00377732 /* DiscoveryManager.m in Sources */, 841BAC922DE35D6B00DA639E /* ThemeManager.m in Sources */, 98D5856D1C0EA79600F6CC00 /* TemporaryHost.m in Sources */,