1
0

feat: add editor setting

This commit is contained in:
2026-04-15 14:15:10 +08:00
parent afcc848e5b
commit c7578cc5a7
7 changed files with 365 additions and 32 deletions

View File

@@ -16,8 +16,8 @@
<ItemGroup>
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="OneOf" Version="3.0.271" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.5" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,67 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BallanceTasEditor.Frontend.Models {
public partial class EditorSetting : ObservableObject {
public EditorSetting() {
// YYC MARK:
// Due to the shitty conflict between CommunityToolkit.Mvvm and Nullable aware,
// I was forcely set this field to clear all warning.
GamePath = "";
// Initialize from singleton.
FromSingleton();
}
[ObservableProperty]
private Shared.TasSequenceKind sequenceKind;
[ObservableProperty]
private Shared.EditorLayoutKind editorLayout;
[ObservableProperty]
private Shared.EditorPasteMode pasteMode;
[ObservableProperty]
private int frameCount;
[ObservableProperty]
private string gamePath;
[MemberNotNull(nameof(SequenceKind))]
[MemberNotNull(nameof(EditorLayout))]
[MemberNotNull(nameof(PasteMode))]
[MemberNotNull(nameof(FrameCount))]
[MemberNotNull(nameof(GamePath))]
private void FromSingleton() {
var singleton = Shared.EditorConfiguration.Instance;
SequenceKind = singleton.SequenceKind;
EditorLayout = singleton.EditorLayout;
PasteMode = singleton.PasteMode;
FrameCount = singleton.FrameCount;
GamePath = singleton.GamePath;
}
private void ToSingleton() {
var singleton = Shared.EditorConfiguration.Instance;
singleton.SequenceKind = SequenceKind;
singleton.EditorLayout = EditorLayout;
singleton.PasteMode = PasteMode;
singleton.FrameCount = FrameCount;
singleton.GamePath = GamePath;
}
public void Save() {
ToSingleton();
Shared.EditorConfiguration.Instance.Save();
}
}
}

View File

