Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Definitions/ObjectModels/Graphics/ImageTable.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System.Text.Json.Serialization;

namespace Definitions.ObjectModels.Graphics;

public record ImageTableGroup(string Name, List<GraphicsElement> GraphicsElements);

public class ImageTable : IHasGraphicsElements
{
[JsonIgnore]
public PaletteMap PaletteMap
{
get;
Expand Down
35 changes: 9 additions & 26 deletions Definitions/ObjectModels/LocoObject.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,16 @@
using Definitions.ObjectModels.Graphics;
using Definitions.ObjectModels.Types;
using System.Text.Json.Serialization;

namespace Definitions.ObjectModels;

public class LocoObject
namespace Definitions.ObjectModels
{
public LocoObject(ObjectType objectType, ILocoStruct obj, StringTable stringTable, ImageTable? imageTable = null)
[JsonConverter(typeof(Serialization.LocoObjectJsonConverter))]
public class LocoObject(ObjectType objectType, ILocoStruct obj, StringTable stringTable, ImageTable? imageTable = null)
{
ObjectType = objectType;
Object = obj;
StringTable = stringTable;
ImageTable = imageTable;
}
public ObjectType ObjectType { get; init; } = objectType;
public ILocoStruct Object { get; set; } = obj;
public StringTable StringTable { get; set; } = stringTable;

public ObjectType ObjectType { get; init; }
public ILocoStruct Object { get; set; }
public StringTable StringTable { get; set; }

public ImageTable? ImageTable { get; set; }
public ImageTable? ImageTable { get; set; } = imageTable;
}
}

//public class LocoObjectWithGraphics : LocoObject
//{
// public LocoObjectWithGraphics(ObjectType objectType, ILocoStruct obj, StringTable stringTable, ImageTable imageTable)
// : base(objectType, obj, stringTable)
// {
// ImageTable = imageTable;
// }

// public ImageTable ImageTable { get; set; }
//}

106 changes: 106 additions & 0 deletions Definitions/ObjectModels/Serialization/LocoObjectJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using Definitions.ObjectModels.Graphics;
using Definitions.ObjectModels.Types;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Definitions.ObjectModels.Serialization
{
public sealed class LocoObjectJsonConverter : JsonConverter<LocoObject>
{
private const string ObjectTypeProp = nameof(LocoObject.ObjectType);
private const string ObjectProp = nameof(LocoObject.Object);
private const string StringTableProp = nameof(LocoObject.StringTable);
private const string ImageTableProp = nameof(LocoObject.ImageTable);
private const string ObjectClrTypeProp = "ObjectClrType";

public override LocoObject? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var doc = JsonDocument.ParseValue(ref reader);
var root = doc.RootElement;

// Read required fields
if (!root.TryGetProperty(ObjectTypeProp, out var objTypeEl))
{
throw new JsonException($"Missing required property '{ObjectTypeProp}'.");
}

if (!root.TryGetProperty(ObjectProp, out var objEl))
{
throw new JsonException($"Missing required property '{ObjectProp}'.");
}

if (!root.TryGetProperty(StringTableProp, out var stringTableEl))
{
throw new JsonException($"Missing required property '{StringTableProp}'.");
}

// Determine the concrete ILocoStruct type
if (!root.TryGetProperty(ObjectClrTypeProp, out var clrTypeEl))
{
throw new JsonException($"Missing required property '{ObjectClrTypeProp}' needed to deserialize '{ObjectProp}'.");
}

var objectType = objTypeEl.Deserialize<ObjectType>(options);
var stringTable = stringTableEl.Deserialize<StringTable>(options)
?? throw new JsonException($"Could not deserialize '{StringTableProp}'.");

ImageTable? imageTable = null;
if (root.TryGetProperty(ImageTableProp, out var imageTableEl) && imageTableEl.ValueKind != JsonValueKind.Null)
{
imageTable = imageTableEl.Deserialize<ImageTable>(options);
}

var clrTypeName = clrTypeEl.GetString();
if (string.IsNullOrWhiteSpace(clrTypeName))
{
throw new JsonException($"'{ObjectClrTypeProp}' was null or empty.");
}

var concreteType = Type.GetType(clrTypeName, throwOnError: true)
?? throw new JsonException($"Could not resolve type '{clrTypeName}'.");

var locoStruct = (ILocoStruct?)objEl.Deserialize(concreteType, options)
?? throw new JsonException($"Could not deserialize '{ObjectProp}' as '{concreteType}'.");

return new LocoObject(objectType, locoStruct, stringTable, imageTable);
}

public override void Write(Utf8JsonWriter writer, LocoObject value, JsonSerializerOptions options)
{
if (value is null)
{
writer.WriteNullValue();
return;
}

if (value.Object is null)
{
throw new JsonException("LocoObject.Object was null during serialization.");
}

writer.WriteStartObject();

// ObjectType
writer.WritePropertyName(ObjectTypeProp);
JsonSerializer.Serialize(writer, value.ObjectType, options);

// Underlying CLR type of the ILocoStruct (prints the correct underlying type)
writer.WriteString(ObjectClrTypeProp, value.Object.GetType().AssemblyQualifiedName);

// Object (serialize using concrete runtime type)
writer.WritePropertyName(ObjectProp);
JsonSerializer.Serialize(writer, value.Object, value.Object.GetType(), options);

// StringTable
writer.WritePropertyName(StringTableProp);
JsonSerializer.Serialize(writer, value.StringTable, options);

// ImageTable (may be null)
writer.WritePropertyName(ImageTableProp);
JsonSerializer.Serialize(writer, value.ImageTable, options);

writer.WriteEndObject();
}
}
}

