using Newtonsoft.Json;
namespace IFSRenderer.Shared;
/// <summary>
/// User settings, stored as JSON under the XDG config directory.
/// </summary>
/// <remarks>
/// The WPF app uses <c>Properties.Settings</c>, which is Windows user-config and has no
/// portable equivalent. The settings themselves are a flat key/value set, so a plain JSON
/// file in the platform's own config location is the natural replacement.
/// </remarks>
public sealed class AppSettings
{
public List<string> RecentFiles { get; set; } = [];
// Authoring - stamped into saved fractals and exported image metadata.
public string AuthorName { get; set; } = "";
public string AuthorLink { get; set; } = "";
// Interaction
public bool InvertAxisY { get; set; }
public double Sensitivity { get; set; } = 1.0;
// Renderer tuning
public int TargetFramerate { get; set; } = 60;
public int WorkgroupCount { get; set; } = 256;
/// <summary>Path to ffmpeg. Empty means "use whatever is on PATH".</summary>
public string FfmpegPath { get; set; } = "";
public bool ShowWelcomeOnStartup { get; set; } = true;
/// <summary>Use a plain white palette for blank params instead of random colours.</summary>
public bool UseWhiteForBlankParams { get; set; }
// --- export options (Settings > Export options) ---
/// <summary>Write title, authors, copyright, date and keywords into exported images.</summary>
public bool SaveMetadata { get; set; } = true;
/// <summary>Embed the fractal params in the image, so a PNG can be reopened as a document.</summary>
public bool IncludeParamsInMetadata { get; set; } = true;
// --- animation export (Settings > Animations) ---
/// <summary>Export frames as 32-bit exr rather than 8-bit png.</summary>
public bool IsRawFrameExportEnabled { get; set; }
/// <summary>Run ffmpeg over the frames once they have rendered.</summary>
public bool IsExportVideoFileEnabled { get; set; }
/// <summary>Extra ffmpeg arguments; input, output, fps and audio are supplied by the app.</summary>
public string FfmpegArgs { get; set; } = "";
// --- performance ---
/// <summary>
/// Drop the display framerate to give the iteration loop more GPU. On by default, as in
/// the canonical settings window.
/// </summary>
public bool PerceptuallyUniformUpdates { get; set; } = true;
[JsonIgnore]
public static string Directory =>
Path.Combine(
Environment.GetEnvironmentVariable("XDG_CONFIG_HOME")
?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config"),
"ifsrenderer");
[JsonIgnore]
public static string FilePath => Path.Combine(Directory, "settings.json");
private const int MaxRecents = 10;
public static AppSettings Load()
{
try
{
if (File.Exists(FilePath))
return JsonConvert.DeserializeObject<AppSettings>(File.ReadAllText(FilePath)) ?? new AppSettings();
}
catch
{
// A corrupt settings file must never stop the app starting.
}
return new AppSettings();
}
public void Save()
{
try
{
System.IO.Directory.CreateDirectory(Directory);
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this, Formatting.Indented));
}
catch
{
// Losing settings is not worth interrupting the user for.
}
}
public void AddRecent(string path)
{
if (string.IsNullOrWhiteSpace(path))
return;
RecentFiles.RemoveAll(p => string.Equals(p, path, StringComparison.Ordinal));
RecentFiles.Insert(0, path);
if (RecentFiles.Count > MaxRecents)
RecentFiles.RemoveRange(MaxRecents, RecentFiles.Count - MaxRecents);
Save();
}
}