Files
ProjectEleri/Source/ProjectEleriEditor/Public/NpcScheduleTimeline.h

120 lines
5.7 KiB
C++

// 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<UNpcScheduleAsset>, 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<UNpcScheduleAsset> 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<FSlotView> 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<FText> 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;
};