MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
ReactiveWidget.h
Go to the documentation of this file.
1/** @noop License Comment
2 * @file
3 * @copyright
4 * This Source Code is subject to the terms of the Mozilla Public License, v2.0.
5 * If a copy of the MPL was not distributed with this file You can obtain one at
6 * https://mozilla.org/MPL/2.0/
7 *
8 * @author David Mórász
9 * @date 2025
10 */
11
12#pragma once
13
14#include "CoreMinimal.h"
15#include "Mcro/Slate.h"
16#include "Mcro/AssertMacros.h"
17#include "Mcro/Observable.h"
18#include "Mcro/Threading.h"
19#include "Mcro/Range.h"
20#include "Mcro/Range/Views.h"
22
23namespace Mcro::Slate
24{
25 using namespace Mcro::Observable;
26 using namespace Mcro::Error;
27 using namespace Mcro::Threading;
28 using namespace Mcro::Range;
29
30 namespace Detail
31 {
32 template <
33 typename Item,
34 CRangeMember Range,
35 CWidgetWithSlots ContainerWidget,
36 CWidget ChildWidget,
37 CRangeMember ChildrenRange,
38 typename IndexType
39 >
40 class TReactiveWidgetBase : public SCompoundWidget
41 {
42 public:
43 using StateRangeType = Range;
44 using ChildrenRangeType = ChildrenRange;
46
48 TSharedRef<ContainerWidget> const& container,
49 Item const& from,
50 IndexType const& at
51 )>;
52 using FUpdateChild = TDelegate<void(
53 TSharedRef<ChildWidget> const& child,
54 Item const& from,
55 IndexType const& at
56 )>;
57 using FRemoveChild = TDelegate<void(
58 TSharedRef<ContainerWidget> const& container,
59 TSharedRef<ChildWidget> const& child,
60 IndexType const& at
61 )>;
62
63 protected:
64
69 TSharedPtr<ContainerWidget> Container;
70 ChildrenRange Children;
71 virtual void OnStateChange(Range const& next) = 0;
72
73 static void DefaultRemoveChild(FRemoveChild& delegate)
74 {
75 if constexpr (requires(ContainerWidget& container, TSharedRef<SWidget> child)
76 {
77 container.RemoveSlot(child);
78 })
79 {
80 delegate = FRemoveChild::CreateLambda([](
81 TSharedRef<ContainerWidget> const& container,
82 TSharedRef<ChildWidget> const& child, int32 at
83 ) {
84 container->RemoveSlot(child);
85 });
86 }
87 }
88
89 template <CWidgetArguments ThisArguments>
90 requires requires(ThisArguments& args)
91 {
92 { args._State } -> CSameAsDecayed< IStatePtr<Range> >;
93 { args._Container } -> CSameAsDecayed< TSharedPtr<ContainerWidget> >;
94 { args._CreateChild } -> CSameAsDecayed< FCreateChild >;
95 { args._UpdateChild } -> CSameAsDecayed< FUpdateChild >;
96 { args._RemoveChild } -> CSameAsDecayed< FRemoveChild >;
97 }
98 void ConstructBase(ThisArguments const& args)
99 {
100 ASSERT_CRASH(args._Container);
101 ASSERT_CRASH(args._State);
102 ASSERT_CRASH(args._CreateChild.IsBound());
103 ASSERT_CRASH(args._RemoveChild.IsBound());
104
105 Container = args._Container;
106 State = args._State.ToWeakPtr();
107 CreateChild = args._CreateChild;
108 UpdateChild = args._UpdateChild;
109 RemoveChild = args._RemoveChild;
110
111 ChildSlot[Container.ToSharedRef()];
112
113 args._State->OnChange(this, [this](Range const& next)
114 {
115 if (IsInGameThread()) OnStateChange(next);
116 else
117 {
118 RunInGameThread(WeakSelf(this), [this]
119 {
120 if (auto state = State.Pin())
121 {
122 auto [value, lock] = state->GetOnAnyThread();
123 OnStateChange(value);
124 }
125 });
126 }
127 });
128 }
129 };
130 }
131
132 /**
133 * @brief
134 * A widget template which can automatically handle changes in an input array state, with given delegates which
135 * tell this widget how children are supposed to be created, updated and removed.
136 *
137 * @tparam Item The type of the items which are transformed into child widgets.
138 * @tparam ContainerWidget The panel which provides the slots for the child widgets.
139 * @tparam ChildWidget The storage type of child widgets, it's fine to leave it SWidget
140 */
141 template <
142 typename Item,
143 CWidgetWithSlots ContainerWidget = SWidget,
144 CWidget ChildWidget = SWidget,
145 typename Base = Detail::TReactiveWidgetBase<
146 Item, TArray<Item>,
147 ContainerWidget, ChildWidget, TArray<TSharedRef<ChildWidget>>, int32
148 >
149 >
150 class TArrayReactiveWidget : public Base
151 {
152 public:
153 using FCreateChild = Base::FCreateChild;
154 using FUpdateChild = Base::FUpdateChild;
155 using FRemoveChild = Base::FRemoveChild;
156 using ContainerSlotArguments = Base::ContainerSlotArguments;
157
159 {
160 Base::DefaultRemoveChild(_RemoveChild);
161 }
163 SLATE_ARGUMENT(TSharedPtr<ContainerWidget>, Container);
167 SLATE_END_ARGS()
168
169 void Construct(FArguments const& args) { Base::ConstructBase(args); }
170
171 protected:
172 virtual void OnStateChange(Base::StateRangeType const& next) override
173 {
174 for (int i = 0; i < FMath::Max(next.Num(), Base::Children.Num()); ++i)
175 {
176 if (next.IsValidIndex(i) && Base::Children.IsValidIndex(i))
177 {
178 Base::UpdateChild.ExecuteIfBound(Base::Children[i], next[i], i);
179 continue;
180 }
181 if (next.IsValidIndex(i))
182 {
183 typename ContainerWidget::FSlot* newSlot = nullptr;
184 Base::CreateChild.Execute(Base::Container, next[i], i).Expose(newSlot);
185 Base::Children.Add(StaticCastSharedRef<ChildWidget>(newSlot->GetWidget()));
186 continue;
187 }
188 if (Base::Children.IsValidIndex(i))
189 {
190 Base::RemoveChild.Execute(Base::Container, Base::Children[i], i);
191 }
192 }
193 if (Base::Children.Num() > next.Num()) Base::Children.SetNum(next.Num());
194 }
195 };
196
197 /**
198 * @brief
199 * A widget template which can automatically handle changes in an input map state, with given delegates which
200 * tell this widget how children are supposed to be created, updated and removed.
201 *
202 * @tparam Key The key associated with items.
203 * @tparam Item The type of the items which are transformed into child widgets.
204 * @tparam ContainerWidget The panel which provides the slots for the child widgets.
205 * @tparam ChildWidget The storage type of child widgets, it's fine to leave it SWidget
206 */
207 template <
208 typename Key, typename Item,
209 CWidget ContainerWidget = SWidget,
210 CWidget ChildWidget = SWidget,
211 typename Base = Detail::TReactiveWidgetBase<
212 Item, TMap<Key, Item>,
213 ContainerWidget, ChildWidget, TMap<Key, TSharedRef<ChildWidget>>, Key
214 >
215 >
216 class TMapReactiveWidget : public Base
217 {
218 public:
219 using FCreateChild = Base::FCreateChild;
220 using FUpdateChild = Base::FUpdateChild;
221 using FRemoveChild = Base::FRemoveChild;
222
224 {
225 Base::DefaultRemoveChild(_RemoveChild);
226 }
228 SLATE_ARGUMENT(TSharedPtr<ContainerWidget>, Container);
232 SLATE_END_ARGS()
233
234 void Construct(FArguments const& args) { Base::ConstructBase(args); }
235
236 protected:
237 virtual void OnStateChange(Base::StateRangeType const& next) override
238 {
239 auto update = next
240 | FilterTuple([this](Key const& key, Item const& value)
241 {
242 return Base::Children.Contains(key);
243 })
244 | GetKeys()
245 ;
246 for (Key const& updating : update)
247 {
248 Base::UpdateChild.ExecuteIfBound(Base::Children[updating], next[updating], updating);
249 }
250 auto dismiss = Base::Children
251 | FilterTuple([&](Key const& key, Item const& value)
252 {
253 return !next.Contains(key);
254 })
255 | GetKeys()
257 ;
258 for (Key const& dismissing : dismiss)
259 {
260 Base::RemoveChild.Execute(Base::Container, Base::Children[dismissing], dismissing);
261 Base::Children.Remove(dismissing);
262 }
263 auto add = next
264 | FilterTuple([this](Key const& key, Item const& value)
265 {
266 return !Base::Children.Contains(key);
267 })
268 | GetKeys()
269 ;
270 for (Key const& adding : add)
271 {
272 typename ContainerWidget::FSlot* newSlot = nullptr;
273 Base::CreateChild.Execute(Base::Container, next[adding], adding).Expose(newSlot);
274 Base::Children.Add(adding, StaticCastSharedRef<ChildWidget>(newSlot->GetWidget()));
275 }
276 }
277 };
278}
#define ASSERT_CRASH(condition,...)
Use this instead of check macro if the checked expression shouldn't be ignored in shipping builds....
Bring modern declarative range operations like views and actions to the Unreal C++ arsenal....
Render a range as the given container.
Definition Conversion.h:277
void ConstructBase(ThisArguments const &args)
TDelegate< void( TSharedRef< ChildWidget > const &child, Item const &from, IndexType const &at)> FUpdateChild
TSharedPtr< ContainerWidget > Container
static void DefaultRemoveChild(FRemoveChild &delegate)
virtual void OnStateChange(Range const &next)=0
TArgumentsOf< typename ContainerWidget::FSlot > ContainerSlotArguments
TDelegate< void( TSharedRef< ContainerWidget > const &container, TSharedRef< ChildWidget > const &child, IndexType const &at)> FRemoveChild
TDelegate< ContainerSlotArguments( TSharedRef< ContainerWidget > const &container, Item const &from, IndexType const &at)> FCreateChild
A widget template which can automatically handle changes in an input array state, with given delegate...
SLATE_EVENT(FUpdateChild, UpdateChild)
SLATE_EVENT(FCreateChild, CreateChild)
SLATE_EVENT(FRemoveChild, RemoveChild)
virtual void OnStateChange(Base::StateRangeType const &next) override
SLATE_ARGUMENT(TSharedPtr< ContainerWidget >, Container)
Base::ContainerSlotArguments ContainerSlotArguments
SLATE_ARGUMENT(IStatePtr< Base::StateRangeType >, State)
SLATE_BEGIN_ARGS(TArrayReactiveWidget)
A widget template which can automatically handle changes in an input map state, with given delegates ...
SLATE_EVENT(FRemoveChild, RemoveChild)
virtual void OnStateChange(Base::StateRangeType const &next) override
SLATE_ARGUMENT(TSharedPtr< ContainerWidget >, Container)
SLATE_ARGUMENT(IStatePtr< Base::StateRangeType >, State)
SLATE_BEGIN_ARGS(TMapReactiveWidget)
SLATE_EVENT(FCreateChild, CreateChild)
SLATE_EVENT(FUpdateChild, UpdateChild)
Constraining given type to a widget which can receive slots.
Definition Slate.h:62
Constraining given type to a Slate widget.
Definition Slate.h:33
Contains utilities for structured error handling.
Definition Error.Fwd.h:19
TSharedPtr< IState< T > > IStatePtr
Convenience alias for shared pointer to a base type of TState. Use this in APIs which may modify or g...
TWeakPtr< IState< T > > IStateWeakPtr
Convenience alias for weak pointer to a base type of TState. Use this in APIs which may modify or get...
FORCEINLINE auto GetKeys()
Definition Views.h:346
auto FilterTuple(Left &&left, Predicate &&predicate)
Filter a range of tuples with structured binding function arguments, so filter predicates shouldn't b...
Definition Views.h:285
auto WeakSelf(const T *self) -> TWeakPtr< T const, Mode >
Same as SharedThis(this) in TSharedFromThis but returning a weak pointer instead.
Extra functionalities for general Slate programming chores, including enhancements of the Slate decla...
Definition Slate.h:28
typename TArgumentsOf_Struct< T >::Type TArgumentsOf
Get the type of arguments from either a widget or a slot type (FArguments or FSlotArguments)
Definition Slate.h:90
MCRO_API void RunInGameThread(TUniqueFunction< void()> &&func)
Simply run a lambda function on the game thread but only use AsyncTask if it's not on the game thread...