A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
SDialogueTextPropertyPickList.cpp
Go to the documentation of this file.
1// Copyright Csaba Molnar, Daniel Butum. All Rights Reserved.
3
4#include "PropertyHandle.h"
5#include "Widgets/Input/SSearchBox.h"
6#include "Framework/Application/SlateApplication.h"
7#include "DetailWidgetRow.h"
8#include "IDocumentation.h"
9#include "Layout/WidgetPath.h"
10
11#define LOCTEXT_NAMESPACE "SDialogueTextPropertyPickList"
12
14// SDialogueTextPropertyPickList
15void SDialogueTextPropertyPickList::Construct(const FArguments& InArgs)
16{
17 // MUST call SetPropertyHandle later or a check will fail.
18 if (InArgs._PropertyHandle.IsValid())
19 {
20 SetPropertyHandle(InArgs._PropertyHandle);
21 }
22 SetToolTipAttribute(InArgs._ToolTipText);
23
24 // Context checkbox arguments
25 bHasContextCheckBox = InArgs._HasContextCheckbox;
26 bIsContextCheckBoxChecked = bHasContextCheckBox ? InArgs._IsContextCheckBoxChecked : false;
27 CurrentContextSuggestionAttributes = InArgs._CurrentContextAvailableSuggestions;
28 ContextCheckBoxTextAttribute = InArgs._ContextCheckBoxText;
29 ContextCheckBoxToolTipTextAttribute = InArgs._ContextCheckBoxToolTipText;
30
31 HintTextAttribute = InArgs._HintText;
32 SuggestionAttributes = InArgs._AvailableSuggestions;
33 OnTextChanged = InArgs._OnTextChanged;
34 OnTextCommitted = InArgs._OnTextCommitted;
35 OnKeyDownHandler = InArgs._OnKeyDownHandler;
36 bDelayChangeNotificationsWhileTyping = InArgs._DelayChangeNotificationsWhileTyping;
37
38 // Assign the main horizontal box of this widget
39 TSharedPtr<SHorizontalBox> ContentBox = SNew(SHorizontalBox);
40 ChildSlot
41 [
42 ContentBox.ToSharedRef()
43 ];
44
45 // right of the combo box
46// const TSharedRef<SHorizontalBox> ButtonBox = SNew(SHorizontalBox);
47// TSharedRef<SWidget> ButtonBoxWrapper =
48// SNew(SBox)
49// .Padding(FMargin(1.f, 0.f))
50// [
51// ButtonBox
52// ];
53
54 // TODO maybe have a look at SNameComboBox and SComboBox
55 // Build the button and text view
56 ComboButtonWidget = SNew(SComboButton)
57 .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle")
58 .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity"))
59 .OnGetMenuContent(this, &Self::GetMenuWidget)
60 .OnMenuOpenChanged(this, &Self::HandleMenuOpenChanged)
61 .OnComboBoxOpened(this, &Self::HandleComboBoxOpened)
62 .IsEnabled(true)
63 .IsFocusable(false) // if set to true, it won't select the search input box
64 .ContentPadding(2.0f)
65 .ButtonContent()
66 [
67 // Show the name of the asset or actor
68 SAssignNew(ComboButtonTextWidget, STextBlock)
69 .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass")
70 .Text(TextAttribute)
71 ];
72
73 ContentBox->AddSlot()
74 [
75 SNew(SHorizontalBox)
76 +SHorizontalBox::Slot()
77 [
78 ComboButtonWidget.ToSharedRef()
79 ]
80
81 // +SHorizontalBox::Slot()
82 // .AutoWidth()
83 // [
84 // ButtonBoxWrapper
85 // ]
86 ];
87
88// ButtonBoxWrapper->SetVisibility(ButtonBox->NumSlots() > 0 ? EVisibility::Visible : EVisibility::Collapsed);
89}
90
91FReply SDialogueTextPropertyPickList::OnPreviewKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
92{
93 if (InKeyEvent.GetKey() == EKeys::Escape)
94 {
95 // Clear any selection first to prevent the currently selection being set in the text box
96 ListViewWidget->ClearSelection();
97 return FReply::Handled();
98 }
99
100 return FReply::Unhandled();
101}
102
103void SDialogueTextPropertyPickList::SetText(const TAttribute<FText>& InNewText)
104{
105 const FText NewText = InNewText.Get();
106 TextAttribute.Set(NewText);
107
108 PropertyHandle->SetValueFromFormattedString(*NewText.ToString());
109 if (ComboButtonTextWidget.IsValid())
110 {
112 }
113 if (InputTextWidget.IsValid())
114 {
116 }
117}
118
119void SDialogueTextPropertyPickList::SetToolTipAttribute(const TAttribute<FText>& InNewText)
120{
121 const FText NewText = InNewText.Get();
122 ToolTipAttribute.Set(NewText);
123 SetToolTipText(ToolTipAttribute);
124}
125
126void SDialogueTextPropertyPickList::SetPropertyHandle(const TSharedPtr<IPropertyHandle>& InPropertyHandle)
127{
128 PropertyHandle = InPropertyHandle;
129 check(PropertyHandle.IsValid());
130
131 // Read the initial value of the text this widget belongs to
132 FText ReadData;
133 if (PropertyHandle->GetValueAsFormattedText(ReadData) == FPropertyAccess::Success)
134 {
135 SetText(ReadData);
136 }
137}
138
140{
141 // Is it cached?
142 if (MenuWidget.IsValid())
143 {
144 return SNew(SVerticalBox)
145 +SVerticalBox::Slot()
146 .AutoHeight()
147 .Padding(2.f, 2.f, 2.f, 5.f)
148 [
149 MenuWidget.ToSharedRef()
150 ];
151 }
152
153 // Cache it
154 MenuWidget = SNew(SVerticalBox);
155
156 // Context Sensitive widget
158 {
159 MenuWidget->AddSlot()
160 .AutoHeight()
161 .Padding(0.f, 0.f, 0.f, 1.f)
162 [
164 ];
165 }
166
167 // Search Box
168 MenuWidget->AddSlot()
169 .AutoHeight()
170 .Padding(0.f, 0.f, 0.f, 1.f)
171 [
172 SNew(STextBlock)
173 .Text(LOCTEXT("SearchHeader", "Search or Set new text (press enter)"))
174 .TextStyle(FCoreStyle::Get(), TEXT("Menu.Heading"))
175 ];
176
177 MenuWidget->AddSlot()
178 .AutoHeight()
179 .Padding(0, 0, 0, 1)
180 [
182 ];
183
184 // List view widget
185 MenuWidget->AddSlot()
186 .FillHeight(1.f)
187 .Padding(1.f, 2.f)
188 [
190 ];
191
192 // Should return a valid widget
193 return GetMenuWidget();
194}
195
197{
198 // It is cached?
199 if (ContextCheckBoxWidget.IsValid())
200 {
201 return SNew(SHorizontalBox)
202
203 // Context Toggle
204 +SHorizontalBox::Slot()
205 .HAlign(HAlign_Right)
206 .VAlign(VAlign_Center)
207 [
208 ContextCheckBoxWidget.ToSharedRef()
209 ];
210 }
211
212 // Cache it
213 ContextCheckBoxWidget = SNew(SCheckBox)
214 .OnCheckStateChanged(this, &Self::HandleContextCheckboxChanged)
215 .IsFocusable(false)
216 .IsChecked(this, &Self::IsContextCheckBoxChecked)
218 [
219 SNew(STextBlock)
221 ];
222
223 // Should return a valid widget
225}
226
228{
229 // Is it cached?
230 if (InputTextWidget.IsValid())
231 {
232 return SNew(SHorizontalBox)
233 +SHorizontalBox::Slot()
234 .FillWidth(1.0f)
235 [
236 InputTextWidget.ToSharedRef()
237 ];
238 }
239
240 // Cache it
241 InputTextWidget = SNew(SSearchBox)
242 .InitialText(TextAttribute)
243 .HintText(HintTextAttribute)
244 .OnTextChanged(this, &Self::HandleTextChanged)
245 .OnTextCommitted(this, &Self::HandleTextCommitted)
246 .SelectAllTextWhenFocused(true)
247 .DelayChangeNotificationsWhileTyping(bDelayChangeNotificationsWhileTyping)
248 .OnKeyDownHandler(this, &Self::HandleKeyDown);
249
250 // Should return a valid widget
251 return GetSearchBoxWidget();
252}
253
255{
256 // Is it cached?
257 if (ListViewContainerWidget.IsValid())
258 {
259 return SNew(SOverlay)
260 +SOverlay::Slot()
261 [
262 ListViewContainerWidget.ToSharedRef()
263 ];
264 }
265
266 // Cache it
268 .Padding(0)
269 .BorderImage(FEditorStyle::GetBrush("NoBorder"));
270
271 ListViewWidget = SNew(SListView<TextListItem>)
272 .SelectionMode(ESelectionMode::Single)
273 .ListItemsSource(&Suggestions)
274 .OnGenerateRow(this, &Self::HandleListGenerateRow)
275 .OnSelectionChanged(this, &Self::HandleListSelectionChanged)
276 .ItemHeight(20);
277
279
280 // Should return a valid widget
281 return GetListViewWidget();
282}
283
284TSharedRef<ITableRow> SDialogueTextPropertyPickList::HandleListGenerateRow(TextListItem Text, const TSharedRef<STableViewBase>& OwnerTable)
285{
286 check(Text.IsValid());
287 return SNew(STableRow<TextListItem>, OwnerTable)
288 [
289 SNew(STextBlock)
290 .Text(FText::FromName(*Text.Get()))
291 .HighlightText(this, &Self::GetHighlightText)
292 ];
293}
294
296{
297 if (!bOpen)
298 {
299 FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::SetDirectly);
300 ComboButtonWidget->SetMenuContent(SNullWidget::NullWidget);
301 }
302}
303
310
313{
315
316 // Check if in suggestion list
317 OnTextChanged.ExecuteIfBound(InSearchText);
318}
319
321void SDialogueTextPropertyPickList::HandleTextCommitted(const FText& NewText, ETextCommit::Type CommitType)
322{
323 // Ignore default
324 if (CommitType == ETextCommit::Default)
325 {
326 return;
327 }
328
329 TSharedPtr<FName> SelectedSuggestion = GetSelectedSuggestion();
330 FText CommittedText;
331 if (SelectedSuggestion.IsValid() && CommitType != ETextCommit::OnCleared)
332 {
333 // Pressed selected a suggestion, set the text
334 CommittedText = FText::FromName(*SelectedSuggestion.Get());
335 }
336 else
337 {
338 if (CommitType == ETextCommit::OnCleared)
339 {
340 // Clear text when escape is pressed then commit an empty string
341 CommittedText = FText::GetEmpty();
342 }
343 else
344 {
345 // otherwise, set the typed text
346 CommittedText = NewText;
347 }
348 }
349
350 // Use "None" as empty string
351 if (CommittedText.IdenticalTo(FText::GetEmpty()))
352 {
353 CommittedText = FText::FromName(NAME_None);
354 }
355
356 // Update the displayed text
357 SetText(CommittedText);
358 OnTextCommitted.ExecuteIfBound(CommittedText, CommitType);
359
360 // Only close the menu when the user did not loose focus
361 if (CommitType != ETextCommit::OnUserMovedFocus)
362 {
363 ComboButtonWidget->SetIsOpen(false);
364 }
365}
366
367FReply SDialogueTextPropertyPickList::HandleKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
368{
369 if (InKeyEvent.GetKey() == EKeys::Up || InKeyEvent.GetKey() == EKeys::Down)
370 {
371 const bool bSelectingUp = InKeyEvent.GetKey() == EKeys::Up;
372 TSharedPtr<FName> SelectedSuggestion = GetSelectedSuggestion();
373
374 if (SelectedSuggestion.IsValid())
375 {
376 // Find the selection index and select the previous or next one
377 int32 TargetIdx = INDEX_NONE;
378 for (int32 SuggestionIdx = 0; SuggestionIdx < Suggestions.Num(); ++SuggestionIdx)
379 {
380 if (Suggestions[SuggestionIdx] == SelectedSuggestion)
381 {
382 if (bSelectingUp)
383 {
384 TargetIdx = SuggestionIdx - 1;
385 }
386 else
387 {
388 TargetIdx = SuggestionIdx + 1;
389 }
390
391 break;
392 }
393 }
394
395 if (Suggestions.IsValidIndex(TargetIdx))
396 {
397 ListViewWidget->SetSelection(Suggestions[TargetIdx]);
398 ListViewWidget->RequestScrollIntoView(Suggestions[TargetIdx]);
399 }
400 }
401 else if (!bSelectingUp && Suggestions.Num() > 0)
402 {
403 // Nothing selected and pressed down, select the first item
404 ListViewWidget->SetSelection(Suggestions[0]);
405 }
406
407 return FReply::Handled();
408 }
409
410 if (OnKeyDownHandler.IsBound())
411 {
412 return OnKeyDownHandler.Execute(MyGeometry, InKeyEvent);
413 }
414
415 return FReply::Unhandled();
416}
417
419{
420 // If the user selected it via click or keyboard then select it, then accept the choice and close the window
421 if (SelectInfo == ESelectInfo::OnMouseClick || SelectInfo == ESelectInfo::OnKeyPress || SelectInfo == ESelectInfo::Direct )
422 {
423 if (NewValue.IsValid())
424 {
425 SetText(FText::FromName(*NewValue.Get()));
427 }
428 else
429 {
430 // Can happen in the case selecting the option directly (SelectInfo == ESelectInfo::Direct)
431 // HandleTextCommitted will be called automatically because it looses focus, but we want
432 // to close the menu explicitly
433 ComboButtonWidget->SetIsOpen(false);
434 }
435 }
436}
437
439{
440 bIsContextCheckBoxChecked = CheckState == ECheckBoxState::Checked;
442 // Return focus to search input so it is easier to navigate down.
444}
445
447{
448 const FString TypedText = InputTextWidget.IsValid() ? InputTextWidget->GetText().ToString() : TEXT("");
449 Suggestions.Empty();
450
451 // Find out what pool of suggestions ot use
452 TArray<FName> AllSuggestions;
454 {
455 // has checkbox and it is true
456 AllSuggestions = CurrentContextSuggestionAttributes.Get();
457 }
458 else
459 {
460 // default
461 AllSuggestions = SuggestionAttributes.Get();
462 }
463
464 // Must have typed something, but that something must be different than the set value
465 if (TypedText.Len() > 0 && TypedText != TextAttribute.Get().ToString())
466 {
467 // Match typed text
468 for (const FName& Suggestion : AllSuggestions)
469 {
470 if (Suggestion.ToString().Contains(TypedText))
471 {
472 Suggestions.Add(MakeShared<FName>(Suggestion));
473 }
474 }
475 }
476 else
477 {
478 // Copy all
479 for (const FName& Suggestion : AllSuggestions)
480 {
481 Suggestions.Add(MakeShared<FName>(Suggestion));
482 }
483 }
484
485 if (ListViewWidget.IsValid())
486 {
487 ListViewWidget->RequestListRefresh();
488 }
489}
490
492{
493 FWidgetPath WidgetToFocusPath;
494 FSlateApplication::Get().GeneratePathToWidgetChecked(InputTextWidget.ToSharedRef(), WidgetToFocusPath);
495 FSlateApplication::Get().SetKeyboardFocus(WidgetToFocusPath, EFocusCause::SetDirectly);
496}
497
499{
500 TSharedPtr<FName> SelectedSuggestion;
501 const TArray<TSharedPtr<FName>>& SelectedSuggestionList = ListViewWidget->GetSelectedItems();
502 if (SelectedSuggestionList.Num() > 0)
503 {
504 // Selection mode is Single, so there should only be one suggestion at the most
505 check(SelectedSuggestionList.Num() == 1)
506 SelectedSuggestion = SelectedSuggestionList[0];
507 }
508
509 return SelectedSuggestion;
510}
511
512
514#undef LOCTEXT_NAMESPACE
void HandleTextChanged(const FText &InSearchText)
TAttribute< TArray< FName > > CurrentContextSuggestionAttributes
void SetText(const TAttribute< FText > &InNewText)
void HandleListSelectionChanged(TextListItem NewValue, ESelectInfo::Type SelectInfo)
TSharedPtr< SListView< TextListItem > > ListViewWidget
TSharedPtr< IPropertyHandle > PropertyHandle
TSharedRef< ITableRow > HandleListGenerateRow(TextListItem Text, const TSharedRef< STableViewBase > &OwnerTable)
FReply OnPreviewKeyDown(const FGeometry &MyGeometry, const FKeyEvent &InKeyEvent) override
void HandleTextCommitted(const FText &InSearchText, ETextCommit::Type CommitInfo)
void SetPropertyHandle(const TSharedPtr< IPropertyHandle > &InPropertyHandle)
void Construct(const FArguments &InArgs)
TAttribute< TArray< FName > > SuggestionAttributes
TSharedRef< SWidget > CreateShadowOverlay(TSharedRef< STableViewBase > Table) const
void HandleContextCheckboxChanged(ECheckBoxState CheckState)
FReply HandleKeyDown(const FGeometry &MyGeometry, const FKeyEvent &InKeyEvent)
void SetToolTipAttribute(const TAttribute< FText > &InNewText)