SWaapiPicker.cpp 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998
  1. /*******************************************************************************
  2. The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
  3. Technology released in source code form as part of the game integration package.
  4. The content of this file may not be used without valid licenses to the
  5. AUDIOKINETIC Wwise Technology.
  6. Note that the use of the game engine is subject to the Unreal(R) Engine End User
  7. License Agreement at https://www.unrealengine.com/en-US/eula/unreal
  8. License Usage
  9. Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
  10. this file in accordance with the end user license agreement provided with the
  11. software or, alternatively, in accordance with the terms contained
  12. in a written agreement between you and Audiokinetic Inc.
  13. Copyright (c) 2023 Audiokinetic Inc.
  14. *******************************************************************************/
  15. /*------------------------------------------------------------------------------------
  16. SWaapiPicker.cpp
  17. ------------------------------------------------------------------------------------*/
  18. /*------------------------------------------------------------------------------------
  19. includes.
  20. ------------------------------------------------------------------------------------*/
  21. #include "WaapiPicker/SWaapiPicker.h"
  22. #include "WaapiPicker/SWaapiPickerRow.h"
  23. #include "WaapiPicker/WaapiPickerViewCommands.h"
  24. #include "AkWaapiUtils.h"
  25. #include "AkAudioStyle.h"
  26. #include "AkSettings.h"
  27. #include "AkSettingsPerUser.h"
  28. #include "AkAudioDevice.h"
  29. #include "AkUnrealHelper.h"
  30. #include "Widgets/Images/SImage.h"
  31. #include "Widgets/Input/SHyperlink.h"
  32. #include "Widgets/Input/SButton.h"
  33. #include "Widgets/Input/SSearchBox.h"
  34. #include "Widgets/Layout/SSpacer.h"
  35. #include "Widgets/Layout/SSeparator.h"
  36. #include "Widgets/Text/SInlineEditableTextBlock.h"
  37. #include "Framework/Application/SlateApplication.h"
  38. #include "Framework/Commands/UICommandList.h"
  39. #include "Framework/Commands/GenericCommands.h"
  40. #include "Framework/MultiBox/MultiBoxBuilder.h"
  41. #include "Misc/ScopedSlowTask.h"
  42. #include "HAL/PlatformProcess.h"
  43. #include "Async/Async.h"
  44. #if WITH_EDITOR
  45. #include "DesktopPlatformModule.h"
  46. #include "Editor/UnrealEd/Public/EditorDirectories.h"
  47. #include "IDesktopPlatform.h"
  48. #endif
  49. /*------------------------------------------------------------------------------------
  50. Defines
  51. ------------------------------------------------------------------------------------*/
  52. #define LOCTEXT_NAMESPACE "AkAudio"
  53. DECLARE_CYCLE_STAT(TEXT("WaapiPicker - ConstructTree"), STAT_WaapiPickerConstructTree, STATGROUP_Audio);
  54. DECLARE_CYCLE_STAT(TEXT("WaapiPicker - TreeExpansionChanged"), STAT_WaapiPickerTreeExpansionChanged, STATGROUP_Audio);
  55. /*------------------------------------------------------------------------------------
  56. Statics and Globals
  57. ------------------------------------------------------------------------------------*/
  58. const FName SWaapiPicker::WaapiPickerTabName = FName("WaapiPicker");
  59. const FText SWaapiPicker::ModalWarning = LOCTEXT("WaapiModalOpened", "Wwise currently has a modal window opened. Please close it to use WAAPI functionality.");
  60. static inline void CallWaapiGetProjectNamePath(FString& ProjectName, FString& ProjectPath)
  61. {
  62. auto waapiClient = FAkWaapiClient::Get();
  63. if (!waapiClient)
  64. return;
  65. TSharedRef<FJsonObject> Args = MakeShared<FJsonObject>();
  66. {
  67. TSharedPtr<FJsonObject> OfType = MakeShared<FJsonObject>();
  68. OfType->SetArrayField(WwiseWaapiHelper::OF_TYPE, TArray<TSharedPtr<FJsonValue>> { MakeShared<FJsonValueString>(WwiseWaapiHelper::PROJECT) });
  69. Args->SetObjectField(WwiseWaapiHelper::FROM, OfType);
  70. }
  71. TSharedRef<FJsonObject> Options = MakeShared<FJsonObject>();
  72. {
  73. Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray<TSharedPtr<FJsonValue>>
  74. {
  75. MakeShared<FJsonValueString>(WwiseWaapiHelper::NAME),
  76. MakeShared<FJsonValueString>(WwiseWaapiHelper::FILEPATH),
  77. });
  78. }
  79. #if AK_SUPPORT_WAAPI
  80. TSharedPtr<FJsonObject> outJsonResult;
  81. if (waapiClient->Call(ak::wwise::core::object::get, Args, Options, outJsonResult))
  82. {
  83. // Recover the information from the Json object Result and use it to get the item id.
  84. TArray<TSharedPtr<FJsonValue>> StructJsonArray = outJsonResult->GetArrayField(WwiseWaapiHelper::RETURN);
  85. if (StructJsonArray.Num())
  86. {
  87. auto Path = StructJsonArray[0]->AsObject()->GetStringField(WwiseWaapiHelper::FILEPATH);
  88. ProjectPath = FPaths::GetPath(Path);
  89. ProjectName = FPaths::GetCleanFilename(Path);
  90. }
  91. else
  92. {
  93. UE_LOG(LogAkAudio, Log, TEXT("Unable to get the project name"));
  94. }
  95. }
  96. #endif
  97. }
  98. inline TSharedPtr<FWwiseTreeItem> SWaapiPicker::FindItemFromPath(const TSharedPtr<FWwiseTreeItem>& ParentItem, const FString& CurrentItemPath)
  99. {
  100. // We get the element to create in an array and loop over it to create them.
  101. TArray<FString> itemPathArray;
  102. CurrentItemPath.ParseIntoArray(itemPathArray, *WwiseWaapiHelper::BACK_SLASH);
  103. TSharedPtr<FWwiseTreeItem> PreviousItem = ParentItem;
  104. for (int i = 1; i < itemPathArray.Num(); i++)
  105. {
  106. TSharedPtr<FWwiseTreeItem> ChildItem = PreviousItem->GetChild(itemPathArray[i]);
  107. if (!ChildItem.IsValid())
  108. {
  109. return TSharedPtr<FWwiseTreeItem>(NULL);
  110. }
  111. PreviousItem = ChildItem;
  112. }
  113. return PreviousItem;
  114. }
  115. inline void SWaapiPicker::FindAndCreateItems(TSharedPtr<FWwiseTreeItem> CurrentItem)
  116. {
  117. LastExpandedItems.Add(CurrentItem->ItemId);
  118. FString LastPathVisited = CurrentItem->FolderPath;
  119. LastPathVisited.RemoveFromEnd(WwiseWaapiHelper::BACK_SLASH + CurrentItem->DisplayName);
  120. TSharedPtr<FWwiseTreeItem> RootItem = GetRootItem(CurrentItem->FolderPath);
  121. if (CurrentItem->FolderPath == RootItem->FolderPath)
  122. {
  123. return;
  124. }
  125. else if (LastPathVisited == RootItem->FolderPath)
  126. {
  127. CurrentItem->Parent = RootItem->Parent.Pin();
  128. RootItem->AddChild(CurrentItem);
  129. return;
  130. }
  131. TSharedPtr<FWwiseTreeItem> ParentItem = FindItemFromPath(RootItem, LastPathVisited);
  132. if (ParentItem.IsValid())
  133. {
  134. CurrentItem->Parent = ParentItem->Parent.Pin();
  135. ParentItem->AddChild(CurrentItem);
  136. }
  137. else
  138. {
  139. TSharedPtr<FJsonObject> Result;
  140. // Request data from Wwise UI using WAAPI and use them to create a Wwise tree item, getting the informations from a specific "PATH".
  141. if (CallWaapiGetInfoFrom(WwiseWaapiHelper::PATH, LastPathVisited, Result, {}))
  142. {
  143. // Recover the information from the Json object Result and use it to construct the tree item.
  144. TSharedPtr<FWwiseTreeItem> NewRootItem = ConstructWwiseTreeItem(Result->GetArrayField(WwiseWaapiHelper::RETURN)[0]);
  145. CurrentItem->Parent = NewRootItem;
  146. NewRootItem->AddChild(CurrentItem);
  147. FindAndCreateItems(NewRootItem);
  148. }
  149. else
  150. {
  151. UE_LOG(LogAkAudio, Log, TEXT("Failed to get information from path : %s"), *LastPathVisited);
  152. }
  153. }
  154. }
  155. inline TSharedPtr<FWwiseTreeItem> SWaapiPicker::GetRootItem(const FString& InFullPath)
  156. {
  157. for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType; ++i)
  158. {
  159. if (InFullPath.StartsWith(RootItems[i]->FolderPath))
  160. {
  161. return RootItems[i];
  162. }
  163. }
  164. return {};
  165. }
  166. bool SWaapiPicker::CallWaapiGetInfoFrom(const FString& inFromField, const FString& inFromString, TSharedPtr<FJsonObject>& outJsonResult, const TArray<TransformStringField>& TransformFields)
  167. {
  168. auto waapiClient = FAkWaapiClient::Get();
  169. if (!waapiClient)
  170. return false;
  171. #if AK_SUPPORT_WAAPI
  172. // Construct the arguments Json object : Getting infos "from - a specific id/path"
  173. TSharedRef<FJsonObject> Args = MakeShared<FJsonObject>();
  174. {
  175. TSharedPtr<FJsonObject> from = MakeShared<FJsonObject>();
  176. from->SetArrayField(inFromField, TArray<TSharedPtr<FJsonValue>> { MakeShared<FJsonValueString>(inFromString) });
  177. Args->SetObjectField(WwiseWaapiHelper::FROM, from);
  178. // In case we would recover the children of the object that have the id : ID or the path : PATH, then we set isGetChildren to true.
  179. if (TransformFields.Num())
  180. {
  181. TArray<TSharedPtr<FJsonValue>> transform;
  182. for (auto TransformValue : TransformFields)
  183. {
  184. TSharedPtr<FJsonObject> insideTransform = MakeShared<FJsonObject>();
  185. TArray<TSharedPtr<FJsonValue>> JsonArray;
  186. for (auto TransformStringValueArg : TransformValue.valueStringArgs)
  187. {
  188. JsonArray.Add(MakeShared<FJsonValueString>(TransformStringValueArg));
  189. }
  190. for (auto TransformNumberValueArg : TransformValue.valueNumberArgs)
  191. {
  192. JsonArray.Add(MakeShared<FJsonValueNumber>(TransformNumberValueArg));
  193. }
  194. insideTransform->SetArrayField(TransformValue.keyArg, JsonArray);
  195. transform.Add(MakeShared<FJsonValueObject>(insideTransform));
  196. }
  197. Args->SetArrayField(WwiseWaapiHelper::TRANSFORM, transform);
  198. }
  199. }
  200. // Construct the Options Json object : Getting specific infos to construct the wwise tree item "id - name - type - childrenCount - path - parent"
  201. TSharedRef<FJsonObject> Options = MakeShared<FJsonObject>();
  202. Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray<TSharedPtr<FJsonValue>>
  203. {
  204. MakeShared<FJsonValueString>(WwiseWaapiHelper::ID),
  205. MakeShared<FJsonValueString>(WwiseWaapiHelper::NAME),
  206. MakeShared<FJsonValueString>(WwiseWaapiHelper::TYPE),
  207. MakeShared<FJsonValueString>(WwiseWaapiHelper::CHILDREN_COUNT),
  208. MakeShared<FJsonValueString>(WwiseWaapiHelper::PATH),
  209. MakeShared<FJsonValueString>(WwiseWaapiHelper::WORKUNIT_TYPE),
  210. });
  211. // Request data from Wwise using WAAPI
  212. return waapiClient->Call(ak::wwise::core::object::get, Args, Options, outJsonResult);
  213. #endif
  214. return false;
  215. }
  216. TSharedPtr<FWwiseTreeItem> SWaapiPicker::ConstructWwiseTreeItem(const TSharedPtr<FJsonValue>& InJsonItem)
  217. {
  218. return ConstructWwiseTreeItem(InJsonItem->AsObject());
  219. }
  220. TSharedPtr<FWwiseTreeItem> SWaapiPicker::ConstructWwiseTreeItem(const TSharedPtr<FJsonObject>& ItemInfoObj)
  221. {
  222. static const FString ValidPaths[] = {
  223. EWwiseItemType::FolderNames[EWwiseItemType::Event],
  224. EWwiseItemType::FolderNames[EWwiseItemType::AuxBus],
  225. EWwiseItemType::FolderNames[EWwiseItemType::ActorMixer],
  226. EWwiseItemType::FolderNames[EWwiseItemType::GameParameter],
  227. EWwiseItemType::FolderNames[EWwiseItemType::State],
  228. EWwiseItemType::FolderNames[EWwiseItemType::Switch],
  229. EWwiseItemType::FolderNames[EWwiseItemType::Trigger],
  230. EWwiseItemType::FolderNames[EWwiseItemType::AcousticTexture],
  231. EWwiseItemType::FolderNames[EWwiseItemType::EffectShareSet]
  232. };
  233. static auto isValidPath = [](const FString& input, const auto& source) -> bool {
  234. for (const auto& item : source)
  235. {
  236. if (input.StartsWith(WwiseWaapiHelper::BACK_SLASH + item))
  237. {
  238. return true;
  239. }
  240. }
  241. return false;
  242. };
  243. const FString itemTypeString = ItemInfoObj->GetStringField(WwiseWaapiHelper::TYPE);
  244. auto itemType = EWwiseItemType::FromString(itemTypeString);
  245. if (itemType == EWwiseItemType::None)
  246. {
  247. return {};
  248. }
  249. const FString itemPath = ItemInfoObj->GetStringField(WwiseWaapiHelper::PATH);
  250. if (isValidPath(itemPath, ValidPaths))
  251. {
  252. const FString itemIdString = ItemInfoObj->GetStringField(WwiseWaapiHelper::ID);
  253. FGuid in_ItemId = FGuid::NewGuid();
  254. FGuid::ParseExact(itemIdString, EGuidFormats::DigitsWithHyphensInBraces, in_ItemId);
  255. const FString itemName = ItemInfoObj->GetStringField(WwiseWaapiHelper::NAME);
  256. if (itemName.IsEmpty())
  257. {
  258. return {};
  259. }
  260. const uint32_t ItemChildrenCount = ItemInfoObj->GetNumberField(WwiseWaapiHelper::CHILDREN_COUNT);
  261. if (itemType == EWwiseItemType::StandaloneWorkUnit)
  262. {
  263. FString WorkUnitType;
  264. if (ItemInfoObj->TryGetStringField(WwiseWaapiHelper::WORKUNIT_TYPE, WorkUnitType) && WorkUnitType == "FOLDER")
  265. {
  266. itemType = EWwiseItemType::PhysicalFolder;
  267. }
  268. }
  269. TSharedPtr<FWwiseTreeItem> treeItem = MakeShared<FWwiseTreeItem>(itemName, itemPath, nullptr, itemType, in_ItemId);
  270. if ((itemType != EWwiseItemType::Event) && (itemType != EWwiseItemType::Sound))
  271. {
  272. treeItem->ChildCountInWwise = ItemChildrenCount;
  273. }
  274. return treeItem;
  275. }
  276. return {};
  277. }
  278. /*------------------------------------------------------------------------------------
  279. Implementation
  280. ------------------------------------------------------------------------------------*/
  281. SWaapiPicker::SWaapiPicker() : CommandList(MakeShared<FUICommandList>())
  282. {
  283. AllowTreeViewDelegates = true;
  284. isPickerVisible = FAkWaapiClient::IsProjectLoaded();
  285. }
  286. void SWaapiPicker::RemoveClientCallbacks()
  287. {
  288. auto waapiClient = FAkWaapiClient::Get();
  289. if (waapiClient == nullptr)
  290. return;
  291. if (ProjectLoadedHandle.IsValid())
  292. {
  293. waapiClient->OnProjectLoaded.Remove(ProjectLoadedHandle);
  294. ProjectLoadedHandle.Reset();
  295. }
  296. if (ConnectionLostHandle.IsValid())
  297. {
  298. waapiClient->OnConnectionLost.Remove(ConnectionLostHandle);
  299. ConnectionLostHandle.Reset();
  300. }
  301. UnsubscribeWaapiCallbacks();
  302. }
  303. SWaapiPicker::~SWaapiPicker()
  304. {
  305. RootItems.Empty();
  306. RemoveClientCallbacks();
  307. if (auto waapiClient = FAkWaapiClient::Get())
  308. {
  309. waapiClient->OnClientBeginDestroy.Remove(ClientBeginDestroyHandle);
  310. }
  311. StopAndDestroyAllTransports();
  312. }
  313. void SWaapiPicker::Construct(const FArguments& InArgs)
  314. {
  315. OnDragDetected = InArgs._OnDragDetected;
  316. OnSelectionChanged = InArgs._OnSelectionChanged;
  317. OnGenerateSoundBanksClicked = InArgs._OnGenerateSoundBanksClicked;
  318. OnRefreshClicked = InArgs._OnRefreshClicked;
  319. OnImportWwiseAssetsClicked = InArgs._OnImportWwiseAssetsClicked;
  320. CallWaapiGetProjectNamePath(ProjectName, ProjectFolder);
  321. bRestrictContextMenu = InArgs._RestrictContextMenu;
  322. if (InArgs._FocusSearchBoxWhenOpened)
  323. {
  324. RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SWaapiPicker::SetFocusPostConstruct));
  325. }
  326. FGenericCommands::Register();
  327. FWaapiPickerViewCommands::Register();
  328. CreateWaapiPickerCommands();
  329. SearchBoxFilter = MakeShared<StringFilter>(StringFilter::FItemToStringArray::CreateSP(this, &SWaapiPicker::PopulateSearchStrings));
  330. SearchBoxFilter->OnChanged().AddSP(this, &SWaapiPicker::FilterUpdated);
  331. if (auto* settings = GetMutableDefault<UAkSettings>())
  332. {
  333. settings->bRequestRefresh = false;
  334. }
  335. ChildSlot
  336. [
  337. SNew(SBorder)
  338. .Padding(4)
  339. .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.GroupBorder"))
  340. [
  341. SNew(SOverlay)
  342. // Picker
  343. + SOverlay::Slot()
  344. .VAlign(VAlign_Fill)
  345. [
  346. SNew(SVerticalBox)
  347. .Visibility(this, &SWaapiPicker::isPickerAllowed)
  348. // Search
  349. + SVerticalBox::Slot()
  350. .AutoHeight()
  351. .Padding(0, 1, 0, 3)
  352. [
  353. SNew(SHorizontalBox)
  354. + SHorizontalBox::Slot()
  355. .AutoWidth()
  356. [
  357. InArgs._SearchContent.Widget
  358. ]
  359. + SHorizontalBox::Slot()
  360. .FillWidth(1.0f)
  361. [
  362. SAssignNew(SearchBoxPtr,SSearchBox)
  363. .HintText( LOCTEXT( "WaapiPickerSearchHint", "Search Wwise Item" ) )
  364. .ToolTipText(LOCTEXT("WaapiPickerSearchTooltip", "Type here to search for a Wwise asset"))
  365. .OnTextChanged( this, &SWaapiPicker::OnSearchBoxChanged )
  366. .SelectAllTextWhenFocused(false)
  367. .DelayChangeNotificationsWhileTyping(true)
  368. ]
  369. ]
  370. // Tree title
  371. +SVerticalBox::Slot()
  372. .AutoHeight()
  373. [
  374. SNew(SHorizontalBox)
  375. + SHorizontalBox::Slot()
  376. .AutoWidth()
  377. .Padding(3.0f)
  378. [
  379. SNew(SImage)
  380. .Image(FAkAudioStyle::GetBrush(EWwiseItemType::Project))
  381. ]
  382. + SHorizontalBox::Slot()
  383. .AutoWidth()
  384. .Padding(0,0,3,0)
  385. [
  386. SNew(STextBlock)
  387. .Font(FAkAudioStyle::GetFontStyle("ContentBrowser.SourceTitleFont") )
  388. .Text( this, &SWaapiPicker::GetProjectName )
  389. .Visibility(InArgs._ShowTreeTitle ? EVisibility::Visible : EVisibility::Collapsed)
  390. ]
  391. + SHorizontalBox::Slot()
  392. .FillWidth(1)
  393. [
  394. SNew( SSpacer )
  395. ]
  396. + SHorizontalBox::Slot()
  397. .AutoWidth()
  398. [
  399. SNew(SButton)
  400. .Text(LOCTEXT("AkPickerRefresh", "Refresh"))
  401. .OnClicked(this, &SWaapiPicker::OnRefreshButtonClicked)
  402. ]
  403. + SHorizontalBox::Slot()
  404. .AutoWidth()
  405. [
  406. SNew(SButton)
  407. .Text(LOCTEXT("AkPickerGenerateSoundData", "Generate SoundBanks..."))
  408. .OnClicked(this, &SWaapiPicker::OnGenerateSoundBanksButtonClicked)
  409. .Visibility(InArgs._ShowGenerateSoundBanksButton ? EVisibility::Visible : EVisibility::Collapsed)
  410. ]
  411. ]
  412. // Separator
  413. +SVerticalBox::Slot()
  414. .AutoHeight()
  415. .Padding(0, 0, 0, 1)
  416. [
  417. SNew(SSeparator)
  418. .Visibility( ( InArgs._ShowSeparator) ? EVisibility::Visible : EVisibility::Collapsed )
  419. ]
  420. // Tree
  421. +SVerticalBox::Slot()
  422. .FillHeight(1.f)
  423. [
  424. SAssignNew(TreeViewPtr, STreeView< TSharedPtr<FWwiseTreeItem> >)
  425. .TreeItemsSource(&RootItems)
  426. .OnGenerateRow( this, &SWaapiPicker::GenerateRow )
  427. //.OnItemScrolledIntoView( this, &SPathView::TreeItemScrolledIntoView )
  428. .ItemHeight(18)
  429. .SelectionMode(InArgs._SelectionMode)
  430. .OnSelectionChanged(this, &SWaapiPicker::TreeSelectionChanged)
  431. .OnExpansionChanged(this, &SWaapiPicker::TreeExpansionChanged)
  432. .OnGetChildren( this, &SWaapiPicker::GetChildrenForTree )
  433. .OnContextMenuOpening(this, &SWaapiPicker::MakeWaapiPickerContextMenu)
  434. .ClearSelectionOnClick(false)
  435. ]
  436. ]
  437. // Empty Picker
  438. + SOverlay::Slot()
  439. .VAlign(VAlign_Center)
  440. .HAlign(HAlign_Center)
  441. [
  442. SNew(SVerticalBox)
  443. + SVerticalBox::Slot()
  444. .VAlign(VAlign_Center)
  445. .HAlign(HAlign_Center)
  446. .AutoHeight()
  447. [
  448. SNew(STextBlock)
  449. .Visibility(this, &SWaapiPicker::isWarningVisible)
  450. .AutoWrapText(true)
  451. .Justification(ETextJustify::Center)
  452. .Text(this, &SWaapiPicker::GetWarningText)
  453. ]
  454. + SVerticalBox::Slot()
  455. .VAlign(VAlign_Center)
  456. .HAlign(HAlign_Center)
  457. .AutoHeight()
  458. [
  459. SNew(SHyperlink)
  460. .Visibility(this, &SWaapiPicker::isWarningVisible)
  461. .Text(LOCTEXT("WaapiDucumentation", "For more informaton, please Visit Waapi Documentation."))
  462. .ToolTipText(LOCTEXT("WaapiDucumentationTooltip", "Opens Waapi documentation in a new browser window"))
  463. .OnNavigate_Lambda([] { FPlatformProcess::LaunchURL(*FString("https://www.audiokinetic.com/library/?source=SDK&id=waapi.html"), nullptr, nullptr); })
  464. ]
  465. ]
  466. ]
  467. ];
  468. ConstructTree();
  469. ExpandFirstLevel();
  470. auto waapiClient = FAkWaapiClient::Get();
  471. if (!waapiClient)
  472. return;
  473. ProjectLoadedHandle = waapiClient->OnProjectLoaded.AddSP(this, &SWaapiPicker::OnProjectLoadedCallback);
  474. ConnectionLostHandle = waapiClient->OnConnectionLost.AddSP(this, &SWaapiPicker::OnConnectionLostCallback);
  475. ClientBeginDestroyHandle = waapiClient->OnClientBeginDestroy.AddSP(this, &SWaapiPicker::RemoveClientCallbacks);
  476. SubscribeWaapiCallbacks();
  477. }
  478. void SWaapiPicker::OnProjectLoadedCallback()
  479. {
  480. /* Construct the tree when we have the same project */
  481. isPickerVisible = true;
  482. isModalActiveInWwise = false;
  483. SubscribeWaapiCallbacks();
  484. CallWaapiGetProjectNamePath(ProjectName, ProjectFolder);
  485. ConstructTree();
  486. }
  487. void SWaapiPicker::OnConnectionLostCallback()
  488. {
  489. /* Empty the tree when we have different projects */
  490. isPickerVisible = false;
  491. UnsubscribeWaapiCallbacks();
  492. ConstructTree();
  493. }
  494. EVisibility SWaapiPicker::isPickerAllowed() const
  495. {
  496. return (isPickerVisible && !isModalActiveInWwise) ? EVisibility::Visible : EVisibility::Hidden;
  497. }
  498. EVisibility SWaapiPicker::isWarningVisible() const
  499. {
  500. return (isPickerVisible && !isModalActiveInWwise) ? EVisibility::Hidden : EVisibility::Visible;
  501. }
  502. FText SWaapiPicker::GetWarningText() const
  503. {
  504. const FText NotConnected = LOCTEXT("EmptyWaapiTree", "Could not establish a WAAPI connection; WAAPI picker is disabled. Please enable WAAPI in your Wwise settings, or use the Wwise Picker.");
  505. return isModalActiveInWwise ? ModalWarning : NotConnected;
  506. }
  507. void SWaapiPicker::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
  508. {
  509. auto* waapiClient = FAkWaapiClient::Get();
  510. auto AkSettings = GetMutableDefault<UAkSettings>();
  511. bool NeedRefresh = AkSettings->bRequestRefresh;
  512. if (isModalActiveInWwise && waapiClient)
  513. {
  514. #if AK_SUPPORT_WAAPI
  515. TSharedRef<FJsonObject> Args = MakeShareable(new FJsonObject());
  516. TSharedRef<FJsonObject> Options = MakeShareable(new FJsonObject());
  517. TSharedPtr<FJsonObject> Result = MakeShareable(new FJsonObject());
  518. waapiClient->Call(ak::wwise::core::getInfo, Args, Options, Result, 10, true);
  519. if (Result->GetStringField(TEXT("uri")) != TEXT("ak.wwise.locked"))
  520. {
  521. NeedRefresh = true;
  522. isModalActiveInWwise = false;
  523. }
  524. #endif
  525. }
  526. if (NeedRefresh)
  527. {
  528. ConstructTree();
  529. AkSettings->bRequestRefresh = false;
  530. }
  531. }
  532. FText SWaapiPicker::GetProjectName() const
  533. {
  534. return FText::FromString(ProjectName);
  535. }
  536. FReply SWaapiPicker::OnRefreshButtonClicked()
  537. {
  538. ConstructTree();
  539. OnRefreshClicked.ExecuteIfBound();
  540. return FReply::Handled();
  541. }
  542. FReply SWaapiPicker::OnGenerateSoundBanksButtonClicked()
  543. {
  544. OnGenerateSoundBanksClicked.ExecuteIfBound();
  545. return FReply::Handled();
  546. }
  547. void SWaapiPicker::ConstructTree()
  548. {
  549. if (FAkWaapiClient::IsProjectLoaded())
  550. {
  551. if (ConstructTreeTask.IsValid() && !ConstructTreeTask->IsComplete())
  552. {
  553. if (auto AkSettings = GetMutableDefault<UAkSettings>())
  554. {
  555. AkSettings->bRequestRefresh = true;
  556. }
  557. return;
  558. }
  559. FString CurrentFilterText = SearchBoxFilter.IsValid() ? SearchBoxFilter->GetRawFilterText().ToString() : TEXT("");
  560. if (!CurrentFilterText.IsEmpty())
  561. {
  562. FilterUpdated();
  563. return;
  564. }
  565. ConstructTreeTask = FFunctionGraphTask::CreateAndDispatchWhenReady([sharedThis = SharedThis(this)]
  566. {
  567. {
  568. FScopeLock autoLock(&sharedThis->RootItemsLock);
  569. sharedThis->RootItems.Empty(EWwiseItemType::LastWwiseBrowserType - EWwiseItemType::Event + 1);
  570. }
  571. auto PopulateTask = FFunctionGraphTask::CreateAndDispatchWhenReady([sharedThis]
  572. {
  573. for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType; ++i)
  574. {
  575. FGuid in_ItemId = FGuid::NewGuid();
  576. TSharedPtr<FJsonObject> Result;
  577. uint32_t ItemChildrenCount = 0;
  578. FString Path = WwiseWaapiHelper::BACK_SLASH + EWwiseItemType::FolderNames[i];
  579. // Request data from Wwise UI using WAAPI and use them to create a Wwise tree item, getting the informations from a specific "PATH".
  580. if (sharedThis->CallWaapiGetInfoFrom(WwiseWaapiHelper::PATH, Path, Result, {}))
  581. {
  582. // Recover the information from the Json object Result and use it to get the item id.
  583. const TSharedPtr<FJsonObject>& ItemInfoObj = Result->GetArrayField(WwiseWaapiHelper::RETURN)[0]->AsObject();
  584. const FString ItemIdString = ItemInfoObj->GetStringField(WwiseWaapiHelper::ID);
  585. Path = ItemInfoObj->GetStringField(WwiseWaapiHelper::PATH);
  586. ItemChildrenCount = ItemInfoObj->GetNumberField(WwiseWaapiHelper::CHILDREN_COUNT);
  587. FGuid::ParseExact(ItemIdString, EGuidFormats::DigitsWithHyphensInBraces, in_ItemId);
  588. }
  589. else
  590. {
  591. UE_LOG(LogAkAudio, Log, TEXT("Failed to get information from id : %s"), *Path);
  592. if (Result->GetStringField(TEXT("uri")) == TEXT("ak.wwise.locked"))
  593. {
  594. UE_LOG(LogAkAudio, Warning, TEXT("%s"), *ModalWarning.ToString());
  595. sharedThis->isModalActiveInWwise = true;
  596. }
  597. else if (auto AkSettings = GetMutableDefault<UAkSettings>())
  598. {
  599. AkSettings->bRequestRefresh = true;
  600. }
  601. return;
  602. }
  603. // Create a new tree item and add it the root list.
  604. TSharedPtr<FWwiseTreeItem> NewRootParent = MakeShared<FWwiseTreeItem>(EWwiseItemType::BrowserDisplayNames[i], Path, nullptr, EWwiseItemType::PhysicalFolder, in_ItemId);
  605. NewRootParent->ChildCountInWwise = ItemChildrenCount;
  606. {
  607. FScopeLock autoLock(&sharedThis->RootItemsLock);
  608. sharedThis->RootItems.Add(NewRootParent);
  609. }
  610. }
  611. }, GET_STATID(STAT_WaapiPickerConstructTree), nullptr, ENamedThreads::AnyThread);
  612. FTaskGraphInterface::Get().WaitUntilTaskCompletes(PopulateTask);
  613. FFunctionGraphTask::CreateAndDispatchWhenReady( [sharedThis]
  614. {
  615. sharedThis->AllowTreeViewDelegates = true;
  616. sharedThis->ExpandFirstLevel();
  617. sharedThis->RestoreTreeExpansion(sharedThis->RootItems);
  618. sharedThis->TreeViewPtr->RequestTreeRefresh();
  619. }, GET_STATID(STAT_WaapiPickerConstructTree), nullptr, ENamedThreads::GameThread);
  620. }, GET_STATID(STAT_WaapiPickerConstructTree), nullptr, ENamedThreads::AnyThread);
  621. }
  622. }
  623. void SWaapiPicker::ExpandFirstLevel()
  624. {
  625. // Expand root items and first-level work units.
  626. for (int32 i = 0; i < RootItems.Num(); i++)
  627. {
  628. TreeViewPtr->SetItemExpansion(RootItems[i], true);
  629. }
  630. }
  631. void SWaapiPicker::ExpandParents(TSharedPtr<FWwiseTreeItem> Item)
  632. {
  633. if (Item->Parent.IsValid())
  634. {
  635. ExpandParents(Item->Parent.Pin());
  636. TreeViewPtr->SetItemExpansion(Item->Parent.Pin(), true);
  637. }
  638. }
  639. TSharedRef<ITableRow> SWaapiPicker::GenerateRow(TSharedPtr<FWwiseTreeItem> TreeItem, const TSharedRef<STableViewBase>& OwnerTable)
  640. {
  641. check(TreeItem.IsValid());
  642. EVisibility RowVisibility = TreeItem->IsVisible ? EVisibility::Visible : EVisibility::Collapsed;
  643. TSharedPtr<ITableRow> NewRow = SNew(STableRow< TSharedPtr<FWwiseTreeItem> >, OwnerTable)
  644. .OnDragDetected(this, &SWaapiPicker::HandleOnDragDetected)
  645. .Visibility(RowVisibility)
  646. [
  647. SNew(SWaapiPickerRow)
  648. .WaapiPickerItem(TreeItem)
  649. .HighlightText(this, &SWaapiPicker::GetHighlightText)
  650. .IsSelected(this, &SWaapiPicker::IsTreeItemSelected, TreeItem)
  651. ];
  652. TreeItem->TreeRow = NewRow;
  653. return NewRow.ToSharedRef();
  654. }
  655. void SWaapiPicker::GetChildrenForTree(TSharedPtr< FWwiseTreeItem > TreeItem, TArray< TSharedPtr<FWwiseTreeItem> >& OutChildren)
  656. {
  657. // In case the item is "unexpanded" and have children (in the Wwise tree), we need to add a default item to show the arrow that says, this item have children.
  658. FString CurrentFilterText = SearchBoxFilter->GetRawFilterText().ToString();
  659. if (!TreeItem->ChildCountInWwise)
  660. {
  661. // This is useful in case when the item contains elements that are being moved to an other path and the item
  662. // has no longer children and was expanded, so we need to remove it form the expansion items list.
  663. LastExpandedItems.Remove(TreeItem->ItemId);
  664. }
  665. else if (CurrentFilterText.IsEmpty())
  666. {
  667. if (!LastExpandedItems.Contains(TreeItem->ItemId))
  668. {
  669. TreeItem->EmptyChildren();
  670. TSharedPtr<FWwiseTreeItem> emptyTreeItem = MakeShared<FWwiseTreeItem>(WwiseWaapiHelper::NAME, WwiseWaapiHelper::PATH, nullptr, EWwiseItemType::PhysicalFolder, FGuid::NewGuid());
  671. TreeItem->AddChild(emptyTreeItem);
  672. }
  673. else
  674. {
  675. // Update the item expansion to be visible in the tree, since it is being expanded by the user.
  676. TreeViewPtr->SetItemExpansion(TreeItem, true);
  677. }
  678. }
  679. OutChildren = TreeItem->GetChildren();
  680. }
  681. FReply SWaapiPicker::HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent)
  682. {
  683. // Refresh the contents
  684. if (OnDragDetected.IsBound())
  685. {
  686. return OnDragDetected.Execute(Geometry, MouseEvent);
  687. }
  688. return FReply::Unhandled();
  689. }
  690. void SWaapiPicker::PopulateSearchStrings(const FString& FolderName, OUT TArray< FString >& OutSearchStrings) const
  691. {
  692. OutSearchStrings.Add(FolderName);
  693. }
  694. void SWaapiPicker::OnSearchBoxChanged(const FText& InSearchText)
  695. {
  696. SearchBoxFilter->SetRawFilterText(InSearchText);
  697. }
  698. FText SWaapiPicker::GetHighlightText() const
  699. {
  700. return SearchBoxFilter->GetRawFilterText();
  701. }
  702. void SWaapiPicker::FilterUpdated()
  703. {
  704. FScopedSlowTask SlowTask(2.f, LOCTEXT("AK_PopulatingPicker", "Populating Waapi Picker..."));
  705. SlowTask.MakeDialog();
  706. if (RootItems.Num())
  707. {
  708. ApplyFilter();
  709. }
  710. TreeViewPtr->RequestTreeRefresh();
  711. }
  712. void SWaapiPicker::SetItemVisibility(TSharedPtr<FWwiseTreeItem> Item, bool IsVisible)
  713. {
  714. if (!Item.IsValid())
  715. return;
  716. if (IsVisible)
  717. {
  718. // Propagate visibility to parents.
  719. SetItemVisibility(Item->Parent.Pin(), IsVisible);
  720. }
  721. Item->IsVisible = IsVisible;
  722. if (Item->TreeRow.IsValid())
  723. {
  724. TSharedRef<SWidget> wid = Item->TreeRow.Pin()->AsWidget();
  725. wid->SetVisibility(IsVisible ? EVisibility::Visible : EVisibility::Collapsed);
  726. }
  727. }
  728. void SWaapiPicker::ApplyFilter()
  729. {
  730. for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType; ++i)
  731. {
  732. RootItems[i]->EmptyChildren();
  733. }
  734. static TSet<FGuid> LastExpandedItemsBeforFilter;
  735. AllowTreeViewDelegates = false;
  736. FString CurrentFilterText = SearchBoxFilter->GetRawFilterText().ToString();
  737. if (CurrentFilterText.IsEmpty())
  738. {
  739. // Recover the last expanded items before filtering.
  740. LastExpandedItems.Empty();
  741. LastExpandedItems = LastExpandedItemsBeforFilter;
  742. LastExpandedItemsBeforFilter.Empty();
  743. AllowTreeViewDelegates = true;
  744. ConstructTree();
  745. return;
  746. }
  747. if (!LastExpandedItemsBeforFilter.Num())
  748. {
  749. // We preserve the last expanded items to re-expand the tree as it was in non filtering mode.
  750. LastExpandedItemsBeforFilter = LastExpandedItems;
  751. LastExpandedItems.Empty();
  752. }
  753. TSharedPtr<FJsonObject> Result;
  754. if (CallWaapiGetInfoFrom(WwiseWaapiHelper::SEARCH, CurrentFilterText, Result,
  755. {
  756. { WwiseWaapiHelper::WHERE , { WwiseWaapiHelper::NAMECONTAINS, CurrentFilterText }, {} },
  757. { WwiseWaapiHelper::RANGE, {}, { 0, 2000 * CurrentFilterText.Len() } }
  758. }))
  759. {
  760. // Recover the information from the Json object Result and use it to construct the tree item.
  761. TArray<TSharedPtr<FJsonValue>> SearchResultArray = Result->GetArrayField(WwiseWaapiHelper::RETURN);
  762. if (SearchResultArray.Num())
  763. {
  764. // The map contains each path and the correspondent object of the search result.
  765. TMap < FString, TSharedPtr<FWwiseTreeItem>> SearchedResultTreeItem;
  766. for (int i = 0; i < SearchResultArray.Num(); i++)
  767. {
  768. // Fill the map with the path-object elements.
  769. TSharedPtr<FWwiseTreeItem> NewRootChild = ConstructWwiseTreeItem(SearchResultArray[i]);
  770. if (NewRootChild.IsValid())
  771. {
  772. FindAndCreateItems(NewRootChild);
  773. }
  774. }
  775. }
  776. }
  777. else
  778. {
  779. UE_LOG(LogAkAudio, Log, TEXT("Failed to get information from item search : %s"), *CurrentFilterText);
  780. }
  781. RestoreTreeExpansion(RootItems);
  782. AllowTreeViewDelegates = true;
  783. }
  784. void SWaapiPicker::RestoreTreeExpansion(const TArray< TSharedPtr<FWwiseTreeItem> >& Items)
  785. {
  786. for (int i = 0; i < Items.Num(); i++)
  787. {
  788. if (LastExpandedItems.Contains(Items[i]->ItemId))
  789. {
  790. TreeViewPtr->SetItemExpansion(Items[i], true);
  791. }
  792. RestoreTreeExpansion(Items[i]->GetChildren());
  793. }
  794. }
  795. void SWaapiPicker::TreeSelectionChanged(TSharedPtr< FWwiseTreeItem > TreeItem, ESelectInfo::Type /*SelectInfo*/)
  796. {
  797. if (AllowTreeViewDelegates)
  798. {
  799. auto& SelectedItems = GetSelectedItems();
  800. LastSelectedItems.Empty();
  801. for (int32 ItemIdx = 0; ItemIdx < SelectedItems.Num(); ++ItemIdx)
  802. {
  803. auto& Item = SelectedItems[ItemIdx];
  804. if (Item.IsValid())
  805. {
  806. LastSelectedItems.Add(Item->ItemId);
  807. }
  808. }
  809. const UAkSettingsPerUser* AkSettingsPerUser = GetDefault<UAkSettingsPerUser>();
  810. if (AkSettingsPerUser && AkSettingsPerUser->AutoSyncSelection)
  811. {
  812. HandleFindWwiseItemInProjectExplorerCommandExecute();
  813. }
  814. OnSelectionChanged.ExecuteIfBound(TreeItem, ESelectInfo::OnMouseClick);
  815. }
  816. }
  817. void SWaapiPicker::TreeExpansionChanged(TSharedPtr< FWwiseTreeItem > TreeItem, bool bIsExpanded)
  818. {
  819. if (!AllowTreeViewDelegates)
  820. {
  821. if (bIsExpanded)
  822. TreeItem->SortChildren();
  823. return;
  824. }
  825. // If the item is not expanded we don't need to request the server to get any information(the children are hidden).
  826. if (!bIsExpanded)
  827. {
  828. LastExpandedItems.Remove(TreeItem->ItemId);
  829. return;
  830. }
  831. LastExpandedItems.Add(TreeItem->ItemId);
  832. FString CurrentFilterText = SearchBoxFilter->GetRawFilterText().ToString();
  833. if (!CurrentFilterText.IsEmpty())
  834. return;
  835. const FString itemIdStringField = TreeItem->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces);
  836. FFunctionGraphTask::CreateAndDispatchWhenReady([sharedThis = SharedThis(this), TreeItem, itemIdStringField]
  837. {
  838. TSharedPtr<FJsonObject> Result;
  839. // Request data from Wwise UI using WAAPI and use them to create a Wwise tree item, getting the informations from a specific "ID".
  840. if (!sharedThis->CallWaapiGetInfoFrom(WwiseWaapiHelper::ID, itemIdStringField, Result, { { WwiseWaapiHelper::SELECT, { WwiseWaapiHelper::CHILDREN }, {} } }))
  841. {
  842. UE_LOG(LogAkAudio, Log, TEXT("Failed to get information from id : %s"), *itemIdStringField);
  843. return;
  844. }
  845. FFunctionGraphTask::CreateAndDispatchWhenReady([sharedThis, Result, TreeItem]
  846. {
  847. // The tree view might have been destroyed between scheduling and running this task
  848. // Recover the information from the Json object Result and use it to construct the tree item.
  849. TArray<TSharedPtr<FJsonValue>> StructJsonArray = Result->GetArrayField(WwiseWaapiHelper::RETURN);
  850. /** If the item have just one child and we are expanding it, this means that we need to construct the children list.
  851. * In case the the number of children gotten form Wwise is not the same of the item, this means that there is some children added/removed,
  852. * so we also need to construct the new list.
  853. */
  854. if ((TreeItem->GetChildren().Num() == 1) || (TreeItem->GetChildren().Num() != StructJsonArray.Num()))
  855. {
  856. TreeItem->EmptyChildren();
  857. for (int i = 0; i < StructJsonArray.Num(); i++)
  858. {
  859. TSharedPtr<FWwiseTreeItem> NewRootChild = sharedThis->ConstructWwiseTreeItem(StructJsonArray[i]);
  860. if (NewRootChild.IsValid())
  861. {
  862. TreeItem->AddChild(NewRootChild);
  863. }
  864. }
  865. TreeItem->SortChildren();
  866. sharedThis->TreeViewPtr->RequestTreeRefresh();
  867. }
  868. }, GET_STATID(STAT_WaapiPickerTreeExpansionChanged), nullptr, ENamedThreads::GameThread);
  869. }, GET_STATID(STAT_WaapiPickerTreeExpansionChanged));
  870. }
  871. bool SWaapiPicker::IsTreeItemSelected(TSharedPtr<FWwiseTreeItem> TreeItem) const
  872. {
  873. return TreeViewPtr->IsItemSelected(TreeItem);
  874. }
  875. TSharedPtr<SWidget> SWaapiPicker::MakeWaapiPickerContextMenu()
  876. {
  877. const FWaapiPickerViewCommands& Commands = FWaapiPickerViewCommands::Get();
  878. // Build up the menu
  879. FMenuBuilder MenuBuilder(true, CommandList);
  880. {
  881. MenuBuilder.BeginSection("WaapiPickerCreate", LOCTEXT("MenuHeader", "WaapiPicker"));
  882. {
  883. MenuBuilder.AddMenuEntry(Commands.RequestPlayWwiseItem);
  884. MenuBuilder.AddMenuEntry(Commands.RequestStopAllWwiseItem);
  885. }
  886. MenuBuilder.EndSection();
  887. MenuBuilder.BeginSection("WaapiPickerEdit", LOCTEXT("EditMenuHeader", "Edit"));
  888. {
  889. MenuBuilder.AddMenuEntry(Commands.RequestRenameWwiseItem);
  890. MenuBuilder.AddMenuEntry(Commands.RequestDeleteWwiseItem);
  891. }
  892. MenuBuilder.EndSection();
  893. if (!bRestrictContextMenu)
  894. {
  895. MenuBuilder.BeginSection("WaapiPickerExplore", LOCTEXT("ExploreMenuHeader", "Explore"));
  896. {
  897. MenuBuilder.AddMenuEntry(Commands.RequestExploreWwiseItem);
  898. MenuBuilder.AddMenuEntry(Commands.RequestFindInProjectExplorerWwisetem);
  899. }
  900. MenuBuilder.EndSection();
  901. }
  902. MenuBuilder.BeginSection("WaapiPickerRefreshAll");
  903. {
  904. MenuBuilder.AddMenuEntry(Commands.RequestRefreshWaapiPicker);
  905. }
  906. MenuBuilder.EndSection();
  907. MenuBuilder.BeginSection("WaapiPickerImport");
  908. {
  909. MenuBuilder.AddMenuEntry(Commands.RequestImportWwiseItem);
  910. }
  911. MenuBuilder.EndSection();
  912. }
  913. return MenuBuilder.MakeWidget();
  914. }
  915. void SWaapiPicker::CreateWaapiPickerCommands()
  916. {
  917. const FWaapiPickerViewCommands& Commands = FWaapiPickerViewCommands::Get();
  918. FUICommandList& ActionList = *CommandList;
  919. // Action for rename a Wwise item.
  920. ActionList.MapAction(Commands.RequestRenameWwiseItem,
  921. FExecuteAction::CreateRaw(this, &SWaapiPicker::HandleRenameWwiseItemCommandExecute),
  922. FCanExecuteAction::CreateRaw(this, &SWaapiPicker::HandleRenameWwiseItemCommandCanExecute));
  923. // Action to play a Wwise item (event).
  924. ActionList.MapAction(Commands.RequestPlayWwiseItem,
  925. FExecuteAction::CreateRaw(this, &SWaapiPicker::HandlePlayWwiseItemCommandExecute),
  926. FCanExecuteAction::CreateRaw(this, &SWaapiPicker::HandlePlayWwiseItemCommandCanExecute));
  927. // Action to stop all playing Wwise item (event).
  928. ActionList.MapAction(Commands.RequestStopAllWwiseItem,
  929. FExecuteAction::CreateSP(this, &SWaapiPicker::StopAndDestroyAllTransports));
  930. // Action for rename a Wwise item.
  931. ActionList.MapAction(Commands.RequestDeleteWwiseItem,
  932. FExecuteAction::CreateRaw(this, &SWaapiPicker::HandleDeleteWwiseItemCommandExecute),
  933. FCanExecuteAction::CreateRaw(this, &SWaapiPicker::HandleDeleteWwiseItemCommandCanExecute));
  934. // Explore an item in the containing folder.
  935. ActionList.MapAction(Commands.RequestExploreWwiseItem,
  936. FExecuteAction::CreateRaw(this, &SWaapiPicker::HandleExploreWwiseItemCommandExecute),
  937. FCanExecuteAction::CreateRaw(this, &SWaapiPicker::HandleWwiseCommandCanExecute));
  938. // Explore an item in the containing folder.
  939. ActionList.MapAction(Commands.RequestFindInProjectExplorerWwisetem,
  940. FExecuteAction::CreateRaw(this, &SWaapiPicker::HandleFindWwiseItemInProjectExplorerCommandExecute),
  941. FCanExecuteAction::CreateRaw(this, &SWaapiPicker::HandleWwiseCommandCanExecute));
  942. // Action for refresh the Waapi Picker.
  943. ActionList.MapAction(Commands.RequestRefreshWaapiPicker,
  944. FExecuteAction::CreateSP(this, &SWaapiPicker::HandleRefreshWaapiPickerCommandExecute));
  945. // Action for undo last action in the Waapi Picker.
  946. ActionList.MapAction(
  947. FGenericCommands::Get().Undo,
  948. FExecuteAction::CreateSP(this, &SWaapiPicker::HandleUndoWaapiPickerCommandExecute));
  949. // Action for redo last action in the Waapi Picker.
  950. ActionList.MapAction(
  951. FGenericCommands::Get().Redo,
  952. FExecuteAction::CreateSP(this, &SWaapiPicker::HandleRedoWaapiPickerCommandExecute));
  953. // Action for importing the selected items from the Waapi Picker.
  954. ActionList.MapAction(
  955. Commands.RequestImportWwiseItem,
  956. FExecuteAction::CreateSP(this, &SWaapiPicker::HandleImportWwiseItemCommandExecute));
  957. }
  958. bool SWaapiPicker::HandleRenameWwiseItemCommandCanExecute() const
  959. {
  960. auto& SelectedItems = GetSelectedItems();
  961. return SelectedItems.Num() == 1 && SelectedItems[0]->IsNotOfType({ EWwiseItemType::PhysicalFolder, EWwiseItemType::StandaloneWorkUnit, EWwiseItemType::NestedWorkUnit });
  962. }
  963. void SWaapiPicker::HandleRenameWwiseItemCommandExecute() const
  964. {
  965. auto& SelectedItems = GetSelectedItems();
  966. if (SelectedItems.Num())
  967. {
  968. TSharedPtr<ITableRow> TableRow = TreeViewPtr->WidgetFromItem(SelectedItems[0]);
  969. // If the Wwise item is selected but not visible, we scroll it into the view.
  970. if (!TableRow.IsValid())
  971. {
  972. TreeViewPtr->RequestScrollIntoView(SelectedItems[0]);
  973. return;
  974. }
  975. // Get the right Row to enter in editing mode.
  976. TSharedPtr<STableRow< TSharedPtr<FWwiseTreeItem>> > TableRowItem = StaticCastSharedPtr<STableRow< TSharedPtr<FWwiseTreeItem>>>(TableRow);
  977. if (TableRowItem.IsValid())
  978. {
  979. TSharedPtr<SWidget> RowContent = TableRowItem->GetContent();
  980. TSharedPtr<SWaapiPickerRow> ItemWidget = StaticCastSharedPtr<SWaapiPickerRow>(RowContent);
  981. if (ItemWidget.IsValid())
  982. {
  983. ItemWidget->EnterEditingMode();
  984. }
  985. }
  986. }
  987. }
  988. bool SWaapiPicker::HandlePlayWwiseItemCommandCanExecute() const
  989. {
  990. auto& SelectedItems = GetSelectedItems();
  991. if (SelectedItems.Num() == 0)
  992. return false;
  993. for (int32 i = 0; i < SelectedItems.Num(); ++i)
  994. {
  995. if (SelectedItems[i]->IsNotOfType({ EWwiseItemType::Event, EWwiseItemType::Sound, EWwiseItemType::BlendContainer, EWwiseItemType::SwitchContainer, EWwiseItemType::RandomSequenceContainer }))
  996. return false;
  997. }
  998. return true;
  999. }
  1000. int32 SWaapiPicker::CreateTransport(const FGuid& in_ItemId)
  1001. {
  1002. const FString itemIdStringField = in_ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces);
  1003. TSharedPtr<FJsonObject> Result;
  1004. int32 transportID = -1;
  1005. #if AK_SUPPORT_WAAPI
  1006. if (SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::core::transport::create, { { WwiseWaapiHelper::OBJECT, itemIdStringField } }, Result))
  1007. {
  1008. transportID = Result->GetIntegerField(WwiseWaapiHelper::TRANSPORT);
  1009. uint64 subscriptionID = SubscribeToTransportStateChanged(transportID);
  1010. ItemToTransport.Add(in_ItemId, TransportInfo(transportID, subscriptionID));
  1011. }
  1012. #endif
  1013. return transportID;
  1014. }
  1015. void SWaapiPicker::DestroyTransport(const FGuid& in_itemID)
  1016. {
  1017. auto waapiClient = FAkWaapiClient::Get();
  1018. if (!waapiClient)
  1019. return;
  1020. if (!ItemToTransport.Contains(in_itemID))
  1021. return;
  1022. TSharedRef<FJsonObject> Args = MakeShared<FJsonObject>();
  1023. Args->SetNumberField(WwiseWaapiHelper::TRANSPORT, ItemToTransport[in_itemID].TransportID);
  1024. TSharedPtr<FJsonObject> Result;
  1025. if (ItemToTransport[in_itemID].SubscriptionID != 0)
  1026. waapiClient->Unsubscribe(ItemToTransport[in_itemID].SubscriptionID, Result);
  1027. #if AK_SUPPORT_WAAPI
  1028. TSharedRef<FJsonObject> Options = MakeShared<FJsonObject>();
  1029. if (waapiClient->Call(ak::wwise::core::transport::destroy, Args, Options, Result))
  1030. ItemToTransport.Remove(in_itemID);
  1031. #endif
  1032. }
  1033. void SWaapiPicker::TogglePlayStop(int32 in_transportID)
  1034. {
  1035. auto waapiClient = FAkWaapiClient::Get();
  1036. if (!waapiClient)
  1037. {
  1038. UE_LOG(LogAkAudio, Log, TEXT("Unable to connect to localhost"));
  1039. return;
  1040. }
  1041. TSharedRef<FJsonObject> Args = MakeShared<FJsonObject>();
  1042. Args->SetStringField(WwiseWaapiHelper::ACTION, WwiseWaapiHelper::PLAYSTOP);
  1043. Args->SetNumberField(WwiseWaapiHelper::TRANSPORT, in_transportID);
  1044. #if AK_SUPPORT_WAAPI
  1045. TSharedPtr<FJsonObject> Result;
  1046. TSharedRef<FJsonObject> Options = MakeShared<FJsonObject>();
  1047. if (!waapiClient->Call(ak::wwise::core::transport::executeAction, Args, Options, Result))
  1048. {
  1049. UE_LOG(LogAkAudio, Log, TEXT("Failed to trigger playback"));
  1050. }
  1051. #endif
  1052. }
  1053. void SWaapiPicker::StopTransport(int32 in_transportID)
  1054. {
  1055. auto waapiClient = FAkWaapiClient::Get();
  1056. if (!waapiClient)
  1057. return;
  1058. TSharedRef<FJsonObject> Args = MakeShared<FJsonObject>();
  1059. Args->SetStringField(WwiseWaapiHelper::ACTION, WwiseWaapiHelper::STOP);
  1060. Args->SetNumberField(WwiseWaapiHelper::TRANSPORT, in_transportID);
  1061. #if AK_SUPPORT_WAAPI
  1062. TSharedPtr<FJsonObject> Result;
  1063. TSharedRef<FJsonObject> Options = MakeShared<FJsonObject>();
  1064. if (!waapiClient->Call(ak::wwise::core::transport::executeAction, Args, Options, Result))
  1065. {
  1066. UE_LOG(LogAkAudio, Log, TEXT("Cannot stop event."));
  1067. }
  1068. #endif
  1069. }
  1070. void SWaapiPicker::HandleStateChanged(TSharedPtr<FJsonObject> in_UEJsonObject)
  1071. {
  1072. const FString newState = in_UEJsonObject->GetStringField(WwiseWaapiHelper::STATE);
  1073. FGuid itemID;
  1074. FGuid::Parse(in_UEJsonObject->GetStringField(WwiseWaapiHelper::OBJECT), itemID);
  1075. const int32 transportID = in_UEJsonObject->GetNumberField(WwiseWaapiHelper::TRANSPORT);
  1076. if (newState == WwiseWaapiHelper::STOPPED)
  1077. {
  1078. DestroyTransport(itemID);
  1079. }
  1080. else if (newState == WwiseWaapiHelper::PLAYING && !ItemToTransport.Contains(itemID))
  1081. {
  1082. ItemToTransport.Add(itemID, TransportInfo(transportID, 0));
  1083. }
  1084. }
  1085. uint64 SWaapiPicker::SubscribeToTransportStateChanged(int32 TransportID)
  1086. {
  1087. auto WaapiClient = FAkWaapiClient::Get();
  1088. if (!WaapiClient)
  1089. return 0;
  1090. auto WampEventCallback = WampEventCallback::CreateLambda(
  1091. [sharedThis = SharedThis(this)](uint64_t ID, TSharedPtr<FJsonObject> UEJsonObject)
  1092. {
  1093. AsyncTask(ENamedThreads::GameThread, [sharedThis, UEJsonObject]
  1094. {
  1095. sharedThis->HandleStateChanged(UEJsonObject);
  1096. });
  1097. });
  1098. TSharedRef<FJsonObject> Options = MakeShared<FJsonObject>();
  1099. Options->SetNumberField(WwiseWaapiHelper::TRANSPORT, TransportID);
  1100. TSharedPtr<FJsonObject> OutJsonResult;
  1101. uint64 SubscriptionID = 0;
  1102. #if AK_SUPPORT_WAAPI
  1103. WaapiClient->Subscribe(ak::wwise::core::transport::stateChanged, Options, WampEventCallback, SubscriptionID, OutJsonResult);
  1104. #endif
  1105. return SubscriptionID;
  1106. }
  1107. void SWaapiPicker::HandlePlayWwiseItemCommandExecute()
  1108. {
  1109. auto& SelectedItems = GetSelectedItems();
  1110. // Loop to play all selected items.
  1111. for (int32 i = 0; i < SelectedItems.Num(); ++i)
  1112. {
  1113. const FGuid& ItemId = SelectedItems[i]->ItemId;
  1114. int32 transportID = -1;
  1115. if (ItemToTransport.Contains(ItemId))
  1116. {
  1117. transportID = ItemToTransport[ItemId].TransportID;
  1118. }
  1119. else
  1120. {
  1121. transportID = CreateTransport(ItemId);
  1122. }
  1123. TogglePlayStop(transportID);
  1124. }
  1125. }
  1126. void SWaapiPicker::StopAndDestroyAllTransports()
  1127. {
  1128. for (auto iter = ItemToTransport.CreateIterator(); iter; ++iter)
  1129. {
  1130. StopTransport(iter->Value.TransportID);
  1131. DestroyTransport(iter->Key);
  1132. }
  1133. ItemToTransport.Empty();
  1134. }
  1135. bool SWaapiPicker::HandleDeleteWwiseItemCommandCanExecute() const
  1136. {
  1137. auto& SelectedItems = GetSelectedItems();
  1138. if ((SelectedItems.Num() > 0) &&
  1139. !(TreeViewPtr->IsItemSelected(RootItems[EWwiseItemType::Event]) || TreeViewPtr->IsItemSelected(RootItems[EWwiseItemType::AuxBus])
  1140. || TreeViewPtr->IsItemSelected(RootItems[EWwiseItemType::ActorMixer]) || TreeViewPtr->IsItemSelected(RootItems[EWwiseItemType::AcousticTexture])))
  1141. {
  1142. for (int32 i = 0; i < SelectedItems.Num(); ++i)
  1143. {
  1144. if (SelectedItems[i]->IsOfType({ EWwiseItemType::PhysicalFolder, EWwiseItemType::StandaloneWorkUnit, EWwiseItemType::NestedWorkUnit }))
  1145. return false;
  1146. }
  1147. return true;
  1148. }
  1149. return false;
  1150. }
  1151. void SWaapiPicker::HandleDeleteWwiseItemCommandExecute()
  1152. {
  1153. #if AK_SUPPORT_WAAPI
  1154. TSharedPtr<FJsonObject> Result;
  1155. SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::core::undo::beginGroup, {}, Result);
  1156. auto& SelectedItems = GetSelectedItems();
  1157. for (int32 i = 0; i < SelectedItems.Num(); ++i)
  1158. {
  1159. const FString itemIdStringField = SelectedItems[i]->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces);
  1160. SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::core::object::delete_, { { WwiseWaapiHelper::OBJECT, itemIdStringField } }, Result);
  1161. }
  1162. SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::core::undo::endGroup, { {WwiseWaapiHelper::DISPLAY_NAME, WwiseWaapiHelper::DELETE_ITEMS} }, Result);
  1163. ConstructTree();
  1164. #endif
  1165. }
  1166. void SWaapiPicker::HandleExploreWwiseItemCommandExecute() const
  1167. {
  1168. auto waapiClient = FAkWaapiClient::Get();
  1169. if (!waapiClient)
  1170. {
  1171. UE_LOG(LogAkAudio, Log, TEXT("Unable to connect to localhost"));
  1172. return;
  1173. }
  1174. auto& SelectedItems = GetSelectedItems();
  1175. if (SelectedItems.Num() == 0)
  1176. return;
  1177. TSharedRef<FJsonObject> Args = MakeShared<FJsonObject>();
  1178. {
  1179. TSharedPtr<FJsonObject> from = MakeShared<FJsonObject>();
  1180. from->SetArrayField(WwiseWaapiHelper::PATH, TArray<TSharedPtr<FJsonValue>> { MakeShared<FJsonValueString>(SelectedItems[0]->FolderPath) });
  1181. Args->SetObjectField(WwiseWaapiHelper::FROM, from);
  1182. }
  1183. TSharedRef<FJsonObject> Options = MakeShared<FJsonObject>();
  1184. Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray<TSharedPtr<FJsonValue>> { MakeShared<FJsonValueString>(WwiseWaapiHelper::FILEPATH) });
  1185. #if AK_SUPPORT_WAAPI
  1186. TSharedPtr<FJsonObject> outJsonResult;
  1187. if (!waapiClient->Call(ak::wwise::core::object::get, Args, Options, outJsonResult))
  1188. {
  1189. UE_LOG(LogAkAudio, Log, TEXT("Call Failed"));
  1190. return;
  1191. }
  1192. auto Path = outJsonResult->GetArrayField(WwiseWaapiHelper::RETURN)[0]->AsObject()->GetStringField(WwiseWaapiHelper::FILEPATH);
  1193. FPlatformProcess::ExploreFolder(*Path);
  1194. #endif
  1195. }
  1196. bool SWaapiPicker::HandleWwiseCommandCanExecute() const
  1197. {
  1198. return GetSelectedItems().Num() == 1;
  1199. }
  1200. void SWaapiPicker::HandleFindWwiseItemInProjectExplorerCommandExecute() const
  1201. {
  1202. auto waapiClient = FAkWaapiClient::Get();
  1203. if (!waapiClient)
  1204. {
  1205. UE_LOG(LogAkAudio, Log, TEXT("Unable to connect to localhost"));
  1206. return;
  1207. }
  1208. auto& SelectedItems = GetSelectedItems();
  1209. if (SelectedItems.Num() == 0)
  1210. return;
  1211. TSharedRef<FJsonObject> Args = MakeShared<FJsonObject>();
  1212. Args->SetStringField(WwiseWaapiHelper::COMMAND, WwiseWaapiHelper::FIND_IN_PROJECT_EXPLORER);
  1213. TArray<TSharedPtr<FJsonValue>> SelectedObjects;
  1214. for (auto selectedItem : SelectedItems)
  1215. {
  1216. SelectedObjects.Add(MakeShared<FJsonValueString>(selectedItem->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces)));
  1217. }
  1218. Args->SetArrayField(WwiseWaapiHelper::OBJECTS, SelectedObjects);
  1219. #if AK_SUPPORT_WAAPI
  1220. TSharedPtr<FJsonObject> Result;
  1221. TSharedRef<FJsonObject> Options = MakeShared<FJsonObject>();
  1222. if (!waapiClient->Call(ak::wwise::ui::commands::execute, Args, Options, Result))
  1223. {
  1224. UE_LOG(LogAkAudio, Log, TEXT("Call Failed"));
  1225. }
  1226. #endif
  1227. }
  1228. void SWaapiPicker::HandleRefreshWaapiPickerCommandExecute()
  1229. {
  1230. ConstructTree();
  1231. }
  1232. void SWaapiPicker::HandleUndoWaapiPickerCommandExecute() const
  1233. {
  1234. #if AK_SUPPORT_WAAPI
  1235. TSharedPtr<FJsonObject> Result;
  1236. SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::ui::commands::execute, { {WwiseWaapiHelper::COMMAND, WwiseWaapiHelper::UNDO} }, Result);
  1237. #endif
  1238. }
  1239. void SWaapiPicker::HandleImportWwiseItemCommandExecute() const
  1240. {
  1241. #if WITH_EDITOR
  1242. const FString& PackagePath = "/Game";
  1243. FString LastWwiseImportPath = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_IMPORT);
  1244. const FString& ContentFolder = AkUnrealHelper::GetContentDirectory();
  1245. FString FolderName;
  1246. // If not prompting individual files, prompt the user to select a target directory.
  1247. IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
  1248. if (DesktopPlatform)
  1249. {
  1250. const FString Title = NSLOCTEXT("UnrealEd", "ChooseADirectory", "Choose A Directory").ToString();
  1251. const bool bFolderSelected = DesktopPlatform->OpenDirectoryDialog(
  1252. FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
  1253. Title,
  1254. LastWwiseImportPath,
  1255. FolderName
  1256. );
  1257. if (bFolderSelected)
  1258. {
  1259. FEditorDirectories::Get().SetLastDirectory(ELastDirectory::GENERIC_IMPORT, LastWwiseImportPath);
  1260. FPaths::MakePathRelativeTo(FolderName, *FPaths::ProjectContentDir());
  1261. OnImportWwiseAssetsClicked.ExecuteIfBound(PackagePath / FolderName);
  1262. }
  1263. }
  1264. #endif // WITH_EDITOR
  1265. }
  1266. void SWaapiPicker::HandleRedoWaapiPickerCommandExecute() const
  1267. {
  1268. #if AK_SUPPORT_WAAPI
  1269. TSharedPtr<FJsonObject> Result;
  1270. SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::ui::commands::execute, { {WwiseWaapiHelper::COMMAND, WwiseWaapiHelper::REDO} }, Result);
  1271. #endif
  1272. }
  1273. FReply SWaapiPicker::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyboardEvent)
  1274. {
  1275. const FKey KeyPressed = InKeyboardEvent.GetKey();
  1276. if ((KeyPressed == EKeys::SpaceBar))
  1277. {
  1278. // Play the wwise item.
  1279. if (HandlePlayWwiseItemCommandCanExecute())
  1280. {
  1281. HandlePlayWwiseItemCommandExecute();
  1282. return FReply::Handled();
  1283. }
  1284. }
  1285. else if (KeyPressed == EKeys::F2)
  1286. {
  1287. // Rename key : Rename selected Wwise item.
  1288. if (HandleRenameWwiseItemCommandCanExecute())
  1289. {
  1290. HandleRenameWwiseItemCommandExecute();
  1291. return FReply::Handled();
  1292. }
  1293. }
  1294. else if (KeyPressed == EKeys::Delete)
  1295. {
  1296. // Delete key : Delete selected Wwise item(s).
  1297. if (HandleDeleteWwiseItemCommandCanExecute())
  1298. {
  1299. HandleDeleteWwiseItemCommandExecute();
  1300. return FReply::Handled();
  1301. }
  1302. }
  1303. else if (KeyPressed == EKeys::F5)
  1304. { // Populates the Waapi Picker.
  1305. HandleRefreshWaapiPickerCommandExecute();
  1306. return FReply::Handled();
  1307. }
  1308. else if ((KeyPressed == EKeys::Z) && InKeyboardEvent.IsControlDown())
  1309. {
  1310. // Undo
  1311. HandleUndoWaapiPickerCommandExecute();
  1312. return FReply::Handled();
  1313. }
  1314. else if ((KeyPressed == EKeys::Y) && InKeyboardEvent.IsControlDown())
  1315. {
  1316. // Redo
  1317. HandleRedoWaapiPickerCommandExecute();
  1318. return FReply::Handled();
  1319. }
  1320. else if (!bRestrictContextMenu && (KeyPressed == EKeys::One) && InKeyboardEvent.IsControlDown() && InKeyboardEvent.IsShiftDown())
  1321. {
  1322. // Finds the specified object in the Project Explorer (Sync Group 1).
  1323. if (HandleWwiseCommandCanExecute())
  1324. {
  1325. HandleFindWwiseItemInProjectExplorerCommandExecute();
  1326. return FReply::Handled();
  1327. }
  1328. }
  1329. return FReply::Unhandled();
  1330. }
  1331. const TArray<TSharedPtr<FWwiseTreeItem>> SWaapiPicker::GetSelectedItems() const
  1332. {
  1333. return TreeViewPtr->GetSelectedItems();
  1334. }
  1335. const FString SWaapiPicker::GetSearchText() const
  1336. {
  1337. return SearchBoxFilter->GetRawFilterText().ToString();
  1338. }
  1339. const void SWaapiPicker::SetSearchText(const FString& newText)
  1340. {
  1341. SearchBoxPtr->SetText(FText::FromString(newText));
  1342. }
  1343. EActiveTimerReturnType SWaapiPicker::SetFocusPostConstruct(double InCurrentTime, float InDeltaTime)
  1344. {
  1345. FWidgetPath WidgetToFocusPath;
  1346. FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchBoxPtr.ToSharedRef(), WidgetToFocusPath);
  1347. FSlateApplication::Get().SetKeyboardFocus(WidgetToFocusPath, EFocusCause::SetDirectly);
  1348. return EActiveTimerReturnType::Stop;
  1349. }
  1350. void SWaapiPicker::SubscribeWaapiCallbacks()
  1351. {
  1352. struct SubscriptionData
  1353. {
  1354. const char* Uri;
  1355. WampEventCallback Callback;
  1356. uint64* SubscriptionId;
  1357. };
  1358. #if AK_SUPPORT_WAAPI
  1359. const SubscriptionData Subscriptions[] = {
  1360. {ak::wwise::core::object::nameChanged, WampEventCallback::CreateSP(this, &SWaapiPicker::OnWaapiRenamed), &WaapiSubscriptionIds.Renamed},
  1361. {ak::wwise::core::object::childAdded, WampEventCallback::CreateSP(this, &SWaapiPicker::OnWaapiChildAdded), &WaapiSubscriptionIds.ChildAdded},
  1362. {ak::wwise::core::object::childRemoved, WampEventCallback::CreateSP(this, &SWaapiPicker::OnWaapiChildRemoved), &WaapiSubscriptionIds.ChildRemoved},
  1363. {ak::wwise::ui::selectionChanged, WampEventCallback::CreateSP(this, &SWaapiPicker::OnWwiseSelectionChanged), &WaapiSubscriptionIds.SelectionChanged},
  1364. };
  1365. #endif
  1366. auto waapiClient = FAkWaapiClient::Get();
  1367. if (!waapiClient)
  1368. {
  1369. return;
  1370. }
  1371. TSharedRef<FJsonObject> Options = MakeShared<FJsonObject>();
  1372. Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray<TSharedPtr<FJsonValue>>
  1373. {
  1374. MakeShared<FJsonValueString>(WwiseWaapiHelper::ID),
  1375. MakeShared<FJsonValueString>(WwiseWaapiHelper::NAME),
  1376. MakeShared<FJsonValueString>(WwiseWaapiHelper::TYPE),
  1377. MakeShared<FJsonValueString>(WwiseWaapiHelper::CHILDREN_COUNT),
  1378. MakeShared<FJsonValueString>(WwiseWaapiHelper::PATH),
  1379. MakeShared<FJsonValueString>(WwiseWaapiHelper::PARENT),
  1380. MakeShared<FJsonValueString>(WwiseWaapiHelper::WORKUNIT_TYPE),
  1381. });
  1382. TSharedPtr<FJsonObject> Result;
  1383. #if AK_SUPPORT_WAAPI
  1384. for (auto& SubscriptionData : Subscriptions)
  1385. {
  1386. if (*SubscriptionData.SubscriptionId == 0)
  1387. {
  1388. waapiClient->Subscribe(SubscriptionData.Uri,
  1389. Options,
  1390. SubscriptionData.Callback,
  1391. *SubscriptionData.SubscriptionId,
  1392. Result
  1393. );
  1394. }
  1395. }
  1396. #endif
  1397. }
  1398. void SWaapiPicker::UnsubscribeWaapiCallbacks()
  1399. {
  1400. auto waapiClient = FAkWaapiClient::Get();
  1401. if (!waapiClient)
  1402. {
  1403. return;
  1404. }
  1405. auto doUnsubscribe = [waapiClient](uint64& subscriptionId) {
  1406. if (subscriptionId > 0)
  1407. {
  1408. TSharedPtr<FJsonObject> Result;
  1409. waapiClient->Unsubscribe(subscriptionId, Result);
  1410. subscriptionId = 0;
  1411. }
  1412. };
  1413. doUnsubscribe(WaapiSubscriptionIds.Renamed);
  1414. doUnsubscribe(WaapiSubscriptionIds.ChildAdded);
  1415. doUnsubscribe(WaapiSubscriptionIds.ChildRemoved);
  1416. doUnsubscribe(WaapiSubscriptionIds.SelectionChanged);
  1417. }
  1418. TSharedPtr<FWwiseTreeItem> SWaapiPicker::FindTreeItemFromJsonObject(const TSharedPtr<FJsonObject>& ObjectJson, const FString& OverrideLastPart)
  1419. {
  1420. FString objectPath;
  1421. if (!ObjectJson->TryGetStringField(WwiseWaapiHelper::PATH, objectPath))
  1422. {
  1423. return {};
  1424. }
  1425. FString stringId;
  1426. if (!ObjectJson->TryGetStringField(WwiseWaapiHelper::ID, stringId))
  1427. {
  1428. return {};
  1429. }
  1430. FGuid id;
  1431. FGuid::ParseExact(stringId, EGuidFormats::DigitsWithHyphensInBraces, id);
  1432. TArray<FString> pathParts;
  1433. objectPath.ParseIntoArray(pathParts, *WwiseWaapiHelper::BACK_SLASH);
  1434. if (pathParts.Num() == 0)
  1435. {
  1436. return {};
  1437. }
  1438. if (!OverrideLastPart.IsEmpty())
  1439. {
  1440. pathParts[pathParts.Num() - 1] = OverrideLastPart;
  1441. }
  1442. TSharedPtr<FWwiseTreeItem> treeItem;
  1443. TArray<TSharedPtr<FWwiseTreeItem>>* children = &RootItems;
  1444. FString folderPath;
  1445. for (auto& part : pathParts)
  1446. {
  1447. folderPath += FString::Printf(TEXT("%s%s"), *WwiseWaapiHelper::BACK_SLASH, *part);
  1448. bool found = false;
  1449. for (auto& item : *children)
  1450. {
  1451. if (item->ItemId == id)
  1452. {
  1453. return item;
  1454. }
  1455. if (item->FolderPath == folderPath)
  1456. {
  1457. treeItem = item;
  1458. children = treeItem->GetChildrenMutable();
  1459. found = true;
  1460. }
  1461. }
  1462. if (!found)
  1463. {
  1464. return {};
  1465. }
  1466. }
  1467. if (treeItem.IsValid() && treeItem->ItemId != id)
  1468. {
  1469. return {};
  1470. }
  1471. return treeItem;
  1472. }
  1473. void SWaapiPicker::OnWaapiRenamed(uint64_t Id, TSharedPtr<FJsonObject> Response)
  1474. {
  1475. FString oldName;
  1476. if (Response->TryGetStringField(WwiseWaapiHelper::OLD_NAME, oldName) && oldName.IsEmpty())
  1477. {
  1478. const TSharedPtr<FJsonObject>* objectJsonPtr = nullptr;
  1479. if (!Response->TryGetObjectField(WwiseWaapiHelper::OBJECT, objectJsonPtr))
  1480. {
  1481. return;
  1482. }
  1483. auto& objectJson = *objectJsonPtr;
  1484. FString stringId;
  1485. if (!objectJson->TryGetStringField(WwiseWaapiHelper::ID, stringId))
  1486. {
  1487. return;
  1488. }
  1489. FGuid id;
  1490. FGuid::ParseExact(stringId, EGuidFormats::DigitsWithHyphensInBraces, id);
  1491. if (auto pendingIt = pendingTreeItems.Find(id))
  1492. {
  1493. CreateTreeItemWaapi(*pendingIt, objectJson);
  1494. pendingTreeItems.Remove(id);
  1495. AsyncTask(ENamedThreads::GameThread, [sharedThis = SharedThis(this)]
  1496. {
  1497. sharedThis->TreeViewPtr->RequestTreeRefresh();
  1498. });
  1499. }
  1500. return;
  1501. }
  1502. const TSharedPtr<FJsonObject>* objectJsonPtr = nullptr;
  1503. if (Response->TryGetObjectField(WwiseWaapiHelper::OBJECT, objectJsonPtr))
  1504. {
  1505. TSharedPtr<FJsonObject> objectJson = *objectJsonPtr;
  1506. auto treeItem = FindTreeItemFromJsonObject(*objectJsonPtr, oldName);
  1507. if (treeItem)
  1508. {
  1509. Response->TryGetStringField(WwiseWaapiHelper::NEW_NAME, treeItem->DisplayName);
  1510. objectJson->TryGetStringField(WwiseWaapiHelper::PATH, treeItem->FolderPath);
  1511. if (treeItem->Parent.IsValid())
  1512. {
  1513. treeItem->Parent.Pin()->SortChildren();
  1514. }
  1515. AsyncTask(ENamedThreads::GameThread, [sharedThis = SharedThis(this)]
  1516. {
  1517. sharedThis->TreeViewPtr->RequestTreeRefresh();
  1518. });
  1519. }
  1520. }
  1521. }
  1522. template<typename ActionFunctor>
  1523. void SWaapiPicker::HandleOnWaapiChildResponse(TSharedPtr<FJsonObject> Response, const ActionFunctor& Action)
  1524. {
  1525. const TSharedPtr<FJsonObject>* parentJsonPtr = nullptr;
  1526. if (!Response->TryGetObjectField(WwiseWaapiHelper::PARENT, parentJsonPtr))
  1527. {
  1528. return;
  1529. }
  1530. const TSharedPtr<FJsonObject>* childJsonPtr = nullptr;
  1531. if (!Response->TryGetObjectField(WwiseWaapiHelper::CHILD, childJsonPtr))
  1532. {
  1533. return;
  1534. }
  1535. FString childName;
  1536. if (childJsonPtr->Get()->TryGetStringField(WwiseWaapiHelper::NAME, childName) && childName.IsEmpty())
  1537. {
  1538. auto parentTreeItem = FindTreeItemFromJsonObject(*parentJsonPtr);
  1539. FString childStringId;
  1540. childJsonPtr->Get()->TryGetStringField(WwiseWaapiHelper::ID, childStringId);
  1541. FGuid childId;
  1542. FGuid::ParseExact(childStringId, EGuidFormats::DigitsWithHyphensInBraces, childId);
  1543. if (parentTreeItem && childId.IsValid())
  1544. {
  1545. pendingTreeItems.Add(childId, parentTreeItem);
  1546. return;
  1547. }
  1548. }
  1549. auto parentTreeItem = FindTreeItemFromJsonObject(*parentJsonPtr);
  1550. if (parentTreeItem)
  1551. {
  1552. Action(parentTreeItem, *childJsonPtr);
  1553. AsyncTask(ENamedThreads::GameThread, [sharedThis = SharedThis(this)]
  1554. {
  1555. sharedThis->TreeViewPtr->RequestTreeRefresh();
  1556. });
  1557. }
  1558. }
  1559. void SWaapiPicker::OnWaapiChildAdded(uint64_t Id, TSharedPtr<FJsonObject> Response)
  1560. {
  1561. HandleOnWaapiChildResponse(Response,
  1562. [sharedThis = SharedThis(this)](const TSharedPtr<FWwiseTreeItem>& parentTreeItem, const TSharedPtr<FJsonObject>& childJson)
  1563. {
  1564. sharedThis->CreateTreeItemWaapi(parentTreeItem, childJson);
  1565. });
  1566. }
  1567. void SWaapiPicker::CreateTreeItemWaapi(const TSharedPtr<FWwiseTreeItem>& parentTreeItem, const TSharedPtr<FJsonObject>& childJson)
  1568. {
  1569. if (!parentTreeItem)
  1570. {
  1571. return;
  1572. }
  1573. auto newChild = ConstructWwiseTreeItem(childJson);
  1574. if (newChild && parentTreeItem)
  1575. {
  1576. parentTreeItem->AddChild(newChild);
  1577. parentTreeItem->SortChildren();
  1578. ++parentTreeItem->ChildCountInWwise;
  1579. if (parentTreeItem->TreeRow.IsValid() && parentTreeItem->TreeRow.Pin()->IsItemExpanded())
  1580. {
  1581. LastExpandedItems.Add(parentTreeItem->ItemId);
  1582. }
  1583. }
  1584. }
  1585. void SWaapiPicker::OnWaapiChildRemoved(uint64_t Id, TSharedPtr<FJsonObject> Response)
  1586. {
  1587. HandleOnWaapiChildResponse(Response,
  1588. [sharedThis=SharedThis(this)](const TSharedPtr<FWwiseTreeItem>& ParentTreeItem, const TSharedPtr<FJsonObject>& ChildJson)
  1589. {
  1590. auto StringId = ChildJson->GetStringField(WwiseWaapiHelper::ID);
  1591. FGuid ParsedID;
  1592. FGuid::ParseExact(StringId, EGuidFormats::DigitsWithHyphensInBraces, ParsedID);
  1593. for (int32 ChildIndex = 0; ChildIndex < ParentTreeItem->GetChildren().Num(); ++ChildIndex)
  1594. {
  1595. ParentTreeItem->RemoveChild(ParsedID);
  1596. if (ParentTreeItem->ChildCountInWwise <= 0)
  1597. {
  1598. sharedThis->LastExpandedItems.Remove(ParentTreeItem->ItemId);
  1599. }
  1600. }
  1601. });
  1602. }
  1603. TSharedPtr<FWwiseTreeItem> SWaapiPicker::FindOrConstructTreeItemFromJsonObject(const TSharedPtr<FJsonObject>& ObjectJson)
  1604. {
  1605. FString objectPath;
  1606. if (!ObjectJson->TryGetStringField(WwiseWaapiHelper::PATH, objectPath))
  1607. {
  1608. return {};
  1609. }
  1610. FString stringId;
  1611. if (!ObjectJson->TryGetStringField(WwiseWaapiHelper::ID, stringId))
  1612. {
  1613. return {};
  1614. }
  1615. FGuid id;
  1616. FGuid::ParseExact(stringId, EGuidFormats::DigitsWithHyphensInBraces, id);
  1617. TArray<FString> pathParts;
  1618. objectPath.ParseIntoArray(pathParts, *WwiseWaapiHelper::BACK_SLASH);
  1619. if (pathParts.Num() == 0)
  1620. {
  1621. return {};
  1622. }
  1623. TSharedPtr<FWwiseTreeItem> treeItem;
  1624. TArray<TSharedPtr<FWwiseTreeItem>>* children = &RootItems;
  1625. TArray<TSharedPtr<FWwiseTreeItem>> itemsToExpand;
  1626. FString folderPath;
  1627. for (auto& part : pathParts)
  1628. {
  1629. folderPath += FString::Printf(TEXT("%s%s"), *WwiseWaapiHelper::BACK_SLASH, *part);
  1630. bool found = false;
  1631. for (auto& item : *children)
  1632. {
  1633. if (item->ItemId == id)
  1634. {
  1635. treeItem = item;
  1636. break;
  1637. }
  1638. if (item->FolderPath == folderPath)
  1639. {
  1640. if (!TreeViewPtr->IsItemExpanded(item))
  1641. {
  1642. const FString itemIdStringField = item->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces);
  1643. TSharedPtr<FJsonObject> Result;
  1644. // Request data from Wwise UI using WAAPI and use them to create a Wwise tree item, getting the informations from a specific "ID".
  1645. if (!CallWaapiGetInfoFrom(WwiseWaapiHelper::ID, itemIdStringField, Result, { { WwiseWaapiHelper::SELECT, { WwiseWaapiHelper::CHILDREN }, {} } }))
  1646. {
  1647. UE_LOG(LogAkAudio, Log, TEXT("Failed to get information from id : %s"), *itemIdStringField);
  1648. return {};
  1649. }
  1650. // The tree view might have been destroyed between scheduling and running this task
  1651. // Recover the information from the Json object Result and use it to construct the tree item.
  1652. TArray<TSharedPtr<FJsonValue>> StructJsonArray = Result->GetArrayField(WwiseWaapiHelper::RETURN);
  1653. /** If the item have just one child and we are expanding it, this means that we need to construct the children list.
  1654. * In case the the number of children gotten form Wwise is not the same of the item, this means that there is some children added/removed,
  1655. * so we also need to construct the new list.
  1656. */
  1657. if ((item->GetChildren().Num() == 1) || (item->GetChildren().Num() != StructJsonArray.Num()))
  1658. {
  1659. item->EmptyChildren();
  1660. for (int i = 0; i < StructJsonArray.Num(); i++)
  1661. {
  1662. TSharedPtr<FWwiseTreeItem> NewRootChild = ConstructWwiseTreeItem(StructJsonArray[i]);
  1663. if (NewRootChild.IsValid())
  1664. {
  1665. item->AddChild(NewRootChild);
  1666. }
  1667. }
  1668. item->SortChildren();
  1669. }
  1670. itemsToExpand.Add(item);
  1671. }
  1672. treeItem = item;
  1673. children = treeItem->GetChildrenMutable();
  1674. found = true;
  1675. }
  1676. }
  1677. if (treeItem && treeItem->ItemId == id)
  1678. {
  1679. break;
  1680. }
  1681. if (!found)
  1682. {
  1683. return {};
  1684. }
  1685. }
  1686. if (treeItem.IsValid() && treeItem->ItemId != id)
  1687. {
  1688. return {};
  1689. }
  1690. for (auto& itemToExpand : itemsToExpand)
  1691. {
  1692. LastExpandedItems.Add(itemToExpand->ItemId);
  1693. TreeViewPtr->SetItemExpansion(itemToExpand, true);
  1694. }
  1695. return treeItem;
  1696. }
  1697. void SWaapiPicker::OnWwiseSelectionChanged(uint64_t Id, TSharedPtr<FJsonObject> Response)
  1698. {
  1699. AsyncTask(ENamedThreads::GameThread, [sharedThis = SharedThis(this), Response]()
  1700. {
  1701. const UAkSettingsPerUser* AkSettingsPerUser = GetDefault<UAkSettingsPerUser>();
  1702. if (AkSettingsPerUser && AkSettingsPerUser->AutoSyncSelection)
  1703. {
  1704. const TArray<TSharedPtr<FJsonValue>>* objectsJsonArray = nullptr;
  1705. if (Response->TryGetArrayField(WwiseWaapiHelper::OBJECTS, objectsJsonArray))
  1706. {
  1707. sharedThis->AllowTreeViewDelegates = false;
  1708. TArray<TSharedPtr<FWwiseTreeItem>> TreeItems;
  1709. for (auto JsonObject : *objectsJsonArray)
  1710. {
  1711. auto TreeItem = sharedThis->FindOrConstructTreeItemFromJsonObject(JsonObject->AsObject());
  1712. if (TreeItem)
  1713. {
  1714. TreeItems.Add(TreeItem);
  1715. }
  1716. }
  1717. sharedThis->TreeViewPtr->RequestTreeRefresh();
  1718. if (TreeItems.Num() > 0)
  1719. {
  1720. sharedThis->TreeViewPtr->ClearSelection();
  1721. sharedThis->TreeViewPtr->SetItemSelection(TreeItems, true);
  1722. sharedThis->TreeViewPtr->RequestScrollIntoView(TreeItems[0]);
  1723. }
  1724. sharedThis->AllowTreeViewDelegates = true;
  1725. }
  1726. }
  1727. });
  1728. }
  1729. #undef LOCTEXT_NAMESPACE