460 results

using System.Runtime.CompilerServices;

public static class OpenSimplex2S
{
	private const long PRIME_X = 0x5205402B9270C86FL;
	private const long PRIME_Y = 0x598CD327003817B5L;
	private const long PRIME_Z = 0x5BCC226E9FA0BACBL;
	private const long PRIME_W = 0x56CC5227E58F554BL;
	private const long HASH_MULTIPLIER = 0x53A3F72DEEC546F5L;
	private const long SEED_FLIP_3D = -0x52D547B2E96ED629L;

	private const double ROOT2OVER2 = 0.7071067811865476;
	private const double SKEW_2D = 0.366025403784439;
	private const double UNSKEW_2D = -0.21132486540518713;

	private const double ROOT3OVER3 = 0.577350269189626;
	private const double FALLBACK_ROTATE3 = 2.0 / 3.0;
	private const double ROTATE3_ORTHOGONALIZER = UNSKEW_2D;

	private const float SKEW_4D = 0.309016994374947f;
	private const float UNSKEW_4D = -0.138196601125011f;

	private const int N_GRADS_2D_EXPONENT = 7;
	private const int N_GRADS_3D_EXPONENT = 8;
	private const int N_GRADS_4D_EXPONENT = 9;
	private const int N_GRADS_2D = 1 << N_GRADS_2D_EXPONENT;
	private const int N_GRADS_3D = 1 << N_GRADS_3D_EXPONENT;
	private const int N_GRADS_4D = 1 << N_GRADS_4D_EXPONENT;

	private const double NORMALIZER_2D = 0.05481866495625118;
	private const double NORMALIZER_3D = 0.2781926117527186;
	private const double NORMALIZER_4D = 0.11127401889945551;

	private const float RSQUARED_2D = 2.0f / 3.0f;
	private const float RSQUARED_3D = 3.0f / 4.0f;
	private const float RSQUARED_4D = 4.0f / 5.0f;

	/*
     * Noise Evaluators
     */

	/**
     * 2D OpenSimplex2S/SuperSimplex noise, standard lattice orientation.
     */
	public static float Noise2( long seed, double x, double y )
	{
		// Get points for A2* lattice
		double s = SKEW_2D * (x + y);
		double xs = x + s, ys = y + s;

		return Noise2_UnskewedBase( seed, xs, ys );
	}

	/**
     * 2D OpenSimplex2S/SuperSimplex noise, with Y pointing down the main diagonal.
     * Might be better for a 2D sandbox style game, where Y is vertical.
     * Probably slightly less optimal for heightmaps or continent maps,
     * unless your map is centered around an equator. It's a slight
     * difference, but the option is here to make it easy.
     */
	public static float Noise2_ImproveX( long seed, double x, double y )
	{
		// Skew transform and rotation baked into one.
		double xx = x * ROOT2OVER2;
		double yy = y * (ROOT2OVER2 * (1 + 2 * SKEW_2D));

		return Noise2_UnskewedBase( seed, yy + xx, yy - xx );
	}

	/**
     * 2D  OpenSimplex2S/SuperSimplex noise base.
     */
	private static float Noise2_UnskewedBase( long seed, double xs, double ys )
	{
		// Get base points and offsets.
		int xsb = FastFloor( xs ), ysb = FastFloor( ys );
		float xi = (float)(xs - xsb), yi = (float)(ys - ysb);

		// Prime pre-multiplication for hash.
		long xsbp = xsb * PRIME_X, ysbp = ysb * PRIME_Y;

		// Unskew.
		float t = (xi + yi) * (float)UNSKEW_2D;
		float dx0 = xi + t, dy0 = yi + t;

		// First vertex.
		float a0 = RSQUARED_2D - dx0 * dx0 - dy0 * dy0;
		float value = (a0 * a0) * (a0 * a0) * Grad( seed, xsbp, ysbp, dx0, dy0 );

		// Second vertex.
		float a1 = (float)(2 * (1 + 2 * UNSKEW_2D) * (1 / UNSKEW_2D + 2)) * t + ((float)(-2 * (1 + 2 * UNSKEW_2D) * (1 + 2 * UNSKEW_2D)) + a0);
		float dx1 = dx0 - (float)(1 + 2 * UNSKEW_2D);
		float dy1 = dy0 - (float)(1 + 2 * UNSKEW_2D);
		value += (a1 * a1) * (a1 * a1) * Grad( seed, xsbp + PRIME_X, ysbp + PRIME_Y, dx1, dy1 );

		// Third and fourth vertices.
		// Nested conditionals were faster than compact bit logic/arithmetic.
		float xmyi = xi - yi;
		if ( t < UNSKEW_2D )
		{
			if ( xi + xmyi > 1 )
			{
				float dx2 = dx0 - (float)(3 * UNSKEW_2D + 2);
				float dy2 = dy0 - (float)(3 * UNSKEW_2D + 1);
				float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;
				if ( a2 > 0 )
				{
					value += (a2 * a2) * (a2 * a2) * Grad( seed, xsbp + (PRIME_X << 1), ysbp + PRIME_Y, dx2, dy2 );
				}
			}
			else
			{
				float dx2 = dx0 - (float)UNSKEW_2D;
				float dy2 = dy0 - (float)(UNSKEW_2D + 1);
				float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;
				if ( a2 > 0 )
				{
					value += (a2 * a2) * (a2 * a2) * Grad( seed, xsbp, ysbp + PRIME_Y, dx2, dy2 );
				}
			}

			if ( yi - xmyi > 1 )
			{
				float dx3 = dx0 - (float)(3 * UNSKEW_2D + 1);
				float dy3 = dy0 - (float)(3 * UNSKEW_2D + 2);
				float a3 = RSQUARED_2D - dx3 * dx3 - dy3 * dy3;
				if ( a3 > 0 )
				{
					value += (a3 * a3) * (a3 * a3) * Grad( seed, xsbp + PRIME_X, ysbp + (PRIME_Y << 1), dx3, dy3 );
				}
			}
			else
			{
				float dx3 = dx0 - (float)(UNSKEW_2D + 1);
				float dy3 = dy0 - (float)UNSKEW_2D;
				float a3 = RSQUARED_2D - dx3 * dx3 - dy3 * dy3;
				if ( a3 > 0 )
				{
					value += (a3 * a3) * (a3 * a3) * Grad( seed, xsbp + PRIME_X, ysbp, dx3, dy3 );
				}
			}
		}
		else
		{
			if ( xi + xmyi < 0 )
			{
				float dx2 = dx0 + (float)(1 + UNSKEW_2D);
				float dy2 = dy0 + (float)UNSKEW_2D;
				float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;
				if ( a2 > 0 )
				{
					value += (a2 * a2) * (a2 * a2) * Grad( seed, xsbp - PRIME_X, ysbp, dx2, dy2 );
				}
			}
			else
			{
				float dx2 = dx0 - (float)(UNSKEW_2D + 1);
				float dy2 = dy0 - (float)UNSKEW_2D;
				float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;
				if ( a2 > 0 )
				{
					value += (a2 * a2) * (a2 * a2) * Grad( seed, xsbp + PRIME_X, ysbp, dx2, dy2 );
				}
			}

			if ( yi < xmyi )
			{
				float dx2 = dx0 + (float)UNSKEW_2D;
				float dy2 = dy0 + (float)(UNSKEW_2D + 1);
				float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;
				if ( a2 > 0 )
				{
					value += (a2 * a2) * (a2 * a2) * Grad( seed, xsbp, ysbp - PRIME_Y, dx2, dy2 );
				}
			}
			else
			{
				float dx2 = dx0 - (float)UNSKEW_2D;
				float dy2 = dy0 - (float)(UNSKEW_2D + 1);
				float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;
				if ( a2 > 0 )
				{
					value += (a2 * a2) * (a2 * a2) * Grad( seed, xsbp, ysbp + PRIME_Y, dx2, dy2 );
				}
			}
		}

		return value;
	}

	/**
     * 3D OpenSimplex2S/SuperSimplex noise, with better visual isotropy in (X, Y).
     * Recommended for 3D terrain and time-varied animations.
     * The Z coordinate should always be the "different" coordinate in whatever your use case is.
     * If Y is vertical in world coordinates, call Noise3_ImproveXZ(x, z, Y) or use Noise3_XZBeforeY.
     * If Z is vertical in world coordinates, call Noise3_ImproveXZ(x, y, Z).
     * For a time varied animation, call Noise3_ImproveXY(x, y, T).
     */
	public static float Noise3_ImproveXY( long seed, double x, double y, double z )
	{
		// Re-orient the cubic lattices without skewing, so Z points up the main lattice diagonal,
		// and the planes formed by XY are moved far out of alignment with the cube faces.
		// Orthonormal rotation. Not a skew transform.
		double xy = x + y;
		double s2 = xy * ROTATE3_ORTHOGONALIZER;
		double zz = z * ROOT3OVER3;
		double xr = x + s2 + zz;
		double yr = y + s2 + zz;
		double zr = xy * -ROOT3OVER3 + zz;

		// Evaluate both lattices to form a BCC lattice.
		return Noise3_UnrotatedBase( seed, xr, yr, zr );
	}

	/**
     * 3D OpenSimplex2S/SuperSimplex noise, with better visual isotropy in (X, Z).
     * Recommended for 3D terrain and time-varied animations.
     * The Y coordinate should always be the "different" coordinate in whatever your use case is.
     * If Y is vertical in world coordinates, call Noise3_ImproveXZ(x, Y, z).
     * If Z is vertical in world coordinates, call Noise3_ImproveXZ(x, Z, y) or use Noise3_ImproveXY.
     * For a time varied animation, call Noise3_ImproveXZ(x, T, y) or use Noise3_ImproveXY.
     */
	public static float Noise3_ImproveXZ( long seed, double x, double y, double z )
	{
		// Re-orient the cubic lattices without skewing, so Y points up the main lattice diagonal,
		// and the planes formed by XZ are moved far out of alignment with the cube faces.
		// Orthonormal rotation. Not a skew transform.
		double xz = x + z;
		double s2 = xz * -0.211324865405187;
		double yy = y * ROOT3OVER3;
		double xr = x + s2 + yy;
		double zr = z + s2 + yy;
		double yr = xz * -ROOT3OVER3 + yy;

		// Evaluate both lattices to form a BCC lattice.
		return Noise3_UnrotatedBase( seed, xr, yr, zr );
	}

	/**
     * 3D OpenSimplex2S/SuperSimplex noise, fallback rotation option
     * Use Noise3_ImproveXY or Noise3_ImproveXZ instead, wherever appropriate.
     * They have less diagonal bias. This function's best use is as a fallback.
     */
	public static float Noise3_Fallback( long seed, double x, double y, double z )
	{
		// Re-orient the cubic lattices via rotation, to produce a familiar look.
		// Orthonormal rotation. Not a skew transform.
		double r = FALLBACK_ROTATE3 * (x + y + z);
		double xr = r - x, yr = r - y, zr = r - z;

		// Evaluate both lattices to form a BCC lattice.
		return Noise3_UnrotatedBase( seed, xr, yr, zr );
	}

	/**
     * Generate overlapping cubic lattices for 3D Re-oriented BCC noise.
     * Lookup table implementation inspired by DigitalShadow.
     * It was actually faster to narrow down the points in the loop itself,
     * than to build up the index with enough info to isolate 8 points.
     */
	private static float Noise3_UnrotatedBase( long seed, double xr, double yr, double zr )
	{
		// Get base points and offsets.
		int xrb = FastFloor( xr ), yrb = FastFloor( yr ), zrb = FastFloor( zr );
		float xi = (float)(xr - xrb), yi = (float)(yr - yrb), zi = (float)(zr - zrb);

		// Prime pre-multiplication for hash. Also flip seed for second lattice copy.
		long xrbp = xrb * PRIME_X, yrbp = yrb * PRIME_Y, zrbp = zrb * PRIME_Z;
		long seed2 = seed ^ -0x52D547B2E96ED629L;

		// -1 if positive, 0 if negative.
		int xNMask = (int)(-0.5f - xi), yNMask = (int)(-0.5f - yi), zNMask = (int)(-0.5f - zi);

		// First vertex.
		float x0 = xi + xNMask;
		float y0 = yi + yNMask;
		float z0 = zi + zNMask;
		float a0 = RSQUARED_3D - x0 * x0 - y0 * y0 - z0 * z0;
		float value = (a0 * a0) * (a0 * a0) * Grad( seed,
			xrbp + (xNMask & PRIME_X), yrbp + (yNMask & PRIME_Y), zrbp + (zNMask & PRIME_Z), x0, y0, z0 );

		// Second vertex.
		float x1 = xi - 0.5f;
		float y1 = yi - 0.5f;
		float z1 = zi - 0.5f;
		float a1 = RSQUARED_3D - x1 * x1 - y1 * y1 - z1 * z1;
		value += (a1 * a1) * (a1 * a1) * Grad( seed2,
			xrbp + PRIME_X, yrbp + PRIME_Y, zrbp + PRIME_Z, x1, y1, z1 );

		// Shortcuts for building the remaining falloffs.
		// Derived by subtracting the polynomials with the offsets plugged in.
		float xAFlipMask0 = ((xNMask | 1) << 1) * x1;
		float yAFlipMask0 = ((yNMask | 1) << 1) * y1;
		float zAFlipMask0 = ((zNMask | 1) << 1) * z1;
		float xAFlipMask1 = (-2 - (xNMask << 2)) * x1 - 1.0f;
		float yAFlipMask1 = (-2 - (yNMask << 2)) * y1 - 1.0f;
		float zAFlipMask1 = (-2 - (zNMask << 2)) * z1 - 1.0f;

		bool skip5 = false;
		float a2 = xAFlipMask0 + a0;
		if ( a2 > 0 )
		{
			float x2 = x0 - (xNMask | 1);
			float y2 = y0;
			float z2 = z0;
			value += (a2 * a2) * (a2 * a2) * Grad( seed,
				xrbp + (~xNMask & PRIME_X), yrbp + (yNMask & PRIME_Y), zrbp + (zNMask & PRIME_Z), x2, y2, z2 );
		}
		else
		{
			float a3 = yAFlipMask0 + zAFlipMask0 + a0;
			if ( a3 > 0 )
			{
				float x3 = x0;
				float y3 = y0 - (yNMask | 1);
				float z3 = z0 - (zNMask | 1);
				value += (a3 * a3) * (a3 * a3) * Grad( seed,
					xrbp + (xNMask & PRIME_X), yrbp + (~yNMask & PRIME_Y), zrbp + (~zNMask & PRIME_Z), x3, y3, z3 );
			}

			float a4 = xAFlipMask1 + a1;
			if ( a4 > 0 )
			{
				float x4 = (xNMask | 1) + x1;
				float y4 = y1;
				float z4 = z1;
				value += (a4 * a4) * (a4 * a4) * Grad( seed2,
					xrbp + (xNMask & unchecked(PRIME_X * 2)), yrbp + PRIME_Y, zrbp + PRIME_Z, x4, y4, z4 );
				skip5 = true;
			}
		}

		bool skip9 = false;
		float a6 = yAFlipMask0 + a0;
		if ( a6 > 0 )
		{
			float x6 = x0;
			float y6 = y0 - (yNMask | 1);
			float z6 = z0;
			value += (a6 * a6) * (a6 * a6) * Grad( seed,
				xrbp + (xNMask & PRIME_X), yrbp + (~yNMask & PRIME_Y), zrbp + (zNMask & PRIME_Z), x6, y6, z6 );
		}
		else
		{
			float a7 = xAFlipMask0 + zAFlipMask0 + a0;
			if ( a7 > 0 )
			{
				float x7 = x0 - (xNMask | 1);
				float y7 = y0;
				float z7 = z0 - (zNMask | 1);
				value += (a7 * a7) * (a7 * a7) * Grad( seed,
					xrbp + (~xNMask & PRIME_X), yrbp + (yNMask & PRIME_Y), zrbp + (~zNMask & PRIME_Z), x7, y7, z7 );
			}

			float a8 = yAFlipMask1 + a1;
			if ( a8 > 0 )
			{
				float x8 = x1;
				float y8 = (yNMask | 1) + y1;
				float z8 = z1;
				value += (a8 * a8) * (a8 * a8) * Grad( seed2,
					xrbp + PRIME_X, yrbp + (yNMask & (PRIME_Y << 1)), zrbp + PRIME_Z, x8, y8, z8 );
				skip9 = true;
			}
		}

		bool skipD = false;
		float aA = zAFlipMask0 + a0;
		if ( aA > 0 )
		{
			float xA = x0;
			float yA = y0;
			float zA = z0 - (zNMask | 1);
			value += (aA * aA) * (aA * aA) * Grad( seed,
				xrbp + (xNMask & PRIME_X), yrbp + (yNMask & PRIME_Y), zrbp + (~zNMask & PRIME_Z), xA, yA, zA );
		}
		else
		{
			float aB = xAFlipMask0 + yAFlipMask0 + a0;
			if ( aB > 0 )
			{
				float xB = x0 - (xNMask | 1);
				float yB = y0 - (yNMask | 1);
				float zB = z0;
				value += (aB * aB) * (aB * aB) * Grad( seed,
					xrbp + (~xNMask & PRIME_X), yrbp + (~yNMask & PRIME_Y), zrbp + (zNMask & PRIME_Z), xB, yB, zB );
			}

			float aC = zAFlipMask1 + a1;
			if ( aC > 0 )
			{
				float xC = x1;
				float yC = y1;
				float zC = (zNMask | 1) + z1;
				value += (aC * aC) * (aC * aC) * Grad( seed2,
					xrbp + PRIME_X, yrbp + PRIME_Y, zrbp + (zNMask & (PRIME_Z << 1)), xC, yC, zC );
				skipD = true;
			}
		}

		if ( !skip5 )
		{
			float a5 = yAFlipMask1 + zAFlipMask1 + a1;
			if ( a5 > 0 )
			{
				float x5 = x1;
				float y5 = (yNMask | 1) + y1;
				float z5 = (zNMask | 1) + z1;
				value += (a5 * a5) * (a5 * a5) * Grad( seed2,
					xrbp + PRIME_X, yrbp + (yNMask & (PRIME_Y << 1)), zrbp + (zNMask & (PRIME_Z << 1)), x5, y5, z5 );
			}
		}

		if ( !skip9 )
		{
			float a9 = xAFlipMask1 + zAFlipMask1 + a1;
			if ( a9 > 0 )
			{
				float x9 = (xNMask | 1) + x1;
				float y9 = y1;
				float z9 = (zNMask | 1) + z1;
				value += (a9 * a9) * (a9 * a9) * Grad( seed2,
					xrbp + (xNMask & unchecked(PRIME_X * 2)), yrbp + PRIME_Y, zrbp + (zNMask & (PRIME_Z << 1)), x9, y9, z9 );
			}
		}

		if ( !skipD )
		{
			float aD = xAFlipMask1 + yAFlipMask1 + a1;
			if ( aD > 0 )
			{
				float xD = (xNMask | 1) + x1;
				float yD = (yNMask | 1) + y1;
				float zD = z1;
				value += (aD * aD) * (aD * aD) * Grad( seed2,
					xrbp + (xNMask & (PRIME_X << 1)), yrbp + (yNMask & (PRIME_Y << 1)), zrbp + PRIME_Z, xD, yD, zD );
			}
		}

		return value;
	}

	/**
     * 4D SuperSimplex noise, with XYZ oriented like Noise3_ImproveXY
     * and W for an extra degree of freedom. W repeats eventually.
     * Recommended for time-varied animations which texture a 3D object (W=time)
     * in a space where Z is vertical
     */
	public static float Noise4_ImproveXYZ_ImproveXY( long seed, double x, double y, double z, double w )
	{
		double xy = x + y;
		double s2 = xy * -0.21132486540518699998;
		double zz = z * 0.28867513459481294226;
		double ww = w * 1.118033988749894;
		double xr = x + (zz + ww + s2), yr = y + (zz + ww + s2);
		double zr = xy * -0.57735026918962599998 + (zz + ww);
		double wr = z * -0.866025403784439 + ww;

		return Noise4_UnskewedBase( seed, xr, yr, zr, wr );
	}

	/**
     * 4D SuperSimplex noise, with XYZ oriented like Noise3_ImproveXZ
     * and W for an extra degree of freedom. W repeats eventually.
     * Recommended for time-varied animations which texture a 3D object (W=time)
     * in a space where Y is vertical
     */
	public static float Noise4_ImproveXYZ_ImproveXZ( long seed, double x, double y, double z, double w )
	{
		double xz = x + z;
		double s2 = xz * -0.21132486540518699998;
		double yy = y * 0.28867513459481294226;
		double ww = w * 1.118033988749894;
		double xr = x + (yy + ww + s2), zr = z + (yy + ww + s2);
		double yr = xz * -0.57735026918962599998 + (yy + ww);
		double wr = y * -0.866025403784439 + ww;

		return Noise4_UnskewedBase( seed, xr, yr, zr, wr );
	}

	/**
     * 4D SuperSimplex noise, with XYZ oriented like Noise3_Fallback
     * and W for an extra degree of freedom. W repeats eventually.
     * Recommended for time-varied animations which texture a 3D object (W=time)
     * where there isn't a clear distinction between horizontal and vertical
     */
	public static float Noise4_ImproveXYZ( long seed, double x, double y, double z, double w )
	{
		double xyz = x + y + z;
		double ww = w * 1.118033988749894;
		double s2 = xyz * -0.16666666666666666 + ww;
		double xs = x + s2, ys = y + s2, zs = z + s2, ws = -0.5 * xyz + ww;

		return Noise4_UnskewedBase( seed, xs, ys, zs, ws );
	}

	/**
     * 4D SuperSimplex noise, fallback lattice orientation.
     */
	public static float Noise4_Fallback( long seed, double x, double y, double z, double w )
	{
		// Get points for A4 lattice
		double s = SKEW_4D * (x + y + z + w);
		double xs = x + s, ys = y + s, zs = z + s, ws = w + s;

		return Noise4_UnskewedBase( seed, xs, ys, zs, ws );
	}

	/**
     * 4D SuperSimplex noise base.
     * Using ultra-simple 4x4x4x4 lookup partitioning.
     * This isn't as elegant or SIMD/GPU/etc. portable as other approaches,
     * but it competes performance-wise with optimized 2014 OpenSimplex.
     */
	private static float Noise4_UnskewedBase( long seed, double xs, double ys, double zs, double ws )
	{
		// Get base points and offsets
		int xsb = FastFloor( xs ), ysb = FastFloor( ys ), zsb = FastFloor( zs ), wsb = FastFloor( ws );
		float xsi = (float)(xs - xsb), ysi = (float)(ys - ysb), zsi = (float)(zs - zsb), wsi = (float)(ws - wsb);

		// Unskewed offsets
		float ssi = (xsi + ysi + zsi + wsi) * UNSKEW_4D;
		float xi = xsi + ssi, yi = ysi + ssi, zi = zsi + ssi, wi = wsi + ssi;

		// Prime pre-multiplication for hash.
		long xsvp = xsb * PRIME_X, ysvp = ysb * PRIME_Y, zsvp = zsb * PRIME_Z, wsvp = wsb * PRIME_W;

		// Index into initial table.
		int index = ((FastFloor( xs * 4 ) & 3) << 0)
			| ((FastFloor( ys * 4 ) & 3) << 2)
			| ((FastFloor( zs * 4 ) & 3) << 4)
			| ((FastFloor( ws * 4 ) & 3) << 6);

		// Point contributions
		float value = 0;
		(int secondaryIndexStart, int secondaryIndexStop) = LOOKUP_4D_A[index];
		for ( int i = secondaryIndexStart; i < secondaryIndexStop; i++ )
		{
			LatticeVertex4D c = LOOKUP_4D_B[i];
			float dx = xi + c.dx, dy = yi + c.dy, dz = zi + c.dz, dw = wi + c.dw;
			float a = (dx * dx + dy * dy) + (dz * dz + dw * dw);
			if ( a < RSQUARED_4D )
			{
				a -= RSQUARED_4D;
				a *= a;
				value += a * a * Grad( seed, xsvp + c.xsvp, ysvp + c.ysvp, zsvp + c.zsvp, wsvp + c.wsvp, dx, dy, dz, dw );
			}
		}
		return value;
	}

	/*
     * Utility
     */

	[MethodImpl( MethodImplOptions.AggressiveInlining )]
	private static float Grad( long seed, long xsvp, long ysvp, float dx, float dy )
	{
		long hash = seed ^ xsvp ^ ysvp;
		hash *= HASH_MULTIPLIER;
		hash ^= hash >> (64 - N_GRADS_2D_EXPONENT + 1);
		int gi = (int)hash & ((N_GRADS_2D - 1) << 1);
		return GRADIENTS_2D[gi | 0] * dx + GRADIENTS_2D[gi | 1] * dy;
	}

	[MethodImpl( MethodImplOptions.AggressiveInlining )]
	private static float Grad( long seed, long xrvp, long yrvp, long zrvp, float dx, float dy, float dz )
	{
		long hash = (seed ^ xrvp) ^ (yrvp ^ zrvp);
		hash *= HASH_MULTIPLIER;
		hash ^= hash >> (64 - N_GRADS_3D_EXPONENT + 2);
		int gi = (int)hash & ((N_GRADS_3D - 1) << 2);
		return GRADIENTS_3D[gi | 0] * dx + GRADIENTS_3D[gi | 1] * dy + GRADIENTS_3D[gi | 2] * dz;
	}

	[MethodImpl( MethodImplOptions.AggressiveInlining )]
	private static float Grad( long seed, long xsvp, long ysvp, long zsvp, long wsvp, float dx, float dy, float dz, float dw )
	{
		long hash = seed ^ (xsvp ^ ysvp) ^ (zsvp ^ wsvp);
		hash *= HASH_MULTIPLIER;
		hash ^= hash >> (64 - N_GRADS_4D_EXPONENT + 2);
		int gi = (int)hash & ((N_GRADS_4D - 1) << 2);
		return (GRADIENTS_4D[gi | 0] * dx + GRADIENTS_4D[gi | 1] * dy) + (GRADIENTS_4D[gi | 2] * dz + GRADIENTS_4D[gi | 3] * dw);
	}

	[MethodImpl( MethodImplOptions.AggressiveInlining )]
	private static int FastFloor( double x )
	{
		int xi = (int)x;
		return x < xi ? xi - 1 : xi;
	}

	/*
     * Lookup Tables & Gradients
     */

	private static readonly float[] GRADIENTS_2D;
	private static readonly float[] GRADIENTS_3D;
	private static readonly float[] GRADIENTS_4D;
	private static readonly (short SecondaryIndexStart, short SecondaryIndexStop)[] LOOKUP_4D_A;
	private static readonly LatticeVertex4D[] LOOKUP_4D_B;

	static OpenSimplex2S()
	{

		GRADIENTS_2D = new float[N_GRADS_2D * 2];
		float[] grad2 = {
				 0.38268343236509f,   0.923879532511287f,
				 0.923879532511287f,  0.38268343236509f,
				 0.923879532511287f, -0.38268343236509f,
				 0.38268343236509f,  -0.923879532511287f,
				-0.38268343236509f,  -0.923879532511287f,
				-0.923879532511287f, -0.38268343236509f,
				-0.923879532511287f,  0.38268343236509f,
				-0.38268343236509f,   0.923879532511287f,
                //-------------------------------------//
                 0.130526192220052f,  0.99144486137381f,
				 0.608761429008721f,  0.793353340291235f,
				 0.793353340291235f,  0.608761429008721f,
				 0.99144486137381f,   0.130526192220051f,
				 0.99144486137381f,  -0.130526192220051f,
				 0.793353340291235f, -0.60876142900872f,
				 0.608761429008721f, -0.793353340291235f,
				 0.130526192220052f, -0.99144486137381f,
				-0.130526192220052f, -0.99144486137381f,
				-0.608761429008721f, -0.793353340291235f,
				-0.793353340291235f, -0.608761429008721f,
				-0.99144486137381f,  -0.130526192220052f,
				-0.99144486137381f,   0.130526192220051f,
				-0.793353340291235f,  0.608761429008721f,
				-0.608761429008721f,  0.793353340291235f,
				-0.130526192220052f,  0.99144486137381f,
		};
		for ( int i = 0; i < grad2.Length; i++ )
		{
			grad2[i] = (float)(grad2[i] / NORMALIZER_2D);
		}
		for ( int i = 0, j = 0; i < GRADIENTS_2D.Length; i++, j++ )
		{
			if ( j == grad2.Length ) j = 0;
			GRADIENTS_2D[i] = grad2[j];
		}

		GRADIENTS_3D = new float[N_GRADS_3D * 4];
		float[] grad3 = {
			 2.22474487139f,       2.22474487139f,      -1.0f,                 0.0f,
			 2.22474487139f,       2.22474487139f,       1.0f,                 0.0f,
			 3.0862664687972017f,  1.1721513422464978f,  0.0f,                 0.0f,
			 1.1721513422464978f,  3.0862664687972017f,  0.0f,                 0.0f,
			-2.22474487139f,       2.22474487139f,      -1.0f,                 0.0f,
			-2.22474487139f,       2.22474487139f,       1.0f,                 0.0f,
			-1.1721513422464978f,  3.0862664687972017f,  0.0f,                 0.0f,
			-3.0862664687972017f,  1.1721513422464978f,  0.0f,                 0.0f,
			-1.0f,                -2.22474487139f,      -2.22474487139f,       0.0f,
			 1.0f,                -2.22474487139f,      -2.22474487139f,       0.0f,
			 0.0f,                -3.0862664687972017f, -1.1721513422464978f,  0.0f,
			 0.0f,                -1.1721513422464978f, -3.0862664687972017f,  0.0f,
			-1.0f,                -2.22474487139f,       2.22474487139f,       0.0f,
			 1.0f,                -2.22474487139f,       2.22474487139f,       0.0f,
			 0.0f,                -1.1721513422464978f,  3.0862664687972017f,  0.0f,
			 0.0f,                -3.0862664687972017f,  1.1721513422464978f,  0.0f,
            //--------------------------------------------------------------------//
            -2.22474487139f,      -2.22474487139f,      -1.0f,                 0.0f,
			-2.22474487139f,      -2.22474487139f,       1.0f,                 0.0f,
			-3.0862664687972017f, -1.1721513422464978f,  0.0f,                 0.0f,
			-1.1721513422464978f, -3.0862664687972017f,  0.0f,                 0.0f,
			-2.22474487139f,      -1.0f,                -2.22474487139f,       0.0f,
			-2.22474487139f,       1.0f,                -2.22474487139f,       0.0f,
			-1.1721513422464978f,  0.0f,                -3.0862664687972017f,  0.0f,
			-3.0862664687972017f,  0.0f,                -1.1721513422464978f,  0.0f,
			-2.22474487139f,      -1.0f,                 2.22474487139f,       0.0f,
			-2.22474487139f,       1.0f,                 2.22474487139f,       0.0f,
			-3.0862664687972017f,  0.0f,                 1.1721513422464978f,  0.0f,
			-1.1721513422464978f,  0.0f,                 3.0862664687972017f,  0.0f,
			-1.0f,                 2.22474487139f,      -2.22474487139f,       0.0f,
			 1.0f,                 2.22474487139f,      -2.22474487139f,       0.0f,
			 0.0f,                 1.1721513422464978f, -3.0862664687972017f,  0.0f,
			 0.0f,                 3.0862664687972017f, -1.1721513422464978f,  0.0f,
			-1.0f,                 2.22474487139f,       2.22474487139f,       0.0f,
			 1.0f,                 2.22474487139f,       2.22474487139f,       0.0f,
			 0.0f,                 3.0862664687972017f,  1.1721513422464978f,  0.0f,
			 0.0f,                 1.1721513422464978f,  3.0862664687972017f,  0.0f,
			 2.22474487139f,      -2.22474487139f,      -1.0f,                 0.0f,
			 2.22474487139f,      -2.22474487139f,       1.0f,                 0.0f,
			 1.1721513422464978f, -3.0862664687972017f,  0.0f,                 0.0f,
			 3.0862664687972017f, -1.1721513422464978f,  0.0f,                 0.0f,
			 2.22474487139f,      -1.0f,                -2.22474487139f,       0.0f,
			 2.22474487139f,       1.0f,                -2.22474487139f,       0.0f,
			 3.0862664687972017f,  0.0f,                -1.1721513422464978f,  0.0f,
			 1.1721513422464978f,  0.0f,                -3.0862664687972017f,  0.0f,
			 2.22474487139f,      -1.0f,                 2.22474487139f,       0.0f,
			 2.22474487139f,       1.0f,                 2.22474487139f,       0.0f,
			 1.1721513422464978f,  0.0f,                 3.0862664687972017f,  0.0f,
			 3.0862664687972017f,  0.0f,                 1.1721513422464978f,  0.0f,
		};
		for ( int i = 0; i < grad3.Length; i++ )
		{
			grad3[i] = (float)(grad3[i] / NORMALIZER_3D);
		}
		for ( int i = 0, j = 0; i < GRADIENTS_3D.Length; i++, j++ )
		{
			if ( j == grad3.Length ) j = 0;
			GRADIENTS_3D[i] = grad3[j];
		}

		GRADIENTS_4D = new float[N_GRADS_4D * 4];
		float[] grad4 = {
			-0.6740059517812944f,   -0.3239847771997537f,   -0.3239847771997537f,    0.5794684678643381f,
			-0.7504883828755602f,   -0.4004672082940195f,    0.15296486218853164f,   0.5029860367700724f,
			-0.7504883828755602f,    0.15296486218853164f,  -0.4004672082940195f,    0.5029860367700724f,
			-0.8828161875373585f,    0.08164729285680945f,   0.08164729285680945f,   0.4553054119602712f,
			-0.4553054119602712f,   -0.08164729285680945f,  -0.08164729285680945f,   0.8828161875373585f,
			-0.5029860367700724f,   -0.15296486218853164f,   0.4004672082940195f,    0.7504883828755602f,
			-0.5029860367700724f,    0.4004672082940195f,   -0.15296486218853164f,   0.7504883828755602f,
			-0.5794684678643381f,    0.3239847771997537f,    0.3239847771997537f,    0.6740059517812944f,
			-0.6740059517812944f,   -0.3239847771997537f,    0.5794684678643381f,   -0.3239847771997537f,
			-0.7504883828755602f,   -0.4004672082940195f,    0.5029860367700724f,    0.15296486218853164f,
			-0.7504883828755602f,    0.15296486218853164f,   0.5029860367700724f,   -0.4004672082940195f,
			-0.8828161875373585f,    0.08164729285680945f,   0.4553054119602712f,    0.08164729285680945f,
			-0.4553054119602712f,   -0.08164729285680945f,   0.8828161875373585f,   -0.08164729285680945f,
			-0.5029860367700724f,   -0.15296486218853164f,   0.7504883828755602f,    0.4004672082940195f,
			-0.5029860367700724f,    0.4004672082940195f,    0.7504883828755602f,   -0.15296486218853164f,
			-0.5794684678643381f,    0.3239847771997537f,    0.6740059517812944f,    0.3239847771997537f,
			-0.6740059517812944f,    0.5794684678643381f,   -0.3239847771997537f,   -0.3239847771997537f,
			-0.7504883828755602f,    0.5029860367700724f,   -0.4004672082940195f,    0.15296486218853164f,
			-0.7504883828755602f,    0.5029860367700724f,    0.15296486218853164f,  -0.4004672082940195f,
			-0.8828161875373585f,    0.4553054119602712f,    0.08164729285680945f,   0.08164729285680945f,
			-0.4553054119602712f,    0.8828161875373585f,   -0.08164729285680945f,  -0.08164729285680945f,
			-0.5029860367700724f,    0.7504883828755602f,   -0.15296486218853164f,   0.4004672082940195f,
			-0.5029860367700724f,    0.7504883828755602f,    0.4004672082940195f,   -0.15296486218853164f,
			-0.5794684678643381f,    0.6740059517812944f,    0.3239847771997537f,    0.3239847771997537f,
			 0.5794684678643381f,   -0.6740059517812944f,   -0.3239847771997537f,   -0.3239847771997537f,
			 0.5029860367700724f,   -0.7504883828755602f,   -0.4004672082940195f,    0.15296486218853164f,
			 0.5029860367700724f,   -0.7504883828755602f,    0.15296486218853164f,  -0.4004672082940195f,
			 0.4553054119602712f,   -0.8828161875373585f,    0.08164729285680945f,   0.08164729285680945f,
			 0.8828161875373585f,   -0.4553054119602712f,   -0.08164729285680945f,  -0.08164729285680945f,
			 0.7504883828755602f,   -0.5029860367700724f,   -0.15296486218853164f,   0.4004672082940195f,
			 0.7504883828755602f,   -0.5029860367700724f,    0.4004672082940195f,   -0.15296486218853164f,
			 0.6740059517812944f,   -0.5794684678643381f,    0.3239847771997537f,    0.3239847771997537f,
            //------------------------------------------------------------------------------------------//
            -0.753341017856078f,    -0.37968289875261624f,  -0.37968289875261624f,  -0.37968289875261624f,
			-0.7821684431180708f,   -0.4321472685365301f,   -0.4321472685365301f,    0.12128480194602098f,
			-0.7821684431180708f,   -0.4321472685365301f,    0.12128480194602098f,  -0.4321472685365301f,
			-0.7821684431180708f,    0.12128480194602098f,  -0.4321472685365301f,   -0.4321472685365301f,
			-0.8586508742123365f,   -0.508629699630796f,     0.044802370851755174f,  0.044802370851755174f,
			-0.8586508742123365f,    0.044802370851755174f, -0.508629699630796f,     0.044802370851755174f,
			-0.8586508742123365f,    0.044802370851755174f,  0.044802370851755174f, -0.508629699630796f,
			-0.9982828964265062f,   -0.03381941603233842f,  -0.03381941603233842f,  -0.03381941603233842f,
			-0.37968289875261624f,  -0.753341017856078f,    -0.37968289875261624f,  -0.37968289875261624f,
			-0.4321472685365301f,   -0.7821684431180708f,   -0.4321472685365301f,    0.12128480194602098f,
			-0.4321472685365301f,   -0.7821684431180708f,    0.12128480194602098f,  -0.4321472685365301f,
			 0.12128480194602098f,  -0.7821684431180708f,   -0.4321472685365301f,   -0.4321472685365301f,
			-0.508629699630796f,    -0.8586508742123365f,    0.044802370851755174f,  0.044802370851755174f,
			 0.044802370851755174f, -0.8586508742123365f,   -0.508629699630796f,     0.044802370851755174f,
			 0.044802370851755174f, -0.8586508742123365f,    0.044802370851755174f, -0.508629699630796f,
			-0.03381941603233842f,  -0.9982828964265062f,   -0.03381941603233842f,  -0.03381941603233842f,
			-0.37968289875261624f,  -0.37968289875261624f,  -0.753341017856078f,    -0.37968289875261624f,
			-0.4321472685365301f,   -0.4321472685365301f,   -0.7821684431180708f,    0.12128480194602098f,
			-0.4321472685365301f,    0.12128480194602098f,  -0.7821684431180708f,   -0.4321472685365301f,
			 0.12128480194602098f,  -0.4321472685365301f,   -0.7821684431180708f,   -0.4321472685365301f,
			-0.508629699630796f,     0.044802370851755174f, -0.8586508742123365f,    0.044802370851755174f,
			 0.044802370851755174f, -0.508629699630796f,    -0.8586508742123365f,    0.044802370851755174f,
			 0.044802370851755174f,  0.044802370851755174f, -0.8586508742123365f,   -0.508629699630796f,
			-0.03381941603233842f,  -0.03381941603233842f,  -0.9982828964265062f,   -0.03381941603233842f,
			-0.37968289875261624f,  -0.37968289875261624f,  -0.37968289875261624f,  -0.753341017856078f,
			-0.4321472685365301f,   -0.4321472685365301f,    0.12128480194602098f,  -0.7821684431180708f,
			-0.4321472685365301f,    0.12128480194602098f,  -0.4321472685365301f,   -0.7821684431180708f,
			 0.12128480194602098f,  -0.4321472685365301f,   -0.4321472685365301f,   -0.7821684431180708f,
			-0.508629699630796f,     0.044802370851755174f,  0.044802370851755174f, -0.8586508742123365f,
			 0.044802370851755174f, -0.508629699630796f,     0.044802370851755174f, -0.8586508742123365f,
			 0.044802370851755174f,  0.044802370851755174f, -0.508629699630796f,    -0.8586508742123365f,
			-0.03381941603233842f,  -0.03381941603233842f,  -0.03381941603233842f,  -0.9982828964265062f,
			-0.3239847771997537f,   -0.6740059517812944f,   -0.3239847771997537f,    0.5794684678643381f,
			-0.4004672082940195f,   -0.7504883828755602f,    0.15296486218853164f,   0.5029860367700724f,
			 0.15296486218853164f,  -0.7504883828755602f,   -0.4004672082940195f,    0.5029860367700724f,
			 0.08164729285680945f,  -0.8828161875373585f,    0.08164729285680945f,   0.4553054119602712f,
			-0.08164729285680945f,  -0.4553054119602712f,   -0.08164729285680945f,   0.8828161875373585f,
			-0.15296486218853164f,  -0.5029860367700724f,    0.4004672082940195f,    0.7504883828755602f,
			 0.4004672082940195f,   -0.5029860367700724f,   -0.15296486218853164f,   0.7504883828755602f,
			 0.3239847771997537f,   -0.5794684678643381f,    0.3239847771997537f,    0.6740059517812944f,
			-0.3239847771997537f,   -0.3239847771997537f,   -0.6740059517812944f,    0.5794684678643381f,
			-0.4004672082940195f,    0.15296486218853164f,  -0.7504883828755602f,    0.5029860367700724f,
			 0.15296486218853164f,  -0.4004672082940195f,   -0.7504883828755602f,    0.5029860367700724f,
			 0.08164729285680945f,   0.08164729285680945f,  -0.8828161875373585f,    0.4553054119602712f,
			-0.08164729285680945f,  -0.08164729285680945f,  -0.4553054119602712f,    0.8828161875373585f,
			-0.15296486218853164f,   0.4004672082940195f,   -0.5029860367700724f,    0.7504883828755602f,
			 0.4004672082940195f,   -0.15296486218853164f,  -0.5029860367700724f,    0.7504883828755602f,
			 0.3239847771997537f,    0.3239847771997537f,   -0.5794684678643381f,    0.6740059517812944f,
			-0.3239847771997537f,   -0.6740059517812944f,    0.5794684678643381f,   -0.3239847771997537f,
			-0.4004672082940195f,   -0.7504883828755602f,    0.5029860367700724f,    0.15296486218853164f,
			 0.15296486218853164f,  -0.7504883828755602f,    0.5029860367700724f,   -0.4004672082940195f,
			 0.08164729285680945f,  -0.8828161875373585f,    0.4553054119602712f,    0.08164729285680945f,
			-0.08164729285680945f,  -0.4553054119602712f,    0.8828161875373585f,   -0.08164729285680945f,
			-0.15296486218853164f,  -0.5029860367700724f,    0.7504883828755602f,    0.4004672082940195f,
			 0.4004672082940195f,   -0.5029860367700724f,    0.7504883828755602f,   -0.15296486218853164f,
			 0.3239847771997537f,   -0.5794684678643381f,    0.6740059517812944f,    0.3239847771997537f,
			-0.3239847771997537f,   -0.3239847771997537f,    0.5794684678643381f,   -0.6740059517812944f,
			-0.4004672082940195f,    0.15296486218853164f,   0.5029860367700724f,   -0.7504883828755602f,
			 0.15296486218853164f,  -0.4004672082940195f,    0.5029860367700724f,   -0.7504883828755602f,
			 0.08164729285680945f,   0.08164729285680945f,   0.4553054119602712f,   -0.8828161875373585f,
			-0.08164729285680945f,  -0.08164729285680945f,   0.8828161875373585f,   -0.4553054119602712f,
			-0.15296486218853164f,   0.4004672082940195f,    0.7504883828755602f,   -0.5029860367700724f,
			 0.4004672082940195f,   -0.15296486218853164f,   0.7504883828755602f,   -0.5029860367700724f,
			 0.3239847771997537f,    0.3239847771997537f,    0.6740059517812944f,   -0.5794684678643381f,
			-0.3239847771997537f,    0.5794684678643381f,   -0.6740059517812944f,   -0.3239847771997537f,
			-0.4004672082940195f,    0.5029860367700724f,   -0.7504883828755602f,    0.15296486218853164f,
			 0.15296486218853164f,   0.5029860367700724f,   -0.7504883828755602f,   -0.4004672082940195f,
			 0.08164729285680945f,   0.4553054119602712f,   -0.8828161875373585f,    0.08164729285680945f,
			-0.08164729285680945f,   0.8828161875373585f,   -0.4553054119602712f,   -0.08164729285680945f,
			-0.15296486218853164f,   0.7504883828755602f,   -0.5029860367700724f,    0.4004672082940195f,
			 0.4004672082940195f,    0.7504883828755602f,   -0.5029860367700724f,   -0.15296486218853164f,
			 0.3239847771997537f,    0.6740059517812944f,   -0.5794684678643381f,    0.3239847771997537f,
			-0.3239847771997537f,    0.5794684678643381f,   -0.3239847771997537f,   -0.6740059517812944f,
			-0.4004672082940195f,    0.5029860367700724f,    0.15296486218853164f,  -0.7504883828755602f,
			 0.15296486218853164f,   0.5029860367700724f,   -0.4004672082940195f,   -0.7504883828755602f,
			 0.08164729285680945f,   0.4553054119602712f,    0.08164729285680945f,  -0.8828161875373585f,
			-0.08164729285680945f,   0.8828161875373585f,   -0.08164729285680945f,  -0.4553054119602712f,
			-0.15296486218853164f,   0.7504883828755602f,    0.4004672082940195f,   -0.5029860367700724f,
			 0.4004672082940195f,    0.7504883828755602f,   -0.15296486218853164f,  -0.5029860367700724f,
			 0.3239847771997537f,    0.6740059517812944f,    0.3239847771997537f,   -0.5794684678643381f,
			 0.5794684678643381f,   -0.3239847771997537f,   -0.6740059517812944f,   -0.3239847771997537f,
			 0.5029860367700724f,   -0.4004672082940195f,   -0.7504883828755602f,    0.15296486218853164f,
			 0.5029860367700724f,    0.15296486218853164f,  -0.7504883828755602f,   -0.4004672082940195f,
			 0.4553054119602712f,    0.08164729285680945f,  -0.8828161875373585f,    0.08164729285680945f,
			 0.8828161875373585f,   -0.08164729285680945f,  -0.4553054119602712f,   -0.08164729285680945f,
			 0.7504883828755602f,   -0.15296486218853164f,  -0.5029860367700724f,    0.4004672082940195f,
			 0.7504883828755602f,    0.4004672082940195f,   -0.5029860367700724f,   -0.15296486218853164f,
			 0.6740059517812944f,    0.3239847771997537f,   -0.5794684678643381f,    0.3239847771997537f,
			 0.5794684678643381f,   -0.3239847771997537f,   -0.3239847771997537f,   -0.6740059517812944f,
			 0.5029860367700724f,   -0.4004672082940195f,    0.15296486218853164f,  -0.7504883828755602f,
			 0.5029860367700724f,    0.15296486218853164f,  -0.4004672082940195f,   -0.7504883828755602f,
			 0.4553054119602712f,    0.08164729285680945f,   0.08164729285680945f,  -0.8828161875373585f,
			 0.8828161875373585f,   -0.08164729285680945f,  -0.08164729285680945f,  -0.4553054119602712f,
			 0.7504883828755602f,   -0.15296486218853164f,   0.4004672082940195f,   -0.5029860367700724f,
			 0.7504883828755602f,    0.4004672082940195f,   -0.15296486218853164f,  -0.5029860367700724f,
			 0.6740059517812944f,    0.3239847771997537f,    0.3239847771997537f,   -0.5794684678643381f,
			 0.03381941603233842f,   0.03381941603233842f,   0.03381941603233842f,   0.9982828964265062f,
			-0.044802370851755174f, -0.044802370851755174f,  0.508629699630796f,     0.8586508742123365f,
			-0.044802370851755174f,  0.508629699630796f,    -0.044802370851755174f,  0.8586508742123365f,
			-0.12128480194602098f,   0.4321472685365301f,    0.4321472685365301f,    0.7821684431180708f,
			 0.508629699630796f,    -0.044802370851755174f, -0.044802370851755174f,  0.8586508742123365f,
			 0.4321472685365301f,   -0.12128480194602098f,   0.4321472685365301f,    0.7821684431180708f,
			 0.4321472685365301f,    0.4321472685365301f,   -0.12128480194602098f,   0.7821684431180708f,
			 0.37968289875261624f,   0.37968289875261624f,   0.37968289875261624f,   0.753341017856078f,
			 0.03381941603233842f,   0.03381941603233842f,   0.9982828964265062f,    0.03381941603233842f,
			-0.044802370851755174f,  0.044802370851755174f,  0.8586508742123365f,    0.508629699630796f,
			-0.044802370851755174f,  0.508629699630796f,     0.8586508742123365f,   -0.044802370851755174f,
			-0.12128480194602098f,   0.4321472685365301f,    0.7821684431180708f,    0.4321472685365301f,
			 0.508629699630796f,    -0.044802370851755174f,  0.8586508742123365f,   -0.044802370851755174f,
			 0.4321472685365301f,   -0.12128480194602098f,   0.7821684431180708f,    0.4321472685365301f,
			 0.4321472685365301f,    0.4321472685365301f,    0.7821684431180708f,   -0.12128480194602098f,
			 0.37968289875261624f,   0.37968289875261624f,   0.753341017856078f,     0.37968289875261624f,
			 0.03381941603233842f,   0.9982828964265062f,    0.03381941603233842f,   0.03381941603233842f,
			-0.044802370851755174f,  0.8586508742123365f,   -0.044802370851755174f,  0.508629699630796f,
			-0.044802370851755174f,  0.8586508742123365f,    0.508629699630796f,    -0.044802370851755174f,
			-0.12128480194602098f,   0.7821684431180708f,    0.4321472685365301f,    0.4321472685365301f,
			 0.508629699630796f,     0.8586508742123365f,   -0.044802370851755174f, -0.044802370851755174f,
			 0.4321472685365301f,    0.7821684431180708f,   -0.12128480194602098f,   0.4321472685365301f,
			 0.4321472685365301f,    0.7821684431180708f,    0.4321472685365301f,   -0.12128480194602098f,
			 0.37968289875261624f,   0.753341017856078f,     0.37968289875261624f,   0.37968289875261624f,
			 0.9982828964265062f,    0.03381941603233842f,   0.03381941603233842f,   0.03381941603233842f,
			 0.8586508742123365f,   -0.044802370851755174f, -0.044802370851755174f,  0.508629699630796f,
			 0.8586508742123365f,   -0.044802370851755174f,  0.508629699630796f,    -0.044802370851755174f,
			 0.7821684431180708f,   -0.12128480194602098f,   0.4321472685365301f,    0.4321472685365301f,
			 0.8586508742123365f,    0.508629699630796f,    -0.044802370851755174f, -0.044802370851755174f,
			 0.7821684431180708f,    0.4321472685365301f,   -0.12128480194602098f,   0.4321472685365301f,
			 0.7821684431180708f,    0.4321472685365301f,    0.4321472685365301f,   -0.12128480194602098f,
			 0.753341017856078f,     0.37968289875261624f,   0.37968289875261624f,   0.37968289875261624f,
		};
		for ( int i = 0; i < grad4.Length; i++ )
		{
			grad4[i] = (float)(grad4[i] / NORMALIZER_4D);
		}
		for ( int i = 0, j = 0; i < GRADIENTS_4D.Length; i++, j++ )
		{
			if ( j == grad4.Length ) j = 0;
			GRADIENTS_4D[i] = grad4[j];
		}

		int[][] lookup4DVertexCodes = {
			new int[] { 0x15, 0x45, 0x51, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x15, 0x45, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA6, 0xAA },
			new int[] { 0x01, 0x05, 0x11, 0x15, 0x41, 0x45, 0x51, 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xAA },
			new int[] { 0x01, 0x15, 0x16, 0x45, 0x46, 0x51, 0x52, 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x15, 0x45, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA9, 0xAA },
			new int[] { 0x05, 0x15, 0x45, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xAA },
			new int[] { 0x05, 0x15, 0x45, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xAA },
			new int[] { 0x05, 0x15, 0x16, 0x45, 0x46, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xAA, 0xAB },
			new int[] { 0x04, 0x05, 0x14, 0x15, 0x44, 0x45, 0x54, 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA },
			new int[] { 0x05, 0x15, 0x45, 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xAA },
			new int[] { 0x05, 0x15, 0x45, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x9A, 0xAA },
			new int[] { 0x05, 0x15, 0x16, 0x45, 0x46, 0x55, 0x56, 0x59, 0x5A, 0x5B, 0x6A, 0x9A, 0xAA, 0xAB },
			new int[] { 0x04, 0x15, 0x19, 0x45, 0x49, 0x54, 0x55, 0x58, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x05, 0x15, 0x19, 0x45, 0x49, 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xAA, 0xAE },
			new int[] { 0x05, 0x15, 0x19, 0x45, 0x49, 0x55, 0x56, 0x59, 0x5A, 0x5E, 0x6A, 0x9A, 0xAA, 0xAE },
			new int[] { 0x05, 0x15, 0x1A, 0x45, 0x4A, 0x55, 0x56, 0x59, 0x5A, 0x5B, 0x5E, 0x6A, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },
			new int[] { 0x15, 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x11, 0x15, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0xA5, 0xA6, 0xAA },
			new int[] { 0x11, 0x15, 0x51, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x96, 0xA6, 0xAA },
			new int[] { 0x11, 0x15, 0x16, 0x51, 0x52, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x96, 0xA6, 0xAA, 0xAB },
			new int[] { 0x14, 0x15, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x99, 0xA5, 0xA9, 0xAA },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x9A, 0xA6, 0xA9, 0xAA },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x15, 0x16, 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x6B, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x14, 0x15, 0x54, 0x55, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x99, 0xA9, 0xAA },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x9A, 0xAA },
			new int[] { 0x15, 0x16, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x6B, 0x9A, 0xAA, 0xAB },
			new int[] { 0x14, 0x15, 0x19, 0x54, 0x55, 0x58, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x99, 0xA9, 0xAA, 0xAE },
			new int[] { 0x15, 0x19, 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x6E, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x15, 0x19, 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x6E, 0x9A, 0xAA, 0xAE },
			new int[] { 0x15, 0x1A, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x6B, 0x6E, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },
			new int[] { 0x10, 0x11, 0x14, 0x15, 0x50, 0x51, 0x54, 0x55, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x11, 0x15, 0x51, 0x55, 0x56, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xAA },
			new int[] { 0x11, 0x15, 0x51, 0x55, 0x56, 0x65, 0x66, 0x6A, 0xA6, 0xAA },
			new int[] { 0x11, 0x15, 0x16, 0x51, 0x52, 0x55, 0x56, 0x65, 0x66, 0x67, 0x6A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x14, 0x15, 0x54, 0x55, 0x59, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA9, 0xAA },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA6, 0xAA },
			new int[] { 0x15, 0x16, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x6B, 0xA6, 0xAA, 0xAB },
			new int[] { 0x14, 0x15, 0x54, 0x55, 0x59, 0x65, 0x69, 0x6A, 0xA9, 0xAA },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA9, 0xAA },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xAA },
			new int[] { 0x15, 0x16, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x6B, 0xAA, 0xAB },
			new int[] { 0x14, 0x15, 0x19, 0x54, 0x55, 0x58, 0x59, 0x65, 0x69, 0x6A, 0x6D, 0xA9, 0xAA, 0xAE },
			new int[] { 0x15, 0x19, 0x55, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x6E, 0xA9, 0xAA, 0xAE },
			new int[] { 0x15, 0x19, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x6E, 0xAA, 0xAE },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x69, 0x6A, 0x6B, 0x6E, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },
			new int[] { 0x10, 0x15, 0x25, 0x51, 0x54, 0x55, 0x61, 0x64, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x11, 0x15, 0x25, 0x51, 0x55, 0x56, 0x61, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xAA, 0xBA },
			new int[] { 0x11, 0x15, 0x25, 0x51, 0x55, 0x56, 0x61, 0x65, 0x66, 0x6A, 0x76, 0xA6, 0xAA, 0xBA },
			new int[] { 0x11, 0x15, 0x26, 0x51, 0x55, 0x56, 0x62, 0x65, 0x66, 0x67, 0x6A, 0x76, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },
			new int[] { 0x14, 0x15, 0x25, 0x54, 0x55, 0x59, 0x64, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA9, 0xAA, 0xBA },
			new int[] { 0x15, 0x25, 0x55, 0x65, 0x66, 0x69, 0x6A, 0x7A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x15, 0x25, 0x55, 0x56, 0x65, 0x66, 0x69, 0x6A, 0x7A, 0xA6, 0xAA, 0xBA },
			new int[] { 0x15, 0x26, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x6B, 0x7A, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },
			new int[] { 0x14, 0x15, 0x25, 0x54, 0x55, 0x59, 0x64, 0x65, 0x69, 0x6A, 0x79, 0xA9, 0xAA, 0xBA },
			new int[] { 0x15, 0x25, 0x55, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x7A, 0xA9, 0xAA, 0xBA },
			new int[] { 0x15, 0x25, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x7A, 0xAA, 0xBA },
			new int[] { 0x15, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x6B, 0x7A, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },
			new int[] { 0x14, 0x15, 0x29, 0x54, 0x55, 0x59, 0x65, 0x68, 0x69, 0x6A, 0x6D, 0x79, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },
			new int[] { 0x15, 0x29, 0x55, 0x59, 0x65, 0x69, 0x6A, 0x6E, 0x7A, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },
			new int[] { 0x15, 0x55, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x6E, 0x7A, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x6B, 0x6E, 0x7A, 0xAA, 0xAB, 0xAE, 0xBA, 0xBF },
			new int[] { 0x45, 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xAA },
			new int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x5A, 0x66, 0x95, 0x96, 0x9A, 0xA6, 0xAA },
			new int[] { 0x41, 0x45, 0x46, 0x51, 0x52, 0x55, 0x56, 0x5A, 0x66, 0x95, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x44, 0x45, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x69, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA9, 0xAA },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xA9, 0xAA },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x45, 0x46, 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0x9B, 0xA6, 0xAA, 0xAB },
			new int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x5A, 0x69, 0x95, 0x99, 0x9A, 0xA9, 0xAA },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xAA },
			new int[] { 0x45, 0x46, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x96, 0x9A, 0x9B, 0xAA, 0xAB },
			new int[] { 0x44, 0x45, 0x49, 0x54, 0x55, 0x58, 0x59, 0x5A, 0x69, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x45, 0x49, 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0x9E, 0xA9, 0xAA, 0xAE },
			new int[] { 0x45, 0x49, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x99, 0x9A, 0x9E, 0xAA, 0xAE },
			new int[] { 0x45, 0x4A, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x9A, 0x9B, 0x9E, 0xAA, 0xAB, 0xAE, 0xAF },
			new int[] { 0x50, 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x66, 0x69, 0x95, 0x96, 0x99, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x51, 0x55, 0x56, 0x59, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x51, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xAA, 0xAB },
			new int[] { 0x51, 0x52, 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xA7, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x56, 0x59, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x15, 0x45, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xAE },
			new int[] { 0x15, 0x45, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x15, 0x45, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xA9, 0xAA, 0xAB, 0xAE },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x58, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAD, 0xAE },
			new int[] { 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },
			new int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x66, 0x69, 0x95, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x95, 0x96, 0xA5, 0xA6, 0xAA },
			new int[] { 0x51, 0x52, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x96, 0xA6, 0xA7, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x15, 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x15, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xBA },
			new int[] { 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x6A, 0x95, 0x99, 0xA5, 0xA9, 0xAA },
			new int[] { 0x15, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAE, 0xBA },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x9A, 0xA6, 0xA9, 0xAA },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x58, 0x59, 0x65, 0x69, 0x6A, 0x99, 0xA9, 0xAA, 0xAD, 0xAE },
			new int[] { 0x55, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x69, 0x6A, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },
			new int[] { 0x50, 0x51, 0x54, 0x55, 0x61, 0x64, 0x65, 0x66, 0x69, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x51, 0x55, 0x61, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xB6, 0xBA },
			new int[] { 0x51, 0x55, 0x56, 0x61, 0x65, 0x66, 0x6A, 0xA5, 0xA6, 0xAA, 0xB6, 0xBA },
			new int[] { 0x51, 0x55, 0x56, 0x62, 0x65, 0x66, 0x6A, 0xA6, 0xA7, 0xAA, 0xAB, 0xB6, 0xBA, 0xBB },
			new int[] { 0x54, 0x55, 0x64, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xB9, 0xBA },
			new int[] { 0x55, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x55, 0x56, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x55, 0x56, 0x65, 0x66, 0x6A, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },
			new int[] { 0x54, 0x55, 0x59, 0x64, 0x65, 0x69, 0x6A, 0xA5, 0xA9, 0xAA, 0xB9, 0xBA },
			new int[] { 0x55, 0x59, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x15, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x68, 0x69, 0x6A, 0xA9, 0xAA, 0xAD, 0xAE, 0xB9, 0xBA, 0xBE },
			new int[] { 0x55, 0x59, 0x65, 0x69, 0x6A, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },
			new int[] { 0x15, 0x55, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xAA, 0xAB, 0xAE, 0xBA, 0xBF },
			new int[] { 0x40, 0x41, 0x44, 0x45, 0x50, 0x51, 0x54, 0x55, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xAA },
			new int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x95, 0x96, 0x9A, 0xA6, 0xAA },
			new int[] { 0x41, 0x45, 0x46, 0x51, 0x52, 0x55, 0x56, 0x95, 0x96, 0x97, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA9, 0xAA },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xAA },
			new int[] { 0x45, 0x46, 0x55, 0x56, 0x5A, 0x95, 0x96, 0x9A, 0x9B, 0xA6, 0xAA, 0xAB },
			new int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x95, 0x99, 0x9A, 0xA9, 0xAA },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA9, 0xAA },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xAA },
			new int[] { 0x45, 0x46, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0x9B, 0xAA, 0xAB },
			new int[] { 0x44, 0x45, 0x49, 0x54, 0x55, 0x58, 0x59, 0x95, 0x99, 0x9A, 0x9D, 0xA9, 0xAA, 0xAE },
			new int[] { 0x45, 0x49, 0x55, 0x59, 0x5A, 0x95, 0x99, 0x9A, 0x9E, 0xA9, 0xAA, 0xAE },
			new int[] { 0x45, 0x49, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0x9E, 0xAA, 0xAE },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x96, 0x99, 0x9A, 0x9B, 0x9E, 0xAA, 0xAB, 0xAE, 0xAF },
			new int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x95, 0x96, 0x99, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xAA },
			new int[] { 0x51, 0x52, 0x55, 0x56, 0x66, 0x95, 0x96, 0x9A, 0xA6, 0xA7, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x45, 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x45, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xEA },
			new int[] { 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA },
			new int[] { 0x45, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAE, 0xEA },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xA9, 0xAA },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x58, 0x59, 0x69, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xAD, 0xAE },
			new int[] { 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x96, 0x99, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },
			new int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x95, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xAA },
			new int[] { 0x51, 0x52, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xA7, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA, 0xEA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x51, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA9, 0xAA },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA6, 0xA9, 0xAA, 0xAB },
			new int[] { 0x54, 0x55, 0x58, 0x59, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA9, 0xAA, 0xAD, 0xAE },
			new int[] { 0x54, 0x55, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xAE },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA6, 0xA9, 0xAA, 0xAE },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x66, 0x69, 0x6A, 0x96, 0x99, 0x9A, 0xA6, 0xA9, 0xAA, 0xAB, 0xAE, 0xAF },
			new int[] { 0x50, 0x51, 0x54, 0x55, 0x61, 0x64, 0x65, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xB5, 0xBA },
			new int[] { 0x51, 0x55, 0x61, 0x65, 0x66, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xB6, 0xBA },
			new int[] { 0x51, 0x55, 0x56, 0x61, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xAA, 0xB6, 0xBA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x96, 0xA5, 0xA6, 0xA7, 0xAA, 0xAB, 0xB6, 0xBA, 0xBB },
			new int[] { 0x54, 0x55, 0x64, 0x65, 0x69, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xB9, 0xBA },
			new int[] { 0x55, 0x65, 0x66, 0x69, 0x6A, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x96, 0xA5, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },
			new int[] { 0x54, 0x55, 0x59, 0x64, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA9, 0xAA, 0xB9, 0xBA },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x55, 0x56, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },
			new int[] { 0x55, 0x56, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x96, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xBA, 0xBB },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x6A, 0x99, 0xA5, 0xA9, 0xAA, 0xAD, 0xAE, 0xB9, 0xBA, 0xBE },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x6A, 0x99, 0xA5, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },
			new int[] { 0x55, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x9A, 0xA6, 0xA9, 0xAA, 0xAB, 0xAE, 0xBA },
			new int[] { 0x40, 0x45, 0x51, 0x54, 0x55, 0x85, 0x91, 0x94, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x85, 0x91, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xAA, 0xEA },
			new int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x85, 0x91, 0x95, 0x96, 0x9A, 0xA6, 0xAA, 0xD6, 0xEA },
			new int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x86, 0x92, 0x95, 0x96, 0x97, 0x9A, 0xA6, 0xAA, 0xAB, 0xD6, 0xEA, 0xEB },
			new int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x85, 0x94, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xEA },
			new int[] { 0x45, 0x55, 0x85, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xDA, 0xEA },
			new int[] { 0x45, 0x55, 0x56, 0x85, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xAA, 0xDA, 0xEA },
			new int[] { 0x45, 0x55, 0x56, 0x86, 0x95, 0x96, 0x9A, 0x9B, 0xA6, 0xAA, 0xAB, 0xDA, 0xEA, 0xEB },
			new int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x85, 0x94, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xD9, 0xEA },
			new int[] { 0x45, 0x55, 0x59, 0x85, 0x95, 0x96, 0x99, 0x9A, 0xA9, 0xAA, 0xDA, 0xEA },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x85, 0x95, 0x96, 0x99, 0x9A, 0xAA, 0xDA, 0xEA },
			new int[] { 0x45, 0x55, 0x56, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0x9B, 0xA6, 0xAA, 0xAB, 0xDA, 0xEA, 0xEB },
			new int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x89, 0x95, 0x98, 0x99, 0x9A, 0x9D, 0xA9, 0xAA, 0xAE, 0xD9, 0xEA, 0xEE },
			new int[] { 0x45, 0x55, 0x59, 0x89, 0x95, 0x99, 0x9A, 0x9E, 0xA9, 0xAA, 0xAE, 0xDA, 0xEA, 0xEE },
			new int[] { 0x45, 0x55, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0x9E, 0xA9, 0xAA, 0xAE, 0xDA, 0xEA, 0xEE },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0x9B, 0x9E, 0xAA, 0xAB, 0xAE, 0xDA, 0xEA, 0xEF },
			new int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x91, 0x94, 0x95, 0x96, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x51, 0x55, 0x91, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xE6, 0xEA },
			new int[] { 0x51, 0x55, 0x56, 0x91, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xAA, 0xE6, 0xEA },
			new int[] { 0x51, 0x55, 0x56, 0x92, 0x95, 0x96, 0x9A, 0xA6, 0xA7, 0xAA, 0xAB, 0xE6, 0xEA, 0xEB },
			new int[] { 0x54, 0x55, 0x94, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xE9, 0xEA },
			new int[] { 0x55, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x55, 0x56, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x55, 0x56, 0x95, 0x96, 0x9A, 0xA6, 0xAA, 0xAB, 0xEA, 0xEB },
			new int[] { 0x54, 0x55, 0x59, 0x94, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xE9, 0xEA },
			new int[] { 0x55, 0x59, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x45, 0x55, 0x56, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xAA, 0xAB, 0xEA, 0xEB },
			new int[] { 0x54, 0x55, 0x59, 0x95, 0x98, 0x99, 0x9A, 0xA9, 0xAA, 0xAD, 0xAE, 0xE9, 0xEA, 0xEE },
			new int[] { 0x55, 0x59, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xAE, 0xEA, 0xEE },
			new int[] { 0x45, 0x55, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA9, 0xAA, 0xAE, 0xEA, 0xEE },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xAA, 0xAB, 0xAE, 0xEA, 0xEF },
			new int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x91, 0x94, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xE5, 0xEA },
			new int[] { 0x51, 0x55, 0x65, 0x91, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA, 0xE6, 0xEA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x91, 0x95, 0x96, 0xA5, 0xA6, 0xAA, 0xE6, 0xEA },
			new int[] { 0x51, 0x55, 0x56, 0x66, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xA7, 0xAA, 0xAB, 0xE6, 0xEA, 0xEB },
			new int[] { 0x54, 0x55, 0x65, 0x94, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xE9, 0xEA },
			new int[] { 0x55, 0x65, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x51, 0x55, 0x56, 0x66, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xAA, 0xAB, 0xEA, 0xEB },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x94, 0x95, 0x99, 0xA5, 0xA9, 0xAA, 0xE9, 0xEA },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x55, 0x56, 0x59, 0x65, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },
			new int[] { 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xEA, 0xEB },
			new int[] { 0x54, 0x55, 0x59, 0x69, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xAD, 0xAE, 0xE9, 0xEA, 0xEE },
			new int[] { 0x54, 0x55, 0x59, 0x69, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xAE, 0xEA, 0xEE },
			new int[] { 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAE, 0xEA, 0xEE },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xA9, 0xAA, 0xAB, 0xAE, 0xEA },
			new int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x95, 0xA1, 0xA4, 0xA5, 0xA6, 0xA9, 0xAA, 0xB5, 0xBA, 0xE5, 0xEA, 0xFA },
			new int[] { 0x51, 0x55, 0x65, 0x95, 0xA1, 0xA5, 0xA6, 0xA9, 0xAA, 0xB6, 0xBA, 0xE6, 0xEA, 0xFA },
			new int[] { 0x51, 0x55, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA, 0xB6, 0xBA, 0xE6, 0xEA, 0xFA },
			new int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xA7, 0xAA, 0xAB, 0xB6, 0xBA, 0xE6, 0xEA, 0xFB },
			new int[] { 0x54, 0x55, 0x65, 0x95, 0xA4, 0xA5, 0xA6, 0xA9, 0xAA, 0xB9, 0xBA, 0xE9, 0xEA, 0xFA },
			new int[] { 0x55, 0x65, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA, 0xEA, 0xFA },
			new int[] { 0x51, 0x55, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA, 0xEA, 0xFA },
			new int[] { 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xAA, 0xAB, 0xBA, 0xEA, 0xFB },
			new int[] { 0x54, 0x55, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xB9, 0xBA, 0xE9, 0xEA, 0xFA },
			new int[] { 0x54, 0x55, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA, 0xEA, 0xFA },
			new int[] { 0x55, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA, 0xEA, 0xFA },
			new int[] { 0x55, 0x56, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xBA, 0xEA },
			new int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA9, 0xAA, 0xAD, 0xAE, 0xB9, 0xBA, 0xE9, 0xEA, 0xFE },
			new int[] { 0x55, 0x59, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA9, 0xAA, 0xAE, 0xBA, 0xEA, 0xFE },
			new int[] { 0x55, 0x59, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAE, 0xBA, 0xEA },
			new int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xAE, 0xBA, 0xEA },
		};
		LatticeVertex4D[] latticeVerticesByCode = new LatticeVertex4D[256];
		for ( int i = 0; i < 256; i++ )
		{
			int cx = ((i >> 0) & 3) - 1;
			int cy = ((i >> 2) & 3) - 1;
			int cz = ((i >> 4) & 3) - 1;
			int cw = ((i >> 6) & 3) - 1;
			latticeVerticesByCode[i] = new LatticeVertex4D( cx, cy, cz, cw );
		}
		int nLatticeVerticesTotal = 0;
		for ( int i = 0; i < 256; i++ )
		{
			nLatticeVerticesTotal += lookup4DVertexCodes[i].Length;
		}
		LOOKUP_4D_A = new (short SecondaryIndexStart, short SecondaryIndexStop)[256];
		LOOKUP_4D_B = new LatticeVertex4D[nLatticeVerticesTotal];
		for ( int i = 0, j = 0; i < 256; i++ )
		{
			LOOKUP_4D_A[i] = ((short)j, (short)(j + lookup4DVertexCodes[i].Length));
			for ( int k = 0; k < lookup4DVertexCodes[i].Length; k++ )
			{
				LOOKUP_4D_B[j++] = latticeVerticesByCode[lookup4DVertexCodes[i][k]];
			}
		}
	}

	private class LatticeVertex4D
	{
		public readonly float dx, dy, dz, dw;
		public readonly long xsvp, ysvp, zsvp, wsvp;
		public LatticeVertex4D( int xsv, int ysv, int zsv, int wsv )
		{
			this.xsvp = xsv * PRIME_X; this.ysvp = ysv * PRIME_Y;
			this.zsvp = zsv * PRIME_Z; this.wsvp = wsv * PRIME_W;
			float ssv = (xsv + ysv + zsv + wsv) * UNSKEW_4D;
			this.dx = -xsv - ssv;
			this.dy = -ysv - ssv;
			this.dz = -zsv - ssv;
			this.dw = -wsv - ssv;
		}
	}
}
using Editor;
using Sandbox;
using System;

