RazorTooltipSystem.razor

Razor UI component that implements a global tooltip system. It creates a singleton GameObject with a ScreenPanel and this RazorTooltipSystem, tracks a hovered Panel, updates tooltip position from the mouse, shows/hides tooltip content via RenderFragment and manipulates styling (position, opacity).

File Access
@using System;
@using Sandbox;
@using Sandbox.UI;
@using Sandbox.Razor;
@inherits PanelComponent

<root>
	<div @ref="TooltipPanel" class="tooltip-container tooltip-panel">
		@Contents
	</div>
</root>

@code
{
	public static RazorTooltipSystem Current
	{
		get
		{
			if(!_current.IsValid())
			{
				var go = new GameObject( "RazorTooltipSystem" );
				var screen = go.AddComponent<ScreenPanel>();
				screen.ZIndex = 5000;
				_current = go.AddComponent<RazorTooltipSystem>();
			}
			return _current;
		}
	}
	static RazorTooltipSystem _current;

	public static Panel Hovering { get; set; }

	Vector2 Position { get; set; }
	RenderFragment Contents = null;
	Panel TooltipPanel;

	protected override void OnTreeFirstBuilt()
	{
		Hide();
	}

	protected override void OnUpdate()
	{
		if ( Hovering is not null && (!Hovering.IsValid() || !Hovering.IsVisible || !Hovering.HasHovered))
		{
			Hide();
		}
		else if ( Hovering is null && TooltipPanel.Style.Opacity > 0)
		{
			Hide();
		}

		var pos = Mouse.Position;
		pos += new Vector2(12, 0);
		Position = pos / new Vector2( Screen.Width, Screen.Height ); // Position.LerpTo(pos / new Vector2(Screen.Width, Screen.Height), RealTime.Delta * 10f);

		if (Position.x < 0.5f)
		{
			TooltipPanel.Style.Left = Length.Percent( Position.x * 100f );
			TooltipPanel.Style.Right = Length.Undefined;
		}
		else
		{
			TooltipPanel.Style.Right = Length.Percent( ( 1f - Position.x ) * 100f );
			TooltipPanel.Style.Left = Length.Undefined;
		}

		if (Position.y < 0.5f)
		{
			TooltipPanel.Style.Top = Length.Percent( Position.y * 100f );
			TooltipPanel.Style.Bottom = Length.Undefined;
		}
		else
		{
			TooltipPanel.Style.Bottom = Length.Percent( ( 1f - Position.y ) * 100f );
			TooltipPanel.Style.Top = Length.Undefined;
		}

	}

	public static void Show(RenderFragment fragment)
	{
		var current = Current;
		current.Contents = fragment;
		if ( current.TooltipPanel is not null )
		{
			current.TooltipPanel.Style.Opacity = 1f;
		}
	}

	public static void Hide ()
	{
		var current = Current;

		Hovering = null;
		if ( current is null )
			return;

		if ( current.Contents is not null )
		{
			current.TooltipPanel.Style.From( Styles.Default );
		}
		current.Contents = builder =>
		{
			builder?.AddAttribute( 1, "style", "" );
		};

		if(current.TooltipPanel is null)
			return;

		current.TooltipPanel.Style.Opacity = 0f;
	}

	protected override int BuildHash () => System.HashCode.Combine( Contents );
}