// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Fonts/SlateFontInfo.h" #include "UObject/WeakObjectPtrTemplates.h" #include "Widgets/SCompoundWidget.h" class UNpcScheduleAsset; /** * A schedule's day drawn as one strip: an hour ruler across the top, one row of slots under it with * the actions that claim them, and a legend under that naming every action, the window it holds and * the anchor it happens at. * * It reads the day out of the actions' start times, because a start is all an FScheduledAction has: * the end of a window is the next start in the schedule, which is also the rule the asset states for * itself, so a 06:00-22:00 and a 22:00-06:00 pair tile the day, a window that runs past midnight is * drawn as two halves of one slot with a marker where it carries on, and a schedule whose actions all * start at one minute covers the whole day with each of them, which is what the game reads it as. * * Two actions authored at the same minute therefore claim one slot between them, and that is the only * case that needs more than one row: the day is drawn on as many lanes as there are claims on the * most-claimed minute, so a minute two actions fight over is seen rather than drawn twice in the same * place. * * Nothing here is interactive. It says what the asset says and leaves the writing to the array above * it, which is the one place an action can be authored. * * The strip draws itself from whatever width the panel gives it, read back at paint time, so it grows * and shrinks with the details panel without ever being laid out again to do it. What is worked out * ahead of painting - the slots, the ruler, the colours, the names - is worked out when the schedule * changes and not while painting, because a rebuild is also what walks the level to name the anchors * and that is not something to do once a frame. */ class SNpcScheduleTimeline : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SNpcScheduleTimeline) : _Schedule(nullptr) { } /** The asset to draw. Weak because a details panel outlives the asset it was opened on, and a * deleted asset has to leave an empty strip rather than a dangling one. */ SLATE_ARGUMENT(TWeakObjectPtr, Schedule) SLATE_END_ARGS() void Construct(const FArguments& InArgs); //~ Begin SWidget interface virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; //~ End SWidget interface protected: virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override; private: /** One slot of the day: the window one action claims, which lane it is drawn on because something * else claims that window too, and the two labels it is drawn with. Worked out by Rebuild. */ struct FSlotView { /** First minute of the day in the slot, 0 at midnight. */ int32 StartMinutes = 0; /** The minute the slot ends at, exclusive, and past midnight rather than wrapped to it, so a * slot that carries on at the start of the next day can be told from one that does not. */ int32 EndMinutes = 0; /** Row the slot is drawn on. Only ever more than 0 under a window another action claims too. */ int32 LaneIndex = 0; /** Colour of the activity, so that a legend row and its slot cannot disagree about it. */ FLinearColor Color = FLinearColor::Gray; /** Activity and window, as it reads inside the slot: "Sleep 22:00 - 06:00". */ FText BlockLabel; /** Activity, window and anchor, as it reads in the legend. Longer than BlockLabel because a * slot can be far too narrow to hold anything and the legend is where the rest is said. */ FText LegendLabel; }; /** A cheap read of everything the strip draws, so that a change can be noticed by reading the * asset rather than by subscribing to every way one of its actions can be written: an element * added, removed, reordered or edited inside a row all come out as a different signature here. * What the strip does not draw is deliberately not in it, so moving an anchor to the side of the * room it was already picked at does not make the strip rebuild itself. */ uint32 ComputeSignature() const; /** Works the slots, the lanes and the legend out of the schedule, and names the anchors they point * at with one walk of the open level. Does not repaint: the caller does that, because a rebuild * during Construct happens before there is anything to repaint. */ void Rebuild(uint32 InSignature, double InCurrentTime); /** The asset being drawn. Null while nothing has been assigned or after it was deleted. */ TWeakObjectPtr Schedule; /** Every slot of the day, in the order the day reads: earliest start first, and actions that share * a start in the order the schedule holds them. The legend is drawn straight off this as well, so * that a line and the slot it belongs to cannot come apart. */ TArray Slots; /** How many lanes the slots need. 1 whenever no two actions were authored at the same minute. */ int32 LaneCount = 0; /** Labels of the hour ruler, one every third hour, made once because they never change. */ TArray HourLabels; /** The signature of the schedule the strip was last built from. */ uint32 CachedSignature = 0; /** When that build happened, so that the anchor names can be re-read on a slow timer. */ double LastBuildTime = 0.0; /** The font the ruler, the slots and the legend are drawn in. */ FSlateFontInfo Font; };