├── .gitattributes ├── .gitignore ├── BUITween.uplugin ├── LICENSE ├── README.md ├── Resources └── Icon128.png └── Source └── BUITween ├── BUITween.Build.cs ├── Private ├── BUITween.cpp ├── BUITweenInstance.cpp └── BUITweenModule.cpp └── Public ├── BUIEasing.h ├── BUITween.h ├── BUITweenInstance.h └── BUITweenModule.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Visual Studio 2015 user specific files 3 | .vs/ 4 | 5 | # Compiled Object files 6 | *.slo 7 | *.lo 8 | *.o 9 | *.obj 10 | 11 | # Precompiled Headers 12 | *.gch 13 | *.pch 14 | 15 | # Compiled Dynamic libraries 16 | *.so 17 | *.dylib 18 | *.dll 19 | 20 | # Fortran module files 21 | *.mod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | *.ipa 34 | 35 | # These project files can be generated by the engine 36 | *.xcodeproj 37 | *.xcworkspace 38 | *.sln 39 | *.suo 40 | *.opensdf 41 | *.sdf 42 | *.VC.db 43 | *.VC.opendb 44 | 45 | Binaries/* 46 | Build/* 47 | Intermediate/* 48 | 49 | -------------------------------------------------------------------------------- /BUITween.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "BUITween", 6 | "Description": "Tweening library for UMG", 7 | "Category": "Other", 8 | "CreatedBy": "benui", 9 | "CreatedByURL": "https://benui.ca/", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": false, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "BUITween", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UI Tweening Libary for UE4/UMG 2 | 3 | Create simple tweens for UMG widgets from C++. 4 | 5 | ```cpp 6 | UBUITween::Create( SomeWidget, 0.2f ) 7 | .FromTranslation( FVector2D( -100, 0 ) ) 8 | .FromOpacity( 0.2f ) 9 | .ToTranslation( FVector2D( 20, 10 ) ) 10 | .ToOpacity( 1.0f ) 11 | .Begin(); 12 | ``` 13 | 14 | 15 | ## Usage 16 | 17 | The plugin module registers itself to tick automatically even during game-world 18 | pause. 19 | 20 | ```cpp 21 | // Make UWidget MyWidget fade in from the left 22 | const float TweenDuration = 0.7f; 23 | const float StartDelay = 0.3f; 24 | UBUITween::Create( MyWidget, TweenDuration, StartDelay ) 25 | .FromTranslation( FVector2D( -100, 0 ) ) 26 | .FromOpacity( 0.2f ) 27 | .ToReset() 28 | .Easing( EBUIEasingType::OutCubic ) 29 | .Begin(); 30 | ``` 31 | 32 | Parameters available: 33 | 34 | | Property | From | To | 35 | | --- | --- | --- | 36 | | **Translation** | `FromTranslation( FVector2D)` | `ToTranslation( FVector2D )` | 37 | | **Scale** | `FromScale( FVector2D )` | `ToScale( FVector2D )` | 38 | | **Rotation** | `FromRotation( float )` | `ToRotation( float )` | 39 | | **Opacity** | `FromOpacity( float )` | `ToOpacity( float )` | 40 | | **Color** | `FromColor( FLinearColor )` | `ToColor( FLinearColor )` | 41 | | **Visibility** | `FromVisibility( ESlateVisibility )` | `ToVisibility( ESlateVisibility )` | 42 | | **Canvas Position** | `FromCanvasPosition( FVector2D )` | `ToCanvasPosition( FVector2D )` | 43 | | **SizeBox Max Height** | `FromMaxDesiredHeight( float )` | `ToMaxDesiredHeight( float )` | 44 | | **Slot Padding** | `FromPadding( FMargin )` | `ToPadding( FMargin )` | 45 | 46 | Other helper functions: 47 | 48 | * `ToReset()` 49 | 50 | ## Callbacks 51 | 52 | ```cpp 53 | UBUITween::Create( MyWidget, 0.5f ) 54 | .FromRotation( -90 ) 55 | .ToRotation( 45 ) 56 | .OnComplete( FBUITweenSignature::CreateLambda([]( UWidget* Owner ) { 57 | // Do some logic on complete 58 | } ) ) 59 | .Begin(); 60 | ``` 61 | 62 | 63 | For the full API, check the source code. 64 | 65 | 66 | ## Caveats 67 | 68 | * I haven't performance-tested it beyond having 5-6 tweens running simultaneously. 69 | * No Blueprint support. 70 | 71 | ## License 72 | 73 | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) 74 | 75 | ## Contact 76 | 77 | If you find it useful, drop me a line [@_benui](https://twitter.com/_benui) on Twitter 78 | 79 | [benui.ca](https://benui.ca) 80 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benui-dev/UE-BUITween/1f14a481908ea3a3fa8fde436a639d4e0559a48b/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/BUITween/BUITween.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class BUITween : ModuleRules 6 | { 7 | public BUITween(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicDependencyModuleNames.AddRange( 12 | new string[] 13 | { 14 | "Core", 15 | } 16 | ); 17 | 18 | 19 | PrivateDependencyModuleNames.AddRange( 20 | new string[] 21 | { 22 | "CoreUObject", 23 | "Engine", 24 | "Slate", 25 | "SlateCore", 26 | "UMG" 27 | } 28 | ); 29 | } 30 | } -------------------------------------------------------------------------------- /Source/BUITween/Private/BUITween.cpp: -------------------------------------------------------------------------------- 1 | #include "BUITween.h" 2 | 3 | TArray< FBUITweenInstance > UBUITween::ActiveInstances = TArray< FBUITweenInstance >(); 4 | TArray< FBUITweenInstance > UBUITween::InstancesToAdd = TArray< FBUITweenInstance >(); 5 | bool UBUITween::bIsInitialized = false; 6 | 7 | void UBUITween::Startup() 8 | { 9 | bIsInitialized = true; 10 | ActiveInstances.Empty(); 11 | InstancesToAdd.Empty(); 12 | } 13 | 14 | 15 | void UBUITween::Shutdown() 16 | { 17 | ActiveInstances.Empty(); 18 | InstancesToAdd.Empty(); 19 | bIsInitialized = false; 20 | } 21 | 22 | 23 | FBUITweenInstance& UBUITween::Create( UWidget* pInWidget, float InDuration, float InDelay, bool bIsAdditive ) 24 | { 25 | // By default let's kill any existing tweens 26 | if ( !bIsAdditive ) 27 | { 28 | Clear( pInWidget ); 29 | } 30 | 31 | FBUITweenInstance Instance( pInWidget, InDuration, InDelay ); 32 | 33 | InstancesToAdd.Add( Instance ); 34 | 35 | return InstancesToAdd.Last(); 36 | } 37 | 38 | 39 | int32 UBUITween::Clear( UWidget* pInWidget ) 40 | { 41 | int32 NumRemoved = 0; 42 | 43 | auto DoesTweenMatchWidgetFn = [pInWidget](const FBUITweenInstance& CurTweenInstance) -> bool { 44 | return ( CurTweenInstance.GetWidget().IsValid() && CurTweenInstance.GetWidget() == pInWidget ); 45 | }; 46 | 47 | NumRemoved += ActiveInstances.RemoveAll(DoesTweenMatchWidgetFn); 48 | NumRemoved += InstancesToAdd.RemoveAll(DoesTweenMatchWidgetFn); 49 | 50 | return NumRemoved; 51 | } 52 | 53 | 54 | void UBUITween::Update( float DeltaTime ) 55 | { 56 | // Reverse it so we can remove 57 | for ( int32 i = ActiveInstances.Num()-1; i >= 0; --i ) 58 | { 59 | FBUITweenInstance& Inst = ActiveInstances[ i ]; 60 | Inst.Update( DeltaTime ); 61 | if ( Inst.IsComplete() ) 62 | { 63 | FBUITweenInstance CompleteInst = Inst; 64 | ActiveInstances.RemoveAt( i ); 65 | 66 | // We do this here outside of the instance update and after removing from active instances because we 67 | // don't know if the callback in the cleanup is going to trigger adding more events 68 | CompleteInst.DoCompleteCleanup(); 69 | } 70 | } 71 | 72 | for ( int32 i = 0; i < InstancesToAdd.Num(); ++i ) 73 | { 74 | ActiveInstances.Add( InstancesToAdd[ i ] ); 75 | } 76 | InstancesToAdd.Empty(); 77 | } 78 | 79 | 80 | bool UBUITween::GetIsTweening( UWidget* pInWidget ) 81 | { 82 | for ( int32 i = 0; i < ActiveInstances.Num(); ++i ) 83 | { 84 | if ( ActiveInstances[ i ].GetWidget() == pInWidget ) 85 | { 86 | return true; 87 | } 88 | } 89 | return false; 90 | } 91 | 92 | 93 | void UBUITween::CompleteAll() 94 | { 95 | // Very hacky way to make sure all Tweens complete immediately. 96 | // First Update clears ActiveTweens, second clears "InstancesToAdd". 97 | Update( 100000 ); 98 | Update( 100000 ); 99 | } 100 | -------------------------------------------------------------------------------- /Source/BUITween/Private/BUITweenInstance.cpp: -------------------------------------------------------------------------------- 1 | #include "BUITweenInstance.h" 2 | 3 | #include "Components/Widget.h" 4 | #include "Components/Image.h" 5 | #include "Components/Border.h" 6 | #include "Components/CanvasPanelSlot.h" 7 | #include "Components/OverlaySlot.h" 8 | #include "Components/VerticalBoxSlot.h" 9 | #include "Components/HorizontalBoxSlot.h" 10 | #include "Components/SizeBox.h" 11 | #include "Blueprint/UserWidget.h" 12 | 13 | DEFINE_LOG_CATEGORY(LogBUITween); 14 | 15 | void FBUITweenInstance::Begin() 16 | { 17 | bShouldUpdate = true; 18 | bHasPlayedStartEvent = false; 19 | bHasPlayedCompleteEvent = false; 20 | 21 | if ( !pWidget.IsValid() ) 22 | { 23 | UE_LOG( LogBUITween, Warning, TEXT( "Trying to start invalid widget" ) ); 24 | return; 25 | } 26 | 27 | // Set all the props to the existng state 28 | TranslationProp.OnBegin( pWidget->RenderTransform.Translation ); 29 | ScaleProp.OnBegin( pWidget->RenderTransform.Scale ); 30 | RotationProp.OnBegin( pWidget->RenderTransform.Angle ); 31 | OpacityProp.OnBegin( pWidget->GetRenderOpacity() ); 32 | 33 | { 34 | UUserWidget* UW = Cast( pWidget ); 35 | if ( UW ) 36 | { 37 | ColorProp.OnBegin( UW->ColorAndOpacity ); 38 | } 39 | UImage* UI = Cast( pWidget ); 40 | if ( UI ) 41 | { 42 | ColorProp.OnBegin( UI->ColorAndOpacity ); 43 | } 44 | UBorder* Border = Cast( pWidget ); 45 | if ( Border ) 46 | { 47 | ColorProp.OnBegin( Border->ContentColorAndOpacity ); 48 | } 49 | } 50 | 51 | VisibilityProp.OnBegin( pWidget->GetVisibility() ); 52 | 53 | UCanvasPanelSlot* CanvasSlot = Cast( pWidget->Slot ); 54 | if ( CanvasSlot ) 55 | { 56 | CanvasPositionProp.OnBegin( CanvasSlot->GetPosition() ); 57 | } 58 | UOverlaySlot* OverlaySlot = Cast( pWidget->Slot ); 59 | UHorizontalBoxSlot* HorizontalBoxSlot = Cast( pWidget->Slot ); 60 | UVerticalBoxSlot* VerticalBoxSlot = Cast( pWidget->Slot ); 61 | if ( OverlaySlot ) 62 | { 63 | PaddingProp.OnBegin( FVector4( 64 | OverlaySlot->Padding.Left, 65 | OverlaySlot->Padding.Top, 66 | OverlaySlot->Padding.Bottom, 67 | OverlaySlot->Padding.Right ) ); 68 | } 69 | else if ( HorizontalBoxSlot ) 70 | { 71 | PaddingProp.OnBegin( FVector4( 72 | HorizontalBoxSlot->Padding.Left, 73 | HorizontalBoxSlot->Padding.Top, 74 | HorizontalBoxSlot->Padding.Bottom, 75 | HorizontalBoxSlot->Padding.Right ) ); 76 | } 77 | else if ( VerticalBoxSlot ) 78 | { 79 | PaddingProp.OnBegin( FVector4( 80 | VerticalBoxSlot->Padding.Left, 81 | VerticalBoxSlot->Padding.Top, 82 | VerticalBoxSlot->Padding.Bottom, 83 | VerticalBoxSlot->Padding.Right ) ); 84 | } 85 | 86 | USizeBox* SizeBox = Cast( pWidget ); 87 | if ( SizeBox ) 88 | { 89 | MaxDesiredHeightProp.OnBegin( SizeBox->MaxDesiredHeight ); 90 | } 91 | 92 | // Apply the starting conditions, even if we delay 93 | Apply( 0 ); 94 | } 95 | 96 | void FBUITweenInstance::Update( float DeltaTime ) 97 | { 98 | if ( !bShouldUpdate && !bIsComplete ) 99 | { 100 | return; 101 | } 102 | if ( !pWidget.IsValid() ) 103 | { 104 | bIsComplete = true; 105 | return; 106 | } 107 | 108 | if ( Delay > 0 ) 109 | { 110 | // TODO could correctly subtract from deltatime and use rmaining on alpha but meh 111 | Delay -= DeltaTime; 112 | return; 113 | } 114 | 115 | if ( !bHasPlayedStartEvent ) 116 | { 117 | OnStartedDelegate.ExecuteIfBound( pWidget.Get() ); 118 | bHasPlayedStartEvent = true; 119 | } 120 | 121 | // Tween each thingy 122 | Alpha += DeltaTime; 123 | if ( Alpha >= Duration ) 124 | { 125 | Alpha = Duration; 126 | bIsComplete = true; 127 | } 128 | 129 | const float EasedAlpha = EasingParam.IsSet() 130 | ? FBUIEasing::Ease( EasingType, Alpha, Duration, EasingParam.GetValue() ) 131 | : FBUIEasing::Ease( EasingType, Alpha, Duration ); 132 | 133 | Apply( EasedAlpha ); 134 | } 135 | 136 | void FBUITweenInstance::Apply( float EasedAlpha ) 137 | { 138 | UWidget* Target = pWidget.Get(); 139 | 140 | if ( ColorProp.IsSet() ) 141 | { 142 | ColorProp.Update( EasedAlpha ); 143 | UUserWidget* UW = Cast( Target ); 144 | if ( UW ) 145 | { 146 | UW->SetColorAndOpacity( ColorProp.CurrentValue ); 147 | } 148 | UImage* UI = Cast( Target ); 149 | if ( UI ) 150 | { 151 | UI->SetColorAndOpacity( ColorProp.CurrentValue ); 152 | } 153 | UBorder* Border = Cast( Target ); 154 | if ( Border ) 155 | { 156 | Border->SetContentColorAndOpacity( ColorProp.CurrentValue ); 157 | } 158 | } 159 | 160 | if ( OpacityProp.IsSet() ) 161 | { 162 | OpacityProp.Update( EasedAlpha ); 163 | Target->SetRenderOpacity( OpacityProp.CurrentValue ); 164 | } 165 | 166 | // Only apply visibility changes at 0 or 1 167 | if ( VisibilityProp.IsSet() ) 168 | { 169 | if ( VisibilityProp.Update( EasedAlpha ) ) 170 | { 171 | Target->SetVisibility( VisibilityProp.CurrentValue ); 172 | } 173 | } 174 | 175 | bool bChangedRenderTransform = false; 176 | FWidgetTransform CurrentTransform = Target->RenderTransform; 177 | 178 | if ( TranslationProp.IsSet() ) 179 | { 180 | TranslationProp.Update( EasedAlpha ); 181 | CurrentTransform.Translation = TranslationProp.CurrentValue; 182 | bChangedRenderTransform = true; 183 | } 184 | if ( ScaleProp.IsSet() ) 185 | { 186 | ScaleProp.Update( EasedAlpha ); 187 | CurrentTransform.Scale = ScaleProp.CurrentValue; 188 | bChangedRenderTransform = true; 189 | } 190 | if ( RotationProp.IsSet() ) 191 | { 192 | if ( RotationProp.Update( EasedAlpha ) ) 193 | { 194 | CurrentTransform.Angle = RotationProp.CurrentValue; 195 | bChangedRenderTransform = true; 196 | } 197 | } 198 | if ( CanvasPositionProp.IsSet() ) 199 | { 200 | if ( CanvasPositionProp.Update( EasedAlpha ) ) 201 | { 202 | UCanvasPanelSlot* CanvasSlot = Cast( pWidget->Slot ); 203 | if ( CanvasSlot ) 204 | CanvasSlot->SetPosition( CanvasPositionProp.CurrentValue ); 205 | } 206 | } 207 | if ( PaddingProp.IsSet() ) 208 | { 209 | if ( PaddingProp.Update( EasedAlpha ) ) 210 | { 211 | UOverlaySlot* OverlaySlot = Cast( pWidget->Slot ); 212 | UHorizontalBoxSlot* HorizontalBoxSlot = Cast( pWidget->Slot ); 213 | UVerticalBoxSlot* VerticalBoxSlot = Cast( pWidget->Slot ); 214 | if ( OverlaySlot ) 215 | OverlaySlot->SetPadding( PaddingProp.CurrentValue ); 216 | else if ( HorizontalBoxSlot ) 217 | HorizontalBoxSlot->SetPadding( PaddingProp.CurrentValue ); 218 | else if ( VerticalBoxSlot ) 219 | VerticalBoxSlot->SetPadding( PaddingProp.CurrentValue ); 220 | } 221 | } 222 | if ( MaxDesiredHeightProp.IsSet() ) 223 | { 224 | if ( MaxDesiredHeightProp.Update( EasedAlpha ) ) 225 | { 226 | USizeBox* SizeBox = Cast( pWidget ); 227 | if ( SizeBox ) 228 | { 229 | SizeBox->SetMaxDesiredHeight( MaxDesiredHeightProp.CurrentValue ); 230 | } 231 | } 232 | } 233 | 234 | if ( bChangedRenderTransform ) 235 | { 236 | Target->SetRenderTransform( CurrentTransform ); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /Source/BUITween/Private/BUITweenModule.cpp: -------------------------------------------------------------------------------- 1 | #include "BUITweenModule.h" 2 | #include "BUITween.h" 3 | 4 | #define LOCTEXT_NAMESPACE "FBUITweenModule" 5 | 6 | void FBUITweenModule::StartupModule() 7 | { 8 | this->LastFrameNumberWeTicked = INDEX_NONE; 9 | 10 | UBUITween::Startup(); 11 | } 12 | 13 | void FBUITweenModule::ShutdownModule() 14 | { 15 | UBUITween::Shutdown(); 16 | } 17 | 18 | void FBUITweenModule::Tick( float DeltaTime ) 19 | { 20 | if ( LastFrameNumberWeTicked != GFrameCounter ) 21 | { 22 | UBUITween::Update( DeltaTime ); 23 | 24 | LastFrameNumberWeTicked = GFrameCounter; 25 | } 26 | } 27 | 28 | #undef LOCTEXT_NAMESPACE 29 | 30 | IMPLEMENT_MODULE(FBUITweenModule, BUITween) 31 | 32 | -------------------------------------------------------------------------------- /Source/BUITween/Public/BUIEasing.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "CoreUObject.h" 5 | 6 | UENUM() 7 | enum class EBUIEasingType 8 | { 9 | Linear, 10 | Smoothstep, 11 | InSine, 12 | OutSine, 13 | InOutSine, 14 | InQuad, 15 | OutQuad, 16 | InOutQuad, 17 | InCubic, 18 | OutCubic, 19 | InOutCubic, 20 | InQuart, 21 | OutQuart, 22 | InOutQuart, 23 | InQuint, 24 | OutQuint, 25 | InOutQuint, 26 | InExpo, 27 | OutExpo, 28 | InOutExpo, 29 | InCirc, 30 | OutCirc, 31 | InOutCirc, 32 | InElastic, 33 | OutElastic, 34 | InOutElastic, 35 | InBack, 36 | OutBack, 37 | InOutBack, 38 | }; 39 | 40 | struct FBUIEasing 41 | { 42 | public: 43 | #define TWO_PI (6.28318530717f) 44 | 45 | static float Ease( EBUIEasingType Type, float time, float duration = 1.0f, float overshootOrAmplitude = 0.1f, float period = 1.0f ) 46 | { 47 | switch ( Type ) 48 | { 49 | case EBUIEasingType::Linear: return Linear( time, duration ); break; 50 | case EBUIEasingType::Smoothstep: return Smoothstep( time, 0, duration); break; 51 | case EBUIEasingType::InSine: return InSine( time, duration ); break; 52 | case EBUIEasingType::OutSine: return OutSine( time, duration ); break; 53 | case EBUIEasingType::InOutSine: return InOutSine( time, duration ); break; 54 | case EBUIEasingType::InQuad: return InQuad( time, duration ); break; 55 | case EBUIEasingType::OutQuad: return OutQuad( time, duration ); break; 56 | case EBUIEasingType::InOutQuad: return InOutQuad( time, duration ); break; 57 | case EBUIEasingType::InCubic: return InCubic( time, duration ); break; 58 | case EBUIEasingType::OutCubic: return OutCubic( time, duration ); break; 59 | case EBUIEasingType::InOutCubic: return InOutCubic( time, duration ); break; 60 | case EBUIEasingType::InQuart: return InQuart( time, duration ); break; 61 | case EBUIEasingType::OutQuart: return OutQuart( time, duration ); break; 62 | case EBUIEasingType::InOutQuart: return InOutQuart( time, duration ); break; 63 | case EBUIEasingType::InQuint: return InQuint( time, duration ); break; 64 | case EBUIEasingType::OutQuint: return OutQuint( time, duration ); break; 65 | case EBUIEasingType::InOutQuint: return InOutQuint( time, duration ); break; 66 | case EBUIEasingType::InExpo: return InExpo( time, duration ); break; 67 | case EBUIEasingType::OutExpo: return OutExpo( time, duration ); break; 68 | case EBUIEasingType::InOutExpo: return InOutExpo( time, duration ); break; 69 | case EBUIEasingType::InCirc: return InCirc( time, duration ); break; 70 | case EBUIEasingType::OutCirc: return OutCirc( time, duration ); break; 71 | case EBUIEasingType::InOutCirc: return InOutCirc( time, duration ); break; 72 | case EBUIEasingType::InElastic: return InElastic( time, duration, overshootOrAmplitude, period ); break; 73 | case EBUIEasingType::OutElastic: return OutElastic( time, duration, overshootOrAmplitude, period ); break; 74 | case EBUIEasingType::InOutElastic: return InOutElastic( time, duration, overshootOrAmplitude, period ); break; 75 | case EBUIEasingType::InBack: return InBack( time, duration, overshootOrAmplitude, period ); break; 76 | case EBUIEasingType::OutBack: return OutBack( time, duration, overshootOrAmplitude, period ); break; 77 | case EBUIEasingType::InOutBack: return InOutBack( time, duration, overshootOrAmplitude, period ); break; 78 | } 79 | return 0; 80 | } 81 | 82 | static float Linear( float time, float duration = 1.0f ) 83 | { 84 | return time / duration; 85 | } 86 | 87 | static float Smoothstep( float x, float edge0 = 0.0f, float edge1 = 1.0f ) 88 | { 89 | x = FMath::Clamp( ( x - edge0 ) / ( edge1 - edge0 ), 0.0, 1.0 ); 90 | return x*x*( 3 - 2 * x ); 91 | } 92 | 93 | static float InSine( float time, float duration = 1.0f ) 94 | { 95 | return -( float ) FMath::Cos( time / duration * HALF_PI ) + 1; 96 | } 97 | 98 | static float OutSine( float time, float duration = 1.0f ) 99 | { 100 | return ( float ) FMath::Sin( time / duration * HALF_PI ); 101 | } 102 | 103 | static float InOutSine( float time, float duration = 1.0f ) 104 | { 105 | return -0.5f * ( ( float ) FMath::Cos( PI * time / duration ) - 1 ); 106 | } 107 | 108 | static float InQuad( float time, float duration = 1.0 ) 109 | { 110 | time /= duration; 111 | return time * time; 112 | } 113 | 114 | static float OutQuad( float time, float duration = 1.0f ) 115 | { 116 | time /= duration; 117 | return -time * ( time - 2 ); 118 | } 119 | 120 | static float InOutQuad( float time, float duration = 1.0f ) 121 | { 122 | time /= duration * 0.5f; 123 | if ( time < 1 ) 124 | return 0.5f * time * time; 125 | --time; 126 | return -0.5f * ( time * ( time - 2 ) - 1 ); 127 | } 128 | 129 | static float InCubic( float time, float duration = 1.0f ) 130 | { 131 | time /= duration; 132 | return time * time * time; 133 | } 134 | 135 | static float OutCubic( float time, float duration = 1.0f ) 136 | { 137 | time = time / duration - 1; 138 | return ( time * time * time + 1 ); 139 | } 140 | 141 | static float InOutCubic( float time, float duration = 1.0f ) 142 | { 143 | time /= duration * 0.5f; 144 | if ( time < 1 ) 145 | return 0.5f * time * time * time; 146 | time -= 2; 147 | return 0.5f * ( time * time * time + 2 ); 148 | } 149 | 150 | static float InQuart( float time, float duration = 1.0f ) 151 | { 152 | time /= duration; 153 | return time * time * time * time; 154 | } 155 | 156 | static float OutQuart( float time, float duration = 1.0f ) 157 | { 158 | time = time / duration - 1; 159 | return -( time * time * time * time - 1 ); 160 | } 161 | 162 | static float InOutQuart( float time, float duration = 1.0f ) 163 | { 164 | time /= duration * 0.5f; 165 | if ( time < 1 ) 166 | return 0.5f * time * time * time * time; 167 | time -= 2; 168 | return -0.5f * ( time * time * time * time - 2 ); 169 | } 170 | 171 | static float InQuint( float time, float duration = 1.0f ) 172 | { 173 | time /= duration; 174 | return time * time * time * time * time; 175 | } 176 | 177 | static float OutQuint( float time, float duration = 1.0f ) 178 | { 179 | time = time / duration - 1; 180 | return ( time * time * time * time * time + 1 ); 181 | } 182 | 183 | static float InOutQuint( float time, float duration = 1.0f ) 184 | { 185 | time /= duration * 0.5f; 186 | if ( time < 1 ) 187 | return 0.5f * time * time * time * time * time; 188 | time -= 2; 189 | return 0.5f * ( time * time * time * time * time + 2 ); 190 | } 191 | 192 | static float InExpo( float time, float duration = 1.0f ) 193 | { 194 | return ( time == 0 ) ? 0 : ( float )FMath::Pow( 2, 10 * ( time / duration - 1 ) ); 195 | } 196 | 197 | static float OutExpo( float time, float duration = 1.0f ) 198 | { 199 | if ( time == duration ) 200 | return 1; 201 | return ( -( float ) FMath::Pow( 2, -10 * time / duration ) + 1 ); 202 | } 203 | 204 | static float InOutExpo( float time, float duration = 1.0f ) 205 | { 206 | if ( time == 0 ) 207 | return 0; 208 | if ( time == duration ) 209 | return 1; 210 | time /= duration; 211 | if ( ( time * 0.5f ) < 1 ) 212 | return 0.5f * ( float )FMath::Pow( 2, 10 * ( time - 1 ) ); 213 | --time; 214 | return 0.5f * ( -( float ) FMath::Pow( 2, -10 * time ) + 2 ); 215 | } 216 | 217 | static float InCirc( float time, float duration = 1.0f ) 218 | { 219 | time /= duration; 220 | return -( ( float ) FMath::Sqrt( 1 - time * time ) - 1 ); 221 | } 222 | 223 | static float OutCirc( float time, float duration = 1.0f ) 224 | { 225 | time = time / duration - 1; 226 | return ( float ) FMath::Sqrt( 1 - time * time ); 227 | } 228 | 229 | static float InOutCirc( float time, float duration = 1.0f ) 230 | { 231 | time /= duration * 0.5f; 232 | if ( time < 1 ) 233 | return -0.5f * ( ( float ) FMath::Sqrt( 1 - time * time ) - 1 ); 234 | time -= 2; 235 | return 0.5f * ( ( float ) FMath::Sqrt( 1 - time * time ) + 1 ); 236 | } 237 | 238 | static float InElastic( float time, float duration = 1.0f, float overshootOrAmplitude = 0.1f, float period = 1.0f ) 239 | { 240 | float s0; 241 | if ( time == 0 ) return 0; 242 | if ( ( time /= duration ) == 1 ) return 1; 243 | if ( period == 0 ) period = duration * 0.3f; 244 | if ( overshootOrAmplitude < 1 ) { 245 | overshootOrAmplitude = 1; 246 | s0 = period / 4; 247 | } 248 | else s0 = period / TWO_PI * ( float ) FMath::Asin( 1 / overshootOrAmplitude ); 249 | time -= 1; 250 | return -( overshootOrAmplitude * ( float ) FMath::Pow( 2, 10 * time ) * ( float ) FMath::Sin( ( time * duration - s0 ) * TWO_PI / period ) ); 251 | } 252 | 253 | static float OutElastic( float time, float duration = 1.0f, float overshootOrAmplitude = 0.1f, float period = 1.0f ) 254 | { 255 | float s1; 256 | if ( time == 0 ) return 0; 257 | time /= duration; 258 | if ( time == 1 ) return 1; 259 | if ( period == 0 ) period = duration * 0.3f; 260 | if ( overshootOrAmplitude < 1 ) { 261 | overshootOrAmplitude = 1; 262 | s1 = period / 4; 263 | } 264 | else s1 = period / TWO_PI * ( float ) FMath::Asin( 1 / overshootOrAmplitude ); 265 | return ( overshootOrAmplitude * ( float ) FMath::Pow( 2, -10 * time ) * ( float ) FMath::Sin( ( time * duration - s1 ) * TWO_PI / period ) + 1 ); 266 | } 267 | 268 | static float InOutElastic( float time, float duration = 1.0f, float overshootOrAmplitude = 0.1f, float period = 1.0f ) 269 | { 270 | float s; 271 | if ( time == 0 ) return 0; 272 | time /= duration * 0.5f; 273 | if ( time == 2 ) return 1; 274 | if ( period == 0 ) period = duration * ( 0.3f * 1.5f ); 275 | if ( overshootOrAmplitude < 1 ) { 276 | overshootOrAmplitude = 1; 277 | s = period / 4; 278 | } 279 | else s = period / TWO_PI * ( float ) FMath::Asin( 1 / overshootOrAmplitude ); 280 | time -= 1; 281 | if ( time < 1 ) 282 | return -0.5f * ( overshootOrAmplitude * ( float ) FMath::Pow( 2, 10 * time ) * ( float ) FMath::Sin( ( time * duration - s ) * TWO_PI / period ) ); 283 | return overshootOrAmplitude * ( float ) FMath::Pow( 2, -10 * time ) * ( float ) FMath::Sin( ( time * duration - s ) * TWO_PI / period ) * 0.5f + 1; 284 | } 285 | 286 | static float InBack( float time, float duration = 1.0f, float overshootOrAmplitude = 0.1f, float period = 1.0f ) 287 | { 288 | time /= duration; 289 | return time * time * ( ( overshootOrAmplitude + 1 ) * time - overshootOrAmplitude ); 290 | } 291 | 292 | static float OutBack( float time, float duration = 1.0f, float overshootOrAmplitude = 0.1f, float period = 1.0f ) 293 | { 294 | time = time / duration - 1; 295 | return ( time * time * ( ( overshootOrAmplitude + 1 ) * time + overshootOrAmplitude ) + 1 ); 296 | } 297 | 298 | static float InOutBack( float time, float duration = 1.0f, float overshootOrAmplitude = 0.1f, float period = 1.0f ) 299 | { 300 | time /= duration * 0.5f; 301 | overshootOrAmplitude *= 1.525f; 302 | if ( time < 1 ) 303 | return 0.5f * ( time * time * ( ( overshootOrAmplitude + 1 ) * time - overshootOrAmplitude ) ); 304 | time -= 2; 305 | return 0.5f * ( time * time * ( ( overshootOrAmplitude + 1 ) * time + overshootOrAmplitude ) + 2 ); 306 | } 307 | 308 | }; 309 | 310 | #undef TWO_PI 311 | -------------------------------------------------------------------------------- /Source/BUITween/Public/BUITween.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BUIEasing.h" 4 | #include "Components/Widget.h" 5 | #include "BUITweenInstance.h" 6 | #include "BUITween.generated.h" 7 | 8 | UCLASS() 9 | class BUITWEEN_API UBUITween : public UObject 10 | { 11 | GENERATED_BODY() 12 | 13 | public: 14 | static void Startup(); 15 | static void Shutdown(); 16 | 17 | // Create a new tween on the target widget, does not start automatically 18 | static FBUITweenInstance& Create( UWidget* pInWidget, float InDuration = 1.0f, float InDelay = 0.0f, bool bIsAdditive = false ); 19 | 20 | // Cancel all tweens on the target widget, returns the number of tween instances removed 21 | static int32 Clear( UWidget* pInWidget ); 22 | 23 | static void Update( float InDeltaTime ); 24 | 25 | static bool GetIsTweening( UWidget* pInWidget ); 26 | 27 | static void CompleteAll(); 28 | 29 | protected: 30 | static bool bIsInitialized; 31 | 32 | static TArray< FBUITweenInstance > ActiveInstances; 33 | 34 | // We delay adding until the end of an update so we don't add to ActiveInstances within our update loop 35 | static TArray< FBUITweenInstance > InstancesToAdd; 36 | }; 37 | -------------------------------------------------------------------------------- /Source/BUITween/Public/BUITweenInstance.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BUIEasing.h" 4 | #include "Components/Widget.h" 5 | #include "BUITweenInstance.generated.h" 6 | 7 | DECLARE_DELEGATE_OneParam( FBUITweenSignature, UWidget* /*Owner*/ ); 8 | 9 | BUITWEEN_API DECLARE_LOG_CATEGORY_EXTERN(LogBUITween, Log, All); 10 | 11 | template 12 | class TBUITweenProp 13 | { 14 | public: 15 | bool bHasStart = false; 16 | bool bHasTarget = false; 17 | inline bool IsSet() const { return bHasStart || bHasTarget; } 18 | T StartValue; 19 | T TargetValue; 20 | T CurrentValue; 21 | bool bIsFirstTime = true; 22 | void SetStart( T InStart ) 23 | { 24 | bHasStart = true; 25 | StartValue = InStart; 26 | CurrentValue = StartValue; 27 | } 28 | void SetTarget( T InTarget ) 29 | { 30 | bHasTarget = true; 31 | TargetValue = InTarget; 32 | } 33 | void OnBegin( T InCurrentValue ) 34 | { 35 | if ( !bHasStart ) 36 | { 37 | StartValue = InCurrentValue; 38 | CurrentValue = InCurrentValue; 39 | } 40 | } 41 | bool Update( float Alpha ) 42 | { 43 | const T OldValue = CurrentValue; 44 | CurrentValue = FMath::Lerp( StartValue, TargetValue, Alpha ); 45 | const bool bShouldUpdate = bIsFirstTime || CurrentValue != OldValue; 46 | bIsFirstTime = false; 47 | return bShouldUpdate; 48 | } 49 | }; 50 | 51 | template 52 | class TBUITweenInstantProp 53 | { 54 | public: 55 | bool bHasStart = false; 56 | bool bHasTarget = false; 57 | inline bool IsSet() const { return bHasStart || bHasTarget; } 58 | T StartValue; 59 | T TargetValue; 60 | T CurrentValue; 61 | bool bIsFirstTime = true; 62 | void SetStart( T InStart ) 63 | { 64 | bHasStart = true; 65 | StartValue = InStart; 66 | CurrentValue = StartValue; 67 | } 68 | void SetTarget( T InTarget ) 69 | { 70 | bHasTarget = true; 71 | TargetValue = InTarget; 72 | } 73 | void OnBegin( T InCurrentValue ) 74 | { 75 | if ( !bHasStart ) 76 | { 77 | StartValue = InCurrentValue; 78 | CurrentValue = InCurrentValue; 79 | } 80 | } 81 | bool Update( float Alpha ) 82 | { 83 | const T OldValue = CurrentValue; 84 | if ( Alpha >= 1 && bHasTarget ) 85 | { 86 | CurrentValue = TargetValue; 87 | } 88 | else 89 | { 90 | CurrentValue = StartValue; 91 | } 92 | const bool bShouldChange = bIsFirstTime || OldValue != CurrentValue; 93 | bIsFirstTime = false; 94 | return bShouldChange; 95 | } 96 | }; 97 | 98 | 99 | USTRUCT() 100 | struct BUITWEEN_API FBUITweenInstance 101 | { 102 | GENERATED_BODY() 103 | 104 | public: 105 | FBUITweenInstance() { } 106 | FBUITweenInstance( UWidget* pInWidget, float InDuration, float InDelay = 0 ) 107 | : pWidget( pInWidget ) 108 | , Duration( InDuration ) 109 | , Delay( InDelay ) 110 | { 111 | ensure( pInWidget != nullptr ); 112 | 113 | } 114 | void Begin(); 115 | void Update( float InDeltaTime ); 116 | void Apply( float EasedAlpha ); 117 | 118 | inline bool operator==( const FBUITweenInstance& other) const 119 | { 120 | return pWidget == other.pWidget; 121 | } 122 | bool IsComplete() const { return bIsComplete; } 123 | 124 | // EasingParam is used for easing functions that have a second parameter, like Elastic 125 | FBUITweenInstance& Easing( EBUIEasingType InType, TOptional InEasingParam = TOptional() ) 126 | { 127 | EasingType = InType; 128 | EasingParam = InEasingParam; 129 | return *this; 130 | } 131 | 132 | FBUITweenInstance& ToTranslation( const FVector2D& InTarget ) 133 | { 134 | TranslationProp.SetTarget( InTarget ); 135 | return *this; 136 | } 137 | FBUITweenInstance& ToTranslation( float X, float Y ) 138 | { 139 | TranslationProp.SetTarget( FVector2D( X, Y ) ); 140 | return *this; 141 | } 142 | FBUITweenInstance& FromTranslation( const FVector2D& InStart ) 143 | { 144 | TranslationProp.SetStart( InStart ); 145 | return *this; 146 | } 147 | FBUITweenInstance& FromTranslation( float X, float Y ) 148 | { 149 | TranslationProp.SetStart( FVector2D( X, Y ) ); 150 | return *this; 151 | } 152 | 153 | FBUITweenInstance& ToScale( const FVector2D& InTarget ) 154 | { 155 | ScaleProp.SetTarget( InTarget ); 156 | return *this; 157 | } 158 | FBUITweenInstance& FromScale( const FVector2D& InStart ) 159 | { 160 | ScaleProp.SetStart( InStart ); 161 | return *this; 162 | } 163 | 164 | FBUITweenInstance& ToOpacity( float InTarget ) 165 | { 166 | OpacityProp.SetTarget( InTarget ); 167 | return *this; 168 | } 169 | FBUITweenInstance& FromOpacity( float InStart ) 170 | { 171 | OpacityProp.SetStart( InStart ); 172 | return *this; 173 | } 174 | 175 | FBUITweenInstance& ToColor( const FLinearColor& InTarget ) 176 | { 177 | ColorProp.SetTarget( InTarget ); 178 | return *this; 179 | } 180 | FBUITweenInstance& FromColor( const FLinearColor& InStart ) 181 | { 182 | ColorProp.SetStart( InStart ); 183 | return *this; 184 | } 185 | 186 | FBUITweenInstance& ToRotation( float InTarget ) 187 | { 188 | RotationProp.SetTarget( InTarget ); 189 | return *this; 190 | } 191 | FBUITweenInstance& FromRotation( float InStart ) 192 | { 193 | RotationProp.SetStart( InStart ); 194 | return *this; 195 | } 196 | 197 | FBUITweenInstance& ToMaxDesiredHeight( float InTarget ) 198 | { 199 | MaxDesiredHeightProp.SetTarget( InTarget ); 200 | return *this; 201 | } 202 | FBUITweenInstance& FromMaxDesiredHeight( float InStart ) 203 | { 204 | MaxDesiredHeightProp.SetStart( InStart ); 205 | return *this; 206 | } 207 | 208 | FBUITweenInstance& ToCanvasPosition( FVector2D InTarget ) 209 | { 210 | CanvasPositionProp.SetTarget( InTarget ); 211 | return *this; 212 | } 213 | FBUITweenInstance& FromCanvasPosition( FVector2D InStart ) 214 | { 215 | CanvasPositionProp.SetStart( InStart ); 216 | return *this; 217 | } 218 | 219 | FBUITweenInstance& ToPadding( const FMargin& InTarget ) 220 | { 221 | PaddingProp.SetTarget( FVector4( InTarget.Left, InTarget.Top, InTarget.Right, InTarget.Bottom ) ); 222 | return *this; 223 | } 224 | FBUITweenInstance& FromPadding( const FMargin& InStart ) 225 | { 226 | PaddingProp.SetStart( FVector4( InStart.Left, InStart.Top, InStart.Right, InStart.Bottom ) ); 227 | return *this; 228 | } 229 | 230 | 231 | FBUITweenInstance& ToVisibility( ESlateVisibility InTarget ) 232 | { 233 | VisibilityProp.SetTarget( InTarget ); 234 | return *this; 235 | } 236 | FBUITweenInstance& FromVisibility( ESlateVisibility InStart ) 237 | { 238 | VisibilityProp.SetStart( InStart ); 239 | return *this; 240 | } 241 | 242 | FBUITweenInstance& OnStart( const FBUITweenSignature& InOnStart ) 243 | { 244 | OnStartedDelegate = InOnStart; 245 | return *this; 246 | } 247 | FBUITweenInstance& OnComplete( const FBUITweenSignature& InOnComplete ) 248 | { 249 | OnCompleteDelegate = InOnComplete; 250 | return *this; 251 | } 252 | 253 | FBUITweenInstance& ToReset() 254 | { 255 | ScaleProp.SetTarget( FVector2D::UnitVector ); 256 | OpacityProp.SetTarget( 1 ); 257 | TranslationProp.SetTarget( FVector2D::ZeroVector ); 258 | ColorProp.SetTarget( FLinearColor::White ); 259 | RotationProp.SetTarget( 0 ); 260 | return *this; 261 | } 262 | 263 | TWeakObjectPtr GetWidget() const { return pWidget; } 264 | 265 | void DoCompleteCleanup() 266 | { 267 | if ( !bHasPlayedCompleteEvent ) 268 | { 269 | OnCompleteDelegate.ExecuteIfBound( pWidget.Get() ); 270 | bHasPlayedCompleteEvent = true; 271 | } 272 | } 273 | 274 | protected: 275 | bool bShouldUpdate = false; 276 | bool bIsComplete = false; 277 | 278 | TWeakObjectPtr pWidget = nullptr; 279 | float Alpha = 0; 280 | float Duration = 1; 281 | float Delay = 0; 282 | 283 | EBUIEasingType EasingType = EBUIEasingType::InOutQuad; 284 | TOptional EasingParam; 285 | 286 | TBUITweenProp TranslationProp; 287 | TBUITweenProp ScaleProp; 288 | TBUITweenProp ColorProp; 289 | TBUITweenProp OpacityProp; 290 | TBUITweenProp RotationProp; 291 | TBUITweenProp CanvasPositionProp; 292 | TBUITweenProp PaddingProp; // FVector4 because FMath::Lerp does not support FMargin 293 | TBUITweenInstantProp VisibilityProp; 294 | TBUITweenProp MaxDesiredHeightProp; 295 | 296 | FBUITweenSignature OnStartedDelegate; 297 | FBUITweenSignature OnCompleteDelegate; 298 | 299 | bool bHasPlayedStartEvent = false; 300 | bool bHasPlayedCompleteEvent = false; 301 | }; 302 | 303 | -------------------------------------------------------------------------------- /Source/BUITween/Public/BUITweenModule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Modules/ModuleManager.h" 5 | #include "Tickable.h" 6 | 7 | class FBUITweenModule : public IModuleInterface, public FTickableGameObject 8 | { 9 | public: 10 | 11 | /** IModuleInterface implementation */ 12 | virtual void StartupModule() override; 13 | virtual void ShutdownModule() override; 14 | 15 | // FTickableGameObject interface 16 | virtual void Tick( float DeltaTime ) override; 17 | 18 | virtual ETickableTickType GetTickableTickType() const override 19 | { 20 | return ETickableTickType::Always; 21 | } 22 | virtual TStatId GetStatId() const override 23 | { 24 | RETURN_QUICK_DECLARE_CYCLE_STAT( UBUITween, STATGROUP_Tickables ); 25 | } 26 | virtual bool IsTickableWhenPaused() const 27 | { 28 | return true; 29 | } 30 | virtual bool IsTickableInEditor() const 31 | { 32 | return false; 33 | } 34 | 35 | 36 | private: 37 | /** The last frame number we were ticked. We don't want to tick multiple times per frame */ 38 | uint32 LastFrameNumberWeTicked; 39 | }; 40 | --------------------------------------------------------------------------------