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

@namespace sGBA

@inherits Panel

<root class="@RootClass" style="@PositionStyle" onclick=@(() => OnActivate?.Invoke())>
	<SelectionRing Active=@Selected StrokeWidth=@RingStrokeWidth CornerRadius=@RingCornerRadius Gap=@RingGap Outset=@true class="all-card-ring" />
	@if (Cover != null)
	{
		<TextureImage Texture=@Cover class="all-card-face has-cover" />
	}
	else
	{
		<div class="all-card-face is-missing">
			<MissingCover />
		</div>
	}
	@if (Selected)
	{
		<div class="@TitleClass" style="@TitleStyle">
			<SvgPanel Src="@TitleArrowSrc" class="all-card-title-arrow" style="@TitleArrowStyle" />
			<div class="all-card-title-clip" style="@TitleClipStyle">
				@if (Marquee)
				{
					<MarqueeText Title=@Title class="all-card-title-marquee" />
				}
				else
				{
					<div class="all-card-title-text">@Title</div>
				}
			</div>
		</div>
	}
</root>

@code
{
	[Parameter] public GameEntry Game { get; set; }
	[Parameter] public Texture Cover { get; set; }
	[Parameter] public bool Selected { get; set; }
	[Parameter] public string PositionStyle { get; set; }
	[Parameter] public float CardLeft { get; set; }
	[Parameter] public float CardWidth { get; set; }
	[Parameter] public float GridWidth { get; set; }
	[Parameter] public bool ShowTitleOnTop { get; set; }
	[Parameter] public Action OnActivate { get; set; }

	private const float TitleMaxWidth = 520f;
	private const float TitlePadding = 34f;
	private const float TitleArrowSize = 22f;
	private const float TitleHeight = 64f;
	private const float TitleVerticalOffset = 92f;
	private const float TitleFitSlack = 18f;
	private const float TitleSafezoneInset = 66f;
	private const float ViewportWidth = 1920f;
	private const float TitleFontSize = 30f;
	private const int TitleFontWeight = 400;
	private const string TitleFontName = "Poppins";
	private static readonly Color TitleColor = new( 0.12f, 0.46f, 0.68f, 1f );
	private const float RingStrokeWidth = 8f;
	private const float RingCornerRadius = 14f;
	private const float RingGap = 4f;

	private string Title => Game?.DisplayTitle;
	private string RootClass => "all-card" + (Selected ? " selected" : "");
	private float Scale => MathF.Max( 0.001f, ScaleToScreen );
	private float TitleClipMaxWidth => TitleMaxWidth - TitlePadding * 2f;
	private bool Marquee => !string.IsNullOrWhiteSpace( Title ) && MeasureTitleWidth() > TitleClipMaxWidth;
	private float BubbleWidth => Marquee ? TitleMaxWidth : MathF.Min( TitleMaxWidth, MeasureTitleWidth() + TitleFitSlack + TitlePadding * 2f );
	private float ClipWidth => MathF.Max( 1f, BubbleWidth - TitlePadding * 2f );
	private string TitleClass => "all-card-title" + (Marquee ? " marquee" : "") + (ShowTitleOnTop ? " top" : " bottom");
	private string TitleArrowSrc => ShowTitleOnTop ? "/ui/title-pointer-down.svg" : "/ui/title-pointer.svg";
	private string TitleClipStyle => $"width: {ClipWidth}px;";

	private float MeasureTitleWidth()
	{
		if (string.IsNullOrWhiteSpace( Title ))
			return 1f;

		float scale = Scale;
		var scope = new TextRendering.Scope( Title, TitleColor, TitleFontSize * scale, TitleFontName, TitleFontWeight );
		return MathF.Ceiling( scope.Measure().x / scale );
	}

	private float BodyLeft
	{
		get
		{
			float width = BubbleWidth;
			float desiredLeft = CardLeft + (CardWidth - width) * 0.5f;
			float gridLeftInViewport = (ViewportWidth - GridWidth) * 0.5f;
			float minLeft = TitleSafezoneInset - gridLeftInViewport;
			float maxLeft = ViewportWidth - TitleSafezoneInset - gridLeftInViewport - width;
			return desiredLeft.Clamp( minLeft, MathF.Max( minLeft, maxLeft ) ) - CardLeft;
		}
	}

	private string TitleStyle
	{
		get
		{
			float width = BubbleWidth;
			string vertical = ShowTitleOnTop ? $"top: -{TitleVerticalOffset}px; bottom: auto;" : $"bottom: -{TitleVerticalOffset}px; top: auto;";
			return $"left: {BodyLeft}px; width: {width}px; {vertical}";
		}
	}

	private string TitleArrowStyle
	{
		get
		{
			float arrowLeft = (CardWidth * 0.5f) - BodyLeft - (TitleArrowSize * 0.5f);
			string vertical = ShowTitleOnTop ? $"top: {TitleHeight}px;" : "top: -12px;";
			return $"left: {arrowLeft}px; margin-left: 0px; {vertical}";
		}
	}

	protected override int BuildHash() => HashCode.Combine( Game?.Path, Cover, Selected, ShowTitleOnTop, CardLeft, CardWidth, GridWidth, (int)(Scale * 1000f) );
}