From 1a6741f9eea281cad7ac0b4b62971ae3316eca6d Mon Sep 17 00:00:00 2001 From: Joseph Moreno <44370115+josephmoresena@users.noreply.github.com> Date: Sat, 11 Dec 2021 21:39:55 -0500 Subject: [PATCH] Sample improves * UserControl sample. * Multi-theme support. * NativeMenu on macOS. * GitHub workflow for Windows, Linux and MacOS artifact compilation. --- .github/workflows/dotnet.yml | 66 +++++++++ AvaloniaCoreRTDemo.sln | 12 +- src/AboutWindow.xaml | 120 ----------------- src/AboutWindow.xaml.cs | 20 --- src/AboutWindowViewModel.cs | 64 --------- src/App.axaml | 18 +++ src/App.axaml.cs | 87 ++++++++++++ src/App.xaml | 8 -- src/App.xaml.cs | 24 ---- src/ApplicationTheme.cs | 12 ++ src/Assets/about.ico | Bin 0 -> 9662 bytes src/{ => Assets}/app.icns | Bin src/{ => Assets}/app.ico | Bin src/AvaloniaCoreRTDemo.csproj | 125 +++++++++++------- src/Controls/MainControl.axaml | 9 ++ .../MainControl.axaml.cs} | 14 +- src/Controls/ViewModels/MainViewModel.cs | 34 +++++ src/{ => Images}/linux.png | Bin src/Images/linux_d.png | Bin 0 -> 9021 bytes src/{ => Images}/macos.png | Bin src/Images/macos_d.png | Bin 0 -> 6545 bytes src/{ => Images}/windows.png | Bin src/Images/windows_d.png | Bin 0 -> 2152 bytes src/Interfaces/IMainWindow.cs | 9 ++ src/Interfaces/IThemeSwitch.cs | 8 ++ src/MainWindow.xaml | 29 ---- src/MainWindowViewModel.cs | 50 ------- src/Program.cs | 14 +- src/Windows/AboutWindow.axaml | 64 +++++++++ src/Windows/AboutWindow.axaml.cs | 28 ++++ src/Windows/MainWindow.axaml | 21 +++ src/Windows/MainWindow.axaml.cs | 28 ++++ src/Windows/MainWindowMacOS.axaml | 26 ++++ src/Windows/MainWindowMacOS.axaml.cs | 28 ++++ src/Windows/ViewModels/AboutViewModel.cs | 68 ++++++++++ src/Windows/ViewModels/MainViewModel.cs | 72 ++++++++++ src/Windows/ViewModels/MainViewModelBase.cs | 51 +++++++ src/nuget.config | 6 +- src/rd.xml | 3 + test.cmd | 2 +- test.command | 22 +-- test.sh | 8 +- 42 files changed, 751 insertions(+), 399 deletions(-) create mode 100644 .github/workflows/dotnet.yml delete mode 100644 src/AboutWindow.xaml delete mode 100644 src/AboutWindow.xaml.cs delete mode 100644 src/AboutWindowViewModel.cs create mode 100644 src/App.axaml create mode 100644 src/App.axaml.cs delete mode 100644 src/App.xaml delete mode 100644 src/App.xaml.cs create mode 100644 src/ApplicationTheme.cs create mode 100644 src/Assets/about.ico rename src/{ => Assets}/app.icns (100%) rename src/{ => Assets}/app.ico (100%) create mode 100644 src/Controls/MainControl.axaml rename src/{MainWindow.xaml.cs => Controls/MainControl.axaml.cs} (50%) create mode 100644 src/Controls/ViewModels/MainViewModel.cs rename src/{ => Images}/linux.png (100%) create mode 100644 src/Images/linux_d.png rename src/{ => Images}/macos.png (100%) create mode 100644 src/Images/macos_d.png rename src/{ => Images}/windows.png (100%) create mode 100644 src/Images/windows_d.png create mode 100644 src/Interfaces/IMainWindow.cs create mode 100644 src/Interfaces/IThemeSwitch.cs delete mode 100644 src/MainWindow.xaml delete mode 100644 src/MainWindowViewModel.cs create mode 100644 src/Windows/AboutWindow.axaml create mode 100644 src/Windows/AboutWindow.axaml.cs create mode 100644 src/Windows/MainWindow.axaml create mode 100644 src/Windows/MainWindow.axaml.cs create mode 100644 src/Windows/MainWindowMacOS.axaml create mode 100644 src/Windows/MainWindowMacOS.axaml.cs create mode 100644 src/Windows/ViewModels/AboutViewModel.cs create mode 100644 src/Windows/ViewModels/MainViewModel.cs create mode 100644 src/Windows/ViewModels/MainViewModelBase.cs diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..e569ecc --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,66 @@ +name: NativeAOT Build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build-on-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup NativeAOT pre-requisites + run: sudo apt-get install clang zlib1g-dev libkrb5-dev --assume-yes + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 6.0.x + - name: Publish + run: | + sudo chmod +x ./test.sh + ./test.sh + - uses: actions/upload-artifact@v2 + with: + name: Linux-Artifact + path: | + ./src/bin/x64/Release/net6.0/linux-x64/publish/*.bin + ./src/bin/x64/Release/net6.0/linux-x64/publish/*.so + ./src/bin/x64/Release/net6.0/linux-x64/publish/*.png + build-on-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 6.0.x + - name: Publish + run: ./test.cmd + - uses: actions/upload-artifact@v2 + with: + name: Windows-Artifact + path: | + .\src\bin\x64\Release\net6.0\win-x64\publish\*.exe + .\src\bin\x64\Release\net6.0\win-x64\publish\*.dll + .\src\bin\x64\Release\net6.0\win-x64\publish\*.png + build-on-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 6.0.x + - name: Publish + run: | + sudo chmod +x ./test.command + ./test.command + cd ./src/bin/x64/Release/net6.0/osx-x64/publish + zip -r -0 macOS-Artifact.zip *.app + mv *.zip ../../../../../../../. + - uses: actions/upload-artifact@v2 + with: + name: macOS-Artifact + path: macOS-Artifact.zip diff --git a/AvaloniaCoreRTDemo.sln b/AvaloniaCoreRTDemo.sln index e12cf41..24fd18d 100644 --- a/AvaloniaCoreRTDemo.sln +++ b/AvaloniaCoreRTDemo.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29911.98 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31912.275 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvaloniaCoreRTDemo", "src\AvaloniaCoreRTDemo.csproj", "{41A52A04-F7D8-4981-9546-DB020E54851D}" EndProject @@ -13,12 +13,12 @@ Global Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {41A52A04-F7D8-4981-9546-DB020E54851D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {41A52A04-F7D8-4981-9546-DB020E54851D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41A52A04-F7D8-4981-9546-DB020E54851D}.Debug|Any CPU.ActiveCfg = Debug|x64 + {41A52A04-F7D8-4981-9546-DB020E54851D}.Debug|Any CPU.Build.0 = Debug|x64 {41A52A04-F7D8-4981-9546-DB020E54851D}.Debug|x64.ActiveCfg = Debug|x64 {41A52A04-F7D8-4981-9546-DB020E54851D}.Debug|x64.Build.0 = Debug|x64 - {41A52A04-F7D8-4981-9546-DB020E54851D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {41A52A04-F7D8-4981-9546-DB020E54851D}.Release|Any CPU.Build.0 = Release|Any CPU + {41A52A04-F7D8-4981-9546-DB020E54851D}.Release|Any CPU.ActiveCfg = Release|x64 + {41A52A04-F7D8-4981-9546-DB020E54851D}.Release|Any CPU.Build.0 = Release|x64 {41A52A04-F7D8-4981-9546-DB020E54851D}.Release|x64.ActiveCfg = Release|x64 {41A52A04-F7D8-4981-9546-DB020E54851D}.Release|x64.Build.0 = Release|x64 EndGlobalSection diff --git a/src/AboutWindow.xaml b/src/AboutWindow.xaml deleted file mode 100644 index 14a606e..0000000 --- a/src/AboutWindow.xaml +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/AboutWindow.xaml.cs b/src/AboutWindow.xaml.cs deleted file mode 100644 index e749cf2..0000000 --- a/src/AboutWindow.xaml.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace AvaloniaCoreRTDemo -{ - public class AboutWindow : Window - { - public AboutWindow() - { - this.InitializeComponent(); - this.DataContext = new AboutWindowViewModel(); - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } - } -} diff --git a/src/AboutWindowViewModel.cs b/src/AboutWindowViewModel.cs deleted file mode 100644 index 424a18d..0000000 --- a/src/AboutWindowViewModel.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using Avalonia.Media.Imaging; -using ReactiveUI; - -namespace AvaloniaCoreRTDemo -{ - public class AboutWindowViewModel : ReactiveObject - { - private readonly IBitmap computerImage; - - public IBitmap ComputerImage => computerImage; - public String NCores => Environment.ProcessorCount.ToString(); - public String OS => RuntimeInformation.OSDescription; - public String OSArch => RuntimeInformation.OSArchitecture.ToString(); - public String OSVersion => Environment.OSVersion.ToString(); - public String ComputerName => Environment.MachineName; - public String UserName => Environment.UserName; - public String SystemPath => Environment.SystemDirectory; - public String CurrentPath => Environment.CurrentDirectory; - public String ProcessArch => RuntimeInformation.ProcessArchitecture.ToString(); - public String RuntimeName => RuntimeInformation.FrameworkDescription; - public String RuntimePath => RuntimeEnvironment.GetRuntimeDirectory(); - public String RuntimeVersion => RuntimeEnvironment.GetSystemVersion(); - public String FrameworkVersion => Environment.Version.ToString(); - - private String ComputerImageName - { - get - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return "windows.png"; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return "macos.png"; - else - return "linux.png"; - } - } - - public AboutWindowViewModel() - { - this.computerImage = GetImageFromResources(this.ComputerImageName); - } - - ~AboutWindowViewModel() - { - this.computerImage.Dispose(); - } - - private static Bitmap GetImageFromResources(String fileName) - { - Assembly asm = Assembly.GetExecutingAssembly(); - String resourceName = asm.GetManifestResourceNames().FirstOrDefault(str => str.EndsWith(fileName)); - if (resourceName != null) - using (Stream a = asm.GetManifestResourceStream(resourceName)) - return new Bitmap(a); - else - return null; - } - } -} diff --git a/src/App.axaml b/src/App.axaml new file mode 100644 index 0000000..e058919 --- /dev/null +++ b/src/App.axaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/App.axaml.cs b/src/App.axaml.cs new file mode 100644 index 0000000..42529d0 --- /dev/null +++ b/src/App.axaml.cs @@ -0,0 +1,87 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; +using Avalonia.Themes.Fluent; + +using AvaloniaCoreRTDemo.Interfaces; +using AvaloniaCoreRTDemo.Windows; + +namespace AvaloniaCoreRTDemo +{ + public sealed class App : Application, IThemeSwitch + { + private IStyle _baseLight; + private IStyle _baseDark; + + private IStyle _fluentLight; + private IStyle _fluentDark; + + private ApplicationTheme _currentTheme; + + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + this.Name = "AvaloniaCoreRTDemo"; + } + + public override void OnFrameworkInitializationCompleted() + { + this.InitializeThemes(); + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = +#if !OSX + new MainWindow(); +#else + new MainWindowMacOS(); +#endif + this.DataContext = desktop.MainWindow.DataContext; + } + base.OnFrameworkInitializationCompleted(); + } + + private void InitializeThemes() + { + this._baseDark = this.Styles[0]; + this._baseLight = this.Styles[1]; + + this.Styles.Remove(this._baseDark); + + this._fluentLight = (FluentTheme)this.Resources["fluentLight"]!; + this._fluentDark = (FluentTheme)this.Resources["fluentDark"]!; + + this._currentTheme = ApplicationTheme.DefaultLight; + } + + ApplicationTheme IThemeSwitch.Current => this._currentTheme; + + void IThemeSwitch.ChangeTheme(ApplicationTheme theme) + { + this._currentTheme = theme; + switch (theme) + { + case ApplicationTheme.DefaultLight: + this.Styles[0] = this._baseLight; + this.Styles.Remove(this._baseDark); + break; + case ApplicationTheme.DefaultDark: + this.Styles[0] = this._baseDark; + this.Styles.Remove(this._baseLight); + break; + case ApplicationTheme.FluentLight: + this.Styles[0] = this._fluentLight; + this.Styles.Remove(this._fluentDark); + if (!this.Styles.Contains(this._baseLight)) + this.Styles.Add(this._baseLight); + break; + case ApplicationTheme.FluentDark: + this.Styles[0] = this._fluentDark; + this.Styles.Remove(this._baseLight); + if (!this.Styles.Contains(this._baseDark)) + this.Styles.Add(this._baseDark); + break; + } + } + } +} \ No newline at end of file diff --git a/src/App.xaml b/src/App.xaml deleted file mode 100644 index 6029ed9..0000000 --- a/src/App.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/src/App.xaml.cs b/src/App.xaml.cs deleted file mode 100644 index 2610577..0000000 --- a/src/App.xaml.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Avalonia; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Markup.Xaml; - -namespace AvaloniaCoreRTDemo -{ - public class App : Application - { - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - } - - public override void OnFrameworkInitializationCompleted() - { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - desktop.MainWindow = new MainWindow(); - } - - base.OnFrameworkInitializationCompleted(); - } - } -} \ No newline at end of file diff --git a/src/ApplicationTheme.cs b/src/ApplicationTheme.cs new file mode 100644 index 0000000..2323998 --- /dev/null +++ b/src/ApplicationTheme.cs @@ -0,0 +1,12 @@ +using System; + +namespace AvaloniaCoreRTDemo +{ + public enum ApplicationTheme : Byte + { + DefaultLight = 0, + DefaultDark = 1, + FluentLight = 2, + FluentDark = 3, + } +} diff --git a/src/Assets/about.ico b/src/Assets/about.ico new file mode 100644 index 0000000000000000000000000000000000000000..b8d8f38726a32f2a88c961498160fa26b25cd698 GIT binary patch literal 9662 zcmeI2T~5M46os!3>JCi&3zT4D;tPojU@PFGe_MbxXl%m{jL*0PMH4ixLY~b4Vl<5R zmKhU^ftdy;GvSs@Pid#&yJt?P4FIr$uSx}QU4-jBfDHiH$A-J0hK=^8OL-!^_S*q* zRciW8IGawykrYX>F}s()M^pZQYxv<0w1!{&0oE|ZA6N~4@>31&nhdS2=9gf_wfUcD+yydGeAy$7Oy(=YPLFlH)$*r-%8e2j_ow>=*IgB0u>3qxrS! zsIM^P@5j-jBPN?%bxis3x1)0p`AJs`!|UMzTi?}qsL$$%Bs#VAU9+>Y1MC1h@ZSzhp3wocex@4$A4*4B8?*s1 fswyogB|!YA$B4A5w6Hw-rRpA&q7UQ>`DCi!FIqtd literal 0 HcmV?d00001 diff --git a/src/app.icns b/src/Assets/app.icns similarity index 100% rename from src/app.icns rename to src/Assets/app.icns diff --git a/src/app.ico b/src/Assets/app.ico similarity index 100% rename from src/app.ico rename to src/Assets/app.ico diff --git a/src/AvaloniaCoreRTDemo.csproj b/src/AvaloniaCoreRTDemo.csproj index a4682c4..26a4b5f 100644 --- a/src/AvaloniaCoreRTDemo.csproj +++ b/src/AvaloniaCoreRTDemo.csproj @@ -1,61 +1,61 @@ - + + - + WinExe - net5.0 - AnyCPU;x64 - app.ico + net6.0 + x64 + Assets/app.ico + true + + link + true + true + true + OSX - + - link false false true - - - + + + - - %(Filename) - - - Designer - - - - - - - - - - - - - - - - - - + + PreserveNewest PreserveNewest - - + + PreserveNewest - + + + + + + + + + + + + + + + $(AssemblyName) $(AssemblyName) com.$(username).$(AssemblyName) @@ -63,18 +63,51 @@ APPL $(AssemblyName) - app.icns + Assets/app.icns NSApplication true 1.0 true - - - - - link - - - - + + + + + + + + + + + + + App.axaml + + + + + + + MainControl.axaml + + + AboutWindow.axaml + + + + + + MainWindow.axaml + + + + + + + + MainWindowMacOS.axaml + + + + + \ No newline at end of file diff --git a/src/Controls/MainControl.axaml b/src/Controls/MainControl.axaml new file mode 100644 index 0000000..cdf5d75 --- /dev/null +++ b/src/Controls/MainControl.axaml @@ -0,0 +1,9 @@ + + + Welcome to Avalonia UI + NativeAOT! + + + + + diff --git a/src/MainWindow.xaml.cs b/src/Controls/MainControl.axaml.cs similarity index 50% rename from src/MainWindow.xaml.cs rename to src/Controls/MainControl.axaml.cs index d170c3e..59d7185 100644 --- a/src/MainWindow.xaml.cs +++ b/src/Controls/MainControl.axaml.cs @@ -1,21 +1,21 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; using Avalonia.Markup.Xaml; -namespace AvaloniaCoreRTDemo +using AvaloniaCoreRTDemo.Controls.ViewModels; + +namespace AvaloniaCoreRTDemo.Controls { - public class MainWindow : Window + public sealed partial class MainControl : UserControl { - public MainWindow() + public MainControl() { InitializeComponent(); - this.DataContext = new MainWindowViewModel(this); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); + this.DataContext = new MainViewModel(); } } -} \ No newline at end of file +} diff --git a/src/Controls/ViewModels/MainViewModel.cs b/src/Controls/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..ee69584 --- /dev/null +++ b/src/Controls/ViewModels/MainViewModel.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; +using System.Runtime.CompilerServices; + +using Avalonia.Media.Imaging; + +using ReactiveUI; + +namespace AvaloniaCoreRTDemo.Controls.ViewModels +{ + internal sealed class MainViewModel : ReactiveObject + { + private readonly IBitmap _dotNetImage; + private readonly IBitmap _avaloniaImage; + + public IBitmap DotNetImage + { + get { return _dotNetImage; } + set { this.RaiseAndSetIfChanged(ref Unsafe.AsRef(this._dotNetImage), value); } + } + + public IBitmap AvaloniaImage + { + get { return _avaloniaImage; } + set { this.RaiseAndSetIfChanged(ref Unsafe.AsRef(this._avaloniaImage), value); } + } + + public MainViewModel() + { + this._dotNetImage = new Bitmap(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "dotnet.png")); + this._avaloniaImage = new Bitmap(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "avalonia.png")); + } + } +} diff --git a/src/linux.png b/src/Images/linux.png similarity index 100% rename from src/linux.png rename to src/Images/linux.png diff --git a/src/Images/linux_d.png b/src/Images/linux_d.png new file mode 100644 index 0000000000000000000000000000000000000000..1ab908b5d5beb8f14ffced2c54f6ef09311d92ec GIT binary patch literal 9021 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 literal 0 HcmV?d00001 diff --git a/src/macos.png b/src/Images/macos.png similarity index 100% rename from src/macos.png rename to src/Images/macos.png diff --git a/src/Images/macos_d.png b/src/Images/macos_d.png new file mode 100644 index 0000000000000000000000000000000000000000..3166c5dbc3f2aeed2c864f9d77d2520cf869a5c2 GIT binary patch literal 6545 zcmZ`e_dnI&|L0y?$j!*e=q5t8L^8@X$_y{c&d6TbSy#$lH?EP9Y**PK^IBQS-ohK% zTehpO&mZvp;hg8=%;$MNpXWKw^Mq?@C{t6iPyzs`pQxa;FCqGWg`D)VZk2u6a|vK> z+RBeX@vj@J05GdQK|Rv-HrX`xNt;&02F_+|sci+nyFphha+9@%B|02^g&f1c5JkCx zq7~A$+m?uj*g0Q!l~UPUBi{R1h2(* zkhM5eCpdmiz(QGq8jp<(?g{E61+mHGisN|x+n?;&0WzYwOGu7f*TW8pkzD+gyhd zK_DMOPXmu|x^_T%^e=S02)E6M=}}xfrc&b|lIIQX}BP>iLvEF~YsgEeXXNV;;9)q!Tv!<>Wl$<%h>rz1>eZ40cmeX8d>B zHPw=@5zN_#UTdM2z)Ekn!w6%cG#JM^GPFMa3xf!aVMfWz4l*=z3_gipC(S*GK*X-P zfU4FP8F`3<9y7KqtwTgaR4pzpZj|PHVke+a2AIT&sUuG2HvsNUXjqzQC)aP^p2Wmz zqx$1tj~ferl;5L9V~XEs+G`PN!u-DDyH;n*0y-*}u)Pfe0`{zvlX^T?&KvC_#_oa; z?k4Z0qBSI9Lc7gjnsN^5_L^y6R0lP6n~=i|i}T+biuIqUB7q1EVRr@IW_Q5HCH|%( z15Qsvj!%c;-``1BtTf!yw4(tRV!jWQ2iX)Rx>?p|u3q`GQvB7}i>=IN&8M}g!tuHg zhH_Jtw|k5Ul#BWFvm_Uj8(jGuXoXwzMA!-@gnP~YZA)_#GCua(@fu}<(nJkxOrJh$ z%mZ<%|8-vnxKlgC$tNV!F$~-%{bsS=OKI3E1zl5Xn0Y*gX=iL=H6s<0d(gzKWIxjcJ?SRqLAjAM*su z3<3px6+C5b&+4`E>eGQv>w`0Fb8}OuGI`+|1+x4zIK1#_4RPeja33tIBrK~>RE5{` zQ+zg~hH)OP^@W<^PGu1%v3}hVoqedqXmGlB_YtQ?>RSPb@1`gxBKb1YYkc>-RR?)ZZ$mV|-rP8)(|Jb$cJt96NbzuURocGgUFQ@D zrZ_{Pi;ZpS>UnVq&tYddR*W33;3V_@t@DZ@Yk0!?aNO*8w+^lw7AL#%ZC8EagAVBu z=cKTDe?RBozm}k1jn+cpB`X?2g*FH}#PIz2Aon15iuyN!@${`|i5*i$t-^YGbyXwZ$ zM~h%*TQ1f9fqjwpEXyn`cJdW}iZDNAI}Eyv94NM-UvJY`5wplTRBQ z{oi%wKEnAKpp@DclR(d9WiP`KQXcQlo0L&bq^IhyP!(%eAxoG*0 zwIvrfJ~goaEyaz;ucqzi{SoTF-0?Ne>kH{k(|gAk(Z#w4 z(L5g#Hum-P;5WeJkBU+@Fi12DBMX)Y#F-StAGXWloa8U8NyvkZ|tvu(jeX$o9k}*#u-A1ZiZdW zD1r{uk*}S3B@Kc-+#@>8LC|&8R&mbvq+m?B7?&STj?ErWi{kaW4LX38II-KGh6Kib zwI~`25CN$lY1V9~K)-R_`+o%yymg}x(aI?!I`GC!-{1p$`R+||&@ZaYpN89eaF3yB zK%+jy^B!6*mJ(>C-Htz9RY9%9ux+|CgBjUh@*X@eRO3u-*{Z^>6+Iz4{TGpoz9;wa zO+d4?Z7gkNkiGtqIa*%wI{x=}G81c{HF8UFv2dEGWc?ZkiHm>0tuJ!GPaP%uVRml4 zd^jCY#szo~1GM~nW<7Fn(i^c3uZCxqWCNTK5GGFj zJf4lYfX_Koa(Mg0(4V!1c+)66cH7?rCm}yB4>*8ilu#LW(_kLEir2GK(2s+kN*$iU zfI{tSCYd$!@?g?3Qv6gTt1$u0=^$T2u4)A*C4z7ld)Mp&4&Y~qsyBL&9cMy1ceOpb zE&$+dIFtxc67w=7uMqy9Yl6nE0`I0RY`e(kyrwsBe9OGDmK+%8LYkPrf2I@&cFN~F zj4nI)khh*wv{#h2n8v{H(!Ds*EyqXid9ttdI>Sn9jziX!;lk5}j2$lkxaI4J<3jo?unEm;rOa0XyT^PpUVOa2T=hIq?8lkS71%F7%W6W==NCBN0w6p zXDTSI1&+5uzIJR^Hk4tTyLWO0&{8qvmI9ZAFw4#eXkb^rb~o@kh97nVr>p!JhMpju zRLog2of2$Ci8xM=DK2sYzn(j~lyrbYl!)2`eQ$Ui12SmpnqI=uCGOO!;f#mqqRIv` zSQ-@Gc&T<3)Rp0#a=PZ3(9BXQnAQ)M4WULq{^U?Ztl8q^#({2aaT>0=&Ih9C=R69N z^z87^xS%FX4P%PH5d*#O&R9Ot$u$ReC9T@=(pr`Wm-Ebifh8KI&9>``f`wdkd?e_B zG~Vg-?THl8kWi9{2!L0CDN%};+f)vY!E_&bnB0cp4O?#oq*o{nw0hCzgO#uD|M*G4 zEs_gWl%D=a3I>~h+TcEv`TM219t-gM9O?Azhz)Nyrz&+;$}B(P;LkvU#=tA71|Lfr z*dTyOY6&Gy5>SzEiC>k_Bhl#|57&ep`!TNz)vVdUKx_LkT3aSGwwmbqvmGkzNiYsv zu0Gh}F&n%ygDK#{u>dAoVZXGOiw8;qpr#jMgBUJ1JI?xHh>Ou__bT9{l1-^K^#H=0 z1xZQm0Wfr5pS}RZ513@y=-u{<65YN_ANm5JKI1Rtw{h2i66H=%*KA_HeKI{pE@k5n zRih<5-xVpUii#0%6)oyOEsl(MGyJ?-jUz$BnspH`3xbL_H8rnDF=z^nK?aOu2!5z z3vD$=a!tV7%)EkEU`~a|L_+2n>Q4DBZWxJ}o0A~Va__ozWE3isE>h^N1Buqq_RY~7 zpj)cz)>lKwws&!p$LZ#+{w+*pB-Lj4t*+b3fO69P@}Igc6B0Co_?}><0CnY+U{B zc|q7u&w!<;=NV!BH28M|)1)=G>&52&BzVEiCSK;?oFzK+&saCF*UO)lhPkk&a3UQ( z=_x5O%#zQUMlg3Xfrz<}Qfxc5zVZ7fzQB)1#{+jNfq1cSMj!^n&mwfO+QC!pmiw_+ zf!W7&dF_$b2CpR$pd$0 zh+0LY($7l)H-9i!HY9sbl~=SV?YxT|h&}PlqVztN6uYW>@A735GE$`P*2l*x!e(!8 z&yT@&w@H2YF@1V^c4o>R+0O2@R>y+R7aQqw-gMse_RFlRJ1eK4_UG4O7Qf6B8Llg*O$T=#(V9E!XO6hS+pzuU~TW zVYe=R-GB-8oGEm9&X4UG9F5H*JqAcOX$A|76trF+^3)^8N z;Ce>N5X|dk4h+BQZg8_&8?t&n^rp37%KO_BXtkkE12yws@ z^5h>Lq^GAVt7kksDSv)f=`64xy2oC`zF2d78jAZRepa#R#*Yr{Y5S`dpN-?7#W+uH z*Vfi*WIXV|C8=mbay^wIOVt{{Vd$0|n8#uCX5rNE>>gjit;EE{;qyE&SMpFtkw75y zSd=k+Ga5`%Tr^d`;LyLh7r1p>U4oLPDfh*FIznMX1Hp=4?@MOct!Q1WkjstDGwWi# zK*|Si&Jy9?Ay(1k`M)re-MRO-!$({#&WdKeFZiO$f!Y6nYZ7mp6ljdZjI%^5o#;MP$ViGH7Yly&)9^uiJsbYW zUCMjQx1Rk6_K1BJyK4xHZuGl0D)?z2F*N~W#l}|;j*jG*!%-$oOiamA&KKuUJdcLE z{(=S2c4jR|YReP!E3MKwLMd%nN^KwA3fQUX5n(|LKh3t0nRfB*iSzVABU7Wk_9 z;^Jc6qHH{ry3bPyiC}LZ3@Z}Npb^*H-gPFyeXM24$2nqDxI7*yL8~zTY@J=r6*3rp z!7PG&V@N|MHg!WdDrX1mK*2(^| z^KSH+iAK~EK88(tiOpm9nV)R*g=bQj(`on6&#a4OE1Is)KJg;oTYppjv|lV!b3MSF z9wGO1rk-D>2W~S)5+)tbwVbz8xg{jZn-a-!M-vUQz!dCO2RRfbh=SrqTF!KZA?R-noldp}9jj259A5qTv%6g9u zj;79gzki!k(OGHO#O!a)+KYt?=ete&esW=%robCgps;mg+}Far?um;Zb7(DOFn#xlM|0|5v5fgSE zmn*VcyFGk~zrjZQDUoJ`klzG1KayFJ1qtn>^_x|7(1^YXWAg463E)e%EoN^o0KLF~`V0XnhLD|MFn=c8PH9_EUThdObm#F|Vw9zeTr4Lax; z*8k}&N35mQ!gn2XS?=XsvO;l?H*jYZ0`ek#A6wj-HQO+XD-F=15M0P-9J%>vT=^E0)MW zX(|A&!+8=Cy1BOaiXM4*d3rX!{1L_cm-Y=oxNYcHiyv2z=@a!S_&E|;Km;>H9|>zS zf}Qc|>0u8FbZP~_GeibmlZSjZ7D=u>FnB#s0=#$MDRr3bgpf2?H+l$IU33?4eP4Ds zVtxqyrVU4Fug($%#bUpTlW-%xU%~t2eX><5RWr=Q4aUg(zReW^oX+Kg!W47*Mv^v1 zOmt|YCSuOLj7~VXujn8MY9a=N!A{L$kC@8#I04z>OFW%uL!HCWE3Ly5-({+p{p<>m zeYu`7VX9-Zl8oC+K4EU9BlZ1RnV-T5N{#xZjG>N5a*taPo*#~WnDxWmVYFuI9u=i> zM4a|a4I}59=cIf=^a$1R@pU3D`8z-h+De+fFJMfLRxT75ZDy`ib{Ubo5srE&Fe?PR zylPSdHhNv#N(YAAjgyY8|;zTy^s=YrSFBu>;Jmfa97*1!~zTasq z)zHwuVejCO^Wx*86HHgzm~D;{j6LW7%%QS1I~rtl&bcM>S_VBVA|OwhGof>X({)hN zCubQZT*?&>PoBI6=K}hEd`&xUeKOVVThreIE6-hi-~;-Zu%fX1Q#+oA_|*kAdmGVg zl;rtNYuW2R-EE^^4X@uds)-wwtxF1l0hT8l*{b1(hlejN-HK$X)9+f{R|tbvZ}>!o zb)WL9Uum|tY_-9hBwcu$0yQnZ)%omVn% z#d9`)*BwEjY@W$y=wTk1pNP$Yq03b5Iapa;m3no4ysP$o`I)cp*}x?_@9w?#54&tB zDx;tGmA`}NJ_)KXsfZ>{h^9UQk>C@QQ^i#;(Z{GGgs2;4$<4Aqa+Gp{rI9Dec2Ia&oI}`{yQCI30A9a;=+Fkb4UCGobKz`i4;ymS zeos|7rZ$*0!$X2y@2j&J*VWY-UU~!i#ivyrb?eu{+l=eHwr9qNhZ9(qi;e4I9PI3} zsNYrR)(g0pnPpm^9d5dfeR;8PqHkbm%gxk6U%k#5O0T*|he%jboV=vU?{At^YXV+A znWnhbJT}M+a4u}LUFJ5;lX0@r!on_IqA44fBI(H*C};>#Ka*m(`-Ye3$frqeM*LB= zP;7kESBt|f5+&_(5)d2L`m+-ERPk?iW0_b8+C+L(L7B;T_OQk}(u4-BJR`0R@qIs^ z(eb1fDI#yk|H-QY2FzDpLF=1aTe2R5IZt9=WG7ypk`158E6IJCL0Dd0sQBIe3@WDL ze=YN#=gM=Vt1mvnt+~-1(m&>`v#!F9xrwOL;X1+de;}+LiY24GeXoMWZbI35@G^Kp zNGEWf#XMYH2&MKXdu zgnf8-o*icG@BZ}nr|6`U%2rVlRkR;JJHI~b4JIOa$DcYAZ=^`kE`{Z9Q Y$67k!8vpCdKNj#rQ3F-{*zE290T8Wa@c;k- literal 0 HcmV?d00001 diff --git a/src/windows.png b/src/Images/windows.png similarity index 100% rename from src/windows.png rename to src/Images/windows.png diff --git a/src/Images/windows_d.png b/src/Images/windows_d.png new file mode 100644 index 0000000000000000000000000000000000000000..a9890c9bd5c16c05ea3b580a6aa6f2e679daa6bb GIT binary patch literal 2152 zcmaJ@`#+TH7JuG%uHH-ntjdOn}E)_48p!nUIz8VCS@ zV$ZU5Q)u-2Arlqyfz?kv3I)Zw+1Y@)e$9CR$U646)|^EDcO^ajTMnyaIG%o5UR#>QoxdP>t8S01j`EM)U@#4J=wU1{Jw7NoOg%>A$Mo3|i>#}{qY_o#RkvkCwMhlKt;k-rn-_a-KU00P9x(djmOH^)mSA6=QUAl@A_kAKKk89UJ*gR%{9+x= zt>c$Eq`l<*eygwZQ4nX{iP37M=XgxqW;VWUix&;7U0Ws)`01dUkpg|IcKau(A6Ex|E2qy!ZT#Eq9e}t-n?cyS>$`a4-ugNHrif3Z>8G=R&0t@== ze5e*gtDP^h*7GfdVb|^w_$VCskQL2s*RsTA0CG0(zN0Cj619im7e^tOy|cH{gV0Kr zRM=?h5XktQUXUVMjaCs0%EvW+d?&eY(K&?z0bz|~h#iG=!+)qBkrX70dslqDNZMU)=-TlrpXRp^Gg=!HkpUZl^s5f)1${Ber8><*9w$7P&EGeTS;U0Dkq+Q0I_M3h!91*L$hrB8 zU?IPTZ*Yafc=U7RLfKdG3#;t8`ruDK`9;1U5+u^SlR6Wg^}T(!8MIp7o;o9ni3;G; z2EGO?760dsh3t4&kQA5SxFKb{_iizSeo+1Qwt!ujIv5!rmQ%p~EKPYOn29ZpsX}>F zNwy%>qyp6A5}m=9_Yh=ksZoGqL5bJfsDbgfJFkJ0iF6i{(^~HiAY)zRq&v#%xA6Dt zc(dDEZppN-MH_+>&IWIwfg_b59^`*a+h~ZDYqMS|DkkkvKlB>xn6DgSu7o+|<9b~c zA<8u+#R8|)^f9r6#Vi{UR+HQqipr4W!~%#0JRKoDtypz(DGy6xU;hrduw7V5zf=h> zB-8#p5Ebkmvbj|RHs!*@kjs^PQkfu|1sN5L4gYmn zYDEN|nwR!P(I`mO@uiiV7J2-P?=%f~S_$QQ|(fw+6-Cz@cI%EY9HVyFZ_T|3o7?Y8L0 zgFC$O>3_*TyM@#;z`)d)lElU0bk-kVCivPdxMM?V&Wg^YT{h#1OX4F4(c7(KEUoX) zorT^)LaV`RL4sjJkA_CT<#4xp^v5eRwm^fona$ke?>IT@~6&o-3h6>8#z zIBdMId)lO-M)%#MJO~8r8P178{;UE{DQ~1cs+x{$#?|PsBqHWV7acg>A_~BLh|H61 za7pk5FYx~qml7~bwENyco5e)77GV@YLvtqEy_<|pt|l8VOE~U&wU*i)jSk=k-4oKU zobIuiuv3gZ~eN|J9y@V>zQSzQ_pL?yZ16)KDqn{Czle@@)KJQmmQjWAYQ$& zvv-gFOf}D)y*s77ueQDP;^~HwlhqUA8fD-6?43slq?X~f<4-@T0;eBG4<~Ww%#!wH zaM~1hwV_w+8t_U7jhGq|w`t7e+bl_rY+B3?J5Vyvrtap~(wj{>ky$tO9-ok9Pk5DzLOTebkzzVS{ zaCN2(vw^ay@NuM)f=Y>D27stB8rt%mHgQcdlEIi~`;^pRvto4s_J`TFbvA+N{{@RN BtNs80 literal 0 HcmV?d00001 diff --git a/src/Interfaces/IMainWindow.cs b/src/Interfaces/IMainWindow.cs new file mode 100644 index 0000000..2f8b2a8 --- /dev/null +++ b/src/Interfaces/IMainWindow.cs @@ -0,0 +1,9 @@ +using AvaloniaCoreRTDemo.Interfaces; + +namespace AvaloniaCoreRTDemo +{ + public interface IMainWindow + { + IThemeSwitch ThemeSwitch { get; } + } +} diff --git a/src/Interfaces/IThemeSwitch.cs b/src/Interfaces/IThemeSwitch.cs new file mode 100644 index 0000000..45815dc --- /dev/null +++ b/src/Interfaces/IThemeSwitch.cs @@ -0,0 +1,8 @@ +namespace AvaloniaCoreRTDemo.Interfaces +{ + public interface IThemeSwitch + { + ApplicationTheme Current { get; } + void ChangeTheme(ApplicationTheme theme); + } +} diff --git a/src/MainWindow.xaml b/src/MainWindow.xaml deleted file mode 100644 index 915b90a..0000000 --- a/src/MainWindow.xaml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - Welcome to Avalonia UI + CoreRT! - - - - - - - diff --git a/src/MainWindowViewModel.cs b/src/MainWindowViewModel.cs deleted file mode 100644 index fad3eb4..0000000 --- a/src/MainWindowViewModel.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Avalonia.Media.Imaging; -using ReactiveUI; -using System; -using System.Reactive; -using Path = System.IO.Path; - -namespace AvaloniaCoreRTDemo -{ - public class MainWindowViewModel: ReactiveObject - { - public MainWindowViewModel(MainWindow window) - { - _window = window; - - DotNetImage = new Bitmap(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "dotnet.png")); - AvaloniaImage = new Bitmap(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "avalonia.png")); - - FileExitCommand = ReactiveCommand.Create(RunFileExit); - } - - public IBitmap DotNetImage - { - get { return dotNetImage; } - set { this.RaiseAndSetIfChanged(ref this.dotNetImage, value); } - } - - public IBitmap AvaloniaImage - { - get { return avaloniaImage; } - set { this.RaiseAndSetIfChanged(ref this.avaloniaImage, value); } - } - - public ReactiveCommand FileExitCommand { get; } - public void HelpAboutMethod() => RunHelpAbout(); - - void RunFileExit() - { - Environment.Exit(0); - } - - void RunHelpAbout() - { - new AboutWindow().ShowDialog(_window); - } - - private IBitmap dotNetImage; - private IBitmap avaloniaImage; - private readonly MainWindow _window; - } -} \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs index a90b6af..0bde694 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,4 +1,5 @@ -using Avalonia; + +using Avalonia; namespace AvaloniaCoreRTDemo { @@ -7,13 +8,14 @@ namespace AvaloniaCoreRTDemo // Initialization code. Don't use any Avalonia, third-party APIs or any // SynchronizationContext-reliant code before AppMain is called: things aren't initialized // yet and stuff might break. - public static void Main(string[] args) => BuildAvaloniaApp() - .StartWithClassicDesktopLifetime(args); + public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); // Avalonia configuration, don't remove; also used by visual designer. public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() - .UsePlatformDetect() - .LogToTrace(); + => AppBuilder.Configure().UsePlatformDetect() +#if OSX + .UseAvaloniaNative() +#endif + .LogToTrace(); } } diff --git a/src/Windows/AboutWindow.axaml b/src/Windows/AboutWindow.axaml new file mode 100644 index 0000000..8ef32d8 --- /dev/null +++ b/src/Windows/AboutWindow.axaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Windows/AboutWindow.axaml.cs b/src/Windows/AboutWindow.axaml.cs new file mode 100644 index 0000000..4b9ca92 --- /dev/null +++ b/src/Windows/AboutWindow.axaml.cs @@ -0,0 +1,28 @@ +using System; + +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +using AvaloniaCoreRTDemo.Windows.ViewModels; + +namespace AvaloniaCoreRTDemo.Windows +{ + public sealed partial class AboutWindow : Window + { + public AboutWindow() + { + this.InitializeComponent(); + } + + public AboutWindow(Boolean darkTheme) + { + this.InitializeComponent(darkTheme); + } + + private void InitializeComponent(Boolean darkTheme = default) + { + AvaloniaXamlLoader.Load(this); + this.DataContext = new AboutViewModel(darkTheme); + } + } +} diff --git a/src/Windows/MainWindow.axaml b/src/Windows/MainWindow.axaml new file mode 100644 index 0000000..f912199 --- /dev/null +++ b/src/Windows/MainWindow.axaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Windows/MainWindow.axaml.cs b/src/Windows/MainWindow.axaml.cs new file mode 100644 index 0000000..a846014 --- /dev/null +++ b/src/Windows/MainWindow.axaml.cs @@ -0,0 +1,28 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +using AvaloniaCoreRTDemo.Interfaces; +using AvaloniaCoreRTDemo.Windows.ViewModels; + +namespace AvaloniaCoreRTDemo.Windows +{ + public sealed partial class MainWindow : Window, IMainWindow + { + public MainWindow() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + this.DataContext = new MainViewModel(this); + } + + IThemeSwitch IMainWindow.ThemeSwitch => App.Current as IThemeSwitch; + } +} diff --git a/src/Windows/MainWindowMacOS.axaml b/src/Windows/MainWindowMacOS.axaml new file mode 100644 index 0000000..4ea0793 --- /dev/null +++ b/src/Windows/MainWindowMacOS.axaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Windows/MainWindowMacOS.axaml.cs b/src/Windows/MainWindowMacOS.axaml.cs new file mode 100644 index 0000000..9c25bcc --- /dev/null +++ b/src/Windows/MainWindowMacOS.axaml.cs @@ -0,0 +1,28 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +using AvaloniaCoreRTDemo.Interfaces; +using AvaloniaCoreRTDemo.Windows.ViewModels; + +namespace AvaloniaCoreRTDemo.Windows +{ + public partial class MainWindowMacOS : Window, IMainWindow + { + public MainWindowMacOS() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + this.DataContext = new MainViewModel(this); + } + + IThemeSwitch IMainWindow.ThemeSwitch => App.Current as IThemeSwitch; + } +} diff --git a/src/Windows/ViewModels/AboutViewModel.cs b/src/Windows/ViewModels/AboutViewModel.cs new file mode 100644 index 0000000..ee4b0a9 --- /dev/null +++ b/src/Windows/ViewModels/AboutViewModel.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + +using Avalonia.Media.Imaging; + +using ReactiveUI; + +namespace AvaloniaCoreRTDemo.Windows.ViewModels +{ + internal sealed class AboutViewModel : ReactiveObject + { + private readonly IBitmap _computerImage; + private readonly Boolean _darkTheme; + + public IBitmap ComputerImage => _computerImage; + public static String NCores => Environment.ProcessorCount.ToString(); + public static String OS => RuntimeInformation.OSDescription; + public static String OSArch => RuntimeInformation.OSArchitecture.ToString(); + public static String OSVersion => Environment.OSVersion.ToString(); + public static String ComputerName => Environment.MachineName; + public static String UserName => Environment.UserName; + public static String SystemPath => Environment.SystemDirectory; + public static String CurrentPath => Environment.CurrentDirectory; + public static String ProcessArch => RuntimeInformation.ProcessArchitecture.ToString(); + public static String RuntimeName => RuntimeInformation.FrameworkDescription; + public static String RuntimePath => RuntimeEnvironment.GetRuntimeDirectory(); + public static String RuntimeVersion => RuntimeEnvironment.GetSystemVersion(); + public static String FrameworkVersion => Environment.Version.ToString(); + + private String ComputerImageName + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return !_darkTheme ? "windows.png" : "windows_d.png"; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return !_darkTheme ? "macos.png" : "macos_d.png"; + else + return !_darkTheme ? "linux.png" : "linux_d.png"; + } + } + + public AboutViewModel(Boolean darkTheme) + { + this._darkTheme = darkTheme; + this._computerImage = GetImageFromResources(this.ComputerImageName); + } + + ~AboutViewModel() + { + this._computerImage.Dispose(); + } + + private static Bitmap GetImageFromResources(String fileName) + { + Assembly asm = Assembly.GetExecutingAssembly(); + String resourceName = asm.GetManifestResourceNames().FirstOrDefault(str => str.EndsWith(fileName)); + if (resourceName != null) + using (Stream bitmapStream = asm.GetManifestResourceStream(resourceName)) + return new Bitmap(bitmapStream); + else + return default; + } + } +} diff --git a/src/Windows/ViewModels/MainViewModel.cs b/src/Windows/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..1a72964 --- /dev/null +++ b/src/Windows/ViewModels/MainViewModel.cs @@ -0,0 +1,72 @@ +using System; +using System.Reactive; + +using Avalonia.Controls; + +using ReactiveUI; + +namespace AvaloniaCoreRTDemo.Windows.ViewModels +{ + internal sealed class MainViewModel : MainViewModelBase + where TWindow : Window, IMainWindow + { + private readonly TWindow _window; + + private Boolean _defaultLightEnable = true; + private Boolean _defaultDarkEnable = true; + private Boolean _fluentLightEnable = true; + private Boolean _fluentDarkEnable = true; + + public Boolean DefaultLightEnabled + { + get => this._defaultLightEnable; + set => this.RaiseAndSetIfChanged(ref this._defaultLightEnable, value); + } + + public Boolean DefaultDarkEnabled + { + get => this._defaultDarkEnable; + set => this.RaiseAndSetIfChanged(ref this._defaultDarkEnable, value); + } + + public Boolean FluentLightEnabled + { + get => this._fluentLightEnable; + set => this.RaiseAndSetIfChanged(ref this._fluentLightEnable, value); + } + + public Boolean FluentDarkEnabled + { + get => this._fluentDarkEnable; + set => this.RaiseAndSetIfChanged(ref this._fluentDarkEnable, value); + } + + public ReactiveCommand FileExitCommand { get; } + + public MainViewModel(TWindow window) + : base(window.ThemeSwitch) + { + this._window = window; + this.FileExitCommand = ReactiveCommand.Create(RunFileExit); + this.ChangeTheme(window.ThemeSwitch.Current); + } + + public void DefaultLightMethod() => this.ChangeTheme(ApplicationTheme.DefaultLight); + public void DefaultDarkMethod() => this.ChangeTheme(ApplicationTheme.DefaultDark); + public void FluentLightMethod() => this.ChangeTheme(ApplicationTheme.FluentLight); + public void FluentDarkMethod() => this.ChangeTheme(ApplicationTheme.FluentDark); + public void HelpAboutMethod() => base.RunHelpAbout(this._window); + + private void RunFileExit() + => Environment.Exit(0); + + private void ChangeTheme(ApplicationTheme theme) + { + this.DefaultLightEnabled = theme != ApplicationTheme.DefaultLight && theme != ApplicationTheme.FluentLight; + this.DefaultDarkEnabled = theme != ApplicationTheme.DefaultDark && theme != ApplicationTheme.FluentDark; + this.FluentLightEnabled = theme != ApplicationTheme.FluentLight; + this.FluentDarkEnabled = theme != ApplicationTheme.FluentDark; + this._window.ThemeSwitch?.ChangeTheme(theme); + } + } +} diff --git a/src/Windows/ViewModels/MainViewModelBase.cs b/src/Windows/ViewModels/MainViewModelBase.cs new file mode 100644 index 0000000..9c9f2c6 --- /dev/null +++ b/src/Windows/ViewModels/MainViewModelBase.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Avalonia.Controls; + +using AvaloniaCoreRTDemo.Interfaces; + +using ReactiveUI; + +namespace AvaloniaCoreRTDemo.Windows.ViewModels +{ + internal abstract class MainViewModelBase : ReactiveObject + { + private readonly IThemeSwitch _themeSwitch; + private Boolean _aboutEnable = true; + + public Boolean AboutEnabled + { + get => this._aboutEnable; + set => this.RaiseAndSetIfChanged(ref this._aboutEnable, value); + } + + public MainViewModelBase(IThemeSwitch window) + => this._themeSwitch = window; + + protected async void RunHelpAbout(Window currentWindow) + { + if (this.AboutEnabled) + try + { + this.AboutEnabled = false; + await new AboutWindow(IsDarkTheme(this._themeSwitch.Current)).ShowDialog(currentWindow); + } + finally + { + this.AboutEnabled = true; + } + } + + private static Boolean IsDarkTheme(ApplicationTheme? theme) + => theme switch + { + ApplicationTheme.DefaultDark => true, + ApplicationTheme.FluentDark => true, + _ => false, + }; + } +} diff --git a/src/nuget.config b/src/nuget.config index 8afae99..1dfbeae 100644 --- a/src/nuget.config +++ b/src/nuget.config @@ -1,11 +1,7 @@ - - - - - + diff --git a/src/rd.xml b/src/rd.xml index fa7ccfe..8e5aae8 100644 --- a/src/rd.xml +++ b/src/rd.xml @@ -9,6 +9,9 @@ + + +