├── .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 | 
2 |
3 | # Parallax Panel
4 | A 2D panel widget that simulates a 3D depth effect in user interfaces.
5 |
6 | 
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 | 
17 |
18 |
19 | Add child widgets to the panel. Each child widget is a layer.
20 |
21 | 
22 |
23 |
24 | Set the **Distance** of each layer under *Slot (Parallax Panel Slot)* in the Details panel.
25 |
26 | 
27 |
28 |
29 | Use the **Offset** property in the Parallax Panel to control the perspective.
30 |
31 | 
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 |
--------------------------------------------------------------------------------