/******************************************************************************* The content of this file includes portions of the proprietary AUDIOKINETIC Wwise Technology released in source code form as part of the game integration package. The content of this file may not be used without valid licenses to the AUDIOKINETIC Wwise Technology. Note that the use of the game engine is subject to the Unreal(R) Engine End User License Agreement at https://www.unrealengine.com/en-US/eula/unreal License Usage Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use this file in accordance with the end user license agreement provided with the software or, alternatively, in accordance with the terms contained in a written agreement between you and Audiokinetic Inc. Copyright (c) 2023 Audiokinetic Inc. *******************************************************************************/ #include "AkWaapiUMG/Components/AkCheckBox.h" #include "AkAudioDevice.h" #include "Widgets/SNullWidget.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Input/SCheckBox.h" #include "AkWaapiUMG/Components/AkBoolPropertyToControlCustomization.h" #include "AkWaapiUMG/Components/WwiseBoolPropertyDragDropOp.h" #include "AkWaapiUtils.h" #include "AkWaapiBlueprints/AkWaapiCalls.h" #include "Async/Async.h" #include "AkWaapiUMG/Components/WwiseUmgDragDropOp.h" #include "WaapiPicker/SWaapiPicker.h" #if WITH_EDITOR #include "Modules/ModuleManager.h" #include "PropertyEditorModule.h" #endif #define LOCTEXT_NAMESPACE "AkWaapiUMG" //////////////////////////////////////////////// // SCheckBoxDropHandler //////////////////////////////////////////////// /** Drag-drop zone for adding a Wwise item or a bool property to the CheckBox */ class SCheckBoxDropHandler : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SCheckBoxDropHandler) {} SLATE_DEFAULT_SLOT(FArguments, Content) SLATE_EVENT(FOnDrop, OnDrop) SLATE_END_ARGS() void Construct(const FArguments& InArgs) { OnDropDelegate = InArgs._OnDrop; this->ChildSlot [ SNew(SBorder) [ InArgs._Content.Widget ] ]; } FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override { if (OnDropDelegate.IsBound()) { return OnDropDelegate.Execute(MyGeometry, DragDropEvent); } return FReply::Handled(); } private: FOnDrop OnDropDelegate; }; ///////////////////////////////////////////////////// // UAkCheckBox UAkCheckBox::UAkCheckBox(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SCheckBox::FArguments SlateDefaults; WidgetStyle = *SlateDefaults._Style; CheckedState = ECheckBoxState::Unchecked; HorizontalAlignment = SlateDefaults._HAlign; IsFocusable = true; } void UAkCheckBox::BeginDestroy() { if (SubscriptionIdNameChanged != 0) { bool isUnsubscribed; UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionIdNameChanged), isUnsubscribed); if (isUnsubscribed) SubscriptionIdNameChanged = 0; } if (SubscriptionId != 0) { bool isUnsubscribed; UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionId), isUnsubscribed); if (isUnsubscribed) SubscriptionId = 0; } Super::BeginDestroy(); } void UAkCheckBox::ReleaseSlateResources(bool bReleaseChildren) { Super::ReleaseSlateResources(bReleaseChildren); MyCheckbox.Reset(); } TSharedRef UAkCheckBox::RebuildWidget() { #if WITH_EDITOR FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); PropertyModule.RegisterCustomPropertyTypeLayout("AkBoolPropertyToControl", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAkBoolPropertyToControlCustomization::MakeInstance)); PropertyModule.NotifyCustomizationModuleChanged(); #endif//WITH_EDITOR MyCheckbox = SNew(SCheckBox) .OnCheckStateChanged(BIND_UOBJECT_DELEGATE(FOnCheckStateChanged, SlateOnCheckStateChangedCallback)) .Style(&WidgetStyle) .HAlign(HorizontalAlignment) .IsFocusable(IsFocusable) ; if (GetChildrenCount() > 0) { MyCheckbox->SetContent(GetContentSlot()->Content ? GetContentSlot()->Content->TakeWidget() : SNullWidget::NullWidget); } return SNew(SCheckBoxDropHandler) .OnDrop(FOnDrop::CreateUObject(this, &UAkCheckBox::OnDropHandler)) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(FMargin(3.0f, 3.0f)) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ MyCheckbox.ToSharedRef() ] + SHorizontalBox::Slot() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(5.0f, 0.0f, 0.0f, 0.0f) .HAlign(HAlign_Left) [ SNew(STextBlock) .Text(TAttribute::Create(TAttribute::FGetter::CreateUObject(this, &UAkCheckBox::GetAkItemControlled))) ] + SVerticalBox::Slot() .AutoHeight() .Padding(5.0f, 0.0f, 0.0f, 0.0f) .HAlign(HAlign_Left) [ SNew(STextBlock) .Text(TAttribute::Create(TAttribute::FGetter::CreateUObject(this, &UAkCheckBox::GetAkBoolProperty))) ] ] ]; } void UAkCheckBox::SynchronizeProperties() { Super::SynchronizeProperties(); MyCheckbox->SetStyle(&WidgetStyle); MyCheckbox->SetIsChecked(PROPERTY_BINDING(ECheckBoxState, CheckedState) ); ItemToControl.ItemPath = ItemToControl.ItemPicked.ItemPath; FGuid ItemId; if (!ItemToControl.ItemPicked.ItemId.IsEmpty()) { if(FGuid::ParseExact(ItemToControl.ItemPicked.ItemId, EGuidFormats::DigitsWithHyphensInBraces, ItemId)) { SetAkItemId(ItemId); } } SetAkBoolProperty(ThePropertyToControl.ItemProperty); } void UAkCheckBox::SetAkItemControlled(const FString& Item) { ItemControlled = Item; } FText UAkCheckBox::GetAkItemControlled() { return FText::FromString(TEXT("Item : ") + ItemControlled); } void UAkCheckBox::SetAkItemId(const FGuid& ItemId) { if (ItemId.IsValid()) { IdItemToControl = ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces); TSharedPtr getResult; if (!SWaapiPicker::CallWaapiGetInfoFrom(WwiseWaapiHelper::ID, IdItemToControl, getResult, {})) { return; } TArray> StructJsonArray = getResult->GetArrayField(WwiseWaapiHelper::RETURN); if (StructJsonArray.Num() > 0) { const TSharedPtr& ItemInfoObj = StructJsonArray[0]->AsObject(); SetAkItemControlled(ItemInfoObj->GetStringField(WwiseWaapiHelper::NAME)); } #if AK_SUPPORT_WAAPI /** UnSubscribe to object renamed to be notified from Wwise using WAAPI, so we can maintain the name of the item controlled up to date dynamically. */ // Connect to Wwise Authoring on localhost. if (SubscriptionIdNameChanged != 0) { bool isUnsubscribed; UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionIdNameChanged), isUnsubscribed); if (isUnsubscribed) SubscriptionIdNameChanged = 0; } TSharedPtr outJsonResult; /** Subscribe to object renamed-created-deleted and removed to be notified from Wwise using WAAPI, so we can maintain the Wise picker up to date dynamically. */ // Connect to Wwise Authoring on localhost. FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); if (waapiClient) { auto wampEventCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr in_UEJsonObject) { AsyncTask(ENamedThreads::GameThread, [this, in_UEJsonObject] { SetAkItemControlled(in_UEJsonObject->GetStringField(WwiseWaapiHelper::NEW_NAME)); }); }); // Construct the options Json object : Getting parent infos. TSharedRef in_options = MakeShareable(new FJsonObject()); // Subscribe to object renamed-created-deleted and removed notifications. waapiClient->Subscribe(ak::wwise::core::object::nameChanged, in_options, wampEventCallback, SubscriptionIdNameChanged, outJsonResult); } SynchronizePropertyWithWwise(); #endif } } const FGuid UAkCheckBox::GetAkItemId() const { FGuid ItemId; if (!IdItemToControl.IsEmpty()) { FGuid::ParseExact(IdItemToControl, EGuidFormats::DigitsWithHyphensInBraces, ItemId); } return ItemId; } void UAkCheckBox::SetAkBoolProperty(const FString& BoolProperty) { BoolPropertyToControl = BoolProperty; SynchronizePropertyWithWwise(); } const FString UAkCheckBox::GetAkProperty() const { return BoolPropertyToControl; } FText UAkCheckBox::GetAkBoolProperty() const { return FText::FromString(TEXT("Property : ") + GetAkProperty()); } void UAkCheckBox::OnSlotAdded(UPanelSlot* InSlot) { // Add the child to the live slot if it already exists if ( MyCheckbox.IsValid() ) { MyCheckbox->SetContent(InSlot->Content ? InSlot->Content->TakeWidget() : SNullWidget::NullWidget); } } void UAkCheckBox::SynchronizePropertyWithWwise() { if (!IdItemToControl.IsEmpty() && !BoolPropertyToControl.IsEmpty()) { TSharedPtr ItemInfoResult; if (CallWappiGetPropertySate(IdItemToControl, BoolPropertyToControl, ItemInfoResult)) { bool result = false; if (ItemInfoResult.Get()->TryGetBoolField(WwiseWaapiHelper::AT + BoolPropertyToControl, result)) { SetIsChecked(result); } } if (SubscriptionId != 0) { bool isUnsubscribed; UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionId), isUnsubscribed); if (isUnsubscribed) SubscriptionId = 0; } TSharedPtr outJsonResult; auto wampEventCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr in_UEJsonObject) { bool result = false; if (in_UEJsonObject->TryGetBoolField(WwiseWaapiHelper::NEW, result)) { SetIsChecked(result); } }); SubscribeToPropertyStateChange(IdItemToControl, BoolPropertyToControl, wampEventCallback, SubscriptionId, outJsonResult); } } void UAkCheckBox::OnSlotRemoved(UPanelSlot* InSlot) { // Remove the widget from the live slot if it exists. if ( MyCheckbox.IsValid() ) { MyCheckbox->SetContent(SNullWidget::NullWidget); } } bool UAkCheckBox::IsPressed() const { if ( MyCheckbox.IsValid() ) { return MyCheckbox->IsPressed(); } return false; } bool UAkCheckBox::IsChecked() const { if ( MyCheckbox.IsValid() ) { return MyCheckbox->IsChecked(); } return ( CheckedState == ECheckBoxState::Checked ); } ECheckBoxState UAkCheckBox::GetCheckedState() const { if ( MyCheckbox.IsValid() ) { return MyCheckbox->GetCheckedState(); } return CheckedState; } void UAkCheckBox::SetIsChecked(bool InIsChecked) { CheckedState = InIsChecked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; if ( MyCheckbox.IsValid() ) { MyCheckbox->SetIsChecked(PROPERTY_BINDING(ECheckBoxState, CheckedState)); } } void UAkCheckBox::SetCheckedState(ECheckBoxState InCheckedState) { CheckedState = InCheckedState; if ( MyCheckbox.IsValid() ) { MyCheckbox->SetIsChecked(PROPERTY_BINDING(ECheckBoxState, CheckedState)); } } void UAkCheckBox::SlateOnCheckStateChangedCallback(ECheckBoxState NewState) { CheckedState = NewState; if (ItemControlled.IsEmpty() || BoolPropertyToControl.IsEmpty() || IdItemToControl.IsEmpty()) { UE_LOG(LogAkAudio, Log, TEXT("No item or property to control")); return; } // Construct the arguments Json object : setting configs TSharedRef args = MakeShareable(new FJsonObject()); { args->SetStringField(WwiseWaapiHelper::OBJECT, IdItemToControl); args->SetStringField(WwiseWaapiHelper::PROPERTY, BoolPropertyToControl); args->SetBoolField(WwiseWaapiHelper::VALUE, IsChecked()); // this gives us a range of volume from [-96 to 12] } // Construct the options Json object; TSharedRef options = MakeShareable(new FJsonObject()); // Connect to Wwise Authoring on localhost. FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); if (waapiClient) { #if AK_SUPPORT_WAAPI TSharedPtr outJsonResult; // Request data from Wwise using WAAPI if (waapiClient->Call(ak::wwise::core::object::setProperty, args, options, outJsonResult)) { } else { UE_LOG(LogAkAudio, Log, TEXT("Call Failed")); return; } #endif } else { UE_LOG(LogAkAudio, Log, TEXT("Unable to connect to localhost")); return; } // Choosing to treat Undetermined as Checked const bool bWantsToBeChecked = NewState != ECheckBoxState::Unchecked; AkOnCheckStateChanged.Broadcast(bWantsToBeChecked); } FReply UAkCheckBox::OnDropHandler(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { FReply HandledState = FReply::Unhandled(); TSharedPtr Operation = DragDropEvent.GetOperation(); if (Operation.IsValid()) { if (Operation->IsOfType()) { const auto& AssetDragDropOp = StaticCastSharedPtr(Operation); if (AssetDragDropOp.IsValid()) { const auto& WwiseAssets = AssetDragDropOp->GetWiseItems(); if (WwiseAssets.Num() && WwiseAssets[0].IsValid()) { if (OnItemDropped.IsBound()) { OnItemDropped.Broadcast(WwiseAssets[0]->ItemId); } else { SetAkItemId(WwiseAssets[0]->ItemId); } HandledState = FReply::Handled(); } } } else if (Operation->IsOfType()) { const auto& AssetDragDropOp = StaticCastSharedPtr(Operation); if (AssetDragDropOp.IsValid()) { const auto& WwiseAssets = AssetDragDropOp->GetWiseProperties(); if (WwiseAssets.Num() && WwiseAssets[0].IsValid()) { if (OnPropertyDropped.IsBound()) { OnPropertyDropped.Broadcast(*WwiseAssets[0].Get()); } else { SetAkBoolProperty(*WwiseAssets[0].Get()); } HandledState = FReply::Handled(); } } } } return HandledState; } #if WITH_EDITOR const FText UAkCheckBox::GetPaletteCategory() { return LOCTEXT("Wwise", "Wwise"); } #endif ///////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE