Editor/SuperShotDocument.cs

Editor-side data model for the SuperShot photo document and its layer/preset types. Defines enums for layer kind and blend mode, classes for filter presets, text/image/adjustment layer settings, SuperShotLayer with layout and sizing helpers, and SuperShotDocument with cloning and basic properties.

File Access
using System;
using System.Collections.Generic;
using Sandbox;

namespace Editor.SuperShot;

public enum SuperShotLayerKind
{
	[Title( "Text" )] Text,
	[Title( "Image" )] Image,
	[Title( "Adjustment" )] Adjustment
}

public enum SuperShotBlendMode
{
	[Title( "Normal" )] Normal,
	[Title( "Multiply" )] Multiply,
	[Title( "Screen" )] Screen,
	[Title( "Additive" )] Additive
}

public sealed class FilterPreset
{
	public string Id { get; set; } = Guid.NewGuid().ToString( "N" );
	public string Name { get; set; } = "New Filter";
	public bool BuiltIn { get; set; }

	[Property, Title( "Use For Capture" )]
	public bool UseForCapture { get; set; } = true;

	[Property, Title( "Use For Game Camera" )]
	public bool UseForGameCamera { get; set; } = true;

	public EditSettings PhotoEditSettings { get; set; } = new();
	public PostFxSettings PostFx { get; set; } = new();

	// Legacy settings files used `Edit`; keep it as a migration shim.
	public EditSettings Edit
	{
		get => PhotoEditSettings;
		set => PhotoEditSettings = value ?? new EditSettings();
	}

	public FilterPreset Clone()
	{
		return new FilterPreset
		{
			Id = Id,
			Name = Name,
			BuiltIn = BuiltIn,
			UseForCapture = UseForCapture,
			UseForGameCamera = UseForGameCamera,
			PhotoEditSettings = PhotoEditSettings?.Clone() ?? new EditSettings(),
			PostFx = PostFx?.Clone() ?? new PostFxSettings()
		};
	}
}

public sealed class TextLayerSettings
{
	[Property, Title( "Text" ), TextArea]
	public string Text { get; set; } = "Text";

	// Legacy pixel font size. The on-canvas box now drives glyph size, so this is hidden but kept for save compatibility.
	public float Size { get; set; } = 64f;

	[Property, Title( "Color" )]
	public Color Color { get; set; } = Color.White;

	[Property, Title( "Anchor" )]
	public ShotAnchor Anchor { get; set; } = ShotAnchor.Center;

	[Property, Title( "Shadow" )]
	public bool Shadow { get; set; } = true;

	[Property, Title( "Shadow Color" ), ShowIf( nameof( Shadow ), true )]
	public Color ShadowColor { get; set; } = Color.Black.WithAlpha( 0.65f );

	[Property, Title( "Shadow Offset" ), ShowIf( nameof( Shadow ), true )]
	public Vector2 ShadowOffset { get; set; } = new( 4f, 4f );
}

public sealed class ImageLayerSettings
{
	[Property, Title( "Image Path" )]
	public string Path { get; set; } = "";
}

public sealed class AdjustmentLayerSettings
{
	[Property, Title( "Adjustments" )]
	public EditSettings Edit { get; set; } = new();
}

public sealed class SuperShotLayer
{
	public string Id { get; set; } = Guid.NewGuid().ToString( "N" );

	[Property, Title( "Name" )]
	public string Name { get; set; } = "Layer";

	public SuperShotLayerKind Kind { get; set; } = SuperShotLayerKind.Text;

	[Property, Title( "Visible" )]
	public bool Visible { get; set; } = true;

	[Property, Title( "Opacity" ), Range( 0f, 1f )]
	public float Opacity { get; set; } = 1f;

	[Property, Title( "Blend Mode" )]
	public SuperShotBlendMode BlendMode { get; set; } = SuperShotBlendMode.Normal;

	[Property, Title( "Position" )]
	public Vector2 Position { get; set; } = new( 0.5f, 0.5f );

	[Property, Title( "Box Size" )]
	public Vector2 Size { get; set; } = new( 0.55f, 0.22f );

	// Legacy uniform scale (pre box-transform). Kept for save compatibility; no longer used for rendering.
	public float Scale { get; set; } = 1f;

	public TextLayerSettings Text { get; set; } = new();
	public ImageLayerSettings Image { get; set; } = new();
	public AdjustmentLayerSettings Adjustment { get; set; } = new();

	public bool HasVisualBox => Kind is SuperShotLayerKind.Text or SuperShotLayerKind.Image;

	public Rect PixelRect( int canvasWidth, int canvasHeight )
	{
		float bw = MathX.Clamp( Size.x, 0.01f, 4f ) * canvasWidth;
		float bh = MathX.Clamp( Size.y, 0.01f, 4f ) * canvasHeight;
		float cx = Position.x * canvasWidth;
		float cy = Position.y * canvasHeight;
		return new Rect( cx - bw * 0.5f, cy - bh * 0.5f, bw, bh );
	}

	public float FontPixels( int canvasHeight, int lineCount )
	{
		lineCount = Math.Max( 1, lineCount );
		float boxH = MathX.Clamp( Size.y, 0.01f, 4f ) * canvasHeight;
		return MathF.Max( 6f, (boxH / lineCount) * 0.78f );
	}

	public SuperShotLayer Clone( bool newIdentity = false )
	{
		return new SuperShotLayer
		{
			Id = newIdentity ? Guid.NewGuid().ToString( "N" ) : Id,
			Name = newIdentity ? $"{Name} Copy" : Name,
			Kind = Kind,
			Visible = Visible,
			Opacity = Opacity,
			BlendMode = BlendMode,
			Position = Position,
			Size = Size,
			Scale = Scale,
			Text = new TextLayerSettings
			{
				Text = Text?.Text ?? "",
				Size = Text?.Size ?? 64f,
				Color = Text?.Color ?? Color.White,
				Anchor = Text?.Anchor ?? ShotAnchor.Center,
				Shadow = Text?.Shadow ?? true,
				ShadowColor = Text?.ShadowColor ?? Color.Black.WithAlpha( 0.65f ),
				ShadowOffset = Text?.ShadowOffset ?? new Vector2( 4f, 4f )
			},
			Image = new ImageLayerSettings { Path = Image?.Path ?? "" },
			Adjustment = new AdjustmentLayerSettings { Edit = Adjustment?.Edit?.Clone() ?? new EditSettings() }
		};
	}
}

public sealed class SuperShotDocument
{
	public int Version { get; set; } = 1;
	public string Name { get; set; } = "Untitled Shot";
	public string SourcePath { get; set; } = "";
	public string ExportPath { get; set; } = "";
	public string SelectedFilterPresetId { get; set; } = "";
	public string SelectedLayerId { get; set; } = "";
	public Vector2Int Size { get; set; }
	public CaptureSettings Capture { get; set; } = new();
	public EditSettings BaseEdit { get; set; } = new();
	public List<SuperShotLayer> Layers { get; set; } = new();

	public SuperShotLayer SelectedLayer => Layers?.Find( x => x.Id == SelectedLayerId );

	public SuperShotDocument Clone()
	{
		var clone = new SuperShotDocument
		{
			Version = Version,
			Name = Name,
			SourcePath = SourcePath,
			ExportPath = ExportPath,
			SelectedFilterPresetId = SelectedFilterPresetId,
			SelectedLayerId = SelectedLayerId,
			Size = Size,
			Capture = Capture,
			BaseEdit = BaseEdit?.Clone() ?? new EditSettings(),
			Layers = new List<SuperShotLayer>()
		};

		if ( Layers is not null )
		{
			foreach ( var layer in Layers )
				clone.Layers.Add( layer?.Clone() );
		}

		return clone;
	}
}