diff --git a/.gitignore b/.gitignore index a6d0372..b390af2 100644 --- a/.gitignore +++ b/.gitignore @@ -76,4 +76,5 @@ Plugins/*/Binaries/* DerivedDataCache/* # Mac shitty metadata -.DS_Store \ No newline at end of file +.DS_Store +Source/Blend4Real/.idea/ diff --git a/Source/Blend4Real/Private/Blend4Real.cpp b/Source/Blend4Real/Private/Blend4Real.cpp index f0abd18..99edeb0 100644 --- a/Source/Blend4Real/Private/Blend4Real.cpp +++ b/Source/Blend4Real/Private/Blend4Real.cpp @@ -122,10 +122,23 @@ void FBlend4RealModule::OnBeginPIE(bool bIsSimulating) { bWasEnabledBeforePIE = false; } + + // Unregister the input processor entirely during PIE to prevent transient mode from + // activating and accessing invalid viewport state + if (BlenderInputHandler.IsValid()) + { + BlenderInputHandler->UnregisterInputProcessor(); + } } void FBlend4RealModule::OnEndPIE(bool bIsSimulating) { + // Re-register the input processor so it can intercept keys again (including for transient mode) + if (BlenderInputHandler.IsValid()) + { + BlenderInputHandler->RegisterInputProcessor(); + } + // Re-enable if it was enabled before PIE started if (bWasEnabledBeforePIE && BlenderInputHandler.IsValid() && !BlenderInputHandler->IsEnabled()) { diff --git a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp index c97ce1c..e8ae4c0 100644 --- a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp +++ b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp @@ -42,9 +42,8 @@ FBlend4RealInputProcessor::~FBlend4RealInputProcessor() void FBlend4RealInputProcessor::RegisterInputProcessor() { - if (bIsEnabled && FSlateApplication::IsInitialized()) + if (FSlateApplication::IsInitialized()) { - PlatformInputs::InitializeKeyboardLayoutCache(); FSlateApplication::Get().RegisterInputPreProcessor(SharedThis(this)); } } @@ -59,6 +58,9 @@ void FBlend4RealInputProcessor::Init(TSharedPtr) GLevelEditorModeTools().SetShowWidget(true); GLevelEditorModeTools().SaveConfig(); + // Always register so we can intercept transform keys (G/R/S) for transient activation even when disabled + RegisterInputProcessor(); + // Load saved enabled state from global editor settings (stored in user's AppData, not project) bool bWasEnabled = false; GConfig->GetBool(TEXT("Blend4Real"), TEXT("bEnabled"), bWasEnabled, GEditorSettingsIni); @@ -76,11 +78,19 @@ void FBlend4RealInputProcessor::UnregisterInputProcessor() { if (FSlateApplication::IsInitialized()) { - PlatformInputs::ShutdownKeyboardLayoutCache(); FSlateApplication::Get().UnregisterInputPreProcessor(SharedThis(this)); } } +void FBlend4RealInputProcessor::EndTransientModeIfActive() +{ + if (bTransientMode) + { + bTransientMode = false; + ToggleEnabled(); + } +} + void FBlend4RealInputProcessor::ToggleEnabled(const bool bInvalidateRender) { bIsEnabled = !bIsEnabled; @@ -110,9 +120,12 @@ void FBlend4RealInputProcessor::ToggleEnabled(const bool bInvalidateRender) { ViewportClient->Invalidate(); } - else if (GEditor && GEditor->GetActiveViewport()) + else if (GEditor) { - GEditor->GetActiveViewport()->Invalidate(); + if (FViewport* ActiveViewport = GEditor->GetActiveViewport()) + { + ActiveViewport->Invalidate(); + } } } @@ -122,13 +135,13 @@ void FBlend4RealInputProcessor::ToggleEnabled(const bool bInvalidateRender) if (bIsEnabled) { - RegisterInputProcessor(); + PlatformInputs::InitializeKeyboardLayoutCache(); PivotVisualizationController->Enable(); UE_LOG(LogTemp, Display, TEXT("Blender Controls: Enabled")); } else { - UnregisterInputProcessor(); + PlatformInputs::ShutdownKeyboardLayoutCache(); PivotVisualizationController->Disable(); UE_LOG(LogTemp, Display, TEXT("Blender Controls: Disabled")); } @@ -232,9 +245,52 @@ void FBlend4RealInputProcessor::Tick(const float DeltaTime, FSlateApplication& S bool FBlend4RealInputProcessor::HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) { + // Instant Blender Controls: when disabled, intercept transform keys to activate transient mode. if (!bIsEnabled) { - return false; + const UBlend4RealSettings* Settings = UBlend4RealSettings::Get(); + if (!Settings || !Settings->bInstantBlenderControls) + { + return false; + } + + const FVector2D MousePosition = SlateApp.GetCursorPos(); + const FName ViewportFilter = Settings->bInstantControlsLevelOnly ? FName("SLevelViewport") : NAME_None; + if (!Blend4RealUtils::IsMouseOverViewport(MousePosition, ViewportFilter)) + { + return false; + } + + if (UBlend4RealSettings::MatchesChord(Settings->InstantDuplicateKey, InKeyEvent)) + { + bTransientMode = true; + ToggleEnabled(); + SelectionActionsController->DuplicateSelectedAndGrab(); + return true; + } + + ETransformMode TransientTransformMode; + if (UBlend4RealSettings::MatchesChord(Settings->InstantTranslationKey, InKeyEvent)) + { + TransientTransformMode = ETransformMode::Translation; + } + else if (UBlend4RealSettings::MatchesChord(Settings->InstantRotationKey, InKeyEvent)) + { + TransientTransformMode = ETransformMode::Rotation; + } + else if (UBlend4RealSettings::MatchesChord(Settings->InstantScaleKey, InKeyEvent)) + { + TransientTransformMode = ETransformMode::Scale; + } + else + { + return false; + } + + bTransientMode = true; + ToggleEnabled(); + TransformController->BeginTransform(TransientTransformMode); + return true; } // Only process input if mouse is over a viewport (not requiring keyboard focus) @@ -312,6 +368,7 @@ bool FBlend4RealInputProcessor::HandleKeyDownEvent(FSlateApplication& SlateApp, TransformController->ApplyNumericTransform(); } TransformController->EndTransform(true); + EndTransientModeIfActive(); return true; } @@ -319,6 +376,7 @@ bool FBlend4RealInputProcessor::HandleKeyDownEvent(FSlateApplication& SlateApp, if (Key == EKeys::Escape && ModMask == 0) { TransformController->EndTransform(false); + EndTransientModeIfActive(); return true; } @@ -393,7 +451,6 @@ bool FBlend4RealInputProcessor::HandleMouseMoveEvent(FSlateApplication& SlateApp const FVector2D Delta = CurrentPosition - LastMousePosition; LastMousePosition = CurrentPosition; - // For ongoing operations (navigation/transform), continue processing even if mouse moves outside viewport // This ensures smooth camera movement and transforms when mouse drags outside viewport const bool bInOperation = NavigationController->IsNavigating() || TransformController->IsTransforming(); @@ -502,11 +559,13 @@ bool FBlend4RealInputProcessor::HandleMouseButtonDownEvent(FSlateApplication& Sl if (UBlend4RealSettings::MatchesChord(Settings->ApplyTransformKey, MouseEvent)) { TransformController->EndTransform(true); + EndTransientModeIfActive(); return true; } if (UBlend4RealSettings::MatchesChord(Settings->CancelTransformKey, MouseEvent)) { TransformController->EndTransform(false); + EndTransientModeIfActive(); return true; } } diff --git a/Source/Blend4Real/Public/Blend4RealInputProcessor.h b/Source/Blend4Real/Public/Blend4RealInputProcessor.h index ee0e031..4ee687b 100644 --- a/Source/Blend4Real/Public/Blend4RealInputProcessor.h +++ b/Source/Blend4Real/Public/Blend4RealInputProcessor.h @@ -16,6 +16,16 @@ class UViewportOrbitInteraction; /** * Input processor for Blender-style controls in Unreal Editor. * Acts as a thin dispatcher routing input to specialized controllers. + * + * Always registered as a Slate input pre-processor. Supports two activation modes: + * + * - Persistent mode: toggled via the toolbar button. Blend4Real stays enabled until + * explicitly toggled off. All Blender controls (transforms, navigation, selection) are active. + * + * - Transient mode (opt-in via "Instant Blender Controls" setting): pressing a transform key + * (G/R/S) while Blend4Real is disabled temporarily activates it for the duration of the + * transform. Once confirmed (LMB/Enter/Space) or cancelled (RMB/Escape), Blend4Real + * disables itself and returns to standard Unreal controls. */ class FBlend4RealInputProcessor : public TSharedFromThis, public IInputProcessor { @@ -35,13 +45,19 @@ class FBlend4RealInputProcessor : public TSharedFromThis InLevelEditor); + /** End transient mode if active: clears the flag and disables Blend4Real. */ + void EndTransientModeIfActive(); + bool bIsEnabled = false; + + /** When true, Blend4Real was activated by a transform key press and will auto-disable when the transform ends. */ + bool bTransientMode = false; bool bCursorHidden = false; FVector2D LastMousePosition = FVector2D::ZeroVector; FIntPoint PreNavigationCursorPos = FIntPoint::ZeroValue; diff --git a/Source/Blend4Real/Public/Blend4RealSettings.h b/Source/Blend4Real/Public/Blend4RealSettings.h index d725595..ee70c52 100644 --- a/Source/Blend4Real/Public/Blend4RealSettings.h +++ b/Source/Blend4Real/Public/Blend4RealSettings.h @@ -33,6 +33,37 @@ class BLEND4REAL_API UBlend4RealSettings : public UDeveloperSettings UPROPERTY(Config, EditAnywhere, Category = "Navigation", meta = (DisplayName = "Orbit Mode", ToolTip = "Controls how the camera orbits when using middle mouse button")) EBlend4RealOrbitMode OrbitMode = EBlend4RealOrbitMode::OrbitAroundMouseProjection; + // Instant Blender Controls: press G/R/S to start a transform without enabling Blend4Real first. + // Blend4Real activates automatically for the duration of the transform, then returns to standard Unreal controls. + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Controls", + ToolTip = "When enabled, pressing a transform key (G/R/S) over a viewport will temporarily activate Blender controls for that transform. Once confirmed or cancelled, controls return to standard Unreal behavior. This works independently of the toolbar toggle.")) + bool bInstantBlenderControls = false; + + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Controls Level Viewport Only", + EditCondition = "bInstantBlenderControls", + ToolTip = "When enabled, Instant Blender Controls only activates in the Level Editor viewport, not in Blueprint or other viewports.")) + bool bInstantControlsLevelOnly = true; + + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Grab Key", + EditCondition = "bInstantBlenderControls", + ToolTip = "Key to start a grab/translate transform in Instant Blender Controls mode.")) + FInputChord InstantTranslationKey = FInputChord(EKeys::G); + + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Rotation Key", + EditCondition = "bInstantBlenderControls", + ToolTip = "Key to start a rotation transform in Instant Blender Controls mode.")) + FInputChord InstantRotationKey = FInputChord(EKeys::R); + + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Scale Key", + EditCondition = "bInstantBlenderControls", + ToolTip = "Key to start a scale transform in Instant Blender Controls mode.")) + FInputChord InstantScaleKey = FInputChord(EKeys::S, EModifierKey::Shift); + + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Duplicate Key", + EditCondition = "bInstantBlenderControls", + ToolTip = "Key to duplicate selected actors and immediately enter grab mode in Instant Blender Controls mode.")) + FInputChord InstantDuplicateKey = FInputChord(EKeys::D, EModifierKey::Shift); + // Helper methods to get orbit flags bool ShouldOrbitAroundSelection() const { return OrbitMode == EBlend4RealOrbitMode::OrbitAroundSelection; } bool ShouldOrbitAroundMouseHit() const { return OrbitMode == EBlend4RealOrbitMode::OrbitAroundMouseProjection; }