Skip to content

Commit f88bf71

Browse files
authored
Merge pull request #21 from JanSeliv/develop
Develop
2 parents aee6147 + bb367ec commit f88bf71

8 files changed

Lines changed: 359 additions & 124 deletions

File tree

CustomShapeButton.uplugin

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"DocsURL": "",
1111
"MarketplaceURL": "",
1212
"SupportURL": "mailto:janseliw@gmail.com",
13-
"EngineVersion": "5.4.0",
13+
"EngineVersion": "5.5.0",
1414
"EnabledByDefault": true,
1515
"CanContainContent": false,
1616
"IsBetaVersion": false,

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<a href="https://github.com/JanSeliv/CustomShapeButton/blob/main/LICENSE">![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)</a>
2-
<a href="https://www.unrealengine.com/">![Unreal Engine](https://img.shields.io/badge/Unreal-5.4-dea309?style=flat&logo=unrealengine)</a>
2+
<a href="https://www.unrealengine.com/">![Unreal Engine](https://img.shields.io/badge/Unreal-5.5-dea309?style=flat&logo=unrealengine)</a>
33

44
<br/>
55
<p align="center">
@@ -24,7 +24,7 @@ The `Custom Shape Button` plugin revolutionizes the way buttons are designed in
2424

2525
With the `Custom Shape Button` plugin, you can now create buttons of any shape or form you envision. Whether you want a circular button, a star-shaped one, or one in the shape of a custom image, this plugin allows you to bring that vision to life. Additionally, the plugin ensures the hover and press behavior works flawlessly with the custom shapes, ensuring a seamless user experience.
2626

27-
![CustomShapeButton](https://github.com/JanSeliv/CustomShapeButton/assets/20540872/46c3be2c-b325-4528-a626-16a4bb2b4d9c)
27+
![CustomShapeButton](https://github.com/user-attachments/assets/68190cb1-a8a1-4931-9a75-fbe04ff37ec9)
2828

2929
## 📚 Documentation
3030

@@ -35,11 +35,13 @@ Detailed documentation about the Custom Shape Button can be found [here](https:/
3535
Check out our [Release](https://github.com/JanSeliv/CustomShapeButton/releases) page for a sample project showcasing the Custom Shape Button plugin.
3636

3737
## 📅 Changelog
38+
#### 2025-06-27
39+
- Updated to **Unreal Engine 5.5**
40+
- [Bug] Fixed overlapping don't work on covered areas when stacked on top of each other: added `Overlap Order` setting for proper handing underlying buttons
3841
#### 2024-04-25
3942
- Updated to **Unreal Engine 5.4**
4043
- Implemented **Materials Support** as alternative to textures: [doc](https://docs.google.com/document/d/1Ws76obIHRMtsdOjB6YP9K7LTjJR-R56h2uv65PKUBL4/edit#heading=h.jlxkng80vqbe):
4144
> ![image](https://github.com/JanSeliv/CustomShapeButton/assets/20540872/c4a083d2-494e-400f-b363-1ffa795024fa)
42-
4345
#### 2023-10-21
4446
- Updated to **Unreal Engine 5.3**.
4547
#### 2023-06-04
@@ -51,7 +53,7 @@ Feedback and contributions from the community are highly appreciated!
5153

5254
If you'd like to contribute, please fork the project and create a pull request targeting the `develop` branch.
5355

54-
If you've found a bug or have an idea for a new feature, please open a new issue on GitHub. Thank you!
56+
If you've found a bug or have an idea for a new feature, please open a new issue on GitHub or join our [Discord](https://discord.gg/jbWgwDefnE). Thank you!
5557

5658
## 📜 License
5759

Source/CustomShapeButton/Private/CustomShapeButton.cpp

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "CustomShapeButton.h"
44
//---
5+
#include "CustomShapeButtonManager.h"
56
#include "SCustomShapeButton.h"
67
//---
78
#include "Components/ButtonSlot.h"
@@ -33,16 +34,18 @@ void UCustomShapeButton::ForceUpdateImage()
3334
// Is called when the underlying SWidget needs to be constructed
3435
TSharedRef<SWidget> UCustomShapeButton::RebuildWidget()
3536
{
37+
// Super is not called intentionally to create own SCustomShapeButton object instead of SButton
38+
3639
const TSharedRef<SCustomShapeButton> NewButtonRef = SNew(SCustomShapeButton)
37-
.OnClicked(BIND_UOBJECT_DELEGATE(FOnClicked, SlateHandleClicked))
38-
.OnPressed(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandlePressed))
39-
.OnReleased(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandleReleased))
40-
.OnHovered_UObject(this, &ThisClass::SlateHandleHovered)
41-
.OnUnhovered_UObject(this, &ThisClass::SlateHandleUnhovered)
42-
.ButtonStyle(&GetStyle())
43-
.ClickMethod(GetClickMethod())
44-
.TouchMethod(GetTouchMethod())
45-
.IsFocusable(GetIsFocusable());
40+
.OnClicked(BIND_UOBJECT_DELEGATE(FOnClicked, SlateHandleClicked))
41+
.OnPressed(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandlePressed))
42+
.OnReleased(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandleReleased))
43+
.OnHovered_UObject(this, &ThisClass::SlateHandleHovered)
44+
.OnUnhovered_UObject(this, &ThisClass::SlateHandleUnhovered)
45+
.ButtonStyle(&GetStyle())
46+
.ClickMethod(GetClickMethod())
47+
.TouchMethod(GetTouchMethod())
48+
.IsFocusable(GetIsFocusable());
4649
MyButton = NewButtonRef;
4750

4851
if (GetChildrenCount())
@@ -53,5 +56,18 @@ TSharedRef<SWidget> UCustomShapeButton::RebuildWidget()
5356
}
5457
}
5558

59+
UCustomShapeButtonManager::Get().RegisterButton(this);
60+
5661
return NewButtonRef;
5762
}
63+
64+
// Is called when the underlying SWidget needs to be destroyed
65+
void UCustomShapeButton::ReleaseSlateResources(bool bReleaseChildren)
66+
{
67+
if (UCustomShapeButtonManager* CustomShapeButtonManager = UCustomShapeButtonManager::GetCustomShapeButtonManager())
68+
{
69+
CustomShapeButtonManager->UnregisterButton(this);
70+
}
71+
72+
Super::ReleaseSlateResources(bReleaseChildren);
73+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) Yevhenii Selivanov
2+
3+
#include "CustomShapeButtonManager.h"
4+
//---
5+
#include "CustomShapeButton.h"
6+
#include "SCustomShapeButton.h"
7+
//---
8+
#include "Engine/Engine.h"
9+
#include "Input/Reply.h"
10+
//---
11+
#include UE_INLINE_GENERATED_CPP_BY_NAME(CustomShapeButtonManager)
12+
13+
// Returns the manager instance, or crash if can't be obtained
14+
UCustomShapeButtonManager& UCustomShapeButtonManager::Get()
15+
{
16+
UCustomShapeButtonManager* CustomShapeButtonManager = GetCustomShapeButtonManager();
17+
checkf(CustomShapeButtonManager, TEXT("ERROR: [%i] %hs:\n'CustomShapeButtonManager' is null!"), __LINE__, __FUNCTION__);
18+
return *CustomShapeButtonManager;
19+
}
20+
21+
// Returns the manager instance, or nullptr if can't be obtained
22+
UCustomShapeButtonManager* UCustomShapeButtonManager::GetCustomShapeButtonManager()
23+
{
24+
return GEngine ? GEngine->GetEngineSubsystem<UCustomShapeButtonManager>() : nullptr;
25+
}
26+
27+
// Registers a button for event redirection
28+
void UCustomShapeButtonManager::RegisterButton(UCustomShapeButton* Button)
29+
{
30+
const TSharedPtr<SCustomShapeButton> SButton = Button ? Button->GetSlateCustomShapeButton() : nullptr;
31+
if (!ensureMsgf(SButton, TEXT("ASSERT: [%i] %hs:\n'SButton' is not valid!"), __LINE__, __FUNCTION__)
32+
|| !CanRegisterButton(Button))
33+
{
34+
// Button can't handle events if the world is not playing or button itself if not valid
35+
return;
36+
}
37+
38+
// Find insertion point to maintain descending order of OverlapOrder
39+
const int32 InsertIndex = RegisteredButtons.IndexOfByPredicate(
40+
[ZOrder = Button->OverlapOrder](const TSoftObjectPtr<UCustomShapeButton>& It)
41+
{
42+
const UCustomShapeButton* OtherButton = It.Get();
43+
return OtherButton && ZOrder > OtherButton->OverlapOrder;
44+
});
45+
46+
if (InsertIndex != INDEX_NONE)
47+
{
48+
RegisteredButtons.Insert(Button, InsertIndex);
49+
}
50+
else
51+
{
52+
RegisteredButtons.Add(Button);
53+
}
54+
}
55+
56+
// Unregisters a button when it is destroyed
57+
void UCustomShapeButtonManager::UnregisterButton(UCustomShapeButton* Button)
58+
{
59+
const int32 Index = Button ? RegisteredButtons.IndexOfByKey(Button) : INDEX_NONE;
60+
if (Index == INDEX_NONE)
61+
{
62+
// Is already removed or was not even registered
63+
return;
64+
}
65+
66+
RegisteredButtons.RemoveAtSwap(Index);
67+
}
68+
69+
// Returns true is button is initialized and ready to handle events
70+
bool UCustomShapeButtonManager::CanRegisterButton(const UCustomShapeButton* Button)
71+
{
72+
#if !WITH_EDITOR
73+
// Manager is always ready to handle events in builds
74+
return true;
75+
#else
76+
// Don't register preview buttons in editor (not -game)
77+
const bool bIsMinusGame = !GIsEditor;
78+
const UWorld* World = Button ? Button->GetWorld() : nullptr;
79+
const bool bIsGameWorld = World && World->IsGameWorld();
80+
return bIsMinusGame || bIsGameWorld;
81+
#endif // WITH_EDITOR
82+
}
83+
84+
// Handles any mouse event using a delegate
85+
FReply UCustomShapeButtonManager::HandleEvent(const FPointerEvent& Event, const TFunctionRef<FReply(const TSharedRef<SCustomShapeButton>&)>& Callback)
86+
{
87+
FReply FinalReply = FReply::Unhandled();
88+
89+
for (const TSoftObjectPtr<UCustomShapeButton>& It : RegisteredButtons)
90+
{
91+
const UCustomShapeButton* Button = It.Get();
92+
SCustomShapeButton* SButton = Button ? Button->GetSlateCustomShapeButton().Get() : nullptr;
93+
if (SButton)
94+
{
95+
SButton->HandleEvent(/*out*/FinalReply, Event, Callback);
96+
}
97+
}
98+
99+
return FinalReply;
100+
}
101+
102+
/*********************************************************************************************
103+
* Overrides
104+
********************************************************************************************* */
105+
106+
// Called when this subsystem is initialized to perform initial setup
107+
void UCustomShapeButtonManager::Initialize(FSubsystemCollectionBase& Collection)
108+
{
109+
Super::Initialize(Collection);
110+
111+
FWorldDelegates::OnWorldCleanup.AddUObject(this, &ThisClass::OnEndPlay);
112+
}
113+
114+
// Is called on the game world end play to cleanup data
115+
void UCustomShapeButtonManager::OnEndPlay(UWorld* World, bool bArg, bool bCond)
116+
{
117+
RegisteredButtons.Empty();
118+
}

0 commit comments

Comments
 (0)