- motion control demo1

- feat: movable on-screen functional buttons
This commit is contained in:
TrueZhuanjia
2025-10-04 16:44:43 +08:00
parent b96e2d1bdf
commit 67af68fcf1
27 changed files with 731 additions and 245 deletions
+75 -28
View File
@@ -35,16 +35,14 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
*/
@implementation StreamView {
UIView* streamFrameTopLayerView;
UIViewController* streamFrameVC;
TemporarySettings* settings;
OnScreenControls* onScreenControls;
KeyboardInputField* keyInputField;
BOOL isInputingText;
NSMutableSet* keysDown;
float streamAspectRatio;
// iOS 13.4 mouse support
NSInteger lastMouseButtonMask;
@@ -78,6 +76,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
UIKeyModifierFlags comboKeyModifierFlags;
WidgetSizeTransition _widgetSizeTransition;
OSCProfilesManager* profilesManager;
}
- (void) setupStreamView:(ControllerSupport*)controllerSupport
@@ -156,7 +155,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
}
// we'll render on-screen controls on the toplayer too:
onScreenControls = [[OnScreenControls alloc] initWithView:self->streamFrameTopLayerView controllerSup:controllerSupport streamConfig:streamConfig]; // don't delete, this is mandatory
_onScreenControls = [[OnScreenControls alloc] initWithView:self->streamFrameTopLayerView controllerSup:controllerSupport streamConfig:streamConfig]; // don't delete, this is mandatory
/*
// here we pass the tap recognizer to the onscreencontrols obj
if (settings.touchMode.intValue == RelativeTouch){
@@ -167,7 +166,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
OnScreenControlsLevel level = (OnScreenControlsLevel)[settings.onscreenControls integerValue];
if (settings.touchMode.intValue != RelativeTouch && settings.touchMode.intValue != NativeTouch ) {
Log(LOG_I, @"On-screen controls disabled in non-relative touch mode");
[onScreenControls setLevel:OnScreenControlsLevelOff];
[_onScreenControls setLevel:OnScreenControlsLevelOff];
//pass touchesCaptureByOnScreenButtons Set to the native touchhandler, this NSSet is init witihin onscreencontrols class, don't do it again in native touch handler class
[OnScreenControls.touchAddrsCapturedByOnScreenControls removeAllObjects]; // reset the attribute to nil
@@ -182,11 +181,11 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
}
//else if (level == OnScreenControlsLevelAuto) {
else if (false) { // level auto cancelled in settings
[controllerSupport initAutoOnScreenControlMode:onScreenControls];
[controllerSupport initAutoOnScreenControlMode:_onScreenControls];
}
else {
Log(LOG_I, @"Setting manual on-screen controls level: %d", (int)level);
[onScreenControls setLevel:level];
[_onScreenControls setLevel:level];
}
// It would be nice to just use GCMouse on iOS 14+ and the older API on iOS 13
// but unfortunately that isn't possible today. GCMouse doesn't recognize many
@@ -263,6 +262,17 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
NSLog(@"keyboard will show %f", CACurrentMediaTime());
}
- (UIViewController *)parentViewController {
UIResponder *responder = self;
while (responder) {
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)responder;
}
responder = responder.nextResponder;
}
return nil;
}
// this method also deals with recovering streamview when local keyboard is turned off
- (void)keyboardWillHide{
// NSLog(@"keyboard will hide markmark %f", CACurrentMediaTime());
@@ -401,7 +411,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
- (void) showOnScreenControls {
#if !TARGET_OS_TV
if(!_widgetToolOpened) [onScreenControls show];
if(!_widgetToolOpened) [_onScreenControls show];
#endif
}
@@ -411,7 +421,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
- (void) disableOnScreenControls {
#if !TARGET_OS_TV
[onScreenControls setLevel:OnScreenControlsLevelOff];
[_onScreenControls setLevel:OnScreenControlsLevelOff];
#endif
}
@@ -438,7 +448,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
andConfig:(StreamConfiguration*)streamConfig {
// we'll render on-screen controllers on the toplayer too.
onScreenControls = [[OnScreenControls alloc] initWithView:self->streamFrameTopLayerView controllerSup:controllerSupport streamConfig:streamConfig];
_onScreenControls = [[OnScreenControls alloc] initWithView:self->streamFrameTopLayerView controllerSup:controllerSupport streamConfig:streamConfig];
/*
// pass mouseRightClickTapRecognizer to onScreenControls obj here:
if([self isOscEnabled]){
@@ -446,7 +456,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
RelativeTouchHandler* relativeTouchHandler = (RelativeTouchHandler *)touchHandler;
onScreenControls.mouseRightClickTapRecognizer = relativeTouchHandler.mouseRightClickTapRecognizer;
} */
if([self isOscEnabled]) [onScreenControls setLevel:(OnScreenControlsLevel)settings.onscreenControls.intValue];
if([self isOscEnabled]) [_onScreenControls setLevel:(OnScreenControlsLevel)settings.onscreenControls.intValue];
}
@@ -477,6 +487,35 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
else return longSide;
}
- (void)saveRelocatedWidgetViews{
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.identifier];
}
}
if(relocatedWidgetDict.allKeys.count == 0) return;
profilesManager = [OSCProfilesManager sharedManager:self->streamFrameTopLayerView.bounds];
OSCProfile *newProfile = [profilesManager getSelectedProfile];
for (NSInteger i = 0; i < newProfile.buttonStatesEncoded.count; i++) {
NSData* buttonStateEncoded = newProfile.buttonStatesEncoded[i];
OnScreenButtonState *newButtonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
if([relocatedWidgetDict.allKeys containsObject:newButtonState.identifier]){
OnScreenWidgetView* widget = [relocatedWidgetDict objectForKey:newButtonState.identifier];
newButtonState.position = [profilesManager normalizeWidgetPosition:widget.center];
NSData *newButtonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:newButtonState requiringSecureCoding:YES error:nil];
newProfile.buttonStatesEncoded[i] = newButtonStateEncoded;
}
}
[profilesManager replaceSelectedProfileWith:newProfile];
}
- (void) reloadOnScreenWidgetViews{
// NSLog(@"reload on screen keyboard buttons here");
@@ -484,25 +523,33 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
// remove all keyboard widget views first
[self clearOnScreenWidgets];
// get streamFrameVC
if(!streamFrameVC) streamFrameVC = [self parentViewController];
// bool customOscEnabled = [self isOscEnabled] && settings.onscreenControls.intValue == OnScreenControlsLevelCustom;
if(![self isOnScreenWidgetEnabled]) return;
OnScreenWidgetView.buttonVisualFeedbackEnabled = settings.buttonVisualFeedback;
OSCProfilesManager* profilesManager = [OSCProfilesManager sharedManager: self.bounds];
if(!profilesManager) profilesManager = [OSCProfilesManager sharedManager:self->streamFrameTopLayerView.bounds];
OSCProfile *oscProfile = [profilesManager getSelectedProfile]; //returns the currently selected OSCProfile
if(!OnScreenWidgetView.editMode){ // in edit mode, keyboard widget view will be updated within layoutool view controller.
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
for (NSData *buttonStateEncoded in oscProfile.buttonStatesEncoded) {
OnScreenButtonState* buttonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
if(buttonState.buttonType == CustomOnScreenWidget){
if(buttonState.widgetType == CustomOnScreenWidget){
OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithCmdString:buttonState.name buttonLabel:buttonState.alias shape:buttonState.widgetShape]; //reconstruct widgetView
//--------------------------------------------------
onScreenControls.delegate = widgetView; // connecting onScreenControls to OnScreenWidgetView, sending the active instance for touchPad stick control
[onScreenControls sendInstance];
_onScreenControls.instanceReceiverDelegate = widgetView; // connecting onScreenControls to OnScreenWidgetView, sending the active instance for touchPad stick control
[_onScreenControls sendInstance];
//--------------------------------------------------
widgetView.functionalButtonDelegate = (id<OnScreenFunctionalButtonDelegate>)streamFrameVC;
widgetView.mixInputDelegate = (id<OnScreenWidgetStickMixedInputDelegate>) [MotionHandler sharedInstance];
widgetView.identifier = buttonState.identifier;
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
widgetView.widthFactor = buttonState.widthFactor;
widgetView.heightFactor = buttonState.heightFactor;
@@ -515,7 +562,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
widgetView.trackballDecelerationRate = buttonState.decelerationRate;
widgetView.stickIndicatorOffset = buttonState.stickIndicatorOffset;
widgetView.minStickOffset = buttonState.minStickOffset;
widgetView.buttonTriggerMode = buttonState.buttonTriggerMode;
widgetView.buttonMode = buttonState.buttonMode;
// Add the widgetView to the view controller's view
[self->streamFrameTopLayerView addSubview:widgetView]; // add keyboard button to the stream frame view. must add it to the target view before setting location.
buttonState.position = [self denormalizeWidgetPosition:buttonState.position];
@@ -533,11 +580,11 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
}
- (OnScreenControlsLevel) getCurrentOscState {
if (onScreenControls == nil) {
if (_onScreenControls == nil) {
return OnScreenControlsLevelOff;
}
else {
return [onScreenControls getLevel];
return [_onScreenControls getLevel];
}
}
@@ -714,10 +761,10 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
[self startInteractionTimer];
if(settings.touchMode.intValue == NativeTouch || settings.touchMode.intValue == RelativeTouch){
[self->onScreenControls handleTouchDownEvent:touches];
[self->_onScreenControls handleTouchDownEvent:touches];
[self->touchHandler touchesBegan:touches withEvent:event];
}
else if(![onScreenControls handleTouchDownEvent:touches]) [touchHandler touchesBegan:touches withEvent:event];
else if(![_onScreenControls handleTouchDownEvent:touches]) [touchHandler touchesBegan:touches withEvent:event];
}
- (UIBarButtonItem *)createButtonWithImageNamed:(NSString *)imageName backgroundColor:(UIColor *)backgroundColor target:(id)target action:(SEL)action keyCode:(NSInteger)keyCode isToggleable:(BOOL)isToggleable {
@@ -880,9 +927,9 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
if(self->settings.touchMode.intValue == NativeTouch || self->settings.touchMode.intValue == RelativeTouch){
[self->touchHandler touchesMoved:touches withEvent:event];
[self->onScreenControls handleTouchMovedEvent:touches];
[self->_onScreenControls handleTouchMovedEvent:touches];
}
else if(![self->onScreenControls handleTouchMovedEvent:touches]) [self->touchHandler touchesMoved:touches withEvent:event];
else if(![self->_onScreenControls handleTouchMovedEvent:touches]) [self->touchHandler touchesMoved:touches withEvent:event];
}
- (void) handleKeyCombos:(UIPress*) press{
@@ -976,9 +1023,9 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
if(settings.touchMode.intValue == NativeTouch || settings.touchMode.intValue == RelativeTouch){
[self->touchHandler touchesEnded:touches withEvent:event]; // when touches ended, must call the native touchhandler before onScreenControls, since the NSSet of touches captured by on screen button shall be updated later
[self->onScreenControls handleTouchUpEvent:touches];
[self->_onScreenControls handleTouchUpEvent:touches];
}
else if(![onScreenControls handleTouchUpEvent:touches]) [touchHandler touchesEnded:touches withEvent:event];
else if(![_onScreenControls handleTouchUpEvent:touches]) [touchHandler touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {