增加首选项对话框

This commit is contained in:
ww-rm
2025-06-13 23:12:15 +08:00
parent 125ce6fa86
commit b3010360b4
12 changed files with 565 additions and 30 deletions

View File

@@ -34,6 +34,7 @@ namespace SpineViewer.ViewModels.MainWindow
_explorerListViewModel = new(this);
_spineObjectListViewModel = new(this);
_sfmlRendererViewModel = new(this);
_preferenceViewModel = new(this);
}
public string Title => $"SpineViewer - v{App.Version}";
@@ -56,6 +57,9 @@ namespace SpineViewer.ViewModels.MainWindow
public ObservableCollectionWithLock<SpineObjectModel> SpineObjects => _spineObjectModels;
private readonly ObservableCollectionWithLock<SpineObjectModel> _spineObjectModels = [];
public PreferenceViewModel PreferenceViewModel => _preferenceViewModel;
private readonly PreferenceViewModel _preferenceViewModel;
/// <summary>
/// 浏览页列表 ViewModel
/// </summary>

View File

@@ -0,0 +1,191 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NLog;
using Spine.SpineWrappers;
using SpineViewer.Models;
using SpineViewer.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace SpineViewer.ViewModels.MainWindow
{
public class PreferenceViewModel : ObservableObject
{
/// <summary>
/// 文件保存路径
/// </summary>
public static readonly string PreferenceFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "preference.json");
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly MainWindowViewModel _vmMain;
public PreferenceViewModel(MainWindowViewModel vmMain)
{
_vmMain = vmMain;
}
/// <summary>
/// 显示首选项对话框
/// </summary>
public RelayCommand Cmd_ShowPreferenceDialog => _cmd_ShowPreferenceDialog ??= new(ShowPreferenceDialog_Execute);
private RelayCommand? _cmd_ShowPreferenceDialog;
private void ShowPreferenceDialog_Execute()
{
var m = Preference;
if (!DialogService.ShowPreferenceDialog(m))
return;
Preference = m;
SavePreference(m);
}
private static void SavePreference(PreferenceModel m)
{
try
{
m.Serialize(PreferenceFilePath);
}
catch (Exception ex)
{
_logger.Error("Failed to save preference to {0}, {1}", PreferenceFilePath, ex.Message);
_logger.Trace(ex.ToString());
}
}
/// <summary>
/// 保存首选项, 保存失败会有日志提示
/// </summary>
public void SavePreference() => SavePreference(Preference);
/// <summary>
/// 加载首选项, 加载失败会有日志提示
/// </summary>
public void LoadPreference()
{
if (!File.Exists(PreferenceFilePath)) return;
try
{
var m = PreferenceModel.Deserialize(PreferenceFilePath);
Preference = m;
}
catch (Exception ex)
{
_logger.Error("Failed to load preference from {0}, {1}", PreferenceFilePath, ex.Message);
_logger.Trace(ex.ToString());
}
}
/// <summary>
/// 获取参数副本或者进行设置
/// </summary>
private PreferenceModel Preference
{
get
{
return new()
{
ForcePremul = ForcePremul,
ForceNearest = ForceNearest,
ForceMipmap = ForceMipmap,
UsePma = UsePma,
DebugTexture = DebugTexture,
DebugBounds = DebugBounds,
DebugBones = DebugBones,
DebugRegions = DebugRegions,
DebugMeshHulls = DebugMeshHulls,
DebugMeshes = DebugMeshes,
DebugBoundingBoxes = DebugBoundingBoxes,
DebugPaths = DebugPaths,
DebugPoints = DebugPoints,
DebugClippings = DebugClippings
};
}
set
{
ForcePremul = value.ForcePremul;
ForceNearest = value.ForceNearest;
ForceMipmap = value.ForceMipmap;
UsePma = value.UsePma;
DebugTexture = value.DebugTexture;
DebugBounds = value.DebugBounds;
DebugBones = value.DebugBones;
DebugRegions = value.DebugRegions;
DebugMeshHulls = value.DebugMeshHulls;
DebugMeshes = value.DebugMeshes;
DebugBoundingBoxes = value.DebugBoundingBoxes;
DebugPaths = value.DebugPaths;
DebugPoints = value.DebugPoints;
DebugClippings = value.DebugClippings;
}
}
#region
public bool ForcePremul
{
get => TextureLoader.DefaultLoader.ForcePremul;
set => SetProperty(TextureLoader.DefaultLoader.ForcePremul, value, v => TextureLoader.DefaultLoader.ForcePremul = v);
}
public bool ForceNearest
{
get => TextureLoader.DefaultLoader.ForceNearest;
set => SetProperty(TextureLoader.DefaultLoader.ForceNearest, value, v => TextureLoader.DefaultLoader.ForceNearest = v);
}
public bool ForceMipmap
{
get => TextureLoader.DefaultLoader.ForceMipmap;
set => SetProperty(TextureLoader.DefaultLoader.ForceMipmap, value, v => TextureLoader.DefaultLoader.ForceMipmap = v);
}
#endregion
// TODO: 是否自动记忆模型参数
#region
public bool UsePma { get => _usePma; set => SetProperty(ref _usePma, value); }
private bool _usePma;
public bool DebugTexture { get => _debugTexture; set => SetProperty(ref _debugTexture, value); }
private bool _debugTexture = true;
public bool DebugBounds { get => _debugBounds; set => SetProperty(ref _debugBounds, value); }
private bool _debugBounds;
public bool DebugBones { get => _debugBones; set => SetProperty(ref _debugBones, value); }
private bool _debugBones;
public bool DebugRegions { get => _debugRegions; set => SetProperty(ref _debugRegions, value); }
private bool _debugRegions;
public bool DebugMeshHulls { get => _debugMeshHulls; set => SetProperty(ref _debugMeshHulls, value); }
private bool _debugMeshHulls;
public bool DebugMeshes { get => _debugMeshes; set => SetProperty(ref _debugMeshes, value); }
private bool _debugMeshes;
public bool DebugBoundingBoxes { get => _debugBoundingBoxes; set => SetProperty(ref _debugBoundingBoxes, value); }
private bool _debugBoundingBoxes;
public bool DebugPaths { get => _debugPaths; set => SetProperty(ref _debugPaths, value); }
private bool _debugPaths;
public bool DebugPoints { get => _debugPoints; set => SetProperty(ref _debugPoints, value); }
private bool _debugPoints;
public bool DebugClippings { get => _debugClippings; set => SetProperty(ref _debugClippings, value); }
private bool _debugClippings;
#endregion
}
}

