├── .gitignore ├── .images ├── 01-palette.png ├── 02-layer-distance.png ├── 02b-hierarchy.png ├── 03-panel-offset.png └── parallax-preview.gif ├── Config └── FilterPlugin.ini ├── LICENSE ├── ParallaxPanel.uplugin ├── README.md ├── Resources └── Icon128.png └── Source └── ParallaxPanel ├── ParallaxPanel.Build.cs ├── Private ├── Module.cpp ├── ParallaxPanel.cpp └── ParallaxPanelSlot.cpp └── Public ├── ParallaxPanel.h └── ParallaxPanelSlot.h /.gitignore: -------------------------------------------------------------------------------- 1 | Binaries 2 | Intermediate 3 | Saved -------------------------------------------------------------------------------- /.images/01-palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-unrealist/parallax-panel/89485c862e825f4ec99ccb155c0a87fc220d00f9/.images/01-palette.png -------------------------------------------------------------------------------- /.images/02-layer-distance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-unrealist/parallax-panel/89485c862e825f4ec99ccb155c0a87fc220d00f9/.images/02-layer-distance.png -------------------------------------------------------------------------------- /.images/02b-hierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-unrealist/parallax-panel/89485c862e825f4ec99ccb155c0a87fc220d00f9/.images/02b-hierarchy.png -------------------------------------------------------------------------------- /.images/03-panel-offset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-unrealist/parallax-panel/89485c862e825f4ec99ccb155c0a87fc220d00f9/.images/03-panel-offset.png -------------------------------------------------------------------------------- /.images/parallax-preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-unrealist/parallax-panel/89485c862e825f4ec99ccb155c0a87fc220d00f9/.images/parallax-preview.gif -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | ; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and 3 | ; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. 4 | ; 5 | ; Examples: 6 | ; /README.txt 7 | ; /Extras/... 8 | ; /Binaries/ThirdParty/*.dll 9 | 10 | README.md 11 | LICENSE 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 citrus - https://unrealist.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ParallaxPanel.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Parallax Panel", 6 | "Description": "A 2D parallax panel widget that creates an illusion of depth in UI.", 7 | "Category": "UI", 8 | "CreatedBy": "citrus", 9 | "CreatedByURL": "https://unrealist.org", 10 | "DocsURL": "https://unrealist.org/plugins/parallax-panel", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": false, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "ParallaxPanel", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Icon for the plugin showing a stylized scene of a mountain with clouds and trees.](Resources/Icon128.png) 2 | 3 | # Parallax Panel 4 | A 2D panel widget that simulates a 3D depth effect in user interfaces. 5 | 6 | ![An animated GIF showing a simple cityscape with each layer moving at different speeds.](.images/parallax-preview.gif) 7 | 8 | ## Features 9 | * Simple and lightweight panel 10 | * Any widget can be a layer 11 | * Supports [widget animations](https://docs.unrealengine.com/5.3/en-US/animating-umg-widgets-in-unreal-engine/) 12 | 13 | ## User Guide 14 | In a widget blueprint, create a Parallax Panel widget from the palette. 15 | 16 | ![Screenshot showing Parallax Panel categorized under Panel in the Palette window.](.images/01-palette.png) 17 |

18 | 19 | Add child widgets to the panel. Each child widget is a layer. 20 | 21 | ![Screenshot showing a Parallax Panel widget with three different image child widgets in the hierarchy.](.images/02b-hierarchy.png) 22 |

23 | 24 | Set the **Distance** of each layer under *Slot (Parallax Panel Slot)* in the Details panel. 25 | 26 | ![Screenshot showing the Distance property in the details panel for a child widget. The property is marked with a red arrow.](.images/02-layer-distance.png) 27 |

28 | 29 | Use the **Offset** property in the Parallax Panel to control the perspective. 30 | 31 | ![Screenshot showing the Offset property in the details panel for a Parallax Panel widget.](.images/03-panel-offset.png) 32 |

33 | 34 | The parallax scrolling equation used in this plugin is $\text{Transform} = \frac{\text{Offset}}{1+(\frac{\text{Distance}}{100})}$. A layer with a distance of 100 will move twice less than a layer with a distance of zero. 35 | 36 | --------------------- 37 | 38 | Forest icons created by Freepik - [Flaticon](https://www.flaticon.com/free-icons/forest) 39 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-unrealist/parallax-panel/89485c862e825f4ec99ccb155c0a87fc220d00f9/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/ParallaxPanel/ParallaxPanel.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 citrus - https://unrealist.org 2 | // Licensed under the MIT License. 3 | 4 | using UnrealBuildTool; 5 | 6 | public class ParallaxPanel : ModuleRules 7 | { 8 | public ParallaxPanel(ReadOnlyTargetRules Target) : base(Target) 9 | { 10 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 11 | 12 | PrivateDependencyModuleNames.AddRange( 13 | new[] 14 | { 15 | "Core", 16 | "CoreUObject", 17 | "SlateCore", 18 | "UMG" 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Source/ParallaxPanel/Private/Module.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 citrus - https://unrealist.org 2 | // Licensed under the MIT License. 3 | 4 | IMPLEMENT_MODULE(FDefaultModuleImpl, ParallaxPanel); 5 | -------------------------------------------------------------------------------- /Source/ParallaxPanel/Private/ParallaxPanel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 citrus - https://unrealist.org 2 | // Licensed under the MIT License. 3 | 4 | #include "ParallaxPanel.h" 5 | 6 | #include "ParallaxPanelSlot.h" 7 | 8 | UParallaxPanel::UParallaxPanel() 9 | { 10 | bIsVariable = false; 11 | SetVisibilityInternal(ESlateVisibility::SelfHitTestInvisible); 12 | } 13 | 14 | #if WITH_EDITOR 15 | const FText UParallaxPanel::GetPaletteCategory() 16 | { 17 | return NSLOCTEXT("ParallaxPanel", "PaletteCategory", "Panel"); 18 | } 19 | #endif 20 | 21 | UParallaxPanelSlot *UParallaxPanel::AddChildToParallaxPanel(UWidget *Content) 22 | { 23 | return Cast(AddChild(Content)); 24 | } 25 | 26 | void UParallaxPanel::SetOffset(FVector2D InOffset) 27 | { 28 | Offset = InOffset; 29 | 30 | if (ContainerOverlay.IsValid()) 31 | { 32 | for (UPanelSlot *PanelSlot : Slots) 33 | { 34 | if (UParallaxPanelSlot *ParallaxPanelSlot = Cast(PanelSlot)) 35 | { 36 | ParallaxPanelSlot->SetOffset(InOffset); 37 | } 38 | } 39 | } 40 | } 41 | 42 | FVector2D UParallaxPanel::GetOffset() const 43 | { 44 | return Offset; 45 | } 46 | 47 | UClass *UParallaxPanel::GetSlotClass() const 48 | { 49 | return UParallaxPanelSlot::StaticClass(); 50 | } 51 | 52 | void UParallaxPanel::OnSlotAdded(UPanelSlot *InSlot) 53 | { 54 | // Add the child to the live canvas if it already exists. 55 | if (ContainerOverlay.IsValid()) 56 | { 57 | CastChecked(InSlot)->BuildSlot(ContainerOverlay.ToSharedRef()); 58 | } 59 | } 60 | 61 | void UParallaxPanel::OnSlotRemoved(UPanelSlot *InSlot) 62 | { 63 | // Remove the widget from the live slot if it exists. 64 | if (ContainerOverlay.IsValid() && InSlot->Content) 65 | { 66 | TSharedPtr Widget = InSlot->Content->GetCachedWidget(); 67 | if (Widget.IsValid()) 68 | { 69 | ContainerOverlay->RemoveSlot(Widget.ToSharedRef()); 70 | } 71 | } 72 | } 73 | 74 | TSharedRef UParallaxPanel::RebuildWidget() 75 | { 76 | ContainerOverlay = SNew(SOverlay); 77 | 78 | for (UPanelSlot *PanelSlot : Slots) 79 | { 80 | if (UParallaxPanelSlot *ParallaxPanelSlot = Cast(PanelSlot)) 81 | { 82 | ParallaxPanelSlot->Parent = this; 83 | ParallaxPanelSlot->BuildSlot(ContainerOverlay.ToSharedRef()); 84 | } 85 | } 86 | 87 | return ContainerOverlay.ToSharedRef(); 88 | } 89 | 90 | void UParallaxPanel::ReleaseSlateResources(bool bReleaseChildren) 91 | { 92 | Super::ReleaseSlateResources(bReleaseChildren); 93 | ContainerOverlay.Reset(); 94 | } 95 | 96 | void UParallaxPanel::SynchronizeProperties() 97 | { 98 | Super::SynchronizeProperties(); 99 | SetOffset(Offset); 100 | } 101 | -------------------------------------------------------------------------------- /Source/ParallaxPanel/Private/ParallaxPanelSlot.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 citrus - https://unrealist.org 2 | // Licensed under the MIT License. 3 | 4 | #include "ParallaxPanelSlot.h" 5 | 6 | #include "Components/Widget.h" 7 | #include "ParallaxPanel.h" 8 | 9 | void UParallaxPanelSlot::SetDistance(float InDistance) 10 | { 11 | Distance = InDistance < 0.f ? 0.f : Distance; 12 | } 13 | 14 | float UParallaxPanelSlot::GetDistance() const 15 | { 16 | return Distance; 17 | } 18 | 19 | void UParallaxPanelSlot::SetOffset(FVector2D InOffset) 20 | { 21 | if (Slot) 22 | { 23 | TSharedRef ContentContainer = Slot->GetWidget(); 24 | 25 | float ParallaxFactor = 1 / (1 + Distance / 100); 26 | ContentContainer->SetRenderTransform(InOffset * ParallaxFactor); 27 | } 28 | } 29 | 30 | void UParallaxPanelSlot::BuildSlot(TSharedRef InOverlay) 31 | { 32 | // Wrap the child widget in a container so that parallax transformation is applied without overriding the child's 33 | // existing render transform properties. 34 | TSharedRef ContentContainer = 35 | SNew(SOverlay) + SOverlay::Slot()[Content == nullptr ? SNullWidget::NullWidget : Content->TakeWidget()]; 36 | 37 | InOverlay->AddSlot() 38 | .Expose(Slot) 39 | .Padding(GetPadding()) 40 | .HAlign(GetHorizontalAlignment()) 41 | .VAlign(GetVerticalAlignment()) 42 | [ContentContainer]; 43 | } 44 | 45 | void UParallaxPanelSlot::SynchronizeProperties() 46 | { 47 | Super::SynchronizeProperties(); 48 | SetDistance(Distance); 49 | } 50 | -------------------------------------------------------------------------------- /Source/ParallaxPanel/Public/ParallaxPanel.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 citrus - https://unrealist.org 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | 6 | #include "Components/PanelWidget.h" 7 | 8 | #include "ParallaxPanel.generated.h" 9 | 10 | /** 11 | * Allows widgets to be stacked on top of each other and translated based on the current offset of the panel and the 12 | * distance of each child widget. 13 | */ 14 | UCLASS(MinimalAPI) 15 | class UParallaxPanel : public UPanelWidget 16 | { 17 | GENERATED_BODY() 18 | 19 | public: 20 | UParallaxPanel(); 21 | 22 | #if WITH_EDITOR 23 | virtual const FText GetPaletteCategory() override; 24 | #endif 25 | 26 | /** Adds a child to this panel. */ 27 | UFUNCTION(BlueprintCallable, Category = "Widget") 28 | class UParallaxPanelSlot *AddChildToParallaxPanel(UWidget *Content); 29 | 30 | /** Sets the offset. This will translate all child widgets based on their distance. */ 31 | UFUNCTION(BlueprintCallable, Category = "Parallax Panel") 32 | void SetOffset(FVector2D InOffset); 33 | 34 | /** Gets the current parallax perspective. */ 35 | UFUNCTION(BlueprintPure, Category="Parallax Panel") 36 | FVector2D GetOffset() const; 37 | 38 | protected: 39 | virtual UClass *GetSlotClass() const override; 40 | virtual void OnSlotAdded(UPanelSlot *InSlot) override; 41 | virtual void OnSlotRemoved(UPanelSlot *InSlot) override; 42 | 43 | virtual TSharedRef RebuildWidget() override; 44 | virtual void ReleaseSlateResources(bool bReleaseChildren) override; 45 | virtual void SynchronizeProperties() override; 46 | 47 | /** The current offset. */ 48 | UPROPERTY(EditAnywhere, Getter, Setter, BlueprintGetter = GetOffset, BlueprintSetter = SetOffset, Category = "Parallax Panel", meta = (Delta = 1, LinearDeltaSensitivity = 1)) 49 | FVector2D Offset; 50 | 51 | TSharedPtr ContainerOverlay; 52 | }; 53 | -------------------------------------------------------------------------------- /Source/ParallaxPanel/Public/ParallaxPanelSlot.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 citrus - https://unrealist.org 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | 6 | #include "Components/OverlaySlot.h" 7 | 8 | #include "ParallaxPanelSlot.generated.h" 9 | 10 | /** 11 | * Slot for UParallaxPanel. The distance property controls how much this slot translates in comparison to other slots 12 | * based on the offset of the parent UParallaxPanel. 13 | */ 14 | UCLASS(MinimalAPI) 15 | class UParallaxPanelSlot : public UOverlaySlot 16 | { 17 | friend class UParallaxPanel; 18 | 19 | GENERATED_BODY() 20 | 21 | public: 22 | /** Sets the distance of this slot in Slate units. */ 23 | UFUNCTION(BlueprintCallable, Category = "Layout|Parallax Panel Slot") 24 | void SetDistance(float InDistance); 25 | 26 | /** Gets the distance of this slot in Slate units. */ 27 | UFUNCTION(BlueprintPure, Category = "Layout|Parallax Panel Slot") 28 | float GetDistance() const; 29 | 30 | protected: 31 | /** Sets the offset of this slot adjusted for distance. */ 32 | void SetOffset(FVector2D InOffset); 33 | 34 | virtual void BuildSlot(TSharedRef InOverlay) override; 35 | virtual void SynchronizeProperties() override; 36 | 37 | /** 38 | * Controls how much this slot translates compared to other slots. A higher value indicates that this slot should 39 | * appear further away and will have smaller translations when the offset of a UParallaxPanel changes. 40 | */ 41 | UPROPERTY(EditAnywhere, Getter, Setter, BlueprintGetter = GetDistance, BlueprintSetter = SetDistance, Category = "Layout|Parallax Panel Slot", meta = (DesignerRebuild, UIMin = 0, ClampMin = 0, Delta = 1, LinearDeltaSensitivity = 1)) 42 | float Distance; 43 | }; 44 | --------------------------------------------------------------------------------