├── .gitignore ├── Docs ├── ActivateScreen.png ├── JamObjectList.png └── JamTabStrip.png ├── README.md ├── Resources └── Icon128.png ├── SharedJamUI.uplugin ├── Source └── SharedJamUI │ ├── Private │ ├── Buttons │ │ ├── JamButton.cpp │ │ └── JamButtonStyle.cpp │ ├── JamMenuManager.cpp │ ├── JamScreenBase.cpp │ ├── JamUIBlueprintLibrary.cpp │ ├── JamUserWidget.cpp │ ├── Lists │ │ ├── JamObjectList.cpp │ │ └── JamObjectTile.cpp │ ├── SharedJamUI.cpp │ ├── SharedJamUIPrivatePCH.h │ └── Tabs │ │ ├── JamTabButton.cpp │ │ └── JamTabStrip.cpp │ ├── Public │ ├── Buttons │ │ ├── JamButton.h │ │ └── JamButtonStyle.h │ ├── JamMenuManager.h │ ├── JamScreenBase.h │ ├── JamUIBlueprintLibrary.h │ ├── JamUserWidget.h │ ├── Lists │ │ ├── JamObjectList.h │ │ └── JamObjectTile.h │ ├── SharedJamUI.h │ └── Tabs │ │ ├── JamTabButton.h │ │ └── JamTabStrip.h │ └── SharedJamUI.Build.cs └── license.txt /.gitignore: -------------------------------------------------------------------------------- 1 | Binaries/ 2 | Intermediate/ 3 | -------------------------------------------------------------------------------- /Docs/ActivateScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joat/SharedJamUI/ebf3edca75617f32b4f4b43afebad6f7bdc32a34/Docs/ActivateScreen.png -------------------------------------------------------------------------------- /Docs/JamObjectList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joat/SharedJamUI/ebf3edca75617f32b4f4b43afebad6f7bdc32a34/Docs/JamObjectList.png -------------------------------------------------------------------------------- /Docs/JamTabStrip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joat/SharedJamUI/ebf3edca75617f32b4f4b43afebad6f7bdc32a34/Docs/JamTabStrip.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SharedJamUI plugin for UE4 2 | 3 | This plugin implements some useful base widgets and a simple menu manager to create game user interfaces in **Unreal Engine 4**. 4 | 5 | ## Quick Start 6 | 7 | It's easy to get up and running: 8 | 9 | * Download the SharedJamUI plugin source from this page (click **Clone or download** -> **Download ZIP**). 10 | 11 | * Unzip the files into a new **SharedJamUI** sub-folder under your project's **Plugins** folder. It should end up looking like *"/MyProject/Plugins/SharedJamUI/"* 12 | 13 | * **Rebuild** your C++ project. The new plugin will be compiled too! 14 | 15 | * Load the editor. 16 | 17 | * Create a class derived from AJamMenuManager (or reparent your existing HUD class to derive from it) 18 | 19 | * Set this new class as the HUD class in your Game Mode class 20 | 21 | * Now you can create new screens by deriving from UJamScreenBase, and display them by calling ActivateScreen on the menu manager 22 | 23 | ![ActivateScreen](Docs/ActivateScreen.png) 24 | 25 | If the rebuild was successful but you don't see the new features, double check that the **Shared Jam UI** plugin is enabled by clicking the **Settings** toolbar button, then click **Plugins**. Locate the **Shared Jam UI** plugin and make sure **Enabled** is checked. 26 | 27 | If you're new to plugins in UE4, you can find lots of information [right here](https://wiki.unrealengine.com/An_Introduction_to_UE4_Plugins). 28 | 29 | ## Plugin Details 30 | 31 | ### Interesting Classes 32 | 33 | *UJamScreenBase* is the parent class for any screen in the UI. Only one screen can be active at once; when a new screen is activated, older screens will be rendered inactive according to the ActivationMode of the new screen (Transparent will set them to HitTestInvisible, Opaque will set them to Collapsed, and Exclusive will destroy them). When a screen is popped, the screen underneath it will be restored to Visible. Screens get events for being pushed onto the menu stack or popped from it, and events when uncovered or obscured by other menus, so they can further customize this behavior (e.g., animating in or out). 34 | 35 | *UJamUserWidget* is the base class for other widgets in the library and can be used as the base class for your widgets as well. It provides easy access to the menu manager (GetMenuManager) and exposes a PreConstruct() method that is called *in the designer* as well as at runtime. This can be used to make generic widget building blocks like buttons display as desired in the designer, but must be used with caution. The game isn't running in the designer, so accessing the player controller or game instance will fail. A good rule of thumb is to only access member properties of the widget during PreConstruct. 36 | 37 | *UJamObjectList* and *UJamObjectTile* work together to create tile views for lists of objects or classes. Derive from UJamObjectList and add a panel widget (e.g., a vertical box) named TileContainer. Place this derived type in another blueprint and set DefaultTileType to a visualizer class derived from UJamObjectTile, then set ObjectsToDisplay/ClassesToDisplay and call RebuildObjectMap. This will create an instance of your tile class for each object/class in the list, and that class can visualize the object however it sees fit. 38 | 39 | ![JamObjectList](Docs/JamObjectList.png) 40 | 41 | *UJamTabStrip* and *UJamTabButton* work together to create a 'radio button' where at most one such button can be active at once (typically used for a tab strip that in turn controls the visibility of other widgets in a widget switcher panel). 42 | 43 | ![JamTabStrip](Docs/JamTabStrip.png) 44 | 45 | 46 | ### Known Issues 47 | 48 | This plugin should be considered 'jam-quality' code, it hasn't been battle tested in production. 49 | 50 | * Menus are hard-referenced, so loading your top level menu will end up loading all submenus into memory as well. This is bad both for memory usage and iteration time in the editor. Lazy-loading screens via asset references while displaying a progress widget will probably be added in the future. 51 | 52 | * Code is under- or undocumented. Have a look at the test project to see how to use the plugin in practice. 53 | 54 | ### Compatibility 55 | 56 | This plugin requires Visual Studio and either a C++ code project or the full Unreal Engine 4 source code from GitHub. If you are new to programming in UE4, please see the official [Programming Guide](https://docs.unrealengine.com/latest/INT/Programming/index.html)! 57 | 58 | The plugin has only been tested on Windows with Visual Studio 2015 and the 4.14 release of UE4, although it should be portable to other platforms and future versions of UE4. 59 | 60 | 61 | ## Support 62 | 63 | Note: This project is maintained in a private Perforce repository and mirrored to GitHub. 64 | 65 | It should be considered 'jam-quality' code and hasn't been battle tested in production, so YMMV; however if any critical fixes are contributed, I'll certainly try to review and integrate them. For bugs, please file an issue, submit a pull request or catch me [on Twitter](https://twitter.com/joatski). 66 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joat/SharedJamUI/ebf3edca75617f32b4f4b43afebad6f7bdc32a34/Resources/Icon128.png -------------------------------------------------------------------------------- /SharedJamUI.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "SharedJamUI", 6 | "Description": "Simple menu manager and various useful base class widgets", 7 | "Category": "User Interface", 8 | "CreatedBy": "Michael Noland", 9 | "CreatedByURL": "http://michaelnoland.com", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": false, 14 | "IsBetaVersion": true, 15 | "Installed": false, 16 | "Modules": [ 17 | { 18 | "Name": "SharedJamUI", 19 | "Type": "Runtime", 20 | "LoadingPhase": "Default" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/Buttons/JamButton.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "SharedJamUIPrivatePCH.h" 4 | #include "JamButton.h" 5 | 6 | UJamButton::UJamButton(const FObjectInitializer& ObjectInitializer) 7 | : Super(ObjectInitializer) 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/Buttons/JamButtonStyle.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "SharedJamUIPrivatePCH.h" 4 | #include "JamButtonStyle.h" 5 | 6 | 7 | UJamButtonStyle::UJamButtonStyle() 8 | { 9 | SButton::FArguments ButtonDefaults; 10 | WidgetStyle = *ButtonDefaults._ButtonStyle; 11 | 12 | ColorAndOpacity = FLinearColor::White; 13 | BackgroundColor = FLinearColor::White; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/JamMenuManager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "SharedJamUIPrivatePCH.h" 4 | #include "JamMenuManager.h" 5 | #include "JamScreenBase.h" 6 | 7 | void AJamMenuManager::ActivateScreen(UJamScreenBase* ScreenToAdd, EJamScreenActivationMode ActivationMode) 8 | { 9 | if (ScreenToAdd != nullptr) 10 | { 11 | if (ActivationMode == EJamScreenActivationMode::Exclusive) 12 | { 13 | // Close the existing screens 14 | while (ScreenStack.Num()) 15 | { 16 | FJamScreenStackEntry DyingEntry = ScreenStack.Pop(/*bAllowShrinking=*/ false); 17 | RemoveScreenInternal(DyingEntry); 18 | } 19 | ActivationMode = EJamScreenActivationMode::Opaque; 20 | } 21 | 22 | if (ScreenStack.Num() > 0) 23 | { 24 | // Let the topmost screen know it isn't topmost anymore 25 | ScreenStack.Last().Screen->OnScreenCovered(); 26 | } 27 | 28 | if (ActivationMode == EJamScreenActivationMode::Opaque) 29 | { 30 | // Hide the existing screens 31 | for (int32 ExistingIndex = ScreenStack.Num() - 1; (ExistingIndex >= 0); --ExistingIndex) 32 | { 33 | UJamScreenBase* Screen = ScreenStack[ExistingIndex].Screen; 34 | if (Screen->GetVisibility() != ESlateVisibility::Collapsed) 35 | { 36 | Screen->SetVisibility(ESlateVisibility::Collapsed); 37 | } 38 | } 39 | } 40 | 41 | // Create the new entry 42 | FJamScreenStackEntry NewScreen; 43 | NewScreen.Screen = ScreenToAdd; 44 | NewScreen.ActivationMode = ActivationMode; 45 | ScreenStack.Add(NewScreen); 46 | 47 | ScreenToAdd->OnScreenPushed(); 48 | ScreenToAdd->OnScreenUncovered(); 49 | ScreenToAdd->AddToPlayerScreen(); 50 | } 51 | } 52 | 53 | void AJamMenuManager::RemoveScreen(UJamScreenBase* ScreenToRemove) 54 | { 55 | const int32 ScreenIndex = ScreenStack.IndexOfByPredicate([ScreenToRemove](const FJamScreenStackEntry& Entry) { return Entry.Screen == ScreenToRemove; }); 56 | if (ScreenIndex != INDEX_NONE) 57 | { 58 | FJamScreenStackEntry DyingEntry = ScreenStack[ScreenIndex]; 59 | ScreenStack.RemoveAt(ScreenIndex, /*Count=*/ 1, /*bAllowShrinking=*/ false); 60 | RemoveScreenInternal(DyingEntry); 61 | 62 | //@TODO: Recompute visibility for remaining stack entries 63 | if (ScreenStack.Num() > 0) 64 | { 65 | // Restore visibility on remaining entries below the topmost entry until we run into an opaque one 66 | const int32 LowestOpaqueIndex = ScreenStack.FindLastByPredicate([](const FJamScreenStackEntry& Entry) { return Entry.ActivationMode == EJamScreenActivationMode::Opaque; }); 67 | const int32 StartIndex = (LowestOpaqueIndex == INDEX_NONE) ? 0 : LowestOpaqueIndex; 68 | 69 | for (int32 Index = StartIndex; Index < ScreenStack.Num() - 1; ++Index) 70 | { 71 | ScreenStack[Index].Screen->SetVisibility(ESlateVisibility::HitTestInvisible); 72 | } 73 | 74 | // Uncover the topmost entry 75 | ScreenStack.Last().Screen->OnScreenUncovered(); 76 | } 77 | } 78 | } 79 | 80 | void AJamMenuManager::PopScreen() 81 | { 82 | if (ScreenStack.Num() > 0) 83 | { 84 | RemoveScreen(ScreenStack.Top().Screen); 85 | } 86 | } 87 | 88 | void AJamMenuManager::RemoveScreenInternal(const FJamScreenStackEntry& ScreenRecord) 89 | { 90 | check(ScreenRecord.Screen); 91 | ScreenRecord.Screen->OnScreenCovered(); 92 | ScreenRecord.Screen->OnScreenPopped(); 93 | 94 | ScreenRecord.Screen->RemoveFromParent(); 95 | } 96 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/JamScreenBase.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "SharedJamUIPrivatePCH.h" 4 | #include "JamScreenBase.h" 5 | #include "Animation/UMGSequencePlayer.h" 6 | 7 | UJamScreenBase::UJamScreenBase(const FObjectInitializer& ObjectInitializer) 8 | : Super(ObjectInitializer) 9 | , TransitionOpenAnimationSpeed(1.0f) 10 | , TransitionCloseAnimationSpeed(1.0f) 11 | { 12 | } 13 | 14 | void UJamScreenBase::OnScreenPushed() 15 | { 16 | bIsScreenAnimationClosedOrClosing = bScreenAnimationStartsClosed; 17 | 18 | K2_OnScreenPushed(); 19 | } 20 | 21 | void UJamScreenBase::OnScreenPopped() 22 | { 23 | K2_OnScreenPopped(); 24 | } 25 | 26 | void UJamScreenBase::OnScreenUncovered() 27 | { 28 | SetVisibility(ESlateVisibility::Visible); 29 | const UWidgetAnimation* TransitionCloseAnimation = GetTransitionCloseAnimation(); 30 | if ((TransitionCloseAnimation != nullptr) && bIsScreenAnimationClosedOrClosing) 31 | { 32 | bIsScreenAnimationClosedOrClosing = false; 33 | if (UUMGSequencePlayer* TransitionPlayer = GetOrAddPlayer(TransitionCloseAnimation)) 34 | { 35 | if (TransitionPlayer->GetPlaybackStatus() == EMovieScenePlayerStatus::Stopped) 36 | { 37 | TransitionPlayer->Play(0.0f, 1, EUMGSequencePlayMode::Reverse, TransitionOpenAnimationSpeed); 38 | } 39 | else 40 | { 41 | TransitionPlayer->Reverse(); 42 | } 43 | } 44 | } 45 | 46 | K2_OnScreenUncovered(); 47 | } 48 | 49 | void UJamScreenBase::OnScreenCovered() 50 | { 51 | SetVisibility(ESlateVisibility::HitTestInvisible); 52 | const UWidgetAnimation* TransitionCloseAnimation = GetTransitionCloseAnimation(); 53 | if ((TransitionCloseAnimation != nullptr) && !bIsScreenAnimationClosedOrClosing) 54 | { 55 | bIsScreenAnimationClosedOrClosing = true; 56 | if (UUMGSequencePlayer* TransitionPlayer = GetOrAddPlayer(TransitionCloseAnimation)) 57 | { 58 | if (TransitionPlayer->GetPlaybackStatus() == EMovieScenePlayerStatus::Stopped) 59 | { 60 | TransitionPlayer->Play(0.0f, 1, EUMGSequencePlayMode::Forward, TransitionCloseAnimationSpeed); 61 | } 62 | else 63 | { 64 | TransitionPlayer->Reverse(); 65 | } 66 | } 67 | } 68 | 69 | K2_OnScreenCovered(); 70 | } 71 | 72 | UWidgetAnimation* UJamScreenBase::GetTransitionCloseAnimation_Implementation() 73 | { 74 | return nullptr; 75 | } -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/JamUIBlueprintLibrary.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "SharedJamUIPrivatePCH.h" 4 | #include "JamUIBlueprintLibrary.h" 5 | 6 | UClass* UJamUIBlueprintLibrary::CastObjectToClass(UObject* Object) 7 | { 8 | return Cast(Object); 9 | } 10 | 11 | UObject* UJamUIBlueprintLibrary::GetUnsafeDefaultObjectFromClass(UClass* InClass) 12 | { 13 | if (InClass != nullptr) 14 | { 15 | return InClass->GetDefaultObject(); 16 | } 17 | return nullptr; 18 | } 19 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/JamUserWidget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "SharedJamUIPrivatePCH.h" 4 | #include "JamUserWidget.h" 5 | #include "JamMenuManager.h" 6 | 7 | void UJamUserWidget::OnWidgetRebuilt() 8 | { 9 | Super::OnWidgetRebuilt(); 10 | 11 | NativePreConstruct(); 12 | if (IsDesignTime()) 13 | { 14 | DesignTimeNativeConstruct(); 15 | } 16 | } 17 | 18 | void UJamUserWidget::DesignTimeNativeConstruct() 19 | { 20 | DesignTimeConstruct(); 21 | } 22 | 23 | void UJamUserWidget::NativePreConstruct() 24 | { 25 | PreConstruct(IsDesignTime()); 26 | } 27 | 28 | AJamMenuManager* UJamUserWidget::GetMenuManager() 29 | { 30 | const FLocalPlayerContext& Context = GetPlayerContext(); 31 | return Context.GetHUD(false); 32 | } 33 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/Lists/JamObjectList.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "SharedJamUIPrivatePCH.h" 4 | #include "JamObjectList.h" 5 | #include "JamObjectTile.h" 6 | #include "Components/PanelWidget.h" 7 | 8 | UJamObjectList::UJamObjectList(const FObjectInitializer& ObjectInitializer) 9 | : Super(ObjectInitializer) 10 | { 11 | } 12 | 13 | TSubclassOf UJamObjectList::PickClassForObject_Implementation(UObject* ObjectToVisualize) 14 | { 15 | return DefaultTileType; 16 | } 17 | 18 | void UJamObjectList::RebuildObjectMap() 19 | { 20 | ResetContainer(); 21 | AppendWidgetsInternal(MakeArrayView(ObjectsToDisplay)); 22 | AppendWidgetsInternal(TArrayView((UObject**)ClassesToDisplay.GetData(), ClassesToDisplay.Num())); 23 | OnListRebuilt(); 24 | } 25 | 26 | void UJamObjectList::NativeConstruct() 27 | { 28 | Super::NativeConstruct(); 29 | RebuildObjectMap(); 30 | } 31 | 32 | void UJamObjectList::NativePreConstruct() 33 | { 34 | Super::NativePreConstruct(); 35 | 36 | if (IsDesignTime()) 37 | { 38 | if (NumDesignerEntries > 0) 39 | { 40 | TArray NullList; 41 | NullList.AddZeroed(NumDesignerEntries); 42 | 43 | ResetContainer(); 44 | AppendWidgetsInternal(TArrayView(NullList)); 45 | OnListRebuilt(); 46 | } 47 | else 48 | { 49 | RebuildObjectMap(); 50 | } 51 | } 52 | } 53 | 54 | void UJamObjectList::AppendWidgetsInternal(TArrayView EffectiveList) 55 | { 56 | if (TileContainer != nullptr) 57 | { 58 | for (UObject* Object : EffectiveList) 59 | { 60 | TSubclassOf TileClass = PickClassForObject(Object); 61 | 62 | if (TileClass != nullptr) 63 | { 64 | UJamObjectTile* Tile = NewObject(this, TileClass); 65 | Tile->ParentList = this; 66 | Tile->MyObject = Object; 67 | 68 | TileContainer->AddChild(Tile); 69 | } 70 | } 71 | } 72 | } 73 | 74 | void UJamObjectList::ResetContainer() 75 | { 76 | if (TileContainer != nullptr) 77 | { 78 | TileContainer->ClearChildren(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/Lists/JamObjectTile.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "SharedJamUIPrivatePCH.h" 4 | #include "JamObjectTile.h" 5 | 6 | UObject* UJamObjectTile::GetDefaultObjectOfMyObjectIfItIsAClass() 7 | { 8 | if (UClass* MyObjectAsClass = Cast(MyObject)) 9 | { 10 | return MyObjectAsClass->GetDefaultObject(); 11 | } 12 | else 13 | { 14 | return nullptr; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/SharedJamUI.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "SharedJamUIPrivatePCH.h" 4 | 5 | #define LOCTEXT_NAMESPACE "FSharedJamUIModule" 6 | 7 | void FSharedJamUIModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 10 | } 11 | 12 | void FSharedJamUIModule::ShutdownModule() 13 | { 14 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 15 | // we call this function before unloading the module. 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | 20 | IMPLEMENT_MODULE(FSharedJamUIModule, SharedJamUI) -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/SharedJamUIPrivatePCH.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "Engine.h" 4 | #include "SharedJamUI.h" 5 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/Tabs/JamTabButton.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "SharedJamUIPrivatePCH.h" 4 | #include "JamTabButton.h" 5 | 6 | void UJamTabButton::SetIsSelected_Implementation(bool bDrawAsSelected) 7 | { 8 | bIsButtonSelected = bDrawAsSelected; 9 | } 10 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Private/Tabs/JamTabStrip.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #include "SharedJamUIPrivatePCH.h" 4 | #include "JamTabStrip.h" 5 | #include "JamTabButton.h" 6 | #include "Blueprint/WidgetTree.h" 7 | 8 | void UJamTabStrip::NativeConstruct() 9 | { 10 | Super::NativeConstruct(); 11 | 12 | GatherButtons(); 13 | UpdateSelectionState(); 14 | } 15 | 16 | void UJamTabStrip::DesignTimeNativeConstruct() 17 | { 18 | Super::DesignTimeNativeConstruct(); 19 | 20 | GatherButtons(); 21 | UpdateSelectionState(); 22 | } 23 | 24 | int32 UJamTabStrip::GetSelectedIndex() 25 | { 26 | return SelectedIndex; 27 | } 28 | 29 | void UJamTabStrip::SetSelectedIndex(int32 NewIndex) 30 | { 31 | const int32 LastSelectedIndex = INDEX_NONE; 32 | if (MyButtons.IsValidIndex(NewIndex)) 33 | { 34 | //@TODO: See if the button actually allows selection 35 | SelectedIndex = NewIndex; 36 | } 37 | else 38 | { 39 | SelectedIndex = INDEX_NONE; 40 | } 41 | 42 | if (SelectedIndex != LastSelectedIndex) 43 | { 44 | UpdateSelectionState(); 45 | } 46 | } 47 | 48 | void UJamTabStrip::UpdateSelectionState() 49 | { 50 | for (UJamTabButton* Button : MyButtons) 51 | { 52 | const bool bButtonSelected = Button->IndexInParent == SelectedIndex; 53 | Button->SetIsSelected(bButtonSelected); 54 | } 55 | 56 | OnSelectionChanged.Broadcast(SelectedIndex); 57 | } 58 | 59 | void UJamTabStrip::GatherButtons() 60 | { 61 | MyButtons.Reset(); 62 | UJamTabStrip* ParentStrip = this; 63 | WidgetTree->ForEachWidget([ParentStrip](UWidget* Widget) 64 | { 65 | if (UJamTabButton* Button = Cast(Widget)) 66 | { 67 | Button->ParentStrip = ParentStrip; 68 | Button->IndexInParent = ParentStrip->MyButtons.Num(); 69 | ParentStrip->MyButtons.Add(Button); 70 | } 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Public/Buttons/JamButton.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #pragma once 4 | 5 | class UJamButtonStyle; 6 | 7 | #include "JamUserWidget.h" 8 | #include "JamButton.generated.h" 9 | 10 | UCLASS() 11 | class SHAREDJAMUI_API UJamButton : public UJamUserWidget 12 | { 13 | GENERATED_BODY() 14 | 15 | public: 16 | UJamButton(const FObjectInitializer& ObjectInitializer); 17 | 18 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 19 | UJamButtonStyle* ButtonStyle; 20 | }; 21 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Public/Buttons/JamButtonStyle.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #pragma once 4 | 5 | #include "Engine/DataAsset.h" 6 | #include "JamButtonStyle.generated.h" 7 | 8 | UCLASS() 9 | class SHAREDJAMUI_API UJamButtonStyle : public UDataAsset 10 | { 11 | GENERATED_BODY() 12 | 13 | public: 14 | UJamButtonStyle(); 15 | 16 | /** The button style used at runtime */ 17 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Appearance", meta=( DisplayName="Style" )) 18 | FButtonStyle WidgetStyle; 19 | 20 | /** The color multiplier for the button content */ 21 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Appearance", meta=( sRGB="true" )) 22 | FLinearColor ColorAndOpacity; 23 | 24 | /** The color multiplier for the button background */ 25 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Appearance", meta=( sRGB="true" )) 26 | FLinearColor BackgroundColor; 27 | 28 | }; 29 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Public/JamMenuManager.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #pragma once 4 | 5 | class UJamScreenBase; 6 | 7 | #include "GameFramework/HUD.h" 8 | #include "JamMenuManager.generated.h" 9 | 10 | UENUM(BlueprintType) 11 | enum class EJamScreenActivationMode : uint8 12 | { 13 | // Show screens below me 14 | Transparent, 15 | 16 | // Do not show screens below me, but don't remove them either 17 | Opaque, 18 | 19 | // Remove any active screens before showing me 20 | Exclusive 21 | }; 22 | 23 | USTRUCT() 24 | struct FJamScreenStackEntry 25 | { 26 | GENERATED_BODY() 27 | 28 | UPROPERTY() 29 | UJamScreenBase* Screen; 30 | 31 | UPROPERTY() 32 | EJamScreenActivationMode ActivationMode; 33 | }; 34 | 35 | /** 36 | * 37 | */ 38 | UCLASS() 39 | class SHAREDJAMUI_API AJamMenuManager : public AHUD 40 | { 41 | GENERATED_BODY() 42 | 43 | public: 44 | // Add a new screen to the menu stack, optionally removing or hiding screens below the top (based on the ActivationMode) 45 | UFUNCTION(BlueprintCallable, Category=UI, meta=(Keywords="Push AddScreen")) 46 | void ActivateScreen(UJamScreenBase* ScreenToAdd, EJamScreenActivationMode ActivationMode); 47 | 48 | // Remove the specified screen regardless of where it is in the stack of screens 49 | UFUNCTION(BlueprintCallable, Category=UI, meta=(Keywords="Pop RemoveScreen")) 50 | void RemoveScreen(UJamScreenBase* ScreenToRemove); 51 | 52 | // Remove the top most screen 53 | UFUNCTION(BlueprintCallable, Category=UI) 54 | void PopScreen(); 55 | 56 | protected: 57 | void RemoveScreenInternal(const FJamScreenStackEntry& ScreenRecord); 58 | 59 | protected: 60 | UPROPERTY() 61 | TArray ScreenStack; 62 | }; 63 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Public/JamScreenBase.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #pragma once 4 | 5 | #include "JamUserWidget.h" 6 | #include "JamScreenBase.generated.h" 7 | 8 | // The base class for a screen that can be added to the menu manager 9 | UCLASS(Abstract) 10 | class SHAREDJAMUI_API UJamScreenBase : public UJamUserWidget 11 | { 12 | GENERATED_BODY() 13 | 14 | public: 15 | UJamScreenBase(const FObjectInitializer& ObjectInitializer); 16 | 17 | // Called when this screen is added to the menu manager (native version, should call K2_OnScreenPushed at some point) 18 | virtual void OnScreenPushed(); 19 | 20 | // Called when this screen is removed from the menu manager (native version, should call K2_OnScreenPopped at some point) 21 | virtual void OnScreenPopped(); 22 | 23 | // Called when this screen becomes the top of the screen stack again (native version, should call K2_OnScreenUncovered at some point) 24 | virtual void OnScreenUncovered(); 25 | 26 | // Called when this screen is covered by another screen (native version, should call K2_OnScreenCovered at some point) 27 | virtual void OnScreenCovered(); 28 | 29 | protected: 30 | // Called when this screen is added to the menu manager 31 | UFUNCTION(BlueprintImplementableEvent, meta=(DisplayName="OnScreenPushed")) 32 | void K2_OnScreenPushed() const; 33 | 34 | // Called when this screen is removed from the menu manager 35 | UFUNCTION(BlueprintImplementableEvent, meta=(DisplayName="OnScreenPopped")) 36 | void K2_OnScreenPopped() const; 37 | 38 | // Called when this screen becomes the top of the screen stack again 39 | UFUNCTION(BlueprintImplementableEvent, meta=(DisplayName="OnScreenOpened")) 40 | void K2_OnScreenUncovered() const; 41 | 42 | // Called when this screen is covered by another screen 43 | UFUNCTION(BlueprintImplementableEvent, meta=(DisplayName="OnScreenOpened")) 44 | void K2_OnScreenCovered() const; 45 | 46 | protected: 47 | // Returns the optional animation to play when closing this screen (animates from open to closed) 48 | UFUNCTION(BlueprintNativeEvent) 49 | UWidgetAnimation* GetTransitionCloseAnimation(); 50 | 51 | // The speed to play the closing animation (in reverse) when opening the screen 52 | UPROPERTY(EditDefaultsOnly, Category=Animation) 53 | float TransitionOpenAnimationSpeed; 54 | 55 | // The speed to play the closing animation when closing the screen 56 | UPROPERTY(EditDefaultsOnly, Category=Animation) 57 | float TransitionCloseAnimationSpeed; 58 | 59 | // If true, the screen animates from closed to opened when first added to the menu manager (the closing animation should be played in reverse) 60 | // Otherwise it starts open and the animation is only played if the screen is closed and then reopened 61 | UPROPERTY(EditDefaultsOnly, Category=Animation) 62 | bool bScreenAnimationStartsClosed; 63 | 64 | private: 65 | bool bIsScreenAnimationClosedOrClosing; 66 | }; 67 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Public/JamUIBlueprintLibrary.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #pragma once 4 | 5 | #include "Kismet/BlueprintFunctionLibrary.h" 6 | #include "JamUIBlueprintLibrary.generated.h" 7 | 8 | /** 9 | * 10 | */ 11 | UCLASS() 12 | class SHAREDJAMUI_API UJamUIBlueprintLibrary : public UBlueprintFunctionLibrary 13 | { 14 | GENERATED_BODY() 15 | 16 | public: 17 | UFUNCTION(BlueprintCallable, Category = "User Interface") 18 | static UClass* CastObjectToClass(UObject* Object); 19 | 20 | UFUNCTION(BlueprintPure, Category = "User Interface") 21 | static UObject* GetUnsafeDefaultObjectFromClass(UClass* Class); 22 | }; 23 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Public/JamUserWidget.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #pragma once 4 | 5 | #include "Blueprint/UserWidget.h" 6 | #include "JamUserWidget.generated.h" 7 | 8 | class AJamMenuManager; 9 | 10 | // Custom base widget with some added utility methods 11 | UCLASS() 12 | class SHAREDJAMUI_API UJamUserWidget : public UUserWidget 13 | { 14 | GENERATED_BODY() 15 | 16 | public: 17 | // UWidget interface 18 | virtual void OnWidgetRebuilt() override; 19 | // End of UWidget interface 20 | 21 | /** 22 | * Called before Construct() is called in the game, or called instead of Construct() in the designer 23 | * Warning: This can be called in the designer, where the game is not running, so accessing game types 24 | * like the player controller or game instance will fail! 25 | */ 26 | UFUNCTION(BlueprintImplementableEvent, BlueprintCosmetic, Category="User Interface", meta=(Keywords="Begin Play")) 27 | void PreConstruct(bool bInDesigner); 28 | 29 | /** 30 | * Called after the underlying slate widget is constructed in the designer. Warning: The game is not running in the designer 31 | * so accessing game types like the player controller or game instance will fail! 32 | */ 33 | UFUNCTION(BlueprintImplementableEvent, BlueprintCosmetic, Category="User Interface", meta=(Keywords="Begin Play")) 34 | void DesignTimeConstruct(); 35 | 36 | UFUNCTION(BlueprintPure, Category="User Interface", meta=(DisplayName="IsDesignTime")) 37 | bool K2_IsDesignTime() const 38 | { 39 | return IsDesignTime(); 40 | } 41 | 42 | /** Returns the menu manager associated with the player that is viewing this widget (or player 1) */ 43 | UFUNCTION(BlueprintCallable, Category="User Interface") 44 | AJamMenuManager* GetMenuManager(); 45 | 46 | protected: 47 | /** 48 | * Called before Construct() is called in the game, or called instead of Construct() in the designer 49 | * Warning: This can be called in the designer, where the game is not running, so accessing game types 50 | * like the player controller or game instance will fail! 51 | */ 52 | virtual void NativePreConstruct(); 53 | 54 | /** 55 | * Called after the underlying slate widget is constructed in the designer. Warning: The game is not running in the designer 56 | * so accessing game types like the player controller or game instance will fail! 57 | */ 58 | virtual void DesignTimeNativeConstruct(); 59 | }; 60 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Public/Lists/JamObjectList.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #pragma once 4 | 5 | class UJamObjectTile; 6 | class UPanelWidget; 7 | 8 | #include "JamUserWidget.h" 9 | #include "JamObjectList.generated.h" 10 | 11 | UCLASS() 12 | class SHAREDJAMUI_API UJamObjectList : public UJamUserWidget 13 | { 14 | GENERATED_BODY() 15 | 16 | public: 17 | UJamObjectList(const FObjectInitializer& ObjectInitializer); 18 | 19 | // UUserWidget interface 20 | virtual void NativeConstruct() override; 21 | // End of UUserWidget interface 22 | 23 | // UJamUserWidget interface 24 | virtual void NativePreConstruct() override; 25 | // End of UJamUserWidget interface 26 | 27 | // Called to determine what kind of tile should be created for the specified object (in the case of a mixed list) 28 | // The default behavior is to return DefaultTileType 29 | UFUNCTION(BlueprintNativeEvent) 30 | TSubclassOf PickClassForObject(UObject* ObjectToVisualize); 31 | 32 | // This should be called after ObjectsToDisplay is mutated, before any changes will show up 33 | UFUNCTION(BlueprintCallable, Category="Panel") 34 | void RebuildObjectMap(); 35 | 36 | protected: 37 | UFUNCTION(BlueprintImplementableEvent) 38 | void OnListRebuilt(); 39 | 40 | public: 41 | // The default type of tile to display 42 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 43 | TSubclassOf DefaultTileType; 44 | 45 | // The number of empty entries to visualize in the designer (if ObjectsToDisplay is empty) 46 | UPROPERTY(EditAnywhere) 47 | int32 NumDesignerEntries; 48 | 49 | // The object list to display 50 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 51 | TArray ObjectsToDisplay; 52 | 53 | // The class list to display (appended to the ObjectsToDisplay list on construction) 54 | // This is sort of a hack to work around the fact that you can't assign a BP CDO to a UObject* in the editor (it actually points to the UBlueprint, which is useless) 55 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 56 | TArray ClassesToDisplay; 57 | 58 | // The container to place the tiles in (e.g., horizontal or vertical box) 59 | UPROPERTY(BlueprintReadOnly, meta=(BindWidget)) 60 | UPanelWidget* TileContainer; 61 | 62 | private: 63 | void AppendWidgetsInternal(TArrayView EffectiveList); 64 | void ResetContainer(); 65 | }; 66 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Public/Lists/JamObjectTile.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #pragma once 4 | 5 | #include "JamUserWidget.h" 6 | #include "JamObjectTile.generated.h" 7 | 8 | class UJamObjectList; 9 | 10 | UCLASS(Abstract) 11 | class SHAREDJAMUI_API UJamObjectTile : public UJamUserWidget 12 | { 13 | GENERATED_BODY() 14 | 15 | public: 16 | // The untyped object that this tile is visualizing (can be null in the designer) 17 | UPROPERTY(BlueprintReadOnly) 18 | UObject* MyObject; 19 | 20 | // The list that contains this tile 21 | UPROPERTY(BlueprintReadOnly) 22 | UJamObjectList* ParentList; 23 | 24 | protected: 25 | 26 | UFUNCTION(BlueprintPure, Category = "User Interface") 27 | UObject* GetDefaultObjectOfMyObjectIfItIsAClass(); 28 | }; 29 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Public/SharedJamUI.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #pragma once 4 | 5 | #include "ModuleManager.h" 6 | 7 | class FSharedJamUIModule : public IModuleInterface 8 | { 9 | public: 10 | 11 | /** IModuleInterface implementation */ 12 | virtual void StartupModule() override; 13 | virtual void ShutdownModule() override; 14 | }; -------------------------------------------------------------------------------- /Source/SharedJamUI/Public/Tabs/JamTabButton.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #pragma once 4 | 5 | #include "JamUserWidget.h" 6 | #include "JamTabButton.generated.h" 7 | 8 | class UJamTabStrip; 9 | 10 | UCLASS() 11 | class SHAREDJAMUI_API UJamTabButton : public UJamUserWidget 12 | { 13 | GENERATED_BODY() 14 | 15 | public: 16 | 17 | UPROPERTY(BlueprintReadOnly) 18 | UJamTabStrip* ParentStrip; 19 | 20 | UPROPERTY(BlueprintReadOnly) 21 | int32 IndexInParent; 22 | 23 | // Can the button be selected 24 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 25 | bool bCanButtonBeSelected; 26 | 27 | // Is the button currently considered selected? 28 | UPROPERTY(BlueprintReadOnly, EditAnywhere) 29 | bool bIsButtonSelected; 30 | 31 | UFUNCTION(BlueprintNativeEvent) 32 | void SetIsSelected(bool bDrawAsSelected); 33 | }; 34 | -------------------------------------------------------------------------------- /Source/SharedJamUI/Public/Tabs/JamTabStrip.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | #pragma once 4 | 5 | #include "JamUserWidget.h" 6 | #include "JamTabStrip.generated.h" 7 | 8 | class UJamTabButton; 9 | 10 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSelectedTabChangedEvent, int32, SelectionIndex); 11 | 12 | UCLASS() 13 | class SHAREDJAMUI_API UJamTabStrip : public UJamUserWidget 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | // UUserWidget interface 19 | virtual void NativeConstruct() override; 20 | // End of UUserWidget interface 21 | 22 | // UJamUserWidget interface 23 | virtual void DesignTimeNativeConstruct() override; 24 | // End of UJamUserWidget interface 25 | 26 | UFUNCTION(BlueprintPure, Category="Button|TabStrip") 27 | int32 GetSelectedIndex(); 28 | 29 | UFUNCTION(BlueprintCallable, Category="Button|TabStrip") 30 | void SetSelectedIndex(int32 NewIndex); 31 | 32 | public: 33 | // Called when the tab selection is changed 34 | // Warning: Called in the designer too, so guard sensitive code with an IsDesignTime check 35 | UPROPERTY(BlueprintAssignable, Category="Button|TabStrip") 36 | FOnSelectedTabChangedEvent OnSelectionChanged; 37 | 38 | protected: 39 | UPROPERTY(BlueprintReadOnly) 40 | TArray MyButtons; 41 | 42 | private: 43 | UPROPERTY(EditAnywhere) 44 | int32 SelectedIndex; 45 | 46 | private: 47 | void GatherButtons(); 48 | void UpdateSelectionState(); 49 | }; 50 | -------------------------------------------------------------------------------- /Source/SharedJamUI/SharedJamUI.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michael Noland. Placed under the zlib license. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class SharedJamUI : ModuleRules 6 | { 7 | public SharedJamUI(TargetInfo Target) 8 | { 9 | 10 | PublicIncludePaths.AddRange( 11 | new string[] { 12 | "SharedJamUI/Public" 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | "SharedJamUI/Private", 21 | // ... add other private include paths required here ... 22 | } 23 | ); 24 | 25 | 26 | PublicDependencyModuleNames.AddRange( 27 | new string[] 28 | { 29 | "Core", 30 | "UMG" 31 | // ... add other public dependencies that you statically link with here ... 32 | } 33 | ); 34 | 35 | 36 | PrivateDependencyModuleNames.AddRange( 37 | new string[] 38 | { 39 | "CoreUObject", 40 | "Engine", 41 | "Slate", 42 | "SlateCore", 43 | // ... add private dependencies that you statically link with here ... 44 | } 45 | ); 46 | 47 | 48 | DynamicallyLoadedModuleNames.AddRange( 49 | new string[] 50 | { 51 | // ... add any modules that your module loads dynamically here ... 52 | } 53 | ); 54 | 55 | bFasterWithoutUnity = true; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Michael Noland 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | --------------------------------------------------------------------------------