1 change: 1 addition & 0 deletions Gui/PlatformSpecific.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public static async Task<IReadOnlyList<IStorageFolder>> OpenFolderPicker()
});
}

public static readonly IReadOnlyList<FilePickerFileType> JsonFileTypes = [new("JSON Files") { Patterns = ["*.json", "*.JSON"] }];
public static readonly IReadOnlyList<FilePickerFileType> DatFileTypes = [new("Locomotion DAT Files") { Patterns = ["*.dat", "*.DAT"] }];
public static readonly IReadOnlyList<FilePickerFileType> PngFileTypes = [new("PNG Files") { Patterns = ["*.png", "*.PNG"] }];
public static readonly IReadOnlyList<FilePickerFileType> SCV5FileTypes = [new("SC5/SV5 Files") { Patterns = ["*.sc5", "*.SC5", "*.sv5", "*.SV5"] }];
Expand Down
74 changes: 68 additions & 6 deletions Gui/ViewModels/LocoTypes/BaseLocoFileViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
using MsBox.Avalonia;
using MsBox.Avalonia.Enums;
using Avalonia.Controls;
using Common.Logging;
using Dat.Data;
using Definitions.ObjectModels.Types;
using Gui.Models;
using MsBox.Avalonia;
using MsBox.Avalonia.Dto;
using MsBox.Avalonia.Enums;
using MsBox.Avalonia.Models;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Threading.Tasks;
using Definitions.ObjectModels.Types;

namespace Gui.ViewModels;

public enum SaveType { JSON, DAT }

// todo: add filename
public record SaveParameters(SaveType SaveType, SawyerEncoding? SawyerEncoding);

public abstract class BaseLocoFileViewModel : ReactiveObject, ILocoFileViewModel
{
protected BaseLocoFileViewModel(FileSystemItem currentFile, ObjectEditorModel model)
Expand All @@ -19,7 +30,7 @@ protected BaseLocoFileViewModel(FileSystemItem currentFile, ObjectEditorModel mo

ReloadCommand = ReactiveCommand.Create(Load);
SaveCommand = ReactiveCommand.CreateFromTask(SaveWrapper);
SaveAsCommand = ReactiveCommand.Create(SaveAs);
SaveAsCommand = ReactiveCommand.CreateFromTask(SaveAsWrapper);
DeleteLocalFileCommand = ReactiveCommand.CreateFromTask(DeleteWrapper);
}

Expand All @@ -36,9 +47,60 @@ protected BaseLocoFileViewModel(FileSystemItem currentFile, ObjectEditorModel mo

public abstract void Load();
public abstract void Save();
public abstract void SaveAs();
public abstract void SaveAs(SaveParameters saveParameters);
public virtual void Delete() { }

async Task SaveAsWrapper()
{
// show save wizard here, asking the user to select a save type (DAT or JSON) and if its DAT, letting them select an option for the DAT encoding

var buttons = new HashSet<string>()
{
"JSON (Experimental)",
$"DAT ({SawyerEncoding.Uncompressed})",
$"DAT ({SawyerEncoding.RunLengthSingle})",
$"DAT ({SawyerEncoding.RunLengthMulti})",
$"DAT ({SawyerEncoding.Rotate})",

};

var box = MessageBoxManager.GetMessageBoxCustom
(new MessageBoxCustomParams
{
ButtonDefinitions = buttons.Select(x => new ButtonDefinition() { Name = x }),
ContentTitle = "Save As",
ContentMessage = "Save as DAT object or JSON file?",
Icon = Icon.Question,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
CanResize = false,
//MaxWidth = 500,
MaxHeight = 800,
SizeToContent = SizeToContent.WidthAndHeight,
ShowInCenter = true,
Topmost = false,
});

var result = await box.ShowAsync();
if (!buttons.Contains(result))
{
return;
}

var type = result == "JSON (Experimental)" ? SaveType.JSON : SaveType.DAT;
SawyerEncoding? encoding = type == SaveType.DAT
? result switch
{
"DAT (Uncompressed)" => SawyerEncoding.Uncompressed,
"DAT (RunLengthSingle)" => SawyerEncoding.RunLengthSingle,
"DAT (RunLengthMulti)" => SawyerEncoding.RunLengthMulti,
"DAT (Rotate)" => SawyerEncoding.Rotate,
_ => null
}
: null;

SaveAs(new SaveParameters(type, encoding));
}

async Task SaveWrapper()
{
// note - this is the DAT file source, not the true source...
Expand All @@ -47,7 +109,7 @@ async Task SaveWrapper()
var box = MessageBoxManager.GetMessageBoxStandard("Confirm Save", $"{CurrentFile.FileName} is a vanilla Locomotion file - are you sure you want to overwrite it?", ButtonEnum.YesNo);
var result = await box.ShowAsync();

if (result != ButtonResult.Yes)
if (result == ButtonResult.Yes)
{
return;
}
Expand Down
2 changes: 1 addition & 1 deletion Gui/ViewModels/LocoTypes/G1ViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public override void Save()
SawyerStreamWriter.SaveG1(savePath, Model.G1);
}

public override void SaveAs()
public override void SaveAs(SaveParameters saveParameters)
{
if (Model.G1 == null)
{
Expand Down
2 changes: 1 addition & 1 deletion Gui/ViewModels/LocoTypes/MusicViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public override void Save()
SaveCore(savePath);
}

public override void SaveAs()
public override void SaveAs(SaveParameters saveParameters)
{
var saveFile = Task.Run(async () => await PlatformSpecific.SaveFilePicker(PlatformSpecific.DatFileTypes)).Result;
var savePath = saveFile?.Path.LocalPath;
Expand Down
Loading