diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard
index 1bad48d1..cd6cae05 100644
--- a/VoidLink/Base.lproj/iPad.storyboard
+++ b/VoidLink/Base.lproj/iPad.storyboard
@@ -2888,8 +2888,8 @@
-
-
+
+
diff --git a/VoidLink/Base.lproj/iPhone.storyboard b/VoidLink/Base.lproj/iPhone.storyboard
index 612c6bc6..c5440cf5 100644
--- a/VoidLink/Base.lproj/iPhone.storyboard
+++ b/VoidLink/Base.lproj/iPhone.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -156,7 +156,7 @@
-
+
@@ -315,7 +315,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -813,7 +862,7 @@
-
+
@@ -823,6 +872,8 @@
+
+
@@ -838,6 +889,7 @@
+
@@ -883,7 +935,7 @@
-
+
diff --git a/VoidLink/Input/CustomOSC/OSCProfile.h b/VoidLink/Input/CustomOSC/OSCProfile.h
index 3edc2b26..e8b567fe 100644
--- a/VoidLink/Input/CustomOSC/OSCProfile.h
+++ b/VoidLink/Input/CustomOSC/OSCProfile.h
@@ -30,6 +30,7 @@ typedef NS_ENUM(NSInteger, MapGyroTo) {
@property NSString *name;
@property NSMutableArray *buttonStatesEncoded;
+@property (nonatomic, assign) int16_t unfoldedExclusiveFolderSequence;
@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 f402ab14..38ef717f 100644
--- a/VoidLink/Input/CustomOSC/OSCProfile.m
+++ b/VoidLink/Input/CustomOSC/OSCProfile.m
@@ -31,6 +31,7 @@
- (void) encodeWithCoder:(NSCoder*)encoder {
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeObject:self.buttonStatesEncoded forKey:@"buttonStates"];
+ [encoder encodeInt32:self.unfoldedExclusiveFolderSequence forKey:@"unfoldedExclusiveFolderSequence"];
[encoder encodeBool:self.isSelected forKey:@"isSelected"];
[encoder encodeInt64:self.mapGyroTo forKey:@"mapGyroTo"];
[encoder encodeBool:self.yawPitchToRightStick forKey:@"yawPitchToRightStick"];
@@ -73,6 +74,7 @@
[NSData class],
nil]
forKey:@"buttonStates"];
+ self.unfoldedExclusiveFolderSequence = [decoder containsValueForKey:@"unfoldedExclusiveFolderSequence"] ? [decoder decodeInt32ForKey:@"unfoldedExclusiveFolderSequence"] : -1;
self.isSelected = [decoder decodeBoolForKey:@"isSelected"];
self.mapGyroTo = [decoder containsValueForKey:@"mapGyroTo"] ? [decoder decodeInt64ForKey:@"mapGyroTo"] : mapGyroToMouse;
self.yawPitchToRightStick = [decoder containsValueForKey:@"yawPitchToRightStick"] ? [decoder decodeBoolForKey:@"yawPitchToRightStick"] : true;
@@ -131,6 +133,7 @@
OSCProfile *copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name mutableCopy]; // NSString → NSMutableString
copy.buttonStatesEncoded = [[NSMutableArray alloc] initWithArray:self.buttonStatesEncoded copyItems:YES];
+ copy.unfoldedExclusiveFolderSequence = self.unfoldedExclusiveFolderSequence;
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 0c68ea27..a55af653 100644
--- a/VoidLink/Input/CustomOSC/OSCProfilesManager.m
+++ b/VoidLink/Input/CustomOSC/OSCProfilesManager.m
@@ -291,6 +291,7 @@ static CGRect layoutViewBounds;
*/
OSCProfile *selectedProfile = [self getSelectedProfile];
selectedProfile.buttonStatesEncoded = buttonStatesEncoded;
+ selectedProfile.unfoldedExclusiveFolderSequence = OnScreenWidgetView.unfoldedExclusiveFolderSequence;
[self replaceSelectedProfileWith:selectedProfile overwriteDefault:NO];
return true;
}
@@ -389,10 +390,14 @@ static CGRect layoutViewBounds;
// save on-screen widget views (keyboard & mouse command) as buttonstate:
_widgetSizeTransition = keepWidgetSize;
for(OnScreenWidgetView* widgetView in OnScreenWidgetViews){
- CGPoint normalizedPosition = [self normalizeWidgetPosition:widgetView.center];
+ CGPoint normalizedPosition = [self normalizeWidgetPosition:widgetView.storedCenter];
OnScreenButtonState *buttonState = [[OnScreenButtonState alloc] initWithButtonName:widgetView.cmdString buttonType:CustomOnScreenWidget andPosition:normalizedPosition];
buttonState.alias = widgetView.widgetLabel;
buttonState.sequence = widgetView.sequence;
+ buttonState.sequenceSet = widgetView.sequenceSet;
+ buttonState.parentSequence = widgetView.parentSequence;
+ buttonState.folded = widgetView.folded;
+ buttonState.revealMode = widgetView.revealMode;
buttonState.widthFactor = [self normalizeSizeWidthFactorWith:widgetView];
buttonState.heightFactor = [self normalizeSizeHeightFactorWith:widgetView];
buttonState.componentSizeFactor = [self normalizeComponentSizeFactorWith:widgetView];
@@ -421,7 +426,6 @@ static CGRect layoutViewBounds;
NSData *buttonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:buttonState requiringSecureCoding:YES error:nil];
[buttonStatesEncoded addObject: buttonStateEncoded];
-
}
return buttonStatesEncoded;
}
@@ -477,7 +481,7 @@ static CGRect layoutViewBounds;
- (OnScreenButtonState *)unarchiveButtonStateEncoded:(NSData *)data {
OnScreenButtonState* buttonState;
- buttonState = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObjects:[NSString class], [OnScreenButtonState class], nil]
+ buttonState = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObjects:[NSString class], [OnScreenButtonState class], [NSSet class], nil]
fromData:data
error:nil];
return buttonState;
diff --git a/VoidLink/Input/CustomOSC/OnScreenButtonState.h b/VoidLink/Input/CustomOSC/OnScreenButtonState.h
index e2c2a3c0..d6d3cf56 100644
--- a/VoidLink/Input/CustomOSC/OnScreenButtonState.h
+++ b/VoidLink/Input/CustomOSC/OnScreenButtonState.h
@@ -27,7 +27,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign) BOOL folded;
@property (nonatomic, assign) uint8_t revealMode;
@property (nonatomic, assign) int16_t sequence;
-@property NSMutableSet *sequenceSet;
+@property (nonatomic, assign) int16_t parentSequence;
+@property NSSet *sequenceSet;
@property CGPoint position;
@property (nonatomic, assign) BOOL isHidden;
@property (nonatomic, assign) uint8_t buttonMode;
diff --git a/VoidLink/Input/CustomOSC/OnScreenButtonState.m b/VoidLink/Input/CustomOSC/OnScreenButtonState.m
index 080d3762..fed5a00b 100644
--- a/VoidLink/Input/CustomOSC/OnScreenButtonState.m
+++ b/VoidLink/Input/CustomOSC/OnScreenButtonState.m
@@ -35,6 +35,7 @@
[encoder encodeBool:self.folded forKey:@"folded"];
[encoder encodeInt32:self.revealMode forKey:@"revealMode"];
[encoder encodeInt32:self.sequence forKey:@"sequence"];
+ [encoder encodeInt32:self.parentSequence forKey:@"parentSequence"];
[encoder encodeObject:self.sequenceSet forKey:@"sequenceSet"];
[encoder encodeObject:self.alias forKey:@"alias"];
[encoder encodeInt:self.widgetType forKey:@"buttonType"]; // keep original key
@@ -73,8 +74,9 @@
if (self = [super init]) {
self.name = [decoder decodeObjectForKey:@"name"];
self.folded = [decoder containsValueForKey:@"folded"] ? [decoder decodeBoolForKey:@"folded"] : false;
- self.revealMode = [decoder containsValueForKey:@"revealMode"] ? [decoder decodeInt32ForKey:@"revealMode"] : exclusive;
+ self.revealMode = [decoder containsValueForKey:@"revealMode"] ? [decoder decodeInt32ForKey:@"revealMode"] : coexist;
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];
self.alias = [decoder decodeObjectForKey:@"alias"];
self.widgetType = [decoder decodeIntForKey:@"buttonType"];
diff --git a/VoidLink/Input/OnScreenWidgetView.swift b/VoidLink/Input/OnScreenWidgetView.swift
index 08f43606..07b8bd72 100644
--- a/VoidLink/Input/OnScreenWidgetView.swift
+++ b/VoidLink/Input/OnScreenWidgetView.swift
@@ -10,7 +10,18 @@ import UIKit
import SVGKit
@objc class OnScreenWidgetView: UIView {
-
+ @objc public static var mapping: [Int16:OnScreenWidgetView] = [:]
+ @objc public static func set(widget:OnScreenWidgetView, for key:Int16) {
+ mapping[key] = widget
+ }
+ @objc public static func removeWidgetFromMappings(key:Int16) {
+ mapping.removeValue(forKey: key)
+ }
+ @objc public static func clearMappings() {
+ mapping.removeAll()
+ }
+ @objc public static var unfoldedExclusiveFolderSequence:Int16 = -1
+
@objc public weak var guidelineDelegate: OnScreenWidgetGuidelineUpdateDelegate?
@objc protocol OnScreenWidgetGuidelineUpdateDelegate: AnyObject {
func updateGuidelinesForOnScreenWidget(_ sender: Any)
@@ -263,8 +274,9 @@ import SVGKit
private var tickFlag: UInt8 = 0
@objc public var folded: Bool = false
- @objc public var revealMode: RevealMode = .exclusive
+ @objc public var revealMode: RevealMode = .coexist
@objc public var sequenceSet: Set = Set()
+ @objc public var parentSequence: Int16 = -1
private weak var capturer: OnScreenWidgetView?
@objc init(cmdString: String, buttonLabel: String, shape:String, profile:OSCProfile) {
@@ -1415,17 +1427,21 @@ import SVGKit
}
}
- private func handleFingerUpOrSlideout() {
+
+ private func handleFingerUpOrSlideout(leaveFunctionalButtonAlone: Bool = false, event: UIEvent? = nil) {
+ print("handleFingerUpOrSlideout triggered, \(self.widgetLabel) \(CACurrentMediaTime())")
if autoTapInterval < OnScreenWidgetView.MinAutotapInterval {
- handleButtonUp()
+ handleButtonUp(leaveFunctionalButtonAlone:leaveFunctionalButtonAlone, event:event)
}
else{
self.autoTapTimer?.pause()
- self.handleButtonUp()
+ self.handleButtonUp(leaveFunctionalButtonAlone: leaveFunctionalButtonAlone)
}
}
private func handleButtonDown() {
+ if !self.isUserInteractionEnabled {return}
+
if !OnScreenWidgetView.editMode, !self.functionalButtonString.isEmpty {self.handleFunctionalButtonDown()}
if !OnScreenWidgetView.editMode {self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)}
@@ -1460,7 +1476,8 @@ import SVGKit
}
}
- private func handleButtonUp() {
+ private func handleButtonUp(leaveFunctionalButtonAlone:Bool = false, event: UIEvent? = nil) {
+ if !self.isUserInteractionEnabled {return}
if !OnScreenWidgetView.editMode {self.sendComboButtonsUpEvent(comboStrings: self.comboButtonStrings)}
if !OnScreenWidgetView.editMode && !self.motionControlButtonString.isEmpty{
@@ -1470,7 +1487,7 @@ import SVGKit
self.buttonUpVisualEffect()
if !OnScreenWidgetView.editMode && !self.functionalButtonString.isEmpty{
- self.handleFunctionalButtonUp()
+ if !leaveFunctionalButtonAlone {self.handleFunctionalButtonUp(event:event)}
}
// legacy keyboard button combo connected by "+"
@@ -1817,9 +1834,9 @@ import SVGKit
if(self.tapToToggleFlag) {self.handleTapDownOrSlidein()}
else {self.handleFingerUpOrSlideout()}
self.tapToToggleFlag = !self.tapToToggleFlag
- case .slideToToggle:
+ case .slideToToggle where !self.isFolder:
self.handleButtonSliding(touches: touches)
- case .slideAndHold:
+ case .slideAndHold where !self.isFolder:
self.handleButtonSliding(touches: touches)
default:
self.handleTapDownOrSlidein()
@@ -1843,11 +1860,11 @@ import SVGKit
capturer = nil
currentLocation = touch.location(in: superview)
forEachWidget { (widget) in
+ guard widget != self else {return}
if widget.isFolder {
- if self.frame.intersects(widget.frame), !self.sequenceSet.contains(widget.sequence) {
+ if isLocation(currentLocation, in: widget), !self.sequenceSet.contains(widget.sequence) {
// self.layer.borderColor = UIColor.systemYellow.cgColor
widget.highlightBorder(highlighted: true)
- if widget == self {return}
capturer = widget
return
}
@@ -1856,7 +1873,13 @@ import SVGKit
}
}
else {
- if self.buttonMode == .movable {currentLocation = touch.location(in: superview)}
+ if self.buttonMode == .movable {
+ currentLocation = touch.location(in: superview)
+ for sequence in self.sequenceSet {
+ let widget = OnScreenWidgetView.mapping[sequence]
+ if widget?.isHidden == true {widget?.center = currentLocation}
+ }
+ }
else {return}
}
@@ -1872,7 +1895,10 @@ import SVGKit
let outOfBoundsX = center.x+offsetX >= (self.superview?.bounds.width)! || center.x+offsetX < 0
let outOfBoundsY = center.y+offsetY >= (self.superview?.bounds.height)! || center.y+offsetY < 0
- if firstTouchMoved {center = CGPoint(x: outOfBoundsX ? center.x : center.x+offsetX, y: outOfBoundsY ? center.y : center.y+offsetY)}
+ if firstTouchMoved {
+ center = CGPoint(x: outOfBoundsX ? center.x : center.x+offsetX, y: outOfBoundsY ? center.y : center.y+offsetY)
+ storedCenter = center
+ }
latestTouchLocation = currentLocation
@@ -1919,14 +1945,17 @@ import SVGKit
}
}
- private func handleFingerUpAfterSliding(touches: Set) {
+ private func handleFingerUpAfterSliding(touches: Set, event: UIEvent? = nil) {
+ // a special touchUp handling for .slideToToggle
for touch in touches {
self.forEachWidget(){ widget in
setLock.lock()
let captured = widget.capturedTouches.contains(touch)
setLock.unlock()
if !captured || widget.buttonMode == .regular {return}
- widget.handleFingerUpOrSlideout()
+ let needReleaseButton = (isLocation(touch.location(in: superview), in: widget)
+ || widget.buttonMode == .slideAndHold)
+ if needReleaseButton {widget.handleFingerUpOrSlideout(event: event)}
setLock.lock()
widget.capturedTouches.remove(touch)
setLock.unlock()
@@ -1953,7 +1982,10 @@ import SVGKit
let locationInSuperView = touch.location(in: self.superview)
self.forEachWidget{ widget in
if widget.widgetType != WidgetTypeEnum.button {return}
- let isSlidableButton = widget.buttonMode == .slideToToggle || widget.buttonMode == .slideAndHold
+ let isSlidableButton = (widget.buttonMode == .slideToToggle
+ || widget.buttonMode == .slideAndHold
+ || (widget.buttonMode == .movable && widget != self)
+ )
if isLocation(locationInSuperView, in: widget){
setLock.lock()
let captured = widget.capturedTouches.contains(touch)
@@ -1972,8 +2004,8 @@ import SVGKit
setLock.unlock()
if !captured || !isSlidableButton {return}
// print("UIButton: \(widget.buttonLabel) out test, \(widget.touchPadString), \(CACurrentMediaTime())")
- if(widget.buttonMode == .slideToToggle){
- widget.handleFingerUpOrSlideout()
+ if(widget.buttonMode == .slideToToggle || widget.buttonMode == .movable){
+ widget.handleFingerUpOrSlideout(leaveFunctionalButtonAlone: widget.isFuncationalButton)
setLock.lock()
widget.capturedTouches.remove(touch)
setLock.unlock()
@@ -2271,6 +2303,10 @@ import SVGKit
private func handleFunctionalButtonDown(){
switch self.functionalButtonString {
+ case "FOLDER":
+ if self.buttonMode != .slideAndHold {break}
+ self.folded = false
+ OnScreenWidgetView.set(folded: false, for: self)
case "ABSTCHDRAG":
let mouseButton = CommandManager.mouseButtonMappings[Set(self.comboButtonStrings).intersection(CommandManager.mouseButtonMappings.keys).first ?? "MLEFT"] ?? BUTTON_LEFT
print("mouseButton \(mouseButton)");
@@ -2292,10 +2328,16 @@ import SVGKit
private var singleTouchEnabled:Bool = true
- private func handleFunctionalButtonUp(){
+ private func handleFunctionalButtonUp(event: UIEvent? = nil){
+ print("handleFunctionalButtonUp \(CACurrentMediaTime())")
if buttonMode == .movable {
- if moveableButtonLongPressed() {return}
+ if moveableButtonLongPressed() && !UITouchUtil.touches(in: self, from: event).isEmpty {return}
+ print("handleFunctionalButtonUp movable, \(moveableButtonLongPressed()) \(CACurrentMediaTime())")
+
switch self.functionalButtonString {
+ // case "FOLDER":
+ // self.folded = !self.folded
+ // OnScreenWidgetView.set(folded: self.folded, for: self)
case "NOSINGLETOUCH":
if !self.isPencilProEnabled() {break}
singleTouchEnabled = !singleTouchEnabled
@@ -2306,6 +2348,9 @@ import SVGKit
}
}
switch self.functionalButtonString {
+ case "FOLDER":
+ self.folded = !self.folded
+ OnScreenWidgetView.set(folded: self.folded, for: self)
case "SETTINGS":
self.functionalButtonDelegate?.expandSettingsView()
case "TOOLBOX":
@@ -2512,8 +2557,9 @@ import SVGKit
}
if !OnScreenWidgetView.editMode && !self.cmdString.contains("+") && !self.comboButtonStrings.isEmpty { // if the command(keystring contains "+", it's a legacy multi-key command
+ // print("self.comboButtonStrings \(self.comboButtonStrings),\(CACurrentMediaTime())")
if self.buttonMode == .slideToToggle || self.buttonMode == .slideAndHold {
- self.handleFingerUpAfterSliding(touches: touches)
+ self.handleFingerUpAfterSliding(touches: touches, event: event) // .slideAndHold has to be released at touchesEnded, don't deal with it here.
setLock.lock()
self.capturedTouches.minus(touches)
setLock.unlock()
@@ -2532,11 +2578,10 @@ import SVGKit
if firstTouchMoved {undoRelocation()}
if let capturer = capturer {
capturer.highlightBorder(highlighted: false)
- guard !self.sequenceSet.contains(capturer.sequence) else { return }
- OnScreenWidgetView.put(widget: self, into: capturer)
- self.isHidden = capturer.folded
+ OnScreenWidgetView.putWidget(self, into: capturer)
}
}
+ capturer = nil
guard let superview = superview else { return }
@@ -2570,7 +2615,10 @@ import SVGKit
}
}
- if self.buttonMode != .tapToToggle {self.handleFingerUpOrSlideout()}
+ if (self.buttonMode != .tapToToggle
+ && self.buttonMode != .slideToToggle
+ && self.buttonMode != .slideAndHold
+ && self == touches.first?.view) {self.handleFingerUpOrSlideout(event:event)}
}
@objc public func setAutoTapIntervalByText(str: String){
@@ -2685,44 +2733,137 @@ import SVGKit
return sequence+1
}
- @objc static func setCollection(hidden:Bool, for folder:OnScreenWidgetView) {
+ private static func setCollection(hidden:Bool, for folder:OnScreenWidgetView, exception:OnScreenWidgetView? = nil, recursive:Bool = false) {
guard folder.isFolder else {return}
+ // guard folder.folded != hidden else { return }
folder.folded = hidden
folder.setupAtrributedText()
if hidden {
folder.forEachWidget{ widget in
- if widget == folder {return}
- if folder.sequenceSet.contains(widget.sequence){
- DispatchQueue.main.async {widget.isHidden = hidden}
- if widget.isFolder {
- // OnScreenWidgetView.setCollection(hidden: hidden, for: widget)
+ guard widget != folder else {return}
+ if folder.sequenceSet.contains(widget.sequence), widget != exception{
+ DispatchQueue.main.async {
+ widget.isUserInteractionEnabled = false
+ UIView.animate(withDuration: folder.buttonMode == .slideAndHold ? 0.05 : 0.15, animations: {
+ widget.center = folder.center
+ },completion: { finished in
+ widget.isUserInteractionEnabled = !folder.folded
+ widget.center = folder.folded ? folder.storedCenter : widget.storedCenter
+ widget.isHidden = folder.folded
+ })
}
}
}
}
else{
folder.forEachWidget{ widget in
- if widget == folder {return}
- if folder.sequenceSet.contains(widget.sequence){
- DispatchQueue.main.async {widget.isHidden = hidden}
+ guard widget != folder else {return}
+ if folder.sequenceSet.contains(widget.sequence), widget != exception{
+ DispatchQueue.main.async {
+ widget.center = folder.storedCenter
+ widget.isUserInteractionEnabled = false
+ widget.isHidden = false
+ UIView.animate(withDuration: folder.buttonMode == .slideAndHold ? 0.05 : 0.15, animations: {
+ widget.center = widget.storedCenter
+ },completion: { finished in
+ widget.isUserInteractionEnabled = !folder.folded
+ widget.center = folder.folded ? folder.storedCenter : widget.storedCenter
+ widget.isHidden = folder.folded
+ })
+ }
}
}
- if folder.revealMode == .exclusive {
- folder.forEachWidget{ widget in
- if widget == folder {return}
- if widget.isFolder, !widget.sequenceSet.contains(folder.sequence) {
- OnScreenWidgetView.setCollection(hidden: true, for: widget)
- }
+ }
+ if recursive {
+ folder.forEachWidget{ widget in
+ guard widget != folder else {return}
+ guard widget.isFolder, widget != exception else {return}
+ if folder.sequenceSet.contains(widget.sequence){
+ OnScreenWidgetView.setCollection(hidden: hidden, for: widget, exception: exception, recursive: true)
}
}
}
}
- private static func put(widget:OnScreenWidgetView, into folder:OnScreenWidgetView){
- widget.forEachWidget{ otherWidget in
- otherWidget.sequenceSet.remove(widget.sequence)
+ @objc static func set(folded:Bool, for folder:OnScreenWidgetView) { // folder综合逻辑
+ guard folder.isFolder else {return}
+ setCollection(hidden: folded, for: folder)
+ if !folded, folder.revealMode == .exclusive {
+ OnScreenWidgetView.unfoldedExclusiveFolderSequence = folder.sequence
+ let currentRootFolder = OnScreenWidgetView.getRootFolder(of: folder) ?? folder
+ var offshootRootFolders:Set = Set()
+ folder.forEachWidget{ widget in
+ guard folder != widget else {return}
+ let rootFolder = getRootFolder(of: widget)
+ guard let rootFolder = rootFolder else {return}
+ offshootRootFolders.insert(rootFolder)
+ }
+ offshootRootFolders.remove(currentRootFolder)
+
+ guard !folder.sequenceSet.isEmpty else {return}
+ for folder in offshootRootFolders {
+ setCollection(hidden: true, for: folder, recursive: true)
+ }
+
+ guard currentRootFolder != folder else {return}
+ setCollection(hidden: true, for:currentRootFolder, exception: folder, recursive: true)
}
+ }
+
+ private static func getRootFolder(of widget:OnScreenWidgetView) -> OnScreenWidgetView?{
+ var widgetRef:OnScreenWidgetView? = widget
+ var parentFolder:OnScreenWidgetView? = OnScreenWidgetView.mapping[widgetRef?.parentSequence ?? -1]
+ repeat {
+ widgetRef = parentFolder
+ parentFolder = OnScreenWidgetView.mapping[widgetRef?.parentSequence ?? -1]
+ } while parentFolder != nil
+ return widgetRef
+ }
+
+ private static func getParentFolders(of widget: OnScreenWidgetView) -> Set {
+ var parents = Set()
+ var current = widget
+ while let parent = OnScreenWidgetView.mapping[current.parentSequence] {
+ parents.insert(parent)
+ current = parent
+ }
+ return parents
+ }
+
+ private static func putWidget(_ widget:OnScreenWidgetView, into folder:OnScreenWidgetView){
+ guard !OnScreenWidgetView.getParentFolders(of: folder).contains(widget) else { return }
+ let parentFolder = OnScreenWidgetView.mapping[widget.parentSequence]
+ if parentFolder != nil {
+ parentFolder?.sequenceSet.remove(widget.sequence)
+ }
+ widget.parentSequence = folder.sequence
folder.sequenceSet.insert(widget.sequence)
+ widget.isHidden = folder.folded
+ if folder.buttonMode == .slideAndHold, widget.isFuncationalButton {
+ widget.buttonMode = .slideToToggle
+ }
+ }
+
+ @objc static func setFree(widget:OnScreenWidgetView){
+ let parentFolder = OnScreenWidgetView.mapping[widget.parentSequence]
+ if parentFolder != nil {
+ parentFolder?.sequenceSet.remove(widget.sequence)
+ }
+ widget.parentSequence = -1
+ }
+
+ @objc static func restoreFoldedStates(){
+ 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)
+ }
}
override func didMoveToSuperview() {
diff --git a/VoidLink/Input/StreamView.m b/VoidLink/Input/StreamView.m
index bb897c18..5e047c6c 100644
--- a/VoidLink/Input/StreamView.m
+++ b/VoidLink/Input/StreamView.m
@@ -467,6 +467,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
- (void) clearOnScreenWidgets{
OnScreenWidgetView.isTweakingHighlight = false;
+ [OnScreenWidgetView clearMappings];
for (UIView *subview in self->_streamFrameTopLayerView.subviews) {
// 检查子视图是否是特定类型的实例
if ([subview isKindOfClass:[OnScreenWidgetView class]]) {
@@ -503,7 +504,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
}
}
- NSLog(@"relocatedWidgetDict.allKeys.count %d, %f", relocatedWidgetDict.allKeys.count, CACurrentMediaTime());
+ // NSLog(@"relocatedWidgetDict.allKeys.count %d, %f", relocatedWidgetDict.allKeys.count, CACurrentMediaTime());
if(relocatedWidgetDict.allKeys.count == 0) return;
@@ -515,7 +516,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
OnScreenButtonState *newButtonState = [oscProfileMan unarchiveButtonStateEncoded:buttonStateEncoded];
if([relocatedWidgetDict.allKeys containsObject:@(newButtonState.sequence)]){
OnScreenWidgetView* widget = [relocatedWidgetDict objectForKey:@(newButtonState.sequence)];
- newButtonState.position = [oscProfileMan normalizeWidgetPosition:widget.center];
+ newButtonState.position = [oscProfileMan normalizeWidgetPosition:widget.storedCenter];
NSData *newButtonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:newButtonState requiringSecureCoding:YES error:nil];
newProfile.buttonStatesEncoded[i] = newButtonStateEncoded;
}
@@ -560,11 +561,11 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
widgetView.sequence = buttonState.sequence == -1 ? i : buttonState.sequence;
if(!sequenceGenerated) sequenceGenerated = buttonState.sequence == -1;
if(!hasMovableWidget) hasMovableWidget = buttonState.buttonMode == movable;
- if(sequenceGenerated){
- NSLog(@"widgetView.sequence %d %f", widgetView.sequence, CACurrentMediaTime());
- buttonState.sequence = widgetView.sequence;
- oscProfile.buttonStatesEncoded[i] = [NSKeyedArchiver archivedDataWithRootObject:buttonState requiringSecureCoding:YES error:nil];
- }
+ [OnScreenWidgetView setWithWidget:widgetView for:widgetView.sequence];
+ widgetView.sequenceSet = buttonState.sequenceSet;
+ widgetView.parentSequence = buttonState.parentSequence;
+ widgetView.folded = 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
widgetView.widthFactor = buttonState.widthFactor;
@@ -602,6 +603,12 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
[widgetView tweakHighlightAlphaWithAlpha:buttonState.highlightAlpha];
[widgetView setupAutoTapTimer];
[widgetView setupInertialScrollerWithFps:self->settings.framerate.intValue];
+
+ if(sequenceGenerated){
+ // NSLog(@"widgetView.sequence %d %f", widgetView.sequence, CACurrentMediaTime());
+ buttonState.sequence = widgetView.sequence;
+ oscProfile.buttonStatesEncoded[i] = [NSKeyedArchiver archivedDataWithRootObject:buttonState requiringSecureCoding:YES error:nil];
+ }
}
else if(buttonState.widgetType == LegacyOnScreenControls) hasLegacyWidget = true;
}
@@ -636,6 +643,8 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
}
}
+ [OnScreenWidgetView restoreFoldedStates];
+
NSLog(@"hasLegacyWidget %d %f", hasLegacyWidget, CACurrentMediaTime());
// legacy widgets
if(!OnScreenWidgetView.editMode && (hasLegacyWidget || !reload)){
diff --git a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m
index f1acb078..3847630f 100644
--- a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m
+++ b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m
@@ -128,6 +128,7 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
}
- (void)clearOnScreenWidgets{
+ [OnScreenWidgetView clearMappings];
for (UIView *subview in self.view.subviews) {
if ([subview isKindOfClass:[OnScreenWidgetView class]]) {
[subview removeFromSuperview];
@@ -156,7 +157,14 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
NSLog(@"reloadOnScreenWidgets name %@", buttonState.name);
if(buttonState.widgetType == CustomOnScreenWidget){
OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithCmdString:buttonState.name buttonLabel:buttonState.alias shape:buttonState.widgetShape profile:oscProfile]; //reconstruct widgetView
+
widgetView.sequence = buttonState.sequence == -1 ? ++sequence : buttonState.sequence;
+ [OnScreenWidgetView setWithWidget:widgetView for:widgetView.sequence];
+ widgetView.sequenceSet = buttonState.sequenceSet;
+ widgetView.parentSequence = buttonState.parentSequence;
+ widgetView.folded = buttonState.folded;
+ widgetView.revealMode = buttonState.revealMode;
+
widgetView.guidelineDelegate = (id)self;
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
widgetView.widthFactor = buttonState.widthFactor;
@@ -194,6 +202,13 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
}
else if(buttonState.widgetType == LegacyOnScreenControls) hasLegacyWidget = true;
}
+
+ for(OnScreenWidgetView* widget in self.onScreenWidgetViews){
+ [widget accessWidgetAttributes];
+ // if(widget.isFolder) [OnScreenWidgetView setWithFolded:widget.folded for:widget];
+ }
+
+ [OnScreenWidgetView restoreFoldedStates];
NSLog(@"reloadOnScreenWidgets hasLegacyWidget %d %f", hasLegacyWidget, CACurrentMediaTime());
if(hasLegacyWidget) [self reloadLegacyOnScreenControls:oscProfile];
else{
@@ -789,6 +804,7 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
OSCProfile* profile = [[OSCProfilesManager sharedManager:CGRectZero] getSelectedProfile];
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.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;
@@ -824,7 +840,12 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
[newWidget setLocationWithPosition:CGPointMake(90, 130)];
newWidget.sequence = [newWidget getAvailableSequence];
}
- else [newWidget setLocationWithPosition:widget.center];
+ else{
+ [newWidget setLocationWithPosition:widget.center];
+ newWidget.sequenceSet = [widget.sequenceSet mutableCopy];
+ newWidget.parentSequence = widget.parentSequence;
+ }
+ [OnScreenWidgetView setWithWidget:newWidget for:newWidget.sequence];
newWidget.sizeReference = widget.sizeReference;
[newWidget resizeWidgetView]; // resize must be called after relocation
[newWidget adjustTransparencyWithAlpha:widget.backgroundAlpha tweakBorderAlpha:NO];
@@ -849,6 +870,7 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
[self.view insertSubview:widgetView belowSubview:self.widgetPanelStack];
widgetView.hidden = true;
widgetView.sequence = [widgetView getAvailableSequence];
+ [OnScreenWidgetView setWithWidget:widgetView for:widgetView.sequence];
widgetView.guidelineDelegate = (id)self;
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
[self.onScreenWidgetViews addObject:widgetView];
@@ -1088,17 +1110,21 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
self.mouseButtonDownSelector.selectedSegmentIndex = selectedWidgetView.mouseButtonAction;
self.buttonModeStack.hidden = selectedWidgetView.widgetType != WidgetTypeEnumButton;
- [self.buttonModeSelector setEnabled:!selectedWidgetView.isFuncationalButton forSegmentAtIndex:0];
- [self.buttonModeSelector setEnabled:!selectedWidgetView.isFuncationalButton forSegmentAtIndex:1];
+ // bool slideToToggleEnabled = !selectedWidgetView.isFuncationalButton || selectedWidgetView.isFolder;
+ // [self.buttonModeSelector setEnabled:!selectedWidgetView.isFuncationalButton forSegmentAtIndex:0];
+ [self.buttonModeSelector setEnabled:true forSegmentAtIndex:slideToToggle];
+ bool slideAndHoldEnabled = !selectedWidgetView.isFuncationalButton || selectedWidgetView.isFolder;
+ [self.buttonModeSelector setEnabled:slideAndHoldEnabled forSegmentAtIndex:slideAndHold];
+ [self.buttonModeSelector setEnabled:!selectedWidgetView.isFolder forSegmentAtIndex:regular];
[self.buttonModeSelector setEnabled:!selectedWidgetView.isFuncationalButton
|| [selectedWidgetView.functionalButtonString containsString:@"ABSTCHDRAG"]
- forSegmentAtIndex:3];
+ forSegmentAtIndex:tapToToggle];
[self.buttonModeSelector setSelectedSegmentIndex:selectedWidgetView.buttonMode];
self.collectedWidgetsStack.hidden = !selectedWidgetView.isFolder;
[self.collectedWidgetsSelector setSelectedSegmentIndex:selectedWidgetView.folded ? 1 :0];
[self.revealModeSelector setSelectedSegmentIndex:selectedWidgetView.revealMode];
- [OnScreenWidgetView setCollectionWithHidden:selectedWidgetView.folded for:selectedWidgetView];
+ [OnScreenWidgetView setWithFolded:selectedWidgetView.folded for:selectedWidgetView];
[self autoFitStack:self.widgetPanelStack];
@@ -1264,14 +1290,14 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
- (void)collectionHiddenChanged:(UISegmentedControl* )sender{
if(self->selectedWidgetView != nil && self->widgetViewSelected){
- [OnScreenWidgetView setCollectionWithHidden:sender.selectedSegmentIndex==1 for:selectedWidgetView];
+ [OnScreenWidgetView setWithFolded:sender.selectedSegmentIndex==1 for:selectedWidgetView];
}
}
- (void)revealModeChanged:(UISegmentedControl* )sender{
if(self->selectedWidgetView != nil && self->widgetViewSelected){
selectedWidgetView.revealMode = sender.selectedSegmentIndex;
- [OnScreenWidgetView setCollectionWithHidden:selectedWidgetView.folded for:selectedWidgetView];
+ [OnScreenWidgetView setWithFolded:selectedWidgetView.folded for:selectedWidgetView];
}
}
@@ -1772,6 +1798,10 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
[self.layoutOSC updateGuidelinesForOnScreenWidget:widget];
[self.view bringSubviewToFront:widget];
trashCanButton.tintColor = trashCanButton.titleLabel.textColor = [self layerIsOverlappingWithTrashcanButton:widget.layer] ? [UIColor redColor] : trashCanStoryBoardColor;
+
+ if(trashCanButton.tintColor == [UIColor redColor] && selectedWidgetView != nil){
+ [OnScreenWidgetView setFreeWithWidget:selectedWidgetView];
+ }
self.undoButton.alpha = 1.0;
}
@@ -1861,6 +1891,8 @@ typedef NS_ENUM(NSUInteger, DecelerationRateSliderMode) {
if(!isToolbarHidden && self->selectedWidgetView != nil && [self layerIsOverlappingWithTrashcanButton:selectedWidgetView.layer]){
+ [OnScreenWidgetView setFreeWithWidget:selectedWidgetView];
+ [OnScreenWidgetView removeWidgetFromMappingsWithKey:selectedWidgetView.sequence];
[self->selectedWidgetView removeFromSuperview];
[self.onScreenWidgetViews removeObject:self->selectedWidgetView];
}
diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings
index c55917b0..03887591 100644
--- a/VoidLink/mul.lproj/iPad.xcstrings
+++ b/VoidLink/mul.lproj/iPad.xcstrings
@@ -4048,6 +4048,24 @@
"state" : "new",
"value" : " Reveal mode "
}
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 展开模式"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 展開模式"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 展開模式"
+ }
}
}
},
@@ -4756,6 +4774,24 @@
"state" : "new",
"value" : "coexist"
}
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "共存"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "共存"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "共存"
+ }
}
}
},
@@ -4768,6 +4804,24 @@
"state" : "new",
"value" : "exclusive"
}
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "互斥"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "互斥"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "互斥"
+ }
}
}
},
@@ -5048,25 +5102,61 @@
}
},
"sch-0e-nfa.segmentTitles[0]" : {
- "comment" : "Class = \"UISegmentedControl\"; sch-0e-nfa.segmentTitles[0] = \"display\"; ObjectID = \"sch-0e-nfa\";",
+ "comment" : "Class = \"UISegmentedControl\"; sch-0e-nfa.segmentTitles[0] = \"unfold\"; ObjectID = \"sch-0e-nfa\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
- "value" : "display"
+ "value" : "unfold"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "展开"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "展開"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "展開"
}
}
}
},
"sch-0e-nfa.segmentTitles[1]" : {
- "comment" : "Class = \"UISegmentedControl\"; sch-0e-nfa.segmentTitles[1] = \"hide\"; ObjectID = \"sch-0e-nfa\";",
+ "comment" : "Class = \"UISegmentedControl\"; sch-0e-nfa.segmentTitles[1] = \"fold\"; ObjectID = \"sch-0e-nfa\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
- "value" : "hide"
+ "value" : "fold"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "收起"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "收起"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "收起"
}
}
}
@@ -5518,6 +5608,24 @@
"state" : "new",
"value" : "Collected widgets "
}
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已收纳控件 "
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已收納小工具 "
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已收納小工具 "
+ }
}
}
},
diff --git a/VoidLink/mul.lproj/iPhone.xcstrings b/VoidLink/mul.lproj/iPhone.xcstrings
index db88b6ee..3d9291b6 100644
--- a/VoidLink/mul.lproj/iPhone.xcstrings
+++ b/VoidLink/mul.lproj/iPhone.xcstrings
@@ -2815,6 +2815,36 @@
}
}
},
+ "kek-77-Kmg.text" : {
+ "comment" : "Class = \"UILabel\"; text = \"Collected Widgets \"; ObjectID = \"kek-77-Kmg\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Collected Widgets "
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已收纳控件 "
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已收納小工具 "
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已收納小工具 "
+ }
+ }
+ }
+ },
"kva-gQ-CZh.text" : {
"comment" : "Class = \"UILabel\"; text = \"Size\"; ObjectID = \"kva-gQ-CZh\";",
"extractionState" : "extracted_with_value",
@@ -3205,6 +3235,66 @@
}
}
},
+ "myu-Th-CTZ.segmentTitles[0]" : {
+ "comment" : "Class = \"UISegmentedControl\"; myu-Th-CTZ.segmentTitles[0] = \"coexist\"; ObjectID = \"myu-Th-CTZ\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "coexist"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "共存"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "共存"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "共存"
+ }
+ }
+ }
+ },
+ "myu-Th-CTZ.segmentTitles[1]" : {
+ "comment" : "Class = \"UISegmentedControl\"; myu-Th-CTZ.segmentTitles[1] = \"exclusive\"; ObjectID = \"myu-Th-CTZ\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "exclusive"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "互斥"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "互斥"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "互斥"
+ }
+ }
+ }
+ },
"n6Z-cT-3a1.text" : {
"comment" : "Class = \"UILabel\"; text = \"Mic Volume\"; ObjectID = \"n6Z-cT-3a1\";",
"extractionState" : "extracted_with_value",
@@ -3775,6 +3865,66 @@
}
}
},
+ "Oo7-lI-LXT.segmentTitles[0]" : {
+ "comment" : "Class = \"UISegmentedControl\"; Oo7-lI-LXT.segmentTitles[0] = \"unfold\"; ObjectID = \"Oo7-lI-LXT\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "unfold"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "展开"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "展開"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "展開"
+ }
+ }
+ }
+ },
+ "Oo7-lI-LXT.segmentTitles[1]" : {
+ "comment" : "Class = \"UISegmentedControl\"; Oo7-lI-LXT.segmentTitles[1] = \"fold\"; ObjectID = \"Oo7-lI-LXT\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "fold"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "收起"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "收起"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "收起"
+ }
+ }
+ }
+ },
"ozU-7C-qKE.text" : {
"comment" : "Class = \"UILabel\"; text = \"Button down\"; ObjectID = \"ozU-7C-qKE\";",
"extractionState" : "extracted_with_value",
@@ -5527,6 +5677,36 @@
}
}
},
+ "YLN-X6-oj9.text" : {
+ "comment" : "Class = \"UILabel\"; text = \" Reveal mode \"; ObjectID = \"YLN-X6-oj9\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : " Reveal mode "
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 展开模式"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 展開模式"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 展開模式"
+ }
+ }
+ }
+ },
"Z9M-yq-an2.title" : {
"comment" : "Class = \"UIBarButtonItem\"; title = \"Import\"; ObjectID = \"Z9M-yq-an2\";",
"extractionState" : "extracted_with_value",