// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "IPropertyTypeCustomization.h" #include "Styling/SlateColor.h" class AActor; class IPropertyHandle; class SWidget; /** * Details panel layout for one FScheduledAction, the element type of * UNpcScheduleAsset::ScheduledActions. * * An action's anchor is stored as a PersistentId rather than as an actor pointer, so that a re-cook or * a re-save cannot leave a schedule dangling. The price of that is a value nobody can type in, so this * customization replaces the raw Guid row with one that names the actor the id currently resolves to, * offers the current selection and every anchor in the level in a menu, and has a button that clears * it. The id therefore always comes from the actor itself and can never drift away from it. A row for * an id that no actor in this level claims is left as it is, because that is a real state (furniture * that was deleted, or furniture sitting in a sublevel that is not loaded) and not something to hide. * * Every other member of the struct keeps the row the details panel would have given it. */ class FNpcScheduleActionCustomization : public IPropertyTypeCustomization { public: static TSharedRef MakeInstance(); virtual void CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override; virtual void CustomizeChildren(TSharedRef PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) override; protected: /** The anchor row: the label, the name of the actor the id resolves to, the assign menu, and the * unassign button. Stands in for the raw Guid field, which is never shown. */ void AddAnchorRow(IDetailChildrenBuilder& ChildBuilder, TSharedRef AnchorHandle) const; /** What the id points at: the actor's label, "None" when nothing is assigned, or the raw id when no * actor in this level claims it. */ FText GetAnchorText(TSharedRef AnchorHandle) const; /** The id itself and what it means, so that an id can be read off the panel and matched against a * log or a save without the panel offering a text box to mistype it into. */ FText GetAnchorTooltip(TSharedRef AnchorHandle) const; /** Subdues the row while nothing is assigned, so an action with no anchor reads differently at a * glance from one whose anchor the panel cannot name. */ FSlateColor GetAnchorColor(TSharedRef AnchorHandle) const; bool CanClearAnchor(TSharedRef AnchorHandle) const; /** Writes one id into the action. An invalid id is what unassign does, so the button that clears * the anchor and the menu entries that set one both come through here. */ void AssignAnchor(TSharedRef AnchorHandle, FGuid NewAnchorGuid) const; /** The assign menu: the current selection first, then every anchor in the level, with the one in * use radioed. */ TSharedRef BuildAnchorMenu(TSharedRef AnchorHandle) const; /** The id a property handle currently holds, read as text so that it works whether the handle is * reached on its own, inside the array, or through a multi selection, which reports no single * value and so comes back as unassigned. */ static FGuid ReadAnchorGuid(const TSharedRef& AnchorHandle); /** The first actor in the current selection that can be an anchor, or null when there is none. */ static AActor* GetSelectedAnchorActor(); /** The id of an actor, invalid when the actor cannot be an anchor. Implementing * IActorPersistentIdInterface is the whole of that rule, since the interface is what carries the id * a schedule stores. */ static FGuid GetAnchorId(AActor* Actor); /** The actor a single id belongs to, or null when no actor in the current level claims it. */ static AActor* FindActorWithGuid(const FGuid& Guid); /** How many actors in the current level claim an id. More than one is a real state - a pasted actor * carries its source's id - and everything that stores the id, this schedule included, resolves to * whichever of them a search reaches first, so the row says so rather than leaving it to be found * in game. Walked on the spot rather than indexed, because this is read while the panel is drawn * and an index would be stale the moment the author adds, deletes or pastes an anchor. */ static int32 CountActorsWithGuid(const FGuid& Guid); /** Every actor in the current level that can be an anchor, sorted by label so that the menu does not * reshuffle itself between two openings. */ static void CollectAnchorActors(TArray& OutAnchors); /** For the menu entries that only exist to explain themselves, such as "nothing is selected". */ static bool NeverExecutable(); /** Past this many anchors the menu stops listing actors and says how many it left out, because a * level can hold hundreds and a menu is not a browser. Assigning from the selection still works. */ static constexpr int32 MaxListedAnchors = 40; };