Editor/sandmod.libraryplus/Widget/LibraryList.cs
using System;
using System.Linq;
using System.Threading.Tasks;
using Editor;
using Sandbox;

namespace LibraryPlus;

public class LibraryList : ListView
{
	public LibraryList( Widget parget ) : base( parget )
	{
		ItemContextMenu = ShowItemContext;
		ItemSelected = OnItemClicked;

		ItemSize = new Vector2( -1, 64 );
	}

	public Action<Package> OnLibrarySelected { get; set; }

	public bool ShowInstalled { get; set; }
	public bool ShowAvailable { get; set; }

	public string Filter { get; set; } = "";

	public void OnItemClicked( object value )
	{
		if ( value is Library lib )
		{
			OnLibrarySelected?.Invoke( lib.Package );
		}

		if ( value is Package p )
		{
			OnLibrarySelected?.Invoke( p );
		}
	}

	private void ShowItemContext( object obj )
	{
		var project = obj as Library;
		var package = obj as Package ?? project?.Package;

		var m = new ContextMenu();

		if ( package != null && package.Org.Ident != "local" )
		{
			m.AddOption( "View in Browser", "public", () => EditorUtility.OpenFolder( package.Url ) );
			m.AddSeparator();
		}

		if ( project is PlusLibrary plusLibrary )
		{
			m.AddOption( "Convert to Standard Library", "sync", () => LibrarySystemPlus.Convert( plusLibrary ) );
			m.AddSeparator();
		}
		else if ( project is ProjectLibrary projectLibrary )
		{
			m.AddOption( "Convert to Plus Library", "sync", () => LibrarySystemPlus.Convert( projectLibrary ) );
			m.AddSeparator();
			m.AddOption( "Project Properties", "tune",
				() => _ = ProjectSettingsWindow.OpenForProject( projectLibrary.Project.Project ) );
			m.AddSeparator();
			m.AddOption( "Publish Project", "upload_file",
				() => _ = ProjectSettingsWindow.OpenForProject( projectLibrary.Project.Project, "upload" ) );
			m.AddSeparator();
		}

		if ( project != null )
		{
			m.AddOption( "Show in Explorer", "folder",
				() => EditorUtility.OpenFolder( project.FileSystem.Settings.FullName ) );
		}

		{
			m.OpenAtCursor();
		}
	}

	protected override void PaintItem( VirtualWidget item )
	{
		if ( item.Object is Library c )
		{
			PaintItem( item, c.Package );
		}

		if ( item.Object is Package p )
		{
			PaintItem( item, p );
		}
	}

	private void PaintItem( VirtualWidget item, Package package )
	{
		var rect = item.Rect;

		if ( Paint.HasPressed )
		{
			rect = rect.Shrink( 2, 2, 0, 0 );
		}

		var library = LibrarySystemPlus.All
			.FirstOrDefault( x => x.Package.ReferenceIdent() == package.ReferenceIdent() );

		var pen = Color.White.WithAlpha( 0.6f );

		if ( Paint.HasMouseOver || Paint.HasSelected )
		{
			Paint.SetBrush( Theme.Primary.WithAlpha( Paint.HasMouseOver ? 0.8f : 0.5f ) );
			Paint.ClearPen();
			Paint.DrawRect( rect, 4 );

			pen = Color.White.WithAlpha( Paint.HasMouseOver ? 1 : 0.9f );
			Paint.ClearBrush();
		}

		rect = rect.Shrink( 4 );

		// Icon
		{
			var iconRect = rect;
			iconRect.Width = iconRect.Height;
			iconRect = iconRect.Shrink( 8 );

			Paint.SetBrushAndPen( Theme.Grey.WithAlpha( 0.4f ) );

			if ( !string.IsNullOrWhiteSpace( package.Thumb ) && !package.Thumb.StartsWith( "/" ) )
			{
				Paint.Draw( iconRect, package.Thumb );
			}
			else
			{
				Paint.DrawRect( iconRect, 4 );
			}

			rect.Left = iconRect.Right + 8;
		}

		//header
		{
			rect.Top += 8;

			Paint.SetFont( "Poppins", 11, 450 );
			Paint.Pen = pen;
			var r = Paint.DrawText( rect, package.Title, TextFlag.LeftTop );

			r.Left = r.Right + 8;
			r.Right = rect.Right;
			r.Bottom -= 3;

			rect.Top = r.Bottom + 4;

			var installedVersion = library != null ? $"v{library?.Version}" : "";
			var type = library != null ? $"({(library.IsPlus ? "plus" : "standard")})" : "";

			Paint.Pen = pen.WithAlphaMultiplied( 0.6f );
			Paint.SetDefaultFont();
			Paint.DrawText( r,
				$"{package.Org.Title} {installedVersion} {type}",
				TextFlag.LeftBottom );
		}

		// body text
		{
			Paint.Pen = pen.WithAlphaMultiplied( 0.6f );
			Paint.SetDefaultFont();
			Paint.DrawText( rect, package.Summary, TextFlag.LeftTop );
		}
	}

	protected override void OnPaint()
	{
		Paint.ClearPen();
		Paint.SetBrush( Theme.ControlBackground );
		Paint.DrawRect( LocalRect );

		Paint.Antialiasing = true;
		Paint.TextAntialiasing = true;

		base.OnPaint();
	}

	[EditorEvent.Frame]
	public void Frame()
	{
		if ( !Visible )
		{
			return;
		}

		if ( ShowInstalled )
		{
			if ( SetContentHash( GetContentHash(), 0.5f ) )
			{
				SetItems( LibrarySystemPlus.All );
				SelectItem( LibrarySystemPlus.All );
			}
		}

		if ( ShowAvailable )
		{
			if ( SetContentHash( GetContentHash(), 0.5f ) )
			{
				_ = UpdateAsync();
			}
		}
	}

	private async Task UpdateAsync()
	{
		var search = "";
		if ( !string.IsNullOrEmpty( Filter ) )
		{
			search = Filter + " ";
		}

		var found = await Package.FindAsync( $"{search}type:library sort:updated" );
		SetItems( found.Packages );

		SelectItem( found.Packages.FirstOrDefault() );
	}

	private int GetContentHash()
	{
		if ( ShowInstalled )
		{
			return LibrarySystemPlus.All.GetHashCode();
		}

		return HashCode.Combine( Filter );
	}
}