From c7578cc5a763191b5c7ecec78c3b9aa1157eadca Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Wed, 15 Apr 2026 14:15:10 +0800 Subject: [PATCH] feat: add editor setting --- .../BallanceTasEditor.csproj | 2 +- .../Frontend/Models/EditorSetting.cs | 67 +++++++ .../Frontend/Models/TasFile.cs | 8 +- .../Frontend/Models/TasSequenceKind.cs | 25 --- .../Frontend/Shared/EditorConfiguration.cs | 185 ++++++++++++++++++ .../Frontend/Shared/Enums.cs | 106 ++++++++++ .../Frontend/ViewModels/MainWindow.cs | 4 +- 7 files changed, 365 insertions(+), 32 deletions(-) create mode 100644 BallanceTasEditor/BallanceTasEditor/Frontend/Models/EditorSetting.cs delete mode 100644 BallanceTasEditor/BallanceTasEditor/Frontend/Models/TasSequenceKind.cs create mode 100644 BallanceTasEditor/BallanceTasEditor/Frontend/Shared/EditorConfiguration.cs create mode 100644 BallanceTasEditor/BallanceTasEditor/Frontend/Shared/Enums.cs diff --git a/BallanceTasEditor/BallanceTasEditor/BallanceTasEditor.csproj b/BallanceTasEditor/BallanceTasEditor/BallanceTasEditor.csproj index be52488..3f5051e 100644 --- a/BallanceTasEditor/BallanceTasEditor/BallanceTasEditor.csproj +++ b/BallanceTasEditor/BallanceTasEditor/BallanceTasEditor.csproj @@ -16,8 +16,8 @@ + - diff --git a/BallanceTasEditor/BallanceTasEditor/Frontend/Models/EditorSetting.cs b/BallanceTasEditor/BallanceTasEditor/Frontend/Models/EditorSetting.cs new file mode 100644 index 0000000..1f7011d --- /dev/null +++ b/BallanceTasEditor/BallanceTasEditor/Frontend/Models/EditorSetting.cs @@ -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(); + } + } + +} diff --git a/BallanceTasEditor/BallanceTasEditor/Frontend/Models/TasFile.cs b/BallanceTasEditor/BallanceTasEditor/Frontend/Models/TasFile.cs index 076528d..730e421 100644 --- a/BallanceTasEditor/BallanceTasEditor/Frontend/Models/TasFile.cs +++ b/BallanceTasEditor/BallanceTasEditor/Frontend/Models/TasFile.cs @@ -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 diff --git a/BallanceTasEditor/BallanceTasEditor/Frontend/Models/TasSequenceKind.cs b/BallanceTasEditor/BallanceTasEditor/Frontend/Models/TasSequenceKind.cs deleted file mode 100644 index 2618773..0000000 --- a/BallanceTasEditor/BallanceTasEditor/Frontend/Models/TasSequenceKind.cs +++ /dev/null @@ -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(), - }; - } - } - -} diff --git a/BallanceTasEditor/BallanceTasEditor/Frontend/Shared/EditorConfiguration.cs b/BallanceTasEditor/BallanceTasEditor/Frontend/Shared/EditorConfiguration.cs new file mode 100644 index 0000000..c304eb0 --- /dev/null +++ b/BallanceTasEditor/BallanceTasEditor/Frontend/Shared/EditorConfiguration.cs @@ -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(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 { + 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 { + 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 { + 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 + + } +} diff --git a/BallanceTasEditor/BallanceTasEditor/Frontend/Shared/Enums.cs b/BallanceTasEditor/BallanceTasEditor/Frontend/Shared/Enums.cs new file mode 100644 index 0000000..cd23e03 --- /dev/null +++ b/BallanceTasEditor/BallanceTasEditor/Frontend/Shared/Enums.cs @@ -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(), + }; + } + } + + +} diff --git a/BallanceTasEditor/BallanceTasEditor/Frontend/ViewModels/MainWindow.cs b/BallanceTasEditor/BallanceTasEditor/Frontend/ViewModels/MainWindow.cs index e9e5f54..d672f0d 100644 --- a/BallanceTasEditor/BallanceTasEditor/Frontend/ViewModels/MainWindow.cs +++ b/BallanceTasEditor/BallanceTasEditor/Frontend/ViewModels/MainWindow.cs @@ -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;