diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard
index ba59c702..b27b0c1c 100644
--- a/VoidLink/Base.lproj/iPad.storyboard
+++ b/VoidLink/Base.lproj/iPad.storyboard
@@ -2626,7 +2626,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3100,6 +3133,8 @@
+
+
@@ -3279,22 +3314,22 @@
-
+
-
+
-
+
-
+
-
+
diff --git a/VoidLink/Base.lproj/iPhone.storyboard b/VoidLink/Base.lproj/iPhone.storyboard
index 95055c18..71075aa3 100644
--- a/VoidLink/Base.lproj/iPhone.storyboard
+++ b/VoidLink/Base.lproj/iPhone.storyboard
@@ -315,7 +315,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -868,6 +904,8 @@
+
+
@@ -3134,16 +3172,16 @@
-
+
-
+
-
+
-
+
diff --git a/VoidLink/Input/CustomOSC/OSCProfilesManager.m b/VoidLink/Input/CustomOSC/OSCProfilesManager.m
index dab5a2b4..895ac83e 100644
--- a/VoidLink/Input/CustomOSC/OSCProfilesManager.m
+++ b/VoidLink/Input/CustomOSC/OSCProfilesManager.m
@@ -450,6 +450,7 @@ static CGRect layoutViewBounds;
buttonState.parentSequence = widgetView.parentSequence;
buttonState.folded = widgetView.folded;
buttonState.revealMode = widgetView.revealMode;
+ buttonState.bulkMoveEnabled = widgetView.bulkMoveEnabled;
buttonState.widthFactor = [self normalizeSizeWidthFactorWith:widgetView];
buttonState.heightFactor = [self normalizeSizeHeightFactorWith:widgetView];
buttonState.componentSizeFactor = [self normalizeComponentSizeFactorWith:widgetView];
diff --git a/VoidLink/Input/CustomOSC/OnScreenButtonState.h b/VoidLink/Input/CustomOSC/OnScreenButtonState.h
index d6d3cf56..5e54b723 100644
--- a/VoidLink/Input/CustomOSC/OnScreenButtonState.h
+++ b/VoidLink/Input/CustomOSC/OnScreenButtonState.h
@@ -26,6 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
@property NSString *alias;
@property (nonatomic, assign) BOOL folded;
@property (nonatomic, assign) uint8_t revealMode;
+@property (nonatomic, assign) BOOL bulkMoveEnabled;
@property (nonatomic, assign) int16_t sequence;
@property (nonatomic, assign) int16_t parentSequence;
@property NSSet *sequenceSet;
diff --git a/VoidLink/Input/CustomOSC/OnScreenButtonState.m b/VoidLink/Input/CustomOSC/OnScreenButtonState.m
index fed5a00b..f3f12b85 100644
--- a/VoidLink/Input/CustomOSC/OnScreenButtonState.m
+++ b/VoidLink/Input/CustomOSC/OnScreenButtonState.m
@@ -34,6 +34,7 @@
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeBool:self.folded forKey:@"folded"];
[encoder encodeInt32:self.revealMode forKey:@"revealMode"];
+ [encoder encodeBool:self.bulkMoveEnabled forKey:@"bulkMoveEnabled"];
[encoder encodeInt32:self.sequence forKey:@"sequence"];
[encoder encodeInt32:self.parentSequence forKey:@"parentSequence"];
[encoder encodeObject:self.sequenceSet forKey:@"sequenceSet"];
@@ -75,6 +76,7 @@
self.name = [decoder decodeObjectForKey:@"name"];
self.folded = [decoder containsValueForKey:@"folded"] ? [decoder decodeBoolForKey:@"folded"] : false;
self.revealMode = [decoder containsValueForKey:@"revealMode"] ? [decoder decodeInt32ForKey:@"revealMode"] : coexist;
+ self.bulkMoveEnabled = [decoder containsValueForKey:@"bulkMoveEnabled"] ? [decoder decodeBoolForKey:@"bulkMoveEnabled"] : true;
self.sequence = [decoder containsValueForKey:@"sequence"] ? [decoder decodeInt32ForKey:@"sequence"] : -1;
self.parentSequence = [decoder containsValueForKey:@"parentSequence"] ? [decoder decodeInt32ForKey:@"parentSequence"] : -1;
self.sequenceSet = [decoder containsValueForKey:@"sequenceSet"] ? [decoder decodeObjectForKey:@"sequenceSet"] : [NSSet set];
diff --git a/VoidLink/Input/OnScreenWidgetView.swift b/VoidLink/Input/OnScreenWidgetView.swift
index 407c9517..cca1164b 100644
--- a/VoidLink/Input/OnScreenWidgetView.swift
+++ b/VoidLink/Input/OnScreenWidgetView.swift
@@ -291,6 +291,7 @@ import SVGKit
@objc public var folded: Bool = false
@objc public var persistedFolded: Bool = false
@objc public var revealMode: RevealMode = .coexist
+ @objc public var bulkMoveEnabled: Bool = false
@objc public var sequenceSet: Set = Set()
@objc public var parentSequence: Int16 = -1
private weak var capturer: OnScreenWidgetView?
@@ -1973,6 +1974,11 @@ import SVGKit
relocatedDuringStreaming = true
// center = currentLocation;
//NSLog("x coord: %f, y coord: %f", self.frame.origin.x, self.frame.origin.y)
+
+ if isFolder, bulkMoveEnabled {
+ self.moveSubWidgetsInBatch(by: CGVector(dx: offsetX, dy: offsetY))
+ }
+
if OnScreenWidgetView.editMode {
guidelineDelegate?.updateGuidelinesForOnScreenWidget(self)
}
@@ -2877,6 +2883,18 @@ import SVGKit
return sequence+1
}
+ @objc func moveSubWidgetsInBatch(by vector:CGVector) {
+ guard isFolder else {return}
+ for sequence in self.sequenceSet {
+ guard let widget = OnScreenWidgetView.mapping[sequence] else {return}
+ widget.storedCenter = CGPoint(x: widget.storedCenter.x+vector.dx, y: widget.storedCenter.y+vector.dy)
+ if !widget.isHidden {
+ widget.center = CGPoint(x: widget.center.x+vector.dx, y: widget.center.y+vector.dy)
+ }
+ widget.relocatedDuringStreaming = true
+ }
+ }
+
@objc static var enableFolderAnimation:Bool = true
private static func setCollection(hidden:Bool, for folder:OnScreenWidgetView, exception:OnScreenWidgetView? = nil, recursive:Bool = false) {
guard folder.isFolder else {return}
diff --git a/VoidLink/Input/StreamView.m b/VoidLink/Input/StreamView.m
index 003ef639..920a8da8 100644
--- a/VoidLink/Input/StreamView.m
+++ b/VoidLink/Input/StreamView.m
@@ -640,6 +640,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
widgetView.folded = buttonState.folded;
widgetView.persistedFolded = buttonState.folded;
widgetView.revealMode = buttonState.revealMode;
+ widgetView.bulkMoveEnabled = buttonState.bulkMoveEnabled;
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
widgetView.widthFactor = buttonState.widthFactor;
diff --git a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h
index 12edc7b8..fb4212fc 100644
--- a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h
+++ b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.h
@@ -133,6 +133,10 @@ NS_ASSUME_NONNULL_BEGIN
@property (weak, nonatomic) IBOutlet UISegmentedControl *collectedWidgetsSelector;
@property (weak, nonatomic) IBOutlet UISegmentedControl *revealModeSelector;
+@property (weak, nonatomic) IBOutlet UIStackView *bulkMoveStack;
+@property (weak, nonatomic) IBOutlet UISegmentedControl *bulkMoveSelector;
+
+
@property (weak, nonatomic) IBOutlet UIStackView *widgetPanelStack;
diff --git a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m
index 5f9b8340..20c3e7c3 100644
--- a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m
+++ b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m
@@ -171,6 +171,7 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
widgetView.parentSequence = buttonState.parentSequence;
widgetView.folded = buttonState.folded;
widgetView.revealMode = buttonState.revealMode;
+ widgetView.bulkMoveEnabled = buttonState.bulkMoveEnabled;
widgetView.guidelineDelegate = (id)self;
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
@@ -816,6 +817,7 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
OnScreenWidgetView* newWidget = [[OnScreenWidgetView alloc] initWithCmdString:widgetInitParams[@"cmdString"] buttonLabel:widgetInitParams[@"buttonLabel"] shape:widgetInitParams[@"shape"] profile:profile]; //reconstruct widgetView
newWidget.sequence = widget.sequence;
newWidget.revealMode = widget.revealMode;
+ newWidget.bulkMoveEnabled = widget.bulkMoveEnabled;
newWidget.guidelineDelegate = (id)self;
newWidget.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
newWidget.widthFactor = widget.widthFactor;
@@ -1136,6 +1138,9 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
[self.collectedWidgetsSelector setSelectedSegmentIndex:selectedWidgetView.folded ? 1 :0];
[self.revealModeSelector setSelectedSegmentIndex:selectedWidgetView.revealMode];
[OnScreenWidgetView setWithFolded:selectedWidgetView.folded for:selectedWidgetView];
+
+ self.bulkMoveStack.hidden = !selectedWidgetView.isFolder;
+ [self.bulkMoveSelector setSelectedSegmentIndex:selectedWidgetView.bulkMoveEnabled];
[self autoFitStack:self.widgetPanelStack];
@@ -1305,6 +1310,12 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
}
}
+- (void)bulkMoveChanged:(UISegmentedControl* )sender{
+ if(self->selectedWidgetView != nil && self->widgetViewSelected){
+ selectedWidgetView.bulkMoveEnabled = sender.selectedSegmentIndex == 1;
+ }
+}
+
- (void)revealModeChanged:(UISegmentedControl* )sender{
if(self->selectedWidgetView != nil && self->widgetViewSelected){
selectedWidgetView.revealMode = sender.selectedSegmentIndex;
@@ -1627,6 +1638,10 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
[self.revealModeSelector addTarget:self action:@selector(revealModeChanged:) forControlEvents:(UIControlEventValueChanged)];
[self.revealModeSelector setTitleTextAttributes:whiteFontAttributes forState:UIControlStateNormal];
self.collectedWidgetsStack.hidden = YES;
+
+ [self.bulkMoveSelector addTarget:self action:@selector(bulkMoveChanged:) forControlEvents:(UIControlEventValueChanged)];
+ [self.bulkMoveSelector setTitleTextAttributes:whiteFontAttributes forState:UIControlStateNormal];
+ self.bulkMoveStack.hidden = YES;
if([self isIPhone]){
[self.vibrationStyleSelector addTarget:self action:@selector(vibrationStyleChanged:) forControlEvents:(UIControlEventValueChanged)];
diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings
index 0d3d34ad..121f086b 100644
--- a/VoidLink/mul.lproj/iPad.xcstrings
+++ b/VoidLink/mul.lproj/iPad.xcstrings
@@ -397,7 +397,7 @@
},
"zh-HK" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "非線性因子"
}
}
@@ -1597,6 +1597,66 @@
}
}
},
+ "bNW-R2-UuH.segmentTitles[0]" : {
+ "comment" : "Class = \"UISegmentedControl\"; bNW-R2-UuH.segmentTitles[0] = \"disabled\"; ObjectID = \"bNW-R2-UuH\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "disabled"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "关闭"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "關閉"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "關閉"
+ }
+ }
+ }
+ },
+ "bNW-R2-UuH.segmentTitles[1]" : {
+ "comment" : "Class = \"UISegmentedControl\"; bNW-R2-UuH.segmentTitles[1] = \"enabled\"; ObjectID = \"bNW-R2-UuH\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "enabled"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "启用"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "啟用"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "啟用"
+ }
+ }
+ }
+ },
"BQH-nQ-IyM.text" : {
"comment" : "Class = \"UILabel\"; text = \"Reverse Hold Button\"; ObjectID = \"BQH-nQ-IyM\";",
"extractionState" : "extracted_with_value",
@@ -1669,7 +1729,7 @@
},
"zh-Hans" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "指定软键盘高度"
}
},
@@ -3524,6 +3584,36 @@
}
}
},
+ "Jeb-fE-sCm.text" : {
+ "comment" : "Class = \"UILabel\"; text = \" Bulk move \"; ObjectID = \"Jeb-fE-sCm\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : " Bulk move "
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : " 批量移动"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : " 批次移動"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : " 批次移動"
+ }
+ }
+ }
+ },
"JTl-un-QJJ.text" : {
"comment" : "Class = \"UILabel\"; text = \"Emulated Controller Type\"; ObjectID = \"JTl-un-QJJ\";",
"extractionState" : "extracted_with_value",
@@ -3878,7 +3968,7 @@
},
"zh-HK" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "串流位元速率"
}
}
@@ -5878,6 +5968,18 @@
}
}
},
+ "wrg-n4-F5w.text" : {
+ "comment" : "Class = \"UILabel\"; text = \" \"; ObjectID = \"wrg-n4-F5w\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : " "
+ }
+ }
+ }
+ },
"WWl-77-twB.text" : {
"comment" : "Class = \"UILabel\"; text = \"SDR Performance Workaround\"; ObjectID = \"WWl-77-twB\";",
"extractionState" : "extracted_with_value",
diff --git a/VoidLink/mul.lproj/iPhone.xcstrings b/VoidLink/mul.lproj/iPhone.xcstrings
index 8c90eed4..682b66a1 100644
--- a/VoidLink/mul.lproj/iPhone.xcstrings
+++ b/VoidLink/mul.lproj/iPhone.xcstrings
@@ -2299,6 +2299,66 @@
}
}
},
+ "ful-j0-kgc.segmentTitles[0]" : {
+ "comment" : "Class = \"UISegmentedControl\"; ful-j0-kgc.segmentTitles[0] = \"disabled\"; ObjectID = \"ful-j0-kgc\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "disabled"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "关闭"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "關閉"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "關閉"
+ }
+ }
+ }
+ },
+ "ful-j0-kgc.segmentTitles[1]" : {
+ "comment" : "Class = \"UISegmentedControl\"; ful-j0-kgc.segmentTitles[1] = \"enabled\"; ObjectID = \"ful-j0-kgc\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "enabled"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "启用"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "啟用"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "啟用"
+ }
+ }
+ }
+ },
"GbY-52-itv.text" : {
"comment" : "Class = \"UILabel\"; text = \"Local Volume\"; ObjectID = \"GbY-52-itv\";",
"extractionState" : "extracted_with_value",
@@ -4057,6 +4117,36 @@
}
}
},
+ "P9b-Th-Ybc.text" : {
+ "comment" : "Class = \"UILabel\"; text = \" Bulk move \"; ObjectID = \"P9b-Th-Ybc\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : " Bulk move "
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : " 批量移动"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : " 批次移動"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : " 批次移動"
+ }
+ }
+ }
+ },
"PaG-9w-YxB.text" : {
"comment" : "Class = \"UILabel\"; text = \"Mute in Background\"; ObjectID = \"PaG-9w-YxB\";",
"extractionState" : "extracted_with_value",
@@ -4735,6 +4825,18 @@
}
}
},
+ "QtA-AD-aJw.text" : {
+ "comment" : "Class = \"UILabel\"; text = \" \"; ObjectID = \"QtA-AD-aJw\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : " "
+ }
+ }
+ }
+ },
"r8X-jt-JfH.text" : {
"comment" : "Class = \"UILabel\"; text = \"Frames to Buffer\"; ObjectID = \"r8X-jt-JfH\";",
"extractionState" : "extracted_with_value",