View File

@@ -121,7 +121,7 @@ namespace SpineViewer.ViewModels.MainWindow
lock (_spineObjectModels.Lock)
{
// XXX: 这里必须要浅拷贝一次, 不能直接对会被修改的绑定数据 args 进行 foreach 遍历
// NOTE: 这里必须要浅拷贝一次, 不能直接对会被修改的绑定数据 args 进行 foreach 遍历
foreach (var sp in args.Cast<SpineObjectModel>().ToArray())
{
_spineObjectModels.Remove(sp);
@@ -249,6 +249,41 @@ namespace SpineViewer.ViewModels.MainWindow
return true;
}
/// <summary>
/// 安全地在末尾添加一个模型, 发生错误会输出日志
/// </summary>
/// <returns>是否添加成功</returns>
public bool AddSpineObject(string skelPath, string? atlasPath = null)
{
try
{
// TODO: 判断是否记忆参数
var pre = _vmMain.PreferenceViewModel;
var sp = new SpineObjectModel(skelPath, atlasPath)
{
UsePma = pre.UsePma,
DebugTexture = pre.DebugTexture,
DebugBounds = pre.DebugBounds,
DebugRegions = pre.DebugRegions,
DebugMeshHulls = pre.DebugMeshHulls,
DebugMeshes = pre.DebugMeshes,
DebugBoundingBoxes = pre.DebugBoundingBoxes,
DebugPaths = pre.DebugPaths,
DebugPoints = pre.DebugPoints,
DebugClippings = pre.DebugClippings
};
lock (_spineObjectModels.Lock) _spineObjectModels.Add(sp);
return true;
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message);
}
return false;
}
/// <summary>
/// 从路径列表添加对象
/// </summary>
@@ -289,17 +324,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
else if (validPaths.Count > 0)
{
var skelPath = validPaths[0];
try
{
var sp = new SpineObjectModel(skelPath);
lock (_spineObjectModels.Lock) _spineObjectModels.Add(sp);
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message);
}
AddSpineObject(validPaths[0]);
_logger.LogCurrentProcessMemoryUsage();
}
}
@@ -326,18 +351,10 @@ namespace SpineViewer.ViewModels.MainWindow
var skelPath = paths[i];
reporter.ProgressText = $"[{i}/{totalCount}] {skelPath}";
try
{
var sp = new SpineObjectModel(skelPath);
lock (_spineObjectModels.Lock) _spineObjectModels.Add(sp);
if (AddSpineObject(skelPath))
success++;
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message);
else
error++;
}
reporter.Done = i + 1;
reporter.ProgressText = $"[{i + 1}/{totalCount}] {skelPath}";