namespace Sturnus.TerrainGenerationTool;
public static class Islands
{
	public static float Default( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize = 0.1f, float warpStrength = 0.5f )
	{
		float nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]
		float ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]
		float warpX;
		float warpY;
		float warpedNx;
		float warpedNy;
		float noise;
		if ( warp )
		{
			// Generate warp offsets using additional noise
			warpX = OpenSimplex2S.Noise2( seed + 10, nx * warpSize, ny * warpSize ) * warpStrength;
			warpY = OpenSimplex2S.Noise2( seed + 11, nx * warpSize, ny * warpSize ) * warpStrength;

			// Apply domain warping
			warpedNx = nx + warpX;
			warpedNy = ny + warpY;
		}
		else
		{
			warpedNx = nx;
			warpedNy = ny;
		}
		
		// Radial distance from the center
		float distance = (float)Math.Sqrt( nx * nx + ny * ny );
		float falloff = 1.0f - Math.Clamp( distance, 0.25f, 1 ); // Smooth taper from center to edge

		// Central mountain shape (parabolic for smooth curvature)
		float centralMountain = (1.0f - distance * distance) * falloff;

		// Beach-style taper near the edges
		float beachStart = 0.5f; // Start of the beach region (distance normalized)
		float beachEnd = 0.98f;   // End of the beach region (ocean level)
		float beachFalloff = Math.Clamp( (distance - beachStart) / (beachEnd - beachStart), 0.1f, 1 );
		float beachTaper = (1.0f - beachFalloff) * 0.25f; // Smooth transition to flat region

		// Add subtle noise for terrain variation
		if ( warp )
		{
			noise = OpenSimplex2S.Noise2( seed, warpedNx * 2, warpedNy * 2 ) * 0.4f;
		}
		else
		{
			noise = OpenSimplex2S.Noise2( seed, nx * 6, ny * 6 ) * 0.05f; // Low-frequency noise
		}
		
		// Combine components: central mountain, beach taper, and noise
		float output = centralMountain * (1.0f - beachFalloff) + beachTaper + noise;

		// Combine all effects
		float heightValue = output;

		// Add a baseline value to ensure no flat zero areas
		float baseline = minHeight; // Minimum height
		float heightValueCombined = MathF.Max( heightValue, baseline );

		// Clamp the final height to valid range
		return heightValueCombined;
	}

	public static float Archipelagos( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize, float warpStrength )
	{
		Random random = new Random( (int)(seed & 0xFFFFFFFF) );
		float nx = (x / (float)width) * 2 - 1;
		float ny = (y / (float)height) * 2 - 1;

		// Apply domain warping
		if ( warp )
		{
			float warpX = OpenSimplex2S.Noise2( seed + 10, nx * warpSize, ny * warpSize ) * warpStrength;
			float warpY = OpenSimplex2S.Noise2( seed + 11, nx * warpSize, ny * warpSize ) * warpStrength;
			nx += warpX;
			ny += warpY;
		}

		// Use noise layers to create clusters of small islands
		float baseNoise = OpenSimplex2S.Noise2( seed, nx * 1.5f, ny * 1.5f );
		float secondaryNoise = OpenSimplex2S.Noise2( seed + 1, nx * 3.0f, ny * 3.0f ) * 0.5f;

		float archipelagoHeight = baseNoise + secondaryNoise;

		// Apply radial falloff to form rounded island clusters
		float distance = MathF.Sqrt( nx * nx + ny * ny );
		float falloff = Math.Clamp( 1 - distance * 1.2f, 0, 1 );
		float heightValue = Math.Clamp( archipelagoHeight * falloff, 0, 1 );
		// Add a baseline value to ensure no flat zero areas
		float baseline = minHeight; // Minimum height
		float heightValueCombined = MathF.Max( heightValue, baseline );

		// Clamp the final height to valid range
		return heightValueCombined;
	}

	public static float Atoll( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize, float warpStrength )
	{
		Random random = new Random( (int)(seed & 0xFFFFFFFF) );
		float nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]
		float ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]

		// Apply domain warping
		if ( warp )
		{
			float warpX = OpenSimplex2S.Noise2( seed + 20, nx * warpSize, ny * warpSize ) * warpStrength;
			float warpY = OpenSimplex2S.Noise2( seed + 21, nx * warpSize, ny * warpSize ) * warpStrength;
			nx += warpX;
			ny += warpY;
		}

		// Calculate distance from the center
		float distance = MathF.Sqrt( nx * nx + ny * ny );

		// Define parameters for the single ring
		float ringCenter = 0.6f; // Center of the ring
		float ringWidth = 0.1f;  // Width of the ring

		// Create a single ring using a Gaussian-like function
		float ring = MathF.Exp( -MathF.Pow( (distance - ringCenter) / ringWidth, 2 ) );

		// Add some noise for variation
		float baseNoise = OpenSimplex2S.Noise2( seed, nx * 1.0f, ny * 1.0f ) * 0.4f;

		// Introduce a beach-like area (reduce noise and height for one side of the map)
		float beachEffect = Math.Clamp( (1 - nx) * 0.5f, 0.2f, 1.0f ); // Reduces height on one side of the map
		float beachNoise = OpenSimplex2S.Noise2( seed + 30, nx * 2.0f, ny * 2.0f ) * 0.2f;

		// Combine the ring, noise, and beach effect
		float heightValue = (ring + baseNoise * beachEffect + beachNoise) * beachEffect;

		// Add a baseline value to ensure no flat zero areas
		float baseline = minHeight; // Minimum height
		float heightValueCombined = MathF.Max( heightValue, baseline );

		// Clamp the final height to valid range
		return heightValueCombined;
	}




	public static float Islets( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize, float warpStrength )
	{
		Random random = new Random( (int)(seed & 0xFFFFFFFF) );
		float nx = (x / (float)width) * 2 - 1;
		float ny = (y / (float)height) * 2 - 1;

		// Apply domain warping
		if ( warp )
		{
			float warpX = OpenSimplex2S.Noise2( seed + 30, nx * warpSize, ny * warpSize ) * warpStrength;
			float warpY = OpenSimplex2S.Noise2( seed + 31, nx * warpSize, ny * warpSize ) * warpStrength;
			nx += warpX;
			ny += warpY;
		}

		// Generate scattered small islands
		float scatterNoise = OpenSimplex2S.Noise2( seed, nx * 6.0f, ny * 6.0f );
		float baseNoise = OpenSimplex2S.Noise2( seed + 1, nx * 3.0f, ny * 3.0f ) * 0.5f;

		float isletHeight = scatterNoise + baseNoise;

		// Apply distance falloff to create isolated islets
		float distance = MathF.Sqrt( nx * nx + ny * ny );
		float falloff = Math.Clamp( 1 - distance * 1.5f, 0, 1 );
		float heightValue = Math.Clamp( isletHeight * falloff, 0, 1 );
		// Add a baseline value to ensure no flat zero areas
		float baseline = minHeight; // Minimum height
		float heightValueCombined = MathF.Max( heightValue, baseline );

		// Clamp the final height to valid range
		return heightValueCombined;
	}

	public static float Oceanic( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize, float warpStrength )
	{
		Random random = new Random( (int)(seed & 0xFFFFFFFF) );
		float nx = (x / (float)width) * 2 - 1;
		float ny = (y / (float)height) * 2 - 1;

		// Apply domain warping
		if ( warp )
		{
			float warpX = OpenSimplex2S.Noise2( seed + 40, nx * warpSize, ny * warpSize ) * warpStrength;
			float warpY = OpenSimplex2S.Noise2( seed + 41, nx * warpSize, ny * warpSize ) * warpStrength;
			nx += warpX;
			ny += warpY;
		}

		// Generate large, continuous landmass with a few scattered features
		float baseNoise = OpenSimplex2S.Noise2( seed, nx * 1.0f, ny * 1.0f );
		float featureNoise = OpenSimplex2S.Noise2( seed + 1, nx * 2.0f, ny * 2.0f ) * 0.5f;

		float oceanicHeight = baseNoise + featureNoise;

		// Apply radial falloff for a natural ocean/land mix
		float distance = MathF.Sqrt( nx * nx + ny * ny );
		float falloff = Math.Clamp( 1 - distance * 1.0f, 0, 1 );

		float heightValue = Math.Clamp( oceanicHeight * falloff, 0, 1 );

		float baseline = minHeight; // Minimum height
		float heightValueCombined = MathF.Max( heightValue, baseline );

		// Clamp the final height to valid range
		return heightValueCombined;
	}

}
using Editor;
using Sandbox;
using System;
using System.Collections.Generic;

namespace Sturnus.TerrainGenerationTool;
public static class Planetary
{
	public static float Sharded(
		int x,
		int y,
		int width,
		int height,
		long seed,
		float minHeight,
		bool warp,              // Apply domain warping
		float warpSize,         // Warp scale
		float warpStrength      // Warp strength
	)
	{
		Random random = new Random( (int)(seed & 0xFFFFFFFF) );
		float nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]
		float ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]

		float shardHeight = 10f;
		float crackDepth = 1f;
		float noiseStrength = 0.05f;
		int cellCount = 5;

		// Apply domain warping for irregularity
		if ( warp )
		{
			float warpX = OpenSimplex2S.Noise2( seed + 10, nx * warpSize, ny * warpSize ) * warpStrength;
			float warpY = OpenSimplex2S.Noise2( seed + 11, nx * warpSize, ny * warpSize ) * warpStrength;
			nx += warpX;
			ny += warpY;
		}

		// Generate cracks
		float minDist = float.MaxValue;
		float secondaryDist = float.MaxValue;
		float cellSize = 2.0f / cellCount; // Normalize the cell size
		for ( int i = 0; i < cellCount; i++ )
		{
			for ( int j = 0; j < cellCount; j++ )
			{
				float cellX = -1 + i * cellSize + OpenSimplex2S.Noise2( seed + 20, i, j ) * cellSize * 0.5f;
				float cellY = -1 + j * cellSize + OpenSimplex2S.Noise2( seed + 21, i, j ) * cellSize * 0.5f;

				float distance = MathF.Sqrt( (nx - cellX) * (nx - cellX) + (ny - cellY) * (ny - cellY) );

				if ( distance < minDist )
				{
					secondaryDist = minDist;
					minDist = distance;
				}
				else if ( distance < secondaryDist )
				{
					secondaryDist = distance;
				}
			}
		}

		// Compute shard height based on the secondary distance
		float shardNoise = OpenSimplex2S.Noise2( seed + 30, nx, ny ) * noiseStrength;
		float heightValue = MathF.Max( secondaryDist - minDist, 0f ) * shardHeight + shardNoise;

		// Apply crack depth at borders between shards
		if ( secondaryDist - minDist < 0.03f ) // Control the width of cracks
		{
			heightValue -= crackDepth;
		}

		// Add a baseline value to ensure no flat zero areas
		//heightValue = MathF.Max( heightValue, minHeight );

		// Clamp height to avoid negative values
		heightValue = Math.Clamp( heightValue, 0, 1 );

		float baseline = minHeight; // Minimum height
		float heightValueCombined = MathF.Max( heightValue, baseline );

		// Clamp the final height to valid range
		return heightValueCombined;
	}

	public static float Craters(
		int x,
		int y,
		int width,
		int height,
		long seed,
		float minHeight,
		bool warp,             // Apply domain warping for irregularity
		float warpSize,        // Warp scale
		float warpStrength     // Warp strength
	)
	{
		Random random = new Random( (int)(seed & 0xFFFFFFFF) );
		float nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]
		float ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]

		int craterCount = 100;
		float minCraterSize = 0.1f;
        float maxCraterSize = 0.3f;
        float craterDepth = 0.05f;
        float rimHeight = 0.05f;
        float rimWidthRatio = 0.2f;
        float noiseStrength = 0.05f;
        float largeCraterRatio = 0.2f;
		float slopeFalloff = 0.9f;  // Controls smoothness of ramps


		// Apply domain warping for irregularity
		if ( warp )
		{
			float warpX = OpenSimplex2S.Noise2( seed + 10, nx * warpSize, ny * warpSize ) * warpStrength;
			float warpY = OpenSimplex2S.Noise2( seed + 11, nx * warpSize, ny * warpSize ) * warpStrength;
			nx += warpX;
			ny += warpY;
		}

		// Base terrain noise
		float baseTerrain = OpenSimplex2S.Noise2( seed + 1, nx * 2.0f, ny * 2.0f ) * noiseStrength;
		baseTerrain = (baseTerrain + 1) * 0.5f; // Normalize to [0, 1]

		float heightValue = baseTerrain;

		// Iterate through craters in reverse order to overwrite previous craters
		for ( int i = craterCount - 1; i >= 0; i-- )
		{
			// Randomize crater properties
			float craterX = random.Next( -100000, 100000 ) / 50000.0f;
			float craterY = random.Next( -100000, 100000 ) / 50000.0f;
			float craterRadius = (i < craterCount * largeCraterRatio)
				? random.Next( (int)(maxCraterSize * 500), (int)(maxCraterSize * 1000) ) / 1000.0f // Large craters
				: random.Next( (int)(minCraterSize * 500), (int)(minCraterSize * 1000) ) / 1000.0f; // Small craters

			// Distance from the current point to the crater center
			float distance = MathF.Sqrt( (nx - craterX) * (nx - craterX) + (ny - craterY) * (ny - craterY) );

			if ( distance < craterRadius )
			{
				float rimStart = craterRadius * (1f - rimWidthRatio);
				float rimEnd = craterRadius;

				// Inside the pit
				if ( distance < rimStart )
				{
					float pitFalloff = Math.Clamp( 1f - (distance / rimStart), 0f, 1f );
					heightValue = baseTerrain - MathF.Pow( pitFalloff, slopeFalloff ) * craterDepth; // Smooth ramp to the center
				}
				// Raised rim
				else if ( distance >= rimStart && distance < rimEnd )
				{
					float rimFalloff = Math.Clamp( (distance - rimStart) / (rimEnd - rimStart), 0f, 1f );
					heightValue = baseTerrain + MathF.Pow( 1f - rimFalloff, slopeFalloff ) * rimHeight; // Rounded rim
				}

				// Reset terrain below the rim to prevent intersecting ridges
				if ( distance >= rimEnd )
				{
					heightValue = baseTerrain;
				}

				// Exit the loop once the current crater is applied
				break;
			}
		}

		// Ensure height values are clamped to [0, 1]
		heightValue = Math.Clamp( heightValue, 0, 1 );

		return heightValue;
	}


}
using Editor;
using Sandbox;
using Sandbox.UI;

namespace Panelize;

public class Properties : Widget
{
	public static object SelectedObject { get; set; }
	private static object currentSelectedObject { get; set; }
	Layout editor;
	public Properties(MainWindow mainWindow) : base(mainWindow)
	{
		WindowTitle = "Properties";
		Layout = Layout.Column();
		editor = Layout.AddRow( 1 );

		SelectedObject = null;
		currentSelectedObject = null;
	}

	[EditorEvent.Frame]
	public void Frame()
	{
		//Log.Info( $"Select {SelectedObject}" );

		if ( SelectedObject != currentSelectedObject )
		{
			Select( SelectedObject );
		}
	}

	private void Select(object obj )
	{
		currentSelectedObject = obj;

		editor.Clear(true);

		var so = obj.GetSerialized();
		Widget inspector = null;
		if(obj is Panel p )
		{
			inspector = new PanelInspector( this, p, PanelEditorSession.Current );
		}
		else
		{
			inspector = InspectorWidget.Create( so );
		}

		if ( inspector.IsValid() )
		{
			editor.Add( inspector, 1 );
		}
		else
		{
			// Try CanEdit still..
			// todo: Everything that should be an inspector should be an InspectorWidget
			inspector = CanEditAttribute.CreateEditorForObject( obj );

			if ( inspector.IsValid() )
			{
				editor.Add( inspector, 1 );
			}
			else
			{
				try
				{
					var sheet = new ControlSheet();
					sheet.AddObject( so );

					var scroller = new ScrollArea( this );
					scroller.Canvas = new Widget();
					scroller.Canvas.Layout = Layout.Column();
					scroller.Canvas.VerticalSizeMode = SizeMode.CanGrow;
					scroller.Canvas.HorizontalSizeMode = SizeMode.Flexible;

					scroller.Canvas.Layout.Add( sheet );
					scroller.Canvas.Layout.AddStretchCell();

					editor.Add( scroller );
				}
				catch { }

			}
		}
	}
}
using Sandbox;
using Sandbox.Diagnostics;
using Sandbox.UI;
using System.Linq;

namespace Panelize;

public class PanelBrowser : Widget
{
	public float EntrySize { get; set; } = 200f;
	public int Columns { get; set; } = 2;
	GridLayout grid;
    public PanelBrowser()
    {
		WindowTitle = "Panel List";
		Layout = Layout.Column();

		grid = Layout.AddLayout(Layout.Grid(), 10);
		grid.Spacing = 5f;
		BuildList();

    }

	private void BuildList()
	{
		grid.Clear( true );
		var builderTypes = EditorTypeLibrary.GetTypes<PanelBuilder>();

		int x = 0;
		int y = 0;
		foreach (var type in builderTypes)
		{
			if ( type.IsAbstract || type.IsGenericType ) continue;

			if (x > Columns)
			{
				x = 0;
				y++;
			}

			PanelPreviewWidget preview = new PanelPreviewWidget( type.Create<PanelBuilder>() )
			{
				FixedWidth = EntrySize,
				FixedHeight = EntrySize
			};
			grid.AddCell(x, y, preview);
			x++;
		}
	}
}

public class PanelPreviewWidget : Widget
{
	PanelBuilder builder;
	Color bgColor = Color.Gray;
	//Drag drag;
    public PanelPreviewWidget( PanelBuilder builder )
    {
		Assert.NotNull( builder );

		this.builder = builder;
		IsDraggable = true;
    }
	protected override void OnPaint()
	{
		Rect size = LocalRect;
		Paint.SetBrushAndPen( bgColor );
		Paint.DrawRect( size.Shrink(5f), 1f );

		Paint.SetPen( Color.White );
		Paint.SetFont( "Roboto", 24f, 800 );
		Paint.DrawText( size, builder.Title );
	}

	bool dragging = false;
	protected override void OnDragStart()
	{
		if ( dragging ) return;

		dragging = true;
		Log.Info( $"Drag Start!" );
	}

	protected override void OnMouseReleased( MouseEvent e )
	{
		if(e.LeftMouseButton && dragging )
		{
			Vector2 pos = e.WindowPosition;
			
			Log.Info( $"Drag stop!" );
			PanelEditorSession.Current.EditorWidget?.OnBuilderDrop( builder, e );
			dragging = false;
		}
	}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Panelize;

public class Checkbox : Widget
{
	public bool Value { get; set; }
	public string IconEnabled { get; set; }
	public string IconDisabled { get; set; }
	public Action<bool> OnEdited;
	public Checkbox( Widget parent = null, bool value = false, string iconEnabled = null, string iconDisabled = null, float? size = null) : base(parent)
	{
		Value = value;
		IconEnabled = iconEnabled;
		IconDisabled = iconDisabled;

		Cursor = CursorShape.Finger;
		MinimumWidth = size ?? ControlWidget.ControlRowHeight;
		MinimumHeight = size ?? ControlWidget.ControlRowHeight;
		HorizontalSizeMode = SizeMode.CanShrink;
		VerticalSizeMode = SizeMode.CanShrink;
	}
	protected override void OnMouseClick( MouseEvent e )
	{
		if(e.LeftMouseButton)
		{
			Value = !Value;
			OnEdited?.Invoke(Value);
		}
	}
	protected override void OnPaint()
	{
		Paint.Antialiasing = true;
		Paint.TextAntialiasing = true;

		float alpha = (ReadOnly ? 0.5f : 1f);
		Rect localRect = LocalRect;

		Color color = Theme.Blue;
		Rect rect = localRect.Shrink( 2 );

		Paint.ClearPen();
		Paint.SetBrush( ControlWidget.ControlColor.Lighten( ReadOnly ? 0.5f : 0f ).WithAlphaMultiplied( alpha ) );
		Paint.DrawRect( rect, 2f );

		if(Value)
		{
			Paint.SetPen( color.WithAlpha( 0.3f * alpha ), 1f );
			Paint.SetBrush( color.WithAlpha( 0.2f * alpha ) );

			Paint.DrawRect( rect, 2f );
			Paint.SetPen( color.WithAlphaMultiplied( 0.5f ) );
			Paint.DrawIcon( rect, IconEnabled ?? "done", 13f );
		}
		else if ( IconDisabled != null )
		{
			Paint.SetPen( Theme.Grey.WithAlphaMultiplied( 0.5f ) );
			Paint.DrawIcon( rect, IconDisabled, 13f );
		}

		if ( IsUnderMouse && !ReadOnly )
		{
			Paint.SetPen( color.WithAlpha(0.5f * alpha), 1);
			Paint.ClearBrush();
			Paint.DrawRect( in rect, 1f );
		}
	}
}
namespace RbxlReader.DataTypes;

public class NumberSequenceKeypoint {
    public float Time;
    public float Value;
    public float Envelope;
}

public class NumberSequence {
    public NumberSequenceKeypoint[] Keypoints;

    public NumberSequence(NumberSequenceKeypoint[] points) {
        Keypoints = points;
    }
}
namespace RbxlReader.DataTypes;

public class UDim {
    public float Scale;
    public int Offset;

    public UDim() {}

    public UDim(float scale, int offset) {
        Scale = scale;
        Offset = offset;
    }
}
using System;
using System.Threading.Tasks;
using Editor;
using Sandbox;

namespace YAAI;

public sealed class YaaiWindow : Window
{
	public Task RunningTask;
	private bool IsRunning = false;
	
	public readonly int MinimumSpeed = 10;
	public readonly int MaximumSpeed = 30;
	
	private Random random = new Random();
	private float RandomSpeed => random.Float( MinimumSpeed, MaximumSpeed );
	public YaaiWindow()
	{
		WindowTitle = "YAAI";

		Width = 250;
		Height = 200;
		HasMaximizeButton = false;
		DeleteOnClose = true;
		WindowFlags = WindowFlags.WithFlag( WindowFlags.WindowStaysOnTopHint, true );
		WindowFlags = WindowFlags.WithFlag( WindowFlags.MinimizeButton, false);
		
		new YaaiWidget(this );
		Show();
		RunningTask = Move();
	}

	protected override bool OnClose()
	{
		// Dispose of Task
		IsRunning = false;
		RunningTask.Wait();
		RunningTask.Dispose();
		
		YaaiManager.OnWindowClose( this );
		return base.OnClose();
	}

	async Task Move()
	{
		IsRunning = true;
		
		bool moveDown = random.Int( 0,1 ) == 1;
		bool moveRight = random.Int( 0,1 ) == 1;
		float Speed = RandomSpeed;
		
		while ( IsWindow && IsRunning )
		{
			float nx = moveRight ? Position.x + Speed/2 : Position.x - Speed/2;
			float ny = moveDown ? Position.y + Speed : Position.y - Speed;

			bool shouldChangeSpeed = false;
			if ( Position.x + Width > ScreenGeometry.Width )
			{
				shouldChangeSpeed = true;
				moveRight = false;
			}
			else if ( Position.x < 0)
			{
				shouldChangeSpeed = true;
				moveRight = true;
			}
			
			if ( Position.y + Height > ScreenGeometry.Height )
			{
				shouldChangeSpeed = true;
				moveDown = false;
			}
			else if ( Position.y < 0)
			{
				shouldChangeSpeed = true;
				moveDown = true;
			}

			if ( shouldChangeSpeed )
				Speed = RandomSpeed;

			Position = Position.WithX( nx ).WithY( ny );
			await Task.Delay( 25 );
		}
		
	}

}
using Sandbox;
using System;

public class EditorTheme
{
	[Group( "Window" )] public Color TabBackground { get; set; } = new Color( 0.231f, 0.231f, 0.231f );
	[Group( "Window" )] public Color TabBarBackground { get; set; } = new Color( 0.141f, 0.141f, 0.141f );
	[Group( "Window" )] public Color TabInactiveBackground { get; set; } = new Color( 0.141f, 0.141f, 0.141f );
	[Group( "Window" )] public Color SurfaceBackground { get; set; } = new Color( 0.231f, 0.231f, 0.231f );
	[Group( "Window" )] public Color SurfaceLightBackground { get; set; } = new Color( 0.412f, 0.412f, 0.412f );
	[Group( "Window" )] public Color SidebarBackground { get; set; } = new Color( 0.141f, 0.141f, 0.141f );
	[Group( "Window" )] public Color WindowBackground { get; set; } = new Color( 0.094f, 0.094f, 0.094f );
	[Group( "Window" )] public Color WidgetBackground { get; set; } = new Color( 0.141f, 0.141f, 0.141f );
	[Group( "Window" )] public Color ControlBackground { get; set; } = new Color( 0.094f, 0.094f, 0.094f );
	[Group( "Window" )] public Color ButtonBackground { get; set; } = new Color( 0.231f, 0.231f, 0.231f );
	[Group( "Window" )] public Color SelectedBackground { get; set; } = new Color( 0.502f, 0.502f, 0.502f );
	[Group( "Window" )] public Color StatusBarBackground { get; set; } = new Color( 0.141f, 0.141f, 0.141f );
	[Group( "Text" )] public Color Text { get; set; } = new Color( 1.0f, 1.0f, 1.0f );
	[Group( "Text" )] public Color TextControl { get; set; } = new Color( 1.0f, 1.0f, 1.0f );
	[Group( "Text" )] public Color TextLight { get; set; } = new Color( 0.62f, 0.62f, 0.62f );
	[Group( "Text" )] public Color TextWidget { get; set; } = new Color( 1.0f, 1.0f, 1.0f );
	[Group( "Text" )] public Color TextButton { get; set; } = new Color( 1.0f, 1.0f, 1.0f );
	[Group( "Text" )] public Color TextSelected { get; set; } = new Color( 0.4f, 0.639f, 1.0f );
	[Group( "Text" )] public Color TextLink { get; set; } = new Color( 0.4f, 0.639f, 1.0f );
	[Group( "Text" )] public Color TextHighlight { get; set; } = new Color( 0.4f, 0.639f, 1.0f );
	[Group( "Text" )] public Color TextDisabled { get; set; } = new Color( 1.0f, 1.0f, 1.0f, 0.47f );
	[Group( "Text" )] public Color TextDark { get; set; } = new Color( 0.0f, 0.0f, 0.0f );
	[Group( "Window" )] public Color Border { get; set; } = new Color( 0.322f, 0.322f, 0.322f );
	[Group( "Window" )] public Color BorderLight { get; set; } = new Color( 0.412f, 0.412f, 0.412f );
	[Group( "Window" )] public Color BorderButton { get; set; } = new Color( 0.412f, 0.412f, 0.412f );
	[Group( "Window" )] public Color Shadow { get; set; } = new Color( 0.141f, 0.141f, 0.141f );
	[Group( "Window" )] public Color Primary { get; set; } = new Color( 0.353f, 0.553f, 0.922f );
	[Group( "Window" )] public Color Overlay { get; set; } = new Color( 0.141f, 0.141f, 0.141f );
	[Group( "Window" )] public Color MultipleValues { get; set; } = new Color( 0.502f, 0.502f, 0.502f );
	[Group( "Window" )] public Color Highlight { get; set; } = new Color( 0.298f, 0.478f, 0.749f );

	[Group( "Checkbox" )] public Color ToggleEnabled { get; set; } = new Color( 0.353f, 0.922f, 0.361f );
	[Group( "Checkbox" )] public Color ToggleDisabled { get; set; } = new Color( 0.337f, 0.431f, 0.337f );

	[Group( "Window" )] public Color Base { get; set; } = new Color(0.125f, 0.125f, 0.125f);

	[Group( "Window" )] public Color BaseAlt { get; set; } = new Color(0.164f,0.164f,0.164f);
	
	[Group( "Colors?" )] public Color Blue { get; set; } = new Color( 0.353f, 0.553f, 0.922f );

	[Group( "Colors?" )] public Color Green { get; set; } = new Color( 0.690f, 0.89f, 0.302f );

	[Group( "Colors?" )] public Color Red { get; set; } = new Color( 0.984f, 0.353f, 0.353f );

	[Group( "Colors?" )] public Color Yellow { get; set; } = new Color( 0.902f, 0.859f, 0.455f );

	[Group( "Colors?" )] public Color Pink { get; set; } = new Color( 0.874f, 0.569f, 0.580f );

	[Group( "Files" )] public Color Prefab { get; set; } = new Color( 0.353f, 0.553f, 0.922f );

	[Group( "Files" )] public Color Folder { get; set; } = new Color( 0.902f, 0.859f, 0.455f );

	[Group( "Layout" )] public int RowHeight { get; set; } = 22;
	[Group( "Layout" )] public int ControlHeight { get; set; } = 22;
	[Group( "Layout" )] public int ControlRadius { get; set; } = 4;

	[FontName][Group( "Text" )] public string HeadingFont { get; set; } = "Inter";

	[FontName][Group( "Text" )] public string DefaultFont { get; set; } = "Inter";

	public EditorTheme()
	{ }

	public static EditorTheme ShallowCopy( EditorTheme source )
	{
		if ( source == null )
			throw new ArgumentNullException( nameof( source ) );

		EditorTheme copy = new EditorTheme();
		var properties = typeof( EditorTheme ).GetProperties();

		foreach ( var prop in properties )
		{
			if ( prop.CanRead && prop.CanWrite )
			{
				var value = prop.GetValue( source, null );
				prop.SetValue( copy, value, null );
			}
		}

		return copy;
	}
}
using System.Text;
using System.Text.Json.Serialization;
using Sandbox.MovieMaker;

namespace Editor.MovieMaker;

#nullable enable

/// <summary>
/// Describes a translation and scale that can be applied to <see cref="MovieTime"/>s.
/// </summary>
/// <param name="Translation">Time offset to apply.</param>
/// <param name="Scale">Time scale to apply.</param>
public readonly record struct MovieTransform(
	[property: JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingDefault )]
	MovieTime Translation = default,
	[property: JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingDefault )]
	MovieTimeScale Scale = default )
{
	public static MovieTransform Identity => default;

	[JsonIgnore]
	public MovieTransform Inverse => new( Scale.Inverse * -Translation, Scale.Inverse );

	public static MovieTime operator *( MovieTransform transform, MovieTime time ) =>
		time * transform.Scale + transform.Translation;

	public static MovieTimeRange operator *( MovieTransform transform, MovieTimeRange timeRange ) =>
		(transform * timeRange.Start, transform * timeRange.End);

	public static MovieTransform operator *( MovieTransform a, MovieTransform b ) =>
		new( a * b.Translation, a.Scale * b.Scale );
	public static MovieTransform operator *( MovieTimeScale a, MovieTransform b ) =>
		new( a * b.Translation, a * b.Scale );

	public static MovieTransform operator +( MovieTransform transform, MovieTime translation ) =>
		transform with { Translation = transform.Translation + translation };

	public static MovieTransform FromTo( MovieTimeRange from, MovieTimeRange to )
	{
		var scale = MovieTimeScale.FromDurationChange( from.Duration, to.Duration );

		return scale * new MovieTransform( -from.Start ) + to.Start;
	}

	private bool PrintMembers( StringBuilder builder )
	{
		if ( this == Identity )
		{
			builder.Append( "Identity" );
			return true;
		}

		if ( !Translation.IsZero )
		{
			builder.Append( $"{nameof(Translation)} = {Translation}" );
		}

		if ( Scale != MovieTimeScale.Identity )
		{
			if ( !Translation.IsZero )
			{
				builder.Append( ", " );
			}

			builder.Append( $"{nameof(Scale)} = {Scale}" );
		}

		return true;
	}
}

namespace Editor.MovieMaker.BlockDisplays;

#nullable enable

public abstract class ThumbnailBlockItem<T> : PropertyBlockItem<T>
{
	protected abstract Pixmap? GetThumbnail();

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

		if ( GetThumbnail() is { } thumb )
		{
			Paint.Draw( LocalRect.Contain( Height ), thumb, 0.5f );
		}
	}
}

public sealed class ResourceBlockItem<T> : ThumbnailBlockItem<T>
	where T : Resource
{
	protected override Pixmap? GetThumbnail() => Block.GetValue( Block.TimeRange.Start ) is { ResourcePath: { } path }
		? AssetSystem.FindByPath( path )?.GetAssetThumb()
		: null;
}
using System.Linq;
using Sandbox.MovieMaker;

namespace Editor.MovieMaker;

#nullable enable

// [MovieModification( "Stretch", Icon = "width_full" )]
file sealed class StretchModification() : PerTrackModification<StretchOptions>( StretchOptions.Default, true )
{
	public override void Start( TimeSelection selection )
	{
		Options = Options with { SourceDuration = selection.TotalTimeRange.Duration };
	}

	protected override ITrackModification<TValue> OnCreateModification<TValue>( IPropertyTrack<TValue> track ) =>
		new StretchTrackModification<TValue>();
}

file sealed record StretchOptions( MovieTime SourceDuration = default ) : IModificationOptions
{
	public static StretchOptions Default => new();
}

file sealed class StretchTrackModification<T> : ITrackModification<T, StretchOptions>
{
	public IEnumerable<PropertyBlock<T>> Apply( IReadOnlyList<PropertyBlock<T>> original,
		TimeSelection selection, StretchOptions options )
	{
		return options.SourceDuration > 0 && options.SourceDuration != selection.TotalTimeRange
			? original.Select( x => Stretch( x, selection, options ) )
			: original;
	}

	private PropertyBlock<T> Stretch( PropertyBlock<T> original, TimeSelection selection, StretchOptions options )
	{
		var signal = original.Signal.SlidingStretch( options.SourceDuration, selection );

		return new PropertyBlock<T>( signal, original.TimeRange );
	}
}
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json.Serialization;
using Sandbox.MovieMaker.Compiled;
using Sandbox.MovieMaker;

namespace Editor.MovieMaker;

#nullable enable

[JsonDiscriminator( "Source" )]
[method: JsonConstructor]
file sealed record CompiledSignal<T>( ProjectSourceClip Source, int TrackIndex, int BlockIndex,
	[property: JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingDefault )] MovieTransform Transform = default,
	[property: JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingDefault )] MovieTime SmoothingSize = default ) : PropertySignal<T>
{
	private IReadOnlyList<T>? _samples;
	private CompiledSampleBlock<T>? _block;

	private CompiledSampleBlock<T> Block => _block ??= (CompiledSampleBlock<T>)((CompiledPropertyTrack<T>)Source.Clip.Tracks[TrackIndex]).Blocks[BlockIndex];

	private IReadOnlyList<T> Samples => _samples ??= Block.Resample( Block.SampleRate, SmoothingSize, _interpolator );

	public CompiledSignal( CompiledSignal<T> copy )
		: base( copy )
	{
		Source = copy.Source;
		TrackIndex = copy.TrackIndex;
		BlockIndex = copy.BlockIndex;
		Transform = copy.Transform;
		SmoothingSize = copy.SmoothingSize;

		_samples = null;
		_block = null;
	}

	private MovieTime GetLocalTime( MovieTime time ) =>
		(Transform.Inverse * time).Clamp( Block.TimeRange ) - Block.TimeRange.Start - Block.Offset;

	public override T GetValue( MovieTime time ) =>
		Samples.Sample( GetLocalTime( time ), Block.SampleRate, _interpolator );

	protected override PropertySignal<T> OnTransform( MovieTransform transform ) =>
		this with { Transform = transform * Transform };

	protected override PropertySignal<T> OnReduce( MovieTime? start, MovieTime? end )
	{
		if ( start is { } s && GetLocalTime( s ) >= Block.TimeRange.Duration ) return Block.GetValue( Block.TimeRange.End );
		if ( end is { } e && GetLocalTime( e ) <= 0d ) return Block.GetValue( Block.TimeRange.Start );

		return this;
	}

	protected override PropertySignal<T> OnSmooth( MovieTime size ) =>
		_interpolator is null ? this : this with { SmoothingSize = size };

	public override IEnumerable<MovieTimeRange> GetPaintHints( MovieTimeRange timeRange )
	{
		if ( timeRange.Intersect( Transform * Block.TimeRange ) is { } intersection )
		{
			return [intersection];
		}

		return [];
	}

	protected override bool PrintMembers( StringBuilder builder )
	{
		builder.Append( $"Source = {Source}, " );
		builder.Append( $"Block = {Block.TimeRange}" );

		if ( Transform != MovieTransform.Identity )
		{
			builder.Append( $", Transform = {Transform}" );
		}

		if ( SmoothingSize != default )
		{
			builder.Append( $", SmoothingSize = {SmoothingSize}" );
		}

		return true;
	}

	public override int GetHashCode()
	{
		return HashCode.Combine( Source, TrackIndex, BlockIndex, Transform, SmoothingSize );
	}

	public bool Equals( CompiledSignal<T>? other )
	{
		if ( other is null ) return false;

		return Source.Equals( other.Source )
			&& TrackIndex == other.TrackIndex
			&& BlockIndex == other.BlockIndex
			&& Transform == other.Transform
			&& SmoothingSize == other.SmoothingSize;
	}

	private static readonly IInterpolator<T>? _interpolator = Interpolator.GetDefault<T>();
}

file sealed class ResampleCache<T>
{
	private readonly record struct Key( int SampleRate, MovieTime SmoothingSize );

#pragma warning disable SB3000
	[SkipHotload]
	private static ConditionalWeakTable<CompiledSampleBlock<T>, Dictionary<Key, WeakReference<T[]>>> Cache { get; } = new();
#pragma warning restore SB3000

	public static T[]? Get( CompiledSampleBlock<T> block, int sampleRate, MovieTime smoothingSize )
	{
		return Cache.TryGetValue( block, out var dict )
			&& dict.TryGetValue( new( sampleRate, smoothingSize ), out var weakRef )
			&& weakRef.TryGetTarget( out var array ) ? array : null;
	}

	public static void Set( CompiledSampleBlock<T> block, int sampleRate, MovieTime smoothingSize, T[] array )
	{
		if ( !Cache.TryGetValue( block, out var dict ) )
		{
			Cache.TryAdd( block, dict = new Dictionary<Key, WeakReference<T[]>>() );
		}

		dict[new( sampleRate, smoothingSize )] = new WeakReference<T[]>( array );
	}
}

partial class PropertySignalExtensions
{
	public static IReadOnlyList<PropertyBlock<T>> AsBlocks<T>( this ProjectSourceClip source, IProjectPropertyTrack track )
	{
		var (refTrack, propertyPath) = track.GetPath();

		if ( source.Clip.GetProperty<T>( refTrack.Id, propertyPath ) is not { } matchingTrack )
		{
			return [];
		}

		var trackIndex = source.Clip.Tracks.IndexOf( matchingTrack );

		return matchingTrack.Blocks
			.Select( (x, i) => new PropertyBlock<T>( x switch
			{
				CompiledConstantBlock<T> constant => constant.Value,
				CompiledSampleBlock<T> => new CompiledSignal<T>( source, trackIndex, i ),
				_ => throw new NotImplementedException()
			}, x.TimeRange ) )
			.ToImmutableArray();
	}

	public static IReadOnlyList<T> Resample<T>( this CompiledSampleBlock<T> source, int sampleRate,
		MovieTime smoothingSize, IInterpolator<T>? interpolator )
	{
		if ( interpolator is null )
		{
			smoothingSize = default;
		}

		if ( sampleRate == source.SampleRate && smoothingSize <= 0d )
		{
			return source.Samples;
		}

		if ( ResampleCache<T>.Get( source, sampleRate, smoothingSize ) is { } cached )
		{
			return cached;
		}

		var sampleCount = sampleRate == source.SampleRate
			? source.Samples.Length
			: source.TimeRange.Duration.GetFrameCount( sampleRate );

		var samples = new T[sampleCount];
		var sourceSamples = source.Samples;

		if ( sampleRate == source.SampleRate )
		{
			sourceSamples.CopyTo( samples );
		}
		else
		{
			for ( var i = 0; i < sampleCount; i++ )
			{
				var t = MovieTime.FromFrames( i, sampleRate );
				samples[i] = sourceSamples.Sample( t, sampleRate, interpolator );
			}
		}

		if ( smoothingSize <= 0d || interpolator is null )
		{
			ResampleCache<T>.Set( source, sampleRate, smoothingSize, samples );
			return samples;
		}

		var smoothingPasses = smoothingSize.GetFrameCount( sampleRate );

		T[] back = samples, front = [..samples];

		for ( var pass = 0; pass < smoothingPasses; pass++ )
		{
			for ( var i = 0; i < sampleCount; i++ )
			{
				var prev = back[Math.Max( 0, i - 1 )];
				var curr = back[i];
				var next = back[Math.Min( sampleCount - 1, i + 1 )];

				var prevCurr = interpolator.Interpolate( prev, curr, 0.5f );
				var currNext = interpolator.Interpolate( curr, next, 0.5f );

				front[i] = interpolator.Interpolate( prevCurr, currNext, 0.5f );
			}

			(back, front) = (front, back);
		}

		ResampleCache<T>.Set( source, sampleRate, smoothingSize, back );
		return back;
	}
}
using System.Diagnostics.CodeAnalysis;
using Sandbox.MovieMaker;

namespace Editor.MovieMaker.BlockDisplays;

#nullable enable

partial class BlockItem
{
	public static BlockItem Create( TimelineTrack parent, ITrackBlock block, MovieTime offset )
	{
		var blockType = block.GetType();
		var propertyType = (block as IPropertyBlock)?.PropertyType;

		var inst = (BlockItem)Activator.CreateInstance( GetBlockItemType( blockType, propertyType ) )!;

		try
		{
			inst.Initialize( parent, block, offset );
		}
		catch ( Exception ex )
		{
			Log.Error( ex );

			BlockItemTypeCache[blockType] = typeof(DefaultBlockItem);

			inst = new DefaultBlockItem();
			inst.Initialize( parent, block, offset );
		}

		return inst;
	}

	[SkipHotload] private static Dictionary<Type, Type> BlockItemTypeCache { get; } = new();

	[EditorEvent.Hotload]
	private static void OnHotload()
	{
		BlockItemTypeCache.Clear();
	}

	private static Type GetBlockItemType( Type targetBlockType, Type? propertyType )
	{
		if ( BlockItemTypeCache.TryGetValue( targetBlockType, out var blockItemType ) ) return blockItemType;

		var bestBlockItemType = typeof(DefaultBlockItem);
		var bestScore = int.MaxValue;

		foreach ( var typeDesc in EditorTypeLibrary.GetTypes<BlockItem>() )
		{
			var type = typeDesc.TargetType;
			var baseDistance = 0;

			if ( type.IsAbstract ) continue;
			if ( type.IsGenericType )
			{
				if ( propertyType is null ) continue;

				if ( !TryMakeGenericType( type, propertyType, out var newType ) )
				{
					continue;
				}

				type = newType;
				baseDistance = 1;
			}

			var score = baseDistance + GetScore( type, targetBlockType, propertyType );

			if ( score > bestScore ) continue;

			bestBlockItemType = type;
			bestScore = score;
		}

		BlockItemTypeCache[targetBlockType] = bestBlockItemType;

		return bestBlockItemType;
	}

	private static bool TryMakeGenericType( Type trackPreviewType, Type propertyType,
		[NotNullWhen( true )] out Type? newTrackPreviewType )
	{
		newTrackPreviewType = null;

		if ( trackPreviewType.GetGenericArguments().Length != 1 )
		{
			return false;
		}

		try
		{
			newTrackPreviewType = trackPreviewType.MakeGenericType( propertyType );
			return true;
		}
		catch
		{
			return false;
		}
	}

	private static int GetScore( Type blockItemType, Type targetBlockType, Type? propertyType )
	{
		var score = int.MaxValue;

		foreach ( var iFace in blockItemType.GetInterfaces() )
		{
			if ( !iFace.IsConstructedGenericType ) continue;

			if ( iFace.GetGenericTypeDefinition() == typeof(IBlockItem<>) )
			{
				var iFaceTargetType = iFace.GetGenericArguments()[0];

				score = Math.Min( score, GetDistance( iFaceTargetType, targetBlockType ) );
			}

			if ( iFace.GetGenericTypeDefinition() == typeof(IPropertyBlockItem<>) && propertyType != null )
			{
				var iFaceTargetType = iFace.GetGenericArguments()[0];

				score = Math.Min( score, GetDistance( iFaceTargetType, propertyType ) );
			}
		}
		return score;
	}

	private static int GetDistance( Type baseType, Type? derivedType )
	{
		if ( !baseType.IsAssignableFrom( derivedType ) ) return int.MaxValue;
		if ( baseType.IsInterface && !derivedType.IsInterface ) return 1;

		var distance = 0;

		while ( baseType != derivedType && derivedType != null )
		{
			derivedType = derivedType.BaseType;
			distance++;
		}

		return distance;
	}
}
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Sandbox.MovieMaker;
using Sandbox.MovieMaker.Compiled;

namespace Editor.MovieMaker;

#nullable enable

public partial interface IProjectTrack : ITrack, IComparable<IProjectTrack>
{
	Guid Id { get; }

	MovieProject Project { get; }
	new IProjectTrack? Parent { get; }
	IReadOnlyList<IProjectTrack> Children { get; }

	bool IsEmpty { get; }
	int Order { get; }

	void Remove();
	IProjectTrack? GetChild( string name );

	ICompiledTrack Compile( ICompiledTrack? compiledParent, bool headerOnly );

	ITrack? ITrack.Parent => Parent;
	IEnumerable<MovieResource> References { get; }

	int IComparable<IProjectTrack>.CompareTo( IProjectTrack? other )
	{
		if ( ReferenceEquals( this, other ) )
		{
			return 0;
		}

		if ( other is null )
		{
			return 1;
		}

		var orderComparison = Order.CompareTo( other.Order );
		if ( orderComparison != 0 ) return orderComparison;

		return string.Compare( Name, other.Name, StringComparison.Ordinal );
	}
}

internal interface IProjectTrackInternal : IProjectTrack
{
	new IProjectTrackInternal? Parent { get; set; }

	void AddChild( IProjectTrackInternal child );
	void RemoveChild( IProjectTrackInternal child );
}

public abstract partial class ProjectTrack<T>( MovieProject project, Guid id, string name ) : IProjectTrackInternal
{
	private readonly List<IProjectTrack> _children = new();
	private bool _childrenChanged;

	public MovieProject Project { get; } = project;

	public Guid Id { get; } = id;
	public string Name { get; set; } = name;
	public Type TargetType { get; } = typeof(T);

	public IProjectTrack? Parent { get; private set; }
	public virtual IEnumerable<MovieResource> References => [];

	public virtual bool IsEmpty => Children.Count == 0;

	public virtual int Order => 0;

	public IReadOnlyList<IProjectTrack> Children
	{
		get
		{
			UpdateChildren();
			return _children;
		}
	}

	public void Remove() => Project.RemoveTrackInternal( this );

	public IProjectTrack? GetChild( string name ) => Children.FirstOrDefault( x => x.Name == name );

	public abstract ICompiledTrack Compile( ICompiledTrack? compiledParent, bool headerOnly );

	IProjectTrackInternal? IProjectTrackInternal.Parent
	{
		get => (IProjectTrackInternal?)Parent;
		set => Parent = value;
	}

	void IProjectTrackInternal.AddChild( IProjectTrackInternal child )
	{
		if ( child.Parent != null )
		{
			throw new ArgumentException( "Track already has a parent!", nameof(child) );
		}

		child.Parent = this;
		_children.Add( child );
		_childrenChanged = true;
	}

	void IProjectTrackInternal.RemoveChild( IProjectTrackInternal child )
	{
		_children.Remove( child );
		_childrenChanged = true;
	}

	private void UpdateChildren()
	{
		if ( !_childrenChanged ) return;

		_childrenChanged = false;
		_children.Sort();
	}
}

public partial interface IProjectReferenceTrack : IProjectTrack, IReferenceTrack
{
	public static IProjectReferenceTrack Create( MovieProject project, Guid id, string name, Type targetType )
	{
		var trackType = typeof(ProjectReferenceTrack<>).MakeGenericType( targetType );

		return (IProjectReferenceTrack)Activator.CreateInstance( trackType, project, id, name )!;
	}

	new ProjectReferenceTrack<GameObject>? Parent { get; }
	new Guid Id { get; }
	new Guid? ReferenceId { get; set; }

	IReferenceTrack<GameObject>? IReferenceTrack.Parent => Parent;
	IProjectTrack? IProjectTrack.Parent => Parent;

	Guid IReferenceTrack.Id => Id;
	Guid IProjectTrack.Id => Id;
	Guid? IReferenceTrack.ReferenceId => ReferenceId;
}

public partial class ProjectReferenceTrack<T>( MovieProject project, Guid id, string name )
	: ProjectTrack<T>( project, id, name ), IProjectReferenceTrack, IReferenceTrack<T>
	where T : class, IValid
{
	public override int Order => -1000;

	public new ProjectReferenceTrack<GameObject>? Parent => (ProjectReferenceTrack<GameObject>?)base.Parent;

	public Guid? ReferenceId { get; set; }

	public override ICompiledTrack Compile( ICompiledTrack? compiledParent, bool headerOnly ) =>
		new CompiledReferenceTrack<T>( Id, Name, (CompiledReferenceTrack<GameObject>)compiledParent!, ReferenceId );

	ITrack? ITrack.Parent => Parent;
}

public interface IProjectBlockTrack : IProjectTrack
{
	MovieTimeRange TimeRange { get; }
	IReadOnlyList<ITrackBlock> Blocks { get; }
}

public partial interface IProjectPropertyTrack : IPropertyTrack, IProjectBlockTrack
{
	public static IProjectPropertyTrack Create( MovieProject project, Guid id, string name, Type targetType )
	{
		var trackType = typeof(ProjectPropertyTrack<>).MakeGenericType( targetType );

		return (IProjectPropertyTrack)Activator.CreateInstance( trackType, project, id, name )!;
	}

	new IProjectTrack? Parent { get; }

	new IReadOnlyList<IProjectPropertyBlock> Blocks { get; }

	ITrack IPropertyTrack.Parent => Parent!;

	/// <summary>
	/// Add empty space from the start of <paramref name="timeRange"/>, with
	/// the duration of <paramref name="timeRange"/>. Will split any blocks that
	/// span the start time.
	/// </summary>
	bool Insert( MovieTimeRange timeRange );

	/// <summary>
	/// Remove the given <paramref name="timeRange"/>, then collapse the removed
	/// time range so any blocks after the end of the range will start earlier.
	/// </summary>
	bool Remove( MovieTimeRange timeRange );

	/// <summary>
	/// Remove any blocks within the <paramref name="timeRange"/>, splitting any
	/// blocks that span the start or end. This doesn't shift any blocks, so will
	/// leave an empty region of time.
	/// </summary>
	bool Clear( MovieTimeRange timeRange );

	/// <summary>
	/// Adds a block, replacing any blocks that overlap its time range.
	/// This will split any blocks that partially overlap.
	/// </summary>
	bool Add( MovieTimeRange timeRange, IPropertySignal signal );

	/// <summary>
	/// Adds a block, replacing any blocks that overlap its time range.
	/// This will split any blocks that partially overlap.
	/// </summary>
	bool Add( IProjectPropertyBlock block );

	bool AddRange( IEnumerable<IProjectPropertyBlock> blocks );

	void SetBlocks( IReadOnlyList<IProjectPropertyBlock> blocks );

	/// <summary>
	/// Copies blocks that overlap the given <paramref name="timeRange"/> and returns
	/// the copies.
	/// </summary>
	IReadOnlyList<IProjectPropertyBlock> Slice( MovieTimeRange timeRange );

	IReadOnlyList<IProjectPropertyBlock> CreateSourceBlocks( ProjectSourceClip source );
	
	IReadOnlyList<ITrackBlock> IProjectBlockTrack.Blocks => Blocks;
	IProjectTrack? IProjectTrack.Parent => Parent;
	ITrack? ITrack.Parent => Parent;
}

public sealed partial class ProjectPropertyTrack<T>( MovieProject project, Guid id, string name )
	: ProjectTrack<T>( project, id, name ), IProjectPropertyTrack, IPropertyTrack<T>
{
	private readonly List<PropertyBlock<T>> _blocks = new();
	private bool _blocksChanged;

	public MovieTimeRange TimeRange => (0d, Blocks.Select( x => x.TimeRange.End )
		.DefaultIfEmpty()
		.Max());

	public IReadOnlyList<PropertyBlock<T>> Blocks
	{
		get
		{
			UpdateBlocks();
			return _blocks;
		}
	}

	public override bool IsEmpty => base.IsEmpty && Blocks.Count == 0;

	IReadOnlyList<IProjectPropertyBlock> IProjectPropertyTrack.Blocks => Blocks;

	public override ICompiledTrack Compile( ICompiledTrack? compiledParent, bool headerOnly )
	{
		var compiled = new CompiledPropertyTrack<T>( Name, compiledParent!, [] );

		if ( headerOnly ) return compiled;

		return compiled with { Blocks = [..Blocks.Select( x => x.Compile( this ) )] };
	}

	public bool TryGetValue( MovieTime time, [MaybeNullWhen( false )] out T value )
	{
		if ( Blocks.GetBlock( time ) is { } block )
		{
			value = block.GetValue( time );
			return true;
		}

		value = default;
		return false;
	}

	bool IPropertyTrack.TryGetValue( MovieTime time, out object? value )
	{
		if ( TryGetValue( time, out var val ) )
		{
			value = val;
			return true;
		}

		value = null;
		return false;
	}

	public bool Insert( MovieTimeRange timeRange )
	{
		return Clear( timeRange.Start )
			| Shift( timeRange.Start, timeRange.Duration );
	}

	public bool Remove( MovieTimeRange timeRange )
	{
		return Clear( timeRange )
			| Shift( timeRange.End, -timeRange.Duration );
	}

	private bool Shift( MovieTime from, MovieTime offset )
	{
		var changed = false;

		for ( var i = 0; i < _blocks.Count; ++i )
		{
			var block = _blocks[i];

			if ( block.TimeRange.Start >= from )
			{
				_blocks[i] += offset;
				_blocksChanged = changed = true;
			}
		}

		return changed;
	}

	public bool Clear( MovieTimeRange timeRange )
	{
		var overlaps = this.GetBlocks( timeRange ).ToArray();

		if ( overlaps.Length == 0 ) return false;

		foreach ( var overlap in overlaps )
		{
			_blocks.Remove( overlap );

			if ( overlap.TimeRange.Start < timeRange.Start )
			{
				_blocks.Add( overlap.Slice( (overlap.TimeRange.Start, timeRange.Start) )! );
			}

			if ( overlap.TimeRange.End > timeRange.End )
			{
				_blocks.Add( overlap.Slice( (timeRange.End, overlap.TimeRange.End) )! );
			}
		}

		_blocksChanged = true;

		return true;
	}

	public bool Add( MovieTimeRange timeRange, PropertySignal<T> signal )
	{
		if ( timeRange.End < 0 ) return false;

		timeRange = timeRange.ClampStart( 0 );

		// Remove any overlaps

		Clear( timeRange );

		// Add to the end of _blocks, it'll get sorted later

		_blocksChanged = true;
		_blocks.AddRange( signal.AsBlocks( timeRange ) );

		return true;
	}

	public bool Add( PropertyBlock<T> block )
	{
		if ( block.TimeRange.Start < 0 )
		{
			throw new ArgumentException( "Block can't have negative start time." );
		}

		if ( _blocks.Any( x => x.TimeRange.Contains( block.TimeRange ) && x.Signal.Equals( block.Signal ) ) )
		{
			return false;
		}

		Clear( block.TimeRange );

		_blocksChanged = true;
		_blocks.Add( block );

		return true;
	}

	public bool AddRange( IEnumerable<PropertyBlock<T>> blocks )
	{
		var changed = false;

		foreach ( var block in blocks )
		{
			changed |= Add( block );
		}

		return changed;
	}

	bool IProjectPropertyTrack.Add( MovieTimeRange timeRange, IPropertySignal signal ) =>
		Add( timeRange, (PropertySignal<T>)signal );

	bool IProjectPropertyTrack.Add( IProjectPropertyBlock block ) => Add( (PropertyBlock<T>)block );

	bool IProjectPropertyTrack.AddRange( IEnumerable<IProjectPropertyBlock> blocks ) =>
		AddRange( blocks.Cast<PropertyBlock<T>>() );

	public void SetBlocks( IReadOnlyList<IProjectPropertyBlock> blocks )
	{
		_blocksChanged = true;
		_blocks.Clear();

		_blocks.AddRange( blocks.Cast<PropertyBlock<T>>() );
	}

	public IReadOnlyList<PropertyBlock<T>> Slice( MovieTimeRange timeRange )
	{
		return Blocks
			.Where( x => x.TimeRange.Intersect( timeRange ) is { } intersection && (!intersection.IsEmpty || timeRange.IsEmpty) )
			.Select( x => x.Slice( timeRange ) )
			.OfType<PropertyBlock<T>>()
			.ToImmutableArray();
	}

	IReadOnlyList<IProjectPropertyBlock> IProjectPropertyTrack.Slice( MovieTimeRange timeRange ) => Slice( timeRange );

	IReadOnlyList<IProjectPropertyBlock> IProjectPropertyTrack.CreateSourceBlocks( ProjectSourceClip source ) =>
		source.AsBlocks<T>( this );

	private void UpdateBlocks()
	{
		if ( !_blocksChanged ) return;

		_blocksChanged = false;

		// Sort by time

		_blocks.Sort( ( a, b ) => a.TimeRange.Start.CompareTo( b.TimeRange.Start ) );

		// Merge touching blocks that have identical values at their interface

		var comparer = EqualityComparer<T>.Default;

		for ( var i = _blocks.Count - 2; i >= 0; --i )
		{
			var prev = _blocks[i];
			var next = _blocks[i + 1];

			if ( prev.TimeRange.End != next.TimeRange.Start ) continue;

			var prevValue = prev.GetValue( prev.TimeRange.End );
			var nextValue = next.GetValue( next.TimeRange.Start );

			if ( !comparer.Equals( prevValue, nextValue ) )
			{
				continue;
			}

			var combinedTimeRange = prev.TimeRange.Union( next.TimeRange );
			var combinedSignal = prev.Signal.HardCut( next.Signal, prev.TimeRange.End ).Reduce( combinedTimeRange );

			_blocks[i] = new PropertyBlock<T>( combinedSignal, combinedTimeRange );
			_blocks.RemoveAt( i + 1 );
		}
	}
}
using System.Linq;
using System.Text.Json.Serialization;
using Sandbox.MovieMaker;

namespace Editor.MovieMaker;

#nullable enable

public abstract partial record PropertySignal : IPropertySignal
{
	[JsonIgnore]
	public abstract Type PropertyType { get; }

	protected PropertySignal( PropertySignal copy )
	{
		// Empty so any lazily computed fields aren't copied
	}

	object? IPropertySignal.GetValue( MovieTime time ) => OnGetValue( time );

	protected abstract object? OnGetValue( MovieTime time );

	/// <summary>
	/// Gets time ranges within the given <paramref name="timeRange"/> that have changing values.
	/// For painting in the timeline.
	/// </summary>
	public virtual IEnumerable<MovieTimeRange> GetPaintHints( MovieTimeRange timeRange ) => [timeRange];
}

/// <summary>
/// A <see cref="IPropertySignal{T}"/> that can be composed with <see cref="PropertyOperation{T}"/>s,
/// and stored in a <see cref="IPropertyBlock{T}"/>.
/// </summary>
public abstract partial record PropertySignal<T>() : PropertySignal, IPropertySignal<T>
{
	[JsonIgnore]
	public sealed override Type PropertyType => typeof(T);

	protected PropertySignal( PropertySignal<T> copy )
		: base( copy )
	{
		// Empty so any lazily computed fields aren't copied
	}

	public abstract T GetValue( MovieTime time );

	protected sealed override object? OnGetValue( MovieTime time ) => GetValue( time );

	/// <summary>
	/// Try to make a more minimal composition for this signal, optionally within a time range.
	/// </summary>
	/// <param name="start">Optional start time, we can discard any features before this if given.</param>
	/// <param name="end">Optional end time, we can discard any features after this if given.</param>
	public PropertySignal<T> Reduce( MovieTime? start = null, MovieTime? end = null )
	{
		return start >= end && !GetKeyframes( start.Value ).Any() ? GetValue( start.Value ) : OnReduce( start, end );
	}

	public PropertySignal<T> Reduce( MovieTimeRange timeRange ) =>
		Reduce( timeRange.Start, timeRange.End );

	public T[] Sample( MovieTimeRange timeRange, int sampleRate )
	{
		var sampleCount = timeRange.Duration.GetFrameCount( sampleRate );
		var samples = new T[sampleCount];

		for ( var i = 0; i < sampleCount; i++ )
		{
			var time = timeRange.Start + MovieTime.FromFrames( i, sampleRate );

			samples[i] = GetValue( time );
		}

		return samples;
	}

	protected abstract PropertySignal<T> OnReduce( MovieTime? start, MovieTime? end );

	public PropertySignal<T> Smooth( MovieTime size ) => size <= 0d ? this : OnSmooth( size );
	protected virtual PropertySignal<T> OnSmooth( MovieTime size ) => this;
}

/// <summary>
/// Extension methods for creating and composing <see cref="IPropertySignal"/>s.
/// </summary>
// ReSharper disable once UnusedMember.Global
public static partial class PropertySignalExtensions;
using Editor;
using Editor.TerrainEditor;

namespace Sandbox;

public class ProjectSettingsWindow : WidgetWindow
{
	public ProjectSettingsWindow( Widget parent ) : base( parent, "Project Settings" )
	{
	
		Parent = parent;

		DeleteOnClose = true;

		Size = new Vector2( 1580, 720 );

		WindowTitle = "Project Settings";
		Float();
		WindowFlags = WindowFlags.Dialog | WindowFlags.Customized | WindowFlags.CloseButton | WindowFlags.WindowSystemMenuHint | WindowFlags.WindowTitle | WindowFlags.MaximizeButton;

		SetWindowIcon( "electrical_services" );

		Layout = Layout.Column();

		var inspectorWidget = InspectorWidget.Create( Project.Current.GetSerialized() );

		Layout.Add( inspectorWidget );



	}
}
using Sandbox;
using System.Linq;
using static Sandbox.ResourceLibrary;
namespace Editor;

public class MotivationNotice : NoticeWidget
{
	public string Portrait { get; init; }
	public string Message { get; init; }

	public string Bubble => FileSystem.Mounted.GetFullPath( "portraits/bubble.png" );

	protected override Vector2 SizeHint() => new( 512, 384 );

	public MotivationNotice()
	{
		var personality = Game.Random.FromArray( GetAll<MotivationResource>().ToArray() );

		Portrait = personality.GetPortrait();
		Message = personality.GetMessage();
	}

	protected override void OnPaint()
	{
		Paint.SetPen( Theme.TextDark );
		Paint.SetDefaultFont( 16 );

		var rect = LocalRect.Align( 350, TextFlag.LeftBottom );
		Paint.Draw( rect, Portrait );

		rect = LocalRect.Align( 250, TextFlag.RightTop );
		Paint.Draw( rect, Bubble );

		rect = rect.Shrink( 0, 0, 0, 55 );
		Paint.DrawText( rect, Message );
	}
}
namespace AltCurves;

/// <summary>
/// User options for snapping values to a grid
/// </summary>
public enum ValueSnapOptions
{
	[Title( "0.1" )]
	Tenth,
	[Title( "0.5" )]
	Half,
	[Title( "1" )]
	One,
	[Title( "2" )]
	Two,
	[Title( "5" )]
	Five,
	[Title( "10" )]
	Ten,
	[Title( "50" )]
	Fifty,
	[Title( "100" )]
	Hundred,
	[Title( "Snap-To-Gridline" )]
	Gridlines,
	[Title( "User-provided amount" )]
	Custom,
};

using Editor;
using System;

namespace AltCurves.GraphicsItems;
public partial class EditableAltCurve : GraphicsItem
{
	/// <summary>
	/// The draggable, invisible handle that represents a keyframe on the curve
	/// The actual visible keyframe position can vary with snapping
	/// </summary>
	public class DragHandle : GraphicsItem
	{
		/// <summary>
		/// Underlying keyframe that we're positioned for, updated on dragging
		/// </summary>
		public AltCurve.Keyframe Keyframe
		{
			get => _keyframe;
			set
			{
				_keyframe = value;
				StartDragKeyframe = _keyframe;
				Position = _transform.CurveToWidgetPosition( new( _keyframe.Time, _keyframe.Value ) );
				Update();
			}
		}
		private AltCurve.Keyframe _keyframe;

		/// <summary>
		/// The keyframe when we started the last dragging operation
		/// </summary>
		public AltCurve.Keyframe StartDragKeyframe { get; private set; }

		public CurveWidgetTransform Transform
		{
			get => _transform;
			set
			{
				_transform = value;
				Position = _transform.CurveToWidgetPosition( new( Keyframe.Time, Keyframe.Value ) );
				Update();
			}
		}
		private CurveWidgetTransform _transform;

		/// <summary>
		/// Called when we're left-clicked for selection logic
		/// Params are index, isShift, isCtrl, isAlt
		/// </summary>
		public Action<int, bool, bool, bool> OnMouseDown { get; set; } = null;
		public Action<int, bool, bool, bool> OnMouseUp { get; set; } = null;

		/// <summary>
		/// Called during drag operations, after the Keyframe has been updated for our new position
		/// Parameter is the index and the pre-drag keyframe.
		/// </summary>
		public Action<int> OnDragging { get; set; } = null;

		/// <summary>
		/// Called when releasing the mouse, the drag has completed
		/// </summary>
		public Action<int> OnDragComplete { get; set; } = null;

		private readonly int _index;
		private Vector2 _mouseDownPos;

		public DragHandle( int index, CurveWidgetTransform transform, AltCurve.Keyframe keyframe, GraphicsItem parent ) : base( parent )
		{
			_index = index;
			_keyframe = keyframe;
			StartDragKeyframe = keyframe;
			HandlePosition = new( 0.5f );
			Cursor = CursorShape.Finger;
			Movable = true;
			Clip = true;
			HoverEvents = true;

			Size = new( 14.0f );
			_transform = transform;
			Position = _transform.CurveToWidgetPosition( new( Keyframe.Time, Keyframe.Value ) );
		}

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

			_keyframe = _keyframe with { Time = _transform.WidgetToCurveX( Position.x ), Value = _transform.WidgetToCurveY( Position.y ) };
			OnDragging?.Invoke( _index );
		}

		protected override void OnMousePressed( GraphicsMouseEvent e )
		{
			if ( e.LeftMouseButton )
			{
				StartDragKeyframe = Keyframe;
				_mouseDownPos = e.ScreenPosition;
				OnMouseDown?.Invoke( _index, e.HasShift, e.HasCtrl, e.HasAlt );
			}

			// Begin drag
			base.OnMousePressed( e );
		}

		protected override void OnMouseReleased( GraphicsMouseEvent e )
		{
			base.OnMouseReleased( e );

			// Either the user moved us from the starting location and we're completing a drag,
			// or they just clicked us normally and the mouse up action should trigger
			if ( e.LeftMouseButton )
			{
				if ( e.ScreenPosition != _mouseDownPos )
				{
					OnDragComplete?.Invoke( _index );
				}
				else
				{
					OnMouseUp?.Invoke( _index, e.HasShift, e.HasCtrl, e.HasAlt );
				}
			}
		}
	}
}
using Editor;

public static class MyEditorMenu
{
	[Menu( "Editor", "Crosshair Maker/My Menu Option" )]
	public static void OpenMyMenu()
	{
		EditorUtility.DisplayDialog( "It worked!", "This is being called from your library's editor code!" );
	}
}
using Sandbox;
using Editor;
using System;
using System.Linq;

namespace ChitChat.Editor;

public class DialogueSelector : Widget
{
	public Action<SerializedProperty> onDialogueActionSelected;
	public Action onSelectedDialogueRemoved;

	private ScrollArea _dialogueArea;
	private ListControlView _listView;

	private Menu _menu;

	private SerializedObject _serializedObject;
	private SerializedCollection _serializedDialogueDatasArray;
	private int _lastSelectedIndex = -1;

	public DialogueSelector(Widget parent, DialogueData data) : base(parent)
	{
		WindowTitle = "Dialogue Selector";
		Name = "Dialogue Selector";
		Size = new Vector2(ChitChatEditorWindow.WINDOW_SIZE_X * 0.5f, ChitChatEditorWindow.WINDOW_SIZE_Y * 0.5f);
		Layout = Layout.Column();
		SetWindowIcon("list_alt");

		_serializedObject = data.GetSerialized();

		Rebuild();
	}

	public void SetData(DialogueData data) => _serializedObject = data.GetSerialized();

	public void SelectLastest()
	{
		_listView.SelectItem(_lastSelectedIndex);
	}

	//TODO: Use this to see if something changed and update asset
	public override void ChildValuesChanged(Widget source)
	{
		base.ChildValuesChanged(source);
	}

	public void Rebuild()
	{
		//Needs to save last selected index to reload it at that index
		if(_listView.IsValid())
			_lastSelectedIndex = _listView.SelectedIndex;

		Layout.Clear(true);

		SerializedProperty prop = _serializedObject.GetProperty(nameof(DialogueData.DialogueDatas));

		//Scroll area
		_dialogueArea = new ScrollArea(this);
		_dialogueArea.Layout = Layout.Column();
		_dialogueArea.Canvas = new Widget(_dialogueArea);
		_dialogueArea.Canvas.Layout = Layout.Column();
		_dialogueArea.Canvas.Layout.Alignment = TextFlag.LeftTop;
		_dialogueArea.Canvas.Parent.Update();
		
		if (prop.TryGetAsObject(out var obj))
		{
			//Dialogue list
			_serializedDialogueDatasArray = (SerializedCollection)obj;
			_listView = new ListControlView(prop, _serializedDialogueDatasArray, OnCreateUIListItem, false);
			_listView.onItemSelected += OnItemSelected;
			_listView.onItemRemoved += OnItemMovedOrRemoved;
			_listView.onItemMoved += OnItemMovedOrRemoved;
			_listView.onItemRightClicked += CreateDialogueActionMenuWithIndex;
			_dialogueArea.Canvas.Layout.Add(_listView);
		}

		//Add dialogue action button
		Widget addButtonContainer = new Widget(_dialogueArea.Canvas);
		addButtonContainer.Layout = Layout.Row();
		addButtonContainer.Layout.Alignment = TextFlag.Left;
		addButtonContainer.Layout.AddSpacingCell(24);

		IconButton addDialogueActionButton = new IconButton("add", CreateDialogueActionMenu);
		addDialogueActionButton.Background = Theme.ControlBackground;
		addButtonContainer.Layout.Add(addDialogueActionButton);
		_dialogueArea.Canvas.Layout.Add(addButtonContainer);

		_dialogueArea.Canvas.Layout.AddStretchCell();

		Layout.Add(_dialogueArea);
	}

	private ControlWidget OnCreateUIListItem(SerializedProperty prop)
	{
		DialogueActionBase baseAction = prop.GetValue<DialogueActionBase>();

		if (baseAction is SpeakAction)
		{
			return new SpeakActionControlWidget(prop);
		}
		else if (baseAction is EventAction)
		{
			return new EventActionControlWidget(prop);

		}
		else if (baseAction is ChoiceAction)
		{
			return new ChoiceActionControlWidget(prop);
		}

		return ControlWidget.Create(prop);
	}

	private void CreateDialogueActionMenu()
	{
		if(_menu.IsValid())
			return;

		CreateDialogueActionMenuWithIndex(_serializedDialogueDatasArray.Count() - 1);
	}

	private void CreateDialogueActionMenuWithIndex(int index)
	{
		if (_menu.IsValid())
			return;

		_menu = new ContextMenu();
		_menu.AddHeading("Dialogue Actions");
		_menu.AddOption("Speak Action", "record_voice_over", () => _serializedDialogueDatasArray.Add(index + 1, new SpeakAction(TemplateWindow.s_SpeakActionTemplate)));
		_menu.AddSeparator();
		_menu.AddOption("Event Action", "event", () => _serializedDialogueDatasArray.Add(index + 1, new EventAction(TemplateWindow.s_EventActionTemplate)));
		_menu.AddSeparator();
		_menu.AddOption("Choice Action", "alt_route", () => _serializedDialogueDatasArray.Add(index + 1, new ChoiceAction(TemplateWindow.s_ChoiceActionTemplate)));

		_menu.OpenAtCursor(true);
		_menu.MinimumWidth = ScreenRect.Width;
	}

	private void OnItemMovedOrRemoved(int index)
	{
		if(index == _listView.SelectedIndex)
			onSelectedDialogueRemoved?.Invoke();
	}

	private void OnItemSelected(SerializedProperty prop)
	{
		onDialogueActionSelected?.Invoke(prop);
	}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Editor;
using Sandbox;

namespace Sandbox.AssetBrowserAddon;

/// <summary>
/// Dialog that captures the settings for a new custom Asset Browser location.
/// </summary>
public sealed class LocationEditor : Dialog
{
    private readonly Action<CustomLocationDefinition> _onConfirm;
    private readonly CustomLocationDefinition _initialDefinition;
    private readonly LineEdit _nameInput;
    private readonly LineEdit _iconInput;
    private readonly LineEdit _includeInput;
    private readonly LineEdit _excludeInput;
    private readonly AssetTypeSelector _assetTypeSelector;
    private readonly ToggleSwitch _projectOnlyToggle;

    public LocationEditor(Action<CustomLocationDefinition> onConfirm, CustomLocationDefinition initialDefinition = null)
    {
        _onConfirm = onConfirm;
        _initialDefinition = initialDefinition;

        Window.Title = initialDefinition is null ? "Add Bookmark" : "Edit Bookmark";
        Window.Size = new Vector2(500, 750);

        Layout = Layout.Column();
        Layout.Margin = 16f;
        Layout.Spacing = 12f;

        _nameInput = AddTextRow("Title", "My Bookmark");
        _iconInput = AddIconRow("Icon", "bookmark");

        Layout.Add( new Label( this ) { Text = "Asset Types" } );
        _assetTypeSelector = Layout.Add( new AssetTypeSelector( this, StyleInput ) );

        _projectOnlyToggle = Layout.Add( new ToggleSwitch( "Only include assets from this project", this ) );
        _projectOnlyToggle.MinimumHeight = Theme.RowHeight;
        _projectOnlyToggle.Value = true;

        _includeInput = AddTextArea("Include Folders", "Separate folders with ;");
        _excludeInput = AddTextArea("Exclude Folders", "Separate folders with ;");
        if ( _initialDefinition is not null )
        {
            _nameInput.Text = _initialDefinition.Name;
            _iconInput.Text = _initialDefinition.Icon;
            _assetTypeSelector.SetSelected( _initialDefinition.AssetTypes );
            _includeInput.Text = string.Join( ';', _initialDefinition.IncludeFolders ?? new List<string>() );
            _excludeInput.Text = string.Join( ';', _initialDefinition.ExcludeFolders ?? new List<string>() );
            _projectOnlyToggle.Value = _initialDefinition.ProjectAssetsOnly;

        }

        var buttonRow = Layout.AddRow();
        buttonRow.Spacing = 8f;
        buttonRow.AddStretchCell();
        buttonRow.Add( new Button( "Cancel", this ) { Clicked = Close } );
        var buttonLabel = _initialDefinition is null ? "Add Bookmark" : "Save Bookmark";
        var buttonIcon = _initialDefinition is null ? "add" : "save";
        buttonRow.Add( new Button.Primary( buttonLabel, buttonIcon, this ) { Clicked = Submit } );
    }

    private LineEdit AddTextRow(string label, string placeholder)
    {
        var row = Layout.AddRow();
        row.Spacing = 8f;
        row.Add( new Label( this ) { Text = label, FixedWidth = 130 } );

        var input = row.Add( new LineEdit( this ) );
        input.PlaceholderText = placeholder;
        StyleInput( input );
        return input;
    }

    private LineEdit AddIconRow(string label, string placeholder)
    {
        var row = Layout.AddRow();
        row.Spacing = 8f;
        row.Add( new Label( this ) { Text = label, FixedWidth = 130 } );

        var input = row.Add( new LineEdit( this ) );
        input.PlaceholderText = placeholder;
        StyleInput( input );

        var button = row.Add( new IconButton( "search", () => ShowIconPicker( input ), this ) );
        button.ToolTip = "Browse material icons";
        button.MinimumWidth = Theme.RowHeight;

        return input;
    }

    private LineEdit AddTextArea(string label, string placeholder)
    {
        var column = Layout.Add( Layout.Column() );
        column.Spacing = 4f;
        column.Add( new Label( this ) { Text = label } );

        var input = column.Add( new LineEdit( this ) );
        input.PlaceholderText = placeholder;
        StyleInput( input );
        return input;
    }

    private void Submit()
    {
        var name = _nameInput.Text?.Trim();
        if ( string.IsNullOrWhiteSpace( name ) )
        {
            EditorUtility.DisplayDialog( "Missing Title", "Please enter a title for the bookmark." );
            return;
        }

        var icon = string.IsNullOrWhiteSpace( _iconInput.Text ) ? "extension" : _iconInput.Text.Trim();

        var definition = _initialDefinition is null
            ? new CustomLocationDefinition()
            : new CustomLocationDefinition { Id = _initialDefinition.Id };

        definition.Name = name;
        definition.Icon = icon;
        definition.AssetTypes = _assetTypeSelector.SelectedTags.Select( NormalizeExtension ).Where( x => !string.IsNullOrWhiteSpace( x ) ).ToList();
        definition.IncludeFolders = SplitToList( _includeInput.Text );
        definition.ExcludeFolders = SplitToList( _excludeInput.Text );
        definition.ProjectAssetsOnly = _projectOnlyToggle.Value;

        _onConfirm?.Invoke( definition );
        Close();
    }

    private void ShowIconPicker( LineEdit target )
    {
        var pickerType = AppDomain.CurrentDomain.GetAssemblies()
            .Select( asm => asm.GetType( "Editor.IconPickerWidget", false ) )
            .FirstOrDefault( t => t is not null );

        var openPopup = pickerType?.GetMethod( "OpenPopup", BindingFlags.Public | BindingFlags.Static );
        if ( openPopup is null )
        {
            EditorUtility.DisplayDialog( "Icon Picker", "Unable to locate the icon picker widget." );
            return;
        }

        openPopup.Invoke( null, new object[]
        {
            this,
            target.Text ?? string.Empty,
            (Action<string>)(value => target.Text = value)
        } );
    }

    private static List<string> SplitToList(string raw)
    {
        if ( string.IsNullOrWhiteSpace( raw ) )
            return new List<string>();

        return raw
            .Split( new[] { ';', ',', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries )
            .Select( part => part.Trim() )
            .Where( part => part.Length > 0 )
            .ToList();
    }

    private static string NormalizeExtension( string value )
    {
        if ( string.IsNullOrWhiteSpace( value ) )
            return string.Empty;

        return value.Trim().TrimStart( '.' ).ToLowerInvariant();
    }

    private static void StyleInput( LineEdit input )
    {
        var background = Theme.ControlBackground.Darken( 0.25f ).Hex;
        var border = Theme.Border.Hex;
        input.SetStyles( $"background-color: {background}; border-color: {border};" );
    }
}
using Sandbox.MovieMaker;

namespace Editor.MovieMaker.BlockDisplays;

#nullable enable

public abstract partial class BlockItem : GraphicsItem
{
	private ITrackBlock? _block;

	public new TimelineTrack Parent { get; private set; } = null!;

	public ITrackBlock Block
	{
		get => _block ?? throw new InvalidOperationException();
		set
		{
			if ( ReferenceEquals( _block, value ) ) return;

			if ( _block is IDynamicBlock oldBlock )
			{
				oldBlock.Changed -= Block_Changed;
			}

			_block = value;

			if ( _block is IDynamicBlock newBlock )
			{
				newBlock.Changed += Block_Changed;
			}
		}
	}

	public MovieTime Offset { get; set; }

	protected IProjectTrack Track => Parent.View.Track;
	protected MovieTimeRange TimeRange => Block.TimeRange + Offset;

	protected int DataHash => HashCode.Combine( Block, TimeRange.Duration, Width );

	private void Initialize( TimelineTrack parent, ITrackBlock block, MovieTime offset )
	{
		base.Parent = Parent = parent;

		Block = block;
		Offset = offset;
	}

	private void Block_Changed() => Layout();

	protected override void OnDestroy()
	{
		// To remove Changed event

		Block = null!;
	}

	public void Layout()
	{
		var session = Parent.Session;

		PrepareGeometryChange();

		Position = new Vector2( session.TimeToPixels( TimeRange.Start ), 1f );
		Size = new Vector2( session.TimeToPixels( TimeRange.Duration ), Parent.Height - 2f );

		Update();
	}

	protected override void OnPaint()
	{
		Paint.SetBrushAndPen( Timeline.Colors.ChannelBackground.Lighten( Parent.View.IsLocked ? 0.2f : 0f ).WithAlpha( 0.75f ) );
		Paint.DrawRect( LocalRect );

		if ( Parent.View.IsLocked ) return;

		Paint.ClearBrush();
		Paint.SetPen( Color.White.WithAlpha( 0.1f ) );
		Paint.DrawLine( LocalRect.BottomLeft, LocalRect.TopLeft );
		Paint.DrawLine( LocalRect.BottomRight, LocalRect.TopRight );
	}
}

public interface IBlockItem<T>;

public abstract class BlockItem<T> : BlockItem, IBlockItem<T>
	where T : ITrackBlock
{
	public new T Block => (T)base.Block;
}

public interface IPropertyBlockItem<T>;

public abstract class PropertyBlockItem<T> : BlockItem<IPropertyBlock<T>>, IPropertyBlockItem<T>;
using Sandbox.MovieMaker;

namespace Editor.MovieMaker;

#nullable enable
public sealed partial class Session
{
	private bool _isPlaying;
	private bool _isLooping = true;
	private float _timeScale = 1f;
	private bool _syncPlayback = true;

	private MovieTime? _lastPlayerPosition;
	private bool _applyNextFrame;
	private MovieTime _lastAppliedTime;

	public bool IsOpenInEditor => Editor.Session == this;

	public bool IsPlaying
	{
		get => IsEditorScene ? _isPlaying : Player.IsPlaying;
		set
		{
			if ( IsEditorScene ) _isPlaying = value;
			else Player.IsPlaying = value;
		}
	}

	public bool IsLooping
	{
		get => IsEditorScene ? _isLooping : Player.IsLooping;
		set
		{
			if ( IsEditorScene ) _isLooping = Cookies.IsLooping = value;
			else Player.IsLooping = value;
		}
	}

	public bool SyncPlayback
	{
		get => _syncPlayback;
		set => _syncPlayback = Cookies.SyncPlayback = value;
	}

	public float TimeScale
	{
		get => IsEditorScene ? _timeScale : Player.TimeScale;
		set
		{
			if ( IsEditorScene ) _timeScale = Cookies.TimeScale = value;
			else Player.TimeScale = value;
		}
	}

	public void ApplyFrame( MovieTime time )
	{
		_applyNextFrame = false;

		if ( IsOpenInEditor && SyncPlayback )
		{
			foreach ( var player in Player.Scene.GetAllComponents<MoviePlayer>() )
			{
				if ( player == Player ) continue;

				player.Position = time;
			}
		}

		ApplyFrameCore( time );
	}

	private void ApplyFrameCore( MovieTime time )
	{
		Parent?.ApplyFrameCore( SequenceTransform * time );

		foreach ( var view in TrackList.AllTracks )
		{
			view.ApplyFrame( time );
		}

		AdvanceAnimations( time - _lastAppliedTime );

		_lastAppliedTime = time;
	}

	public void RefreshNextFrame()
	{
		_applyNextFrame = true;
	}

	private void PlaybackFrame()
	{
		if ( IsPlaying && IsEditorScene )
		{
			var targetTime = PlayheadTime + MovieTime.FromSeconds( RealTime.Delta * TimeScale );

			// Handle looping / reaching end

			if ( !IsRecording )
			{
				if ( LoopTimeRange is { } loopRange )
				{
					if ( targetTime <= loopRange.Start || targetTime >= loopRange.End )
					{
						targetTime = loopRange.Start;
					}
				}
				else if ( targetTime >= Project.Duration && Project.Duration.IsPositive )
				{
					if ( IsLooping )
					{
						targetTime = MovieTime.Zero;
					}
					else
					{
						targetTime = Project.Duration;

						IsPlaying = false;
					}
				}
			}

			PlayheadTime = targetTime;
		}
		else if ( _lastPlayerPosition is { } lastPlayerPosition && lastPlayerPosition != Player.Position )
		{
			// We're setting the backing field here because we don't want to call ApplyFrame / set the Player position,
			// since we're reacting to the Player advancing time.

			_playheadTime = lastPlayerPosition;
			PlayheadChanged?.Invoke( PlayheadTime );
		}

		_lastPlayerPosition = Player.Position;
	}
}
using Sandbox.MovieMaker;
using System.Linq;
using System.Reflection;

namespace Editor.MovieMaker;

#nullable enable

/// <summary>
/// Panel containing the track list.
/// </summary>
public sealed class ListPanel : MovieEditorPanel
{
	public TrackListWidget TrackList { get; }

	public ListPanel( MovieEditor parent, Session session )
		: base( parent )
	{
		TrackList = new TrackListWidget( this, session );
		Layout.Add( TrackList );

		MinimumWidth = 300;

		// File menu

		var fileGroup = ToolBar.AddGroup( true );

		var resourceIcon = typeof( MovieResource ).GetCustomAttribute<GameResourceAttribute>()!.Icon;

		var fileDisplay = new ToolBarItemDisplay( "File", "folder", "Actions for saving / loading / importing movies, or switching player components." );
		var fileAction = fileGroup.AddAction( fileDisplay, () =>
		{
			var menu = new Menu();

			menu.AddHeading( "File" );

			menu.AddOption( "New Movie", "note_add", Editor.SwitchToNewEmbedded );

			menu.AddSeparator();

			var openMenu = menu.AddMenu( "Open Movie", "file_open" );

			var movies = ResourceLibrary.GetAll<MovieResource>().ToArray();

			openMenu.AddOptions( movies, x => $"{x.ResourcePath}:{resourceIcon}", Editor.SwitchResource );

			session.CreateImportMenu( menu );

			menu.AddSeparator();

			menu.AddOption( $"Save Movie", "save", parent.OnSave, shortcut: "CTRL+S" );

			var saveAsMenu = menu.AddMenu( $"Save Movie As..", "save_as" );

			var embed = saveAsMenu.AddOption( "Embedded", "attach_file", parent.SwitchToEmbedded );

			embed.Checkable = true;
			embed.Checked = session.Resource is EmbeddedMovieResource;
			embed.ToolTip = "Store the movie inside this Movie Player component, embedded in the current scene or prefab.";

			saveAsMenu.AddOption( "New Movie Resource", resourceIcon, parent.SaveFileAs );

			menu.OpenAt( fileGroup.ScreenRect.BottomLeft );
		} );

		fileAction.ToolTip = "File menu for opening, importing, or saving movie projects.";

		// MoviePlayer selection

		var playerGroup = ToolBar.AddGroup( true );
		var playerCombo = playerGroup.Layout.Add( new PlayerComboBox( session ) );

		playerCombo.HorizontalSizeMode = SizeMode.CanGrow | SizeMode.Expand;

		playerCombo.Bind( "Value" ).From(
			() => session.Player,
			value => session.Editor.Switch( value ) );
	}

	protected override void OnPaint()
	{
		Paint.SetBrushAndPen( Theme.TabBackground );
		Paint.DrawRect( LocalRect );
	}
}

file class PlayerComboBox : IconComboBox<MoviePlayer?>
{
	private readonly Session _session;

	public PlayerComboBox( Session session )
	{
		_session = session;

		IconAspect = null;
	}

	protected override IEnumerable<MoviePlayer?> OnGetOptions() =>
		_session.Player.Scene.IsValid ? _session.Player.Scene.GetAllComponents<MoviePlayer>() : [];

	protected override string OnGetOptionTitle( MoviePlayer? option ) => option?.GameObject.Name ?? "None";

	protected override void OnPaintOptionIcon( MoviePlayer? option, Rect rect )
	{
		Paint.DrawText( rect, OnGetOptionTitle( option ) );
	}

	protected override void OnCreateMenu( Menu menu )
	{
		base.OnCreateMenu( menu );

		menu.AddSeparator();

		menu.AddOption( "Create New Movie Player", "live_tv", _session.Editor.CreateNewPlayer );
	}
}
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace EditorAllChat;

public class MessagesRequest
{
	[JsonPropertyName( "success" )]
	public bool Success { get; set; }

	[JsonPropertyName( "messages" )]
	public List<MessageObject> Messages { get; set; }

	[JsonPropertyName( "count" )]
	public int Count { get; set; }

	[JsonPropertyName( "total_stored" )]
	public int TotalStored { get; set; }
}
using System.Text.Json.Serialization;
namespace EditorAllChat;

public class MessageResponse
{
	[JsonPropertyName( "success" )]
	public bool Success { get; set; }

	[JsonPropertyName( "message" )]
	public string Message { get; set; }

	[JsonPropertyName( "data" )]
	public MessageObject Data { get; set; }
}
using System;
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;

namespace MANIFOLD.BHLib.Editor {
    public class TimelineWidget : GraphicsView {
        public class EventMarker : GraphicsItem {
            public float time;
            public List<AttackEvent> events;
            public bool selected;
            public int selectedIndex;
            public Action<EventMarker> onSelected;
            
            private TimelineWidget timeline;

            public EventMarker(TimelineWidget widget) {
                timeline = widget;
                events = new List<AttackEvent>();
                ZIndex = 0;
                HoverEvents = true;
            }

            protected override void OnMousePressed(GraphicsMouseEvent e) {
                if (e.LeftMouseButton) {
                    if (!selected) selectedIndex = 0;
                    else {
                        selectedIndex++;
                        if (selectedIndex >= events.Count) selectedIndex = 0;
                    }
                    selected = true;
                    Update();
                    onSelected?.Invoke(this);
                    e.Accepted = true;
                }
            }

            protected override void OnPaint() {
                base.OnPaint();
                
                Paint.Antialiasing = false;

                Color unselectedColor = Color.White.Darken(0.05f);
                Color color;
                if (selected) color = Color.Orange;
                else if (Paint.HasMouseOver) color = Gizmo.Colors.Hovered;
                else color = unselectedColor;
                Paint.SetPen(color, 2);
                
                var rect = LocalRect.Shrink(2);
                Paint.DrawLine(new Vector2(rect.Left, rect.Bottom), new Vector2(rect.Left, rect.Top));
                // Paint.DrawText(rect.Shrink(4, 0, 0, 6), string.Join("\n", events.Select(x => x.Name)), TextFlag.LeftBottom);

                rect = rect.Shrink(4, 0, 0, 6);
                for (int i = 0; i < events.Count; i++) {
                    Color textColor;
                    if (selected) {
                        textColor = i == selectedIndex ? color : unselectedColor;
                    } else {
                        textColor = color;
                    }
                    
                    Paint.SetPen(textColor);
                    Paint.DrawText(rect, events[i].Name, TextFlag.LeftBottom);
                    rect = rect.Shrink(0, 0, 0, 9);
                }
            }
        }
        
        public class TimeAxis : GraphicsItem {
            private TimelineWidget timeline;

            public TimeAxis(TimelineWidget widget) {
                timeline = widget;
                ZIndex = 10;
                HoverEvents = true;
            }

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

                Paint.Antialiasing = false;
                Paint.ClearPen();
                Paint.SetBrush(Theme.ControlBackground);
                Paint.DrawRect(LocalRect);
                
                Paint.SetDefaultFont(7);

                var rect = LocalRect.Shrink(1);
                var zoom = timeline.ZoomFactor;
                var spacing = 100 * zoom;
                var lines = rect.Width / spacing;
                var w = spacing;
                var subdivisions = (int)(3 * zoom);
                var subLineSpacing = w / subdivisions;

                for (int i = 0; i < lines; i++) {
                    float xPos = rect.Left + w * i;
                    
                    Paint.SetPen(Theme.Text.WithAlpha(0.5f));
                    Paint.DrawLine(new Vector2(xPos, rect.Bottom), new Vector2(xPos, rect.Bottom - 8));
                    Paint.DrawText(new Vector2(xPos, rect.Top), $"{i}");
                    Paint.SetPen(Theme.Text.WithAlpha(0.2f));

                    for (int j = 0; j < subdivisions; j++) {
                        var sublineX = w * i + subLineSpacing * j;
                        Paint.DrawLine(new Vector2(rect.Left + sublineX, rect.Bottom), new Vector2(rect.Left + sublineX, rect.Bottom - 4));
                    }
                }
            }

            protected override void OnMousePressed(GraphicsMouseEvent e) {
                base.OnMousePressed(e);

                if (e.LeftMouseButton) {
                    timeline.ScrubTo(timeline.TimeFromPosition(e.LocalPosition.x));
                }
            }
        }

        public class Scrubber : GraphicsItem {
            private TimelineWidget timeline;

            public Scrubber(TimelineWidget widget) {
                timeline = widget;
                ZIndex = 20;
                HoverEvents = true;
                Cursor = CursorShape.SizeH;
                Movable = true;
                Selectable = true;
            }

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

                Paint.Antialiasing = false;
                Paint.ClearPen();
                Paint.SetBrush(Theme.Green.WithAlpha(0.7f));
                Paint.DrawRect(new Rect(0, new Vector2(LocalRect.Width, Theme.RowHeight + 1)));
                Paint.SetPen(Theme.Green.WithAlpha(0.7f));
                Paint.DrawLine(new Vector2(4, Theme.RowHeight + 1), new Vector2(4, LocalRect.Bottom));
            }

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

                timeline.ScrubTo(timeline.TimeFromPosition(Position.x));
                
                Position = Position.WithY(0);
                Position = Position.WithX(MathF.Max(-4, Position.x));
            }
        }
        
        private ITimeline timeline;
        private float time;
        private float zoomFactor;
        private bool showHidden;
        
        private TimeAxis timeAxis;
        private Scrubber scrubber;
        private List<EventMarker> markers;
        private Dictionary<AttackEvent, EventMarker> eventToMarker;
        private EventMarker selectedMarker;

        public ITimeline Timeline {
            get => timeline;
            set {
                timeline = value;
                RebuildMarkers();
                DoLayout();
            }
        }
        
        public float Range { get; set; }

        public float ZoomFactor {
            get => zoomFactor;
            set {
                zoomFactor = value;
                DoLayout();
                timeAxis.Update();
                scrubber.Update();
            }
        }

        public float Time {
            get => time;
            set {
                time = value;
                DoLayout();
            }
        }

        public bool ShowHidden {
            get => showHidden;
            set {
                showHidden = value;
                RebuildMarkers();
            }
        }
        
        public AttackEvent SelectedEvent => selectedMarker?.events[selectedMarker.selectedIndex];
        
        public Action<AttackEvent> OnEventSelected { get; set; }
        public Action OnTimeScrubbed { get; set; }
        
        public TimelineWidget(Widget parent = null) : base(parent) {
            Antialiasing = false;
            BilinearFiltering = false;
            
            SceneRect = new Rect(0, Size);
            HorizontalScrollbar = ScrollbarMode.Auto;
            VerticalScrollbar = ScrollbarMode.Off;
            MouseTracking = true;
            
            Scale = 1;
            zoomFactor = 1;
            
            timeAxis = new TimeAxis(this);
            Add(timeAxis);
            scrubber = new Scrubber(this);
            Add(scrubber);

            markers = new List<EventMarker>();
            eventToMarker = new Dictionary<AttackEvent, EventMarker>();
        }

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

            var size = Size;
            size.x = MathF.Max(size.x, PositionFromTime(Range + 3));
            SceneRect = new Rect(0, size);
            timeAxis.Size = new Vector2(size.x, Theme.RowHeight);
            scrubber.Size = new Vector2(9, size.y);

            var rect = SceneRect;
            rect.Top = timeAxis.SceneRect.Bottom;

            scrubber.Position = scrubber.Position.WithX(PositionFromTime(Time) - 3).SnapToGrid(1f);
            
            foreach (var marker in markers) {
                var markerRect = rect;
                markerRect.Left = PositionFromTime(marker.time);
                markerRect.Width = 80;
                marker.SceneRect = markerRect;
            }
        }
        
        protected override void OnWheel(WheelEvent e) {
            e.Accept();
        }
        
        public void RebuildMarkers() {
            var previousSelectedEvent = SelectedEvent;
            
            foreach (var marker in markers) {
                marker.Destroy();
            }
            markers.Clear();
            eventToMarker.Clear();

            if (timeline != null) {
                foreach (var evt in Timeline.Events) {
                    if (!ShowHidden && evt.Hidden) continue;
                    AddEvent(evt);
                }
            }
            DoLayout();

            if (previousSelectedEvent != null) {
                if (eventToMarker.TryGetValue(previousSelectedEvent, out EventMarker marker)) {
                    marker.selected = true;
                    marker.selectedIndex = marker.events.IndexOf(previousSelectedEvent);
                    try {
                        marker.Update();
                    } catch {
                        // ignored
                    }
                }
            }
        }

        public void ScrubTo(float time) {
            Time = time;
            OnTimeScrubbed?.Invoke();
        }
        
        public float PositionFromTime(float time) {
            return 100 * ZoomFactor * time;
        }

        public float TimeFromPosition(float position) {
            return (ZoomFactor / 100) * position;
        }

        private void AddEvent(AttackEvent evt) {
            var existing = markers.FirstOrDefault(x => x.time.AlmostEqual(evt.Time));
            if (existing != null) {
                existing.events.Add(evt);
                eventToMarker.Add(evt, existing);
            } else {
                EventMarker marker = new EventMarker(this);
                marker.time = evt.Time;
                marker.events.Add(evt);
                marker.onSelected = OnMarkerSelected;
                Add(marker);
                
                markers.Add(marker);
                eventToMarker.Add(evt, marker);
            }
        }

        private void OnMarkerSelected(EventMarker marker) {
            if (marker != selectedMarker && selectedMarker != null) {
                selectedMarker.selected = false;
                try {
                    selectedMarker.Update();
                } catch {
                    // ignored
                }
            }
            selectedMarker = marker;
            OnEventSelected?.Invoke(SelectedEvent);
        }
    }
}
namespace Braxnet;

public class CompletionsCapability
{
}
using System.Text.Json.Serialization;

namespace Braxnet;

public class PromptsCapability
{
	[JsonPropertyName( "listChanged" )] public bool ListChanged { get; set; }
}
using System.Text.Json.Serialization;

namespace Braxnet;

public class RootsCapability
{
	[JsonPropertyName( "listChanged" )] public bool ListChanged { get; set; }
}
using System.Threading.Tasks;
using System.Text.Json;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;
using Sandbox;
using Editor;
using FileSystem = Editor.FileSystem;

namespace Braxnet.Commands;

[MCPCommand( "resources/list" )]
public class ListResourcesCommand : IMCPCommand
{
	public string Name => "resources/list";

	public async Task<object> ExecuteAsync( JsonRpcRequest request, string sessionId, string protocolVersion )
	{
		var resources = new List<Resource>();

		try
		{
			var projectDir = Path.Combine( FileSystem.Mounted.GetFullPath( "." ), ".." );
			projectDir = Path.GetFullPath( projectDir );

			var files = Directory.GetFiles( projectDir, "*.*", SearchOption.AllDirectories )
				.Where( f => !f.Contains( ".git" ) && !f.Contains( ".vscode" ) && !f.Contains( ".idea" ) )
				.Take( 100 );

			foreach ( var file in files )
			{
				var relativePath = Path.GetRelativePath( projectDir, file );
				var fileInfo = new FileInfo( file );

				resources.Add( new Resource
				{
					Uri = $"file://{file.Replace( '\\', '/' )}",
					Name = relativePath.Replace( '\\', '/' ),
					Title = Path.GetFileName( file ),
					Description = $"Project file: {relativePath}",
					MimeType = MCPServer.GetMimeType( file ),
					Size = fileInfo.Length
				} );
			}
		}
		catch ( Exception ex )
		{
			Log.Error( $"Error listing resources: {ex.Message}" );
		}

		return new ListResourcesResult { Resources = resources };
	}
}

[MCPCommand( "resources/read" )]
public class ReadResourceCommand : IMCPCommand
{
	public string Name => "resources/read";

	public async Task<object> ExecuteAsync( JsonRpcRequest request, string sessionId, string protocolVersion )
	{
		var contents = new List<ResourceContents>();

		if ( request.Params.HasValue )
		{
			var paramsObj = JsonSerializer.Deserialize<Dictionary<string, object>>(
				request.Params.Value.GetRawText(), MCPServer.JsonOptions );

			if ( paramsObj?.ContainsKey( "uri" ) == true )
			{
				var uriStr = paramsObj["uri"]?.ToString();
				if ( !string.IsNullOrEmpty( uriStr ) && uriStr.StartsWith( "file://" ) )
				{
					var filePath = uriStr.Substring( 7 );
					try
					{
						if ( File.Exists( filePath ) )
						{
							var text = await File.ReadAllTextAsync( filePath );
							contents.Add( new ResourceContents
							{
								Uri = uriStr, Text = text, MimeType = MCPServer.GetMimeType( filePath )
							} );
						}
					}
					catch ( Exception ex )
					{
						Log.Error( $"Error reading file {filePath}: {ex.Message}" );
						throw new Exception( $"Could not read file: {ex.Message}" );
					}
				}
			}
		}

		return new ReadResourceResult { Contents = contents };
	}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Sandbox;

namespace Braxnet.Commands.Tools;

[MCPTool( "get_children", "Get Children Tool",
	"Get the children of a GameObject in the current scene" )]
public class GetChildrenTool : IMCPTool
{
	public string Name => "get_children";
	public string Title => "Get Children Tool";
	public string Description => "Get the children of a GameObject in the current scene";

	public JsonElement InputSchema => JsonSerializer.SerializeToElement( new
	{
		type = "object",
		properties =
			new { id = new { type = "string", description = "The ID of the GameObject to get children for" } },
		required = new[] { "id" }
	} );

	public JsonElement OutputSchema => default;

	public async Task<CallToolResult> ExecuteAsync( Dictionary<string, object> arguments, string sessionId )
	{
		var result = new CallToolResult();

		if ( !arguments.TryGetValue( "id", out var idObj ) || idObj is not string id )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = "Invalid or missing 'id' argument." } );
			return result;
		}

		if ( string.IsNullOrEmpty( id ) )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = "GameObject ID cannot be empty." } );
			return result;
		}

		if ( !Guid.TryParse( id, out var guid ) )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = "Invalid GameObject ID format." } );
			return result;
		}

		var gameObject = Game.ActiveScene.Directory.FindByGuid( guid );
		if ( gameObject == null )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = $"GameObject with ID '{id}' not found." } );
			return result;
		}

		await GameTask.MainThread(); // Ensure this runs on the main thread

		var childrenList = gameObject.Children.Select( child => new
		{
			Id = child.Id,
			Name = child.Name,
			LocalPosition = child.LocalPosition,
			LocalRotation = child.LocalRotation,
			LocalScale = child.LocalScale,
			Components = child.Components.Count,
			ChildrenCount = child.Children.Count,
			Enabled = child.Enabled,
			Tags = child.Tags.ToList(),
		} ).ToList();

		result.StructuredContent = new { Children = childrenList };

		return result;
	}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Editor;
using Sandbox;

namespace Braxnet.Commands.Tools;

[MCPTool( "get_game_objects", "List Game Objects",
	"List all GameObjects in the current scene" )]
public class ListGameObjectsTool : IMCPTool
{
	public string Name => "get_game_objects";
	public string Title => "List Game Objects";
	public string Description => "List all GameObjects in the current scene";

	public JsonElement InputSchema => JsonSerializer.SerializeToElement( new
	{
		type = "object", properties = new { }, required = Array.Empty<string>()
	} );

	public JsonElement OutputSchema => JsonSerializer.SerializeToElement( new
	{
		type = "object",
		properties = new
		{
			SceneFile = new { type = "string", description = "Path to the scene file" },
			GameObjects = new
			{
				type = "array",
				items = new
				{
					type = "object",
					properties = new
					{
						Id = new { type = "string", description = "GameObject ID" },
						Name = new { type = "string", description = "GameObject name" },
						Position =
							new
							{
								type = "object",
								properties =
									new
									{
										x = new { type = "number" },
										y = new { type = "number" },
										z = new { type = "number" }
									}
							},
						Rotation =
							new
							{
								type = "object",
								properties =
									new
									{
										pitch = new { type = "number" },
										yaw = new { type = "number" },
										roll = new { type = "number" }
									}
							},
						Components = new { type = "integer", description = "Number of components" },
						ChildrenCount =
							new { type = "integer", description = "Number of child GameObjects" },
						Enabled =
							new { type = "boolean", description = "Is the GameObject enabled?" },
						Tags = new
						{
							type = "array",
							items = new { type = "string" },
							description = "List of tags assigned to the GameObject"
						}
					},
					required = new[]
					{
						"Id", "Name", "Position", "Rotation", "Components", "ChildrenCount", "Enabled", "Tags"
					}
				}
			}
		},
		required = new[] { "SceneFile", "GameObjects" }
	} );

	public async Task<CallToolResult> ExecuteAsync( Dictionary<string, object> arguments, string sessionId )
	{
		var result = new CallToolResult();

		try
		{
			var rootGameObjects = SceneEditorSession.Active.Scene.Children;
			if ( rootGameObjects == null || rootGameObjects.Count == 0 )
			{
				result.Content.Add( new TextContent { Text = "No GameObjects found in the scene." } );
				return result;
			}

			result.Content.Add( new TextContent
			{
				Text = $"Found {rootGameObjects.Count} root GameObjects in the scene."
			} );

			var gameObjectsList = new List<GameObjectInfo>();
			foreach ( var gameObject in rootGameObjects )
			{
				gameObjectsList.Add( new GameObjectInfo
				{
					Id = gameObject.Id,
					Name = gameObject.Name,
					Position = gameObject.WorldPosition,
					Rotation = gameObject.WorldRotation,
					Components = gameObject.Components.Count,
					ChildrenCount = gameObject.Children.Count,
					Enabled = gameObject.Enabled,
					Tags = gameObject.Tags.ToList(),
				} );
			}

			result.StructuredContent = new
			{
				SceneFile = SceneEditorSession.Active.Scene.Source.ResourcePath, GameObjects = gameObjectsList
			};
		}
		catch ( Exception ex )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = $"Error listing GameObjects: {ex.Message}" } );
		}

		return result;
	}

	public class GameObjectInfo
	{
		public Guid Id { get; set; }
		public string Name { get; set; }
		public Vector3 Position { get; set; }
		public Angles Rotation { get; set; }
		public int Components { get; set; }
		public int ChildrenCount { get; set; }
		public bool Enabled { get; set; }
		public List<string> Tags { get; set; }
	}
}
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Editor;
using Sandbox;

namespace Braxnet.Commands.Tools;

[MCPTool( "duplicate_game_object", "Duplicate Game Object",
	"Duplicate a GameObject in the current scene" )]
public class DuplicateGameObjectTool : IMCPTool
{
	public string Name => "duplicate_game_object";
	public string Title => "Duplicate Game Object";
	public string Description => "Duplicate a GameObject in the current scene";

	public JsonElement InputSchema => JsonSerializer.SerializeToElement( new
	{
		type = "object",
		properties = new
		{
			id = new { type = "string", description = "The ID of the GameObject to duplicate" },
			position =
				new
				{
					type = "object",
					properties =
						new
						{
							x = new { type = "number", description = "X position" },
							y = new { type = "number", description = "Y position" },
							z = new { type = "number", description = "Z position" }
						},
					description = "Position to place the duplicated GameObject"
				},
			rotation = new
			{
				type = "object",
				properties =
					new
					{
						pitch = new { type = "number", description = "Pitch rotation" },
						yaw = new { type = "number", description = "Yaw rotation" },
						roll = new { type = "number", description = "Roll rotation" }
					},
				description = "Rotation of the duplicated GameObject"
			},
			parentId = new
			{
				type = "string",
				description = "Optional parent GameObject ID to set the duplicated GameObject's parent"
			}
		},
		required = new[] { "id" }
	} );

	public JsonElement OutputSchema => default;

	public async Task<CallToolResult> ExecuteAsync( Dictionary<string, object> arguments, string sessionId )
	{
		var result = new CallToolResult();

		if ( !arguments.TryGetValue( "id", out var idObj ) || idObj is not string id )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = "Invalid or missing 'id' argument." } );
			return result;
		}

		if ( string.IsNullOrEmpty( id ) )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = "GameObject ID cannot be empty." } );
			return result;
		}

		if ( !Guid.TryParse( id, out var guid ) )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = "Invalid GameObject ID format." } );
			return result;
		}

		var gameObject = SceneEditorSession.Active.Scene.Directory.FindByGuid( guid );
		if ( gameObject == null )
		{
			result.IsError = true;
			result.Content.Add( new TextContent { Text = $"GameObject with ID '{id}' not found." } );
			return result;
		}

		await GameTask.MainThread(); // Ensure this runs on the main thread

		GameObject duplicate = null;

		using ( SceneEditorSession.Active.UndoScope( "Duplicate GameObject" )
			       .WithGameObjectCreations().Push() )
		{
			var position = arguments.GetValueOrDefault( "position" ) as JsonElement?;
			var rotation = arguments.GetValueOrDefault( "rotation" ) as JsonElement?;
			var parentId = arguments.GetValueOrDefault( "parentId" ) as string;

			duplicate = gameObject.Clone();

			if ( position.HasValue && position.Value.ValueKind == JsonValueKind.Object )
			{
				var posX = position.Value.GetProperty( "x" ).GetSingle();
				var posY = position.Value.GetProperty( "y" ).GetSingle();
				var posZ = position.Value.GetProperty( "z" ).GetSingle();
				duplicate.WorldPosition = new Vector3( posX, posY, posZ );
			}

			if ( rotation.HasValue && rotation.Value.ValueKind == JsonValueKind.Object )
			{
				var pitch = rotation.Value.GetProperty( "pitch" ).GetSingle();
				var yaw = rotation.Value.GetProperty( "yaw" ).GetSingle();
				var roll = rotation.Value.GetProperty( "roll" ).GetSingle();
				duplicate.WorldRotation = new Angles( pitch, yaw, roll );
			}

			if ( !string.IsNullOrEmpty( parentId ) && Guid.TryParse( parentId, out var parentGuid ) )
			{
				var parentObject = SceneEditorSession.Active.Scene.Directory.FindByGuid( parentGuid );
				if ( parentObject != null )
				{
					duplicate.SetParent( parentObject );
				}
				else
				{
					result.IsError = true;
					result.Content.Add(
						new TextContent { Text = $"Parent GameObject with ID '{parentId}' not found." } );
					return result;
				}
			}
		}

		result.Content.Add( new TextContent { Text = $"Successfully duplicated GameObject: {gameObject.Name}" } );
		result.StructuredContent = new
		{
			duplicatedGameObjectId = duplicate.Id,
			duplicatedGameObjectName = duplicate.Name,
			duplicatedGameObjectPosition =
				new[] { duplicate.WorldPosition.x, duplicate.WorldPosition.y, duplicate.WorldPosition.z },
			duplicatedGameObjectRotation = new[]
			{
				duplicate.WorldRotation.Pitch(), duplicate.WorldRotation.Yaw(), duplicate.WorldRotation.Roll()
			}
		};

		return result;
	}
}
using System;
using System.Numerics;
using Editor;
using IconRenderer;
using Sandbox;

internal class IconRendering : SceneRenderingWidget
{
	public readonly CameraComponent camera;
	public readonly SkinnedModelRenderer model;
	public readonly DirectionalLight light;
	public readonly SpriteRenderer mockBackground;

	private float hoverTime;

	public IconRendering( string modelName, string background ) : base( null )
	{
		Size = 512;

		Scene = Scene.CreateEditorScene();

		using ( Scene.Push() )
		{
			{
				camera = new GameObject( true, "camera" ).GetOrAddComponent<CameraComponent>( false );
				camera.BackgroundColor = Color.Black;
				camera.Enabled = true;
			}
			{
				light = new GameObject( true, "light" ).GetOrAddComponent<DirectionalLight>( false );
				light.LightColor = Color.White;
				light.Enabled = true;
			}
			{
				model = new GameObject( true, "model" ).GetOrAddComponent<SkinnedModelRenderer>( false );
				model.Model = Model.Load( modelName ?? "models/error.vmdl" );
				model.Enabled = true;
			}
			{
				mockBackground = new GameObject( true, "mockBackground" ).GetOrAddComponent<SpriteRenderer>( false );
				mockBackground.Texture = Texture.Load( background );
				mockBackground.Size = new Vector2( 512, 512 );
				mockBackground.LocalPosition = new Vector3( -256, 0, 0 );
				mockBackground.Enabled = true;
			}
		}
	}

	public override void OnDestroyed()
	{
		base.OnDestroyed();

		Scene?.Destroy();
		Scene = null;
	}

	public override void PreFrame()
	{
		Scene.EditorTick( RealTime.Now, RealTime.Delta );
	}
}
using Editor;
using Sandbox;
using Todo.Dialogs;

namespace Todo.Widgets;

public sealed class ToolsControls : Widget
{
	public ToolsControls( Widget parent ) : base( parent )
	{
		Layout = Layout.Row();
		Layout.Spacing = 2f;

		ToolButton refreshButton = Layout.Add( new ToolButton( "", "refresh", this ) );
		refreshButton.MouseClick = TodoDock.Instance.RefreshItems;
		refreshButton.ToolTip = "Refresh All";

		ToolButton showVisibilityButton = Layout.Add( new ToolButton( "", "visibility", this ) );
		showVisibilityButton.MouseClick = OpenVisibilityMenu;
		showVisibilityButton.ToolTip = "Change Visibility";
		showVisibilityButton.OnPaintOverride = () =>
		{
			OnVisibilityPaint( showVisibilityButton.LocalRect, showVisibilityButton );
			return true;
		};

		ToolButton moreButton = Layout.Add( new ToolButton( "", "more_vert", this ) );
		moreButton.MouseClick = OpenMoreMenu;
		moreButton.ToolTip = "More";
	}

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

	// Edited version of ToolButton's OnPaint method
	private void OnVisibilityPaint( Rect rect, ToolButton button )
	{
		Color color = Color.White;

		bool showManual = TodoDock.Cookies.ShowManualEntries;
		bool showCode = TodoDock.Cookies.ShowCodeEntries;

		if ( showManual && showCode )
		{
			color = Theme.Blue;
		}
		else if ( showCode )
		{
			color = Theme.Green;
		}
		else if ( showManual is false && showCode is false )
		{
			color = Theme.TextDisabled;
		}

		Paint.ClearPen();
		if ( Paint.HasMouseOver )
			Paint.SetBrush( Theme.SurfaceBackground );
		else
			Paint.SetBrush( Theme.ControlBackground );

		Paint.DrawRect( rect.Shrink( 1.0f ), Theme.ControlRadius );

		Paint.SetPen( color );
		Paint.DrawIcon( rect, button.Icon, 14, TextFlag.Center );

		Update();
	}

	private void OpenVisibilityMenu()
	{
		var menu = new Menu( this );

		{
			var option = menu.AddOption( new Option( this, "Show Manual Entries", "checklist" ) );
			option.Checkable = true;
			option.Checked = TodoDock.Cookies.ShowManualEntries;
			option.Toggled = SetManual;
		}

		{
			var option = menu.AddOption( new Option( this, "Show Code Entries", "code" ) );
			option.Checkable = true;
			option.Checked = TodoDock.Cookies.ShowCodeEntries;
			option.Toggled = SetCode;
		}

		menu.DeleteOnClose = true;
		menu.OpenAtCursor( true );
	}

	[Shortcut( "todo.toggle-manual-entries", "CTRL+1", typeof( TodoDock ), ShortcutType.Application )]
	private void ToggleManual()
	{
		SetManual( !TodoDock.Cookies.ShowManualEntries );
	}

	[Shortcut( "todo.toggle-code-entries", "CTRL+2", typeof( TodoDock ), ShortcutType.Application )]
	private void ToggleCode()
	{
		SetCode( !TodoDock.Cookies.ShowCodeEntries );
	}

	private void SetManual( bool state )
	{
		TodoDock.Cookies.ShowManualEntries = state;
		TodoDock.Instance.SaveAndRefresh();
	}

	private void SetCode( bool state )
	{
		TodoDock.Cookies.ShowCodeEntries = state;
		TodoDock.Instance.SaveAndRefresh();
	}

	private void OpenMoreMenu()
	{
		var menu = new Menu( this );

		{
			var option = menu.AddOption( new Option( this, "Import Entries", "download" ) );
			option.Triggered = TodoDock.Instance.Import;
		}

		{
			var option = menu.AddOption( new Option( this, "Export Entries", "upload" ) );
			option.Triggered = TodoDock.Instance.Export;
		}

		menu.AddSeparator();

		{
			var option = menu.AddOption( new Option( this, "Settings", "settings" ) );
			option.Triggered = OpenSettingsWidget;
		}

		{
			var option = menu.AddOption( new Option( this, "Help", "question_mark" ) );
			option.Triggered = OpenHelpWidget;
		}

		menu.DeleteOnClose = true;
		menu.OpenAtCursor( true );
	}

	private void OpenHelpWidget()
	{
		var widget = new HelpDialog( null );
		widget.Show();
	}

	private void OpenSettingsWidget()
	{
		var widget = new SettingsDialog( null );
		widget.Show();
	}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Editor;
using Sandbox;
using Sandbox.UI;
using Sandbox.VR;
using Label = Editor.Label;

namespace DataTablesEditor;

/// <summary>
/// I am absolutely not proud of this at all.
/// </summary>
internal class TableView : Widget
{
	public ListView ListView { get; set; }

	public List<Column> Columns { get; set; } = new();

	TableHeader Header;

	public Action<object> ItemClicked;

	public Dictionary<VirtualWidget, int> WidgetIndexMap = new();

	public TableView( Widget parent ) : base( parent )
	{
		ListView = new( this );

		ListView.ItemPaint = widget => PaintRow( widget );
		ListView.ItemSize = new Vector2( 0, 24 );
		ListView.ItemSpacing = 0;
		ListView.ItemClicked = o =>
		{
			if ( ItemClicked is not null )
				ItemClicked( o );
		};
		ListView.Margin = new Margin( 0, 4, 0, 0 );
		ListView.MultiSelect = true;

		Layout = Layout.Column();
		Layout.Add( Header = new TableHeader( this ) );
		Layout.Add( ListView );
	}

	public Column AddColumn( string name = null, int? width = null, Func<object, string> value = null )
	{
		var col = new Column();
		col.Name = name;
		col.Width = width ?? 50;
		col.Value = value;

		Columns.Add( col );

		return col;
	}

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

		int i = 0;
		foreach ( var lbl in Header.Labels )
		{
			Columns[i].Width = (int)lbl.Width;
			i++;
		}
	}

	public void FillHeader()
	{
		foreach ( var column in Columns )
		{
			Header.AddColumn( column );
		}
	}

	public void SetItems<T>( IEnumerable<T> items )
	{
		foreach ( var elem in items.ToList().Cast<object>() )
		{
			ListView.AddItem( elem );
		}
	}

	public void AddItem( object item )
	{
		ListView.AddItem( item );
	}

	private void PaintRow( VirtualWidget widget )
	{
		/*if ( widget.Object is not T t )
			return;*/

		var isAlt = widget.Row % 2 == 0;
		var backgroundColor = widget.Selected
			? Theme.Selection
			: (widget.Hovered ? Theme.ButtonDefault.WithAlpha( 0.7f ) :
				isAlt ? Color.Parse( "#262627" ).GetValueOrDefault() : Color.Parse( "#313131" ).GetValueOrDefault());

		Paint.ClearPen();
		Paint.SetBrush( backgroundColor );
		Paint.DrawRect( widget.Rect );

		Rect rect = widget.Rect;

		Paint.SetDefaultFont();

		foreach ( var column in Columns )
		{
			Paint.SetPen( widget.Selected ? Color.White : column.TextColor );
			var width = column.Width + 4;
			rect.Width = width;
			Paint.DrawText( rect.Shrink( 8, 0 ), column.Value(widget.Object), column.TextFlag | TextFlag.CenterVertically | TextFlag.SingleLine );
			rect.Left += width;

			Paint.SetPen( Color.Parse("#414141").GetValueOrDefault() );
			Paint.DrawLine( rect.TopLeft.WithX( rect.TopLeft.x - 1 ), rect.BottomLeft.WithX( rect.BottomLeft.x - 1 ) );
			Paint.SetPen( widget.Selected ? Color.White : Theme.ControlText );
		}

		Paint.SetPen( Color.Parse("#414141").GetValueOrDefault() );
		Paint.DrawLine( widget.Rect.BottomLeft, widget.Rect.BottomRight );
	}

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

		Paint.ClearPen();
		Paint.SetBrush( Theme.WidgetBackground.WithAlpha( 0.5f ) );
		Paint.DrawRect( LocalRect );

		Paint.ClearPen();
		Paint.SetBrush( Theme.WindowBackground );
		Paint.DrawRect( LocalRect.Shrink( 0, 0, 0, LocalRect.Height - 30 ) );
	}
}

internal class Column
{
	private string _name;
	public string Name
	{
		get
		{
			return _name;
		}
		set
		{
			if ( value is null )
				_name = null;
			else
			{
				StringBuilder result = new StringBuilder();
				result.Append( value[0] );

				for (int i = 1; i < value.Length; i++)
				{
					if ( char.IsUpper( value[i] ) && !char.IsUpper( value[i - 1] ) )
						result.Append( ' ' );

					result.Append( value[i] );
				}

				_name = result.ToString();
			}
		}
	}
	public int Width;
	public Func<object, string> Value;
	public TextFlag TextFlag = TextFlag.Left;
	public Label Label;
	public Color TextColor = Theme.ControlText;
}

internal class TableHeader : Widget
{
	readonly TableView Table;

	public List<Label> Labels = new();

	public HeaderSplitter _splitter;

	private static List<TableHeader> _headers;

	static TableHeader()
	{
		_headers = new();
	}

	[EditorEvent.Frame]
	public static void Frame()
	{
		foreach ( var Header in _headers )
		{
			if ( Header is null || !Header.IsValid )
				continue;

			var list = Header._splitter.Labels;
			for ( int i = 0; i < list.Count; i++ )
			{
				Header.Table.Columns[i].Width = (int)list[i].Width;
				Header.Table.ListView.Update();
			}
		}
	}

	public TableHeader( TableView parent ) : base( parent )
	{
		Table = parent;
		MinimumHeight = 25;

		Layout = Layout.Row();

		_splitter = new(this);
		_splitter.IsHorizontal = true;

		_headers.Add( this );

		Layout.Add( _splitter );
	}

	public override void Close()
	{
		base.Close();

		_headers.Remove( this );
	}

	public void AddColumn( Column column )
	{
		if ( _splitter.IsValid() )
			Labels.Add( _splitter.AddColumn( column ) );
	}
}

internal class HeaderSplitter : Splitter
{
	public List<Label> Labels = new();

	public HeaderSplitter(Widget parent) : base(parent)
	{
	}

	private int splitterCount = 0;
	public Label AddColumn( Column column )
	{
		var lbl = new Label( column.Name );
		lbl.ContentMargins = new Margin( 8, 0, 0, 0 );
		lbl.SetStyles( "font-weight: bold; font-size: 12px;" );
		lbl.OnPaintOverride = () =>
		{
			Paint.SetBrush( ControlWidget.ControlColor );
			Paint.SetPen( Color.Transparent );
			Paint.DrawRect( lbl.LocalRect );

			return false;
		};
		column.Label = lbl;
		Labels.Add( lbl );

		AddWidget( lbl );
		SetCollapsible( splitterCount++, false );

		return lbl;
	}
}
using System;
using System.Collections.Generic;
using Andicraft.SecondOrderDynamics;
using Editor;
using Sandbox;

public class DynamicsGraphWidget : Widget
{
	private SerializedObject _targetObject;
	public DynamicsGraphWidget(Widget parent, SerializedObject baseProp) : base(parent, false)
	{
		// Create a Column Layout
		Layout = Layout.Column();
		// Give it some Margins/Spacing
		Layout.Margin = 4;
		Layout.Spacing = 4;
		
		// Apply some CSS styling
		ToolTip = "Five second preview of the current dynamics settings";

		var f = new Frame( this ) { HorizontalSizeMode = SizeMode.Expand, Size = new Vector2( 100 ) };
		Layout.Add(f);
		_targetObject = baseProp;
	}
	
	protected override void OnPaint()
	{
		Paint.SetBrushAndPen( new Color( 0, 0, 0, .2f ), new Color( 0, 0, 0, .2f ) );
		var borderRect = ContentRect;
		borderRect.Left += 1;
		Paint.DrawRect( borderRect );
		
		_targetObject.TryGetProperty( "Frequency", out var f );
		_targetObject.TryGetProperty( "Damping", out var d );
		_targetObject.TryGetProperty( "Response", out var r );

		var fd = new FloatDynamics( f.GetValue<float>(), d.GetValue<float>(), r.GetValue<float>(), 1);
		
		Paint.SetPen( Color.Red );
		Paint.ClearBrush();


		var linePts = new List<Vector2>();
		var baseY = ContentRect.Center.y;
		var waveHeight = ContentRect.Height * .5f;
		
		for ( int i = 0; i < 60*5; i++ )
		{
			var x = (ContentRect.Width / 60f*5f) * (i / 60f * 5f);
			var y = Math.Clamp(baseY + fd.Update( 1 / 60f, 0 ) * waveHeight, ContentRect.Top, ContentRect.Bottom);
			linePts.Add( new Vector2( x, y ) );
		}
		Paint.DrawLine(linePts);
	}
}
using RbxlReader.DataTypes;

namespace RbxlReader.Instances;

public class InstanceProperty {
    public PropertyType Type {get;}

    public object Value {get; set;}
    public byte[] RawBuffer = Array.Empty<byte>();
    

    public InstanceProperty(PropertyType type, object value) {
        this.Type = type;
        Value = value;
    }
}
namespace RbxlReader.DataTypes;

public class Vector3int16 {
    public short X,Y,Z;
    public override string ToString() => $"{X}, {Y}, {Z}";
}
using Editor;
using Sandbox;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace QuickAsset;

public class QuickAssetMenu : Menu
{
	public static QuickAssetMenu Current { get; set; }
	public string Filter { get; set; }

	private LineEdit _searchInput;
	private ListView _list;

	private bool UseCloud
	{
		get => EditorCookie.Get( "qam.UseCloud", false );
		set => EditorCookie.Set( "qam.UseCloud", value );
	}

	private IReadOnlyList<string> RecentPackages
	{
		get => EditorCookie.Get<List<string>>( "qam.RecentPackages", [] );
		set => EditorCookie.Set( "qam.RecentPackages", value );
	}

	private IReadOnlyList<string> RecentAssets
	{
		get => EditorCookie.Get<List<string>>( "qam.RecentAssets", [] );
		set => EditorCookie.Set( "qam.RecentAssets", value );
	}

	public QuickAssetMenu() : base( null )
	{
		IsWindow = false;
		Current = this;

		Layout = Layout.Column();
		FixedWidth = 300;
		FixedHeight = 410;

		Layout.Margin = 8;
		Layout.Spacing = 8;

		var row = Layout.AddRow();

		_searchInput = row.Add( new LineEdit(), 1 );
		_searchInput.TextChanged += v =>
		{
			UpdateList();
		};
		_searchInput.PlaceholderText = "Search for assets...";
		_searchInput.FocusMode = FocusMode.Click;
		_searchInput.Focus();

		var cloudToggle = row.Add( new IconButton( "cloud_off" ) );

		void SetCloudToggle()
		{
			cloudToggle.Icon = UseCloud ? "cloud" : "cloud_off";
			cloudToggle.ToolTip = UseCloud ? "Cloud Enabled" : "Cloud Disabled";
		}

		SetCloudToggle();

		cloudToggle.OnClick = () =>
		{
			UseCloud = !UseCloud;
			SetCloudToggle();
			UpdateList();
		};

		Bind( nameof( Filter ) ).ReadOnly().From( _searchInput, x => x.Text );

		Layout.Add( BuildAssets(), 1 );
	}

	// fucking shite x2
	private bool IsSpawnable( AssetType type )
	{
		if ( type == null )
			return false;

		if ( type.IsSimpleAsset )
			return false;

		if ( type.FileExtension == "vmdl" )
			return true;

		if ( type.FileExtension == "prefab" )
			return true;

		if ( type.FileExtension == "sound" )
			return true;

		if ( type.FileExtension == "vpcf" )
			return true;

		return false;
	}

	private void UpdateList()
	{
		if ( UseCloud )
			_ = SetListCloud();
		else
			SetListAssets();
	}

	private void SetListAssets()
	{
		List<object> options = new();

		if ( string.IsNullOrEmpty( Filter ) )
		{
			foreach ( var recent in RecentAssets )
			{
				var asset = AssetSystem.FindByPath( recent );

				if ( asset == null )
					continue;

				if ( !IsSpawnable( asset.AssetType ) )
					continue;

				options.Add( asset );
			}
		}
		else
		{
			var assets = AssetSystem.All
				.Where( x => x.Path.Contains( Filter, StringComparison.OrdinalIgnoreCase ) )
				.Where( x => IsSpawnable( x.AssetType ) )
				.OrderByDescending( x => x.LastOpened )
				.OrderByDescending( x => RecentAssets.Contains( x.Path ) );

			options.AddRange( assets );
		}

		_list.SetItems( options );
	}

	private async Task SetListCloud()
	{
		List<object> options = new();

		if ( string.IsNullOrEmpty( Filter ) )
		{
			foreach ( var ident in RecentPackages )
			{
				var package = await Package.Fetch( ident, false );

				if ( package == null )
					continue;

				if ( !IsSpawnable( GetAssetType( package.TypeName ) ) )
					continue;

				options.Add( package );
			}
		}
		else
		{
			var tokenSource = new CancellationTokenSource();
			var token = tokenSource.Token;
			var found = await Package.FindAsync( Filter, 200, 0, token );

			if ( found.Packages.Length == 0 )
				return;

			//
			// Add them all to the list
			//
			var packages = found.Packages.Where( x => IsSpawnable( GetAssetType( x.TypeName ) ) )
				.OrderByDescending( x => x.Updated )
				.OrderByDescending( x => RecentPackages.Contains( x.FullIdent ) );

			options.AddRange( packages );
		}

		_list.SetItems( options );
	}

	private Widget BuildAssets()
	{
		var canvas = new Widget( null );
		canvas.Layout = Layout.Row();

		_list = new ListView( canvas );
		UpdateList();
		_list.Margin = 0;
		_list.ItemSize = new Vector2( 0, 40 );
		_list.ItemPaint = PaintListMode;
		_list.ItemSpacing = 0;
		_list.ItemClicked = o =>
		{
			if ( o is Asset a )
				_ = OnSelected( a );

			if ( o is Package p )
				_ = OnSelected( p );
		};
		_list.OnPaintOverride = PaintList;

		canvas.Layout.Add( _list );

		return canvas;
	}

	public static AssetType GetAssetType( string typeName ) => typeName switch
	{
		"map" => AssetType.MapFile,
		"model" => AssetType.Model,
		"material" => AssetType.Material,
		"sound" => AssetType.SoundFile,
		"shader" => AssetType.Shader,

		_ => null
	};

	private void PaintListMode( VirtualWidget item )
	{
		string title = "";
		string subtitle = "";
		Color color = Color.White;
		bool isRecent = false;

		{
			if ( item.Object is Asset asset )
			{
				title = asset.Name;
				subtitle = asset.AssetType.FriendlyName;
				color = asset.AssetType.Color;
				isRecent = RecentAssets.Contains( asset.Path );
			}
			else if ( item.Object is Package package )
			{
				var type = GetAssetType( package.TypeName );

				title = package.Title;
				subtitle = package.Org.Title;
				color = type.Color;
				isRecent = RecentPackages.Contains( package.FullIdent );
			}
			else
			{
				Log.Warning( "Can't paint type" );
				return;
			}
		}

		if ( Paint.HasSelected || Paint.HasPressed )
		{
			Paint.ClearPen();
			Paint.SetBrush( Paint.HasPressed ? Theme.Primary.WithAlpha( 0.4f ) : Theme.Primary.WithAlpha( 0.2f ) );
			Paint.DrawRect( item.Rect.Shrink( 0 ), 3 );

			Paint.SetPen( Theme.White );
		}
		else if ( Paint.HasMouseOver )
		{
			Paint.ClearPen();
			Paint.SetBrush( Theme.Blue.Darken( 0.7f ).Desaturate( 0.3f ).WithAlpha( 0.5f ) );
			Paint.DrawRect( item.Rect );
			Paint.SetPen( Theme.White );
		}

		var itemRect = item.Rect.Shrink( 1 );
		itemRect = itemRect.Shrink( 0, 0, 8, 0 );

		var rect = itemRect;
		var textRect = rect.Shrink( 40 + 8, 4, 0, 4 );

		{
			Paint.SetPen( Theme.ControlText );
			Paint.SetDefaultFont();
			Paint.DrawText( textRect, title, TextFlag.LeftTop | TextFlag.SingleLine );
		}

		{
			Paint.SetDefaultFont();
			Paint.SetPen( Theme.ControlText.WithAlpha( 0.5f ) );
			Paint.DrawText( textRect, subtitle, TextFlag.LeftBottom | TextFlag.SingleLine );
		}

		if ( isRecent )
		{
			Paint.SetDefaultFont();
			Paint.SetPen( Theme.ControlText.WithAlpha( 0.5f ) );
			Paint.DrawIcon( textRect, "history", 13.0f, TextFlag.RightCenter );
		}

		Paint.ClearPen();

		var ir = itemRect;
		ir.Size = new Vector2( itemRect.Height, itemRect.Height );

		{
			var aPos = rect.TopLeft;
			var bPos = rect.BottomLeft;

			var aColor = color.WithAlpha( 0 );
			var bColor = color.WithAlpha( 0.5f );

			Paint.SetBrushLinear( aPos, bPos, aColor, bColor );
			Paint.DrawRect( ir );

			if ( item.Object is Asset asset )
			{
				Paint.Draw( ir, asset.GetAssetThumb() ?? asset.AssetType.Icon64 );
			}
			else if ( item.Object is Package package )
			{
				Paint.Draw( ir, package.Thumb );
			}
		}

		ir.Top = ir.Bottom - 4;
		Paint.ClearPen();
		Paint.SetBrush( color );
		Paint.DrawRect( ir );
	}

	public override void Hide()
	{
		base.Hide();

		WindowOpacity = 0;
	}

	protected override void OnKeyPress( KeyEvent e )
	{
		if ( e.KeyboardModifiers.HasFlag( KeyboardModifiers.Ctrl ) && e.Key is KeyCode.K )
			_searchInput.Focus();
	}

	// fucking shite
	private Ray GetRay( Vector2 cursorPosition, Vector2 screenSize )
	{
		var state = SceneViewportWidget.LastSelected.State;
		var fov = 80.0f;

		var aspect = screenSize.x / screenSize.y;
		var posNormalized = new Vector2( (2.0f * cursorPosition.x / screenSize.x) - 1, (2.0f * cursorPosition.y / screenSize.y) - 1 ) * -1.0f;

		float halfWidth = MathF.Tan( fov * MathF.PI / 360.0f );
		float halfHeight = halfWidth / aspect;

		var ray = new Vector3( 1.0f, posNormalized.x / (1.0f / halfWidth), posNormalized.y / (1.0f / halfHeight) ) * state.CameraRotation;
		return new Ray( state.CameraPosition, ray.Normal );
	}

	private async Task CreateGameObject( string path )
	{
		using var a = SceneEditorSession.Scope();
		using var b = SceneEditorSession.Active.UndoScope( "Quick Add Asset" ).Push();

		var drop = await BaseDropObject.CreateDropFor( path );
		var rect = SceneViewportWidget.LastSelected.ScreenRect;
		var cursorPos = CursorPosition - rect.TopLeft;

		var state = SceneViewportWidget.LastSelected.State;

		var ray = GetRay( cursorPos, rect.Size );

		var tr = SceneEditorSession.Active.Scene.Trace
						.WithoutTags( "trigger" )
						.Ray( ray, 1000f )
						.Run();

		if ( drop is not null )
		{
			await drop.StartInitialize( path );
			drop.UpdateDrag( tr, EditorScene.GizmoSettings );

			using ( var sc = SceneEditorSession.Active.Scene.Push() )
			{
				await drop.OnDrop();
				var go = drop.GameObject;

				if ( go.IsValid() )
				{
					EditorScene.Selection.Add( go );
				}
			}

			drop.Delete();
		}
	}

	private async Task OnSelected( Package package )
	{
		Close();

		// Remove existing, append to back
		RecentPackages = [package.FullIdent, .. RecentPackages.Where( x => x != package.FullIdent ).Take( 8 )];

		await CreateGameObject( package.Url );
	}

	private async Task OnSelected( Asset asset )
	{
		Close();

		// Remove existing, append to back
		RecentAssets = [asset.Path, .. RecentAssets.Where( x => x != asset.Path ).Take( 8 )];

		await CreateGameObject( asset.Path );
	}

	private bool PaintList()
	{
		if ( _list.Items.Any() )
			return false;

		Paint.ClearBrush();
		Paint.ClearPen();

		Paint.SetPen( Theme.ControlText.WithAlpha( 0.5f ) );
		Paint.SetDefaultFont();


		if ( string.IsNullOrEmpty( Filter ) )
		{
			Paint.DrawText( _list.ContentRect, "Type to start searching for assets" );
		}
		else
		{
			Paint.DrawText( _list.ContentRect, "Nothing found :(" );
		}

		return true;
	}

	private Vector2 CursorPosition;

	[Shortcut( "quick-asset.toggle", "SHIFT+A", ShortcutType.Widget )]
	public static void ShowQuickAsset()
	{
		var rect = SceneViewWidget.Current.ScreenRect;
		var cursorPos = Editor.Application.CursorPosition;

		if ( !rect.IsInside( cursorPos ) )
			return;

		Current?.Destroy();

		Current = new QuickAssetMenu();
		Current.Show();

		Current.CursorPosition = cursorPos;
		Current.Position = cursorPos + 16;
	}
}
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Win32;

namespace Editor.CodeEditors;

[Title( "Cursor" )]
public class Cursor : ICodeEditor

{
	public void OpenFile( string path, int? line, int? column )
	{
		var codeWorkspace = $"{Environment.CurrentDirectory}/s&box.code-workspace";
		CreateWorkspace( codeWorkspace );

		Launch( $"\"{codeWorkspace}\" -g \"{path}:{line}:{column}\"" );
	}

	public void OpenSolution()
	{
		var codeWorkspace = $"{Environment.CurrentDirectory}/s&box.code-workspace";
		CreateWorkspace( codeWorkspace );

		// Need to wrap the code workspace in quotes, but CreateWorkspace doesn't need that
		Launch( $"\"{codeWorkspace}\"" );
	}

	public void OpenAddon( Project addon )
	{
		var projectPath = (addon != null) ? addon.GetRootPath() : "";

		Launch( $"\"{projectPath}\"" );
	}

	public bool IsInstalled() => !string.IsNullOrEmpty( GetLocation() );

	private static void Launch( string arguments )
	{
		var startInfo = new System.Diagnostics.ProcessStartInfo
		{
			FileName = GetLocation(),
			Arguments = arguments,
			CreateNoWindow = true,
		};

		System.Diagnostics.Process.Start( startInfo );
	}

	private static void CreateWorkspace( string path )
	{
		StringBuilder builder = new();
		builder.AppendLine( "{" );
		builder.AppendLine( "    \"folders\": [" );

		foreach ( var addon in EditorUtility.Projects.GetAll() )
		{
			if ( !addon.Active ) continue;

			builder.AppendLine( "        {" );
			builder.AppendLine( $"            \"name\": \"{addon.Config.Ident}\"," );
			builder.AppendLine( $"            \"path\": \"{addon.GetRootPath().Replace( @"\", @"\\" )}\"," );
			builder.AppendLine( "        }," );
		}

		builder.AppendLine( "    ]" );

		// You need the C# extension to do anything
		// builder.AppendLine( "    \"extensions\": {" );
		// builder.AppendLine( "        \"recommendations\": [" );
		// builder.AppendLine( "            \"ms-dotnettools.csharp\"" );
		// builder.AppendLine( "        ]," );
		// builder.AppendLine( "    }" );

		// Settings: make sure we're using .net 6 and that roslyn analyzers are on (they never fucking are)
		// builder.AppendLine( "    \"settings\": {" );
		// builder.AppendLine( "        \"omnisharp.useModernNet\": true," );
		// builder.AppendLine( "        \"omnisharp.enableRoslynAnalyzers\": true" );
		// builder.AppendLine( "    }" );

		builder.AppendLine( "}" );

		File.WriteAllText( path, builder.ToString() );
	}

	static string Location;

	[System.Diagnostics.CodeAnalysis.SuppressMessage( "Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>" )]
	private static string GetLocation()
	{
		if ( Location != null )
		{
			return Location;
		}

		string value = null;
		using ( var key = Registry.ClassesRoot.OpenSubKey( @"Applications\\Cursor.exe\\shell\\open\\command" ) )
		{
			value = key?.GetValue( "" ) as string;
		}

		if ( value == null )
		{
			return null;
		}

		// Given `"C:\Users\<user>\AppData\Local\Programs\cursor\Cursor.exe" "%1"` grab the first bit
		Regex rgx = new Regex( "\"(.*)\" \".*\"", RegexOptions.IgnoreCase );
		var matches = rgx.Matches( value );
		if ( matches.Count == 0 || matches[0].Groups.Count < 2 )
		{
			return null;
		}

		Location = matches[0].Groups[1].Value;
		return Location;
	}
}
using Editor;

public static class MyEditorMenu
{
	[Menu( "Editor", "TestLibrary1/My Menu Option" )]
	public static void OpenMyMenu()
	{
		EditorUtility.DisplayDialog( "It worked!", "This is being called from your library's editor code!" );
	}
}

namespace Editor.rectedittemplateexporter;

public enum DragState
{
	None,
	WaitingForMovement,
	Dragging,
}

enum GridSnapMode
{
	Nearest,
	RoundDown,
	RoundUp,
}

public class RectView : Widget
{
	private readonly Window Session;
	private Document Document => Session.Document;

	private Rect DrawRect;
	private Pixmap SourceImage;
	private Pixmap ScaledImage;
	private DragState DragState;
	private Vector2 DragStartPos;
	private Rect NewRect;
	private List<Document.Rectangle> RectanglesUnderCursor;

	public RectView( Window session ) : base( session )
	{
		Session = session;

		Name = "Rect View";
		WindowTitle = "Rect View";
		SetWindowIcon( "space_dashboard" );

		MouseTracking = true;
		FocusMode = FocusMode.Click;

		DrawRect = GetDrawRect();
	}

	private Vector2 SnapUVToGrid( Vector2 uv )
	{
		var gridCountX = GetGridCountX();
		var gridCountY = GetGridCountY();

		var x = (int)(gridCountX * uv.x + 0.5f);
		var y = (int)(gridCountY * uv.y + 0.5f);

		return new Vector2( x / (float)gridCountX, y / (float)gridCountY );
	}

	private Vector2 PixelToUV_OnGrid( Vector2 vPixel )
	{
		return SnapUVToGrid( PixelToUV( vPixel ) );
	}

	private void DragUpdate( Vector2 mousePos )
	{
		var minStart = PixelToUV_OnGrid( DragStartPos );
		var maxStart = PixelToUV_OnGrid( DragStartPos );
		var current = PixelToUV_OnGrid( mousePos );
		var min = Vector2.Min( current, minStart );
		var max = Vector2.Max( current, maxStart );

		NewRect = new Rect( min, max - min );

		Update();
	}

	[EditorEvent.Frame]
	protected void OnFrame()
	{
		FindRectanglesUnderCursor( FromScreen( Application.CursorPosition ) );
	}

	protected override void OnMouseMove( MouseEvent e )
	{
		base.OnMouseMove( e );

		if ( DragState == DragState.WaitingForMovement && (NewRect.Width > 0.0f || NewRect.Height > 0.0f) )
		{
			DragState = DragState.Dragging;
		}

		if ( DragState != DragState.None )
		{
			DragUpdate( e.LocalPosition );
		}
	}

	protected override void OnMousePress( MouseEvent e )
	{
		base.OnMousePress( e );

		if ( e.Button == MouseButtons.Left )
		{
			DragState = DragState.WaitingForMovement;
			DragStartPos = e.LocalPosition;
		}
	}

	protected override void OnMouseReleased( MouseEvent e )
	{
		base.OnMouseReleased( e );

		if ( e.Button == MouseButtons.Left )
		{
			if ( DragState == DragState.Dragging )
			{
				if ( Document is not null && NewRect.Width > 0.0f && NewRect.Height > 0.0f )
				{
					Document.SelectRectangle( Document.AddRectangle( NewRect ), SelectionOperation.Set );
					Session.Snapshot( "Create Rectangle" );
				}
			}
			else
			{
				Document.SelectRectangle( GetFirstRectangleUnderCursor(), SelectionOperation.Set );
				Session.Snapshot( "Select Rectangle" );
			}

			DragState = DragState.None;
			NewRect = default;
		}
	}

	public void SetMaterial( Material material )
	{
		SourceImage = null;
		ScaledImage = null;

		if ( material is null )
			return;

		var texture = material.FirstTexture;
		if ( texture is null )
			return;

		SourceImage = Pixmap.FromTexture( texture, false );
		if ( SourceImage is null )
			return;

		UpdateScaledBackgroundImage();
	}

	private void UpdateScaledBackgroundImage()
	{
		ScaledImage = SourceImage?.Resize( DrawRect.Size );
	}

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

		DrawRect = GetDrawRect();

		UpdateScaledBackgroundImage();
	}

	protected override void OnPaint()
	{
		Paint.ClearPen();

		Paint.SetBrush( Theme.ControlBackground );
		Paint.DrawRect( LocalRect );

		Paint.SetBrush( Color.Gray );
		Paint.DrawRect( DrawRect );

		if ( ScaledImage is not null )
		{
			Paint.Draw( DrawRect, ScaledImage );
		}

		if ( Session.GridEnabled )
		{
			DrawGrid();
		}

		DrawRectangleSet( Document?.Rectangles );

		if ( DragState == DragState.Dragging )
		{
			var topLeft = UVToPixel( NewRect.TopLeft );
			var bottomRight = UVToPixel( NewRect.BottomRight );
			var newRect = new Rect( topLeft, bottomRight - topLeft );
			Paint.ClearBrush();
			Paint.SetPen( Color.Yellow, 3 );
			Paint.DrawRect( newRect );
		}
	}

	private Vector2 UVToPixel( Vector2 uv )
	{
		return new Vector2( (int)((uv.x * DrawRect.Width) + DrawRect.Left), (int)((uv.y * DrawRect.Height) + DrawRect.Top) );
	}

	private Vector2 PixelToUV( Vector2 pixel )
	{
		return new Vector2( (pixel.x - DrawRect.Left) / DrawRect.Width, (pixel.y - DrawRect.Top) / DrawRect.Height );
	}

	private int GetGridPower()
	{
		return Session.GridEnabled ? Session.GridPower : 10;
	}

	private int GetGridCountX()
	{
		var width = SourceImage is null ? 512 : System.Math.Max( (int)SourceImage.Width, 1 );
		var height = SourceImage is null ? 512 : System.Math.Max( (int)SourceImage.Height, 1 );
		var gridPower = GetGridPower();

		if ( width >= height )
		{
			return 1 << gridPower;
		}
		else
		{
			return (1 << gridPower) * width / height;
		}
	}

	private int GetGridCountY()
	{
		var width = SourceImage is null ? 512 : System.Math.Max( (int)SourceImage.Width, 1 );
		var height = SourceImage is null ? 512 : System.Math.Max( (int)SourceImage.Height, 1 );
		var gridPower = GetGridPower();

		if ( height >= width )
		{
			return 1 << gridPower;
		}
		else
		{
			return (1 << gridPower) * height / width;
		}
	}

	public Document.Rectangle GetFirstRectangleUnderCursor()
	{
		return RectanglesUnderCursor?.FirstOrDefault();
	}

	public void FindRectanglesUnderCursor( Vector2 mousePos )
	{
		RectanglesUnderCursor = FindRectanglesContainingPoint( PixelToUV( mousePos ) );

		Update();
	}

	public List<Document.Rectangle> FindRectanglesContainingPoint( Vector2 vPoint )
	{
		return Document.Rectangles
			 .Where( rectangle => rectangle.IsPointInRectangle( vPoint ) )
			 .Select( rectangle => new { Rectangle = rectangle, Distance = rectangle.DistanceFromPointToCenter( vPoint ) } )
			 .OrderBy( item => item.Distance )
			 .Select( item => item.Rectangle )
			 .ToList();
	}

	private void DrawRectangleSet( IEnumerable<Document.Rectangle> rectangles )
	{
		if ( rectangles is null )
			return;

		foreach ( var rectangle in rectangles.Where( x => !Document.IsRectangleSelected( x ) ) )
		{
			Paint.SetBrush( rectangle.Color.WithAlpha( 0.5f ) );
			Paint.SetPen( Color.Black.WithAlpha( 192 / 255.0f ), 1 );
			DrawRectangle( rectangle );
		}

		foreach ( var rectangle in Document.SelectedRectangles )
		{
			Paint.SetBrush( new Color32( 255, 255, 0, 64 ) );
			Paint.SetPen( new Color32( 255, 255, 0 ), 1 );
			DrawRectangle( rectangle );
		}

		var rectangleUnderCursor = GetFirstRectangleUnderCursor();
		if ( rectangleUnderCursor is not null && !Document.IsRectangleSelected( rectangleUnderCursor ) )
		{
			Paint.SetBrush( new Color32( 0, 255, 0, 64 ) );
			Paint.SetPen( Color.Green );
			DrawRectangle( rectangleUnderCursor );
		}
	}

	private void DrawRectangle( Document.Rectangle rectangle, int nMinInset = 0, int nMaxInset = 0 )
	{
		if ( rectangle is null )
			return;

		var minPoint = UVToPixel( rectangle.Min );
		var maxPoint = UVToPixel( rectangle.Max );
		minPoint += new Vector2( nMinInset, nMinInset );
		maxPoint -= new Vector2( nMaxInset + 1, nMaxInset + 1 );

		Paint.DrawRect( new Rect( minPoint, maxPoint - minPoint ) );
	}

	private void DrawGrid()
	{
		const float gridOpacity = 64 / 255.0f;

		var gridCountX = GetGridCountX();
		var gridCountY = GetGridCountY();

		var stepX = 1.0f / gridCountX;
		var stepY = 1.0f / gridCountY;

		var rect = DrawRect;

		Paint.ClearBrush();

		for ( int ix = 0; ix <= gridCountX; ++ix )
		{
			var u = ix * stepX;
			var gx = UVToPixel( new Vector2( u, 0 ) ).x;

			if ( gx > rect.Left )
			{
				Paint.SetPen( new Color( 1, 1, 1, gridOpacity ) );
				Paint.DrawLine( new Vector2( gx - 1, rect.Top + 1 ), new Vector2( gx - 1, rect.Height + rect.Top - 2 ) );
			}

			if ( gx < (rect.Left + rect.Width) )
			{
				Paint.SetPen( new Color( 0, 0, 0, gridOpacity ) );
				Paint.DrawLine( new Vector2( gx, rect.Top + 1 ), new Vector2( gx, rect.Height + rect.Top - 2 ) );
			}
		}

		for ( int iy = 0; iy <= gridCountY; ++iy )
		{
			var v = iy * stepY;
			var gy = UVToPixel( new Vector2( 0, v ) ).y;

			if ( gy > rect.Top )
			{
				Paint.SetPen( new Color( 1, 1, 1, gridOpacity ) );

				if ( gy == (rect.Top + rect.Height) )
				{
					Paint.DrawLine( new Vector2( rect.Left, gy - 1 ), new Vector2( rect.Width + rect.Left - 1, gy - 1 ) );
				}
				else
				{
					Paint.DrawLine( new Vector2( rect.Left + 1, gy - 1 ), new Vector2( rect.Width + rect.Left - 2, gy - 1 ) );
				}

			}

			if ( gy < (rect.Top + rect.Height) )
			{
				Paint.SetPen( new Color( 0, 0, 0, gridOpacity ) );

				if ( gy == rect.Top )
				{
					Paint.DrawLine( new Vector2( rect.Left, gy ), new Vector2( rect.Width + rect.Left - 1, gy ) );
				}
				else
				{
					Paint.DrawLine( new Vector2( rect.Left + 1, gy ), new Vector2( rect.Width + rect.Left - 2, gy ) );
				}
			}
		}
	}

	private Rect GetDrawRect()
	{
		const int marigin = 16;
		const int drawSnapSize = 4;

		var imageSize = SourceImage is null ? 0 : SourceImage.Size;
		var widgetWidth = System.Math.Max( (int)Width - (marigin * 2), 128 );
		var widgetHeight = System.Math.Max( (int)Height - (marigin * 2), 128 );
		var imageWidth = System.Math.Max( (int)imageSize.x, 1 );
		var imageHeight = System.Math.Max( (int)imageSize.y, 1 );

		int drawWidth;
		int drawHeight;

		if ( (imageWidth > 0) && (imageHeight > 0) )
		{
			var aspect = imageWidth / (float)imageHeight;
			var relativeWidth = (int)(widgetWidth / System.MathF.Max( aspect, 1.0f ));
			var relativeHeight = (int)(widgetHeight * System.MathF.Min( aspect, 1.0f ));

			if ( relativeWidth <= relativeHeight )
			{
				drawWidth = widgetWidth;
				drawHeight = widgetWidth * imageHeight / imageWidth;
			}
			else
			{
				drawHeight = widgetHeight;
				drawWidth = widgetHeight * imageWidth / imageHeight;
			}
		}
		else
		{
			var drawSize = System.Math.Min( widgetWidth, widgetHeight );
			drawHeight = drawSize;
			drawWidth = drawSize;
		}

		drawWidth = drawWidth / drawSnapSize * drawSnapSize;
		drawHeight = drawHeight / drawSnapSize * drawSnapSize;

		return new Rect( marigin, marigin, drawWidth, drawHeight );
	}
}
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Panelize;

public abstract class EnumControl<T> : Widget where T : struct, Enum
{
	public Dictionary<T, DisplayInfo> DisplayOverrides { get; set; } = new();
	/// <summary>
	/// Display items in this order.
	/// </summary>
	public List<T> ValueOverrides { get; set; } = new();
	public T Value { get; protected set; }
	public Action<T> OnValueChanged { get; set; }
	public EnumControl(T defaultValue = default)
	{
		Value = default;
	}
	public virtual void SetValue(T value)
	{
		Value = value;
		OnValueChanged?.Invoke( value );
	}
	public void SetDisplay( T value, string icon = "", string name = "", string description = "", string group = "" )
	{
		bool iconChanged = !string.IsNullOrEmpty( icon );
		bool nameChanged = !string.IsNullOrEmpty( name );
		bool descriptionChanged = !string.IsNullOrEmpty( description );
		bool groupChanged = !string.IsNullOrEmpty( group );

		if ( DisplayOverrides.TryGetValue( value, out var o ) )
		{
			if(iconChanged)
				o.Icon = icon;

			if ( nameChanged )
				o.Name = name;

			if(descriptionChanged) 
				o.Description = description;

			if ( groupChanged )
				o.Group = group;

			DisplayOverrides[value] = o;
		}
		else
		{
			DisplayInfo info = new();
			DisplayInfo existing = DisplayInfo.ForEnumValues<T>()
				.Where(e => e.value.Equals(value)).FirstOrDefault().info;

			info.Icon = iconChanged ? icon : existing.Icon;
			info.Name = nameChanged ? name : existing.Name;
			info.Description = descriptionChanged ? description : existing.Description;
			info.Group = groupChanged ? group : existing.Group;
			info.Browsable = true;
			DisplayOverrides.Add( value, info );
		}
	}
	/// <summary>
	/// Set order of values to be displayed.
	/// Values not in list are not displayed.
	/// </summary>
	/// <param name="values"></param>
	public void SetOrder( params T[] values )
	{
		ValueOverrides = values.ToList();
	}
	public (T, DisplayInfo)[] GetValueDisplays()
	{
		var valueDisplays = DisplayInfo.ForEnumValues<T>();
		if ( ValueOverrides != null && ValueOverrides.Count > 0 )
		{
			valueDisplays = valueDisplays
				.IntersectBy( ValueOverrides, kv => kv.value )
				.OrderBy( kv => ValueOverrides.IndexOf( kv.value ) )
				.ToArray();
		}

		if(DisplayOverrides.Count > 0)
		{
			List<(T value, DisplayInfo info)> newDisplays = new();
			foreach((T value, DisplayInfo info) in valueDisplays)
			{
				if(DisplayOverrides.ContainsKey(value))
				{
					DisplayInfo o = DisplayOverrides[value];
					newDisplays.Add( (value, o) );
				}
				else
				{
					newDisplays.Add( (value, info) );
				}
			}

			valueDisplays = newDisplays.ToArray();
		}

		return valueDisplays;
	}

	public DisplayInfo GetValueDisplay(T value)
	{
		if(DisplayOverrides.ContainsKey(value))
		{
			return DisplayOverrides[value];
		}

		return DisplayInfo.ForEnumValues<T>()
				.Where( e => e.value.Equals( value ) ).FirstOrDefault().info;
	}
}
using Sandbox.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Panelize;

public class BoxSizeControl : Widget
{
	LengthControl topControl;
	LengthControl leftControl;
	LengthControl rightControl;
	LengthControl bottomControl;
	GridLayout editor;
	public BoxSizeControl()
	{
		Layout = Layout.Column();
		editor = Layout.Grid();
		editor.Spacing = 16f;
		Layout.Add(editor);

		SetSizeMode( SizeMode.CanShrink, SizeMode.Default );

		topControl = CreateControl();
		leftControl = CreateControl();
		rightControl = CreateControl();
		bottomControl = CreateControl();

		editor.AddCell( 1, 0, topControl );
		editor.AddCell( 0, 1, leftControl );
		editor.AddCell( 2, 1, rightControl );
		editor.AddCell( 1, 2, bottomControl );
	}

	public void Bind(SerializedProperty topProperty, SerializedProperty leftProperty, SerializedProperty rightProperty, SerializedProperty bottomProperty )
	{
		topControl.Bind( topProperty );
		leftControl.Bind( leftProperty );
		rightControl.Bind( rightProperty );
		bottomControl.Bind( bottomProperty );
	}

	private LengthControl CreateControl()
	{
		LengthControl control = new( this, true )
		{
			MaximumWidth = 110f,
			//AmountControlWidth = 110f,
			//AmountSliderControlWidth = 40f,
			AmountSliderWidth = 70f,
			//UnitControlWidth = 110f
		};

		control.SetUnit( LengthUnit.Auto );
		return control;
	}

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

		var rect = editor.GetCellRect( 1, 1 ).Shrink(24f, 8f);

		Paint.SetPen( Theme.Grey );
		Paint.DrawRect( rect );

		Paint.SetPen( Theme.White );

		var topRect = editor.GetCellRect( 0, 1 );
		topRect.Top += topRect.Height + 16f;
		Paint.DrawText( topRect, "Top" );

		var leftRect = editor.GetCellRect( 1, 0 );
		leftRect.Left += leftRect.Width + 32f;
		Paint.DrawText( leftRect, "Left" );

		var rightRect = editor.GetCellRect( 1, 2 );
		rightRect.Left -= rightRect.Width + 32f;
		Paint.DrawText( rightRect, "Right" );

		var bottomRect = editor.GetCellRect( 2, 1 );
		bottomRect.Top -= bottomRect.Height + 16f;
		Paint.DrawText( bottomRect, "Bottom" );
	}
}
using System;
using System.Linq;
using System.Reflection;
using Sandbox;
using Sandbox.UI;

namespace Panelize;
public class PropertySheetRow : Widget
{
	public Widget Control { get; private set; }

	public static PropertySheetRow Create( SerializedProperty property )
	{
		Widget editor = default;

		if(EditorTypeLibrary.TryGetType(property.PropertyType, out _))
		{
			try
			{
				editor = ControlWidget.Create( property );
			}
			catch ( Exception e )
			{
				Log.Warning( e, $"Error creating controlwidget for {property.Name}" );
			}
		}

		if ( !editor.IsValid() )
		{
			editor = CanEditAttribute.CreateEditorFor( property.PropertyType );
		}

		if ( !editor.IsValid() ) return null;

		var row = new PropertySheetRow( property );
		row.Build( editor );

		return row;
	}
	public static PropertySheetRow Create( PropertyInfo info, object target )
	{
		CustomSerializedProperty property = new( info, target );
		return Create( property );
	}
	public SerializedProperty Property { get; private set; }
	public PropertySheetRow( SerializedProperty property )
	{
		Property = property;
		FocusMode = FocusMode.Click;
	}

	public void Build( Widget controlWidget, bool hasLabel = true, bool isExpanded = false )
	{
		if ( Property is null )
			return;

		var gridLayout = Layout.Grid();

		gridLayout.HorizontalSpacing = 0;
		gridLayout.Margin = new Margin( 0, 0, 4, 0 );
		Layout = gridLayout;

		Control = controlWidget;
		ToolTip = $"<font>{Property.Description ?? Property.DisplayName}</font>";
		HorizontalSizeMode = SizeMode.CanShrink;

		Control.HorizontalSizeMode = SizeMode.Flexible;

		/*
		if ( EditorUtility.Prefabs.GetVariables( property.Parent ) is not null )
		{
			gridLayout.AddCell( 0, 1, new VariableButton( property, controlWidget ), 1, 1, TextFlag.LeftTop );
		}

		if ( property.IsNullable )
		{
			gridLayout.AddCell( 1, 1, new PropertyButton( property, controlWidget ), 1, 1, TextFlag.LeftTop );
			controlWidget.Enabled = !property.IsNull;
		}
		*/

		if ( hasLabel )
		{
			var label = new PropertySheetPropertyLabel( Property );

			label.ContentMargins = isExpanded ? new( 0, 0, 0, 4 ) : new( 0, 0, 4, 0 );
			gridLayout.AddCell( 2, 1, label, xSpan: (isExpanded ? 2 : 1), alignment: TextFlag.LeftTop );
			gridLayout.AddCell( 3 - (isExpanded ? 1 : 0), 1 + (isExpanded ? 1 : 0), controlWidget, alignment: TextFlag.LeftTop );
		}
		else
		{
			gridLayout.AddCell( 2, 1, controlWidget, xSpan: 2, alignment: TextFlag.LeftTop );
		}

		gridLayout.SetColumnStretch( 0, 0, 0, 1 );
		gridLayout.SetMinimumColumnWidth( 0, 0 );
		gridLayout.SetMinimumColumnWidth( 1, 0 );
		gridLayout.SetMinimumColumnWidth( 2, 140 );
	}
}

file class PropertySheetPropertyLabel : PropertySheetLabel
{
	private SerializedProperty Property { get; }
	private Drag _drag;

	public PropertySheetPropertyLabel( SerializedProperty property )
	{
		Property = property;
		Text = Property.DisplayName;

		IsDraggable = false;
	}

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

		_drag = new Drag( this )
		{
			Data = { Object = Property, Text = Property.As.String }
		};

		_drag.Execute();
	}

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

		Paint.Pen = Theme.ControlText.WithAlpha( Paint.HasMouseOver ? 1.0f : 0.6f );
		Paint.DrawText( LocalRect.Shrink( 8, 4 ), Property.DisplayName, TextFlag.LeftTop );

		if ( !IsDraggable ) return;

		var isDragging = _drag.IsValid();
		if ( isDragging )
		{
			Paint.ClearPen();
			Paint.SetBrush( Theme.Pink.WithAlpha( 0.3f ) );
			Paint.DrawRect( ContentRect, 3f );
		}
	}
}