From 49ed0e43e9f7ae3178ce31739da2d760503f9363 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Wed, 27 Aug 2025 22:35:47 +0800 Subject: [PATCH] reduce on-screen widget visual effect performance cost --- VoidLink/Input/OnScreenWidgetView.swift | 147 +++++++++++------- .../LayoutOnScreenControlsViewController.m | 30 ++-- 2 files changed, 111 insertions(+), 66 deletions(-) diff --git a/VoidLink/Input/OnScreenWidgetView.swift b/VoidLink/Input/OnScreenWidgetView.swift index 0a733aa6..ee80517e 100644 --- a/VoidLink/Input/OnScreenWidgetView.swift +++ b/VoidLink/Input/OnScreenWidgetView.swift @@ -508,9 +508,14 @@ import UIKit setupButtonDownVisualEffectLayer(); if CommandManager.directionPads.contains(touchPadString) {setupLrudDirectionIndicatorlayers()} + if CommandManager.stickTouchPads.contains(touchPadString) {self.l3r3Indicator = createl3r3Indicator()} + if self.hasStickIndicator { + if self.crossMarkLayer.superlayer == nil {self.crossMarkLayer = createCrossMark()} + if self.stickBallLayer.superlayer == nil {self.stickBallLayer = createStickBall()} + } } - private func createAndShowl3r3Indicator() -> CAShapeLayer{ + private func createl3r3Indicator() -> CAShapeLayer{ let indicatorFrame = CAShapeLayer(); let indicatorBorder = CAShapeLayer(); @@ -525,18 +530,26 @@ import UIKit indicatorBorder.fillColor = UIColor.clear.cgColor let path = UIBezierPath(roundedRect: indicatorBorder.bounds, cornerRadius: indicatorBorder.cornerRadius) indicatorBorder.path = path.cgPath - indicatorBorder.borderColor = voidlinkPurple + indicatorBorder.borderColor = UIColor.clear.cgColor self.layer.superlayer?.addSublayer(indicatorBorder) - indicatorBorder.position = CGPointMake(CGRectGetMinX(self.frame)+touchBeganLocation.x, CGRectGetMinY(self.frame)+touchBeganLocation.y) + + return indicatorBorder + } + + private func showl3r3Indicator(){ + CATransaction.begin() + CATransaction.setDisableActions(true) + + self.l3r3Indicator.borderColor = voidlinkPurple + self.l3r3Indicator.position = CGPointMake(CGRectGetMinX(self.frame)+touchBeganLocation.x, CGRectGetMinY(self.frame)+touchBeganLocation.y) + + CATransaction.commit() if vibrationOn { vibrationGenerator.prepare() vibrationGenerator.impactOccurred() - // print("vibrationInstance: \(vibrationGenerator)") } - - return indicatorBorder } @@ -544,10 +557,13 @@ import UIKit //Indicator overlay for on-screen game controller left or right sticks (non-vector mode) private func handleStickBallReachingBorder(){ + CATransaction.begin() + CATransaction.setDisableActions(true) stickBallLayer.lineWidth = 0.6 // stickBallLayer.shadowOffset = CGSize(width: 0.0, height: 0.0) stickBallLayer.shadowOffset = CGSize(width: 0.0, height: 0.0) // stickBallLayer.shadowColor = stickBallLayer.strokeColor + CATransaction.commit() } private func handleStickBallLeavingBorder(){ @@ -570,13 +586,30 @@ import UIKit stickMarkerRelativeLocation = CGPointMake(touchBeganLocation.x, touchBeganLocation.y) } - self.crossMarkLayer = createAndShowCrossMarkOnTouchPoint(at: stickMarkerRelativeLocation) - self.stickBallLayer = createAndShowStickBall(at: stickMarkerRelativeLocation) + showStickBall(at: stickMarkerRelativeLocation) + showCrossMarkOnTouchPoint(at: stickMarkerRelativeLocation) } // cross mark for left & right gamePad - private func createAndShowCrossMarkOnTouchPoint(at point: CGPoint) -> CAShapeLayer { + private func createCrossMark() -> CAShapeLayer { let crossLayer = CAShapeLayer() + + crossLayer.strokeColor = crossMarkColor + crossLayer.lineWidth = 1.2 + crossLayer.fillColor = crossMarkColor + + self.layer.superlayer?.addSublayer(crossLayer) + crossLayer.shadowColor = UIColor.black.cgColor + crossLayer.shadowOffset = CGSize(width: 1, height: 1) + crossLayer.shadowRadius = 0; + crossLayer.shadowOpacity = 0.8 + + crossLayer.isHidden = true + + return crossLayer + } + + private func showCrossMarkOnTouchPoint(at point: CGPoint) { let path = UIBezierPath() let crossSize = 26.0 @@ -587,34 +620,22 @@ import UIKit path.move(to: CGPoint(x: point.x, y: point.y - crossSize / 2)) path.addLine(to: CGPoint(x: point.x, y: point.y + crossSize / 2)) - crossLayer.path = path.cgPath - crossLayer.strokeColor = crossMarkColor - crossLayer.lineWidth = 1.2 - crossLayer.fillColor = crossMarkColor + CATransaction.begin() + CATransaction.setDisableActions(true) - self.layer.superlayer?.addSublayer(crossLayer) - crossLayer.position = CGPointMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame)) - // crossLayer.shadowRadius = 1 - crossLayer.shadowColor = UIColor.black.cgColor - crossLayer.shadowOffset = CGSize(width: 1, height: 1) - crossLayer.shadowRadius = 0; - crossLayer.shadowOpacity = 0.8 + self.crossMarkLayer.path = path.cgPath + self.crossMarkLayer.position = CGPointMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame)) + self.crossMarkLayer.isHidden = false - return crossLayer + CATransaction.commit() } + - private func createAndShowStickBall(at center: CGPoint) -> CAShapeLayer { - // Create a circular path using UIBezierPath - let path = UIBezierPath(arcCenter: center, radius: 8, startAngle: 0, endAngle: 2 * .pi, clockwise: true) - + private func createStickBall() -> CAShapeLayer { // Create a CAShapeLayer let stickBallLayer = CAShapeLayer() - stickBallLayer.path = path.cgPath // Assign the circular path to the shape layer self.layer.superlayer?.addSublayer(stickBallLayer) - stickBallLayer.position = CGPointMake(CGRectGetMidX(self.crossMarkLayer.frame), CGRectGetMidY(self.crossMarkLayer.frame)) - - // stickBallLayer.position = CG - + // Set the stroke color and width (border of the circle) stickBallLayer.strokeColor = UIColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 1.0).cgColor //stickBallLayer. @@ -625,9 +646,26 @@ import UIKit // Set the fill color (inside of the circle) stickBallLayer.fillColor = stickBallColor // Light fill with some transparency + + stickBallLayer.isHidden = true return stickBallLayer } + + private func showStickBall(at center: CGPoint) { + let path = UIBezierPath(arcCenter: center, radius: 8, startAngle: 0, endAngle: 2 * .pi, clockwise: true) + + CATransaction.begin() + CATransaction.setDisableActions(true) + + // Create a CAShapeLayer + self.stickBallLayer.path = path.cgPath // Assign the circular path to the shape layer + self.stickBallLayer.position = CGPointMake(CGRectGetMidX(self.crossMarkLayer.frame), CGRectGetMidY(self.crossMarkLayer.frame)) + self.stickBallLayer.isHidden = false + + CATransaction.commit() + } + @objc public func updateStickIndicator(){ CATransaction.begin() @@ -653,8 +691,12 @@ import UIKit CATransaction.commit() } - private func resetStickBallPositionAndRemoveIndicator(){ + private func resetStickBallPositionAndHideIndicator(){ + CATransaction.begin() + CATransaction.setDisableActions(true) handleStickBallLeavingBorder() + CATransaction.commit() + CATransaction.begin() // CATransaction.setDisableActions(true) CATransaction.setAnimationDuration(0.15) @@ -665,8 +707,11 @@ import UIKit usleep(200000) DispatchQueue.main.async { // switch to main thread to update UI if !self.touchBegan { - self.crossMarkLayer.removeFromSuperlayer() - self.stickBallLayer.removeFromSuperlayer() + CATransaction.begin() + CATransaction.setDisableActions(true) + self.crossMarkLayer.isHidden = true + self.stickBallLayer.isHidden = true + CATransaction.commit() } } } @@ -747,10 +792,10 @@ import UIKit // show the indicator based on the touchBeganLocation indicatorLayer.position = CGPointMake(CGRectGetMinX(self.frame)+touchBeganLocation.x, CGRectGetMinY(self.frame)+touchBeganLocation.y) - vibrationGenerator.prepare() - vibrationGenerator.impactOccurred() - print("vibrationInstance: \(vibrationGenerator)") - + if vibrationOn { + vibrationGenerator.prepare() + vibrationGenerator.impactOccurred() + } } private func handleLrudTouchMove(){ @@ -1136,28 +1181,22 @@ import UIKit if self.widgetType == WidgetTypeEnum.touchPad && touches.count == 1{ // don't use event?.allTouches?.count here, it will counts all touches including the ones captured by other UIViews switch self.touchPadString { case "LSPAD": - self.crossMarkLayer.removeFromSuperlayer() - self.stickBallLayer.removeFromSuperlayer() - self.l3r3Indicator.removeFromSuperlayer() self.showStickIndicator() if quickDoubleTapDetected { - self.l3r3Indicator = self.createAndShowl3r3Indicator() + self.showl3r3Indicator() self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)} case "RSPAD": - self.crossMarkLayer.removeFromSuperlayer() - self.stickBallLayer.removeFromSuperlayer() - self.l3r3Indicator.removeFromSuperlayer() self.showStickIndicator() if quickDoubleTapDetected { - self.l3r3Indicator = self.createAndShowl3r3Indicator() + self.showl3r3Indicator() self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)} case "LSVPAD": if quickDoubleTapDetected { - self.l3r3Indicator = self.createAndShowl3r3Indicator() + self.showl3r3Indicator() self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)} case "RSVPAD": if quickDoubleTapDetected { - self.l3r3Indicator = self.createAndShowl3r3Indicator() + self.showl3r3Indicator() self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)} case "DPAD", "WASDPAD", "ARROWPAD": if allCapturedTouchesCount == 1 {showLrudBall(at: touchBeganLocation)} @@ -1170,7 +1209,7 @@ import UIKit } case "DS4TOUCH": if quickDoubleTapDetected { - self.l3r3Indicator = self.createAndShowl3r3Indicator() + self.showl3r3Indicator() self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings) } default: @@ -1476,10 +1515,10 @@ import UIKit switch self.touchPadString{ case "LSPAD": self.onScreenControls.clearLeftStickTouchPadFlag() - if widgetType == WidgetTypeEnum.touchPad {self.resetStickBallPositionAndRemoveIndicator()} + if widgetType == WidgetTypeEnum.touchPad {self.resetStickBallPositionAndHideIndicator()} case "RSPAD": self.onScreenControls.clearRightStickTouchPadFlag() - if widgetType == WidgetTypeEnum.touchPad {self.resetStickBallPositionAndRemoveIndicator()} + if widgetType == WidgetTypeEnum.touchPad {self.resetStickBallPositionAndHideIndicator()} case "LSVPAD": self.onScreenControls.clearLeftStickTouchPadFlag() case "RSVPAD": @@ -1506,8 +1545,10 @@ import UIKit } } - self.l3r3Indicator.borderColor = UIColor.clear.cgColor - + if CommandManager.stickTouchPads.contains(touchPadString){ + self.l3r3Indicator.borderColor = UIColor.clear.cgColor + } + if CommandManager.directionPads.contains(touchPadString){ self.upIndicator.borderColor = UIColor.clear.cgColor self.downIndicator.borderColor = UIColor.clear.cgColor @@ -1560,8 +1601,6 @@ import UIKit setupView(); //re-setup widgetView style - self.crossMarkLayer.removeFromSuperlayer() - self.stickBallLayer.removeFromSuperlayer() if self.widgetType == WidgetTypeEnum.touchPad{ switch self.touchPadString{ case "LSPAD", "RSPAD": diff --git a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m index 1fdeb0e5..45c4e10d 100644 --- a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m +++ b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m @@ -91,7 +91,7 @@ - (void)reloadOnScreenWidgetViews{ NSLog(@"reloadOnScreenWidgets %f", CACurrentMediaTime()); OnScreenWidgetView.editMode = true; - [self clearStickIndicator]; + [self hideStickIndicators]; for (UIView *subview in self.view.subviews) { if ([subview isKindOfClass:[OnScreenWidgetView class]]) { @@ -296,7 +296,7 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator{ if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) return; viewWillBeResized = true; - [self clearStickIndicator]; + [self hideStickIndicators]; if(!_quickSwitchEnabled) [self saveTapped:nil]; } @@ -711,9 +711,15 @@ label.numberOfLines = 1; } -- (void)clearStickIndicator{ - [self->selectedWidgetView.stickBallLayer removeFromSuperlayer]; - [self->selectedWidgetView.crossMarkLayer removeFromSuperlayer]; +- (void)hideStickIndicators{ + for(UIView* view in self.view.subviews){ + if([view isKindOfClass:[OnScreenWidgetView class]]){ + OnScreenWidgetView* widget = (OnScreenWidgetView* )view; + [widget.crossMarkLayer setHidden:true]; + [widget.stickBallLayer setHidden:true]; + + } + } } /* @@ -725,13 +731,13 @@ - (void)widgetViewTapped: (NSNotification *)notification{ //self.undoButton.alpha = selectedWidgetView.layoutChanges.count>1 && !CGPointEqualToPoint(selectedWidgetView.layoutChanges.lastObject.CGPointValue, selectedWidgetView.initialCenter)? 1.0 : 0.3; - + [self hideStickIndicators]; + // receive the selected widgetView obj passed from the notification [self enableCommonWidgetTools]; - OnScreenWidgetView* widgetView = (OnScreenWidgetView* )notification.object; - [self clearStickIndicator]; + OnScreenWidgetView* widgetView = (OnScreenWidgetView* )notification.object; self->widgetViewSelected = true; self->controllerLayerSelected = false; self->selectedWidgetView = widgetView; @@ -775,7 +781,7 @@ } if(showStickIndicatorOffsetStack){ // illustrating the indicator offset, - [self clearStickIndicator]; + [self hideStickIndicators]; selectedWidgetView.touchBeganLocation = CGPointMake(CGRectGetWidth(selectedWidgetView.frame)/2, CGRectGetHeight(selectedWidgetView.frame)/4); [selectedWidgetView showStickIndicator];// this will create the indicator CAShapeLayers [self.stickIndicatorOffsetSlider setValue:self->selectedWidgetView.stickIndicatorOffset]; @@ -815,7 +821,7 @@ - (void)legacyOscLayerTapped: (NSNotification *)notification{ [self enableCommonWidgetTools]; CALayer* controllerLayer = (CALayer* )notification.object; - [self clearStickIndicator]; + [self hideStickIndicators]; self->widgetViewSelected = false; self->selectedWidgetView = nil; @@ -1202,7 +1208,7 @@ - (void) presentProfilesTableView{ [self saveTapped:nil]; - [self clearStickIndicator]; + [self hideStickIndicators]; selectedWidgetView = nil; UIStoryboard *storyboard; BOOL isIPhone = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone); @@ -1329,7 +1335,7 @@ if(!isToolbarHidden && self->selectedWidgetView != nil && [self layerIsOverlappingWithTrashcanButton:selectedWidgetView.layer]){ [self->selectedWidgetView removeFromSuperview]; [self.onScreenWidgetViews removeObject:self->selectedWidgetView]; - [self clearStickIndicator]; + [self hideStickIndicators]; [selectedWidgetView.buttonDownVisualEffectLayer removeFromSuperlayer]; }