diff --git a/VoidLink/Input/CustomOSC/OSCProfile.h b/VoidLink/Input/CustomOSC/OSCProfile.h index e8b567fe..81245401 100644 --- a/VoidLink/Input/CustomOSC/OSCProfile.h +++ b/VoidLink/Input/CustomOSC/OSCProfile.h @@ -31,6 +31,7 @@ typedef NS_ENUM(NSInteger, MapGyroTo) { @property NSString *name; @property NSMutableArray *buttonStatesEncoded; @property (nonatomic, assign) int16_t unfoldedExclusiveFolderSequence; +@property NSSet *postExclusiveUnfoldedSequences; @property BOOL isSelected; @property (nonatomic, assign) MapGyroTo mapGyroTo; @property (nonatomic, assign) bool yawPitchToRightStick; diff --git a/VoidLink/Input/CustomOSC/OSCProfile.m b/VoidLink/Input/CustomOSC/OSCProfile.m index 38ef717f..38a7bfe1 100644 --- a/VoidLink/Input/CustomOSC/OSCProfile.m +++ b/VoidLink/Input/CustomOSC/OSCProfile.m @@ -32,6 +32,7 @@ [encoder encodeObject:self.name forKey:@"name"]; [encoder encodeObject:self.buttonStatesEncoded forKey:@"buttonStates"]; [encoder encodeInt32:self.unfoldedExclusiveFolderSequence forKey:@"unfoldedExclusiveFolderSequence"]; + [encoder encodeObject:self.postExclusiveUnfoldedSequences forKey:@"postExclusiveUnfoldedSequences"]; [encoder encodeBool:self.isSelected forKey:@"isSelected"]; [encoder encodeInt64:self.mapGyroTo forKey:@"mapGyroTo"]; [encoder encodeBool:self.yawPitchToRightStick forKey:@"yawPitchToRightStick"]; @@ -75,6 +76,15 @@ nil] forKey:@"buttonStates"]; self.unfoldedExclusiveFolderSequence = [decoder containsValueForKey:@"unfoldedExclusiveFolderSequence"] ? [decoder decodeInt32ForKey:@"unfoldedExclusiveFolderSequence"] : -1; + self.postExclusiveUnfoldedSequences = [decoder containsValueForKey:@"postExclusiveUnfoldedSequences"] ? + [decoder decodeObjectOfClasses: + [NSSet setWithObjects: + [NSMutableSet class], + [NSSet class], + [NSNumber class], + nil] + forKey:@"postExclusiveUnfoldedSequences"] : + [NSSet set]; self.isSelected = [decoder decodeBoolForKey:@"isSelected"]; self.mapGyroTo = [decoder containsValueForKey:@"mapGyroTo"] ? [decoder decodeInt64ForKey:@"mapGyroTo"] : mapGyroToMouse; self.yawPitchToRightStick = [decoder containsValueForKey:@"yawPitchToRightStick"] ? [decoder decodeBoolForKey:@"yawPitchToRightStick"] : true; @@ -134,6 +144,7 @@ copy.name = [self.name mutableCopy]; // NSString → NSMutableString copy.buttonStatesEncoded = [[NSMutableArray alloc] initWithArray:self.buttonStatesEncoded copyItems:YES]; copy.unfoldedExclusiveFolderSequence = self.unfoldedExclusiveFolderSequence; + copy.postExclusiveUnfoldedSequences = [self.postExclusiveUnfoldedSequences copy]; copy.isSelected = self.isSelected; copy.mapGyroTo = self.mapGyroTo; copy.yawPitchToRightStick = self.yawPitchToRightStick; diff --git a/VoidLink/Input/CustomOSC/OSCProfilesManager.m b/VoidLink/Input/CustomOSC/OSCProfilesManager.m index a55af653..89a5485d 100644 --- a/VoidLink/Input/CustomOSC/OSCProfilesManager.m +++ b/VoidLink/Input/CustomOSC/OSCProfilesManager.m @@ -209,7 +209,7 @@ static CGRect layoutViewBounds; - (NSMutableArray *) getAllProfiles { NSData *profilesArrayEncoded = [[NSUserDefaults standardUserDefaults] objectForKey: @"OSCProfiles"]; // Get the encoded array of encoded OSC profiles from persistent storage NSSet *classes = [NSSet setWithObjects:[NSString class], [NSMutableData class], [NSMutableArray class], [OSCProfile class], [OnScreenButtonState class], nil]; - + NSMutableArray *profilesEncoded = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:profilesArrayEncoded error:nil]; // Decode the encoded array itself, NOT the objects contained in the array /* Decode each of the encoded profiles, place them into an array, then return the array */ @@ -292,6 +292,7 @@ static CGRect layoutViewBounds; OSCProfile *selectedProfile = [self getSelectedProfile]; selectedProfile.buttonStatesEncoded = buttonStatesEncoded; selectedProfile.unfoldedExclusiveFolderSequence = OnScreenWidgetView.unfoldedExclusiveFolderSequence; + selectedProfile.postExclusiveUnfoldedSequences = OnScreenWidgetView.postExclusiveUnfoldedSequences; [self replaceSelectedProfileWith:selectedProfile overwriteDefault:NO]; return true; } @@ -481,7 +482,7 @@ static CGRect layoutViewBounds; - (OnScreenButtonState *)unarchiveButtonStateEncoded:(NSData *)data { OnScreenButtonState* buttonState; - buttonState = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObjects:[NSString class], [OnScreenButtonState class], [NSSet class], nil] + buttonState = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObjects:[NSString class], [OnScreenButtonState class], [NSSet class], [NSNumber class], nil] fromData:data error:nil]; return buttonState; diff --git a/VoidLink/Input/OnScreenWidgetView.swift b/VoidLink/Input/OnScreenWidgetView.swift index 0ea27ecd..cf62b9f6 100644 --- a/VoidLink/Input/OnScreenWidgetView.swift +++ b/VoidLink/Input/OnScreenWidgetView.swift @@ -11,9 +11,13 @@ import SVGKit @objc class OnScreenWidgetView: UIView { @objc public static var mapping: [Int16:OnScreenWidgetView] = [:] + @objc public static var isRestoring: Bool = false @objc public static func set(widget:OnScreenWidgetView, for key:Int16) { mapping[key] = widget } + @objc public static func widgetFor(key:Int16) -> OnScreenWidgetView? { + return mapping[key] + } @objc public static func removeWidgetFromMappings(key:Int16) { mapping.removeValue(forKey: key) } @@ -21,6 +25,12 @@ import SVGKit mapping.removeAll() } @objc public static var unfoldedExclusiveFolderSequence:Int16 = -1 + @objc public static var postExclusiveUnfoldedSequences:Set = Set() + @objc public static func setPostExclusiveUnfoldeds(_ sequences:NSSet){ + let sequenceSet:Set = sequences as? Set ?? Set() + postExclusiveUnfoldedSequences = Set(sequenceSet) + print("postExclusiveUnfoldedSequences \(postExclusiveUnfoldedSequences) \(CACurrentMediaTime())") + } @objc public weak var guidelineDelegate: OnScreenWidgetGuidelineUpdateDelegate? @objc protocol OnScreenWidgetGuidelineUpdateDelegate: AnyObject { @@ -274,6 +284,7 @@ import SVGKit private var tickFlag: UInt8 = 0 @objc public var folded: Bool = false + @objc public var persistedFolded: Bool = false @objc public var revealMode: RevealMode = .coexist @objc public var sequenceSet: Set = Set() @objc public var parentSequence: Int16 = -1 @@ -2804,6 +2815,7 @@ import SVGKit setCollection(hidden: folded, for: folder) if !folded, folder.revealMode == .exclusive { OnScreenWidgetView.unfoldedExclusiveFolderSequence = folder.sequence + if(!isRestoring) {OnScreenWidgetView.postExclusiveUnfoldedSequences.removeAll()} let currentRootFolder = OnScreenWidgetView.getRootFolder(of: folder) ?? folder var offshootRootFolders:Set = Set() folder.forEachWidget{ widget in @@ -2822,6 +2834,15 @@ import SVGKit guard currentRootFolder != folder else {return} setCollection(hidden: true, for:currentRootFolder, exception: folder, recursive: true) } + if folded, folder.revealMode == .exclusive { + OnScreenWidgetView.unfoldedExclusiveFolderSequence = -1 + } + if folder.revealMode == .coexist { + if folded { + OnScreenWidgetView.postExclusiveUnfoldedSequences.remove(folder.sequence) + } + else {OnScreenWidgetView.postExclusiveUnfoldedSequences.insert(folder.sequence)} + } } private static func getRootFolder(of widget:OnScreenWidgetView) -> OnScreenWidgetView?{ @@ -2867,17 +2888,27 @@ import SVGKit } @objc static func restoreFoldedStates(){ + isRestoring = true var unfoldedExclusiveFolder: OnScreenWidgetView? if unfoldedExclusiveFolderSequence != -1 { unfoldedExclusiveFolder = OnScreenWidgetView.mapping[unfoldedExclusiveFolderSequence] - if(unfoldedExclusiveFolder != nil){ - OnScreenWidgetView.set(folded: false, for: unfoldedExclusiveFolder!) - } } for widget in OnScreenWidgetView.mapping.values { OnScreenWidgetView.setCollection(hidden: widget.folded, for: widget, exception: unfoldedExclusiveFolder) } + + if(unfoldedExclusiveFolder != nil){ + OnScreenWidgetView.set(folded: false, for: unfoldedExclusiveFolder!) + } + + print("postExclusiveUnfoldedSequences \(postExclusiveUnfoldedSequences) stamp \(CACurrentMediaTime())") + for sequence in postExclusiveUnfoldedSequences { + guard let folder = OnScreenWidgetView.mapping[sequence] else { continue } + OnScreenWidgetView.set(folded: false, for: folder) + } + + isRestoring = false } override func didMoveToSuperview() { diff --git a/VoidLink/Input/StreamView.h b/VoidLink/Input/StreamView.h index 2d96add0..0cf36b9e 100644 --- a/VoidLink/Input/StreamView.h +++ b/VoidLink/Input/StreamView.h @@ -60,7 +60,7 @@ andConfig:(StreamConfiguration*)streamConfig; - (void) clearOnScreenWidgets; - (void) reloadOnScreenWidgetViews:(bool)reload; -- (void) saveRelocatedWidgetViews; +- (void) saveStreamViewWidgetChanges; - (bool) isOnScreenWidgetEnabled; - (CGSize) getVideoAreaSize; diff --git a/VoidLink/Input/StreamView.m b/VoidLink/Input/StreamView.m index 5e047c6c..ae0bf7c4 100644 --- a/VoidLink/Input/StreamView.m +++ b/VoidLink/Input/StreamView.m @@ -494,19 +494,29 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; else return longSide; } -- (void)saveRelocatedWidgetViews{ +- (void)saveStreamViewWidgetChanges{ + /* NSMutableDictionary* relocatedWidgetDict = [NSMutableDictionary dictionary]; for(UIView* subview in self->_streamFrameTopLayerView.subviews){ if([subview isKindOfClass:[OnScreenWidgetView class]]){ OnScreenWidgetView* widget = (OnScreenWidgetView* )subview; if(widget.relocatedDuringStreaming) [relocatedWidgetDict setObject:widget forKey:@(widget.sequence)]; + OnScreenWidgetView* widget = [OnScreenWidgetView widgetForKey:newButtonState.sequence]; + if(widget.isFolder) } } + */ - // NSLog(@"relocatedWidgetDict.allKeys.count %d, %f", relocatedWidgetDict.allKeys.count, CACurrentMediaTime()); - - if(relocatedWidgetDict.allKeys.count == 0) return; + NSMutableSet* relocatedWidgetSequences = [NSMutableSet set]; + bool hasFolderStateChanged = false; + for(OnScreenWidgetView* widget in [OnScreenWidgetView.mapping allValues]){ + if(widget.relocatedDuringStreaming) [relocatedWidgetSequences addObject:@(widget.sequence)]; + if(!hasFolderStateChanged) hasFolderStateChanged = widget.folded != widget.persistedFolded; + } + + if(relocatedWidgetSequences.count == 0 && !hasFolderStateChanged) return; + NSLog(@"relocatedWidgetSequences.count %lu, hasFolderStateChanged %d %F", relocatedWidgetSequences.count, hasFolderStateChanged, CACurrentMediaTime()); oscProfileMan = [OSCProfilesManager sharedManager:self->_streamFrameTopLayerView.bounds]; OSCProfile *newProfile = [oscProfileMan getSelectedProfile]; @@ -514,14 +524,20 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; for (NSInteger i = 0; i < newProfile.buttonStatesEncoded.count; i++) { NSData* buttonStateEncoded = newProfile.buttonStatesEncoded[i]; OnScreenButtonState *newButtonState = [oscProfileMan unarchiveButtonStateEncoded:buttonStateEncoded]; - if([relocatedWidgetDict.allKeys containsObject:@(newButtonState.sequence)]){ - OnScreenWidgetView* widget = [relocatedWidgetDict objectForKey:@(newButtonState.sequence)]; + OnScreenWidgetView* widget = [OnScreenWidgetView.mapping objectForKey:@(newButtonState.sequence)]; + NSData *newButtonStateEncoded; + if([relocatedWidgetSequences containsObject:@(widget.sequence)] + || widget.isFolder){ newButtonState.position = [oscProfileMan normalizeWidgetPosition:widget.storedCenter]; - NSData *newButtonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:newButtonState requiringSecureCoding:YES error:nil]; + newButtonState.folded = widget.folded; + newButtonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:newButtonState requiringSecureCoding:YES error:nil]; newProfile.buttonStatesEncoded[i] = newButtonStateEncoded; } } + newProfile.unfoldedExclusiveFolderSequence = OnScreenWidgetView.unfoldedExclusiveFolderSequence; + newProfile.postExclusiveUnfoldedSequences = OnScreenWidgetView.postExclusiveUnfoldedSequences; + [oscProfileMan replaceSelectedProfileWith:newProfile overwriteDefault:NO]; } @@ -565,6 +581,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; widgetView.sequenceSet = buttonState.sequenceSet; widgetView.parentSequence = buttonState.parentSequence; widgetView.folded = buttonState.folded; + widgetView.persistedFolded = buttonState.folded; widgetView.revealMode = buttonState.revealMode; widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place @@ -643,6 +660,8 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; } } + OnScreenWidgetView.unfoldedExclusiveFolderSequence = oscProfile.unfoldedExclusiveFolderSequence; + [OnScreenWidgetView setPostExclusiveUnfoldeds:oscProfile.postExclusiveUnfoldedSequences]; [OnScreenWidgetView restoreFoldedStates]; NSLog(@"hasLegacyWidget %d %f", hasLegacyWidget, CACurrentMediaTime()); diff --git a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m index 3847630f..001c5253 100644 --- a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m +++ b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m @@ -208,6 +208,8 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) { // if(widget.isFolder) [OnScreenWidgetView setWithFolded:widget.folded for:widget]; } + OnScreenWidgetView.unfoldedExclusiveFolderSequence = oscProfile.unfoldedExclusiveFolderSequence; + [OnScreenWidgetView setPostExclusiveUnfoldeds:oscProfile.postExclusiveUnfoldedSequences]; [OnScreenWidgetView restoreFoldedStates]; NSLog(@"reloadOnScreenWidgets hasLegacyWidget %d %f", hasLegacyWidget, CACurrentMediaTime()); if(hasLegacyWidget) [self reloadLegacyOnScreenControls:oscProfile]; diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index d12d5d22..c994d093 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -761,7 +761,7 @@ } - (void)openWidgetLayoutTool{ - [_streamView saveRelocatedWidgetViews]; + [_streamView saveStreamViewWidgetChanges]; _streamView.widgetToolOpened = true; [self->_streamView disableOnScreenControls]; [self->_streamView clearOnScreenWidgets]; // clear all onScreenKeyboardButtons before entering edit mode @@ -772,7 +772,7 @@ } - (void)openWidgetProfileTableWithPickProfile:(BOOL)pickProfile{ - [_streamView saveRelocatedWidgetViews]; + [_streamView saveStreamViewWidgetChanges]; _streamView.widgetToolOpened = true; [self->_streamView disableOnScreenControls]; [self->_streamView clearOnScreenWidgets]; // clear all onScreenKeyboardButtons before entering edit mode @@ -1066,7 +1066,7 @@ //sleep(1); appDidEnterBackgroundWithoutPip = true; - [_streamView saveRelocatedWidgetViews]; + [_streamView saveStreamViewWidgetChanges]; #if !TARGET_OS_TV #endif @@ -1150,7 +1150,7 @@ - (void)expandSettingsView{ self.mainFrameViewcontroller.settingsExpandedInStreamView = true; //notify mainFrameViewContorller that this is a setting expansion in stream view, some settings shall be disabled. - [_streamView saveRelocatedWidgetViews]; + [_streamView saveStreamViewWidgetChanges]; [self.mainFrameViewcontroller expandSettingsView]; } @@ -1675,7 +1675,7 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - [self->_streamView saveRelocatedWidgetViews]; + [self->_streamView saveStreamViewWidgetChanges]; if (_isRestoringFromPiP) { Log(LOG_I, @"View size changed during PiP restore, skipping redundant reconfiguration.");