From 5851899dee1dfe35023ce4829b1d8f833a02c07a Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sun, 28 May 2023 02:20:25 -0400 Subject: [PATCH 01/60] First Commit. WIP native WinUI Interface --- .gitignore | 9 +- QuickDraw.sln | 38 +- QuickDrawWindows/App.xaml | 22 +- QuickDrawWindows/App.xaml.cs | 142 +++---- QuickDrawWindows/AssemblyInfo.cs | 10 - .../Assets/LockScreenLogo.scale-200.png | Bin 0 -> 432 bytes QuickDrawWindows/{ => Assets}/QuickDraw.ico | Bin QuickDrawWindows/Assets/QuickDraw256x256.png | Bin 0 -> 7712 bytes .../Assets/SplashScreen.scale-200.png | Bin 0 -> 5372 bytes .../Assets/Square150x150Logo.scale-200.png | Bin 0 -> 1755 bytes .../Assets/Square44x44Logo.scale-200.png | Bin 0 -> 637 bytes ...x44Logo.targetsize-24_altform-unplated.png | Bin 0 -> 283 bytes QuickDrawWindows/Assets/StoreLogo.png | Bin 0 -> 456 bytes QuickDrawWindows/Assets/TitleBar16x16-200.png | Bin 0 -> 1397 bytes QuickDrawWindows/Assets/TitleBar16x16.png | Bin 0 -> 918 bytes .../Assets/Wide310x150Logo.scale-200.png | Bin 0 -> 2097 bytes QuickDrawWindows/DialogCenteringService.cs | 227 ----------- QuickDrawWindows/FolderDialog.cs | 313 --------------- QuickDrawWindows/Installing.xaml | 13 - QuickDrawWindows/Installing.xaml.cs | 34 -- QuickDrawWindows/MainPage.xaml | 128 ++++++ QuickDrawWindows/MainPage.xaml.cs | 300 ++++++++++++++ QuickDrawWindows/MainWindow.xaml | 59 ++- QuickDrawWindows/MainWindow.xaml.cs | 367 ++---------------- .../PublishProfiles/win10-arm64.pubxml | 20 + .../PublishProfiles/win10-x64.pubxml | 20 + .../PublishProfiles/win10-x86.pubxml | 20 + .../Properties/launchSettings.json | 10 + QuickDrawWindows/QuickDraw.csproj | 138 ++++--- QuickDrawWindows/SlidePage.xaml | 23 ++ QuickDrawWindows/SlidePage.xaml.cs | 291 ++++++++++++++ QuickDrawWindows/app.manifest | 20 + 32 files changed, 1079 insertions(+), 1125 deletions(-) delete mode 100644 QuickDrawWindows/AssemblyInfo.cs create mode 100644 QuickDrawWindows/Assets/LockScreenLogo.scale-200.png rename QuickDrawWindows/{ => Assets}/QuickDraw.ico (100%) create mode 100644 QuickDrawWindows/Assets/QuickDraw256x256.png create mode 100644 QuickDrawWindows/Assets/SplashScreen.scale-200.png create mode 100644 QuickDrawWindows/Assets/Square150x150Logo.scale-200.png create mode 100644 QuickDrawWindows/Assets/Square44x44Logo.scale-200.png create mode 100644 QuickDrawWindows/Assets/Square44x44Logo.targetsize-24_altform-unplated.png create mode 100644 QuickDrawWindows/Assets/StoreLogo.png create mode 100644 QuickDrawWindows/Assets/TitleBar16x16-200.png create mode 100644 QuickDrawWindows/Assets/TitleBar16x16.png create mode 100644 QuickDrawWindows/Assets/Wide310x150Logo.scale-200.png delete mode 100644 QuickDrawWindows/DialogCenteringService.cs delete mode 100644 QuickDrawWindows/FolderDialog.cs delete mode 100644 QuickDrawWindows/Installing.xaml delete mode 100644 QuickDrawWindows/Installing.xaml.cs create mode 100644 QuickDrawWindows/MainPage.xaml create mode 100644 QuickDrawWindows/MainPage.xaml.cs create mode 100644 QuickDrawWindows/Properties/PublishProfiles/win10-arm64.pubxml create mode 100644 QuickDrawWindows/Properties/PublishProfiles/win10-x64.pubxml create mode 100644 QuickDrawWindows/Properties/PublishProfiles/win10-x86.pubxml create mode 100644 QuickDrawWindows/Properties/launchSettings.json create mode 100644 QuickDrawWindows/SlidePage.xaml create mode 100644 QuickDrawWindows/SlidePage.xaml.cs create mode 100644 QuickDrawWindows/app.manifest diff --git a/.gitignore b/.gitignore index c5e41aa..24c3430 100644 --- a/.gitignore +++ b/.gitignore @@ -233,4 +233,11 @@ project.lock.json WebSrc/language-server-log.txt -._* \ No newline at end of file +._* + + +## Dev Licence Files + +**/devlics +**/devlics/* +*.devlic \ No newline at end of file diff --git a/QuickDraw.sln b/QuickDraw.sln index da69f1c..18e7b0a 100644 --- a/QuickDraw.sln +++ b/QuickDraw.sln @@ -1,25 +1,43 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31025.218 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33712.159 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickDraw", "QuickDrawWindows\QuickDraw.csproj", "{31B19DB2-2047-44DC-8C27-A34C113171CF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickDraw", "QuickDrawWindows\QuickDraw.csproj", "{CBC43D79-6F32-480A-BC3A-54383C19E4DD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {31B19DB2-2047-44DC-8C27-A34C113171CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {31B19DB2-2047-44DC-8C27-A34C113171CF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {31B19DB2-2047-44DC-8C27-A34C113171CF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {31B19DB2-2047-44DC-8C27-A34C113171CF}.Release|Any CPU.Build.0 = Release|Any CPU + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|ARM64.Build.0 = Debug|ARM64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x64.ActiveCfg = Debug|x64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x64.Build.0 = Debug|x64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x64.Deploy.0 = Debug|x64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x86.ActiveCfg = Debug|x86 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x86.Build.0 = Debug|x86 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Debug|x86.Deploy.0 = Debug|x86 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|ARM64.ActiveCfg = Release|ARM64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|ARM64.Build.0 = Release|ARM64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|ARM64.Deploy.0 = Release|ARM64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x64.ActiveCfg = Release|x64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x64.Build.0 = Release|x64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x64.Deploy.0 = Release|x64 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x86.ActiveCfg = Release|x86 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x86.Build.0 = Release|x86 + {CBC43D79-6F32-480A-BC3A-54383C19E4DD}.Release|x86.Deploy.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {03EDC6A8-A6C7-4B32-B248-CB1593722EC5} + SolutionGuid = {9BC0E5F9-D71B-443D-90F5-DBF6C0A50453} EndGlobalSection EndGlobal diff --git a/QuickDrawWindows/App.xaml b/QuickDrawWindows/App.xaml index c2b1991..6491c79 100644 --- a/QuickDrawWindows/App.xaml +++ b/QuickDrawWindows/App.xaml @@ -1,8 +1,20 @@ - + + - + + + + + + + + Transparent + Transparent + diff --git a/QuickDrawWindows/App.xaml.cs b/QuickDrawWindows/App.xaml.cs index 6c9ea91..5851f4e 100644 --- a/QuickDrawWindows/App.xaml.cs +++ b/QuickDrawWindows/App.xaml.cs @@ -1,118 +1,64 @@ -using System; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using Microsoft.UI.Xaml.Shapes; +using System; +using System.Collections.Generic; using System.Diagnostics; -using System.Windows; -using Microsoft.Web.WebView2.Core; -using System.Net; using System.IO; -using System.ComponentModel; -using System.Net.Http; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Activation; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Path = System.IO.Path; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. namespace QuickDraw { /// - /// Interaction logic for App.xaml + /// Provides application-specific behavior to supplement the default Application class. /// public partial class App : Application { - private readonly HttpClient httpClient = new(); - private InstallingWindow installingWindow; - - private string installerFile; - - private static bool HasWebView2() + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() { +#if DEBUG try { - string versionString = CoreWebView2Environment.GetAvailableBrowserVersionString(); - Version requiredVersion = Version.Parse("89.0.774.75"); - Version version = Version.Parse(versionString.Split(" ")[0]); - - return version.CompareTo(requiredVersion) >= 0; - } - catch (WebView2RuntimeNotFoundException) - { - return false; + var syncfusionKey = System.IO.File.ReadAllText(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"..\..\..\..\..\devlics\syncfusion.devlic")); + Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(syncfusionKey); } + catch { }; +#else + // Do whatever I should do with the actual license key +#endif + this.InitializeComponent(); } - private async void DownloadWebView2() + /// + /// Invoked when the application is launched. + /// + /// Details about the launch request and process. + protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) { - using (var response = await httpClient.GetAsync("https://go.microsoft.com/fwlink/p/?LinkId=2124703", HttpCompletionOption.ResponseHeadersRead)) - { - if (response.IsSuccessStatusCode) - { - using (var stream = await response.Content.ReadAsStreamAsync()) - using (var fileStream = new FileStream(installerFile, FileMode.Create)) - { - await stream.CopyToAsync(fileStream); - } - DownloadRuntimeCompleted(); - } else - { - InstallError(); - } - - } + m_window = new MainWindow(); + m_window.Activate(); } - private void InstallError() - { - installingWindow.Hide(); - - MessageBoxResult dialogResult = MessageBox.Show("Microsoft Edge WebView2 did not install properly. Click OK to try again or Cancel to quit.", - "QuickDraw", MessageBoxButton.OKCancel, MessageBoxImage.Exclamation); - - if (dialogResult == MessageBoxResult.OK) - { - installingWindow.Show(); - if (File.Exists(installerFile)) - { - File.Delete(installerFile); - } - DownloadWebView2(); - } - else - { - Shutdown(); - } - } - - protected override /*async*/ void OnStartup(StartupEventArgs e) - { - string tempFolder = Path.GetTempPath(); - installerFile = Path.Combine(tempFolder, "MicrosoftEdgeWebview2Setup.exe"); - - // Check for WebView2 Runtime, install if needed - if (HasWebView2()) - { - MainWindow = new QuickDrawWindow(); - MainWindow.Show(); - } - else - { - installingWindow = new InstallingWindow(); - installingWindow.Show(); - DownloadWebView2(); - } - } - - private async void DownloadRuntimeCompleted() - { - Process process = new(); - process.StartInfo.FileName = installerFile; - process.StartInfo.Arguments = @"/silent /install"; - process.StartInfo.Verb = "runas"; - process.StartInfo.UseShellExecute = true; - _ = process.Start(); - await process.WaitForExitAsync(); - - if (HasWebView2()) - { - installingWindow.Hide(); - MainWindow = new QuickDrawWindow(); - MainWindow.Show(); - return; - } - } + private Window m_window; + public MainWindow Window => (MainWindow)m_window; } } diff --git a/QuickDrawWindows/AssemblyInfo.cs b/QuickDrawWindows/AssemblyInfo.cs deleted file mode 100644 index 8b5504e..0000000 --- a/QuickDrawWindows/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Windows; - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) - ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) -)] diff --git a/QuickDrawWindows/Assets/LockScreenLogo.scale-200.png b/QuickDrawWindows/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..7440f0d4bf7c7e26e4e36328738c68e624ee851e GIT binary patch literal 432 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezr3(FqV6|IEGZ*x-#9g>~Mkr+x6^F zy~CDX2QIMs&Gcs3RnRBoxBA!*(Mfw0KTCYuYk0WlEIV>qBmPl! zq4ukrvfADX@#p8fbLY(H47N+k`FZ(FZh?cDro7>{8mkBO3>^oaIx`3!Jl)Qq)HI!+ z(S=1{o~eT)&W^=Ea8C`-17(Jv5(nHFJ{dOjGdxLVkY_y6&S1whfuFI4MM0kF0f&cO zPDVpV%nz;Id$>+0Ga5e9625-JcI)oq=#Pa3p^>8BB}21BUw@eN!-6@w%X+^`+Vn?! zryu|3T>kVWNBYyBc=7Y6H#s1Ah!OI_nezW zXTqOdkv2Az6KKBV=$yHdF^R3Fqw(TZEoNSZX>reXJ#bwX42%f|Pgg&ebxsLQ010xn AssI20 literal 0 HcmV?d00001 diff --git a/QuickDrawWindows/QuickDraw.ico b/QuickDrawWindows/Assets/QuickDraw.ico similarity index 100% rename from QuickDrawWindows/QuickDraw.ico rename to QuickDrawWindows/Assets/QuickDraw.ico diff --git a/QuickDrawWindows/Assets/QuickDraw256x256.png b/QuickDrawWindows/Assets/QuickDraw256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..1dbe0fa5a3761147193ee7e417d021efa2334329 GIT binary patch literal 7712 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 literal 0 HcmV?d00001 diff --git a/QuickDrawWindows/Assets/Square44x44Logo.scale-200.png b/QuickDrawWindows/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..f713bba67f551ef91020b75716a4dc8ebd744b1c GIT binary patch literal 637 zcmeAS@N?(olHy`uVBq!ia0vp^5g^RL1|$oo8kjIJFu8cTIEGZ*dUI*J;2{SImxtDO zm%3!R$UazoY}x{$j0P5ABYXWr(l=jxJ6ps1W{tV=^>{Dl><3nv3A}sm=EZ)#l3`NR zpZda3^rNox*D1%NC98Z~L*6zipLw~Gxn&(Y-;KmJ+aR6eLabU-L#y8HW%7P-E_-VlLqIabbHPHKT*)fT@9iWJ7iWgOT9%0}Lrj>lztPxWq6sPw3pi z#-<=#$jjrP_DD*i!RLsn0mIA=>4~N)IMYWIf=j%-zuKCdMG%tHYot70D1| zvWa0wMhauW#S>1CnI_;>!1Q3zMA17@DOVq{MQ+{U7^a&yA+%dMCG;WNPV0i;w$tu; zX^b}UKziPM)(<;)ruW;-`)bBN+rQNM*Zs_>?n$|FVFo-e*PZb*@U7VAd+tHb4e?=Blc~}S6K)wL}r*Gf`BM#QB z+y>N$mCswb4d{^{S9v_!eQj4fTRMOwOCi?lSk9%<=vAz}jM-*PQtH@Odn1LZcd^j#o> hW$4xn+CT+ep9lJ{OAO?njobhL002ovPDHLkV1nYebbkN< literal 0 HcmV?d00001 diff --git a/QuickDrawWindows/Assets/StoreLogo.png b/QuickDrawWindows/Assets/StoreLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..a4586f26bdf7841cad10f39cdffe2aca3af252c1 GIT binary patch literal 456 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 literal 0 HcmV?d00001 diff --git a/QuickDrawWindows/Assets/Wide310x150Logo.scale-200.png b/QuickDrawWindows/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..8b4a5d0dd5f6c6ab408e1edf04a07888859a9eab GIT binary patch literal 2097 zcmc&$ZA@EL7(Q+3r2+3&!t4k)FxZ4;Vj0QoMdu<_3Kciz7YR4=A(qT^w)Jxi;?x!9 zuyq6dMF?b>c0sq%A~kJcD9FY~qQRMt?ZR3YyDZt}Od;|mgpc{2dv9AHF){kXU%k({ z=Y8JidEayHTkG@twPZ|U3_^%3ct-OgLSiFAqDN!|tbCX@c@?4P`2x*TMK!+Q4b?k0 ziW7!!KF6dPWcF<%I|iznM~`QJ_V7sHGV_D`dhgpA9Vd@&X}ErK+j~_rdv;Bp?OA@a zFXOk7eWOJe5NcK;6h$FaM&7JxNc#-@QTwzW6x#d_zmQNkz5) zPI;kh;3d;5UCJU+9a(cOxX(|edWoOiAEdGU#kPJ&xnc2||3vDbuhBCkj-pb0as$Zl z5;}4n=**n6(1g`JEtSy;SG6X;#-F~Oz3lESG2b5`j@wAwY4Yp<=4Xeb>iH=6aicF?DxD&q{`!&}ct zBI)aycwuobQAf&678Uf+Mmh-@9RUhyH~>?w0dixO0#jZjEc9R^=5NZw=|a(kcB?9^ zfnTiEFXp-q#B;Tn>(O%$A*ud^Rg&eVH6Y_5Y%!E39RR&s?XpG`gKwU!6FE1 z7X)DC7)*(5g}lh`4`{i~DZcWupZI`K)_4P)VE{@gc7@Xsd^86zl~_mOYH?I4!aGeX z^E(_=L6?PgveDQ+r%P@UISEXrkn`LHJZ##+!-anV>6h)IkKp;E@p8+3&(5%kS2)ld*J*rJccZM0iyaAx7+F~GW1UWFK&3X$PE1^}NH zgAG9ck5K!{07OwU@j@Do>TbH=CDEo#4m0cEyAuXy_<&jlzJVcKweSJ5 z&=q~iIn18$w8yb=rmEmHxVEUA^?RwnB?6Qlp1os8@*dWTGL2bhzZ!s*xqScR?EPL` zo(JwNdKUUYy7GtvZ3asXm)cgFvCx9EmAi;|w=a0iGiv%%VYKh`P0Wma4y`Xyx|T~( zAmfGbgbEEC7)j8b@WA@+5W3a61HJXC1dX@6_T|Czk0I0zBk%tnW~()VWITGI!`$c< gARL?UBrYYkwoDw4eo*CrzXGTrZ@;GF>596)00d&n@&Et; literal 0 HcmV?d00001 diff --git a/QuickDrawWindows/DialogCenteringService.cs b/QuickDrawWindows/DialogCenteringService.cs deleted file mode 100644 index 7747145..0000000 --- a/QuickDrawWindows/DialogCenteringService.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using System.Drawing; -using System.Runtime.InteropServices; -using System.Windows.Forms; - -namespace QuickDraw -{ - public sealed class DialogCenteringService : IDisposable - { - private readonly IWin32Window _owner; - private readonly Win32Native.HookProc _hookProc; - private IntPtr _hHook; - - public DialogCenteringService(IWin32Window owner) - { - _owner = owner ?? throw new ArgumentNullException(nameof(owner)); - _hookProc = DialogHookProc; - _hHook = Win32Native.SetWindowsHookEx(Win32Native.WH_CALLWNDPROCRET, _hookProc, IntPtr.Zero, Win32Native.GetCurrentThreadId()); - } - - #region Disposing - ~DialogCenteringService() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - // if you have managed resources, get rid of them now - } - if (_hHook != IntPtr.Zero) - { - Win32Native.UnhookWindowsHookEx(_hHook); - _hHook = IntPtr.Zero; - } - } - #endregion - - private IntPtr DialogHookProc(int nCode, IntPtr wParam, IntPtr lParam) - { - if (nCode < 0) - { - return Win32Native.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam); - } - - var msg = (Win32Native.CWPRETSTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32Native.CWPRETSTRUCT)); - - if (msg.message == (int)Win32Native.CbtHookAction.HCBT_ACTIVATE) - { - try - { - CenterWindow(msg.hwnd); - } - finally - { - Win32Native.UnhookWindowsHookEx(_hHook); - _hHook = IntPtr.Zero; - } - } - - return Win32Native.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam); - } - - private bool CenterWindow(IntPtr hChildWnd) - { - var recParent = GetWindowRect(_owner.Handle); - return recParent != null ? CenterWindow(hChildWnd, recParent.Value) : false; - } - - private static Rectangle? GetWindowRect(IntPtr hWnd) - { - var rect = new Win32Native.RECT(); - if (Win32Native.GetWindowRect(hWnd, ref rect)) - { - return new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); - } - return null; - } - - private static bool CenterWindow(IntPtr hChildWnd, Rectangle recParent) - { - var recChild = GetWindowRect(hChildWnd); - if (recChild != null) - { - var centeredPoint = GetCenteredPoint(recParent, recChild.Value); - return Win32Native.SetWindowPos( - hChildWnd, - IntPtr.Zero, - centeredPoint.X, centeredPoint.Y, -1, -1, - Win32Native.SetWindowPosFlags.SWP_ASYNCWINDOWPOS | Win32Native.SetWindowPosFlags.SWP_NOSIZE | - Win32Native.SetWindowPosFlags.SWP_NOACTIVATE | Win32Native.SetWindowPosFlags.SWP_NOOWNERZORDER | - Win32Native.SetWindowPosFlags.SWP_NOZORDER); - } - return false; - } - - private static Point GetCenteredPoint(Rectangle recParent, Rectangle recChild) - { - var ptParentCenter = new Point - { - X = recParent.X + (recParent.Width / 2), - Y = recParent.Y + (recParent.Height / 2) - }; - - var ptStart = new Point - { - X = ptParentCenter.X - (recChild.Width / 2), - Y = ptParentCenter.Y - (recChild.Height / 2) - }; - - // get centered rectangle - var recCentered = new Rectangle(ptStart.X, ptStart.Y, recChild.Width, recChild.Height); - - // find the working area of the parent - var workingArea = Screen.FromRectangle(recParent).WorkingArea; - - // make sure child window isn't spanning across mulitiple screens - if (workingArea.X > recCentered.X) - { - recCentered = new Rectangle(workingArea.X, recCentered.Y, recCentered.Width, recCentered.Height); - } - if (workingArea.Y > recCentered.Y) - { - recCentered = new Rectangle(recCentered.X, workingArea.Y, recCentered.Width, recCentered.Height); - } - if (workingArea.Right < recCentered.Right) - { - recCentered = new Rectangle(workingArea.Right - recCentered.Width, recCentered.Y, recCentered.Width, recCentered.Height); - } - if (workingArea.Bottom < recCentered.Bottom) - { - recCentered = new Rectangle(recCentered.X, workingArea.Bottom - recCentered.Height, recCentered.Width, recCentered.Height); - } - - return new Point(recCentered.X, recCentered.Y); - } - - #region Native/Unsafe - private static class Win32Native - { - public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); - public const int WH_CALLWNDPROCRET = 12; - - public enum CbtHookAction - { - HCBT_MOVESIZE, - HCBT_MINMAX, - HCBT_QS, - HCBT_CREATEWND, - HCBT_DESTROYWND, - HCBT_ACTIVATE, - HCBT_CLICKSKIPPED, - HCBT_KEYSKIPPED, - HCBT_SYSCOMMAND, - HCBT_SETFOCUS - } - - [Flags] - public enum SetWindowPosFlags : uint - { - SWP_ASYNCWINDOWPOS = 0x4000U, - SWP_DEFERERASE = 0x2000U, - SWP_DRAWFRAME = 0x0020U, - SWP_FRAMECHANGED = 0x0020U, - SWP_HIDEWINDOW = 0x0080U, - SWP_NOACTIVATE = 0x0010U, - SWP_NOCOPYBITS = 0x0100U, - SWP_NOMOVE = 0x0002U, - SWP_NOOWNERZORDER = 0x0200U, - SWP_NOREDRAW = 0x0008U, - SWP_NOREPOSITION = 0x0200U, - SWP_NOSENDCHANGING = 0x0400U, - SWP_NOSIZE = 0x0001U, - SWP_NOZORDER = 0x0004U, - SWP_SHOWWINDOW = 0x0040U - } - - [StructLayout(LayoutKind.Sequential)] - public struct RECT - { - public int left; - public int top; - public int right; - public int bottom; - } - - [StructLayout(LayoutKind.Sequential)] - public struct CWPRETSTRUCT - { - public IntPtr lResult; - public IntPtr lParam; - public IntPtr wParam; - public uint message; - public IntPtr hwnd; - }; - - [DllImport("kernel32.dll")] - public static extern int GetCurrentThreadId(); - - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect); - - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SetWindowPosFlags uFlags); - - [DllImport("user32.dll")] - public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); - - [DllImport("user32.dll")] - public static extern int UnhookWindowsHookEx(IntPtr idHook); - - [DllImport("user32.dll")] - public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam); - } - #endregion - } -} \ No newline at end of file diff --git a/QuickDrawWindows/FolderDialog.cs b/QuickDrawWindows/FolderDialog.cs deleted file mode 100644 index 146a0d2..0000000 --- a/QuickDrawWindows/FolderDialog.cs +++ /dev/null @@ -1,313 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace QuickDraw -{ - // Disable warning CS0108: 'x' hides inherited member 'y'. Use the new keyword if hiding was intended. - #pragma warning disable 0108 - - internal static class IIDGuid - { - internal const string IModalWindow = "b4db1657-70d7-485e-8e3e-6fcb5a5c1802"; - internal const string IFileDialog = "42f85136-db7e-439c-85f1-e4075d135fc8"; - internal const string IFileOpenDialog = "d57c7288-d4ad-4768-be02-9d969532d960"; - internal const string IShellItem = "43826d1e-e718-42ee-bc55-a1e261c37bfe"; - internal const string IShellItemArray = "B63EA76D-1F85-456F-A19C-48159EFA858B"; - } - - internal static class CLSIDGuid - { - internal const string FileOpenDialog = "DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7"; - internal const string ShellItem = "9ac9fbe1-e0a2-4ad6-b4ee-e212013ea917"; - } - - [Flags] - internal enum FileOpenDialogOptions : uint - { - NoChangeDir = 0x00000008, - PickFolders = 0x00000020, - AllowMultiSelect = 0x00000200, - PathMustExist = 0x00000800, - } - - public enum SIGDN : uint - { - NORMALDISPLAY = 0, - PARENTRELATIVEPARSING = 0x80018001, - PARENTRELATIVEFORADDRESSBAR = 0x8001c001, - DESKTOPABSOLUTEPARSING = 0x80028000, - PARENTRELATIVEEDITING = 0x80031001, - DESKTOPABSOLUTEEDITING = 0x8004c000, - FILESYSPATH = 0x80058000, - URL = 0x80068000 - } - - [ComImport] - [Guid(IIDGuid.IShellItem)] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IShellItem - { - void BindToHandler(IntPtr pbc, - [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, - [MarshalAs(UnmanagedType.LPStruct)] Guid riid, - out IntPtr ppv); - - void GetParent(out IShellItem ppsi); - - void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName); - - void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); - - void Compare(IShellItem psi, uint hint, out int piOrder); - }; - - [ComImport, - Guid(IIDGuid.IShellItemArray), - InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - interface IShellItemArray - { - // Not supported: IBindCtx - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid rbhid, - [In] ref Guid riid, out IntPtr ppvOut); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetPropertyStore([In] int Flags, [In] ref Guid riid, out IntPtr ppv); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetPropertyDescriptionList([In] ref PropertyKey keyType, [In] ref Guid riid, out IntPtr ppv); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetAttributes([In] ShellItemArrayAttributeFlags dwAttribFlags, [In] uint sfgaoMask, out uint psfgaoAttribs); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetCount(out uint pdwNumItems); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetItemAt([In] uint dwIndex, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - // Not supported: IEnumShellItems (will use GetCount and GetItemAt instead) - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems); - } - - - - // These two are here for methods we don't use (but need to keep the function order), so they are empty - struct COMDLG_FILTERSPEC { } - - internal interface IFileDialogEvents { } - - internal enum FileDialogAddPosition : uint - { - - } - - public struct PropertyKey - { - - } - - public enum ShellItemArrayAttributeFlags - { - - } - - // End Empty Definitions - - - [ComImport(), - Guid(IIDGuid.IModalWindow), - InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IModalWindow - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), - PreserveSig] - int Show([In] IntPtr parent); - } - - [ComImport(), - Guid(IIDGuid.IFileDialog), - InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IFileDialog : IModalWindow - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), - PreserveSig] - int Show([In] IntPtr parent); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFileTypes([In] uint cFileTypes, [In] COMDLG_FILTERSPEC[] rgFilterSpec); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFileTypeIndex([In] uint iFileType); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetFileTypeIndex(out uint piFileType); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void Advise([In, MarshalAs(UnmanagedType.Interface)] IFileDialogEvents pfde, out uint pdwCookie); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void Unadvise([In] uint dwCookie); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetOptions([In] FileOpenDialogOptions fos); - - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetOptions(out FileOpenDialogOptions pfos); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, FileDialogAddPosition fdap); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void Close([MarshalAs(UnmanagedType.Error)] int hr); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetClientGuid([In] ref Guid guid); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void ClearClientData(); - - // Not supported: IShellItemFilter is not defined, converting to IntPtr - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); - } - - [ComImport(), - Guid(IIDGuid.IFileOpenDialog), - InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IFileOpenDialog : IFileDialog - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), - PreserveSig] - int Show([In] IntPtr parent); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFileTypes([In] uint cFileTypes, [In] COMDLG_FILTERSPEC[] rgFilterSpec); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFileTypeIndex([In] uint iFileType); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetFileTypeIndex(out uint piFileType); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void Advise([In, MarshalAs(UnmanagedType.Interface)] IFileDialogEvents pfde, out uint pdwCookie); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void Unadvise([In] uint dwCookie); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetOptions([In] FileOpenDialogOptions fos); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetOptions(out FileOpenDialogOptions pfos); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, FileDialogAddPosition fdap); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void Close([MarshalAs(UnmanagedType.Error)] int hr); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetClientGuid([In] ref Guid guid); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void ClearClientData(); - - // Not supported: IShellItemFilter is not defined, converting to IntPtr - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); - - // Defined by IFileOpenDialog - // --------------------------------------------------------------------------------- - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetResults([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppenum); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void GetSelectedItems([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppsai); - } - - [ComImport, - ClassInterface(ClassInterfaceType.None), - TypeLibType(TypeLibTypeFlags.FCanCreate), - Guid(CLSIDGuid.FileOpenDialog)] - internal class FileOpenDialogRCW - { - } - - [ComImport, - Guid(IIDGuid.IFileOpenDialog), - CoClass(typeof(FileOpenDialogRCW))] - internal interface NativeFileOpenDialog : IFileOpenDialog - { - } -} \ No newline at end of file diff --git a/QuickDrawWindows/Installing.xaml b/QuickDrawWindows/Installing.xaml deleted file mode 100644 index 3e50ea3..0000000 --- a/QuickDrawWindows/Installing.xaml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/QuickDrawWindows/Installing.xaml.cs b/QuickDrawWindows/Installing.xaml.cs deleted file mode 100644 index 49e0738..0000000 --- a/QuickDrawWindows/Installing.xaml.cs +++ /dev/null @@ -1,34 +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.Shapes; - -namespace QuickDraw -{ - /// - /// Interaction logic for Window1.xaml - /// - public partial class InstallingWindow : Window - { - public void OnClosed(object sender, EventArgs args) - { - ((App)Application.Current).Shutdown(); - } - - public InstallingWindow() - { - InitializeComponent(); - - this.Closed += OnClosed; - } - } -} diff --git a/QuickDrawWindows/MainPage.xaml b/QuickDrawWindows/MainPage.xaml new file mode 100644 index 0000000..a2e78e6 --- /dev/null +++ b/QuickDrawWindows/MainPage.xaml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/QuickDrawWindows/MainPage.xaml.cs b/QuickDrawWindows/MainPage.xaml.cs new file mode 100644 index 0000000..2db8629 --- /dev/null +++ b/QuickDrawWindows/MainPage.xaml.cs @@ -0,0 +1,300 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using CommunityToolkit.WinUI.UI; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace QuickDraw +{ + + /// + /// Converts the value of the internal slider into text. + /// + /// Internal use only. + internal class StringToEnumConverter : IValueConverter + { + private Type _enum; + + public StringToEnumConverter(Type type) + { + _enum = type; + } + + public object Convert(object value, + Type targetType, + object parameter, + string language) + { + var _name = Enum.ToObject(_enum, (int)Double.Parse((string)value)); + + // Look for a 'Display' attribute. + var _member = _enum + .GetRuntimeFields() + .FirstOrDefault(x => x.Name == _name.ToString()); + if (_member == null) + { + return _name; + } + + var _attr = (DisplayAttribute)_member + .GetCustomAttribute(typeof(DisplayAttribute)); + if (_attr == null) + { + return _name; + } + + return _attr.Name; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + string language) + { + return value; // Never called + } + + } + + /// + /// Converts the value of the internal slider into text. + /// + /// Internal use only. + internal class DoubleToEnumConverter : IValueConverter + { + private Type _enum; + + public DoubleToEnumConverter(Type type) + { + _enum = type; + } + + public object Convert(object value, + Type targetType, + object parameter, + string language) + { + var _name = Enum.ToObject(_enum, (int)(double)value); + + // Look for a 'Display' attribute. + var _member = _enum + .GetRuntimeFields() + .FirstOrDefault(x => x.Name == _name.ToString()); + if (_member == null) + { + return _name; + } + + var _attr = (DisplayAttribute)_member + .GetCustomAttribute(typeof(DisplayAttribute)); + if (_attr == null) + { + return _name; + } + + return _attr.Name; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + string language) + { + return value; // Never called + } + } + public static class TextBlockExtensions + { + public static double PreWrappedWidth(this TextBlock textBlock) + { + var tempTextBlock = new TextBlock { Text = textBlock.Text }; + + tempTextBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity)); + tempTextBlock.Arrange(new Rect(new Point(), textBlock.DesiredSize)); + + return tempTextBlock.ActualWidth; + } + } + + public class ImageFolder + { + public string Path { get; set; } + public int ImageCount { get; set; } + + public ImageFolder(string path, int imageCount) + { + Path = path; + ImageCount = imageCount; + } + } + + public class GridLengthConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + double val = (double)value; + GridLength gridLength = new GridLength(val); + + return gridLength; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + GridLength val = (GridLength)value; + + return val.Value; + } + } + + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class MainPage : Page + { + + enum TimerEnum + { + [Display(Name = "30s")] + T30s, + [Display(Name = "1m")] + T1m, + [Display(Name = "2m")] + T2m, + [Display(Name = "5m")] + T5m, + [Display(Name = "No Limit")] + NoLimit + }; + + public List ImageFolders { get; set; } = new List() { + new ImageFolder("D:\\Reference\\Image Reference\\Poses\\Nude Female", 35743), + new ImageFolder("D:\\Reference\\Image Reference\\Poses\\Nude Male", 13758), + new ImageFolder("D:\\Reference\\Image Reference\\Heads\\Expressions Women", 3632) + }; + + public class ByWidth : IComparer + { + public int Compare(TextBlock x, TextBlock y) + { + int widthCompare = x.PreWrappedWidth().CompareTo(y.PreWrappedWidth()); + + if (widthCompare == 0 && !ReferenceEquals(x, y)) + { + return 1; + } + + return widthCompare; + } + } + + private SortedSet _pathTexts = new SortedSet(new ByWidth()); + private SortedSet _imageCountTexts = new SortedSet(new ByWidth()); + private HashSet _pathColumns = new HashSet(); + private HashSet _imageCountColumns = new HashSet(); + + public double PathColumnWidth { get; set; } = 1000; + public double ImageCountColumnWidth { get; set; } = 1000; + + public MainPage() + { + this.InitializeComponent(); + this.DataContext = this; + this.Resources.Add("doubleToEnumConverter", new DoubleToEnumConverter(typeof(TimerEnum))); + this.Resources.Add("stringToEnumConverter", new StringToEnumConverter(typeof(TimerEnum))); + } + + private void UpdateColumnWidths(TextBlock pathText, TextBlock imageCountText, bool debug = false) + { + if (_pathTexts.Count < 1 || _imageCountTexts.Count < 1) { return; } + var grid = (Grid)pathText.Parent; + + ImageCountColumnWidth = _imageCountTexts.Max.ActualWidth + 20; + + var gridWidth = grid.ActualWidth; + var availableWidth = gridWidth - (grid.ColumnDefinitions[3].ActualWidth + ImageCountColumnWidth + 20); + + var maxPathColumnWidth = _pathTexts.Max != null ? _pathTexts.Max.PreWrappedWidth() : 0; + PathColumnWidth = Math.Max(100, Math.Min(availableWidth,maxPathColumnWidth)); + + Debug.WriteLine(_pathTexts.Max.ActualWidth); + + foreach (ColumnDefinition pathColumn in _pathColumns) + { + pathColumn.Width = new GridLength(PathColumnWidth, GridUnitType.Pixel); + } + + foreach (ColumnDefinition imageCountColumn in _imageCountColumns) + { + imageCountColumn.Width = new GridLength(ImageCountColumnWidth, GridUnitType.Pixel); + } + + } + + private void PathText_SizeChanged(object sender, SizeChangedEventArgs e) + { + var pathText = (TextBlock)sender; + var grid = (Grid)pathText.Parent; + + var imageCountText = (TextBlock)grid.FindChild("ImageCountText"); + + _pathTexts.Remove(pathText); + _pathTexts.Add(pathText); + + _pathColumns.Add(grid.ColumnDefinitions[0]); + + UpdateColumnWidths(pathText, imageCountText, true); + } + + private void ImageCountText_SizeChanged(object sender, SizeChangedEventArgs e) + { + var imageCountText = (TextBlock)sender; + var grid = (Grid)imageCountText.Parent; + + var pathText = (TextBlock)grid.FindChild("PathText"); + + _imageCountTexts.Remove(imageCountText); + _imageCountTexts.Add(imageCountText); + + _imageCountColumns.Add(grid.ColumnDefinitions[1]); + + UpdateColumnWidths(pathText, imageCountText); + } + + private void AppBarButton_Click(object sender, RoutedEventArgs e) + { + + } + + private void Grid_SizeChanged(object sender, SizeChangedEventArgs e) + { + Grid grid = (Grid)sender; + + var pathText = (TextBlock)grid.FindChild("PathText"); + var imageCountText = (TextBlock)grid.FindChild("ImageCountText"); + + UpdateColumnWidths(pathText, imageCountText); + } + + private void StartButton_Click(object sender, RoutedEventArgs e) + { + (Application.Current as App)?.Window.NavigateToSlideshow(); + } + } +} diff --git a/QuickDrawWindows/MainWindow.xaml b/QuickDrawWindows/MainWindow.xaml index 6d7012c..9fc512c 100644 --- a/QuickDrawWindows/MainWindow.xaml +++ b/QuickDrawWindows/MainWindow.xaml @@ -1,17 +1,42 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QuickDrawWindows/MainWindow.xaml.cs b/QuickDrawWindows/MainWindow.xaml.cs index 25184ef..4af8346 100644 --- a/QuickDrawWindows/MainWindow.xaml.cs +++ b/QuickDrawWindows/MainWindow.xaml.cs @@ -1,360 +1,43 @@ -using Microsoft.Web.WebView2.Core; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Animation; +using Microsoft.UI.Xaml.Navigation; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using System.Windows; using System.IO; -using System.Runtime.InteropServices; -using System.Windows.Input; -using System.Net; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.ViewManagement; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. namespace QuickDraw { - public class Message - { - public string type { get; set; } - } - - public class PathOpMessage : Message - { - public string path { get; set; } - } - - public class PathListOpMessage : Message - { - public List paths { get; set; } - } - - public class GetImagesMessage : PathListOpMessage - { - public int interval { get; set; } - } - - public struct ImageFolder - { - public string Path { get; set; } - - public int Count { get; set; } - } - /// - /// Interaction logic for MainWindow.xaml + /// An empty window that can be used on its own or navigated to within a Frame. /// - public partial class QuickDrawWindow : Window, System.Windows.Forms.IWin32Window + public sealed partial class MainWindow : WinUIEx.WindowEx { - private string domain; - public static readonly RoutedCommand StopPropagation = new(); - private void ExecutedStopPropagation(object sender, ExecutedRoutedEventArgs e) - { - // Do nothing, disabling this key combo - e.Handled = true; - } - - private void CanExecuteStopPropagation(object sender, CanExecuteRoutedEventArgs e) - { - e.CanExecute = true; - } - - private readonly Dictionary folderMappings = new(); - public IntPtr Handle => new System.Windows.Interop.WindowInteropHelper(this).Handle; - - private void WebViewUpdateFolders(List folders) - { - string jsonString = JsonSerializer.Serialize(new Dictionary - { - { "type", "UpdateFolders" }, - { "data", folders } - }); - - webView.CoreWebView2.PostWebMessageAsJson(jsonString); - } - public void OnClosed(object sender, EventArgs args) + public MainWindow() { - ((App)Application.Current).Shutdown(); - } - - public QuickDrawWindow() - { - InitializeComponent(); - - Closed += OnClosed; - - InitializeAsync(); + this.InitializeComponent(); + this.ExtendsContentIntoTitleBar = true; + this.SetTitleBar(this.AppTitleBar); + this.MainFrame.Navigate(typeof(MainPage)); } - private async void InitializeAsync() + public void NavigateToSlideshow() { - string userDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"QuickDraw"); - Directory.CreateDirectory(userDataFolder); - CoreWebView2EnvironmentOptions options = new(); - CoreWebView2Environment env = CoreWebView2Environment.CreateAsync("", userDataFolder, options).GetAwaiter().GetResult(); - - await webView.EnsureCoreWebView2Async(env); -#if DEBUG && !FAKE_RELEASE - domain = "http://localhost:8080"; -#else - webView.CoreWebView2.SetVirtualHostNameToFolderMapping( - "quickdraw.invalid", "WebSrc", - Microsoft.Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow - ); - -#if !FAKE_RELEASE - webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; - webView.CoreWebView2.Settings.IsZoomControlEnabled = false; - webView.CoreWebView2.Settings.AreDevToolsEnabled = false; - - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.F, ModifierKeys.Control))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.G, ModifierKeys.Control))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.G, ModifierKeys.Control | ModifierKeys.Shift))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.P, ModifierKeys.Control))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.R, ModifierKeys.Control))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.F5))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.BrowserRefresh))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.R, ModifierKeys.Control | ModifierKeys.Shift))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.F5, ModifierKeys.Control))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.F5, ModifierKeys.Shift))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.BrowserRefresh, ModifierKeys.Control))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.BrowserRefresh, ModifierKeys.Shift))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.F3))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.F3, ModifierKeys.Shift))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.BrowserBack))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.Left, ModifierKeys.Alt))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.BrowserForward))); - webView.InputBindings.Add(new InputBinding(QuickDrawWindow.StopPropagation, new KeyGesture(Key.Right, ModifierKeys.Alt))); -#endif - - domain = "https://quickdraw.invalid"; -#endif - _ = webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync($@" - window.QuickDrawWindows = true; - "); - - webView.Source = new Uri($"{domain}/index.html"); - - webView.CoreWebView2.WebMessageReceived += ReceiveMessage; - } - - private static void OpenFolderInExplorer(string path) - { - if (Directory.Exists(path)) - { - ProcessStartInfo startInfo = new() - { - Arguments = path, - FileName = "explorer.exe" - }; - - _ = Process.Start(startInfo); - } - } - - private void OpenImageInExplorer(string path) - { - Uri imageUri = new(path); - string folder = folderMappings[imageUri.Host]; - string imagePath = Path.GetFullPath($"{folder}{WebUtility.UrlDecode(imageUri.AbsolutePath).Replace("/", "\\")}"); - Debug.WriteLine(imagePath); - - if (File.Exists(imagePath)) - { - ProcessStartInfo startInfo = new() - { - Arguments = $"/select,\"{imagePath}\"", - FileName = "explorer.exe" - }; - - _ = Process.Start(startInfo); - } - } - - private static IEnumerable GetFolderImages(string filepath) - { - IEnumerable files = Directory.EnumerateFiles(filepath, "*.*", SearchOption.AllDirectories) - .Where(s => s.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) - || s.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) - || s.EndsWith(".png", StringComparison.OrdinalIgnoreCase)); - - return files; - } - - private void GetImages(List folders, int interval) - { - HashSet images = new(); - // clear mappings - foreach (KeyValuePair mapping in folderMappings) - { - webView.CoreWebView2.ClearVirtualHostNameToFolderMapping(mapping.Key); - } - folderMappings.Clear(); - - int folderNum = 0; - - foreach (string folder in folders) - { - string hostName = $"quickdraw-folder{folderNum}.invalid"; - - webView.CoreWebView2.SetVirtualHostNameToFolderMapping( - hostName, folder, - CoreWebView2HostResourceAccessKind.Allow - ); - - folderMappings.Add(hostName, folder); - - IEnumerable files = GetFolderImages(folder).Select(p => p.Replace(folder, $"https://{hostName}").Replace("\\", "/").Replace("#", "%23")); - - images.UnionWith(files.ToHashSet()); - - folderNum++; - } - - if (images.Count > 0) - { - string jsonString = JsonSerializer.Serialize(new Dictionary - { - { "interval", interval * 1000 }, - { "images", images } - }); - - _ = webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync($@" - var slideshowData = {jsonString}; - "); - - webView.CoreWebView2.Navigate($"{domain}/slideshow.html"); - } - else - { - using DialogCenteringService centeringService = new(this); - _ = MessageBox.Show(this, "No images found! Select one or more folders.", "QuickDraw", MessageBoxButton.OK, MessageBoxImage.Exclamation); - } - } - - private void RefreshFolderCount(string path) - { - int count = GetFolderImages(path).Count(); - - if (count > 0) - { - WebViewUpdateFolders(new List { new ImageFolder { Path = path, Count = count } }); - } - } - - private void RefreshAllFolderCounts(List paths) - { - List folders = new(); - - foreach (string path in paths) - { - int count = GetFolderImages(path).Count(); - - if (count > 0) - { - folders.Add(new ImageFolder { Path = path, Count = count }); - } - } - - WebViewUpdateFolders(folders); - } - - private async void OpenFolders() - { - List folders = await Task.Run(() => - { - List folders = new(); - - IFileOpenDialog dialog = null; - uint count = 0; - try - { - dialog = new NativeFileOpenDialog(); - dialog.SetOptions( - FileOpenDialogOptions.NoChangeDir - | FileOpenDialogOptions.PickFolders - | FileOpenDialogOptions.AllowMultiSelect - | FileOpenDialogOptions.PathMustExist - ); - _ = dialog.Show(IntPtr.Zero); - - dialog.GetResults(out IShellItemArray shellItemArray); - - if (shellItemArray != null) - { - string filepath = null; - shellItemArray.GetCount(out count); - - for (uint i = 0; i < count; i++) - { - shellItemArray.GetItemAt(i, out IShellItem shellItem); - - if (shellItem != null) - { - shellItem.GetDisplayName(SIGDN.FILESYSPATH, out IntPtr i_result); - filepath = Marshal.PtrToStringAuto(i_result); - Marshal.FreeCoTaskMem(i_result); - - IEnumerable files = GetFolderImages(filepath); - - folders.Add(new ImageFolder { Path = filepath, Count = files.Count() }); - } - } - } - } - catch (COMException) - { - // No files or other weird error, do nothing. - } - finally - { - if (dialog != null) - { - _ = Marshal.FinalReleaseComObject(dialog); - } - } - return folders; - }); - - if (folders.Count > 0) - { - WebViewUpdateFolders(folders); - } - } - - private void ReceiveMessage(object sender, CoreWebView2WebMessageReceivedEventArgs args) - { - Message message = JsonSerializer.Deserialize(args.WebMessageAsJson); - - switch (message.type) - { - case "addFolders": - OpenFolders(); - break; - case "refreshFolder": - PathOpMessage refreshFolderMessage = JsonSerializer.Deserialize(args.WebMessageAsJson); - RefreshFolderCount(refreshFolderMessage.path); - break; - case "refreshFolders": - PathListOpMessage refreshFoldersMessage = JsonSerializer.Deserialize(args.WebMessageAsJson); - RefreshAllFolderCounts(refreshFoldersMessage.paths); - break; - case "openFolder": - PathOpMessage openFolderMessage = JsonSerializer.Deserialize(args.WebMessageAsJson); - OpenFolderInExplorer(openFolderMessage.path); - break; - case "getImages": - GetImagesMessage getImagesMessage = JsonSerializer.Deserialize(args.WebMessageAsJson); - GetImages(getImagesMessage.paths, getImagesMessage.interval); - break; - case "openImage": - PathOpMessage openImageMessage = JsonSerializer.Deserialize(args.WebMessageAsJson); - OpenImageInExplorer(openImageMessage.path); - break; - default: - break; - } + this.MainFrame.Navigate(typeof(SlidePage), null, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromRight }); } } } diff --git a/QuickDrawWindows/Properties/PublishProfiles/win10-arm64.pubxml b/QuickDrawWindows/Properties/PublishProfiles/win10-arm64.pubxml new file mode 100644 index 0000000..a7fdd16 --- /dev/null +++ b/QuickDrawWindows/Properties/PublishProfiles/win10-arm64.pubxml @@ -0,0 +1,20 @@ + + + + + FileSystem + ARM64 + win10-arm64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + + + \ No newline at end of file diff --git a/QuickDrawWindows/Properties/PublishProfiles/win10-x64.pubxml b/QuickDrawWindows/Properties/PublishProfiles/win10-x64.pubxml new file mode 100644 index 0000000..26ea7e5 --- /dev/null +++ b/QuickDrawWindows/Properties/PublishProfiles/win10-x64.pubxml @@ -0,0 +1,20 @@ + + + + + FileSystem + x64 + win10-x64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + + + \ No newline at end of file diff --git a/QuickDrawWindows/Properties/PublishProfiles/win10-x86.pubxml b/QuickDrawWindows/Properties/PublishProfiles/win10-x86.pubxml new file mode 100644 index 0000000..34d14d4 --- /dev/null +++ b/QuickDrawWindows/Properties/PublishProfiles/win10-x86.pubxml @@ -0,0 +1,20 @@ + + + + + FileSystem + x86 + win10-x86 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + + + \ No newline at end of file diff --git a/QuickDrawWindows/Properties/launchSettings.json b/QuickDrawWindows/Properties/launchSettings.json new file mode 100644 index 0000000..386c6fd --- /dev/null +++ b/QuickDrawWindows/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "QuickDraw (Package)": { + "commandName": "MsixPackage" + }, + "QuickDraw (Unpackaged)": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/QuickDrawWindows/QuickDraw.csproj b/QuickDrawWindows/QuickDraw.csproj index 6d040ea..682ead3 100644 --- a/QuickDrawWindows/QuickDraw.csproj +++ b/QuickDrawWindows/QuickDraw.csproj @@ -1,72 +1,100 @@  - WinExe - net6.0-windows10.0.20348.0 - true - true - Always - QuickDraw.ico - Matthew Fraser - MF Digital Media - Gesture drawing app that shows you random images to draw. - Copyright © 2021 MF Digital Media - https://github.com/blendermf/QuickDraw - QuickDraw.png - - https://github.com/blendermf/QuickDraw - Github - Drawing, Gesture, Quick, Poses - en-CA - LICENSE - false - false - QuickDraw.App - 1.0.4 - You can now refresh folder image counts (without re-adding the folder) -Clicking an image during a session now shows it in file explorer. - false - - $(PostBuildEventDependsOn); - PostBuildMacros; - - 7.0 + net6.0-windows10.0.19041.0 + 10.0.17763.0 + QuickDraw + x86;x64;ARM64 + win10-x86;win10-x64;win10-arm64 + win10-$(Platform).pubxml + true + true + None + QuickDraw + en-CA + 1.1.0 + QuickDraw.Program + Matthew Fraser + QuickDraw + Gesture drawing app that shows you random images to draw. + Copyright © 2023 MF Digital Media + https://github.com/blendermf/QuickDraw + QuickDraw256x256.png + README.md + https://github.com/blendermf/QuickDraw + Drawing, Gesture, Quick, Poses + Ported the Windows app to a native .Net app (no longer uses WebView) + LICENSE + Assets\QuickDraw.ico + app.manifest + + + + - - none - false - + + + + + + + + + - + + + + + + + + + + + + - - WebSrc\%(RecursiveDir)%(Filename)%(Extension) - PreserveNewest - - + True - + \ - + True - + \ - - - - - - - - - - - - + + + True + \ + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + true + diff --git a/QuickDrawWindows/SlidePage.xaml b/QuickDrawWindows/SlidePage.xaml new file mode 100644 index 0000000..bbd0625 --- /dev/null +++ b/QuickDrawWindows/SlidePage.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + diff --git a/QuickDrawWindows/SlidePage.xaml.cs b/QuickDrawWindows/SlidePage.xaml.cs new file mode 100644 index 0000000..c8a0686 --- /dev/null +++ b/QuickDrawWindows/SlidePage.xaml.cs @@ -0,0 +1,291 @@ +using CommunityToolkit.Common; +using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas.UI; +using Microsoft.Graphics.Canvas.UI.Xaml; +using Microsoft.UI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Windows.Devices.AllJoyn; +using Windows.Foundation; +using Windows.Foundation.Collections; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace QuickDraw +{ + enum LoadDirection + { + Backwards, + Forwards + } + + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class SlidePage : Page + { + + private const int CACHE_SIZE = 9; + private const int HALF_CACHE_SIZE = CACHE_SIZE / 2; + + List imagePaths = new List + { + "C:\\Users\\blendermf\\Pictures\\Reference\\4b8e79076da0a545f5d8474d48d6aba9.jpg", + "C:\\Users\\blendermf\\Pictures\\Reference\\b266995705bf837bd882ef7ec8a0c658.jpg", + "C:\\Users\\blendermf\\Pictures\\Reference\\ce509886c60122d6841b5a3c40e40c13.jpg", + + "C:\\Users\\blendermf\\Pictures\\Reference\\Baton_Rouge_Louisiana (18 of 108).jpg", + "C:\\Users\\blendermf\\Pictures\\Reference\\5ffd4bf8b2e9765d4357e39da574e65a.jpg", + "C:\\Users\\blendermf\\Pictures\\Reference\\ea193f9771bbe55c1562ca8587371c2f.jpg", + + "C:\\Users\\blendermf\\Pictures\\Reference\\4e671baaf8d17256d82978d04bb68275.jpg", + "C:\\Users\\blendermf\\Pictures\\Reference\\5f1f0087ebb95483a19f3acf438248c3.jpg", + "C:\\Users\\blendermf\\Pictures\\Reference\\9c9c5055fcfa58b4a4c8c0969a908984.jpg", + }; + + LinkedList cachedImages = new LinkedList (); + + LinkedListNode currentImageNode = null; + int currentImageIndex = 0; + Task imageLoadTask; + + public SlidePage() + { + this.InitializeComponent(); + Task.Run(LoadImageInit); + } + + private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args) + { + HandleLoadExceptions(); + + if (currentImageNode == null) + { + + } + else + { + CanvasVirtualBitmap bitmap = currentImageNode.Value; + + Size canvasSize = new Size(sender.ActualWidth, sender.ActualHeight); + double canvasAspect = canvasSize.Width / canvasSize.Height; + double bitmapAspect = bitmap.Bounds.Width / bitmap.Bounds.Height; + Size imageRenderSize; + Point imagePos; + + if (bitmapAspect > canvasAspect) + { + imageRenderSize = new Size( + canvasSize.Width, + canvasSize.Width / bitmapAspect + ); + imagePos = new Point(0, (canvasSize.Height - imageRenderSize.Height) / 2); + } + else + { + imageRenderSize = new Size( + canvasSize.Height * bitmapAspect, + canvasSize.Height + ); + imagePos = new Point((canvasSize.Width - imageRenderSize.Width) / 2, 0); + + } + args.DrawingSession.DrawImage(currentImageNode.Value, new Rect(imagePos, imageRenderSize), bitmap.Bounds); + } + } + + public void LoadImageInit() + { + Debug.Assert(imageLoadTask == null); + imageLoadTask = FillImageCacheAsync(this.SlideCanvas, currentImageIndex).ContinueWith(_ => SlideCanvas.Invalidate()); + } + + int mod (int n, int d) + { + int r = n % d; + return r < 0 ? r+d : r; + } + + async Task FillImageCacheAsync(CanvasControl resourceCreator, int index) + { + var bitmap = await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePaths[index]); + currentImageNode = cachedImages.AddFirst(bitmap); + currentImageIndex = index; + + var remainingCacheSize = Math.Min(imagePaths.Count, CACHE_SIZE) - 1; + var numBefore = (remainingCacheSize / 2); + var numAfter = (remainingCacheSize / 2) + (remainingCacheSize % 2); + + var beforeImages = Enumerable.Range(index - numBefore, numBefore) + .AsParallel() + .Reverse() + .Select(i => mod(i, imagePaths.Count)) + .ToArray() + .Select(i => + { + return imagePaths[i]; + }); + var afterImages = Enumerable.Range(index + 1, numAfter) + .AsParallel() + .Select(i => mod(i, imagePaths.Count)) + .ToArray() + .Select(i => + { + return imagePaths[i]; + }); + + async Task LoadBeforeAsync() + { + foreach (var image in beforeImages) + { + var bitmap = await CanvasVirtualBitmap.LoadAsync(resourceCreator, image); + + lock (cachedImages) + { + cachedImages.AddFirst(bitmap); + } + } + } + + async Task LoadAfterAsync() + { + foreach (var image in afterImages) + { + var bitmap = await CanvasVirtualBitmap.LoadAsync(resourceCreator, image); + + lock (cachedImages) + { + cachedImages.AddLast(bitmap); + } + } + } + + Task loadBeforeTask = LoadBeforeAsync(); + Task loadAfterTask = LoadAfterAsync(); + await loadBeforeTask; + await loadAfterTask; + } + + async Task UpdateImageAsync(CanvasControl resourceCreator, LoadDirection direction) + { + switch (direction) + { + case LoadDirection.Backwards: + { + cachedImages.Last.Value.Dispose(); + cachedImages.RemoveLast(); + + var imageIndex = mod(currentImageIndex - HALF_CACHE_SIZE, imagePaths.Count); + cachedImages.AddFirst(await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePaths[imageIndex])); + } + break; + + case LoadDirection.Forwards: + { + cachedImages.First.Value.Dispose(); + cachedImages.RemoveFirst(); + + var imageIndex = mod(currentImageIndex + HALF_CACHE_SIZE, imagePaths.Count); + cachedImages.AddFirst(await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePaths[imageIndex])); + } + break; + } + } + + private void SlideCanvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args) + { + // Initially required synchronous resources (currently none) + + args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction()); + } + + async Task CreateResourcesAsync(CanvasControl sender) + { + // Cancel old load + if (imageLoadTask != null) + { + imageLoadTask.AsAsyncAction().Cancel(); + try { await imageLoadTask; } catch { } + imageLoadTask = null; + } + + // Unload previously loaded images + currentImageNode = null; + cachedImages.Clear(); + + // Refresh cache + LoadImageInit(); + } + + void HandleLoadExceptions() + { + if (imageLoadTask == null || !imageLoadTask.IsCompleted) + return; + + try + { + imageLoadTask.Wait(); + } + catch (AggregateException aggregateException) + { + // .NET async tasks wrap all errors in an AggregateException. + // We unpack this so Win2D can directly see any lost device errors. + aggregateException.Handle(exception => { throw exception; }); + } + finally + { + imageLoadTask = null; + } + } + + private void BackButton_Click(object sender, RoutedEventArgs e) + { + currentImageIndex = mod(currentImageIndex - 1, imagePaths.Count); + + if (imagePaths.Count <= CACHE_SIZE) + { + currentImageNode = currentImageNode.Previous ?? cachedImages.Last; + } + else + { + // Incremental load of cache + currentImageNode = currentImageNode.Previous; + } + + SlideCanvas.Invalidate(); + } + + private void NextButton_Click(object sender, RoutedEventArgs e) + { + currentImageIndex = mod(currentImageIndex + 1, imagePaths.Count); + + if (imagePaths.Count <= CACHE_SIZE) + { + currentImageNode = currentImageNode.Next ?? cachedImages.First; + } + else + { + // Incremental load of cache + currentImageNode = currentImageNode.Next; + } + + SlideCanvas.Invalidate(); + } + } +} diff --git a/QuickDrawWindows/app.manifest b/QuickDrawWindows/app.manifest new file mode 100644 index 0000000..575a7bb --- /dev/null +++ b/QuickDrawWindows/app.manifest @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + PerMonitorV2 + + + \ No newline at end of file From 08e961792a9a4dc5ee8d10c261e6c000df6fdb5a Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sun, 28 May 2023 08:37:18 -0400 Subject: [PATCH 02/60] Created custom TitleBar control, will Animate height between pages. Fixed naming of TitleBar.png for different scaling factors --- ...tleBar16x16.png => TitleBar.scale-100.png} | Bin ...ar16x16-200.png => TitleBar.scale-200.png} | Bin QuickDrawWindows/MainPage.xaml | 2 +- QuickDrawWindows/MainWindow.xaml | 35 ++++------- QuickDrawWindows/MainWindow.xaml.cs | 1 + QuickDrawWindows/QuickDraw.csproj | 3 +- QuickDrawWindows/Themes/Generic.xaml | 55 ++++++++++++++++++ QuickDrawWindows/TitleBar.cs | 24 ++++++++ QuickDrawWindows/app.manifest | 2 +- 9 files changed, 94 insertions(+), 28 deletions(-) rename QuickDrawWindows/Assets/{TitleBar16x16.png => TitleBar.scale-100.png} (100%) rename QuickDrawWindows/Assets/{TitleBar16x16-200.png => TitleBar.scale-200.png} (100%) create mode 100644 QuickDrawWindows/Themes/Generic.xaml create mode 100644 QuickDrawWindows/TitleBar.cs diff --git a/QuickDrawWindows/Assets/TitleBar16x16.png b/QuickDrawWindows/Assets/TitleBar.scale-100.png similarity index 100% rename from QuickDrawWindows/Assets/TitleBar16x16.png rename to QuickDrawWindows/Assets/TitleBar.scale-100.png diff --git a/QuickDrawWindows/Assets/TitleBar16x16-200.png b/QuickDrawWindows/Assets/TitleBar.scale-200.png similarity index 100% rename from QuickDrawWindows/Assets/TitleBar16x16-200.png rename to QuickDrawWindows/Assets/TitleBar.scale-200.png diff --git a/QuickDrawWindows/MainPage.xaml b/QuickDrawWindows/MainPage.xaml index a2e78e6..0cc7b36 100644 --- a/QuickDrawWindows/MainPage.xaml +++ b/QuickDrawWindows/MainPage.xaml @@ -9,7 +9,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:slider="using:Syncfusion.UI.Xaml.Sliders" mc:Ignorable="d" > - + diff --git a/QuickDrawWindows/MainWindow.xaml b/QuickDrawWindows/MainWindow.xaml index 9fc512c..ae2bd80 100644 --- a/QuickDrawWindows/MainWindow.xaml +++ b/QuickDrawWindows/MainWindow.xaml @@ -10,33 +10,18 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" > - + + + + + + + + - - - - - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/QuickDrawWindows/MainWindow.xaml.cs b/QuickDrawWindows/MainWindow.xaml.cs index 4af8346..7b9f8cf 100644 --- a/QuickDrawWindows/MainWindow.xaml.cs +++ b/QuickDrawWindows/MainWindow.xaml.cs @@ -37,6 +37,7 @@ public MainWindow() public void NavigateToSlideshow() { + VisualStateManager.GoToState(this.AppTitleBar, "SlideLayout", true); this.MainFrame.Navigate(typeof(SlidePage), null, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromRight }); } } diff --git a/QuickDrawWindows/QuickDraw.csproj b/QuickDrawWindows/QuickDraw.csproj index 682ead3..546e4e9 100644 --- a/QuickDrawWindows/QuickDraw.csproj +++ b/QuickDrawWindows/QuickDraw.csproj @@ -30,6 +30,7 @@ + @@ -46,8 +47,8 @@ - + diff --git a/QuickDrawWindows/Themes/Generic.xaml b/QuickDrawWindows/Themes/Generic.xaml new file mode 100644 index 0000000..cd81949 --- /dev/null +++ b/QuickDrawWindows/Themes/Generic.xaml @@ -0,0 +1,55 @@ + + + + diff --git a/QuickDrawWindows/TitleBar.cs b/QuickDrawWindows/TitleBar.cs new file mode 100644 index 0000000..cc302ae --- /dev/null +++ b/QuickDrawWindows/TitleBar.cs @@ -0,0 +1,24 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Documents; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace QuickDraw +{ + public sealed class TitleBar : Control + { + public TitleBar() + { + this.DefaultStyleKey = typeof(TitleBar); + } + } +} diff --git a/QuickDrawWindows/app.manifest b/QuickDrawWindows/app.manifest index 575a7bb..b426497 100644 --- a/QuickDrawWindows/app.manifest +++ b/QuickDrawWindows/app.manifest @@ -1,6 +1,6 @@ - + From afb74db11f05daf822639bca89cf011a38041bc7 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sun, 28 May 2023 09:34:46 -0400 Subject: [PATCH 03/60] Removed Titlebar Height Animation (clickable controls there are buggy) Added layering to TitleBar background --- QuickDrawWindows/MainWindow.xaml | 5 ++++- QuickDrawWindows/Themes/Generic.xaml | 9 ++++----- QuickDrawWindows/TitleBar.cs | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/QuickDrawWindows/MainWindow.xaml b/QuickDrawWindows/MainWindow.xaml index ae2bd80..79fce3d 100644 --- a/QuickDrawWindows/MainWindow.xaml +++ b/QuickDrawWindows/MainWindow.xaml @@ -20,8 +20,11 @@ - + + + + \ No newline at end of file diff --git a/QuickDrawWindows/Themes/Generic.xaml b/QuickDrawWindows/Themes/Generic.xaml index cd81949..aadd01c 100644 --- a/QuickDrawWindows/Themes/Generic.xaml +++ b/QuickDrawWindows/Themes/Generic.xaml @@ -7,7 +7,7 @@ - + @@ -23,9 +23,7 @@ - - - + @@ -36,7 +34,7 @@ - + + diff --git a/QuickDrawWindows/TitleBar.cs b/QuickDrawWindows/TitleBar.cs index cc302ae..b765e0c 100644 --- a/QuickDrawWindows/TitleBar.cs +++ b/QuickDrawWindows/TitleBar.cs @@ -14,8 +14,9 @@ namespace QuickDraw { - public sealed class TitleBar : Control + public sealed class TitleBar :ContentControl { + public Border TitleBarDragElement; public TitleBar() { this.DefaultStyleKey = typeof(TitleBar); From d5c3b5227a00f889eb1b3621ba32a96ed091e1fb Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sun, 28 May 2023 20:11:16 -0400 Subject: [PATCH 04/60] Added back titlebar animation with plans for controls there Now using AppWindow.TitleBar which hopefully should work better --- QuickDrawWindows/MainWindow.xaml.cs | 38 +++++++++++++++++++++++++--- QuickDrawWindows/Themes/Generic.xaml | 13 +++++----- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/QuickDrawWindows/MainWindow.xaml.cs b/QuickDrawWindows/MainWindow.xaml.cs index 7b9f8cf..5246cf7 100644 --- a/QuickDrawWindows/MainWindow.xaml.cs +++ b/QuickDrawWindows/MainWindow.xaml.cs @@ -1,3 +1,5 @@ +using Microsoft.UI; +using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; @@ -11,9 +13,11 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.ViewManagement; +using WinRT.Interop; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -25,20 +29,48 @@ namespace QuickDraw /// public sealed partial class MainWindow : WinUIEx.WindowEx { - + private AppWindow m_AppWindow; public MainWindow() { this.InitializeComponent(); - this.ExtendsContentIntoTitleBar = true; - this.SetTitleBar(this.AppTitleBar); + + m_AppWindow = GetAppWindowForCurrentWindow(); + + var titlebar = this.AppWindow.TitleBar; + titlebar.ExtendsContentIntoTitleBar = true; + titlebar.ButtonBackgroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionButtonBackground"]).Color; + titlebar.ButtonHoverBackgroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionButtonBackgroundPointerOver"]).Color; + titlebar.ButtonPressedBackgroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionButtonBackgroundPressed"]).Color; + titlebar.ButtonInactiveBackgroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionBackgroundDisabled"]).Color; + + titlebar.ButtonForegroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionButtonStroke"]).Color; + titlebar.ButtonHoverForegroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionButtonStrokePointerOver"]).Color; + titlebar.ButtonPressedForegroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionButtonStrokePressed"]).Color; + titlebar.ButtonInactiveForegroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionForegroundDisabled"]).Color; + + titlebar.PreferredHeightOption = TitleBarHeightOption.Standard; this.MainFrame.Navigate(typeof(MainPage)); } public void NavigateToSlideshow() { + var titlebar = this.AppWindow.TitleBar; + var delay = TimeSpan.Parse((string)Application.Current.Resources["ControlFastAnimationDuration"]); + Task.Run(async delegate { + await Task.Delay(delay); + titlebar.PreferredHeightOption = TitleBarHeightOption.Tall; + }); + VisualStateManager.GoToState(this.AppTitleBar, "SlideLayout", true); this.MainFrame.Navigate(typeof(SlidePage), null, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromRight }); } + + private AppWindow GetAppWindowForCurrentWindow() + { + IntPtr hWnd = WindowNative.GetWindowHandle(this); + WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd); + return AppWindow.GetFromWindowId(wndId); + } } } diff --git a/QuickDrawWindows/Themes/Generic.xaml b/QuickDrawWindows/Themes/Generic.xaml index aadd01c..c33e3ab 100644 --- a/QuickDrawWindows/Themes/Generic.xaml +++ b/QuickDrawWindows/Themes/Generic.xaml @@ -7,13 +7,13 @@ - + - + @@ -23,7 +23,9 @@ - + + + @@ -34,7 +36,7 @@ - + - - + \ No newline at end of file From 67161c11826fa758219701fa9169fefa24db8259 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sun, 28 May 2023 21:44:14 -0400 Subject: [PATCH 05/60] Handle the window being inactive correctly in TitleBar --- QuickDrawWindows/MainWindow.xaml | 3 ++- QuickDrawWindows/MainWindow.xaml.cs | 17 +++++++++++++++- QuickDrawWindows/Themes/Generic.xaml | 30 +++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/QuickDrawWindows/MainWindow.xaml b/QuickDrawWindows/MainWindow.xaml index 79fce3d..036223a 100644 --- a/QuickDrawWindows/MainWindow.xaml +++ b/QuickDrawWindows/MainWindow.xaml @@ -9,13 +9,14 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" + Activated="MainWindow_Activated" > - + diff --git a/QuickDrawWindows/MainWindow.xaml.cs b/QuickDrawWindows/MainWindow.xaml.cs index 5246cf7..5319515 100644 --- a/QuickDrawWindows/MainWindow.xaml.cs +++ b/QuickDrawWindows/MainWindow.xaml.cs @@ -1,3 +1,4 @@ +using CommunityToolkit.WinUI.UI; using Microsoft.UI; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; @@ -16,6 +17,7 @@ using System.Threading.Tasks; using Windows.Foundation; using Windows.Foundation.Collections; +using Windows.UI; using Windows.UI.ViewManagement; using WinRT.Interop; @@ -46,11 +48,12 @@ public MainWindow() titlebar.ButtonForegroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionButtonStroke"]).Color; titlebar.ButtonHoverForegroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionButtonStrokePointerOver"]).Color; titlebar.ButtonPressedForegroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionButtonStrokePressed"]).Color; - titlebar.ButtonInactiveForegroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionForegroundDisabled"]).Color; + titlebar.ButtonInactiveForegroundColor = Color.FromArgb(0xff, 0x66,0x66, 0x66); //WindowCaptionForegroundDisabled converted to gray with no alpha, for some reason alpha is ignored here titlebar.PreferredHeightOption = TitleBarHeightOption.Standard; this.MainFrame.Navigate(typeof(MainPage)); + } public void NavigateToSlideshow() @@ -72,5 +75,17 @@ private AppWindow GetAppWindowForCurrentWindow() WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd); return AppWindow.GetFromWindowId(wndId); } + + private void MainWindow_Activated(object sender, WindowActivatedEventArgs args) + { + if (args.WindowActivationState == WindowActivationState.Deactivated) + { + VisualStateManager.GoToState(this.AppTitleBar, "Inactive", true); + } + else + { + VisualStateManager.GoToState(this.AppTitleBar, "Active", true); + } + } } } diff --git a/QuickDrawWindows/Themes/Generic.xaml b/QuickDrawWindows/Themes/Generic.xaml index c33e3ab..ffa1529 100644 --- a/QuickDrawWindows/Themes/Generic.xaml +++ b/QuickDrawWindows/Themes/Generic.xaml @@ -29,6 +29,29 @@ + + + + + + + + + + + + + + + + + + + + + + + @@ -45,7 +68,12 @@ + Margin="8,0,0,0"> + + + + + From 9ccfa9715ff669679e71c84d043c00bb2d6def9d Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Tue, 30 May 2023 14:38:27 -0400 Subject: [PATCH 06/60] Now will load images when the list is bigger than the cache. Misc simplification / cleanup --- QuickDrawWindows/MainPage.xaml.cs | 32 ++--- QuickDrawWindows/MainWindow.xaml.cs | 10 -- QuickDrawWindows/SlidePage.xaml.cs | 209 ++++++++++++++++------------ 3 files changed, 131 insertions(+), 120 deletions(-) diff --git a/QuickDrawWindows/MainPage.xaml.cs b/QuickDrawWindows/MainPage.xaml.cs index 2db8629..7115e02 100644 --- a/QuickDrawWindows/MainPage.xaml.cs +++ b/QuickDrawWindows/MainPage.xaml.cs @@ -30,7 +30,7 @@ namespace QuickDraw /// Internal use only. internal class StringToEnumConverter : IValueConverter { - private Type _enum; + private readonly Type _enum; public StringToEnumConverter(Type type) { @@ -79,7 +79,7 @@ public object ConvertBack(object value, /// Internal use only. internal class DoubleToEnumConverter : IValueConverter { - private Type _enum; + private readonly Type _enum; public DoubleToEnumConverter(Type type) { @@ -150,7 +150,7 @@ public class GridLengthConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, string language) { double val = (double)value; - GridLength gridLength = new GridLength(val); + GridLength gridLength = new(val); return gridLength; } @@ -204,10 +204,10 @@ public int Compare(TextBlock x, TextBlock y) } } - private SortedSet _pathTexts = new SortedSet(new ByWidth()); - private SortedSet _imageCountTexts = new SortedSet(new ByWidth()); - private HashSet _pathColumns = new HashSet(); - private HashSet _imageCountColumns = new HashSet(); + private readonly SortedSet _pathTexts = new(new ByWidth()); + private readonly SortedSet _imageCountTexts = new(new ByWidth()); + private readonly HashSet _pathColumns = new(); + private readonly HashSet _imageCountColumns = new(); public double PathColumnWidth { get; set; } = 1000; public double ImageCountColumnWidth { get; set; } = 1000; @@ -220,10 +220,9 @@ public MainPage() this.Resources.Add("stringToEnumConverter", new StringToEnumConverter(typeof(TimerEnum))); } - private void UpdateColumnWidths(TextBlock pathText, TextBlock imageCountText, bool debug = false) + private void UpdateColumnWidths(Grid grid) { if (_pathTexts.Count < 1 || _imageCountTexts.Count < 1) { return; } - var grid = (Grid)pathText.Parent; ImageCountColumnWidth = _imageCountTexts.Max.ActualWidth + 20; @@ -233,8 +232,6 @@ private void UpdateColumnWidths(TextBlock pathText, TextBlock imageCountText, bo var maxPathColumnWidth = _pathTexts.Max != null ? _pathTexts.Max.PreWrappedWidth() : 0; PathColumnWidth = Math.Max(100, Math.Min(availableWidth,maxPathColumnWidth)); - Debug.WriteLine(_pathTexts.Max.ActualWidth); - foreach (ColumnDefinition pathColumn in _pathColumns) { pathColumn.Width = new GridLength(PathColumnWidth, GridUnitType.Pixel); @@ -252,14 +249,12 @@ private void PathText_SizeChanged(object sender, SizeChangedEventArgs e) var pathText = (TextBlock)sender; var grid = (Grid)pathText.Parent; - var imageCountText = (TextBlock)grid.FindChild("ImageCountText"); - _pathTexts.Remove(pathText); _pathTexts.Add(pathText); _pathColumns.Add(grid.ColumnDefinitions[0]); - UpdateColumnWidths(pathText, imageCountText, true); + UpdateColumnWidths(grid); } private void ImageCountText_SizeChanged(object sender, SizeChangedEventArgs e) @@ -267,14 +262,12 @@ private void ImageCountText_SizeChanged(object sender, SizeChangedEventArgs e) var imageCountText = (TextBlock)sender; var grid = (Grid)imageCountText.Parent; - var pathText = (TextBlock)grid.FindChild("PathText"); - _imageCountTexts.Remove(imageCountText); _imageCountTexts.Add(imageCountText); _imageCountColumns.Add(grid.ColumnDefinitions[1]); - UpdateColumnWidths(pathText, imageCountText); + UpdateColumnWidths(grid); } private void AppBarButton_Click(object sender, RoutedEventArgs e) @@ -286,10 +279,7 @@ private void Grid_SizeChanged(object sender, SizeChangedEventArgs e) { Grid grid = (Grid)sender; - var pathText = (TextBlock)grid.FindChild("PathText"); - var imageCountText = (TextBlock)grid.FindChild("ImageCountText"); - - UpdateColumnWidths(pathText, imageCountText); + UpdateColumnWidths(grid); } private void StartButton_Click(object sender, RoutedEventArgs e) diff --git a/QuickDrawWindows/MainWindow.xaml.cs b/QuickDrawWindows/MainWindow.xaml.cs index 5319515..a910f45 100644 --- a/QuickDrawWindows/MainWindow.xaml.cs +++ b/QuickDrawWindows/MainWindow.xaml.cs @@ -31,13 +31,10 @@ namespace QuickDraw /// public sealed partial class MainWindow : WinUIEx.WindowEx { - private AppWindow m_AppWindow; public MainWindow() { this.InitializeComponent(); - m_AppWindow = GetAppWindowForCurrentWindow(); - var titlebar = this.AppWindow.TitleBar; titlebar.ExtendsContentIntoTitleBar = true; titlebar.ButtonBackgroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionButtonBackground"]).Color; @@ -69,13 +66,6 @@ public void NavigateToSlideshow() this.MainFrame.Navigate(typeof(SlidePage), null, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromRight }); } - private AppWindow GetAppWindowForCurrentWindow() - { - IntPtr hWnd = WindowNative.GetWindowHandle(this); - WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd); - return AppWindow.GetFromWindowId(wndId); - } - private void MainWindow_Activated(object sender, WindowActivatedEventArgs args) { if (args.WindowActivationState == WindowActivationState.Deactivated) diff --git a/QuickDrawWindows/SlidePage.xaml.cs b/QuickDrawWindows/SlidePage.xaml.cs index c8a0686..f08f34b 100644 --- a/QuickDrawWindows/SlidePage.xaml.cs +++ b/QuickDrawWindows/SlidePage.xaml.cs @@ -19,6 +19,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading; using System.Threading.Tasks; using Windows.Devices.AllJoyn; using Windows.Foundation; @@ -44,31 +45,56 @@ public sealed partial class SlidePage : Page private const int CACHE_SIZE = 9; private const int HALF_CACHE_SIZE = CACHE_SIZE / 2; - List imagePaths = new List + private readonly List imagePaths = new() { - "C:\\Users\\blendermf\\Pictures\\Reference\\4b8e79076da0a545f5d8474d48d6aba9.jpg", - "C:\\Users\\blendermf\\Pictures\\Reference\\b266995705bf837bd882ef7ec8a0c658.jpg", - "C:\\Users\\blendermf\\Pictures\\Reference\\ce509886c60122d6841b5a3c40e40c13.jpg", + @"C:\Users\blendermf\Pictures\Reference\4b8e79076da0a545f5d8474d48d6aba9.jpg", + @"C:\Users\blendermf\Pictures\Reference\b266995705bf837bd882ef7ec8a0c658.jpg", + @"C:\Users\blendermf\Pictures\Reference\ce509886c60122d6841b5a3c40e40c13.jpg", - "C:\\Users\\blendermf\\Pictures\\Reference\\Baton_Rouge_Louisiana (18 of 108).jpg", - "C:\\Users\\blendermf\\Pictures\\Reference\\5ffd4bf8b2e9765d4357e39da574e65a.jpg", - "C:\\Users\\blendermf\\Pictures\\Reference\\ea193f9771bbe55c1562ca8587371c2f.jpg", + @"C:\Users\blendermf\Pictures\Reference\Baton_Rouge_Louisiana (18 of 108).jpg", + @"C:\Users\blendermf\Pictures\Reference\5ffd4bf8b2e9765d4357e39da574e65a.jpg", + @"C:\Users\blendermf\Pictures\Reference\ea193f9771bbe55c1562ca8587371c2f.jpg", - "C:\\Users\\blendermf\\Pictures\\Reference\\4e671baaf8d17256d82978d04bb68275.jpg", - "C:\\Users\\blendermf\\Pictures\\Reference\\5f1f0087ebb95483a19f3acf438248c3.jpg", - "C:\\Users\\blendermf\\Pictures\\Reference\\9c9c5055fcfa58b4a4c8c0969a908984.jpg", + @"C:\Users\blendermf\Pictures\Reference\4e671baaf8d17256d82978d04bb68275.jpg", + @"C:\Users\blendermf\Pictures\Reference\5f1f0087ebb95483a19f3acf438248c3.jpg", + @"C:\Users\blendermf\Pictures\Reference\9c9c5055fcfa58b4a4c8c0969a908984.jpg", + + @"D:\Reference\Image Reference\Poses\Women\00ab97eb3768fe67600e16e4e2f4be28.jpg", + @"D:\Reference\Image Reference\Poses\Women\00d8a150d3327954bb6d217c94d93e10.jpg", + @"D:\Reference\Image Reference\Poses\Women\0cc5e36c58c975b8f580af6ba9104fc1.jpg", + + @"D:\Reference\Image Reference\Poses\Women\0d44ac6bbc2f76ad0dbd5a6a4e49656f.jpg", + @"D:\Reference\Image Reference\Poses\Women\01c3307c28a95405b448f8ba6b55c8c8.jpg", + @"D:\Reference\Image Reference\Poses\Women\01d050c898dbdbfc16f8c58984f6cfb0.jpg", + + @"D:\Reference\Image Reference\Poses\Women\01d6068f148122e65c20d71700a172cd.jpg", + @"D:\Reference\Image Reference\Poses\Women\0fe196d54935159419d425c96e084839.jpg", + @"D:\Reference\Image Reference\Poses\Women\0d43509436a0dea9e92274def74be668.jpg", + + @"D:\Reference\Image Reference\Poses\Women\1b7cebc4eec7b5a8bd7a8eb8cbfc0561.jpg", + @"D:\Reference\Image Reference\Poses\Women\1c91b180a9bba47b9af3e9e65f08b6e0.jpg", + @"D:\Reference\Image Reference\Poses\Women\1bc1899d6610ed5322316abcf1ca14ea.jpg", + + @"D:\Reference\Image Reference\Poses\Women\0e6817b0c076405a00fb987f4f405eea.jpg", + @"D:\Reference\Image Reference\Poses\Women\0e92728c5c2758332a5b141d174b5696.jpg", + @"D:\Reference\Image Reference\Poses\Women\0ef896c25863a1a4b1fdf60b901c1fd6.jpg", + + @"D:\Reference\Image Reference\Poses\Women\2c5d9ca90e2bc17a33a15508e5e4cce6.jpg", + @"D:\Reference\Image Reference\Poses\Women\2d897822c8ba784742b96fdf39880494.jpg", + @"D:\Reference\Image Reference\Poses\Women\2df95b09f17abe50abffc7a1d43e2324.jpg", }; - LinkedList cachedImages = new LinkedList (); - - LinkedListNode currentImageNode = null; - int currentImageIndex = 0; - Task imageLoadTask; + private readonly LinkedList cachedImages = new(); + + private LinkedListNode currentImageNode = null; + private int imageCachePosition = 0; + private Task imageLoadTask; + + private readonly object cachedImagesLock = new(); public SlidePage() { this.InitializeComponent(); - Task.Run(LoadImageInit); } private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args) @@ -77,13 +103,15 @@ private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args) if (currentImageNode == null) { - + Debug.WriteLine("Nothing to Show"); } else { - CanvasVirtualBitmap bitmap = currentImageNode.Value; + CanvasVirtualBitmap bitmap; - Size canvasSize = new Size(sender.ActualWidth, sender.ActualHeight); + bitmap = currentImageNode.Value; + + Size canvasSize = new(sender.ActualWidth, sender.ActualHeight); double canvasAspect = canvasSize.Width / canvasSize.Height; double bitmapAspect = bitmap.Bounds.Width / bitmap.Bounds.Height; Size imageRenderSize; @@ -106,44 +134,47 @@ private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args) imagePos = new Point((canvasSize.Width - imageRenderSize.Width) / 2, 0); } - args.DrawingSession.DrawImage(currentImageNode.Value, new Rect(imagePos, imageRenderSize), bitmap.Bounds); + args.DrawingSession.DrawImage(bitmap, new Rect(imagePos, imageRenderSize), bitmap.Bounds); } } - public void LoadImageInit() + private void LoadImageInit() { Debug.Assert(imageLoadTask == null); - imageLoadTask = FillImageCacheAsync(this.SlideCanvas, currentImageIndex).ContinueWith(_ => SlideCanvas.Invalidate()); + + imageLoadTask = FillImageCacheAsync(this.SlideCanvas, imageCachePosition).ContinueWith(async _ => { + SlideCanvas.Invalidate(); + }); } - int mod (int n, int d) + private static int Mod (int n, int d) { int r = n % d; return r < 0 ? r+d : r; } - async Task FillImageCacheAsync(CanvasControl resourceCreator, int index) + private async Task FillImageCacheAsync(CanvasControl resourceCreator, int index) { var bitmap = await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePaths[index]); + currentImageNode = cachedImages.AddFirst(bitmap); - currentImageIndex = index; + + imageCachePosition = index; var remainingCacheSize = Math.Min(imagePaths.Count, CACHE_SIZE) - 1; var numBefore = (remainingCacheSize / 2); var numAfter = (remainingCacheSize / 2) + (remainingCacheSize % 2); var beforeImages = Enumerable.Range(index - numBefore, numBefore) - .AsParallel() .Reverse() - .Select(i => mod(i, imagePaths.Count)) + .Select(i => Mod(i, imagePaths.Count)) .ToArray() .Select(i => { return imagePaths[i]; }); var afterImages = Enumerable.Range(index + 1, numAfter) - .AsParallel() - .Select(i => mod(i, imagePaths.Count)) + .Select(i => Mod(i, imagePaths.Count)) .ToArray() .Select(i => { @@ -155,8 +186,7 @@ async Task LoadBeforeAsync() foreach (var image in beforeImages) { var bitmap = await CanvasVirtualBitmap.LoadAsync(resourceCreator, image); - - lock (cachedImages) + lock(cachedImages) { cachedImages.AddFirst(bitmap); } @@ -168,7 +198,6 @@ async Task LoadAfterAsync() foreach (var image in afterImages) { var bitmap = await CanvasVirtualBitmap.LoadAsync(resourceCreator, image); - lock (cachedImages) { cachedImages.AddLast(bitmap); @@ -176,46 +205,18 @@ async Task LoadAfterAsync() } } - Task loadBeforeTask = LoadBeforeAsync(); - Task loadAfterTask = LoadAfterAsync(); + Task loadBeforeTask = Task.Run(LoadBeforeAsync); + Task loadAfterTask = Task.Run(LoadAfterAsync); await loadBeforeTask; await loadAfterTask; } - async Task UpdateImageAsync(CanvasControl resourceCreator, LoadDirection direction) - { - switch (direction) - { - case LoadDirection.Backwards: - { - cachedImages.Last.Value.Dispose(); - cachedImages.RemoveLast(); - - var imageIndex = mod(currentImageIndex - HALF_CACHE_SIZE, imagePaths.Count); - cachedImages.AddFirst(await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePaths[imageIndex])); - } - break; - - case LoadDirection.Forwards: - { - cachedImages.First.Value.Dispose(); - cachedImages.RemoveFirst(); - - var imageIndex = mod(currentImageIndex + HALF_CACHE_SIZE, imagePaths.Count); - cachedImages.AddFirst(await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePaths[imageIndex])); - } - break; - } - } - private void SlideCanvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args) { - // Initially required synchronous resources (currently none) - - args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction()); + args.TrackAsyncAction(CreateResourcesAsync().AsAsyncAction()); } - async Task CreateResourcesAsync(CanvasControl sender) + private async Task CreateResourcesAsync() { // Cancel old load if (imageLoadTask != null) @@ -223,17 +224,17 @@ async Task CreateResourcesAsync(CanvasControl sender) imageLoadTask.AsAsyncAction().Cancel(); try { await imageLoadTask; } catch { } imageLoadTask = null; + } // Unload previously loaded images currentImageNode = null; cachedImages.Clear(); - // Refresh cache LoadImageInit(); } - void HandleLoadExceptions() + private void HandleLoadExceptions() { if (imageLoadTask == null || !imageLoadTask.IsCompleted) return; @@ -242,50 +243,80 @@ void HandleLoadExceptions() { imageLoadTask.Wait(); } - catch (AggregateException aggregateException) + catch(AggregateException aggregateException) { - // .NET async tasks wrap all errors in an AggregateException. - // We unpack this so Win2D can directly see any lost device errors. aggregateException.Handle(exception => { throw exception; }); } - finally - { - imageLoadTask = null; - } } - private void BackButton_Click(object sender, RoutedEventArgs e) + private async Task UpdateImageAsync(CanvasControl resourceCreator, LoadDirection direction) { - currentImageIndex = mod(currentImageIndex - 1, imagePaths.Count); + var increment = direction == LoadDirection.Forwards ? 1 : -1; + var halfCache = direction == LoadDirection.Forwards ? HALF_CACHE_SIZE : -HALF_CACHE_SIZE; - if (imagePaths.Count <= CACHE_SIZE) - { - currentImageNode = currentImageNode.Previous ?? cachedImages.Last; - } - else + var imageIndex = Mod(imageCachePosition + increment + halfCache, imagePaths.Count); + + var bitmap = await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePaths[imageIndex]); + + switch (direction) { - // Incremental load of cache - currentImageNode = currentImageNode.Previous; + case LoadDirection.Backwards: + { + cachedImages.AddFirst(bitmap); + + cachedImages.Last.Value.Dispose(); + cachedImages.RemoveLast(); + } + break; + + case LoadDirection.Forwards: + { + cachedImages.AddLast(bitmap); + + cachedImages.First.Value.Dispose(); + cachedImages.RemoveFirst(); + } + break; } - SlideCanvas.Invalidate(); + imageCachePosition = Mod(imageCachePosition + increment, imagePaths.Count); } - private void NextButton_Click(object sender, RoutedEventArgs e) + private async Task Move(LoadDirection direction) { - currentImageIndex = mod(currentImageIndex + 1, imagePaths.Count); + + if (imagePaths.Count > CACHE_SIZE) + { + + await UpdateImageAsync(this.SlideCanvas, direction); + } + + var increment = direction == LoadDirection.Forwards ? 1 : -1; if (imagePaths.Count <= CACHE_SIZE) { - currentImageNode = currentImageNode.Next ?? cachedImages.First; - } + currentImageNode = direction == LoadDirection.Forwards ? + (currentImageNode.Next ?? cachedImages.First) : + (currentImageNode.Previous ?? cachedImages.Last); + } else { - // Incremental load of cache - currentImageNode = currentImageNode.Next; + currentImageNode = direction == LoadDirection.Forwards ? + currentImageNode.Next : + currentImageNode.Previous; } SlideCanvas.Invalidate(); } + + private async void BackButton_Click(object sender, RoutedEventArgs e) + { + await Move(LoadDirection.Backwards); + } + + private async void NextButton_Click(object sender, RoutedEventArgs e) + { + await Move(LoadDirection.Forwards); + } } } From 4b229e3898b3d49bcea805dd4006747948313db2 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Wed, 31 May 2023 09:29:31 -0400 Subject: [PATCH 07/60] Implemented (at least the visuals) of the Titlebars Moved them into each page, put all logic for their sizing in their respective classes. Changed the design of MainPage a bit. --- QuickDrawWindows/MainPage.xaml | 183 +++++++++++++++------------ QuickDrawWindows/MainTitleBar.cs | 108 ++++++++++++++++ QuickDrawWindows/MainWindow.xaml | 14 +- QuickDrawWindows/MainWindow.xaml.cs | 30 ++--- QuickDrawWindows/SlidePage.xaml | 11 +- QuickDrawWindows/SlidePage.xaml.cs | 9 +- QuickDrawWindows/SlideTitleBar.cs | 119 +++++++++++++++++ QuickDrawWindows/Themes/Generic.xaml | 118 ++++++++++++----- QuickDrawWindows/TitleBar.cs | 25 ---- QuickDrawWindows/Utilities.cs | 60 +++++++++ 10 files changed, 495 insertions(+), 182 deletions(-) create mode 100644 QuickDrawWindows/MainTitleBar.cs create mode 100644 QuickDrawWindows/SlideTitleBar.cs delete mode 100644 QuickDrawWindows/TitleBar.cs create mode 100644 QuickDrawWindows/Utilities.cs diff --git a/QuickDrawWindows/MainPage.xaml b/QuickDrawWindows/MainPage.xaml index 0cc7b36..b338133 100644 --- a/QuickDrawWindows/MainPage.xaml +++ b/QuickDrawWindows/MainPage.xaml @@ -28,101 +28,118 @@ - + - + - - - - - - - - - - - - - - - - - - + + - - + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + diff --git a/QuickDrawWindows/MainTitleBar.cs b/QuickDrawWindows/MainTitleBar.cs new file mode 100644 index 0000000..a3eb4ad --- /dev/null +++ b/QuickDrawWindows/MainTitleBar.cs @@ -0,0 +1,108 @@ +using CommunityToolkit.WinUI.UI; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Documents; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection.Emit; +using System.Runtime.InteropServices.WindowsRuntime; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace QuickDraw +{ + + + public sealed class MainTitleBar :ContentControl + { + //public ColumnDefinition LeftPaddingColumn; + AppWindow m_appWindow; + MainWindow m_window; + AppWindowTitleBar m_titleBar; + + double m_leftInset = 0; + double m_rightInset = 0; + public MainTitleBar() + { + this.DefaultStyleKey = typeof(MainTitleBar); + } + + protected override void OnApplyTemplate() + { + m_window = ((App)Application.Current).Window; + m_appWindow = m_window.AppWindow; + m_titleBar = m_appWindow.TitleBar; + + m_window.Activated += Window_Activated; + this.Unloaded += MainTitleBar_Unloaded; + + this.SizeChanged += TitleBar_SizeChanged; + + m_titleBar.PreferredHeightOption = TitleBarHeightOption.Standard; + ApplyInset(); + SetDragRegion(); + } + + void ApplyInset() + { + var scale = Utilities.GetInvertedScaleAdjustment(m_window); + + m_leftInset = (double)m_titleBar.LeftInset * scale; + m_rightInset = (double)m_titleBar.RightInset * scale; + + (GetTemplateChild("LeftInsetColumn") as ColumnDefinition).Width = new GridLength(m_leftInset, GridUnitType.Pixel); + (GetTemplateChild("RightInsetColumn") as ColumnDefinition).Width = new GridLength(m_rightInset, GridUnitType.Pixel); + } + + private void SetDragRegion() + { + double scale = Utilities.GetScaleAdjustment(m_window); + + var titleWidth = (GetTemplateChild("TitleColumn") as ColumnDefinition).ActualWidth; + + List dragRectsList = new(); + + Windows.Graphics.RectInt32 dragRect = new( + (int)(m_leftInset * scale), + 0, + (int)(titleWidth * scale), + (int)(this.ActualHeight * scale) + ); + + Windows.Graphics.RectInt32[] dragRects = { dragRect }; + + m_titleBar.SetDragRectangles(dragRects); + } + + private void TitleBar_SizeChanged(object sender, SizeChangedEventArgs e) + { + SetDragRegion(); + } + + void MainTitleBar_Unloaded(object sender, RoutedEventArgs e) + { + m_appWindow = null; + m_window = null; + m_titleBar = null; + } + + private void Window_Activated(object sender, WindowActivatedEventArgs args) + { + if (args.WindowActivationState == WindowActivationState.Deactivated) + { + VisualStateManager.GoToState(this, "Inactive", true); + } + else + { + VisualStateManager.GoToState(this, "Active", true); + } + } + } +} diff --git a/QuickDrawWindows/MainWindow.xaml b/QuickDrawWindows/MainWindow.xaml index 036223a..8cd3c46 100644 --- a/QuickDrawWindows/MainWindow.xaml +++ b/QuickDrawWindows/MainWindow.xaml @@ -9,23 +9,11 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" - Activated="MainWindow_Activated" > - - - - - - - - - - - - + \ No newline at end of file diff --git a/QuickDrawWindows/MainWindow.xaml.cs b/QuickDrawWindows/MainWindow.xaml.cs index a910f45..ac28063 100644 --- a/QuickDrawWindows/MainWindow.xaml.cs +++ b/QuickDrawWindows/MainWindow.xaml.cs @@ -11,6 +11,7 @@ using Microsoft.UI.Xaml.Navigation; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; @@ -19,8 +20,10 @@ using Windows.Foundation.Collections; using Windows.UI; using Windows.UI.ViewManagement; +using Windows.UI.WindowManagement; using WinRT.Interop; + // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -47,35 +50,22 @@ public MainWindow() titlebar.ButtonPressedForegroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionButtonStrokePressed"]).Color; titlebar.ButtonInactiveForegroundColor = Color.FromArgb(0xff, 0x66,0x66, 0x66); //WindowCaptionForegroundDisabled converted to gray with no alpha, for some reason alpha is ignored here - titlebar.PreferredHeightOption = TitleBarHeightOption.Standard; - this.MainFrame.Navigate(typeof(MainPage)); } + + public void NavigateToSlideshow() { - var titlebar = this.AppWindow.TitleBar; - var delay = TimeSpan.Parse((string)Application.Current.Resources["ControlFastAnimationDuration"]); - Task.Run(async delegate { - await Task.Delay(delay); - titlebar.PreferredHeightOption = TitleBarHeightOption.Tall; - }); - - VisualStateManager.GoToState(this.AppTitleBar, "SlideLayout", true); - this.MainFrame.Navigate(typeof(SlidePage), null, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromRight }); + MainFrame.Navigate(typeof(SlidePage), null, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromRight }); } - private void MainWindow_Activated(object sender, WindowActivatedEventArgs args) + public void NavigateToMain() { - if (args.WindowActivationState == WindowActivationState.Deactivated) - { - VisualStateManager.GoToState(this.AppTitleBar, "Inactive", true); - } - else - { - VisualStateManager.GoToState(this.AppTitleBar, "Active", true); - } + MainFrame.Navigate(typeof(MainPage), null, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromLeft }); } + + } } diff --git a/QuickDrawWindows/SlidePage.xaml b/QuickDrawWindows/SlidePage.xaml index bbd0625..606df54 100644 --- a/QuickDrawWindows/SlidePage.xaml +++ b/QuickDrawWindows/SlidePage.xaml @@ -14,10 +14,11 @@ - - - - - + + + + + + diff --git a/QuickDrawWindows/SlidePage.xaml.cs b/QuickDrawWindows/SlidePage.xaml.cs index f08f34b..a4b3d00 100644 --- a/QuickDrawWindows/SlidePage.xaml.cs +++ b/QuickDrawWindows/SlidePage.xaml.cs @@ -95,6 +95,14 @@ public sealed partial class SlidePage : Page public SlidePage() { this.InitializeComponent(); + + this.Unloaded += SlidePage_Unloaded; + } + + void SlidePage_Unloaded(object sender, RoutedEventArgs e) + { + this.SlideCanvas.RemoveFromVisualTree(); + this.SlideCanvas = null; } private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args) @@ -103,7 +111,6 @@ private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args) if (currentImageNode == null) { - Debug.WriteLine("Nothing to Show"); } else { diff --git a/QuickDrawWindows/SlideTitleBar.cs b/QuickDrawWindows/SlideTitleBar.cs new file mode 100644 index 0000000..d429b5a --- /dev/null +++ b/QuickDrawWindows/SlideTitleBar.cs @@ -0,0 +1,119 @@ +using CommunityToolkit.WinUI.UI; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Documents; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection.Emit; +using System.Runtime.InteropServices.WindowsRuntime; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace QuickDraw +{ + + + public sealed class SlideTitleBar : ContentControl + { + + AppWindow m_appWindow; + MainWindow m_window; + AppWindowTitleBar m_titleBar; + + double m_leftInset = 0; + double m_rightInset = 0; + + public SlideTitleBar() + { + this.DefaultStyleKey = typeof(SlideTitleBar); + } + + protected override void OnApplyTemplate() + { + m_window = ((App)Application.Current).Window; + m_appWindow = m_window.AppWindow; + m_titleBar = m_appWindow.TitleBar; + + this.Unloaded += SlideTitleBar_Unloaded; + + this.SizeChanged += TitleBar_SizeChanged; + + (GetTemplateChild("BackButton") as Button).Click += SlideTitleBar_BackClick; + + m_titleBar.PreferredHeightOption = TitleBarHeightOption.Tall; + ApplyInset(); + SetDragRegion(); + } + + void ApplyInset() + { + var scale = Utilities.GetInvertedScaleAdjustment(m_window); + + m_leftInset = (double)m_titleBar.LeftInset * scale; + m_rightInset = (double)m_titleBar.RightInset * scale; + + (GetTemplateChild("LeftInsetColumn") as ColumnDefinition).Width = new GridLength(m_leftInset, GridUnitType.Pixel); + (GetTemplateChild("RightInsetColumn") as ColumnDefinition).Width = new GridLength(m_rightInset, GridUnitType.Pixel); + + // TODO: set min widths of the centering columns, store it for the drag region + } + + private void SetDragRegion() + { + double scale = Utilities.GetScaleAdjustment(m_window); + + var backWidth = (GetTemplateChild("BackColumn") as ColumnDefinition).ActualWidth; + var centerLeftWidth = (GetTemplateChild("CenterLeftColumn") as ColumnDefinition).ActualWidth; + var centerRightWidth = (GetTemplateChild("CenterRightColumn") as ColumnDefinition).ActualWidth; + + List dragRectsList = new(); + + Windows.Graphics.RectInt32 dragRectL = new( + (int)((m_leftInset + backWidth) * scale), + 0, + (int)((centerLeftWidth - backWidth - m_leftInset) * scale), + (int)(this.ActualHeight * scale) + ); + + dragRectsList.Add(dragRectL); + + + Windows.Graphics.RectInt32 dragRectR = new( + (int)((this.ActualWidth - centerRightWidth) * scale), + 0, + (int)((centerRightWidth - m_rightInset) * scale), + (int)(this.ActualHeight * scale) + ); + + dragRectsList.Add(dragRectR); + + Windows.Graphics.RectInt32[] dragRects = dragRectsList.ToArray(); + + m_titleBar.SetDragRectangles(dragRects); + } + + private void TitleBar_SizeChanged(object sender, SizeChangedEventArgs e) + { + SetDragRegion(); + } + + void SlideTitleBar_Unloaded(object sender, RoutedEventArgs e) + { + m_appWindow = null; + m_window = null; + m_titleBar = null; + } + + void SlideTitleBar_BackClick(object sender, RoutedEventArgs e) + { + m_window.NavigateToMain(); + } + } +} diff --git a/QuickDrawWindows/Themes/Generic.xaml b/QuickDrawWindows/Themes/Generic.xaml index ffa1529..3bb395e 100644 --- a/QuickDrawWindows/Themes/Generic.xaml +++ b/QuickDrawWindows/Themes/Generic.xaml @@ -2,34 +2,79 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:QuickDraw"> - - + - \ No newline at end of file + + + + + From ca86e9d9834f7f8dfedc74300c742dbd72847cd8 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Mon, 17 Mar 2025 19:18:02 -0400 Subject: [PATCH 17/60] Removed unecessary style for MFFolderListViewItem that no longer exists --- QuickDrawWindows/Themes/Generic.xaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/QuickDrawWindows/Themes/Generic.xaml b/QuickDrawWindows/Themes/Generic.xaml index 0a491b1..a140e19 100644 --- a/QuickDrawWindows/Themes/Generic.xaml +++ b/QuickDrawWindows/Themes/Generic.xaml @@ -3,8 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:QuickDraw"> - + diff --git a/QuickDrawWindows/MFImageFolderListView.xaml.cs b/QuickDrawWindows/MFImageFolderListView.xaml.cs index 49c4804..a87ec73 100644 --- a/QuickDrawWindows/MFImageFolderListView.xaml.cs +++ b/QuickDrawWindows/MFImageFolderListView.xaml.cs @@ -20,6 +20,8 @@ using System.Threading.Tasks; using System.Collections.Specialized; using QuickDraw.Utilities; +using System.Diagnostics; +using WinRT; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -95,81 +97,81 @@ public MFImageFolderListView() { this.InitializeComponent(); - var app = (App.Current as App); + var settings = (App.Current as App)?.Settings; - void UpdateImageFolderCollection() - { - ImageFolderCollection = new MFObservableCollection(app.Settings.ImageFolderList.ImageFolders); + ImageFolderCollection = new MFObservableCollection(settings.ImageFolderList.ImageFolders); - ImageFolderCollection.CollectionChanged += (sender, e) => { - if ((e as MFNotifyCollectionChangedEventArgs)?.FromModel ?? false) - { - return; - } - switch (e.Action) - { - case NotifyCollectionChangedAction.Move: - app.Settings.ImageFolderList.ImageFolders.RemoveRange(0, e.OldItems.Count); - app.Settings.ImageFolderList.ImageFolders.InsertRange(e.NewStartingIndex, e.NewItems.OfType()); + ImageFolderListView.Loaded += (sender, e) => { + foreach (var i in ImageFolderCollection.Index().Where(ft => ft.Item.Selected).Select(ft => ft.Index)) + { + ImageFolderListView.SelectRange(new(i, 1)); + } - break; - case NotifyCollectionChangedAction.Remove: - app.Settings.ImageFolderList.ImageFolders.RemoveRange(e.OldStartingIndex, e.OldItems.Count); + }; - break; + ImageFolderCollection.CollectionChanged += (sender, e) => { + if ((e as MFNotifyCollectionChangedEventArgs)?.FromModel ?? false) + { + return; + } + switch (e.Action) + { + case NotifyCollectionChangedAction.Move: + settings.ImageFolderList.ImageFolders.RemoveRange(0, e.OldItems.Count); + settings.ImageFolderList.ImageFolders.InsertRange(e.NewStartingIndex, e.NewItems.OfType()); + + break; + case NotifyCollectionChangedAction.Remove: + settings.ImageFolderList.ImageFolders.RemoveRange(e.OldStartingIndex, e.OldItems.Count); + + break; + + case NotifyCollectionChangedAction.Add: + if (e.NewStartingIndex != -1) + { + settings.ImageFolderList.ImageFolders.InsertRange(e.NewStartingIndex, e.NewItems.OfType()); + } + else + { + settings.ImageFolderList.ImageFolders.AddRange(e.NewItems.OfType()); + } + break; + default: + break; + } + settings.WriteSettings(); + }; + + settings.ImageFolderList.CollectionChanged += (sender, e) => + { + DispatcherQueue.EnqueueAsync(() => + { + var i = 0; + switch (e.Action) + { case NotifyCollectionChangedAction.Add: - if (e.NewStartingIndex != -1) + i = 0; + foreach (var item in e.NewItems) { - app.Settings.ImageFolderList.ImageFolders.InsertRange(e.NewStartingIndex, e.NewItems.OfType()); - } else + ImageFolderCollection.AddFromModel(item as MFImageFolder); + i++; + } + break; + case NotifyCollectionChangedAction.Replace: + i = 0; + foreach (var item in e.OldItems) { - app.Settings.ImageFolderList.ImageFolders.AddRange(e.NewItems.OfType()); + ImageFolderCollection.SetFromModel(e.NewStartingIndex + i, item as MFImageFolder); + i++; } - - break; + break; default: break; } - app.Settings.WriteSettings(); - }; + }); - app.Settings.ImageFolderList.CollectionChanged += (sender, e) => - { - DispatcherQueue.EnqueueAsync(() => - { - var i = 0; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - i = 0; - foreach (var item in e.NewItems) - { - ImageFolderCollection.AddFromModel(item as MFImageFolder); - i++; - } - break; - case NotifyCollectionChangedAction.Replace: - i = 0; - foreach (var item in e.OldItems) - { - ImageFolderCollection.SetFromModel(e.NewStartingIndex + i, item as MFImageFolder); - i++; - } - break; - default: - break; - } - }); - - app.Settings.WriteSettings(); - }; - } - - UpdateImageFolderCollection(); - - app.Settings.PropertyChanged += (sender, e) => { - UpdateImageFolderCollection(); + settings.WriteSettings(); }; } @@ -271,7 +273,87 @@ private void OpenFolders() private void AddFoldersButton_Click(object sender, RoutedEventArgs e) { + OpenFolders(); + ImageFolderListView.SelectRange(new(0, 1)); + } + + private void ImageFolderListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + var settings = (App.Current as App)?.Settings; + + foreach (var item in e.AddedItems.Cast()) + { + item.Selected = true; + var index = ImageFolderCollection.IndexOf(item); + + settings.ImageFolderList.ImageFolders[index].Selected = true; + } + + foreach (var item in e.RemovedItems.Cast()) + { + if (ItemsDraggedSelected.Contains(item)) + { + ImageFolderListView.SelectedItems.Add(item); + continue; + } + item.Selected = false; + var index = ImageFolderCollection.IndexOf(item); + + if (index >= 0) + { + settings.ImageFolderList.ImageFolders[index].Selected = false; + } + } + + settings.WriteSettings(); + } + + private List ItemsDraggedSelected = []; + + private void ImageFolderListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e) + { + var items = e.Items.Cast(); + + + + var hovereditems = VisualTreeHelper.FindElementsInHostCoordinates(lastPointerPos, ImageFolderListView); + var hoveritemelem = hovereditems.First(i => i is ListViewItem); + var hoveritem = ImageFolderListView.ItemFromContainer(hoveritemelem as ListViewItem); + + ItemsDraggedSelected.AddRange(items.Where(f => f.Selected)); + + + foreach (var item in items) + { + if (item != hoveritem) + { + var itemelem = ImageFolderListView.ContainerFromItem(item) as ListViewItem; + + VisualStateManager.GoToState(itemelem, "DragHidden", true); + } + } + } + + private void ImageFolderListView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args) + { + + var items = args.Items.Cast(); + + foreach (var item in items) + { + var itemelem = ImageFolderListView.ContainerFromItem(item) as ListViewItem; + + VisualStateManager.GoToState(itemelem, "DragVisible", true); + } + ItemsDraggedSelected.Clear(); + } + + Point lastPointerPos = new(); + + private void ImageFolderListView_PointerMoved(object sender, PointerRoutedEventArgs e) + { + lastPointerPos = e.GetCurrentPoint(null).Position; } } } diff --git a/QuickDrawWindows/Models/MFImageFolder.cs b/QuickDrawWindows/Models/MFImageFolder.cs index 682d50f..5e7817c 100644 --- a/QuickDrawWindows/Models/MFImageFolder.cs +++ b/QuickDrawWindows/Models/MFImageFolder.cs @@ -15,6 +15,8 @@ public class MFImageFolder [JsonIgnore] public bool IsLoading { get; set; } = false; + public bool Selected { get; set; } = false; + public MFImageFolder(string path, int imageCount, bool isLoading = false) { Path = path; diff --git a/QuickDrawWindows/QuickDraw.csproj b/QuickDrawWindows/QuickDraw.csproj index 0d01a56..31b8b4a 100644 --- a/QuickDrawWindows/QuickDraw.csproj +++ b/QuickDrawWindows/QuickDraw.csproj @@ -8,7 +8,7 @@ win-x86;win-x64;win-arm64 win10-$(Platform).pubxml true - false + true None QuickDraw en-CA From cf904fe2fbb5a25452e588427e65103c4e1844b0 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Fri, 21 Mar 2025 23:38:06 -0400 Subject: [PATCH 24/60] Removed the NotifyCollectonChangedActon.Move handling Removed some whitespace --- QuickDrawWindows/MFImageFolderListView.xaml.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/QuickDrawWindows/MFImageFolderListView.xaml.cs b/QuickDrawWindows/MFImageFolderListView.xaml.cs index a87ec73..3a18bdc 100644 --- a/QuickDrawWindows/MFImageFolderListView.xaml.cs +++ b/QuickDrawWindows/MFImageFolderListView.xaml.cs @@ -116,11 +116,6 @@ public MFImageFolderListView() } switch (e.Action) { - case NotifyCollectionChangedAction.Move: - settings.ImageFolderList.ImageFolders.RemoveRange(0, e.OldItems.Count); - settings.ImageFolderList.ImageFolders.InsertRange(e.NewStartingIndex, e.NewItems.OfType()); - - break; case NotifyCollectionChangedAction.Remove: settings.ImageFolderList.ImageFolders.RemoveRange(e.OldStartingIndex, e.OldItems.Count); @@ -315,8 +310,6 @@ private void ImageFolderListView_DragItemsStarting(object sender, DragItemsStart { var items = e.Items.Cast(); - - var hovereditems = VisualTreeHelper.FindElementsInHostCoordinates(lastPointerPos, ImageFolderListView); var hoveritemelem = hovereditems.First(i => i is ListViewItem); var hoveritem = ImageFolderListView.ItemFromContainer(hoveritemelem as ListViewItem); @@ -331,13 +324,14 @@ private void ImageFolderListView_DragItemsStarting(object sender, DragItemsStart var itemelem = ImageFolderListView.ContainerFromItem(item) as ListViewItem; VisualStateManager.GoToState(itemelem, "DragHidden", true); + + } } } private void ImageFolderListView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args) { - var items = args.Items.Cast(); foreach (var item in items) From 0cc4eac40e145d388180e33d7e8b7d612d25a1aa Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sat, 22 Mar 2025 00:05:16 -0400 Subject: [PATCH 25/60] Select/Deselect All functionality --- QuickDrawWindows/MFImageFolderListView.xaml | 2 +- QuickDrawWindows/MFImageFolderListView.xaml.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/QuickDrawWindows/MFImageFolderListView.xaml b/QuickDrawWindows/MFImageFolderListView.xaml index 48b8f7a..d2ddbc3 100644 --- a/QuickDrawWindows/MFImageFolderListView.xaml +++ b/QuickDrawWindows/MFImageFolderListView.xaml @@ -29,7 +29,7 @@ - + diff --git a/QuickDrawWindows/MFImageFolderListView.xaml.cs b/QuickDrawWindows/MFImageFolderListView.xaml.cs index 3a18bdc..f675eb7 100644 --- a/QuickDrawWindows/MFImageFolderListView.xaml.cs +++ b/QuickDrawWindows/MFImageFolderListView.xaml.cs @@ -301,6 +301,14 @@ private void ImageFolderListView_SelectionChanged(object sender, SelectionChange } } + if (ImageFolderListView.SelectedItems.Count == ImageFolderCollection.Count) + { + SelectAllCheckbox.IsChecked = true; + } else + { + SelectAllCheckbox.IsChecked = false; + } + settings.WriteSettings(); } @@ -349,5 +357,12 @@ private void ImageFolderListView_PointerMoved(object sender, PointerRoutedEventA { lastPointerPos = e.GetCurrentPoint(null).Position; } + + private void SelectAllCheckbox_Click(object sender, RoutedEventArgs e) + { + if (SelectAllCheckbox.IsChecked ?? false) { ImageFolderListView.SelectAll(); } + else { ImageFolderListView.DeselectAll(); } + + } } } From 10597db87bbbcae21ba17d1dd0364f494d0bca2f Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:47:03 -0400 Subject: [PATCH 26/60] Removed placeholder images in slide view, now actually loads them from folders. --- .../MFImageFolderListView.xaml.cs | 27 ++++++++++++++++ QuickDrawWindows/MainPage.xaml.cs | 30 ++++++++++++++--- QuickDrawWindows/Models/MFImageFolderList.cs | 23 ++----------- QuickDrawWindows/Models/MFSettings.cs | 3 ++ QuickDrawWindows/SlidePage.xaml.cs | 31 +++--------------- QuickDrawWindows/Utilities/Filesystem.cs | 32 +++++++++++++++++++ 6 files changed, 94 insertions(+), 52 deletions(-) create mode 100644 QuickDrawWindows/Utilities/Filesystem.cs diff --git a/QuickDrawWindows/MFImageFolderListView.xaml.cs b/QuickDrawWindows/MFImageFolderListView.xaml.cs index f675eb7..ddbaac9 100644 --- a/QuickDrawWindows/MFImageFolderListView.xaml.cs +++ b/QuickDrawWindows/MFImageFolderListView.xaml.cs @@ -22,6 +22,7 @@ using QuickDraw.Utilities; using System.Diagnostics; using WinRT; +using System.Collections.Concurrent; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -364,5 +365,31 @@ private void SelectAllCheckbox_Click(object sender, RoutedEventArgs e) else { ImageFolderListView.DeselectAll(); } } + + private async Task> GetSelectedFolders() + { + return await DispatcherQueue.EnqueueAsync(() => + { + return ImageFolderListView.SelectedItems.Cast().Select(f => f.Path).ToList(); + }); + } + + public async Task> GetImages() + { + IEnumerable folders = await GetSelectedFolders(); + ConcurrentBag images = []; + + await Parallel.ForEachAsync(folders, async (folder, ct) => + { + IEnumerable files = await Filesystem.GetFolderImages(folder); + + foreach (var file in files) + { + images.Add(file); + } + }); + + return images; + } } } diff --git a/QuickDrawWindows/MainPage.xaml.cs b/QuickDrawWindows/MainPage.xaml.cs index 1a78900..dc5d2ac 100644 --- a/QuickDrawWindows/MainPage.xaml.cs +++ b/QuickDrawWindows/MainPage.xaml.cs @@ -149,10 +149,32 @@ public MainPage() private void StartButton_Click(object sender, RoutedEventArgs e) { - (Application.Current as App)?.Window.NavigateToSlideshow(); + Task.Run(async () => { + return await ImageFolderListView.GetImages(); + }).ContinueWith(t => + { + if (t.IsFaulted) + { + // Log + } else + { + var images = t.Result as IEnumerable; + if (images.Count() > 0) + { + DispatcherQueue.EnqueueAsync(() => + { + + var app = (Application.Current as App); + app.Settings.SlidePaths = [.. images]; + app?.Window.NavigateToSlideshow(); + }); + } else + { + // TODO: Show user error + } + + } + }); } - - - } } diff --git a/QuickDrawWindows/Models/MFImageFolderList.cs b/QuickDrawWindows/Models/MFImageFolderList.cs index 4270c9a..c5821e4 100644 --- a/QuickDrawWindows/Models/MFImageFolderList.cs +++ b/QuickDrawWindows/Models/MFImageFolderList.cs @@ -15,26 +15,6 @@ public class MFImageFolderList : INotifyCollectionChanged public event NotifyCollectionChangedEventHandler CollectionChanged; - private static async Task> GetFolderImages(string filepath) - { - return await Task.Run(() => - { - var enumerationOptions = new EnumerationOptions - { - IgnoreInaccessible = true, - RecurseSubdirectories = true, - AttributesToSkip = System.IO.FileAttributes.Hidden | System.IO.FileAttributes.System | System.IO.FileAttributes.ReparsePoint - }; - - IEnumerable files = Directory.EnumerateFiles(filepath, "*.*", enumerationOptions) - .Where(s => s.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) - || s.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) - || s.EndsWith(".png", StringComparison.OrdinalIgnoreCase)); - - return files; - }); - } - public void AddFolderPath(string path) { var folder = new MFImageFolder(path, 0, true); @@ -55,7 +35,7 @@ public void AddFolderPath(string path) Task.Run(async () => { - return await GetFolderImages(path); + return await Filesystem.GetFolderImages(path); }).ContinueWith((t) => { if (t.IsFaulted) @@ -80,6 +60,7 @@ public void AddFolderPath(string path) public void AddFolderPaths(IEnumerable paths) { + // TODO: Add paths in order, then load image counts in parallel foreach (var path in paths) { AddFolderPath(path); diff --git a/QuickDrawWindows/Models/MFSettings.cs b/QuickDrawWindows/Models/MFSettings.cs index 8265909..d910f29 100644 --- a/QuickDrawWindows/Models/MFSettings.cs +++ b/QuickDrawWindows/Models/MFSettings.cs @@ -24,6 +24,9 @@ public class MFSettings : INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; + [JsonIgnore] + public List SlidePaths { get; set; } = []; + private async Task _writeSettings() { var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); diff --git a/QuickDrawWindows/SlidePage.xaml.cs b/QuickDrawWindows/SlidePage.xaml.cs index 5e7ca0b..0d1975a 100644 --- a/QuickDrawWindows/SlidePage.xaml.cs +++ b/QuickDrawWindows/SlidePage.xaml.cs @@ -48,32 +48,7 @@ public sealed partial class SlidePage : Page private const int CACHE_SIZE = 9; private const int HALF_CACHE_SIZE = CACHE_SIZE / 2; - private readonly List imagePaths = new() - { - @"T:\Reference\Image Reference\Poses\Women\00ab97eb3768fe67600e16e4e2f4be28.jpg", - @"T:\Reference\Image Reference\Poses\Women\00d8a150d3327954bb6d217c94d93e10.jpg", - @"T:\Reference\Image Reference\Poses\Women\0cc5e36c58c975b8f580af6ba9104fc1.jpg", - - @"T:\Reference\Image Reference\Poses\Women\0d44ac6bbc2f76ad0dbd5a6a4e49656f.jpg", - @"T:\Reference\Image Reference\Poses\Women\01c3307c28a95405b448f8ba6b55c8c8.jpg", - @"T:\Reference\Image Reference\Poses\Women\01d050c898dbdbfc16f8c58984f6cfb0.jpg", - - @"T:\Reference\Image Reference\Poses\Women\01d6068f148122e65c20d71700a172cd.jpg", - @"T:\Reference\Image Reference\Poses\Women\0fe196d54935159419d425c96e084839.jpg", - @"T:\Reference\Image Reference\Poses\Women\0d43509436a0dea9e92274def74be668.jpg", - - @"T:\Reference\Image Reference\Poses\Women\1b7cebc4eec7b5a8bd7a8eb8cbfc0561.jpg", - @"T:\Reference\Image Reference\Poses\Women\1c91b180a9bba47b9af3e9e65f08b6e0.jpg", - @"T:\Reference\Image Reference\Poses\Women\1bc1899d6610ed5322316abcf1ca14ea.jpg", - - @"T:\Reference\Image Reference\Poses\Women\0e6817b0c076405a00fb987f4f405eea.jpg", - @"T:\Reference\Image Reference\Poses\Women\0e92728c5c2758332a5b141d174b5696.jpg", - @"T:\Reference\Image Reference\Poses\Women\0ef896c25863a1a4b1fdf60b901c1fd6.jpg", - - @"T:\Reference\Image Reference\Poses\Women\2c5d9ca90e2bc17a33a15508e5e4cce6.jpg", - @"T:\Reference\Image Reference\Poses\Women\2d897822c8ba784742b96fdf39880494.jpg", - @"T:\Reference\Image Reference\Poses\Women\2df95b09f17abe50abffc7a1d43e2324.jpg", - }; + private List imagePaths = null; private readonly LinkedList cachedImages = new(); @@ -96,11 +71,13 @@ public SlidePage() { this.InitializeComponent(); + imagePaths = (App.Current as App).Settings.SlidePaths; + this.Unloaded += SlidePage_Unloaded; m_SlideTimer = DispatcherQueue.CreateTimer(); m_SlideTimer.IsRepeating = true; - m_SlideTimer.Interval = new(TimeSpan.TicksPerMillisecond * (long)1000); + m_SlideTimer.Interval = new(TimeSpan.TicksPerMillisecond * (long)1); m_SlideTimer.Tick += async (sender, e) => { AppTitleBar.Progress = (double)m_TicksElapsed / (double)30; diff --git a/QuickDrawWindows/Utilities/Filesystem.cs b/QuickDrawWindows/Utilities/Filesystem.cs new file mode 100644 index 0000000..99fe287 --- /dev/null +++ b/QuickDrawWindows/Utilities/Filesystem.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QuickDraw.Utilities +{ + public class Filesystem + { + public static async Task> GetFolderImages(string filepath) + { + return await Task.Run(() => + { + var enumerationOptions = new EnumerationOptions + { + IgnoreInaccessible = true, + RecurseSubdirectories = true, + AttributesToSkip = System.IO.FileAttributes.Hidden | System.IO.FileAttributes.System | System.IO.FileAttributes.ReparsePoint + }; + + IEnumerable files = Directory.EnumerateFiles(filepath, "*.*", enumerationOptions) + .Where(s => s.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) + || s.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) + || s.EndsWith(".png", StringComparison.OrdinalIgnoreCase)); + + return files; + }); + } + } +} From d949728184300db7c05874661dcbe2430184b43e Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sat, 22 Mar 2025 02:03:13 -0400 Subject: [PATCH 27/60] Set SlideShow timer interval back to the proper 1s --- QuickDrawWindows/SlidePage.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QuickDrawWindows/SlidePage.xaml.cs b/QuickDrawWindows/SlidePage.xaml.cs index 0d1975a..0024add 100644 --- a/QuickDrawWindows/SlidePage.xaml.cs +++ b/QuickDrawWindows/SlidePage.xaml.cs @@ -77,7 +77,7 @@ public SlidePage() m_SlideTimer = DispatcherQueue.CreateTimer(); m_SlideTimer.IsRepeating = true; - m_SlideTimer.Interval = new(TimeSpan.TicksPerMillisecond * (long)1); + m_SlideTimer.Interval = new(TimeSpan.TicksPerMillisecond * (long)1000); m_SlideTimer.Tick += async (sender, e) => { AppTitleBar.Progress = (double)m_TicksElapsed / (double)30; From ebb7f6855729b56961a2c95bae445503644e9b5d Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sat, 22 Mar 2025 02:11:29 -0400 Subject: [PATCH 28/60] Moved all image/folder getting/counting to MFImageFolderList model --- .../MFImageFolderListView.xaml.cs | 20 +--------- QuickDrawWindows/MainPage.xaml.cs | 2 +- QuickDrawWindows/Models/MFImageFolderList.cs | 40 ++++++++++++++++++- QuickDrawWindows/Utilities/Filesystem.cs | 32 --------------- 4 files changed, 41 insertions(+), 53 deletions(-) delete mode 100644 QuickDrawWindows/Utilities/Filesystem.cs diff --git a/QuickDrawWindows/MFImageFolderListView.xaml.cs b/QuickDrawWindows/MFImageFolderListView.xaml.cs index ddbaac9..fd2d9f4 100644 --- a/QuickDrawWindows/MFImageFolderListView.xaml.cs +++ b/QuickDrawWindows/MFImageFolderListView.xaml.cs @@ -366,30 +366,12 @@ private void SelectAllCheckbox_Click(object sender, RoutedEventArgs e) } - private async Task> GetSelectedFolders() + public async Task> GetSelectedFolders() { return await DispatcherQueue.EnqueueAsync(() => { return ImageFolderListView.SelectedItems.Cast().Select(f => f.Path).ToList(); }); } - - public async Task> GetImages() - { - IEnumerable folders = await GetSelectedFolders(); - ConcurrentBag images = []; - - await Parallel.ForEachAsync(folders, async (folder, ct) => - { - IEnumerable files = await Filesystem.GetFolderImages(folder); - - foreach (var file in files) - { - images.Add(file); - } - }); - - return images; - } } } diff --git a/QuickDrawWindows/MainPage.xaml.cs b/QuickDrawWindows/MainPage.xaml.cs index dc5d2ac..d8b6a5e 100644 --- a/QuickDrawWindows/MainPage.xaml.cs +++ b/QuickDrawWindows/MainPage.xaml.cs @@ -150,7 +150,7 @@ public MainPage() private void StartButton_Click(object sender, RoutedEventArgs e) { Task.Run(async () => { - return await ImageFolderListView.GetImages(); + return await MFImageFolderList.GetImages(await ImageFolderListView.GetSelectedFolders()); }).ContinueWith(t => { if (t.IsFaulted) diff --git a/QuickDrawWindows/Models/MFImageFolderList.cs b/QuickDrawWindows/Models/MFImageFolderList.cs index c5821e4..ba41277 100644 --- a/QuickDrawWindows/Models/MFImageFolderList.cs +++ b/QuickDrawWindows/Models/MFImageFolderList.cs @@ -1,5 +1,6 @@ using QuickDraw.Utilities; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; using System.IO; @@ -15,6 +16,43 @@ public class MFImageFolderList : INotifyCollectionChanged public event NotifyCollectionChangedEventHandler CollectionChanged; + private static async Task> GetFolderImages(string filepath) + { + return await Task.Run(() => + { + var enumerationOptions = new EnumerationOptions + { + IgnoreInaccessible = true, + RecurseSubdirectories = true, + AttributesToSkip = System.IO.FileAttributes.Hidden | System.IO.FileAttributes.System | System.IO.FileAttributes.ReparsePoint + }; + + IEnumerable files = Directory.EnumerateFiles(filepath, "*.*", enumerationOptions) + .Where(s => s.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) + || s.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) + || s.EndsWith(".png", StringComparison.OrdinalIgnoreCase)); + + return files; + }); + } + + public static async Task> GetImages(IEnumerable folders) + { + ConcurrentBag images = []; + + await Parallel.ForEachAsync(folders, async (folder, ct) => + { + IEnumerable files = await GetFolderImages(folder); + + foreach (var file in files) + { + images.Add(file); + } + }); + + return images; + } + public void AddFolderPath(string path) { var folder = new MFImageFolder(path, 0, true); @@ -35,7 +73,7 @@ public void AddFolderPath(string path) Task.Run(async () => { - return await Filesystem.GetFolderImages(path); + return await GetFolderImages(path); }).ContinueWith((t) => { if (t.IsFaulted) diff --git a/QuickDrawWindows/Utilities/Filesystem.cs b/QuickDrawWindows/Utilities/Filesystem.cs deleted file mode 100644 index 99fe287..0000000 --- a/QuickDrawWindows/Utilities/Filesystem.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace QuickDraw.Utilities -{ - public class Filesystem - { - public static async Task> GetFolderImages(string filepath) - { - return await Task.Run(() => - { - var enumerationOptions = new EnumerationOptions - { - IgnoreInaccessible = true, - RecurseSubdirectories = true, - AttributesToSkip = System.IO.FileAttributes.Hidden | System.IO.FileAttributes.System | System.IO.FileAttributes.ReparsePoint - }; - - IEnumerable files = Directory.EnumerateFiles(filepath, "*.*", enumerationOptions) - .Where(s => s.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) - || s.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) - || s.EndsWith(".png", StringComparison.OrdinalIgnoreCase)); - - return files; - }); - } - } -} From 766dc4a2de1b73cff46e6911dbaaaa6ed6be1124 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sun, 23 Mar 2025 00:20:09 -0400 Subject: [PATCH 29/60] Images are now shuffled properly Made publish profiles no longer self contained. May reevaluate when publishing. --- QuickDrawWindows/MainPage.xaml.cs | 22 ++++++++++++++----- .../PublishProfiles/win10-arm64.pubxml | 2 +- .../PublishProfiles/win10-x64.pubxml | 2 +- .../PublishProfiles/win10-x86.pubxml | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/QuickDrawWindows/MainPage.xaml.cs b/QuickDrawWindows/MainPage.xaml.cs index d8b6a5e..f15a486 100644 --- a/QuickDrawWindows/MainPage.xaml.cs +++ b/QuickDrawWindows/MainPage.xaml.cs @@ -20,6 +20,7 @@ using System.ComponentModel; using Windows.ApplicationModel.Store; using QuickDraw.Models; +using System.Security.Cryptography; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -120,12 +121,23 @@ public object ConvertBack(object value, } } + static class ListShuffleExtension + { + public static void Shuffle(this IList list) + { + for (var i = 0; i < list.Count - 1; i++) + { + var j = RandomNumberGenerator.GetInt32(i + 1, list.Count); + (list[i], list[j]) = (list[j], list[i]); + } + } + } + /// /// An empty page that can be used on its own or navigated to within a Frame. /// public sealed partial class MainPage : Page { - enum TimerEnum { [Display(Name = "30s")] @@ -158,14 +170,14 @@ private void StartButton_Click(object sender, RoutedEventArgs e) // Log } else { - var images = t.Result as IEnumerable; + var images = t.Result.ToList(); if (images.Count() > 0) { + images.Shuffle(); + var app = (Application.Current as App); + app.Settings.SlidePaths = [.. images]; DispatcherQueue.EnqueueAsync(() => { - - var app = (Application.Current as App); - app.Settings.SlidePaths = [.. images]; app?.Window.NavigateToSlideshow(); }); } else diff --git a/QuickDrawWindows/Properties/PublishProfiles/win10-arm64.pubxml b/QuickDrawWindows/Properties/PublishProfiles/win10-arm64.pubxml index a7fdd16..96b410e 100644 --- a/QuickDrawWindows/Properties/PublishProfiles/win10-arm64.pubxml +++ b/QuickDrawWindows/Properties/PublishProfiles/win10-arm64.pubxml @@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. ARM64 win10-arm64 bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ - true + False False False True diff --git a/QuickDrawWindows/Properties/PublishProfiles/win10-x64.pubxml b/QuickDrawWindows/Properties/PublishProfiles/win10-x64.pubxml index ca40a9e..ee4d320 100644 --- a/QuickDrawWindows/Properties/PublishProfiles/win10-x64.pubxml +++ b/QuickDrawWindows/Properties/PublishProfiles/win10-x64.pubxml @@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. x64 win-x64 bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ - true + False False False True diff --git a/QuickDrawWindows/Properties/PublishProfiles/win10-x86.pubxml b/QuickDrawWindows/Properties/PublishProfiles/win10-x86.pubxml index 34d14d4..5c4d6d8 100644 --- a/QuickDrawWindows/Properties/PublishProfiles/win10-x86.pubxml +++ b/QuickDrawWindows/Properties/PublishProfiles/win10-x86.pubxml @@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. x86 win10-x86 bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ - true + False False False True From aa5922f3b8b5e5181847b96e53c99af2cd54257f Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sun, 23 Mar 2025 00:46:48 -0400 Subject: [PATCH 30/60] Made Next/Previous buttons properly reset the timer Fixed timing issues with the progress bar. --- QuickDrawWindows/SlidePage.xaml.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/QuickDrawWindows/SlidePage.xaml.cs b/QuickDrawWindows/SlidePage.xaml.cs index 0024add..38921d5 100644 --- a/QuickDrawWindows/SlidePage.xaml.cs +++ b/QuickDrawWindows/SlidePage.xaml.cs @@ -80,13 +80,16 @@ public SlidePage() m_SlideTimer.Interval = new(TimeSpan.TicksPerMillisecond * (long)1000); m_SlideTimer.Tick += async (sender, e) => { - AppTitleBar.Progress = (double)m_TicksElapsed / (double)30; m_TicksElapsed += 1; - if (m_TicksElapsed > 30) + AppTitleBar.Progress = (double)m_TicksElapsed / (double)5; + if (m_TicksElapsed >= 5) { m_TicksElapsed = 0; await Move(LoadDirection.Forwards); + + await Task.Delay(100); + AppTitleBar.Progress = 0; } }; @@ -372,11 +375,17 @@ private async Task Move(LoadDirection direction) private async void AppTitleBar_NextButtonClick(object sender, RoutedEventArgs e) { + AppTitleBar.Progress = 0; + m_TicksElapsed = 0; + m_SlideTimer.Start(); await Move(LoadDirection.Forwards); } private async void AppTitleBar_PreviousButtonClick(object sender, RoutedEventArgs e) { + AppTitleBar.Progress = 0; + m_TicksElapsed = 0; + m_SlideTimer.Start(); await Move(LoadDirection.Backwards); } From 0b50e08ce73a6aa5a49ed552d93b52b9392f38a7 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sun, 23 Mar 2025 02:13:13 -0400 Subject: [PATCH 31/60] Properly Set/Load/Use the duration slider value --- QuickDrawWindows/MFImageFolderListView.xaml | 2 +- .../MFImageFolderListView.xaml.cs | 2 + QuickDrawWindows/MainPage.xaml | 3 +- QuickDrawWindows/MainPage.xaml.cs | 33 +++++---- QuickDrawWindows/Models/MFSettings.cs | 68 ++++++++++++++++++- QuickDrawWindows/SlidePage.xaml.cs | 55 ++++++++------- 6 files changed, 122 insertions(+), 41 deletions(-) diff --git a/QuickDrawWindows/MFImageFolderListView.xaml b/QuickDrawWindows/MFImageFolderListView.xaml index d2ddbc3..0022939 100644 --- a/QuickDrawWindows/MFImageFolderListView.xaml +++ b/QuickDrawWindows/MFImageFolderListView.xaml @@ -36,7 +36,7 @@ - + diff --git a/QuickDrawWindows/MFImageFolderListView.xaml.cs b/QuickDrawWindows/MFImageFolderListView.xaml.cs index fd2d9f4..c5c222a 100644 --- a/QuickDrawWindows/MFImageFolderListView.xaml.cs +++ b/QuickDrawWindows/MFImageFolderListView.xaml.cs @@ -108,6 +108,8 @@ public MFImageFolderListView() ImageFolderListView.SelectRange(new(i, 1)); } + ImageFolderListView.SelectionChanged += ImageFolderListView_SelectionChanged; + }; ImageFolderCollection.CollectionChanged += (sender, e) => { diff --git a/QuickDrawWindows/MainPage.xaml b/QuickDrawWindows/MainPage.xaml index 9353963..3d675ef 100644 --- a/QuickDrawWindows/MainPage.xaml +++ b/QuickDrawWindows/MainPage.xaml @@ -47,7 +47,8 @@ - (this IList list) /// public sealed partial class MainPage : Page { - enum TimerEnum - { - [Display(Name = "30s")] - T30s, - [Display(Name = "1m")] - T1m, - [Display(Name = "2m")] - T2m, - [Display(Name = "5m")] - T5m, - [Display(Name = "No Limit")] - NoLimit - }; - public MainPage() { this.InitializeComponent(); this.Resources.Add("doubleToEnumConverter", new DoubleToEnumConverter(typeof(TimerEnum))); this.Resources.Add("stringToEnumConverter", new StringToEnumConverter(typeof(TimerEnum))); + + TimerSlider.Loaded += TimerSlider_Loaded; + } + + private void TimerSlider_Loaded(object sender, RoutedEventArgs e) + { + var settings = (App.Current as App).Settings; + (sender as SfSlider).Value = settings.SlideTimerDuration.ToSliderValue(); + + TimerSlider.ValueChanged += TimerSlider_ValueChanged; } private void StartButton_Click(object sender, RoutedEventArgs e) @@ -188,5 +185,13 @@ private void StartButton_Click(object sender, RoutedEventArgs e) } }); } + + private void TimerSlider_ValueChanged(object sender, SliderValueChangedEventArgs e) + { + var settings = (App.Current as App)?.Settings; + + settings.SlideTimerDuration = e.NewValue.ToTimerEnum(); + settings.WriteSettings(); + } } } diff --git a/QuickDrawWindows/Models/MFSettings.cs b/QuickDrawWindows/Models/MFSettings.cs index d910f29..e8b1a2a 100644 --- a/QuickDrawWindows/Models/MFSettings.cs +++ b/QuickDrawWindows/Models/MFSettings.cs @@ -2,9 +2,14 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -13,8 +18,64 @@ namespace QuickDraw.Models { + public static class ObjectExtension + { + public static void CopyPropertiesTo(this object fromObject, object toObject) + { + PropertyInfo[] toObjectProperties = toObject.GetType().GetProperties(); + foreach (PropertyInfo propTo in toObjectProperties) + { + PropertyInfo propFrom = fromObject.GetType().GetProperty(propTo.Name); + if (propFrom != null && propFrom.CanWrite) + propTo.SetValue(toObject, propFrom.GetValue(fromObject, null), null); + } + } + } + + public enum TimerEnum + { + [Display(Name = "30s")] + T30s, + [Display(Name = "1m")] + T1m, + [Display(Name = "2m")] + T2m, + [Display(Name = "5m")] + T5m, + [Display(Name = "No Limit")] + NoLimit + }; + + static class TimerEnumExtension + { + private static Dictionary TimerEnumToSeconds { get; } = new() + { + { TimerEnum.T30s, 30 }, + { TimerEnum.T1m, 60 }, + { TimerEnum.T2m, 120 }, + { TimerEnum.T5m, 300 }, + { TimerEnum.NoLimit, 0 } + }; + + public static uint ToSeconds(this TimerEnum e) + { + return TimerEnumToSeconds[e]; + } + + public static double ToSliderValue(this TimerEnum e) + { + return (double)((int)e); + } + + public static TimerEnum ToTimerEnum(this double e) + { + return (TimerEnum)(Math.Clamp((int)e,0, 4)); + } + } + public class MFSettings : INotifyPropertyChanged { + public MFImageFolderList ImageFolderList { get; set; } = new MFImageFolderList(); [JsonIgnore] @@ -27,6 +88,11 @@ public class MFSettings : INotifyPropertyChanged [JsonIgnore] public List SlidePaths { get; set; } = []; + public TimerEnum SlideTimerDuration { + get; + set; + } + private async Task _writeSettings() { var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); @@ -108,7 +174,7 @@ public void ReadSettings() // Other errors } - UpdateSettings(newSettings); + newSettings.CopyPropertiesTo(this); }); } } diff --git a/QuickDrawWindows/SlidePage.xaml.cs b/QuickDrawWindows/SlidePage.xaml.cs index 38921d5..f18ed4b 100644 --- a/QuickDrawWindows/SlidePage.xaml.cs +++ b/QuickDrawWindows/SlidePage.xaml.cs @@ -12,6 +12,7 @@ using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Navigation; +using QuickDraw.Models; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -61,9 +62,9 @@ public sealed partial class SlidePage : Page private readonly object cachedImagesLock = new(); private readonly object currentImageNodeLock = new(); - private readonly DispatcherQueueTimer m_SlideTimer; + private readonly DispatcherQueueTimer m_SlideTimer = null; - private int m_TicksElapsed = 0; + private uint m_TicksElapsed = 0; private TaskCompletionSource imageCacheFilled = new(); @@ -71,34 +72,41 @@ public SlidePage() { this.InitializeComponent(); - imagePaths = (App.Current as App).Settings.SlidePaths; + var settings = (App.Current as App).Settings; + imagePaths = settings.SlidePaths; + + var timerDurationEnum = settings.SlideTimerDuration; this.Unloaded += SlidePage_Unloaded; - m_SlideTimer = DispatcherQueue.CreateTimer(); - m_SlideTimer.IsRepeating = true; - m_SlideTimer.Interval = new(TimeSpan.TicksPerMillisecond * (long)1000); - m_SlideTimer.Tick += async (sender, e) => + if (timerDurationEnum != TimerEnum.NoLimit) { - m_TicksElapsed += 1; - AppTitleBar.Progress = (double)m_TicksElapsed / (double)5; - if (m_TicksElapsed >= 5) + var timerDuration = settings.SlideTimerDuration.ToSeconds(); + m_SlideTimer = DispatcherQueue.CreateTimer(); + m_SlideTimer.IsRepeating = true; + m_SlideTimer.Interval = new(TimeSpan.TicksPerMillisecond * (long)1000); + m_SlideTimer.Tick += async (sender, e) => { - m_TicksElapsed = 0; + m_TicksElapsed += 1; + AppTitleBar.Progress = (double)m_TicksElapsed / (double)timerDuration; + if (m_TicksElapsed >= timerDuration) + { + m_TicksElapsed = 0; - await Move(LoadDirection.Forwards); + await Move(LoadDirection.Forwards); - await Task.Delay(100); - AppTitleBar.Progress = 0; - - } - }; - m_SlideTimer.Start(); + await Task.Delay(100); + AppTitleBar.Progress = 0; + + } + }; + m_SlideTimer.Start(); + } } void SlidePage_Unloaded(object sender, RoutedEventArgs e) { - this.m_SlideTimer.Stop(); + this.m_SlideTimer?.Stop(); this.SlideCanvas.RemoveFromVisualTree(); this.SlideCanvas = null; } @@ -377,7 +385,7 @@ private async void AppTitleBar_NextButtonClick(object sender, RoutedEventArgs e) { AppTitleBar.Progress = 0; m_TicksElapsed = 0; - m_SlideTimer.Start(); + m_SlideTimer?.Start(); await Move(LoadDirection.Forwards); } @@ -385,7 +393,7 @@ private async void AppTitleBar_PreviousButtonClick(object sender, RoutedEventArg { AppTitleBar.Progress = 0; m_TicksElapsed = 0; - m_SlideTimer.Start(); + m_SlideTimer?.Start(); await Move(LoadDirection.Backwards); } @@ -398,16 +406,15 @@ private void AppTitleBar_GrayscaleButtonClick(object sender, RoutedEventArgs e) private void AppTitleBar_PauseButtonClick(object sender, RoutedEventArgs e) { - if (m_SlideTimer.IsRunning) + if (m_SlideTimer?.IsRunning ?? false) { m_SlideTimer.Stop(); AppTitleBar.IsPaused = true; } else { - m_SlideTimer.Start(); + m_SlideTimer?.Start(); AppTitleBar.IsPaused = false; - } } } From 8d43216495fc331ca9edf388ccad387f2f6ae75f Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Wed, 26 Mar 2025 20:03:53 -0400 Subject: [PATCH 32/60] Delete folder button now works. Fixed some grid column sizing bugs --- QuickDrawWindows/MFImageFolderListView.xaml | 2 +- .../MFImageFolderListView.xaml.cs | 57 +++++++++++++------ QuickDrawWindows/MFImageFolderView.xaml | 6 +- QuickDrawWindows/MFImageFolderView.xaml.cs | 51 ++++++++++++++++- QuickDrawWindows/Models/MFImageFolderList.cs | 7 +++ 5 files changed, 100 insertions(+), 23 deletions(-) diff --git a/QuickDrawWindows/MFImageFolderListView.xaml b/QuickDrawWindows/MFImageFolderListView.xaml index 0022939..b403c9a 100644 --- a/QuickDrawWindows/MFImageFolderListView.xaml +++ b/QuickDrawWindows/MFImageFolderListView.xaml @@ -39,7 +39,7 @@ - + diff --git a/QuickDrawWindows/MFImageFolderListView.xaml.cs b/QuickDrawWindows/MFImageFolderListView.xaml.cs index c5c222a..2559a96 100644 --- a/QuickDrawWindows/MFImageFolderListView.xaml.cs +++ b/QuickDrawWindows/MFImageFolderListView.xaml.cs @@ -108,6 +108,15 @@ public MFImageFolderListView() ImageFolderListView.SelectRange(new(i, 1)); } + if (ImageFolderListView.SelectedItems.Count == ImageFolderCollection.Count) + { + SelectAllCheckbox.IsChecked = true; + } + else + { + SelectAllCheckbox.IsChecked = false; + } + ImageFolderListView.SelectionChanged += ImageFolderListView_SelectionChanged; }; @@ -164,37 +173,43 @@ public MFImageFolderListView() i++; } break; + case NotifyCollectionChangedAction.Remove: + foreach (var item in e.OldItems) + { + ImageFolderCollection.RemoveFromModel(item as MFImageFolder); + } + break; default: break; } }); - - settings.WriteSettings(); }; } - private void UpdateColumnWidths(Grid grid) + private void UpdateColumnWidths() { if (_pathTexts.Count < 1 || _imageCountTexts.Count < 1) { return; } + Grid grid = (_pathTexts.Max as FrameworkElement).Parent as Grid; - ProgressRing progressRing = _imageCountTexts.Max.Parent.FindDescendant(); - var ImageCountColumnWidth = Math.Max(_imageCountTexts.Max.ActualWidth, progressRing.ActualWidth) + 20; + ProgressRing progressRing = _imageCountTexts.Max.Parent?.FindDescendant(); + if (progressRing != null) + { + var ImageCountColumnWidth = Math.Max(_imageCountTexts.Max.ActualWidth, progressRing.ActualWidth) + 20; - var gridWidth = grid.ActualWidth; - var availableWidth = gridWidth - (grid.ColumnDefinitions[3].ActualWidth + ImageCountColumnWidth + 20); + var gridWidth = grid.ActualWidth; + var availableWidth = gridWidth - (grid.ColumnDefinitions[3].ActualWidth + ImageCountColumnWidth + 20); - var maxPathColumnWidth = _pathTexts.Max != null ? _pathTexts.Max.PreWrappedWidth() + 1 : 0; - var PathColumnWidth = Math.Max(100, Math.Min(availableWidth, maxPathColumnWidth)); + var maxPathColumnWidth = _pathTexts.Max != null ? _pathTexts.Max.PreWrappedWidth() + 1 : 0; + var PathColumnWidth = Math.Max(100, Math.Min(availableWidth, maxPathColumnWidth)); - DesiredPathColumnWidth = new GridLength(PathColumnWidth); - DesiredImageCountColumnWidth = new GridLength(ImageCountColumnWidth); + DesiredPathColumnWidth = new GridLength(PathColumnWidth); + DesiredImageCountColumnWidth = new GridLength(ImageCountColumnWidth); + } } private void MFImageFolderControl_ColumnWidthChanged(object sender, MFImageFolderView.ColumnWidthChangedEventArgs args) { - Grid grid = args.columnType == ColumnType.Grid ? sender as Grid : (sender as FrameworkElement).Parent as Grid; - switch (args.columnType) { case ColumnType.Path: @@ -211,7 +226,7 @@ private void MFImageFolderControl_ColumnWidthChanged(object sender, MFImageFolde break; } - UpdateColumnWidths(grid); + UpdateColumnWidths(); } private void OpenFolders() @@ -252,8 +267,9 @@ private void OpenFolders() } } - var Settings = (App.Current as App).Settings; - Settings.ImageFolderList.AddFolderPaths(paths); + var settings = (App.Current as App).Settings; + settings.ImageFolderList.AddFolderPaths(paths); + settings.WriteSettings(); } } catch (COMException) @@ -273,7 +289,6 @@ private void AddFoldersButton_Click(object sender, RoutedEventArgs e) { OpenFolders(); - ImageFolderListView.SelectRange(new(0, 1)); } private void ImageFolderListView_SelectionChanged(object sender, SelectionChangedEventArgs e) @@ -375,5 +390,13 @@ public async Task> GetSelectedFolders() return ImageFolderListView.SelectedItems.Cast().Select(f => f.Path).ToList(); }); } + + private void MFImageFolderView_ColumnDataRemove(object sender, MFImageFolderView.ColumnDataRemoveEventArgs args) + { + _pathTexts.Remove(args.PathText); + _imageCountTexts.Remove(args.ImageCountText); + + UpdateColumnWidths(); + } } } diff --git a/QuickDrawWindows/MFImageFolderView.xaml b/QuickDrawWindows/MFImageFolderView.xaml index fc492e2..a4e507d 100644 --- a/QuickDrawWindows/MFImageFolderView.xaml +++ b/QuickDrawWindows/MFImageFolderView.xaml @@ -64,17 +64,17 @@ - - - + + + - + - + - + - + @@ -32,63 +91,30 @@ - + - - + + - + - + - - + + - - - - - - - - - - - - - - - - - - - - - diff --git a/QuickDrawWindows/Views/MainPage.xaml b/QuickDrawWindows/Views/MainPage.xaml index 77abd1c..0331e08 100644 --- a/QuickDrawWindows/Views/MainPage.xaml +++ b/QuickDrawWindows/Views/MainPage.xaml @@ -32,7 +32,11 @@ - + - - - - - + TextWrapping="NoWrap" /> - + - + - + - + - - - + + + diff --git a/QuickDrawWindows/Views/MainTitlebarControl.xaml.cs b/QuickDrawWindows/Views/MainTitlebarControl.xaml.cs index 7e89e55..94b2050 100644 --- a/QuickDrawWindows/Views/MainTitlebarControl.xaml.cs +++ b/QuickDrawWindows/Views/MainTitlebarControl.xaml.cs @@ -1,29 +1,31 @@ +using DependencyPropertyGenerator; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; +using QuickDraw.Utilities; using QuickDraw.Views.Base; +using Windows.Graphics; namespace QuickDraw.Views; public sealed partial class MainTitlebarControl : Base.TitlebarBaseControl { - public bool Disabled + public MainTitlebarControl() { - get { return (bool)GetValue(DisabledProperty); } - set { SetValue(DisabledProperty, value); } + InitializeComponent(); } - public static readonly DependencyProperty DisabledProperty = DependencyProperty.Register( - nameof(Disabled), - typeof(bool), - typeof(SlideTitlebarControl), - new PropertyMetadata(false) - ); - public MainTitlebarControl() + protected override RectInt32[] CalculateDragRegions() { - this.Resources["WindowCaptionForegroundColor"] = (Application.Current.Resources["WindowCaptionForeground"] as SolidColorBrush)?.Color; - this.Resources["WindowCaptionForegroundDisabledColor"] = (Application.Current.Resources["WindowCaptionForegroundDisabled"] as SolidColorBrush)?.Color; + var scale = TitlebarService?.Scale ?? 1.0; - InitializeComponent(); + RectInt32 dragRect = new( + (int)(LeftInset.Value), + 0, + (int)(TitleColumn.ActualWidth * scale), + (int)(ActualHeight * scale) + ); + + return [dragRect]; } } diff --git a/QuickDrawWindows/Views/SlidePage2.xaml b/QuickDrawWindows/Views/SlidePage2.xaml index 3a9371a..1126947 100644 --- a/QuickDrawWindows/Views/SlidePage2.xaml +++ b/QuickDrawWindows/Views/SlidePage2.xaml @@ -23,9 +23,13 @@ x:Name="AppTitleBar" Grid.Row="0" BackButtonCommand="{x:Bind ViewModel.GoBackCommand}" + DragRegionsChanged="{x:Bind ViewModel.HandleDragRegionsChanged}" GrayscaleButtonCommand="{x:Bind ViewModel.ToggleGrayscaleCommand}" + Inactive="{x:Bind ViewModel.TitlebarInactive, Mode=OneWay}" LeftInset="{x:Bind ViewModel.TitlebarLeftInset}" - Progress="50" /> + PauseVisibility="{x:Bind ViewModel.PauseVisibility, Mode=OneWay}" + Progress="50" + TitlebarService="{x:Bind ViewModel.TitlebarService}" /> progress; - set { - if (IsLoaded && GetTemplateChild("ProgressBar") is ProgressBar progressBar) - { - progressBar.Value = value * 100; - } - progress = value; - } - } - - private bool m_paused = false; - public bool IsPaused - { - get => m_paused; - set - { - m_paused = value; - if (IsLoaded) - { - var button = GetTemplateChild("PauseButton") as Button; - var icon = button?.FindDescendant(); - - if (icon != null) - { - icon.Symbol = m_paused ? Symbol.Play : Symbol.Pause; - } - } - } - } - - public SlideTitleBar() - { - DefaultStyleKey = typeof(SlideTitleBar); - } - - void NextButton_Click(object sender, RoutedEventArgs e) - { - NextButtonClick?.Invoke(sender, e); - } - - void PreviousButton_Click(object sender, RoutedEventArgs e) - { - PreviousButtonClick?.Invoke(sender, e); - } - - void GrayscaleButton_Click(object sender, RoutedEventArgs e) - { - GrayscaleButtonClick?.Invoke(sender, e); - } - - void PauseButton_Click(object sender, RoutedEventArgs e) - { - PauseButtonClick?.Invoke(sender, e); - } - - protected override void OnApplyTemplate() - { - m_window = App.Window; - m_appWindow = m_window.AppWindow; - m_titleBar = m_appWindow.TitleBar; - - Unloaded += SlideTitleBar_Unloaded; - - SizeChanged += TitleBar_SizeChanged; - - if (GetTemplateChild("NextButton") is Button nextButton) - { - nextButton.Click += NextButton_Click; - } - - if (GetTemplateChild("PreviousButton") is Button previousButton) - { - previousButton.Click += PreviousButton_Click; - } - - if (GetTemplateChild("GrayscaleButton") is Button grayscaleButton) - { - grayscaleButton.Click += GrayscaleButton_Click; - } - - if (GetTemplateChild("PauseButton") is Button pauseButton ) - { - pauseButton.Click += PauseButton_Click; - -/* var settings = (App.Current as App)?.Settings; - - if (settings != null) - { - if (settings.SlideTimerDuration == Models.TimerEnum.NoLimit) - { - pauseButton.Visibility = Visibility.Collapsed; - } - }*/ - } - - - - if (GetTemplateChild("BackButton") is Button backButton) { - backButton.Click += SlideTitleBar_BackClick; - } - - _ = AdjustLayout(); - } - - async Task AdjustLayout() - { - var delay = TimeSpan.Parse((string)Application.Current.Resources["ControlFastAnimationDuration"]); - - await Task.Delay(delay); - if (m_titleBar != null) - { - m_titleBar.PreferredHeightOption = TitleBarHeightOption.Tall; - m_titleBar.IconShowOptions = IconShowOptions.HideIconAndSystemMenu; - } - ApplyInset(); - SetDragRegion(); - } - - void ApplyInset() - { - if (m_window == null) return; - - var scale = MonitorInfo.GetInvertedScaleAdjustment(m_window); - - m_leftInset = (m_titleBar?.LeftInset ?? 0) * scale; - m_rightInset = (m_titleBar?.RightInset ?? 0) * scale; - - - if (GetTemplateChild("LeftInsetColumn") is ColumnDefinition leftInsetColumn) - { - leftInsetColumn.Width = new GridLength(m_leftInset, GridUnitType.Pixel); - } - - if (GetTemplateChild("RightInsetColumn") is ColumnDefinition rightInsetColumn) - { - rightInsetColumn.Width = new GridLength(m_rightInset, GridUnitType.Pixel); - } - - // TODO: set min widths of the centering columns, store it for the drag region - } - - private void SetDragRegion() - { - if (m_window == null) return; - - double scale = MonitorInfo.GetScaleAdjustment(m_window); - - var backWidth = (GetTemplateChild("BackColumn") as ColumnDefinition)?.ActualWidth ?? 0; - var centerLeftWidth = (GetTemplateChild("CenterLeftColumn") as ColumnDefinition)?.ActualWidth ?? 0; - var centerRightWidth = (GetTemplateChild("CenterRightColumn") as ColumnDefinition)?.ActualWidth ?? 0; - - List dragRectsList = new(); - - Windows.Graphics.RectInt32 dragRectL = new( - (int)((m_leftInset + backWidth) * scale), - 0, - (int)((centerLeftWidth - backWidth - m_leftInset) * scale), - (int)(ActualHeight * scale) - ); - - dragRectsList.Add(dragRectL); - - - Windows.Graphics.RectInt32 dragRectR = new( - (int)((ActualWidth - centerRightWidth) * scale), - 0, - (int)((centerRightWidth - m_rightInset) * scale), - (int)(ActualHeight * scale) - ); - - dragRectsList.Add(dragRectR); - - Windows.Graphics.RectInt32[] dragRects = dragRectsList.ToArray(); - - m_titleBar?.SetDragRectangles(dragRects); - } - - private void TitleBar_SizeChanged(object sender, SizeChangedEventArgs e) - { - SetDragRegion(); - } - - void SlideTitleBar_Unloaded(object sender, RoutedEventArgs e) - { - m_appWindow = null; - m_window = null; - m_titleBar = null; - } - - void SlideTitleBar_BackClick(object sender, RoutedEventArgs e) - { - //m_window?.NavigateToMain(); - } -} diff --git a/QuickDrawWindows/Views/SlideTitlebarControl.xaml b/QuickDrawWindows/Views/SlideTitlebarControl.xaml index 3072246..ef9bcc2 100644 --- a/QuickDrawWindows/Views/SlideTitlebarControl.xaml +++ b/QuickDrawWindows/Views/SlideTitlebarControl.xaml @@ -7,6 +7,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:QuickDraw.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + SizeChanged="TitlebarBaseControl_SizeChanged" mc:Ignorable="d"> + Command="{x:Bind PauseButtonCommand}" + Visibility="{x:Bind PauseVisibility, Mode=OneWay}"> @@ -114,7 +116,6 @@ - @@ -137,6 +138,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/QuickDrawWindows/Views/SlideTitlebarControl.xaml.cs b/QuickDrawWindows/Views/SlideTitlebarControl.xaml.cs index 4db7b2d..69183f1 100644 --- a/QuickDrawWindows/Views/SlideTitlebarControl.xaml.cs +++ b/QuickDrawWindows/Views/SlideTitlebarControl.xaml.cs @@ -1,10 +1,16 @@ +using DependencyPropertyGenerator; +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using QuickDraw.Contracts.Services; +using QuickDraw.Utilities; +using System.Collections.Generic; using System.Windows.Input; -using DependencyPropertyGenerator; +using Windows.Graphics; namespace QuickDraw.Views; [DependencyProperty("Paused", DefaultValue = false)] +[DependencyProperty("PauseVisibility", DefaultValue = Visibility.Visible)] [DependencyProperty("Progress", DefaultValue = 25)] [DependencyProperty("NextButtonCommand")] [DependencyProperty("PreviousButtonCommand")] @@ -17,4 +23,28 @@ public SlideTitlebarControl() { InitializeComponent(); } + + protected override RectInt32[] CalculateDragRegions() + { + var scale = TitlebarService?.Scale ?? 1.0; + var backWidth = BackColumn.ActualWidth; + var centerLeftWidth = CenterLeftColumn.ActualWidth; + var centerRightWidth = CenterRightColumn.ActualWidth; + + RectInt32 dragRectL = new( + (int)(LeftInset.Value + backWidth * scale), + 0, + (int)((centerLeftWidth - backWidth) * scale - LeftInset.Value), + (int)(ActualHeight * scale) + ); + + RectInt32 dragRectR = new( + (int)((ActualWidth - centerRightWidth) * scale), + 0, + (int)(centerRightWidth * scale - RightInset.Value), + (int)(ActualHeight * scale) + ); + + return [dragRectL, dragRectR]; + } } From afb7f3e320cb0cc0754e1b467c4c203b638aed0a Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Fri, 19 Sep 2025 05:39:05 -0400 Subject: [PATCH 54/60] Fixed some titlebar bugs Drag regions were completely broke on high dpi settings Insets weren't being set on the pages properly Moved SizeChanged binding for toolbars from the xaml to the base titlebar class --- .../Contracts/Services/ITitlebarService.cs | 2 -- QuickDrawWindows/Services/TitlebarService.cs | 9 +++------ QuickDrawWindows/Views/Base/TitlebarBaseControl.cs | 6 ++++++ QuickDrawWindows/Views/MainPage.xaml | 4 +++- QuickDrawWindows/Views/MainTitlebarControl.xaml | 1 - QuickDrawWindows/Views/MainTitlebarControl.xaml.cs | 4 ++-- QuickDrawWindows/Views/SlidePage2.xaml | 1 + QuickDrawWindows/Views/SlideTitlebarControl.xaml | 1 - QuickDrawWindows/Views/SlideTitlebarControl.xaml.cs | 10 ++++++---- 9 files changed, 21 insertions(+), 17 deletions(-) diff --git a/QuickDrawWindows/Contracts/Services/ITitlebarService.cs b/QuickDrawWindows/Contracts/Services/ITitlebarService.cs index 0792d2d..d92b375 100644 --- a/QuickDrawWindows/Contracts/Services/ITitlebarService.cs +++ b/QuickDrawWindows/Contracts/Services/ITitlebarService.cs @@ -12,6 +12,4 @@ public interface ITitlebarService public void Initialize(Window window); - public double Scale { get; } - } diff --git a/QuickDrawWindows/Services/TitlebarService.cs b/QuickDrawWindows/Services/TitlebarService.cs index 9768eab..0a25c5d 100644 --- a/QuickDrawWindows/Services/TitlebarService.cs +++ b/QuickDrawWindows/Services/TitlebarService.cs @@ -10,7 +10,6 @@ public class TitlebarService : ITitlebarService { private MainWindow? _window; private AppWindowTitleBar? _titlebar; - private double _scale; private GridLength _leftInset; private GridLength _rightInset; @@ -34,16 +33,14 @@ public void Initialize(Window window) _titlebar.ButtonInactiveForegroundColor = Color.FromArgb(0xff, 0x66, 0x66, 0x66); //WindowCaptionForegroundDisabled converted to gray with no alpha, for some reason alpha is ignored here //titlebar.ButtonInactiveForegroundColor = ((SolidColorBrush)Application.Current.Resources["WindowCaptionForegroundDisabled"]).Color; - _scale = MonitorInfo.GetInvertedScaleAdjustment(_window); + var scaleInv = MonitorInfo.GetInvertedScaleAdjustment(_window); - _leftInset = new(_titlebar.LeftInset * _scale, GridUnitType.Pixel); - _rightInset = new(_titlebar.RightInset * _scale, GridUnitType.Pixel); + _leftInset = new(_titlebar.LeftInset * scaleInv, GridUnitType.Pixel); + _rightInset = new(_titlebar.LeftInset * scaleInv, GridUnitType.Pixel); } public AppWindowTitleBar? TitleBar => _titlebar; public GridLength LeftInset => _leftInset; public GridLength RightInset => _rightInset; - - public double Scale => _scale; } diff --git a/QuickDrawWindows/Views/Base/TitlebarBaseControl.cs b/QuickDrawWindows/Views/Base/TitlebarBaseControl.cs index 3bdf9c0..44d8aa8 100644 --- a/QuickDrawWindows/Views/Base/TitlebarBaseControl.cs +++ b/QuickDrawWindows/Views/Base/TitlebarBaseControl.cs @@ -26,6 +26,12 @@ public DragRegionsChangedEventArgs(RectInt32[] dragRegions) public abstract partial class TitlebarBaseControl : UserControl { public event EventHandler? DragRegionsChanged; + + public TitlebarBaseControl() + { + SizeChanged += TitlebarBaseControl_SizeChanged; + } + protected void TitlebarBaseControl_SizeChanged(object sender, Microsoft.UI.Xaml.SizeChangedEventArgs e) { var regions = CalculateDragRegions(); diff --git a/QuickDrawWindows/Views/MainPage.xaml b/QuickDrawWindows/Views/MainPage.xaml index 0331e08..be35a87 100644 --- a/QuickDrawWindows/Views/MainPage.xaml +++ b/QuickDrawWindows/Views/MainPage.xaml @@ -36,7 +36,9 @@ x:Name="AppTitleBar" Grid.Row="0" DragRegionsChanged="{x:Bind ViewModel.HandleDragRegionsChanged}" - Inactive="{x:Bind ViewModel.TitlebarInactive, Mode=OneWay}" /> + Inactive="{x:Bind ViewModel.TitlebarInactive, Mode=OneWay}" + LeftInset="{x:Bind ViewModel.TitlebarLeftInset}" + RightInset="{x:Bind ViewModel.TitlebarRightInset}" /> ("BackButtonCommand")] public sealed partial class SlideTitlebarControl : Base.TitlebarBaseControl { + public SlideTitlebarControl() { InitializeComponent(); @@ -26,22 +27,23 @@ public SlideTitlebarControl() protected override RectInt32[] CalculateDragRegions() { - var scale = TitlebarService?.Scale ?? 1.0; + var scale = this.XamlRoot.RasterizationScale; + var backWidth = BackColumn.ActualWidth; var centerLeftWidth = CenterLeftColumn.ActualWidth; var centerRightWidth = CenterRightColumn.ActualWidth; RectInt32 dragRectL = new( - (int)(LeftInset.Value + backWidth * scale), + (int)((LeftInset.Value + backWidth) * scale), 0, - (int)((centerLeftWidth - backWidth) * scale - LeftInset.Value), + (int)((centerLeftWidth - backWidth - LeftInset.Value) * scale), (int)(ActualHeight * scale) ); RectInt32 dragRectR = new( (int)((ActualWidth - centerRightWidth) * scale), 0, - (int)(centerRightWidth * scale - RightInset.Value), + (int)((centerRightWidth - RightInset.Value) * scale), (int)(ActualHeight * scale) ); From 6e02b8c1844df9ccb0bca9002d23e0c5d1c4e1b6 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Tue, 23 Sep 2025 22:11:34 -0400 Subject: [PATCH 55/60] Implemented most of the new SlidePage / SlideViewModel TODO: add back the functionality of opening explorer to file when clicking on the current image GetImages -> GetImagesForFolders, GetFolderImages -> GetImagesForFolder in ImageFolderList Removed SlidePaths from settings, moved to new service ISlideImageService Removed old SlidePage view --- QuickDraw.Core/Models/ImageFolderList.cs | 15 +- QuickDraw.Core/Models/Settings.cs | 3 - QuickDrawWindows/App.xaml | 6 +- QuickDrawWindows/App.xaml.cs | 3 +- .../Contracts/Services/ISlideImageService.cs | 15 + QuickDrawWindows/MainWindow.xaml | 13 +- QuickDrawWindows/Services/PageService.cs | 2 +- QuickDrawWindows/Services/SettingsService.cs | 8 + .../Services/SlideImageService.cs | 56 ++ QuickDrawWindows/ViewModels/MainViewModel.cs | 30 +- QuickDrawWindows/ViewModels/SlideViewModel.cs | 148 ++++- QuickDrawWindows/Views/MainPage.xaml | 2 +- QuickDrawWindows/Views/MainPage.xaml.cs | 43 +- QuickDrawWindows/Views/SlidePage.xaml | 25 +- QuickDrawWindows/Views/SlidePage.xaml.cs | 509 +++++------------- QuickDrawWindows/Views/SlidePage2.xaml | 44 -- QuickDrawWindows/Views/SlidePage2.xaml.cs | 120 ----- .../Views/SlideTitlebarControl.xaml | 9 +- .../Views/SlideTitlebarControl.xaml.cs | 2 +- 19 files changed, 425 insertions(+), 628 deletions(-) create mode 100644 QuickDrawWindows/Contracts/Services/ISlideImageService.cs create mode 100644 QuickDrawWindows/Services/SlideImageService.cs delete mode 100644 QuickDrawWindows/Views/SlidePage2.xaml delete mode 100644 QuickDrawWindows/Views/SlidePage2.xaml.cs diff --git a/QuickDraw.Core/Models/ImageFolderList.cs b/QuickDraw.Core/Models/ImageFolderList.cs index 94e05f8..e77cd75 100644 --- a/QuickDraw.Core/Models/ImageFolderList.cs +++ b/QuickDraw.Core/Models/ImageFolderList.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.Collections.Specialized; -using System.IO; -using System.Linq; -using System.Threading.Tasks; namespace QuickDraw.Core.Models; @@ -14,7 +9,7 @@ public class ImageFolderList : INotifyCollectionChanged public event NotifyCollectionChangedEventHandler? CollectionChanged; - private static async Task> GetFolderImages(string filepath) + private static async Task> GetImagesForFolder(string filepath) { return await Task.Run(() => { @@ -34,13 +29,13 @@ private static async Task> GetFolderImages(string filepath) }); } - public static async Task> GetImages(IEnumerable folders) + public static async Task> GetImagesForFolders(IEnumerable folders) { ConcurrentBag images = []; await Parallel.ForEachAsync(folders, async (folder, ct) => { - IEnumerable files = await GetFolderImages(folder); + IEnumerable files = await GetImagesForFolder(folder); foreach (var file in files) { @@ -81,7 +76,7 @@ private void LoadFolderCount(ImageFolder folder) Task.Run(async () => { await Task.Delay(200); - return await GetFolderImages(folder.Path); + return await GetImagesForFolder(folder.Path); }).ContinueWith((t) => { if (t.IsFaulted) diff --git a/QuickDraw.Core/Models/Settings.cs b/QuickDraw.Core/Models/Settings.cs index b3278b5..22e92dc 100644 --- a/QuickDraw.Core/Models/Settings.cs +++ b/QuickDraw.Core/Models/Settings.cs @@ -49,7 +49,4 @@ public class Settings public ImageFolderList ImageFolderList { get; set; } = new ImageFolderList(); public TimerEnum SlideTimerDuration { get; set; } - - [JsonIgnore] - public List SlidePaths { get; set; } = []; } diff --git a/QuickDrawWindows/App.xaml b/QuickDrawWindows/App.xaml index 0f2d2ea..e0687e7 100644 --- a/QuickDrawWindows/App.xaml +++ b/QuickDrawWindows/App.xaml @@ -1,4 +1,4 @@ - + - + - + Transparent Transparent diff --git a/QuickDrawWindows/App.xaml.cs b/QuickDrawWindows/App.xaml.cs index 7dc5529..d90443f 100644 --- a/QuickDrawWindows/App.xaml.cs +++ b/QuickDrawWindows/App.xaml.cs @@ -76,13 +76,14 @@ public App() services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // Views and ViewModels services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddTransient(); }). Build(); } diff --git a/QuickDrawWindows/Contracts/Services/ISlideImageService.cs b/QuickDrawWindows/Contracts/Services/ISlideImageService.cs new file mode 100644 index 0000000..cdc0935 --- /dev/null +++ b/QuickDrawWindows/Contracts/Services/ISlideImageService.cs @@ -0,0 +1,15 @@ +using QuickDraw.Core.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace QuickDraw.Contracts.Services; + +public interface ISlideImageService +{ + public Task LoadImages(IEnumerable folders); + + public List Images { get; } + + public TimerEnum SlideDuration { get; set; } +} + diff --git a/QuickDrawWindows/MainWindow.xaml b/QuickDrawWindows/MainWindow.xaml index db89119..d40fdc2 100644 --- a/QuickDrawWindows/MainWindow.xaml +++ b/QuickDrawWindows/MainWindow.xaml @@ -1,17 +1,16 @@ - + - + mc:Ignorable="d"> + - + - + \ No newline at end of file diff --git a/QuickDrawWindows/Services/PageService.cs b/QuickDrawWindows/Services/PageService.cs index 1bbe52c..0718976 100644 --- a/QuickDrawWindows/Services/PageService.cs +++ b/QuickDrawWindows/Services/PageService.cs @@ -19,7 +19,7 @@ public PageService() { // Configure Pages and their View Models Configure(); - Configure(); + Configure(); } public Type GetPageType(string key) diff --git a/QuickDrawWindows/Services/SettingsService.cs b/QuickDrawWindows/Services/SettingsService.cs index 3da8993..00f51a8 100644 --- a/QuickDrawWindows/Services/SettingsService.cs +++ b/QuickDrawWindows/Services/SettingsService.cs @@ -56,6 +56,10 @@ public async Task ReadSettings() { Debug.WriteLine(ex); } + finally + { + _ioSemaphore.Release(); + } } public async Task WriteSettings() @@ -81,5 +85,9 @@ public async Task WriteSettings() { Debug.WriteLine(ex); } + finally + { + _ioSemaphore.Release(); + } } } diff --git a/QuickDrawWindows/Services/SlideImageService.cs b/QuickDrawWindows/Services/SlideImageService.cs new file mode 100644 index 0000000..cab1fa3 --- /dev/null +++ b/QuickDrawWindows/Services/SlideImageService.cs @@ -0,0 +1,56 @@ +using QuickDraw.Contracts.Services; +using QuickDraw.Core.Models; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Security.Cryptography; +using System.Threading.Tasks; + +namespace QuickDraw.Services; + +static class TestRandom +{ + private static readonly Random _random = new Random(0); + + public static int GetInt32(Int32 from, Int32 to) + { + return _random.Next(from, to); + } +} + +static class ListExtensions +{ + public static void Shuffle(this IList list) + { + for (var i = 0; i < list.Count - 1; i++) + { + var j = RandomNumberGenerator.GetInt32(i + 1, list.Count); + + (list[i], list[j]) = (list[j], list[i]); + } + } +} + +public class SlideImageService : ISlideImageService +{ + public List Images { get; private set; } = []; + + public TimerEnum SlideDuration { get; set; } + + public async Task LoadImages(IEnumerable folders) + { + try + { + Images = [.. await ImageFolderList.GetImagesForFolders(folders)]; + Images.Shuffle(); + } + catch (Exception ex) + { + // TODO: Properly log + Debug.WriteLine(ex); + } + + return Images.Count; + } +} \ No newline at end of file diff --git a/QuickDrawWindows/ViewModels/MainViewModel.cs b/QuickDrawWindows/ViewModels/MainViewModel.cs index f4eb72d..ffac3ae 100644 --- a/QuickDrawWindows/ViewModels/MainViewModel.cs +++ b/QuickDrawWindows/ViewModels/MainViewModel.cs @@ -6,7 +6,9 @@ using QuickDraw.Contracts.Services; using QuickDraw.Contracts.ViewModels; using QuickDraw.Core.Models; +using QuickDraw.Services; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; @@ -16,28 +18,44 @@ namespace QuickDraw.ViewModels; public partial class MainViewModel : Base.ViewModelWithToolbarBase, INavigationAware { [ObservableProperty] - public partial double TimerSiderValue { get; set; } = TimerEnum.T2m.ToSliderValue(); + public partial double TimerSliderValue { get; set; } = TimerEnum.T2m.ToSliderValue(); [ObservableProperty] public partial ObservableCollection ImageFolderCollection { get; set; } private INavigationService _navigationService; private ISettingsService _settingsService; + private ISlideImageService _slideImageService; - public MainViewModel(INavigationService navigationService, ISettingsService settingsService, ITitlebarService titlebarService) : base(titlebarService) + public MainViewModel(INavigationService navigationService, ISettingsService settingsService, ITitlebarService titlebarService, ISlideImageService slideImageService) : base(titlebarService) { _navigationService = navigationService; _settingsService = settingsService; + _slideImageService = slideImageService; ImageFolderCollection = new ObservableCollection(_settingsService.Settings!.ImageFolderList.ImageFolders.Select(f => new ImageFolderViewModel(f))); } + public IEnumerable GetSelectedFolders() + { + return ImageFolderCollection.Where(f => f.Selected).Select(f => f.Path); + } + [RelayCommand] - private void StartSlideShow() + private async Task StartSlideShowAsync() { - // TODO: move actual logic here to start this - _navigationService.NavigateTo(typeof(SlideViewModel).FullName!, null, false, - new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromRight }); + var count = await _slideImageService.LoadImages(GetSelectedFolders()); + + if (count > 0) + { + _slideImageService.SlideDuration = TimerSliderValue.ToTimerEnum(); + _navigationService.NavigateTo(typeof(SlideViewModel).FullName!, null, false, + new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromRight }); + } + else + { + // TODO: Display the fact there were no images found in the selected folders + } } public async void OnNavigatedTo(object parameter) diff --git a/QuickDrawWindows/ViewModels/SlideViewModel.cs b/QuickDrawWindows/ViewModels/SlideViewModel.cs index e0dbad8..55c194b 100644 --- a/QuickDrawWindows/ViewModels/SlideViewModel.cs +++ b/QuickDrawWindows/ViewModels/SlideViewModel.cs @@ -1,19 +1,137 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Microsoft.UI.Dispatching; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Media.Animation; using QuickDraw.Contracts.Services; using QuickDraw.Contracts.ViewModels; -using QuickDraw.Services; +using QuickDraw.Core.Models; using System; -using System.ComponentModel; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; namespace QuickDraw.ViewModels; -public partial class SlideViewModel(ITitlebarService titlebarService, INavigationService navigationService) : Base.ViewModelWithToolbarBase(titlebarService), INavigationAware +public enum LoadDirection { + Backwards, + Forwards +} + +static class IntExtensions +{ + public static int Mod(this int value, int denominator) + { + int r = value % denominator; + return r < 0 ? r + denominator : r; + } +} + +public class LoadImageEventArgs +{ + public string ImagePath; + + public LoadImageEventArgs(string imagePath) => this.ImagePath = imagePath; +} + +public partial class SlideViewModel(ITitlebarService titlebarService, INavigationService navigationService, ISlideImageService slideImageService) : Base.ViewModelWithToolbarBase(titlebarService), INavigationAware +{ + public event EventHandler? NextImageHandler; + public event EventHandler? PreviousImageHandler; + + private int _currentImageIndex = 0; + + public string? CurrentImagePath { get; set; } + + public string? NextImagePath { get; set; } + + public string? PreviousImagePath { get; set; } + + private List Images { get => slideImageService.Images; } + + [ObservableProperty] + public partial double Progress { get; set; } + + [RelayCommand] + public void UpdateCurrentImages() + { + CurrentImagePath = slideImageService.Images[_currentImageIndex]; + + NextImagePath = slideImageService.Images[(_currentImageIndex + 1).Mod(Images.Count)]; + + PreviousImagePath = slideImageService.Images[(_currentImageIndex - 1).Mod(Images.Count)]; + } + + [RelayCommand] + public void NextImage(bool fromSlider = false) + { + _ticksElapsed = 0; + + if (!fromSlider) + { + Progress = 0; + } + + if (!Paused && !fromSlider) _slideTimer?.Start(); + + _currentImageIndex = (_currentImageIndex + 1).Mod(Images.Count); + NextImageHandler?.Invoke(this, new(slideImageService.Images[(_currentImageIndex + 1).Mod(Images.Count)])); + + } + + [RelayCommand] + public void PreviousImage() + { + _ticksElapsed = 0; + + Progress = 0; + + if (!Paused) _slideTimer?.Start(); + + _currentImageIndex = (_currentImageIndex - 1).Mod(Images.Count); + PreviousImageHandler?.Invoke(this, new(slideImageService.Images[(_currentImageIndex - 1).Mod(Images.Count)])); + } + + DispatcherQueueTimer? _slideTimer = null; + private uint _ticksElapsed = 0; + + public void StartTimer(DispatcherQueue dispatcherQueue) + { + var timerDurationEnum = slideImageService.SlideDuration; + + if (timerDurationEnum != TimerEnum.NoLimit) + { + _slideTimer = dispatcherQueue.CreateTimer(); + var timerDuration = timerDurationEnum.ToSeconds(); + + Progress = 0; + _slideTimer.IsRepeating = true; + _slideTimer.Interval = new(TimeSpan.TicksPerMillisecond * (long)1000); + _slideTimer.Tick += async (sender, e) => + { + _ticksElapsed += 1; + Progress = 100 * (double)_ticksElapsed / (double)timerDuration; + if (_ticksElapsed >= timerDuration) + { + _ticksElapsed = 0; + + NextImage(true); + + await Task.Delay(100); + Progress = 0; + + } + }; + _slideTimer.Start(); + } + else + { + PauseVisibility = Visibility.Collapsed; + } + } + public event EventHandler? InvalidateCanvas; [ObservableProperty] @@ -28,6 +146,24 @@ public partial class SlideViewModel(ITitlebarService titlebarService, INavigatio [RelayCommand] public void GoBack() => navigationService.GoBack(); + [RelayCommand] + public void PausePlay() + { + if (Paused) + { + _slideTimer?.Start(); + Paused = false; + } + else + { + _slideTimer?.Stop(); + Paused = true; + } + } + + [ObservableProperty] + public partial bool Paused { get; private set; } + partial void OnGrayscaleChanged(bool oldValue, bool newValue) { if (oldValue != newValue) @@ -36,7 +172,7 @@ partial void OnGrayscaleChanged(bool oldValue, bool newValue) } } - public async void OnNavigatedTo(object parameter) + public async void OnNavigatedTo(object parameter) { // TODO: toggle pause visibility based on if there is a timer or not // eg. if SlideTimerDuration == Models.TimerEnum.NoLimit @@ -50,5 +186,5 @@ public async void OnNavigatedTo(object parameter) TitlebarService?.TitleBar?.IconShowOptions = IconShowOptions.HideIconAndSystemMenu; } - public void OnNavigatedFrom() {} + public void OnNavigatedFrom() { } } diff --git a/QuickDrawWindows/Views/MainPage.xaml b/QuickDrawWindows/Views/MainPage.xaml index be35a87..47303be 100644 --- a/QuickDrawWindows/Views/MainPage.xaml +++ b/QuickDrawWindows/Views/MainPage.xaml @@ -76,7 +76,7 @@ StepFrequency="1" ToolTipFormat="N0" ToolTipTemplate="{StaticResource ToolTipTemplate}" - Value="{x:Bind ViewModel.TimerSiderValue}" /> + Value="{x:Bind ViewModel.TimerSliderValue, Mode=TwoWay}" /> + BackButtonCommand="{x:Bind ViewModel.GoBackCommand}" + DragRegionsChanged="{x:Bind ViewModel.HandleDragRegionsChanged}" + GrayscaleButtonCommand="{x:Bind ViewModel.ToggleGrayscaleCommand}" + Inactive="{x:Bind ViewModel.TitlebarInactive, Mode=OneWay}" + LeftInset="{x:Bind ViewModel.TitlebarLeftInset}" + NextButtonCommand="{x:Bind ViewModel.NextImageCommand}" + PauseButtonCommand="{x:Bind ViewModel.PausePlayCommand}" + PauseVisibility="{x:Bind ViewModel.PauseVisibility, Mode=OneWay}" + Paused="{x:Bind ViewModel.Paused, Mode=OneWay}" + PreviousButtonCommand="{x:Bind ViewModel.PreviousImageCommand}" + Progress="{x:Bind ViewModel.Progress, Mode=OneWay}" + RightInset="{x:Bind ViewModel.TitlebarRightInset}" + TitlebarService="{x:Bind ViewModel.TitlebarService}" /> + Draw="CanvasControl_Draw" /> \ No newline at end of file diff --git a/QuickDrawWindows/Views/SlidePage.xaml.cs b/QuickDrawWindows/Views/SlidePage.xaml.cs index 247b2b4..35b0d27 100644 --- a/QuickDrawWindows/Views/SlidePage.xaml.cs +++ b/QuickDrawWindows/Views/SlidePage.xaml.cs @@ -2,21 +2,14 @@ using Microsoft.Graphics.Canvas.Effects; using Microsoft.Graphics.Canvas.UI; using Microsoft.Graphics.Canvas.UI.Xaml; -using Microsoft.UI.Dispatching; using Microsoft.UI.Input; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Input; using QuickDraw.ViewModels; using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; +using System.Collections.Concurrent; using System.Threading.Tasks; using Windows.Foundation; -using Windows.Storage; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -27,7 +20,7 @@ public partial class MFPointerGrid : Grid { public MFPointerGrid() : base() { - + } public void SetCursor(InputCursor? cursor) @@ -36,453 +29,231 @@ public void SetCursor(InputCursor? cursor) } } -enum LoadDirection -{ - Backwards, - Forwards -} - -/// -/// An empty page that can be used on its own or navigated to within a Frame. -/// public sealed partial class SlidePage : Page { - public SlideViewModel ViewModel - { - get; - } - - private const int CACHE_SIZE = 9; - private const int HALF_CACHE_SIZE = CACHE_SIZE / 2; - - private List? imagePaths = null; + // TODO: Implement clicking the image to open it in explorer + private Task? _initImageLoadTask; - private readonly LinkedList cachedImages = new(); + private (string, CanvasVirtualBitmap?) _currentBitmap; + private (string, CanvasVirtualBitmap?) _nextBitmap; + private (string, CanvasVirtualBitmap?) _prevBitmap; - private LinkedListNode? currentImageNode = null; - private int imageCachePosition = 0; - private Task? imageLoadTask; + private Task? _currentTask; - private bool grayscale = false; + private readonly ConcurrentQueue> _loadTaskQueue = new(); - // TODO: Switch to Semaphores, locks don't work properly with async - private readonly object cachedImagesLock = new(); - private readonly object currentImageNodeLock = new(); - - private readonly DispatcherQueueTimer? m_SlideTimer = null; - - private uint m_TicksElapsed = 0; - - private TaskCompletionSource imageCacheFilled = new(); - - private Rect? currentImageBoundsInView = null; + public SlideViewModel ViewModel + { + get; + } public SlidePage() { ViewModel = App.GetService(); + ViewModel.InvalidateCanvas += ViewModel_InvalidateCanvas; + ViewModel.NextImageHandler += (sender, args) => NextImage(args.ImagePath); + ViewModel.PreviousImageHandler += (sender, args) => PrevImage(args.ImagePath); - this.InitializeComponent(); + CanvasDevice.DebugLevel = CanvasDebugLevel.Information; - /* var settings = (App.Current as App)?.Settings; - imagePaths = settings?.SlidePaths; - - var timerDurationEnum = settings?.SlideTimerDuration ?? TimerEnum.NoLimit; - - this.Unloaded += SlidePage_Unloaded; - - if (timerDurationEnum != TimerEnum.NoLimit) - { - var timerDuration = timerDurationEnum.ToSeconds(); - m_SlideTimer = DispatcherQueue.CreateTimer(); - m_SlideTimer.IsRepeating = true; - m_SlideTimer.Interval = new(TimeSpan.TicksPerMillisecond * (long)1000); - m_SlideTimer.Tick += async (sender, e) => - { - m_TicksElapsed += 1; - AppTitleBar.Progress = (double)m_TicksElapsed / (double)timerDuration; - if (m_TicksElapsed >= timerDuration) - { - m_TicksElapsed = 0; - - await Move(LoadDirection.Forwards); - - await Task.Delay(100); - AppTitleBar.Progress = 0; - - } - }; - m_SlideTimer.Start(); - }*/ + this.InitializeComponent(); } - void SlidePage_Unloaded(object sender, RoutedEventArgs e) + private async Task LoadNextTask(ICanvasResourceCreator resourceCreator, string imagePath) { - this.m_SlideTimer?.Stop(); - this.SlideCanvas.RemoveFromVisualTree(); - this.SlideCanvas = null; + _prevBitmap.Item2?.Dispose(); + _prevBitmap = _currentBitmap; + SlideCanvas?.Invalidate(); + _currentBitmap = _nextBitmap; + _nextBitmap = (imagePath, null); + _nextBitmap = (imagePath, await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePath)); } - private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args) - { - HandleLoadExceptions(); - bool isCurrentImageNull = true; - - lock (currentImageNodeLock) - { - isCurrentImageNull = currentImageNode == null; - } - - if (!isCurrentImageNull) - { - CanvasVirtualBitmap? bitmap; - - lock (currentImageNodeLock) - { - bitmap = currentImageNode?.Value; - } - - if (bitmap != null) - { - - Size canvasSize = new(sender.ActualWidth, sender.ActualHeight); - double canvasAspect = canvasSize.Width / canvasSize.Height; - double bitmapAspect = (bitmap?.Bounds.Width ?? 1.0) / (bitmap?.Bounds.Height ?? 1.0); - Size imageRenderSize; - Point imagePos; - - if (bitmapAspect > canvasAspect) - { - imageRenderSize = new Size( - canvasSize.Width, - canvasSize.Width / bitmapAspect - ); - imagePos = new Point(0, (canvasSize.Height - imageRenderSize.Height) / 2); - } - else - { - imageRenderSize = new Size( - canvasSize.Height * bitmapAspect, - canvasSize.Height - ); - imagePos = new Point((canvasSize.Width - imageRenderSize.Width) / 2, 0); - } - - Rect destBounds = new(imagePos, imageRenderSize); - currentImageBoundsInView = destBounds; - - CanvasCommandList cl = new(sender); - - using (CanvasDrawingSession clds = cl.CreateDrawingSession()) - { - clds.DrawImage(bitmap, destBounds, bitmap?.Bounds ?? new Rect()); - } - - if (grayscale) - { - GrayscaleEffect grayscale = new() - { - Source = bitmap - }; - args.DrawingSession.DrawImage(grayscale, destBounds, bitmap?.Bounds ?? new Rect()); - } - else - { - args.DrawingSession.DrawImage(cl); - } - } - } - } - - private void LoadImageInit() + private async Task LoadPrevTask(ICanvasResourceCreator resourceCreator, string imagePath) { - Debug.Assert(imageLoadTask == null); - imageLoadTask = FillImageCacheAsync(this.SlideCanvas, imageCachePosition).ContinueWith(_ => { - SlideCanvas.Invalidate(); - }); - } + _nextBitmap.Item2?.Dispose(); + _nextBitmap = _currentBitmap; + SlideCanvas?.Invalidate(); + _currentBitmap = _prevBitmap; - private static int Mod (int n, int d) - { - int r = n % d; - return r < 0 ? r+d : r; + _prevBitmap = (imagePath, null); + _prevBitmap = (imagePath, await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePath)); } - private async Task FillImageCacheAsync(CanvasControl resourceCreator, int index) + private void TaskContinue() { - if (imagePaths == null) + if (!_loadTaskQueue.IsEmpty) { - // TODO: Log - return; + if (_loadTaskQueue.TryDequeue(out var dequeueResult)) + { + _currentTask = dequeueResult().ContinueWith(task => { + TaskContinue(); + }); + } } - var bitmap = await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePaths[index]); - - lock (currentImageNodeLock) + else { - currentImageNode = cachedImages.AddFirst(bitmap); + _currentTask = null; } + } - lock (cachedImagesLock) + public void NextImage(string imagePath) + { + if (_currentTask == null) { - imageCachePosition = index; - } - - var remainingCacheSize = Math.Min(imagePaths.Count, CACHE_SIZE) - 1; - var numBefore = (remainingCacheSize / 2); - var numAfter = (remainingCacheSize / 2) + (remainingCacheSize % 2); - - var beforeImages = Enumerable.Range(index - numBefore, numBefore) - .Reverse() - .Select(i => Mod(i, imagePaths.Count)) - .ToArray() - .Select(i => + _currentTask = LoadNextTask(SlideCanvas, imagePath).ContinueWith(task => { - return imagePaths[i]; + TaskContinue(); }); - var afterImages = Enumerable.Range(index + 1, numAfter) - .Select(i => Mod(i, imagePaths.Count)) - .ToArray() - .Select(i => - { - return imagePaths[i]; - }); - - async Task LoadBeforeAsync() + } + else { - foreach (var image in beforeImages) - { - var bitmap = await CanvasVirtualBitmap.LoadAsync(resourceCreator, image); - lock(cachedImagesLock) - { - cachedImages.AddFirst(bitmap); - } - } + _loadTaskQueue.Enqueue(() => LoadNextTask(SlideCanvas, imagePath)); } + } - async Task LoadAfterAsync() + public void PrevImage(string imagePath) + { + if (_currentTask == null) { - foreach (var image in afterImages) + _currentTask = LoadPrevTask(SlideCanvas, imagePath).ContinueWith(task => { - var bitmap = await CanvasVirtualBitmap.LoadAsync(resourceCreator, image); - lock (cachedImagesLock) - { - cachedImages.AddLast(bitmap); - } - } + TaskContinue(); + }); } - - Task loadBeforeTask = Task.Run(LoadBeforeAsync); - Task loadAfterTask = Task.Run(LoadAfterAsync); - await loadBeforeTask; - await loadAfterTask; - - if (!imageCacheFilled.Task.IsCompleted) + else { - imageCacheFilled.SetResult(true); + _loadTaskQueue.Enqueue(() => LoadPrevTask(SlideCanvas, imagePath)); } } - private void SlideCanvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args) + private void ViewModel_InvalidateCanvas(object? sender, EventArgs e) { - args.TrackAsyncAction(CreateResourcesAsync().AsAsyncAction()); + SlideCanvas.Invalidate(); } - private async Task CreateResourcesAsync() + void SlidePage_Unloaded(object sender, RoutedEventArgs e) { - // Cancel old load - if (imageLoadTask != null) - { - imageLoadTask.AsAsyncAction().Cancel(); - try { await imageLoadTask; } catch { } - imageLoadTask = null; - - } + this.SlideCanvas.RemoveFromVisualTree(); + this.SlideCanvas = null; + } - // Unload previously loaded images - - lock (currentImageNodeLock) + private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args) + { + if (!IsLoadInProgress()) { - currentImageNode = null; - } + var bitmap = _currentBitmap.Item2; + #region Draw the image. + if (bitmap != null) + { + CanvasCommandList cl = new(sender); + _ = DrawBitmapToView(ref cl, bitmap, new Size(sender.ActualWidth, sender.ActualHeight), ViewModel.Grayscale); - lock (cachedImagesLock) - { - cachedImages.Clear(); + args.DrawingSession.DrawImage(cl); + } + #endregion } - - imageCacheFilled = new(); - - LoadImageInit(); } - private void HandleLoadExceptions() + private bool IsLoadInProgress() { - if (imageLoadTask == null || !imageLoadTask.IsCompleted) - return; + // No loading task? + if (_initImageLoadTask == null) + return false; + + // Loading task is still running? + if (!_initImageLoadTask.IsCompleted) + return true; + // Query the load task results and re-throw any exceptions + // so Win2D can see them. This implements requirement #2. try { - imageLoadTask.Wait(); + _initImageLoadTask.Wait(); } - catch(AggregateException aggregateException) + catch (AggregateException aggregateException) { + // .NET async tasks wrap all errors in an AggregateException. + // We unpack this so Win2D can directly see any lost device errors. aggregateException.Handle(exception => { throw exception; }); } - } - - private async Task UpdateImageAsync(CanvasControl resourceCreator, LoadDirection direction) - { - var increment = direction == LoadDirection.Forwards ? 1 : -1; - var halfCache = direction == LoadDirection.Forwards ? HALF_CACHE_SIZE : -HALF_CACHE_SIZE; - - var imageIndex = 0; - lock (cachedImagesLock) + finally { - imageIndex = Mod(imageCachePosition + increment + halfCache, imagePaths?.Count ?? 0); + _initImageLoadTask = null; } - var bitmap = await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePaths?[imageIndex]); - - lock (cachedImagesLock) - { - switch (direction) - { - case LoadDirection.Backwards: - { - cachedImages.AddFirst(bitmap); - - cachedImages?.Last?.Value.Dispose(); - cachedImages?.RemoveLast(); - } - break; - - case LoadDirection.Forwards: - { - cachedImages.AddLast(bitmap); - - cachedImages?.First?.Value.Dispose(); - cachedImages?.RemoveFirst(); - } - break; - } - - imageCachePosition = Mod(imageCachePosition + increment, imagePaths?.Count ?? 0); - } + return false; } - private async Task Move(LoadDirection direction) + private static Rect? DrawBitmapToView(ref CanvasCommandList cl, CanvasVirtualBitmap? bitmap, Size canvasSize, bool grayscale) { - await imageCacheFilled.Task; + if (bitmap == null) + return null; - if ((imagePaths?.Count ?? 0) > CACHE_SIZE) - { - await UpdateImageAsync(this.SlideCanvas, direction); - } + double canvasAspect = canvasSize.Width / canvasSize.Height; + double bitmapAspect = (bitmap?.Bounds.Width ?? 1.0) / (bitmap?.Bounds.Height ?? 1.0); + Size imageRenderSize; + Point imagePos; - if (imagePaths?.Count <= CACHE_SIZE) + if (bitmapAspect > canvasAspect) { - lock (currentImageNodeLock) - { - currentImageNode = direction == LoadDirection.Forwards ? - (currentImageNode?.Next ?? cachedImages.First) : - (currentImageNode?.Previous ?? cachedImages.Last); - } + imageRenderSize = new Size( + canvasSize.Width, + canvasSize.Width / bitmapAspect + ); + imagePos = new Point(0, (canvasSize.Height - imageRenderSize.Height) / 2); } else { - lock (currentImageNodeLock) - { - currentImageNode = direction == LoadDirection.Forwards ? - currentImageNode?.Next : - currentImageNode?.Previous; - } + imageRenderSize = new Size( + canvasSize.Height * bitmapAspect, + canvasSize.Height + ); + imagePos = new Point((canvasSize.Width - imageRenderSize.Width) / 2, 0); } - SlideCanvas?.Invalidate(); - } + Rect destBounds = new(imagePos, imageRenderSize); - private async void AppTitleBar_NextButtonClick(object sender, RoutedEventArgs e) - { - AppTitleBar.Progress = 0; - m_TicksElapsed = 0; - m_SlideTimer?.Start(); - await Move(LoadDirection.Forwards); - } + using CanvasDrawingSession clds = cl.CreateDrawingSession(); - private async void AppTitleBar_PreviousButtonClick(object sender, RoutedEventArgs e) - { - AppTitleBar.Progress = 0; - m_TicksElapsed = 0; - m_SlideTimer?.Start(); - await Move(LoadDirection.Backwards); - } + ICanvasImage? finalImage = grayscale ? new GrayscaleEffect() { Source = bitmap } : bitmap; - private void AppTitleBar_GrayscaleButtonClick(object sender, RoutedEventArgs e) - { - grayscale = !grayscale; - SlideCanvas?.Invalidate(); + clds.DrawImage(finalImage, destBounds, bitmap?.Bounds ?? new Rect()); + + return destBounds; } - private void AppTitleBar_PauseButtonClick(object sender, RoutedEventArgs e) + private void SlideCanvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args) { - - if (m_SlideTimer?.IsRunning ?? false) - { - m_SlideTimer.Stop(); - AppTitleBar.Paused = true; - } - else - { - m_SlideTimer?.Start(); - AppTitleBar.Paused = false; - } + args.TrackAsyncAction(CreateResourceAsync(sender).AsAsyncAction()); } - private void SlideCanvas_PointerReleased(object sender, PointerRoutedEventArgs e) + async Task CreateResourceAsync(CanvasControl sender) { - if (IsPointerEventInImageBounds(e)) + // Cancel old load + if (_initImageLoadTask != null) { - var imageIndex = 0; - lock (cachedImagesLock) - { - imageIndex = imageCachePosition; - } - - if (imageIndex == -1) - return; - - var path = imagePaths?[imageIndex]; + _initImageLoadTask.AsAsyncAction().Cancel(); + try { await _initImageLoadTask; } catch { } + _initImageLoadTask = null; - if (path != null) - { - Task.Run(async () => - { - Windows.System.FolderLauncherOptions launchOptions = new(); - - var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(App.Window); - WinRT.Interop.InitializeWithWindow.Initialize(launchOptions, hWnd); - - var file = await StorageFile.GetFileFromPathAsync(path); - launchOptions.ItemsToSelect.Add(file); - - await Windows.System.Launcher.LaunchFolderPathAsync(Path.GetDirectoryName(path), launchOptions); - }); - } } - } - private bool IsPointerEventInImageBounds(PointerRoutedEventArgs e) - { - return currentImageBoundsInView?.Contains(e.GetCurrentPoint(SlideCanvas).Position) ?? false; + _initImageLoadTask = FillImageCacheAsync(sender).ContinueWith(_ => SlideCanvas.Invalidate()); } - private void SlideCanvas_PointerMoved(object sender, PointerRoutedEventArgs e) + private async Task FillImageCacheAsync(CanvasControl resourceCreator) { - if (IsPointerEventInImageBounds(e)) - { - SlideCanvasContainer.SetCursor(InputSystemCursor.Create(InputSystemCursorShape.Hand)); - } else - { - SlideCanvasContainer.SetCursor(null); - } + ViewModel.UpdateCurrentImagesCommand?.Execute(null); + + var prevBitmapTask = CanvasVirtualBitmap.LoadAsync(resourceCreator, ViewModel.PreviousImagePath!); + var currBitmapTask = CanvasVirtualBitmap.LoadAsync(resourceCreator, ViewModel.CurrentImagePath!); + var nextBitmapTask = CanvasVirtualBitmap.LoadAsync(resourceCreator, ViewModel.NextImagePath!); + + _prevBitmap = (ViewModel.PreviousImagePath!, await prevBitmapTask); + _currentBitmap = (ViewModel.CurrentImagePath!, await currBitmapTask); + _nextBitmap = (ViewModel.NextImagePath!, await nextBitmapTask); + + ViewModel.StartTimer(DispatcherQueue); } } diff --git a/QuickDrawWindows/Views/SlidePage2.xaml b/QuickDrawWindows/Views/SlidePage2.xaml deleted file mode 100644 index fc4db6e..0000000 --- a/QuickDrawWindows/Views/SlidePage2.xaml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/QuickDrawWindows/Views/SlidePage2.xaml.cs b/QuickDrawWindows/Views/SlidePage2.xaml.cs deleted file mode 100644 index dd97c0e..0000000 --- a/QuickDrawWindows/Views/SlidePage2.xaml.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Microsoft.Graphics.Canvas; -using Microsoft.Graphics.Canvas.Effects; -using Microsoft.Graphics.Canvas.UI; -using Microsoft.Graphics.Canvas.UI.Xaml; -using Microsoft.UI.Dispatching; -using Microsoft.UI.Input; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Input; -using QuickDraw.ViewModels; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using System.Threading.Tasks; -using Windows.Foundation; -using Windows.Storage; - -// To learn more about WinUI, the WinUI project structure, -// and more about our project templates, see: http://aka.ms/winui-project-info. - -namespace QuickDraw.Views; - -/// -/// An empty page that can be used on its own or navigated to within a Frame. -/// -public sealed partial class SlidePage2 : Page -{ - public SlideViewModel ViewModel - { - get; - } - - private CanvasVirtualBitmap? _bitmap; - - public SlidePage2() - { - ViewModel = App.GetService(); - - ViewModel.InvalidateCanvas += ViewModel_InvalidateCanvas; - - this.InitializeComponent(); - } - - private void ViewModel_InvalidateCanvas(object? sender, EventArgs e) - { - SlideCanvas.Invalidate(); - } - - void SlidePage_Unloaded(object sender, RoutedEventArgs e) - { - this.SlideCanvas.RemoveFromVisualTree(); - this.SlideCanvas = null; - } - - private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args) - { - CanvasVirtualBitmap? bitmap = _bitmap; - - #region Draw the image. - if (bitmap != null) - { - CanvasCommandList cl = new(sender); - _ = DrawBitmapToView(ref cl, bitmap, new Size(sender.ActualWidth, sender.ActualHeight), ViewModel.Grayscale); - - args.DrawingSession.DrawImage(cl); - } - #endregion - } - - private static Rect? DrawBitmapToView(ref CanvasCommandList cl, CanvasVirtualBitmap? bitmap, Size canvasSize, bool grayscale) - { - if (bitmap == null) - return null; - - double canvasAspect = canvasSize.Width / canvasSize.Height; - double bitmapAspect = (bitmap?.Bounds.Width ?? 1.0) / (bitmap?.Bounds.Height ?? 1.0); - Size imageRenderSize; - Point imagePos; - - if (bitmapAspect > canvasAspect) - { - imageRenderSize = new Size( - canvasSize.Width, - canvasSize.Width / bitmapAspect - ); - imagePos = new Point(0, (canvasSize.Height - imageRenderSize.Height) / 2); - } - else - { - imageRenderSize = new Size( - canvasSize.Height * bitmapAspect, - canvasSize.Height - ); - imagePos = new Point((canvasSize.Width - imageRenderSize.Width) / 2, 0); - } - - Rect destBounds = new(imagePos, imageRenderSize); - - using CanvasDrawingSession clds = cl.CreateDrawingSession(); - - ICanvasImage? finalImage = grayscale ? new GrayscaleEffect() { Source = bitmap } : bitmap; - - clds.DrawImage(finalImage, destBounds, bitmap?.Bounds ?? new Rect()); - - return destBounds; - } - - private void SlideCanvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args) - { - args.TrackAsyncAction(CreateResourceAsync(sender).AsAsyncAction()); - } - - async Task CreateResourceAsync(CanvasControl sender) - { - _bitmap = await CanvasVirtualBitmap.LoadAsync(sender, @"D:\Reference\Image Reference\Poses\Women\3fec304497c805d248918ffc8667db5c.png"); - } -} diff --git a/QuickDrawWindows/Views/SlideTitlebarControl.xaml b/QuickDrawWindows/Views/SlideTitlebarControl.xaml index c82cb9b..201a9dd 100644 --- a/QuickDrawWindows/Views/SlideTitlebarControl.xaml +++ b/QuickDrawWindows/Views/SlideTitlebarControl.xaml @@ -17,6 +17,8 @@ 0 0 + True + False @@ -31,7 +33,7 @@ x:Name="ProgressBar" Grid.Column="0" VerticalAlignment="Bottom" - Value="{x:Bind Progress}" /> + Value="{x:Bind Progress, Mode=OneWay}" /> + Command="{x:Bind NextButtonCommand}" + CommandParameter="{StaticResource False}"> @@ -130,7 +133,7 @@ - + diff --git a/QuickDrawWindows/Views/SlideTitlebarControl.xaml.cs b/QuickDrawWindows/Views/SlideTitlebarControl.xaml.cs index 14ff48d..ffefb9f 100644 --- a/QuickDrawWindows/Views/SlideTitlebarControl.xaml.cs +++ b/QuickDrawWindows/Views/SlideTitlebarControl.xaml.cs @@ -11,7 +11,7 @@ namespace QuickDraw.Views; [DependencyProperty("Paused", DefaultValue = false)] [DependencyProperty("PauseVisibility", DefaultValue = Visibility.Visible)] -[DependencyProperty("Progress", DefaultValue = 25)] +[DependencyProperty("Progress", DefaultValue = 0)] [DependencyProperty("NextButtonCommand")] [DependencyProperty("PreviousButtonCommand")] [DependencyProperty("GrayscaleButtonCommand")] From 28d68f57e5768888111902392194db09aec051cc Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Tue, 30 Sep 2025 12:01:12 -0400 Subject: [PATCH 56/60] Simplified slide image drawing mostly to avoid dispose issues --- QuickDrawWindows/Views/SlidePage.xaml.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/QuickDrawWindows/Views/SlidePage.xaml.cs b/QuickDrawWindows/Views/SlidePage.xaml.cs index 35b0d27..fb82dce 100644 --- a/QuickDrawWindows/Views/SlidePage.xaml.cs +++ b/QuickDrawWindows/Views/SlidePage.xaml.cs @@ -63,21 +63,21 @@ private async Task LoadNextTask(ICanvasResourceCreator resourceCreator, string i { _prevBitmap.Item2?.Dispose(); _prevBitmap = _currentBitmap; - SlideCanvas?.Invalidate(); _currentBitmap = _nextBitmap; _nextBitmap = (imagePath, null); _nextBitmap = (imagePath, await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePath)); + SlideCanvas?.Invalidate(); } private async Task LoadPrevTask(ICanvasResourceCreator resourceCreator, string imagePath) { _nextBitmap.Item2?.Dispose(); _nextBitmap = _currentBitmap; - SlideCanvas?.Invalidate(); _currentBitmap = _prevBitmap; _prevBitmap = (imagePath, null); _prevBitmap = (imagePath, await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePath)); + SlideCanvas?.Invalidate(); } private void TaskContinue() @@ -143,15 +143,8 @@ private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args) if (!IsLoadInProgress()) { var bitmap = _currentBitmap.Item2; - #region Draw the image. - if (bitmap != null) - { - CanvasCommandList cl = new(sender); - _ = DrawBitmapToView(ref cl, bitmap, new Size(sender.ActualWidth, sender.ActualHeight), ViewModel.Grayscale); - args.DrawingSession.DrawImage(cl); - } - #endregion + _ = DrawBitmapToView(args.DrawingSession, bitmap, new Size(sender.ActualWidth, sender.ActualHeight), ViewModel.Grayscale); } } @@ -185,7 +178,7 @@ private bool IsLoadInProgress() return false; } - private static Rect? DrawBitmapToView(ref CanvasCommandList cl, CanvasVirtualBitmap? bitmap, Size canvasSize, bool grayscale) + private static Rect? DrawBitmapToView(CanvasDrawingSession session, CanvasVirtualBitmap? bitmap, Size canvasSize, bool grayscale) { if (bitmap == null) return null; @@ -214,11 +207,9 @@ private bool IsLoadInProgress() Rect destBounds = new(imagePos, imageRenderSize); - using CanvasDrawingSession clds = cl.CreateDrawingSession(); - ICanvasImage? finalImage = grayscale ? new GrayscaleEffect() { Source = bitmap } : bitmap; - clds.DrawImage(finalImage, destBounds, bitmap?.Bounds ?? new Rect()); + session.DrawImage(finalImage, destBounds, bitmap?.Bounds ?? new Rect()); return destBounds; } From c9143d272d2954a54c3c692260ce236df33f0081 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:21:27 -0400 Subject: [PATCH 57/60] Simplified the image load queue using Channel Cleaned up some small things --- .../Services/NavigationService.cs | 4 +- QuickDrawWindows/ViewModels/SlideViewModel.cs | 12 +- QuickDrawWindows/Views/MainPage.xaml.cs | 31 ++--- QuickDrawWindows/Views/SlidePage.xaml.cs | 114 +++++++++++------- 4 files changed, 83 insertions(+), 78 deletions(-) diff --git a/QuickDrawWindows/Services/NavigationService.cs b/QuickDrawWindows/Services/NavigationService.cs index b244307..172d223 100644 --- a/QuickDrawWindows/Services/NavigationService.cs +++ b/QuickDrawWindows/Services/NavigationService.cs @@ -95,7 +95,7 @@ public bool NavigateTo(string pageKey, object? parameter = null, bool clearNavig _frame.Tag = clearNavigation; var vmBeforeNavigation = _frame.GetPageViewModel(); - var navigated = false; + bool navigated; if (transitionInfo != null) { navigated = _frame.Navigate(pageType, parameter, transitionInfo); @@ -139,5 +139,5 @@ private void OnNavigated(object sender, NavigationEventArgs e) } } - public void SetListDataItemForNextConnectedAnimation(object item) => Frame.SetListDataItemForNextConnectedAnimation(item); + public void SetListDataItemForNextConnectedAnimation(object item) => Frame?.SetListDataItemForNextConnectedAnimation(item); } \ No newline at end of file diff --git a/QuickDrawWindows/ViewModels/SlideViewModel.cs b/QuickDrawWindows/ViewModels/SlideViewModel.cs index 55c194b..7ff8117 100644 --- a/QuickDrawWindows/ViewModels/SlideViewModel.cs +++ b/QuickDrawWindows/ViewModels/SlideViewModel.cs @@ -14,12 +14,6 @@ namespace QuickDraw.ViewModels; -public enum LoadDirection -{ - Backwards, - Forwards -} - static class IntExtensions { public static int Mod(this int value, int denominator) @@ -29,11 +23,9 @@ public static int Mod(this int value, int denominator) } } -public class LoadImageEventArgs +public class LoadImageEventArgs(string imagePath) { - public string ImagePath; - - public LoadImageEventArgs(string imagePath) => this.ImagePath = imagePath; + public string ImagePath = imagePath; } public partial class SlideViewModel(ITitlebarService titlebarService, INavigationService navigationService, ISlideImageService slideImageService) : Base.ViewModelWithToolbarBase(titlebarService), INavigationAware diff --git a/QuickDrawWindows/Views/MainPage.xaml.cs b/QuickDrawWindows/Views/MainPage.xaml.cs index 78c3d16..de78834 100644 --- a/QuickDrawWindows/Views/MainPage.xaml.cs +++ b/QuickDrawWindows/Views/MainPage.xaml.cs @@ -19,21 +19,17 @@ namespace QuickDraw.Views; /// Converts the value of the internal slider into text. /// /// Internal use only. -internal partial class StringToEnumConverter : IValueConverter +internal partial class StringToEnumConverter(Type type) : IValueConverter { - private readonly Type _enum; - - public StringToEnumConverter(Type type) => _enum = type; - public object? Convert(object value, Type targetType, object parameter, string language) { - var _name = Enum.ToObject(_enum, (int)Double.Parse((string)value)); + var _name = Enum.ToObject(type, (int)Double.Parse((string)value)); // Look for a 'Display' attribute. - var _member = _enum + var _member = type .GetRuntimeFields() .FirstOrDefault(x => x.Name == _name.ToString()); if (_member == null) @@ -41,8 +37,8 @@ internal partial class StringToEnumConverter : IValueConverter return _name; } - var _attr = (DisplayAttribute?)_member - .GetCustomAttribute(typeof(DisplayAttribute)); + var _attr = _member + .GetCustomAttribute(); if (_attr == null) { return _name; @@ -65,24 +61,17 @@ public object ConvertBack(object value, /// Converts the value of the internal slider into text. /// /// Internal use only. -internal partial class DoubleToEnumConverter : IValueConverter +internal partial class DoubleToEnumConverter(Type type) : IValueConverter { - private readonly Type _enum; - - public DoubleToEnumConverter(Type type) - { - _enum = type; - } - public object? Convert(object value, Type targetType, object parameter, string language) { - var _name = Enum.ToObject(_enum, (int)(double)value); + var _name = Enum.ToObject(type, (int)(double)value); // Look for a 'Display' attribute. - var _member = _enum + var _member = type .GetRuntimeFields() .FirstOrDefault(x => x.Name == _name.ToString()); if (_member == null) @@ -90,8 +79,8 @@ public DoubleToEnumConverter(Type type) return _name; } - var _attr = (DisplayAttribute?)_member - .GetCustomAttribute(typeof(DisplayAttribute)); + var _attr = _member + .GetCustomAttribute(); if (_attr == null) { return _name; diff --git a/QuickDrawWindows/Views/SlidePage.xaml.cs b/QuickDrawWindows/Views/SlidePage.xaml.cs index fb82dce..b741ccb 100644 --- a/QuickDrawWindows/Views/SlidePage.xaml.cs +++ b/QuickDrawWindows/Views/SlidePage.xaml.cs @@ -5,9 +5,13 @@ using Microsoft.UI.Input; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; using QuickDraw.ViewModels; using System; using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Windows.Foundation; @@ -16,6 +20,34 @@ namespace QuickDraw.Views; +public enum LoadDirection +{ + Backwards, + Forwards +} + +public record LoadData(string Path, LoadDirection Direction); + +public class ChannelQueue +{ + private readonly Channel _channel = Channel.CreateUnbounded(); + + public bool Enqueue(T data) + { + return _channel.Writer.TryWrite(data); + } + + public ValueTask DequeueAsync(CancellationToken token = default) + { + return _channel.Reader.ReadAsync(token); + } + + public ValueTask WaitForNext(CancellationToken token = default) + { + return _channel.Reader.WaitToReadAsync(token); + } +} + public partial class MFPointerGrid : Grid { public MFPointerGrid() : base() @@ -38,9 +70,8 @@ public sealed partial class SlidePage : Page private (string, CanvasVirtualBitmap?) _nextBitmap; private (string, CanvasVirtualBitmap?) _prevBitmap; - private Task? _currentTask; - - private readonly ConcurrentQueue> _loadTaskQueue = new(); + private readonly CancellationTokenSource _cts = new(); + private readonly ChannelQueue _imageLoadQueue = new(); public SlideViewModel ViewModel { @@ -59,7 +90,32 @@ public SlidePage() this.InitializeComponent(); } - private async Task LoadNextTask(ICanvasResourceCreator resourceCreator, string imagePath) + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + + _ = HandleLoads(_cts.Token); + } + + private async Task HandleLoads(CancellationToken token) + { + while (await _imageLoadQueue.WaitForNext(token)) + { + var data = await _imageLoadQueue.DequeueAsync(token); + + switch (data.Direction) + { + case LoadDirection.Forwards: + await LoadNext(SlideCanvas, data.Path); + break; + case LoadDirection.Backwards: + await LoadPrev(SlideCanvas, data.Path); + break; + } + } + } + + private async Task LoadNext(ICanvasResourceCreator resourceCreator, string imagePath) { _prevBitmap.Item2?.Dispose(); _prevBitmap = _currentBitmap; @@ -69,62 +125,24 @@ private async Task LoadNextTask(ICanvasResourceCreator resourceCreator, string i SlideCanvas?.Invalidate(); } - private async Task LoadPrevTask(ICanvasResourceCreator resourceCreator, string imagePath) + private async Task LoadPrev(ICanvasResourceCreator resourceCreator, string imagePath) { _nextBitmap.Item2?.Dispose(); _nextBitmap = _currentBitmap; _currentBitmap = _prevBitmap; - _prevBitmap = (imagePath, null); _prevBitmap = (imagePath, await CanvasVirtualBitmap.LoadAsync(resourceCreator, imagePath)); SlideCanvas?.Invalidate(); } - private void TaskContinue() - { - if (!_loadTaskQueue.IsEmpty) - { - if (_loadTaskQueue.TryDequeue(out var dequeueResult)) - { - _currentTask = dequeueResult().ContinueWith(task => { - TaskContinue(); - }); - } - } - else - { - _currentTask = null; - } - } - public void NextImage(string imagePath) { - if (_currentTask == null) - { - _currentTask = LoadNextTask(SlideCanvas, imagePath).ContinueWith(task => - { - TaskContinue(); - }); - } - else - { - _loadTaskQueue.Enqueue(() => LoadNextTask(SlideCanvas, imagePath)); - } + _imageLoadQueue.Enqueue(new(imagePath,LoadDirection.Forwards)); } public void PrevImage(string imagePath) { - if (_currentTask == null) - { - _currentTask = LoadPrevTask(SlideCanvas, imagePath).ContinueWith(task => - { - TaskContinue(); - }); - } - else - { - _loadTaskQueue.Enqueue(() => LoadPrevTask(SlideCanvas, imagePath)); - } + _imageLoadQueue.Enqueue(new(imagePath, LoadDirection.Backwards)); } private void ViewModel_InvalidateCanvas(object? sender, EventArgs e) @@ -134,6 +152,12 @@ private void ViewModel_InvalidateCanvas(object? sender, EventArgs e) void SlidePage_Unloaded(object sender, RoutedEventArgs e) { + _cts.Cancel(); + + _currentBitmap.Item2?.Dispose(); + _prevBitmap.Item2?.Dispose(); + _nextBitmap.Item2?.Dispose(); + this.SlideCanvas.RemoveFromVisualTree(); this.SlideCanvas = null; } From 56b56d3f61c656c1560b22bb2bc21c0d48f470c6 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:27:18 -0400 Subject: [PATCH 58/60] Properly clean up events Some minor handler renaming --- .../Contracts/ViewModels/IUnloadable.cs | 7 ++++++ .../Contracts/ViewModels/IViewModel.cs | 6 +++++ .../Services/ActivationService.cs | 2 +- .../Base/ViewModelWithToolbarBase.cs | 11 +++++---- QuickDrawWindows/ViewModels/MainViewModel.cs | 2 +- QuickDrawWindows/ViewModels/SlideViewModel.cs | 6 ++--- QuickDrawWindows/Views/Base/PageBase.cs | 24 +++++++++++++++++++ .../Views/Base/TitlebarBaseControl.cs | 4 ++-- QuickDrawWindows/Views/MainPage.xaml | 13 +++++----- QuickDrawWindows/Views/MainPage.xaml.cs | 7 +++--- QuickDrawWindows/Views/SlidePage.xaml | 9 +++---- QuickDrawWindows/Views/SlidePage.xaml.cs | 11 +++++---- 12 files changed, 73 insertions(+), 29 deletions(-) create mode 100644 QuickDrawWindows/Contracts/ViewModels/IUnloadable.cs create mode 100644 QuickDrawWindows/Contracts/ViewModels/IViewModel.cs create mode 100644 QuickDrawWindows/Views/Base/PageBase.cs diff --git a/QuickDrawWindows/Contracts/ViewModels/IUnloadable.cs b/QuickDrawWindows/Contracts/ViewModels/IUnloadable.cs new file mode 100644 index 0000000..ba94100 --- /dev/null +++ b/QuickDrawWindows/Contracts/ViewModels/IUnloadable.cs @@ -0,0 +1,7 @@ +namespace QuickDraw.Contracts.ViewModels; + +public interface IUnloadable +{ + public void Unloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e); +} + diff --git a/QuickDrawWindows/Contracts/ViewModels/IViewModel.cs b/QuickDrawWindows/Contracts/ViewModels/IViewModel.cs new file mode 100644 index 0000000..943ec4c --- /dev/null +++ b/QuickDrawWindows/Contracts/ViewModels/IViewModel.cs @@ -0,0 +1,6 @@ +namespace QuickDraw.Contracts.ViewModels; + +public interface IViewModel +{ +} + diff --git a/QuickDrawWindows/Services/ActivationService.cs b/QuickDrawWindows/Services/ActivationService.cs index 3e93df7..3fef82e 100644 --- a/QuickDrawWindows/Services/ActivationService.cs +++ b/QuickDrawWindows/Services/ActivationService.cs @@ -10,7 +10,7 @@ namespace QuickDraw.Services; -public class ActivationService(ActivationHandler defaultHandler, IEnumerable activationHandlers, ISettingsService settingsService, ITitlebarService titlebarService) : IActivationService +public class ActivationService(ActivationHandler defaultHandler, IEnumerable activationHandlers, ISettingsService settingsService) : IActivationService { public async Task ActivateAsync(object activationArgs) { diff --git a/QuickDrawWindows/ViewModels/Base/ViewModelWithToolbarBase.cs b/QuickDrawWindows/ViewModels/Base/ViewModelWithToolbarBase.cs index 42147d5..255d414 100644 --- a/QuickDrawWindows/ViewModels/Base/ViewModelWithToolbarBase.cs +++ b/QuickDrawWindows/ViewModels/Base/ViewModelWithToolbarBase.cs @@ -1,14 +1,12 @@ using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; using Microsoft.UI.Xaml; using QuickDraw.Contracts.Services; using QuickDraw.Contracts.ViewModels; using QuickDraw.Views.Base; -using System.Diagnostics; namespace QuickDraw.ViewModels.Base; -public partial class ViewModelWithToolbarBase : ObservableObject +public partial class ViewModelWithTitlebarBase : ObservableObject, IUnloadable, IViewModel { public ITitlebarService TitlebarService; @@ -18,7 +16,7 @@ public partial class ViewModelWithToolbarBase : ObservableObject [ObservableProperty] public partial bool TitlebarInactive { get; set; } - public ViewModelWithToolbarBase(ITitlebarService titlebarService) + public ViewModelWithTitlebarBase(ITitlebarService titlebarService) { TitlebarService = titlebarService; App.Window.Activated += Window_Activated; @@ -40,4 +38,9 @@ private void Window_Activated(object sender, WindowActivatedEventArgs args) TitlebarInactive = false; } } + + public virtual void Unloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + { + App.Window.Activated -= Window_Activated; + } } diff --git a/QuickDrawWindows/ViewModels/MainViewModel.cs b/QuickDrawWindows/ViewModels/MainViewModel.cs index ffac3ae..4a009e5 100644 --- a/QuickDrawWindows/ViewModels/MainViewModel.cs +++ b/QuickDrawWindows/ViewModels/MainViewModel.cs @@ -15,7 +15,7 @@ namespace QuickDraw.ViewModels; -public partial class MainViewModel : Base.ViewModelWithToolbarBase, INavigationAware +public partial class MainViewModel : Base.ViewModelWithTitlebarBase, INavigationAware { [ObservableProperty] public partial double TimerSliderValue { get; set; } = TimerEnum.T2m.ToSliderValue(); diff --git a/QuickDrawWindows/ViewModels/SlideViewModel.cs b/QuickDrawWindows/ViewModels/SlideViewModel.cs index 7ff8117..4b308f1 100644 --- a/QuickDrawWindows/ViewModels/SlideViewModel.cs +++ b/QuickDrawWindows/ViewModels/SlideViewModel.cs @@ -28,7 +28,7 @@ public class LoadImageEventArgs(string imagePath) public string ImagePath = imagePath; } -public partial class SlideViewModel(ITitlebarService titlebarService, INavigationService navigationService, ISlideImageService slideImageService) : Base.ViewModelWithToolbarBase(titlebarService), INavigationAware +public partial class SlideViewModel(ITitlebarService titlebarService, INavigationService navigationService, ISlideImageService slideImageService) : Base.ViewModelWithTitlebarBase(titlebarService), INavigationAware { public event EventHandler? NextImageHandler; public event EventHandler? PreviousImageHandler; @@ -89,7 +89,7 @@ public void PreviousImage() DispatcherQueueTimer? _slideTimer = null; private uint _ticksElapsed = 0; - public void StartTimer(DispatcherQueue dispatcherQueue) + public void StartTimer(DispatcherQueue dispatcherQueue) { var timerDurationEnum = slideImageService.SlideDuration; @@ -154,7 +154,7 @@ public void PausePlay() } [ObservableProperty] - public partial bool Paused { get; private set; } + public partial bool Paused { get; private set; } partial void OnGrayscaleChanged(bool oldValue, bool newValue) { diff --git a/QuickDrawWindows/Views/Base/PageBase.cs b/QuickDrawWindows/Views/Base/PageBase.cs new file mode 100644 index 0000000..5edcd34 --- /dev/null +++ b/QuickDrawWindows/Views/Base/PageBase.cs @@ -0,0 +1,24 @@ +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using QuickDraw.Contracts.ViewModels; + +namespace QuickDraw.Views.Base; + +public partial class PageBase : Page +{ + protected IViewModel ViewModelBase { get; } + + public PageBase(IViewModel viewModel) + { + ViewModelBase = viewModel; + if (viewModel is IUnloadable unloadable) + { + Unloaded += unloadable.Unloaded; + } + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + } +} \ No newline at end of file diff --git a/QuickDrawWindows/Views/Base/TitlebarBaseControl.cs b/QuickDrawWindows/Views/Base/TitlebarBaseControl.cs index 44d8aa8..79fcd44 100644 --- a/QuickDrawWindows/Views/Base/TitlebarBaseControl.cs +++ b/QuickDrawWindows/Views/Base/TitlebarBaseControl.cs @@ -29,10 +29,10 @@ public abstract partial class TitlebarBaseControl : UserControl public TitlebarBaseControl() { - SizeChanged += TitlebarBaseControl_SizeChanged; + SizeChanged += OnSizeChanged; } - protected void TitlebarBaseControl_SizeChanged(object sender, Microsoft.UI.Xaml.SizeChangedEventArgs e) + protected void OnSizeChanged(object sender, Microsoft.UI.Xaml.SizeChangedEventArgs e) { var regions = CalculateDragRegions(); diff --git a/QuickDrawWindows/Views/MainPage.xaml b/QuickDrawWindows/Views/MainPage.xaml index 47303be..1536eda 100644 --- a/QuickDrawWindows/Views/MainPage.xaml +++ b/QuickDrawWindows/Views/MainPage.xaml @@ -1,8 +1,9 @@ - - + - + - + @@ -24,7 +25,7 @@ - + @@ -102,4 +103,4 @@ - + diff --git a/QuickDrawWindows/Views/MainPage.xaml.cs b/QuickDrawWindows/Views/MainPage.xaml.cs index de78834..36cdbf1 100644 --- a/QuickDrawWindows/Views/MainPage.xaml.cs +++ b/QuickDrawWindows/Views/MainPage.xaml.cs @@ -2,6 +2,7 @@ using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Data; using QuickDraw.ViewModels; +using QuickDraw.Views.Base; using Syncfusion.UI.Xaml.Sliders; using System; using System.Collections.Generic; @@ -103,17 +104,17 @@ public object ConvertBack(object value, /// /// An empty page that can be used on its own or navigated to within a Frame. /// -public sealed partial class MainPage : Page +public sealed partial class MainPage : PageBase { public MainViewModel ViewModel { get; } - public MainPage() + public MainPage() : base(App.GetService()) { - ViewModel = App.GetService(); + ViewModel = (MainViewModel)base.ViewModelBase; this.InitializeComponent(); this.Resources.Add("doubleToEnumConverter", new DoubleToEnumConverter(typeof(Core.Models.TimerEnum))); diff --git a/QuickDrawWindows/Views/SlidePage.xaml b/QuickDrawWindows/Views/SlidePage.xaml index fa3cdf6..026694a 100644 --- a/QuickDrawWindows/Views/SlidePage.xaml +++ b/QuickDrawWindows/Views/SlidePage.xaml @@ -1,8 +1,9 @@ - - + - + @@ -45,4 +46,4 @@ Draw="CanvasControl_Draw" /> - \ No newline at end of file + \ No newline at end of file diff --git a/QuickDrawWindows/Views/SlidePage.xaml.cs b/QuickDrawWindows/Views/SlidePage.xaml.cs index b741ccb..525b532 100644 --- a/QuickDrawWindows/Views/SlidePage.xaml.cs +++ b/QuickDrawWindows/Views/SlidePage.xaml.cs @@ -7,6 +7,7 @@ using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Navigation; using QuickDraw.ViewModels; +using QuickDraw.Views.Base; using System; using System.Collections.Concurrent; using System.Diagnostics; @@ -61,7 +62,7 @@ public void SetCursor(InputCursor? cursor) } } -public sealed partial class SlidePage : Page +public sealed partial class SlidePage : PageBase { // TODO: Implement clicking the image to open it in explorer private Task? _initImageLoadTask; @@ -78,10 +79,10 @@ public SlideViewModel ViewModel get; } - public SlidePage() + public SlidePage() : base(App.GetService()) { - ViewModel = App.GetService(); - ViewModel.InvalidateCanvas += ViewModel_InvalidateCanvas; + ViewModel = (SlideViewModel)ViewModelBase; + ViewModel.InvalidateCanvas += InvalidateCanvas; ViewModel.NextImageHandler += (sender, args) => NextImage(args.ImagePath); ViewModel.PreviousImageHandler += (sender, args) => PrevImage(args.ImagePath); @@ -145,7 +146,7 @@ public void PrevImage(string imagePath) _imageLoadQueue.Enqueue(new(imagePath, LoadDirection.Backwards)); } - private void ViewModel_InvalidateCanvas(object? sender, EventArgs e) + private void InvalidateCanvas(object? sender, EventArgs e) { SlideCanvas.Invalidate(); } From 5fbbb505bc2347f377b33a2a605564308345c832 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:56:29 -0400 Subject: [PATCH 59/60] Moved the StartTimer call outside of a call started from CreateResources (prevents the timer starting when losing the device, causing unwanted unpauses) --- QuickDrawWindows/Views/SlidePage.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QuickDrawWindows/Views/SlidePage.xaml.cs b/QuickDrawWindows/Views/SlidePage.xaml.cs index 525b532..d06804d 100644 --- a/QuickDrawWindows/Views/SlidePage.xaml.cs +++ b/QuickDrawWindows/Views/SlidePage.xaml.cs @@ -95,6 +95,8 @@ protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); + ViewModel.StartTimer(DispatcherQueue); + _ = HandleLoads(_cts.Token); } @@ -269,7 +271,5 @@ private async Task FillImageCacheAsync(CanvasControl resourceCreator) _prevBitmap = (ViewModel.PreviousImagePath!, await prevBitmapTask); _currentBitmap = (ViewModel.CurrentImagePath!, await currBitmapTask); _nextBitmap = (ViewModel.NextImagePath!, await nextBitmapTask); - - ViewModel.StartTimer(DispatcherQueue); } } From a85ed3e21fda63128a5c8f45b8d785d15f6ed672 Mon Sep 17 00:00:00 2001 From: Matthew Fraser <1923697+blendermf@users.noreply.github.com> Date: Sat, 18 Oct 2025 13:09:47 -0400 Subject: [PATCH 60/60] Updated SDKs --- QuickDrawWindows/App.xaml.cs | 1 - QuickDrawWindows/QuickDraw.csproj | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/QuickDrawWindows/App.xaml.cs b/QuickDrawWindows/App.xaml.cs index d90443f..bce2156 100644 --- a/QuickDrawWindows/App.xaml.cs +++ b/QuickDrawWindows/App.xaml.cs @@ -32,7 +32,6 @@ public static T GetService() public App() { - // TODO: Move to App SDK 1.8 (currently syncfusion doesn't work with that version) try { string resourceName = "syncfusion.license"; diff --git a/QuickDrawWindows/QuickDraw.csproj b/QuickDrawWindows/QuickDraw.csproj index 42b1265..8a17ada 100644 --- a/QuickDrawWindows/QuickDraw.csproj +++ b/QuickDrawWindows/QuickDraw.csproj @@ -60,12 +60,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - - + + + +