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.
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;
}
}