From 66af740423dd0ff8b93e434d55f54ac1bb142e7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=AD=B1=E5=82=91?= <840465812@qq.com>
Date: Sun, 30 May 2021 22:57:53 +0800
Subject: [PATCH] =?UTF-8?q?1.=20=E6=96=B0=E5=BB=BA=20.NET=20Core=203.1=20W?=
=?UTF-8?q?PF=20=E9=A1=B9=E7=9B=AE=20ChineseChess.GUI=EF=BC=8C=E4=BD=BF?=
=?UTF-8?q?=E7=94=A8=E5=BE=AE=E8=BD=AF=E5=BB=BA=E8=AE=AE=E6=A8=A1=E6=9D=BF?=
=?UTF-8?q?=E7=94=9F=E6=88=90=202.=20=E7=A7=BB=E9=99=A4=20=E5=8E=9F?=
=?UTF-8?q?=E5=85=88=E7=9A=84ChineseChess.UI=E9=A1=B9=E7=9B=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ChineseChess.GUI.Core.csproj | 15 ++
.../Contracts/Services/IFileService.cs | 11 +
ChineseChess.GUI.Core/Services/FileService.cs | 43 ++++
ChineseChess.GUI.Core/readme.txt | 2 +
.../ChineseChess.GUI.Tests.xUnit.csproj | 35 ++++
ChineseChess.GUI.Tests.xUnit/PagesTests.cs | 98 +++++++++
.../SettingsViewModelTests.cs | 66 ++++++
ChineseChess.GUI.Tests.xUnit/Tests.cs | 13 ++
ChineseChess.GUI/App.xaml | 28 +++
ChineseChess.GUI/App.xaml.cs | 96 +++++++++
ChineseChess.GUI/ChineseChess.GUI.csproj | 38 ++++
.../Activation/IActivationHandler.cs | 11 +
.../Services/IApplicationInfoService.cs | 9 +
.../Contracts/Services/INavigationService.cs | 22 ++
.../Contracts/Services/IPageService.cs | 12 ++
.../Services/IPersistAndRestoreService.cs | 9 +
.../Contracts/Services/ISystemService.cs | 7 +
.../Services/IThemeSelectorService.cs | 15 ++
.../Contracts/ViewModels/INavigationAware.cs | 9 +
.../Contracts/Views/IShellWindow.cs | 13 ++
.../Converters/EnumToBooleanConverter.cs | 36 ++++
ChineseChess.GUI/Helpers/FrameExtensions.cs | 23 ++
ChineseChess.GUI/Models/AppConfig.cs | 11 +
ChineseChess.GUI/Models/AppTheme.cs | 9 +
.../Properties/Resources.Designer.cs | 197 ++++++++++++++++++
ChineseChess.GUI/Properties/Resources.resx | 147 +++++++++++++
.../Services/ApplicationHostService.cs | 93 +++++++++
.../Services/ApplicationInfoService.cs | 23 ++
.../Services/NavigationService.cs | 101 +++++++++
ChineseChess.GUI/Services/PageService.cs | 68 ++++++
.../Services/PersistAndRestoreService.cs | 49 +++++
ChineseChess.GUI/Services/SystemService.cs | 24 +++
.../Services/ThemeSelectorService.cs | 63 ++++++
ChineseChess.GUI/Styles/MetroWindow.xaml | 14 ++
ChineseChess.GUI/Styles/TextBlock.xaml | 54 +++++
.../Styles/Themes/HC.Dark.Blue.xaml | 40 ++++
.../Styles/Themes/HC.Light.Blue.xaml | 40 ++++
ChineseChess.GUI/Styles/_FontSizes.xaml | 14 ++
ChineseChess.GUI/Styles/_Thickness.xaml | 30 +++
.../MenuItemTemplateSelector.cs | 29 +++
ChineseChess.GUI/ViewModels/MainViewModel.cs | 14 ++
.../ViewModels/SettingsViewModel.cs | 69 ++++++
ChineseChess.GUI/ViewModels/ShellViewModel.cs | 114 ++++++++++
ChineseChess.GUI/Views/MainPage.xaml | 30 +++
ChineseChess.GUI/Views/MainPage.xaml.cs | 15 ++
ChineseChess.GUI/Views/SettingsPage.xaml | 106 ++++++++++
ChineseChess.GUI/Views/SettingsPage.xaml.cs | 15 ++
ChineseChess.GUI/Views/ShellWindow.xaml | 116 +++++++++++
ChineseChess.GUI/Views/ShellWindow.xaml.cs | 27 +++
ChineseChess.GUI/WTS.ProjectConfig.xml | 11 +
ChineseChess.GUI/app.manifest | 11 +
ChineseChess.GUI/appsettings.json | 7 +
ChineseChess.UI/App.config | 6 -
ChineseChess.UI/App.xaml | 9 -
ChineseChess.UI/App.xaml.cs | 17 --
ChineseChess.UI/ChineseChess.UI.csproj | 104 ---------
ChineseChess.UI/MainWindow.xaml | 14 --
ChineseChess.UI/MainWindow.xaml.cs | 28 ---
ChineseChess.UI/Properties/AssemblyInfo.cs | 55 -----
.../Properties/Resources.Designer.cs | 70 -------
ChineseChess.UI/Properties/Resources.resx | 117 -----------
.../Properties/Settings.Designer.cs | 29 ---
ChineseChess.UI/Properties/Settings.settings | 7 -
ChineseChess.sln | 60 +++++-
64 files changed, 2206 insertions(+), 462 deletions(-)
create mode 100644 ChineseChess.GUI.Core/ChineseChess.GUI.Core.csproj
create mode 100644 ChineseChess.GUI.Core/Contracts/Services/IFileService.cs
create mode 100644 ChineseChess.GUI.Core/Services/FileService.cs
create mode 100644 ChineseChess.GUI.Core/readme.txt
create mode 100644 ChineseChess.GUI.Tests.xUnit/ChineseChess.GUI.Tests.xUnit.csproj
create mode 100644 ChineseChess.GUI.Tests.xUnit/PagesTests.cs
create mode 100644 ChineseChess.GUI.Tests.xUnit/SettingsViewModelTests.cs
create mode 100644 ChineseChess.GUI.Tests.xUnit/Tests.cs
create mode 100644 ChineseChess.GUI/App.xaml
create mode 100644 ChineseChess.GUI/App.xaml.cs
create mode 100644 ChineseChess.GUI/ChineseChess.GUI.csproj
create mode 100644 ChineseChess.GUI/Contracts/Activation/IActivationHandler.cs
create mode 100644 ChineseChess.GUI/Contracts/Services/IApplicationInfoService.cs
create mode 100644 ChineseChess.GUI/Contracts/Services/INavigationService.cs
create mode 100644 ChineseChess.GUI/Contracts/Services/IPageService.cs
create mode 100644 ChineseChess.GUI/Contracts/Services/IPersistAndRestoreService.cs
create mode 100644 ChineseChess.GUI/Contracts/Services/ISystemService.cs
create mode 100644 ChineseChess.GUI/Contracts/Services/IThemeSelectorService.cs
create mode 100644 ChineseChess.GUI/Contracts/ViewModels/INavigationAware.cs
create mode 100644 ChineseChess.GUI/Contracts/Views/IShellWindow.cs
create mode 100644 ChineseChess.GUI/Converters/EnumToBooleanConverter.cs
create mode 100644 ChineseChess.GUI/Helpers/FrameExtensions.cs
create mode 100644 ChineseChess.GUI/Models/AppConfig.cs
create mode 100644 ChineseChess.GUI/Models/AppTheme.cs
create mode 100644 ChineseChess.GUI/Properties/Resources.Designer.cs
create mode 100644 ChineseChess.GUI/Properties/Resources.resx
create mode 100644 ChineseChess.GUI/Services/ApplicationHostService.cs
create mode 100644 ChineseChess.GUI/Services/ApplicationInfoService.cs
create mode 100644 ChineseChess.GUI/Services/NavigationService.cs
create mode 100644 ChineseChess.GUI/Services/PageService.cs
create mode 100644 ChineseChess.GUI/Services/PersistAndRestoreService.cs
create mode 100644 ChineseChess.GUI/Services/SystemService.cs
create mode 100644 ChineseChess.GUI/Services/ThemeSelectorService.cs
create mode 100644 ChineseChess.GUI/Styles/MetroWindow.xaml
create mode 100644 ChineseChess.GUI/Styles/TextBlock.xaml
create mode 100644 ChineseChess.GUI/Styles/Themes/HC.Dark.Blue.xaml
create mode 100644 ChineseChess.GUI/Styles/Themes/HC.Light.Blue.xaml
create mode 100644 ChineseChess.GUI/Styles/_FontSizes.xaml
create mode 100644 ChineseChess.GUI/Styles/_Thickness.xaml
create mode 100644 ChineseChess.GUI/TemplateSelectors/MenuItemTemplateSelector.cs
create mode 100644 ChineseChess.GUI/ViewModels/MainViewModel.cs
create mode 100644 ChineseChess.GUI/ViewModels/SettingsViewModel.cs
create mode 100644 ChineseChess.GUI/ViewModels/ShellViewModel.cs
create mode 100644 ChineseChess.GUI/Views/MainPage.xaml
create mode 100644 ChineseChess.GUI/Views/MainPage.xaml.cs
create mode 100644 ChineseChess.GUI/Views/SettingsPage.xaml
create mode 100644 ChineseChess.GUI/Views/SettingsPage.xaml.cs
create mode 100644 ChineseChess.GUI/Views/ShellWindow.xaml
create mode 100644 ChineseChess.GUI/Views/ShellWindow.xaml.cs
create mode 100644 ChineseChess.GUI/WTS.ProjectConfig.xml
create mode 100644 ChineseChess.GUI/app.manifest
create mode 100644 ChineseChess.GUI/appsettings.json
delete mode 100644 ChineseChess.UI/App.config
delete mode 100644 ChineseChess.UI/App.xaml
delete mode 100644 ChineseChess.UI/App.xaml.cs
delete mode 100644 ChineseChess.UI/ChineseChess.UI.csproj
delete mode 100644 ChineseChess.UI/MainWindow.xaml
delete mode 100644 ChineseChess.UI/MainWindow.xaml.cs
delete mode 100644 ChineseChess.UI/Properties/AssemblyInfo.cs
delete mode 100644 ChineseChess.UI/Properties/Resources.Designer.cs
delete mode 100644 ChineseChess.UI/Properties/Resources.resx
delete mode 100644 ChineseChess.UI/Properties/Settings.Designer.cs
delete mode 100644 ChineseChess.UI/Properties/Settings.settings
diff --git a/ChineseChess.GUI.Core/ChineseChess.GUI.Core.csproj b/ChineseChess.GUI.Core/ChineseChess.GUI.Core.csproj
new file mode 100644
index 0000000..8c0ef43
--- /dev/null
+++ b/ChineseChess.GUI.Core/ChineseChess.GUI.Core.csproj
@@ -0,0 +1,15 @@
+
+
+
+ netstandard2.0
+ ChineseChess.GUI.Core
+
+
+
+
+
+
+
+
+
+
diff --git a/ChineseChess.GUI.Core/Contracts/Services/IFileService.cs b/ChineseChess.GUI.Core/Contracts/Services/IFileService.cs
new file mode 100644
index 0000000..806ad86
--- /dev/null
+++ b/ChineseChess.GUI.Core/Contracts/Services/IFileService.cs
@@ -0,0 +1,11 @@
+namespace ChineseChess.GUI.Core.Contracts.Services
+{
+ public interface IFileService
+ {
+ T Read(string folderPath, string fileName);
+
+ void Save(string folderPath, string fileName, T content);
+
+ void Delete(string folderPath, string fileName);
+ }
+}
diff --git a/ChineseChess.GUI.Core/Services/FileService.cs b/ChineseChess.GUI.Core/Services/FileService.cs
new file mode 100644
index 0000000..ae23112
--- /dev/null
+++ b/ChineseChess.GUI.Core/Services/FileService.cs
@@ -0,0 +1,43 @@
+using System.IO;
+using System.Text;
+
+using ChineseChess.GUI.Core.Contracts.Services;
+
+using Newtonsoft.Json;
+
+namespace ChineseChess.GUI.Core.Services
+{
+ public class FileService : IFileService
+ {
+ public T Read(string folderPath, string fileName)
+ {
+ var path = Path.Combine(folderPath, fileName);
+ if (File.Exists(path))
+ {
+ var json = File.ReadAllText(path);
+ return JsonConvert.DeserializeObject(json);
+ }
+
+ return default;
+ }
+
+ public void Save(string folderPath, string fileName, T content)
+ {
+ if (!Directory.Exists(folderPath))
+ {
+ Directory.CreateDirectory(folderPath);
+ }
+
+ var fileContent = JsonConvert.SerializeObject(content);
+ File.WriteAllText(Path.Combine(folderPath, fileName), fileContent, Encoding.UTF8);
+ }
+
+ public void Delete(string folderPath, string fileName)
+ {
+ if (fileName != null && File.Exists(Path.Combine(folderPath, fileName)))
+ {
+ File.Delete(Path.Combine(folderPath, fileName));
+ }
+ }
+ }
+}
diff --git a/ChineseChess.GUI.Core/readme.txt b/ChineseChess.GUI.Core/readme.txt
new file mode 100644
index 0000000..dd20676
--- /dev/null
+++ b/ChineseChess.GUI.Core/readme.txt
@@ -0,0 +1,2 @@
+This core project is a .net standard project.
+It's a great place to put all your logic that is not platform dependent (e.g. model/helper classes) so they can be reused.
\ No newline at end of file
diff --git a/ChineseChess.GUI.Tests.xUnit/ChineseChess.GUI.Tests.xUnit.csproj b/ChineseChess.GUI.Tests.xUnit/ChineseChess.GUI.Tests.xUnit.csproj
new file mode 100644
index 0000000..46d3728
--- /dev/null
+++ b/ChineseChess.GUI.Tests.xUnit/ChineseChess.GUI.Tests.xUnit.csproj
@@ -0,0 +1,35 @@
+
+
+
+ netcoreapp3.1
+ false
+ uap10.0.18362
+ x64;x86;AnyCPU
+ ChineseChess.GUI.Tests.xUnit
+
+
+
+ x86
+
+
+
+ x64
+
+
+
+ AnyCPU
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChineseChess.GUI.Tests.xUnit/PagesTests.cs b/ChineseChess.GUI.Tests.xUnit/PagesTests.cs
new file mode 100644
index 0000000..f7d6507
--- /dev/null
+++ b/ChineseChess.GUI.Tests.xUnit/PagesTests.cs
@@ -0,0 +1,98 @@
+using System.IO;
+using System.Reflection;
+
+using ChineseChess.GUI.Contracts.Services;
+using ChineseChess.GUI.Core.Contracts.Services;
+using ChineseChess.GUI.Core.Services;
+using ChineseChess.GUI.Models;
+using ChineseChess.GUI.Services;
+using ChineseChess.GUI.ViewModels;
+using ChineseChess.GUI.Views;
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+using Xunit;
+
+namespace ChineseChess.GUI.Tests.XUnit
+{
+ public class PagesTests
+ {
+ private readonly IHost _host;
+
+ public PagesTests()
+ {
+ var appLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
+ _host = Host.CreateDefaultBuilder()
+ .ConfigureAppConfiguration(c => c.SetBasePath(appLocation))
+ .ConfigureServices(ConfigureServices)
+ .Build();
+ }
+
+ private void ConfigureServices(HostBuilderContext context, IServiceCollection services)
+ {
+ // Core Services
+ services.AddSingleton();
+
+ // Services
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ // ViewModels
+ services.AddTransient();
+ services.AddTransient();
+
+ // Configuration
+ services.Configure(context.Configuration.GetSection(nameof(AppConfig)));
+ }
+
+ // TODO WTS: Add tests for functionality you add to SettingsViewModel.
+ [Fact]
+ public void TestSettingsViewModelCreation()
+ {
+ var vm = _host.Services.GetService(typeof(SettingsViewModel));
+ Assert.NotNull(vm);
+ }
+
+ [Fact]
+ public void TestGetSettingsPageType()
+ {
+ if (_host.Services.GetService(typeof(IPageService)) is IPageService pageService)
+ {
+ var pageType = pageService.GetPageType(typeof(SettingsViewModel).FullName);
+ Assert.Equal(typeof(SettingsPage), pageType);
+ }
+ else
+ {
+ Assert.True(false, $"Can't resolve {nameof(IPageService)}");
+ }
+ }
+
+ // TODO WTS: Add tests for functionality you add to MainViewModel.
+ [Fact]
+ public void TestMainViewModelCreation()
+ {
+ var vm = _host.Services.GetService(typeof(MainViewModel));
+ Assert.NotNull(vm);
+ }
+
+ [Fact]
+ public void TestGetMainPageType()
+ {
+ if (_host.Services.GetService(typeof(IPageService)) is IPageService pageService)
+ {
+ var pageType = pageService.GetPageType(typeof(MainViewModel).FullName);
+ Assert.Equal(typeof(MainPage), pageType);
+ }
+ else
+ {
+ Assert.True(false, $"Can't resolve {nameof(IPageService)}");
+ }
+ }
+ }
+}
diff --git a/ChineseChess.GUI.Tests.xUnit/SettingsViewModelTests.cs b/ChineseChess.GUI.Tests.xUnit/SettingsViewModelTests.cs
new file mode 100644
index 0000000..bcc903f
--- /dev/null
+++ b/ChineseChess.GUI.Tests.xUnit/SettingsViewModelTests.cs
@@ -0,0 +1,66 @@
+using System;
+
+using ChineseChess.GUI.Contracts.Services;
+using ChineseChess.GUI.Models;
+using ChineseChess.GUI.ViewModels;
+
+using Microsoft.Extensions.Options;
+
+using Moq;
+
+using Xunit;
+
+namespace ChineseChess.GUI.Tests.XUnit
+{
+ public class SettingsViewModelTests
+ {
+ public SettingsViewModelTests()
+ {
+ }
+
+ [Fact]
+ public void TestSettingsViewModel_SetCurrentTheme()
+ {
+ var mockThemeSelectorService = new Mock();
+ mockThemeSelectorService.Setup(mock => mock.GetCurrentTheme()).Returns(AppTheme.Light);
+ var mockAppConfig = new Mock>();
+ var mockSystemService = new Mock();
+ var mockApplicationInfoService = new Mock();
+
+ var settingsVm = new SettingsViewModel(mockAppConfig.Object, mockThemeSelectorService.Object, mockSystemService.Object, mockApplicationInfoService.Object);
+ settingsVm.OnNavigatedTo(null);
+
+ Assert.Equal(AppTheme.Light, settingsVm.Theme);
+ }
+
+ [Fact]
+ public void TestSettingsViewModel_SetCurrentVersion()
+ {
+ var mockThemeSelectorService = new Mock();
+ var mockAppConfig = new Mock>();
+ var mockSystemService = new Mock();
+ var mockApplicationInfoService = new Mock();
+ var testVersion = new Version(1, 2, 3, 4);
+ mockApplicationInfoService.Setup(mock => mock.GetVersion()).Returns(testVersion);
+
+ var settingsVm = new SettingsViewModel(mockAppConfig.Object, mockThemeSelectorService.Object, mockSystemService.Object, mockApplicationInfoService.Object);
+ settingsVm.OnNavigatedTo(null);
+
+ Assert.Equal($"ChineseChess.GUI - {testVersion}", settingsVm.VersionDescription);
+ }
+
+ [Fact]
+ public void TestSettingsViewModel_SetThemeCommand()
+ {
+ var mockThemeSelectorService = new Mock();
+ var mockAppConfig = new Mock>();
+ var mockSystemService = new Mock();
+ var mockApplicationInfoService = new Mock();
+
+ var settingsVm = new SettingsViewModel(mockAppConfig.Object, mockThemeSelectorService.Object, mockSystemService.Object, mockApplicationInfoService.Object);
+ settingsVm.SetThemeCommand.Execute(AppTheme.Light.ToString());
+
+ mockThemeSelectorService.Verify(mock => mock.SetTheme(AppTheme.Light));
+ }
+ }
+}
diff --git a/ChineseChess.GUI.Tests.xUnit/Tests.cs b/ChineseChess.GUI.Tests.xUnit/Tests.cs
new file mode 100644
index 0000000..9493604
--- /dev/null
+++ b/ChineseChess.GUI.Tests.xUnit/Tests.cs
@@ -0,0 +1,13 @@
+using Xunit;
+
+namespace ChineseChess.GUI.Tests.XUnit
+{
+ // TODO WTS: Add appropriate unit tests.
+ public class Tests
+ {
+ [Fact]
+ public void TestMethod1()
+ {
+ }
+ }
+}
diff --git a/ChineseChess.GUI/App.xaml b/ChineseChess.GUI/App.xaml
new file mode 100644
index 0000000..bc8f9f7
--- /dev/null
+++ b/ChineseChess.GUI/App.xaml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ChineseChess.GUI/App.xaml.cs b/ChineseChess.GUI/App.xaml.cs
new file mode 100644
index 0000000..8032844
--- /dev/null
+++ b/ChineseChess.GUI/App.xaml.cs
@@ -0,0 +1,96 @@
+using System.IO;
+using System.Reflection;
+using System.Windows;
+using System.Windows.Threading;
+
+using ChineseChess.GUI.Contracts.Services;
+using ChineseChess.GUI.Contracts.Views;
+using ChineseChess.GUI.Core.Contracts.Services;
+using ChineseChess.GUI.Core.Services;
+using ChineseChess.GUI.Models;
+using ChineseChess.GUI.Services;
+using ChineseChess.GUI.ViewModels;
+using ChineseChess.GUI.Views;
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace ChineseChess.GUI
+{
+ public partial class App : Application
+ {
+ private IHost _host;
+
+ public T GetService()
+ where T : class
+ => _host.Services.GetService(typeof(T)) as T;
+
+ public App()
+ {
+ }
+
+ private async void OnStartup(object sender, StartupEventArgs e)
+ {
+ var appLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
+
+ // For more information about .NET generic host see https://docs.microsoft.com/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.0
+ _host = Host.CreateDefaultBuilder(e.Args)
+ .ConfigureAppConfiguration(c =>
+ {
+ c.SetBasePath(appLocation);
+ })
+ .ConfigureServices(ConfigureServices)
+ .Build();
+
+ await _host.StartAsync();
+ }
+
+ private void ConfigureServices(HostBuilderContext context, IServiceCollection services)
+ {
+ // TODO WTS: Register your services, viewmodels and pages here
+
+ // App Host
+ services.AddHostedService();
+
+ // Activation Handlers
+
+ // Core Services
+ services.AddSingleton();
+
+ // Services
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ // Views and ViewModels
+ services.AddTransient();
+ services.AddTransient();
+
+ services.AddTransient();
+ services.AddTransient();
+
+ services.AddTransient();
+ services.AddTransient();
+
+ // Configuration
+ services.Configure(context.Configuration.GetSection(nameof(AppConfig)));
+ }
+
+ private async void OnExit(object sender, ExitEventArgs e)
+ {
+ await _host.StopAsync();
+ _host.Dispose();
+ _host = null;
+ }
+
+ private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
+ {
+ // TODO WTS: Please log and handle the exception as appropriate to your scenario
+ // For more info see https://docs.microsoft.com/dotnet/api/system.windows.application.dispatcherunhandledexception?view=netcore-3.0
+ }
+ }
+}
diff --git a/ChineseChess.GUI/ChineseChess.GUI.csproj b/ChineseChess.GUI/ChineseChess.GUI.csproj
new file mode 100644
index 0000000..339e969
--- /dev/null
+++ b/ChineseChess.GUI/ChineseChess.GUI.csproj
@@ -0,0 +1,38 @@
+
+
+ WinExe
+ netcoreapp3.1
+ ChineseChess.GUI
+ true
+ app.manifest
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+ PublicResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
+ Always
+
+
+
diff --git a/ChineseChess.GUI/Contracts/Activation/IActivationHandler.cs b/ChineseChess.GUI/Contracts/Activation/IActivationHandler.cs
new file mode 100644
index 0000000..afc6890
--- /dev/null
+++ b/ChineseChess.GUI/Contracts/Activation/IActivationHandler.cs
@@ -0,0 +1,11 @@
+using System.Threading.Tasks;
+
+namespace ChineseChess.GUI.Contracts.Activation
+{
+ public interface IActivationHandler
+ {
+ bool CanHandle();
+
+ Task HandleAsync();
+ }
+}
diff --git a/ChineseChess.GUI/Contracts/Services/IApplicationInfoService.cs b/ChineseChess.GUI/Contracts/Services/IApplicationInfoService.cs
new file mode 100644
index 0000000..fa3c5c0
--- /dev/null
+++ b/ChineseChess.GUI/Contracts/Services/IApplicationInfoService.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace ChineseChess.GUI.Contracts.Services
+{
+ public interface IApplicationInfoService
+ {
+ Version GetVersion();
+ }
+}
diff --git a/ChineseChess.GUI/Contracts/Services/INavigationService.cs b/ChineseChess.GUI/Contracts/Services/INavigationService.cs
new file mode 100644
index 0000000..e61ef3e
--- /dev/null
+++ b/ChineseChess.GUI/Contracts/Services/INavigationService.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Windows.Controls;
+
+namespace ChineseChess.GUI.Contracts.Services
+{
+ public interface INavigationService
+ {
+ event EventHandler Navigated;
+
+ bool CanGoBack { get; }
+
+ void Initialize(Frame shellFrame);
+
+ bool NavigateTo(string pageKey, object parameter = null, bool clearNavigation = false);
+
+ void GoBack();
+
+ void UnsubscribeNavigation();
+
+ void CleanNavigation();
+ }
+}
diff --git a/ChineseChess.GUI/Contracts/Services/IPageService.cs b/ChineseChess.GUI/Contracts/Services/IPageService.cs
new file mode 100644
index 0000000..678a6d2
--- /dev/null
+++ b/ChineseChess.GUI/Contracts/Services/IPageService.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Windows.Controls;
+
+namespace ChineseChess.GUI.Contracts.Services
+{
+ public interface IPageService
+ {
+ Type GetPageType(string key);
+
+ Page GetPage(string key);
+ }
+}
diff --git a/ChineseChess.GUI/Contracts/Services/IPersistAndRestoreService.cs b/ChineseChess.GUI/Contracts/Services/IPersistAndRestoreService.cs
new file mode 100644
index 0000000..c9b9cb3
--- /dev/null
+++ b/ChineseChess.GUI/Contracts/Services/IPersistAndRestoreService.cs
@@ -0,0 +1,9 @@
+namespace ChineseChess.GUI.Contracts.Services
+{
+ public interface IPersistAndRestoreService
+ {
+ void RestoreData();
+
+ void PersistData();
+ }
+}
diff --git a/ChineseChess.GUI/Contracts/Services/ISystemService.cs b/ChineseChess.GUI/Contracts/Services/ISystemService.cs
new file mode 100644
index 0000000..22658d1
--- /dev/null
+++ b/ChineseChess.GUI/Contracts/Services/ISystemService.cs
@@ -0,0 +1,7 @@
+namespace ChineseChess.GUI.Contracts.Services
+{
+ public interface ISystemService
+ {
+ void OpenInWebBrowser(string url);
+ }
+}
diff --git a/ChineseChess.GUI/Contracts/Services/IThemeSelectorService.cs b/ChineseChess.GUI/Contracts/Services/IThemeSelectorService.cs
new file mode 100644
index 0000000..aed1073
--- /dev/null
+++ b/ChineseChess.GUI/Contracts/Services/IThemeSelectorService.cs
@@ -0,0 +1,15 @@
+using System;
+
+using ChineseChess.GUI.Models;
+
+namespace ChineseChess.GUI.Contracts.Services
+{
+ public interface IThemeSelectorService
+ {
+ void InitializeTheme();
+
+ void SetTheme(AppTheme theme);
+
+ AppTheme GetCurrentTheme();
+ }
+}
diff --git a/ChineseChess.GUI/Contracts/ViewModels/INavigationAware.cs b/ChineseChess.GUI/Contracts/ViewModels/INavigationAware.cs
new file mode 100644
index 0000000..7f449c9
--- /dev/null
+++ b/ChineseChess.GUI/Contracts/ViewModels/INavigationAware.cs
@@ -0,0 +1,9 @@
+namespace ChineseChess.GUI.Contracts.ViewModels
+{
+ public interface INavigationAware
+ {
+ void OnNavigatedTo(object parameter);
+
+ void OnNavigatedFrom();
+ }
+}
diff --git a/ChineseChess.GUI/Contracts/Views/IShellWindow.cs b/ChineseChess.GUI/Contracts/Views/IShellWindow.cs
new file mode 100644
index 0000000..e450467
--- /dev/null
+++ b/ChineseChess.GUI/Contracts/Views/IShellWindow.cs
@@ -0,0 +1,13 @@
+using System.Windows.Controls;
+
+namespace ChineseChess.GUI.Contracts.Views
+{
+ public interface IShellWindow
+ {
+ Frame GetNavigationFrame();
+
+ void ShowWindow();
+
+ void CloseWindow();
+ }
+}
diff --git a/ChineseChess.GUI/Converters/EnumToBooleanConverter.cs b/ChineseChess.GUI/Converters/EnumToBooleanConverter.cs
new file mode 100644
index 0000000..75b1cf1
--- /dev/null
+++ b/ChineseChess.GUI/Converters/EnumToBooleanConverter.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace ChineseChess.GUI.Converters
+{
+ public class EnumToBooleanConverter : IValueConverter
+ {
+ public Type EnumType { get; set; }
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (parameter is string enumString)
+ {
+ if (Enum.IsDefined(EnumType, value))
+ {
+ var enumValue = Enum.Parse(EnumType, enumString);
+
+ return enumValue.Equals(value);
+ }
+ }
+
+ return false;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (parameter is string enumString)
+ {
+ return Enum.Parse(EnumType, enumString);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Helpers/FrameExtensions.cs b/ChineseChess.GUI/Helpers/FrameExtensions.cs
new file mode 100644
index 0000000..c63852f
--- /dev/null
+++ b/ChineseChess.GUI/Helpers/FrameExtensions.cs
@@ -0,0 +1,23 @@
+namespace System.Windows.Controls
+{
+ public static class FrameExtensions
+ {
+ public static object GetDataContext(this Frame frame)
+ {
+ if (frame.Content is FrameworkElement element)
+ {
+ return element.DataContext;
+ }
+
+ return null;
+ }
+
+ public static void CleanNavigation(this Frame frame)
+ {
+ while (frame.CanGoBack)
+ {
+ frame.RemoveBackEntry();
+ }
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Models/AppConfig.cs b/ChineseChess.GUI/Models/AppConfig.cs
new file mode 100644
index 0000000..a03823c
--- /dev/null
+++ b/ChineseChess.GUI/Models/AppConfig.cs
@@ -0,0 +1,11 @@
+namespace ChineseChess.GUI.Models
+{
+ public class AppConfig
+ {
+ public string ConfigurationsFolder { get; set; }
+
+ public string AppPropertiesFileName { get; set; }
+
+ public string PrivacyStatement { get; set; }
+ }
+}
diff --git a/ChineseChess.GUI/Models/AppTheme.cs b/ChineseChess.GUI/Models/AppTheme.cs
new file mode 100644
index 0000000..d20c8b4
--- /dev/null
+++ b/ChineseChess.GUI/Models/AppTheme.cs
@@ -0,0 +1,9 @@
+namespace ChineseChess.GUI.Models
+{
+ public enum AppTheme
+ {
+ Default,
+ Light,
+ Dark
+ }
+}
diff --git a/ChineseChess.GUI/Properties/Resources.Designer.cs b/ChineseChess.GUI/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..4f1ae4b
--- /dev/null
+++ b/ChineseChess.GUI/Properties/Resources.Designer.cs
@@ -0,0 +1,197 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace ChineseChess.GUI.Properties {
+ using System;
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ChineseChess.GUI.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to wts.ItemName.
+ ///
+ public static string AppDisplayName {
+ get {
+ return ResourceManager.GetString("AppDisplayName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ShellGoBackButton.
+ ///
+ public static string ShellGoBackButton {
+ get {
+ return ResourceManager.GetString("ShellGoBackButton", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ShellHamburgerButtonName.
+ ///
+ public static string ShellHamburgerButtonName {
+ get {
+ return ResourceManager.GetString("ShellHamburgerButtonName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Main.
+ ///
+ public static string MainPageTitle {
+ get {
+ return ResourceManager.GetString("MainPageTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Main.
+ ///
+ public static string ShellMainPage {
+ get {
+ return ResourceManager.GetString("ShellMainPage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Settings page placeholder text. Your app description goes here..
+ ///
+ public static string SettingsPageAboutText {
+ get {
+ return ResourceManager.GetString("SettingsPageAboutText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to About this application.
+ ///
+ public static string SettingsPageAboutTitle {
+ get {
+ return ResourceManager.GetString("SettingsPageAboutTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Choose Theme.
+ ///
+ public static string SettingsPageChooseThemeText {
+ get {
+ return ResourceManager.GetString("SettingsPageChooseThemeText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Personalization.
+ ///
+ public static string SettingsPagePersonalizationTitle {
+ get {
+ return ResourceManager.GetString("SettingsPagePersonalizationTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Privacy Statement.
+ ///
+ public static string SettingsPagePrivacyStatementText {
+ get {
+ return ResourceManager.GetString("SettingsPagePrivacyStatementText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Dark.
+ ///
+ public static string SettingsPageRadioButtonDarkTheme {
+ get {
+ return ResourceManager.GetString("SettingsPageRadioButtonDarkTheme", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Light.
+ ///
+ public static string SettingsPageRadioButtonLightTheme {
+ get {
+ return ResourceManager.GetString("SettingsPageRadioButtonLightTheme", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Default.
+ ///
+ public static string SettingsPageRadioButtonWindowsDefaultTheme {
+ get {
+ return ResourceManager.GetString("SettingsPageRadioButtonWindowsDefaultTheme", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Settings.
+ ///
+ public static string SettingsPageTitle {
+ get {
+ return ResourceManager.GetString("SettingsPageTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Settings.
+ ///
+ public static string ShellSettingsPage {
+ get {
+ return ResourceManager.GetString("ShellSettingsPage", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Properties/Resources.resx b/ChineseChess.GUI/Properties/Resources.resx
new file mode 100644
index 0000000..4ef21cc
--- /dev/null
+++ b/ChineseChess.GUI/Properties/Resources.resx
@@ -0,0 +1,147 @@
+
+
+
+ Settings
+
+
+ Settings
+
+
+ Settings page placeholder text. Your app description goes here.
+
+
+ About this application
+
+
+ Choose Theme
+
+
+ Personalization
+
+
+ Privacy Statement
+
+
+ Dark
+
+
+ Light
+
+
+ Default
+
+
+ Main
+
+
+ Main
+
+
+ Go back
+
+
+ Open or close navigation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ChineseChess.GUI
+
+
diff --git a/ChineseChess.GUI/Services/ApplicationHostService.cs b/ChineseChess.GUI/Services/ApplicationHostService.cs
new file mode 100644
index 0000000..e29fb7d
--- /dev/null
+++ b/ChineseChess.GUI/Services/ApplicationHostService.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+using ChineseChess.GUI.Contracts.Activation;
+using ChineseChess.GUI.Contracts.Services;
+using ChineseChess.GUI.Contracts.Views;
+using ChineseChess.GUI.ViewModels;
+
+using Microsoft.Extensions.Hosting;
+
+namespace ChineseChess.GUI.Services
+{
+ public class ApplicationHostService : IHostedService
+ {
+ private readonly IServiceProvider _serviceProvider;
+ private readonly INavigationService _navigationService;
+ private readonly IPersistAndRestoreService _persistAndRestoreService;
+ private readonly IThemeSelectorService _themeSelectorService;
+ private readonly IEnumerable _activationHandlers;
+ private IShellWindow _shellWindow;
+ private bool _isInitialized;
+
+ public ApplicationHostService(IServiceProvider serviceProvider, IEnumerable activationHandlers, INavigationService navigationService, IThemeSelectorService themeSelectorService, IPersistAndRestoreService persistAndRestoreService)
+ {
+ _serviceProvider = serviceProvider;
+ _activationHandlers = activationHandlers;
+ _navigationService = navigationService;
+ _themeSelectorService = themeSelectorService;
+ _persistAndRestoreService = persistAndRestoreService;
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ // Initialize services that you need before app activation
+ await InitializeAsync();
+
+ await HandleActivationAsync();
+
+ // Tasks after activation
+ await StartupAsync();
+ _isInitialized = true;
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken)
+ {
+ _persistAndRestoreService.PersistData();
+ await Task.CompletedTask;
+ }
+
+ private async Task InitializeAsync()
+ {
+ if (!_isInitialized)
+ {
+ _persistAndRestoreService.RestoreData();
+ _themeSelectorService.InitializeTheme();
+ await Task.CompletedTask;
+ }
+ }
+
+ private async Task StartupAsync()
+ {
+ if (!_isInitialized)
+ {
+ await Task.CompletedTask;
+ }
+ }
+
+ private async Task HandleActivationAsync()
+ {
+ var activationHandler = _activationHandlers.FirstOrDefault(h => h.CanHandle());
+
+ if (activationHandler != null)
+ {
+ await activationHandler.HandleAsync();
+ }
+
+ await Task.CompletedTask;
+
+ if (App.Current.Windows.OfType().Count() == 0)
+ {
+ // Default activation that navigates to the apps default page
+ _shellWindow = _serviceProvider.GetService(typeof(IShellWindow)) as IShellWindow;
+ _navigationService.Initialize(_shellWindow.GetNavigationFrame());
+ _shellWindow.ShowWindow();
+ _navigationService.NavigateTo(typeof(MainViewModel).FullName);
+ await Task.CompletedTask;
+ }
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Services/ApplicationInfoService.cs b/ChineseChess.GUI/Services/ApplicationInfoService.cs
new file mode 100644
index 0000000..02a8ae0
--- /dev/null
+++ b/ChineseChess.GUI/Services/ApplicationInfoService.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Diagnostics;
+using System.Reflection;
+
+using ChineseChess.GUI.Contracts.Services;
+
+namespace ChineseChess.GUI.Services
+{
+ public class ApplicationInfoService : IApplicationInfoService
+ {
+ public ApplicationInfoService()
+ {
+ }
+
+ public Version GetVersion()
+ {
+ // Set the app version in ChineseChess.GUI > Properties > Package > PackageVersion
+ string assemblyLocation = Assembly.GetExecutingAssembly().Location;
+ var version = FileVersionInfo.GetVersionInfo(assemblyLocation).FileVersion;
+ return new Version(version);
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Services/NavigationService.cs b/ChineseChess.GUI/Services/NavigationService.cs
new file mode 100644
index 0000000..93759e4
--- /dev/null
+++ b/ChineseChess.GUI/Services/NavigationService.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Windows.Controls;
+using System.Windows.Navigation;
+
+using ChineseChess.GUI.Contracts.Services;
+using ChineseChess.GUI.Contracts.ViewModels;
+
+namespace ChineseChess.GUI.Services
+{
+ public class NavigationService : INavigationService
+ {
+ private readonly IPageService _pageService;
+ private Frame _frame;
+ private object _lastParameterUsed;
+
+ public event EventHandler Navigated;
+
+ public bool CanGoBack => _frame.CanGoBack;
+
+ public NavigationService(IPageService pageService)
+ {
+ _pageService = pageService;
+ }
+
+ public void Initialize(Frame shellFrame)
+ {
+ if (_frame == null)
+ {
+ _frame = shellFrame;
+ _frame.Navigated += OnNavigated;
+ }
+ }
+
+ public void UnsubscribeNavigation()
+ {
+ _frame.Navigated -= OnNavigated;
+ _frame = null;
+ }
+
+ public void GoBack()
+ {
+ if (_frame.CanGoBack)
+ {
+ var vmBeforeNavigation = _frame.GetDataContext();
+ _frame.GoBack();
+ if (vmBeforeNavigation is INavigationAware navigationAware)
+ {
+ navigationAware.OnNavigatedFrom();
+ }
+ }
+ }
+
+ public bool NavigateTo(string pageKey, object parameter = null, bool clearNavigation = false)
+ {
+ var pageType = _pageService.GetPageType(pageKey);
+
+ if (_frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(_lastParameterUsed)))
+ {
+ _frame.Tag = clearNavigation;
+ var page = _pageService.GetPage(pageKey);
+ var navigated = _frame.Navigate(page, parameter);
+ if (navigated)
+ {
+ _lastParameterUsed = parameter;
+ var dataContext = _frame.GetDataContext();
+ if (dataContext is INavigationAware navigationAware)
+ {
+ navigationAware.OnNavigatedFrom();
+ }
+ }
+
+ return navigated;
+ }
+
+ return false;
+ }
+
+ public void CleanNavigation()
+ => _frame.CleanNavigation();
+
+ private void OnNavigated(object sender, NavigationEventArgs e)
+ {
+ if (sender is Frame frame)
+ {
+ bool clearNavigation = (bool)frame.Tag;
+ if (clearNavigation)
+ {
+ frame.CleanNavigation();
+ }
+
+ var dataContext = frame.GetDataContext();
+ if (dataContext is INavigationAware navigationAware)
+ {
+ navigationAware.OnNavigatedTo(e.ExtraData);
+ }
+
+ Navigated?.Invoke(sender, dataContext.GetType().FullName);
+ }
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Services/PageService.cs b/ChineseChess.GUI/Services/PageService.cs
new file mode 100644
index 0000000..7acbd79
--- /dev/null
+++ b/ChineseChess.GUI/Services/PageService.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Controls;
+
+using ChineseChess.GUI.Contracts.Services;
+using ChineseChess.GUI.ViewModels;
+using ChineseChess.GUI.Views;
+
+using Microsoft.Toolkit.Mvvm.ComponentModel;
+
+namespace ChineseChess.GUI.Services
+{
+ public class PageService : IPageService
+ {
+ private readonly Dictionary _pages = new Dictionary();
+ private readonly IServiceProvider _serviceProvider;
+
+ public PageService(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ Configure();
+ Configure();
+ }
+
+ public Type GetPageType(string key)
+ {
+ Type pageType;
+ lock (_pages)
+ {
+ if (!_pages.TryGetValue(key, out pageType))
+ {
+ throw new ArgumentException($"Page not found: {key}. Did you forget to call PageService.Configure?");
+ }
+ }
+
+ return pageType;
+ }
+
+ public Page GetPage(string key)
+ {
+ var pageType = GetPageType(key);
+ return _serviceProvider.GetService(pageType) as Page;
+ }
+
+ private void Configure()
+ where VM : ObservableObject
+ where V : Page
+ {
+ lock (_pages)
+ {
+ var key = typeof(VM).FullName;
+ if (_pages.ContainsKey(key))
+ {
+ throw new ArgumentException($"The key {key} is already configured in PageService");
+ }
+
+ var type = typeof(V);
+ if (_pages.Any(p => p.Value == type))
+ {
+ throw new ArgumentException($"This type is already configured with key {_pages.First(p => p.Value == type).Key}");
+ }
+
+ _pages.Add(key, type);
+ }
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Services/PersistAndRestoreService.cs b/ChineseChess.GUI/Services/PersistAndRestoreService.cs
new file mode 100644
index 0000000..ea052e1
--- /dev/null
+++ b/ChineseChess.GUI/Services/PersistAndRestoreService.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using ChineseChess.GUI.Contracts.Services;
+using ChineseChess.GUI.Core.Contracts.Services;
+using ChineseChess.GUI.Models;
+
+using Microsoft.Extensions.Options;
+
+namespace ChineseChess.GUI.Services
+{
+ public class PersistAndRestoreService : IPersistAndRestoreService
+ {
+ private readonly IFileService _fileService;
+ private readonly AppConfig _appConfig;
+ private readonly string _localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+
+ public PersistAndRestoreService(IFileService fileService, IOptions appConfig)
+ {
+ _fileService = fileService;
+ _appConfig = appConfig.Value;
+ }
+
+ public void PersistData()
+ {
+ if (App.Current.Properties != null)
+ {
+ var folderPath = Path.Combine(_localAppData, _appConfig.ConfigurationsFolder);
+ var fileName = _appConfig.AppPropertiesFileName;
+ _fileService.Save(folderPath, fileName, App.Current.Properties);
+ }
+ }
+
+ public void RestoreData()
+ {
+ var folderPath = Path.Combine(_localAppData, _appConfig.ConfigurationsFolder);
+ var fileName = _appConfig.AppPropertiesFileName;
+ var properties = _fileService.Read(folderPath, fileName);
+ if (properties != null)
+ {
+ foreach (DictionaryEntry property in properties)
+ {
+ App.Current.Properties.Add(property.Key, property.Value);
+ }
+ }
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Services/SystemService.cs b/ChineseChess.GUI/Services/SystemService.cs
new file mode 100644
index 0000000..0a9b9dc
--- /dev/null
+++ b/ChineseChess.GUI/Services/SystemService.cs
@@ -0,0 +1,24 @@
+using System.Diagnostics;
+
+using ChineseChess.GUI.Contracts.Services;
+
+namespace ChineseChess.GUI.Services
+{
+ public class SystemService : ISystemService
+ {
+ public SystemService()
+ {
+ }
+
+ public void OpenInWebBrowser(string url)
+ {
+ // For more info see https://github.com/dotnet/corefx/issues/10361
+ var psi = new ProcessStartInfo
+ {
+ FileName = url,
+ UseShellExecute = true
+ };
+ Process.Start(psi);
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Services/ThemeSelectorService.cs b/ChineseChess.GUI/Services/ThemeSelectorService.cs
new file mode 100644
index 0000000..2b569b9
--- /dev/null
+++ b/ChineseChess.GUI/Services/ThemeSelectorService.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Windows;
+
+using ChineseChess.GUI.Contracts.Services;
+using ChineseChess.GUI.Models;
+
+using ControlzEx.Theming;
+
+using MahApps.Metro.Theming;
+
+namespace ChineseChess.GUI.Services
+{
+ public class ThemeSelectorService : IThemeSelectorService
+ {
+ private const string HcDarkTheme = "pack://application:,,,/Styles/Themes/HC.Dark.Blue.xaml";
+ private const string HcLightTheme = "pack://application:,,,/Styles/Themes/HC.Light.Blue.xaml";
+
+ public ThemeSelectorService()
+ {
+ }
+
+ public void InitializeTheme()
+ {
+ // TODO WTS: Mahapps.Metro supports syncronization with high contrast but you have to provide custom high contrast themes
+ // We've added basic high contrast dictionaries for Dark and Light themes
+ // Please complete these themes following the docs on https://mahapps.com/docs/themes/thememanager#creating-custom-themes
+ ThemeManager.Current.AddLibraryTheme(new LibraryTheme(new Uri(HcDarkTheme), MahAppsLibraryThemeProvider.DefaultInstance));
+ ThemeManager.Current.AddLibraryTheme(new LibraryTheme(new Uri(HcLightTheme), MahAppsLibraryThemeProvider.DefaultInstance));
+
+ var theme = GetCurrentTheme();
+ SetTheme(theme);
+ }
+
+ public void SetTheme(AppTheme theme)
+ {
+ if (theme == AppTheme.Default)
+ {
+ ThemeManager.Current.ThemeSyncMode = ThemeSyncMode.SyncAll;
+ ThemeManager.Current.SyncTheme();
+ }
+ else
+ {
+ ThemeManager.Current.ThemeSyncMode = ThemeSyncMode.SyncWithHighContrast;
+ ThemeManager.Current.SyncTheme();
+ ThemeManager.Current.ChangeTheme(Application.Current, $"{theme}.Blue", SystemParameters.HighContrast);
+ }
+
+ App.Current.Properties["Theme"] = theme.ToString();
+ }
+
+ public AppTheme GetCurrentTheme()
+ {
+ if (App.Current.Properties.Contains("Theme"))
+ {
+ var themeName = App.Current.Properties["Theme"].ToString();
+ Enum.TryParse(themeName, out AppTheme theme);
+ return theme;
+ }
+
+ return AppTheme.Default;
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Styles/MetroWindow.xaml b/ChineseChess.GUI/Styles/MetroWindow.xaml
new file mode 100644
index 0000000..2ddb27e
--- /dev/null
+++ b/ChineseChess.GUI/Styles/MetroWindow.xaml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/ChineseChess.GUI/Styles/TextBlock.xaml b/ChineseChess.GUI/Styles/TextBlock.xaml
new file mode 100644
index 0000000..e8ad872
--- /dev/null
+++ b/ChineseChess.GUI/Styles/TextBlock.xaml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ChineseChess.GUI/Styles/Themes/HC.Dark.Blue.xaml b/ChineseChess.GUI/Styles/Themes/HC.Dark.Blue.xaml
new file mode 100644
index 0000000..4daf357
--- /dev/null
+++ b/ChineseChess.GUI/Styles/Themes/HC.Dark.Blue.xaml
@@ -0,0 +1,40 @@
+
+
+
+ Dark.Blue
+ ChineseChess.GUI
+ Blue (Dark)
+ Dark
+ Blue
+ true
+ #FF0078D7
+
+
+
+
+
+
+ #FFFFFFFF
+ #FF000000
+ #FFFFFFFF
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChineseChess.GUI/Styles/Themes/HC.Light.Blue.xaml b/ChineseChess.GUI/Styles/Themes/HC.Light.Blue.xaml
new file mode 100644
index 0000000..2b68ae6
--- /dev/null
+++ b/ChineseChess.GUI/Styles/Themes/HC.Light.Blue.xaml
@@ -0,0 +1,40 @@
+
+
+
+ Light.Blue
+ ChineseChess.GUI
+ Blue (Light)
+ Light
+ Blue
+ true
+ #FF0078D7
+
+
+
+
+
+
+ #FF000000
+ #FFFFFFFF
+ #FF000000
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChineseChess.GUI/Styles/_FontSizes.xaml b/ChineseChess.GUI/Styles/_FontSizes.xaml
new file mode 100644
index 0000000..814c112
--- /dev/null
+++ b/ChineseChess.GUI/Styles/_FontSizes.xaml
@@ -0,0 +1,14 @@
+
+
+ 12
+
+ 14
+
+ 18
+
+ 22
+
+
diff --git a/ChineseChess.GUI/Styles/_Thickness.xaml b/ChineseChess.GUI/Styles/_Thickness.xaml
new file mode 100644
index 0000000..49abd94
--- /dev/null
+++ b/ChineseChess.GUI/Styles/_Thickness.xaml
@@ -0,0 +1,30 @@
+
+
+
+ 24,0,0,0
+ 24,24,24,0
+ 0,24,0,0
+ 24,0,24,0
+ 0, 0, 0, 24
+ 24,24,24,24
+
+
+ 12, 0, 0, 0
+ 12, 12, 0, 0
+ 12, 0, 12, 0
+ 0, 12, 0, 0
+ 0, 0, 12, 0
+ 0, 12, 0, 12
+ 12, 12, 12, 12
+
+
+ 8, 0, 0, 0
+ 0, 8, 0, 0
+ 8, 8, 8, 8
+
+
+ 0, 4, 0, 0
+
+
diff --git a/ChineseChess.GUI/TemplateSelectors/MenuItemTemplateSelector.cs b/ChineseChess.GUI/TemplateSelectors/MenuItemTemplateSelector.cs
new file mode 100644
index 0000000..7bd94b5
--- /dev/null
+++ b/ChineseChess.GUI/TemplateSelectors/MenuItemTemplateSelector.cs
@@ -0,0 +1,29 @@
+using System.Windows;
+using System.Windows.Controls;
+
+using MahApps.Metro.Controls;
+
+namespace ChineseChess.GUI.TemplateSelectors
+{
+ public class MenuItemTemplateSelector : DataTemplateSelector
+ {
+ public DataTemplate GlyphDataTemplate { get; set; }
+
+ public DataTemplate ImageDataTemplate { get; set; }
+
+ public override DataTemplate SelectTemplate(object item, DependencyObject container)
+ {
+ if (item is HamburgerMenuGlyphItem)
+ {
+ return GlyphDataTemplate;
+ }
+
+ if (item is HamburgerMenuImageItem)
+ {
+ return ImageDataTemplate;
+ }
+
+ return base.SelectTemplate(item, container);
+ }
+ }
+}
diff --git a/ChineseChess.GUI/ViewModels/MainViewModel.cs b/ChineseChess.GUI/ViewModels/MainViewModel.cs
new file mode 100644
index 0000000..ec59779
--- /dev/null
+++ b/ChineseChess.GUI/ViewModels/MainViewModel.cs
@@ -0,0 +1,14 @@
+using System;
+
+using Microsoft.Toolkit.Mvvm.ComponentModel;
+using Microsoft.Toolkit.Mvvm.Input;
+
+namespace ChineseChess.GUI.ViewModels
+{
+ public class MainViewModel : ObservableObject
+ {
+ public MainViewModel()
+ {
+ }
+ }
+}
diff --git a/ChineseChess.GUI/ViewModels/SettingsViewModel.cs b/ChineseChess.GUI/ViewModels/SettingsViewModel.cs
new file mode 100644
index 0000000..90ec165
--- /dev/null
+++ b/ChineseChess.GUI/ViewModels/SettingsViewModel.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Windows.Input;
+
+using ChineseChess.GUI.Contracts.Services;
+using ChineseChess.GUI.Contracts.ViewModels;
+using ChineseChess.GUI.Models;
+
+using Microsoft.Extensions.Options;
+using Microsoft.Toolkit.Mvvm.ComponentModel;
+using Microsoft.Toolkit.Mvvm.Input;
+
+namespace ChineseChess.GUI.ViewModels
+{
+ // TODO WTS: Change the URL for your privacy policy in the appsettings.json file, currently set to https://YourPrivacyUrlGoesHere
+ public class SettingsViewModel : ObservableObject, INavigationAware
+ {
+ private readonly AppConfig _appConfig;
+ private readonly IThemeSelectorService _themeSelectorService;
+ private readonly ISystemService _systemService;
+ private readonly IApplicationInfoService _applicationInfoService;
+ private AppTheme _theme;
+ private string _versionDescription;
+ private ICommand _setThemeCommand;
+ private ICommand _privacyStatementCommand;
+
+ public AppTheme Theme
+ {
+ get { return _theme; }
+ set { SetProperty(ref _theme, value); }
+ }
+
+ public string VersionDescription
+ {
+ get { return _versionDescription; }
+ set { SetProperty(ref _versionDescription, value); }
+ }
+
+ public ICommand SetThemeCommand => _setThemeCommand ??= new RelayCommand(OnSetTheme);
+
+ public ICommand PrivacyStatementCommand => _privacyStatementCommand ??= new RelayCommand(OnPrivacyStatement);
+
+ public SettingsViewModel(IOptions appConfig, IThemeSelectorService themeSelectorService, ISystemService systemService, IApplicationInfoService applicationInfoService)
+ {
+ _appConfig = appConfig.Value;
+ _themeSelectorService = themeSelectorService;
+ _systemService = systemService;
+ _applicationInfoService = applicationInfoService;
+ }
+
+ public void OnNavigatedTo(object parameter)
+ {
+ VersionDescription = $"{Properties.Resources.AppDisplayName} - {_applicationInfoService.GetVersion()}";
+ Theme = _themeSelectorService.GetCurrentTheme();
+ }
+
+ public void OnNavigatedFrom()
+ {
+ }
+
+ private void OnSetTheme(string themeName)
+ {
+ var theme = (AppTheme)Enum.Parse(typeof(AppTheme), themeName);
+ _themeSelectorService.SetTheme(theme);
+ }
+
+ private void OnPrivacyStatement()
+ => _systemService.OpenInWebBrowser(_appConfig.PrivacyStatement);
+ }
+}
diff --git a/ChineseChess.GUI/ViewModels/ShellViewModel.cs b/ChineseChess.GUI/ViewModels/ShellViewModel.cs
new file mode 100644
index 0000000..d37985b
--- /dev/null
+++ b/ChineseChess.GUI/ViewModels/ShellViewModel.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows.Input;
+
+using ChineseChess.GUI.Contracts.Services;
+using ChineseChess.GUI.Properties;
+
+using MahApps.Metro.Controls;
+
+using Microsoft.Toolkit.Mvvm.ComponentModel;
+using Microsoft.Toolkit.Mvvm.Input;
+
+namespace ChineseChess.GUI.ViewModels
+{
+ public class ShellViewModel : ObservableObject
+ {
+ private readonly INavigationService _navigationService;
+ private HamburgerMenuItem _selectedMenuItem;
+ private HamburgerMenuItem _selectedOptionsMenuItem;
+ private RelayCommand _goBackCommand;
+ private ICommand _menuItemInvokedCommand;
+ private ICommand _optionsMenuItemInvokedCommand;
+ private ICommand _loadedCommand;
+ private ICommand _unloadedCommand;
+
+ public HamburgerMenuItem SelectedMenuItem
+ {
+ get { return _selectedMenuItem; }
+ set { SetProperty(ref _selectedMenuItem, value); }
+ }
+
+ public HamburgerMenuItem SelectedOptionsMenuItem
+ {
+ get { return _selectedOptionsMenuItem; }
+ set { SetProperty(ref _selectedOptionsMenuItem, value); }
+ }
+
+ // TODO WTS: Change the icons and titles for all HamburgerMenuItems here.
+ public ObservableCollection MenuItems { get; } = new ObservableCollection()
+ {
+ new HamburgerMenuGlyphItem() { Label = Resources.ShellMainPage, Glyph = "\uE8A5", TargetPageType = typeof(MainViewModel) },
+ };
+
+ public ObservableCollection OptionMenuItems { get; } = new ObservableCollection()
+ {
+ new HamburgerMenuGlyphItem() { Label = Resources.ShellSettingsPage, Glyph = "\uE713", TargetPageType = typeof(SettingsViewModel) }
+ };
+
+ public RelayCommand GoBackCommand => _goBackCommand ??= new RelayCommand(OnGoBack, CanGoBack);
+
+ public ICommand MenuItemInvokedCommand => _menuItemInvokedCommand ??= new RelayCommand(OnMenuItemInvoked);
+
+ public ICommand OptionsMenuItemInvokedCommand => _optionsMenuItemInvokedCommand ??= new RelayCommand(OnOptionsMenuItemInvoked);
+
+ public ICommand LoadedCommand => _loadedCommand ??= new RelayCommand(OnLoaded);
+
+ public ICommand UnloadedCommand => _unloadedCommand ??= new RelayCommand(OnUnloaded);
+
+ public ShellViewModel(INavigationService navigationService)
+ {
+ _navigationService = navigationService;
+ }
+
+ private void OnLoaded()
+ {
+ _navigationService.Navigated += OnNavigated;
+ }
+
+ private void OnUnloaded()
+ {
+ _navigationService.Navigated -= OnNavigated;
+ }
+
+ private bool CanGoBack()
+ => _navigationService.CanGoBack;
+
+ private void OnGoBack()
+ => _navigationService.GoBack();
+
+ private void OnMenuItemInvoked()
+ => NavigateTo(SelectedMenuItem.TargetPageType);
+
+ private void OnOptionsMenuItemInvoked()
+ => NavigateTo(SelectedOptionsMenuItem.TargetPageType);
+
+ private void NavigateTo(Type targetViewModel)
+ {
+ if (targetViewModel != null)
+ {
+ _navigationService.NavigateTo(targetViewModel.FullName);
+ }
+ }
+
+ private void OnNavigated(object sender, string viewModelName)
+ {
+ var item = MenuItems
+ .OfType()
+ .FirstOrDefault(i => viewModelName == i.TargetPageType?.FullName);
+ if (item != null)
+ {
+ SelectedMenuItem = item;
+ }
+ else
+ {
+ SelectedOptionsMenuItem = OptionMenuItems
+ .OfType()
+ .FirstOrDefault(i => viewModelName == i.TargetPageType?.FullName);
+ }
+
+ GoBackCommand.NotifyCanExecuteChanged();
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Views/MainPage.xaml b/ChineseChess.GUI/Views/MainPage.xaml
new file mode 100644
index 0000000..1e84b6a
--- /dev/null
+++ b/ChineseChess.GUI/Views/MainPage.xaml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ChineseChess.GUI/Views/MainPage.xaml.cs b/ChineseChess.GUI/Views/MainPage.xaml.cs
new file mode 100644
index 0000000..03d94fe
--- /dev/null
+++ b/ChineseChess.GUI/Views/MainPage.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+using ChineseChess.GUI.ViewModels;
+
+namespace ChineseChess.GUI.Views
+{
+ public partial class MainPage : Page
+ {
+ public MainPage(MainViewModel viewModel)
+ {
+ InitializeComponent();
+ DataContext = viewModel;
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Views/SettingsPage.xaml b/ChineseChess.GUI/Views/SettingsPage.xaml
new file mode 100644
index 0000000..b266adf
--- /dev/null
+++ b/ChineseChess.GUI/Views/SettingsPage.xaml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ChineseChess.GUI/Views/SettingsPage.xaml.cs b/ChineseChess.GUI/Views/SettingsPage.xaml.cs
new file mode 100644
index 0000000..375059f
--- /dev/null
+++ b/ChineseChess.GUI/Views/SettingsPage.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+using ChineseChess.GUI.ViewModels;
+
+namespace ChineseChess.GUI.Views
+{
+ public partial class SettingsPage : Page
+ {
+ public SettingsPage(SettingsViewModel viewModel)
+ {
+ InitializeComponent();
+ DataContext = viewModel;
+ }
+ }
+}
diff --git a/ChineseChess.GUI/Views/ShellWindow.xaml b/ChineseChess.GUI/Views/ShellWindow.xaml
new file mode 100644
index 0000000..71adfe7
--- /dev/null
+++ b/ChineseChess.GUI/Views/ShellWindow.xaml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ChineseChess.GUI/Views/ShellWindow.xaml.cs b/ChineseChess.GUI/Views/ShellWindow.xaml.cs
new file mode 100644
index 0000000..6fc4acc
--- /dev/null
+++ b/ChineseChess.GUI/Views/ShellWindow.xaml.cs
@@ -0,0 +1,27 @@
+using System.Windows.Controls;
+
+using ChineseChess.GUI.Contracts.Views;
+using ChineseChess.GUI.ViewModels;
+
+using MahApps.Metro.Controls;
+
+namespace ChineseChess.GUI.Views
+{
+ public partial class ShellWindow : MetroWindow, IShellWindow
+ {
+ public ShellWindow(ShellViewModel viewModel)
+ {
+ InitializeComponent();
+ DataContext = viewModel;
+ }
+
+ public Frame GetNavigationFrame()
+ => shellFrame;
+
+ public void ShowWindow()
+ => Show();
+
+ public void CloseWindow()
+ => Close();
+ }
+}
diff --git a/ChineseChess.GUI/WTS.ProjectConfig.xml b/ChineseChess.GUI/WTS.ProjectConfig.xml
new file mode 100644
index 0000000..55fd5dd
--- /dev/null
+++ b/ChineseChess.GUI/WTS.ProjectConfig.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ChineseChess.GUI/app.manifest b/ChineseChess.GUI/app.manifest
new file mode 100644
index 0000000..d1b636a
--- /dev/null
+++ b/ChineseChess.GUI/app.manifest
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
\ No newline at end of file
diff --git a/ChineseChess.GUI/appsettings.json b/ChineseChess.GUI/appsettings.json
new file mode 100644
index 0000000..11ec1ba
--- /dev/null
+++ b/ChineseChess.GUI/appsettings.json
@@ -0,0 +1,7 @@
+{
+ "AppConfig": {
+ "configurationsFolder": "ChineseChess.GUI\\Configurations",
+ "appPropertiesFileName": "AppProperties.json",
+ "privacyStatement": "https://YourPrivacyUrlGoesHere/"
+ }
+}
diff --git a/ChineseChess.UI/App.config b/ChineseChess.UI/App.config
deleted file mode 100644
index 88fa402..0000000
--- a/ChineseChess.UI/App.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ChineseChess.UI/App.xaml b/ChineseChess.UI/App.xaml
deleted file mode 100644
index ea2515f..0000000
--- a/ChineseChess.UI/App.xaml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
diff --git a/ChineseChess.UI/App.xaml.cs b/ChineseChess.UI/App.xaml.cs
deleted file mode 100644
index 94815ff..0000000
--- a/ChineseChess.UI/App.xaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows;
-
-namespace ChineseChess.UI
-{
- ///
- /// App.xaml 的交互逻辑
- ///
- public partial class App : Application
- {
- }
-}
diff --git a/ChineseChess.UI/ChineseChess.UI.csproj b/ChineseChess.UI/ChineseChess.UI.csproj
deleted file mode 100644
index d6d3259..0000000
--- a/ChineseChess.UI/ChineseChess.UI.csproj
+++ /dev/null
@@ -1,104 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {92AB0092-581C-4518-916D-FFC5084282FB}
- WinExe
- ChineseChess.UI
- ChineseChess.UI
- v4.5.2
- 512
- {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- 4
- true
- true
-
-
- AnyCPU
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
-
-
-
-
-
-
-
-
- 4.0
-
-
-
-
-
-
-
- MSBuild:Compile
- Designer
-
-
- MSBuild:Compile
- Designer
-
-
- App.xaml
- Code
-
-
- MainWindow.xaml
- Code
-
-
-
-
- Code
-
-
- True
- True
- Resources.resx
-
-
- True
- Settings.settings
- True
-
-
- ResXFileCodeGenerator
- Resources.Designer.cs
-
-
- SettingsSingleFileGenerator
- Settings.Designer.cs
-
-
-
-
-
-
-
- {6e2fa057-9341-496b-bd68-ec3e4ce3e18a}
- ChineseChess.Core
-
-
-
-
\ No newline at end of file
diff --git a/ChineseChess.UI/MainWindow.xaml b/ChineseChess.UI/MainWindow.xaml
deleted file mode 100644
index 7818bbf..0000000
--- a/ChineseChess.UI/MainWindow.xaml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
diff --git a/ChineseChess.UI/MainWindow.xaml.cs b/ChineseChess.UI/MainWindow.xaml.cs
deleted file mode 100644
index d3be992..0000000
--- a/ChineseChess.UI/MainWindow.xaml.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-
-namespace ChineseChess.UI
-{
- ///
- /// MainWindow.xaml 的交互逻辑
- ///
- public partial class MainWindow : Window
- {
- public MainWindow()
- {
- InitializeComponent();
- }
- }
-}
diff --git a/ChineseChess.UI/Properties/AssemblyInfo.cs b/ChineseChess.UI/Properties/AssemblyInfo.cs
deleted file mode 100644
index 8d121d6..0000000
--- a/ChineseChess.UI/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System.Reflection;
-using System.Resources;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Windows;
-
-// 有关程序集的一般信息由以下
-// 控制。更改这些特性值可修改
-// 与程序集关联的信息。
-[assembly: AssemblyTitle("ChineseChess.UI")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("ChineseChess.UI")]
-[assembly: AssemblyCopyright("Copyright © 2021")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// 将 ComVisible 设置为 false 会使此程序集中的类型
-//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
-//请将此类型的 ComVisible 特性设置为 true。
-[assembly: ComVisible(false)]
-
-//若要开始生成可本地化的应用程序,请设置
-//.csproj 文件中的 CultureYouAreCodingWith
-//例如,如果您在源文件中使用的是美国英语,
-//使用的是美国英语,请将 设置为 en-US。 然后取消
-//对以下 NeutralResourceLanguage 特性的注释。 更新
-//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
-
-//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
-
-
-[assembly: ThemeInfo(
- ResourceDictionaryLocation.None, //主题特定资源词典所处位置
- //(未在页面中找到资源时使用,
- //或应用程序资源字典中找到时使用)
- ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
- //(未在页面中找到资源时使用,
- //、应用程序或任何主题专用资源字典中找到时使用)
-)]
-
-
-// 程序集的版本信息由下列四个值组成:
-//
-// 主版本
-// 次版本
-// 生成号
-// 修订号
-//
-//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
-//通过使用 "*",如下所示:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/ChineseChess.UI/Properties/Resources.Designer.cs b/ChineseChess.UI/Properties/Resources.Designer.cs
deleted file mode 100644
index b6638a9..0000000
--- a/ChineseChess.UI/Properties/Resources.Designer.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// 此代码由工具生成。
-// 运行时版本: 4.0.30319.42000
-//
-// 对此文件的更改可能导致不正确的行为,如果
-// 重新生成代码,则所做更改将丢失。
-//
-//------------------------------------------------------------------------------
-
-
-namespace ChineseChess.UI.Properties
-{
- ///
- /// 强类型资源类,用于查找本地化字符串等。
- ///
- // 此类是由 StronglyTypedResourceBuilder
- // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
- // 若要添加或删除成员,请编辑 .ResX 文件,然后重新运行 ResGen
- // (以 /str 作为命令选项),或重新生成 VS 项目。
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources
- {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources()
- {
- }
-
- ///
- /// 返回此类使用的缓存 ResourceManager 实例。
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager
- {
- get
- {
- if ((resourceMan == null))
- {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ChineseChess.UI.Properties.Resources", typeof(Resources).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- ///
- /// 重写当前线程的 CurrentUICulture 属性,对
- /// 使用此强类型资源类的所有资源查找执行重写。
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture
- {
- get
- {
- return resourceCulture;
- }
- set
- {
- resourceCulture = value;
- }
- }
- }
-}
diff --git a/ChineseChess.UI/Properties/Resources.resx b/ChineseChess.UI/Properties/Resources.resx
deleted file mode 100644
index af7dbeb..0000000
--- a/ChineseChess.UI/Properties/Resources.resx
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
\ No newline at end of file
diff --git a/ChineseChess.UI/Properties/Settings.Designer.cs b/ChineseChess.UI/Properties/Settings.Designer.cs
deleted file mode 100644
index 51bff2c..0000000
--- a/ChineseChess.UI/Properties/Settings.Designer.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-
-namespace ChineseChess.UI.Properties
-{
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
- internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
- {
-
- private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
-
- public static Settings Default
- {
- get
- {
- return defaultInstance;
- }
- }
- }
-}
diff --git a/ChineseChess.UI/Properties/Settings.settings b/ChineseChess.UI/Properties/Settings.settings
deleted file mode 100644
index 033d7a5..0000000
--- a/ChineseChess.UI/Properties/Settings.settings
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ChineseChess.sln b/ChineseChess.sln
index 08ff48a..9a82c90 100644
--- a/ChineseChess.sln
+++ b/ChineseChess.sln
@@ -3,24 +3,72 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31129.286
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChineseChess.UI", "ChineseChess.UI\ChineseChess.UI.csproj", "{92AB0092-581C-4518-916D-FFC5084282FB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChineseChess.Core", "ChineseChess.Core\ChineseChess.Core.csproj", "{6E2FA057-9341-496B-BD68-EC3E4CE3E18A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChineseChess.Core", "ChineseChess.Core\ChineseChess.Core.csproj", "{6E2FA057-9341-496B-BD68-EC3E4CE3E18A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChineseChess.GUI", "ChineseChess.GUI\ChineseChess.GUI.csproj", "{0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChineseChess.GUI.Core", "ChineseChess.GUI.Core\ChineseChess.GUI.Core.csproj", "{FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChineseChess.GUI.Tests.xUnit", "ChineseChess.GUI.Tests.xUnit\ChineseChess.GUI.Tests.xUnit.csproj", "{649553A0-4005-4383-BBE5-E601198A49A7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {92AB0092-581C-4518-916D-FFC5084282FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {92AB0092-581C-4518-916D-FFC5084282FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {92AB0092-581C-4518-916D-FFC5084282FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {92AB0092-581C-4518-916D-FFC5084282FB}.Release|Any CPU.Build.0 = Release|Any CPU
{6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Debug|x64.Build.0 = Debug|Any CPU
+ {6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Debug|x86.Build.0 = Debug|Any CPU
{6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Release|x64.ActiveCfg = Release|Any CPU
+ {6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Release|x64.Build.0 = Release|Any CPU
+ {6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Release|x86.ActiveCfg = Release|Any CPU
+ {6E2FA057-9341-496B-BD68-EC3E4CE3E18A}.Release|x86.Build.0 = Release|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Debug|x64.Build.0 = Debug|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Debug|x86.Build.0 = Debug|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Release|x64.ActiveCfg = Release|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Release|x64.Build.0 = Release|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Release|x86.ActiveCfg = Release|Any CPU
+ {0B8FD863-5EBC-48F0-9A7F-9604C1185F0B}.Release|x86.Build.0 = Release|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Debug|x64.Build.0 = Debug|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Debug|x86.Build.0 = Debug|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Release|x64.ActiveCfg = Release|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Release|x64.Build.0 = Release|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Release|x86.ActiveCfg = Release|Any CPU
+ {FF15C546-6BA2-4DA8-8BB6-70C4ABB2B8E1}.Release|x86.Build.0 = Release|Any CPU
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Debug|x64.ActiveCfg = Debug|x64
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Debug|x64.Build.0 = Debug|x64
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Debug|x86.ActiveCfg = Debug|x86
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Debug|x86.Build.0 = Debug|x86
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Release|x64.ActiveCfg = Release|x64
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Release|x64.Build.0 = Release|x64
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Release|x86.ActiveCfg = Release|x86
+ {649553A0-4005-4383-BBE5-E601198A49A7}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE