UI/Home/HomeCarousel.razor
@using Sandbox.UI

@namespace sGBA

@inherits Panel

<root class="carousel-window">
	<div class="carousel-strip">
		@foreach (var item in Items)
		{
			var index = item.Index;
			var offset = item.Offset;
			var cover = item.Game != null ? CoverFor?.Invoke( item.Game ) : null;

			<div @key="RenderKey(item.Key)" class="@ItemClass(index, offset, item.IsViewMore)" style="left: @(LeftFor(offset))px;">
				<SelectionRing Active=@ShowRing(index) [email protected] StrokeWidth=@RingStrokeWidth CornerRadius=@RingCornerRadius Gap=@RingGap(item.IsViewMore) Outset=@true class="carousel-selection-ring" />
				@if (item.IsViewMore)
				{
					<ViewMoreCard class="view-more-card" onclick=@(() => OnActivate?.Invoke( index )) />
					<div class="view-more-label">#home.viewmore</div>
				}
				else if (cover != null)
				{
					<TextureImage Texture=@cover class="carousel-card-face has-cover" onclick=@(() => OnActivate?.Invoke( index )) />
				}
				else
				{
					<div class="carousel-card-face is-missing" onclick=@(() => OnActivate?.Invoke( index ))>
						<MissingCover />
					</div>
				}
			</div>
		}
	</div>
</root>

@code
{
	[Parameter] public IEnumerable<HomeCarouselEntry> Items { get; set; }
	[Parameter] public int SelectedIndex { get; set; }
	[Parameter] public bool NavFocused { get; set; }
	[Parameter] public bool ShowSelection { get; set; }
	[Parameter] public int RenderVersion { get; set; }
	[Parameter] public int ArtworkVersion { get; set; }
	[Parameter] public int MountedRange { get; set; }
	[Parameter] public bool Suppress { get; set; }
	[Parameter] public Func<GameEntry, Texture> CoverFor { get; set; }
	[Parameter] public Action<int> OnActivate { get; set; }

	private const float SelectedLeft = 210f;
	private const float NormalCardWidth = 300f;
	private const float FeaturedCardWidth = 420f;
	private const float CardGap = 34f;
	private const float RingStrokeWidth = 8f;
	private const float RingCornerRadius = 17f;
	private const float GameRingGap = 8f;
	private const float ViewMoreRingGap = 0f;

	private float LeftFor( int offset )
	{
		if (offset == 0)
			return SelectedLeft;

		if (offset < 0)
			return SelectedLeft - ((NormalCardWidth + CardGap) * -offset);

		float anchorVisualWidth = NavFocused ? NormalCardWidth : FeaturedCardWidth;
		return SelectedLeft + anchorVisualWidth + CardGap + ((NormalCardWidth + CardGap) * (offset - 1));
	}

	private bool IsMounted( int offset ) => offset >= -MountedRange && offset <= MountedRange;

	private string ItemClass( int index, int offset, bool isViewMore )
	{
		var cls = "carousel-item";
		if (isViewMore) cls += " view-more";
		if (index == SelectedIndex && !NavFocused) cls += " featured selected";
		if (index < SelectedIndex) cls += " before";
		if (index > SelectedIndex) cls += " after";
		if (!IsMounted( offset )) cls += " offscreen";
		return cls;
	}

	private string RenderKey( string key ) => $"{key}:{RenderVersion}";

	private static float RingGap( bool isViewMore ) => isViewMore ? ViewMoreRingGap : GameRingGap;

	private bool ShowRing( int index ) => ShowSelection && index == SelectedIndex && !NavFocused;

	protected override void OnParametersSet()
	{
		base.OnParametersSet();

		if (Suppress)
			SkipTransitions();
	}

	protected override int BuildHash() => HashCode.Combine( Items, SelectedIndex, NavFocused, ShowSelection, RenderVersion, ArtworkVersion );
}