@@ -30,28 +30,28 @@ namespace BallanceTasEditor.Frontend.Models {
get => FileBody is null;
}
public void NewFile(TasSequenceKind kind, int count, uint fps) {
public void NewFile(Shared.TasSequenceKind kind, int count, uint fps) {
// Check status
if (IsFileLoaded) {
throw new InvalidOperationException();
}
// Initialize sequence
var seq = TasSequenceKindHelper.CreateSequenceByKind(kind);
var seq = Shared.TasSequenceKindHelper.CreateSequenceByKind(kind);
// Initialize items
Backend.TasStorage.Init(seq, count, fps);
// Set members
FileBody = seq;
}
public void LoadFile(TasSequenceKind kind, string path) {
public void LoadFile(Shared.TasSequenceKind kind, string path) {
// Check status
if (IsFileLoaded) {
throw new InvalidOperationException();
}
// Initialize sequence
var seq = TasSequenceKindHelper.CreateSequenceByKind(kind);
var seq = Shared.TasSequenceKindHelper.CreateSequenceByKind(kind);
// Load into sequence
Backend.TasStorage.Load(path, seq);
// Set members

View File

@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace BallanceTasEditor.Frontend.Models {
public enum TasSequenceKind {
Array,
DoubleLinkedList
}
public static class TasSequenceKindHelper {
public static Backend.ITasSequence CreateSequenceByKind(TasSequenceKind kind) {
return kind switch {
TasSequenceKind.Array => new Backend.ListTasSequence(),
TasSequenceKind.DoubleLinkedList => new Backend.LegacyTasSequence(),
_ => throw new UnreachableException(),
};
}
}
}

View File

@@ -0,0 +1,185 @@
using BallanceTasEditor.Frontend.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BallanceTasEditor.Frontend.Shared {
public class EditorConfiguration {
public static readonly EditorConfiguration Instance = new EditorConfiguration();
private EditorConfiguration() {
Init();
}
public TasSequenceKind SequenceKind { get; set; }
public EditorLayoutKind EditorLayout { get; set; }
public EditorPasteMode PasteMode { get; set; }
public int FrameCount { get; set; }
public string GamePath { get; set; }
[MemberNotNull(nameof(SequenceKind))]
[MemberNotNull(nameof(EditorLayout))]
[MemberNotNull(nameof(PasteMode))]
[MemberNotNull(nameof(FrameCount))]
[MemberNotNull(nameof(GamePath))]
private void FromRaw(RawEditorConfiguration raw) {
SequenceKind = raw.SequenceKind;
EditorLayout = raw.EditorLayout;
PasteMode = raw.PasteMode;
FrameCount = raw.FrameCount;
GamePath = raw.GamePath;
}
private RawEditorConfiguration ToRaw() {
return new RawEditorConfiguration {
SequenceKind = SequenceKind,
EditorLayout = EditorLayout,
PasteMode = PasteMode,
FrameCount = FrameCount,
GamePath = GamePath
};
}
private string GetSettingFilePath() {
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string selfAppDataPath = Path.Combine(appDataPath, "BallanceTasEditor");
return Path.Combine(selfAppDataPath, "BallanceTasEditor.cfg");
}
private bool Load() {
try {
using (var fs = new StreamReader(GetSettingFilePath(), Encoding.UTF8)) {
// Read semester object.
var raw = JsonConvert.DeserializeObject<RawEditorConfiguration>(fs.ReadToEnd()).Unwrap();
// Apply to self.
FromRaw(raw);
}
return true;
} catch (Exception) {
return false;
}
}
[MemberNotNull(nameof(SequenceKind))]
[MemberNotNull(nameof(EditorLayout))]
[MemberNotNull(nameof(PasteMode))]
[MemberNotNull(nameof(FrameCount))]
[MemberNotNull(nameof(GamePath))]
private void Init() {
var defaultRaw = new RawEditorConfiguration();
FromRaw(defaultRaw);
}
public void LoadOrInit() {
if (!Load()) {
Init();
}
}
public void Save() {
try {
// Check file path directory.
var settingFilePath = GetSettingFilePath();
var settingFileDirectory = Path.GetDirectoryName(settingFilePath).Unwrap();
if (!Directory.Exists(settingFileDirectory)) {
Directory.CreateDirectory(settingFileDirectory);
}
// Write it.
using (var fs = new StreamWriter(GetSettingFilePath(), false, Encoding.UTF8)) {
fs.Write(JsonConvert.SerializeObject(ToRaw()));
}
} catch (Exception) {
// Do nothing.
}
}
private class RawEditorConfiguration {
[JsonProperty("sequence_kind", Required = Required.Always)]
[JsonConverter(typeof(TasSequenceKindConverter))]
public TasSequenceKind SequenceKind { get; set; } = TasSequenceKind.Array;
[JsonProperty("editor_layout", Required = Required.Always)]
[JsonConverter(typeof(EditorLayoutKindConverter))]
public EditorLayoutKind EditorLayout { get; set; } = EditorLayoutKind.Vertical;
[JsonProperty("paste_mode", Required = Required.Always)]
[JsonConverter(typeof(EditorPasteModeConverter))]
public EditorPasteMode PasteMode { get; set; } = EditorPasteMode.Insert;
[JsonProperty("frame_count", Required = Required.Always)]
public int FrameCount { get; set; } = 20;
[JsonProperty("game_path", Required = Required.Always)]
public string GamePath { get; set; } = "";
}
#region Custom JSON Converter
private class TasSequenceKindConverter : JsonConverter<TasSequenceKind> {
public override TasSequenceKind ReadJson(JsonReader reader, Type objectType, TasSequenceKind existingValue, bool hasExistingValue, JsonSerializer serializer) {
if (reader.TokenType == JsonToken.String) {
var value = (reader.Value as string).Unwrap();
if (TasSequenceKindHelper.TryParse(value, out var kind)) {
return kind;
} else {
throw new JsonSerializationException($"given string can not be parsed as TasSequenceKind: {value}");
}
} else {
throw new JsonSerializationException($"expect a integer but got {reader.TokenType}");
}
}
public override void WriteJson(JsonWriter writer, TasSequenceKind value, JsonSerializer serializer) {
writer.WriteValue(TasSequenceKindHelper.ToString(value));
}
}
private class EditorLayoutKindConverter : JsonConverter<EditorLayoutKind> {
public override EditorLayoutKind ReadJson(JsonReader reader, Type objectType, EditorLayoutKind existingValue, bool hasExistingValue, JsonSerializer serializer) {
if (reader.TokenType == JsonToken.String) {
var value = (reader.Value as string).Unwrap();
if (EditorLayoutKindHelper.TryParse(value, out var kind)) {
return kind;
} else {
throw new JsonSerializationException($"given string can not be parsed as EditorLayoutKind: {value}");
}
} else {
throw new JsonSerializationException($"expect a integer but got {reader.TokenType}");
}
}
public override void WriteJson(JsonWriter writer, EditorLayoutKind value, JsonSerializer serializer) {
writer.WriteValue(EditorLayoutKindHelper.ToString(value));
}
}
private class EditorPasteModeConverter : JsonConverter<EditorPasteMode> {
public override EditorPasteMode ReadJson(JsonReader reader, Type objectType, EditorPasteMode existingValue, bool hasExistingValue, JsonSerializer serializer) {
if (reader.TokenType == JsonToken.String) {
var value = (reader.Value as string).Unwrap();
if (EditorPasteModeHelper.TryParse(value, out var kind)) {
return kind;
} else {
throw new JsonSerializationException($"given string can not be parsed as EditorPasteMode: {value}");
}
} else {
throw new JsonSerializationException($"expect a integer but got {reader.TokenType}");
}
}
public override void WriteJson(JsonWriter writer, EditorPasteMode value, JsonSerializer serializer) {
writer.WriteValue(EditorPasteModeHelper.ToString(value));
}
}
#endregion
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BallanceTasEditor.Frontend.Shared {
public enum TasSequenceKind {
Array,
DoubleLinkedList
}
public static class TasSequenceKindHelper {
public static Backend.ITasSequence CreateSequenceByKind(TasSequenceKind kind) {
return kind switch {
TasSequenceKind.Array => new Backend.ListTasSequence(),
TasSequenceKind.DoubleLinkedList => new Backend.LegacyTasSequence(),
_ => throw new UnreachableException(),
};
}
public static bool TryParse(string s, out TasSequenceKind value) {
switch (s) {
case "array":
value = TasSequenceKind.Array;
return true;
case "double_linked_list":
value = TasSequenceKind.DoubleLinkedList;
return true;
default:
value = default;
return false;
}
}
public static string ToString(TasSequenceKind kind) {
return kind switch {
TasSequenceKind.Array => "array",
TasSequenceKind.DoubleLinkedList => "double_linked_list",
_ => throw new UnreachableException(),
};
}
}
public enum EditorLayoutKind {
Vertical,
Horizontal
}
public static class EditorLayoutKindHelper {
public static bool TryParse(string s, out EditorLayoutKind value) {
switch (s) {
case "vertical":
value = EditorLayoutKind.Vertical;
return true;
case "horizontal":
value = EditorLayoutKind.Horizontal;
return true;
default:
value = default;
return false;
}
}
public static string ToString(EditorLayoutKind kind) {
return kind switch {
EditorLayoutKind.Vertical => "vertical",
EditorLayoutKind.Horizontal => "horizontal",
_ => throw new UnreachableException(),
};
}
}
public enum EditorPasteMode {
Insert,
Override
}
public static class EditorPasteModeHelper {
public static bool TryParse(string s, out EditorPasteMode value) {
switch (s) {
case "insert":
value = EditorPasteMode.Insert;
return true;
case "override":
value = EditorPasteMode.Override;
return true;
default:
value = default;
return false;
}
}
public static string ToString(EditorPasteMode mode) {
return mode switch {
EditorPasteMode.Insert => "insert",
EditorPasteMode.Override => "override",
_ => throw new UnreachableException(),
};
}
}
}

View File

@@ -57,7 +57,7 @@ namespace BallanceTasEditor.Frontend.ViewModels {
if (dialog is null) return;
// Create new file
TasFile.NewFile(Models.TasSequenceKind.Array, dialog.Count, dialog.Fps);
TasFile.NewFile(Shared.TasSequenceKind.Array, dialog.Count, dialog.Fps);
// Set members
TasFilePath = null;
// Send notification
@@ -76,7 +76,7 @@ namespace BallanceTasEditor.Frontend.ViewModels {
// Load file
try {
TasFile.LoadFile(Models.TasSequenceKind.Array, dialog.Path);
TasFile.LoadFile(Shared.TasSequenceKind.Array, dialog.Path);
} catch (Exception e) {
m_DialogService.ShowOpenFileFailedDialog(e);
return;