diff --git a/Daybreak.API/Daybreak.API.csproj b/Daybreak.API/Daybreak.API.csproj index 510d0134..50515fa1 100644 --- a/Daybreak.API/Daybreak.API.csproj +++ b/Daybreak.API/Daybreak.API.csproj @@ -18,8 +18,22 @@ false true x86 + + + Default + false + RemoveDuplicateStaticWebAssets;$(ResolveStaticWebAssetsInputsDependsOn) + + + + + + + + IL2104 diff --git a/Daybreak.Shared/Daybreak.Shared.csproj b/Daybreak.Shared/Daybreak.Shared.csproj index 672ec6ad..e4f2ad89 100644 --- a/Daybreak.Shared/Daybreak.Shared.csproj +++ b/Daybreak.Shared/Daybreak.Shared.csproj @@ -7,6 +7,10 @@ preview True AnyCPU;x86 + + + Default + false diff --git a/Daybreak/Daybreak.csproj b/Daybreak/Daybreak.csproj index 89e9a328..7c782d38 100644 --- a/Daybreak/Daybreak.csproj +++ b/Daybreak/Daybreak.csproj @@ -17,8 +17,21 @@ win-x86 true en + + + Default + RemoveDuplicateStaticWebAssets;$(ResolveStaticWebAssetsInputsDependsOn) + + + + + + + + @@ -50,18 +63,13 @@ - - - - - diff --git a/Daybreak/Launch/Launcher.cs b/Daybreak/Launch/Launcher.cs index 8ede222c..c29685a1 100644 --- a/Daybreak/Launch/Launcher.cs +++ b/Daybreak/Launch/Launcher.cs @@ -76,6 +76,11 @@ public sealed class Launcher : BlazorHybridApplication AllocateAnsiConsole(); #endif + // Ensure GPU acceleration is enabled in WebView2 + // This environment variable is read by WebView2 before creating the browser process + Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", + "--enable-gpu --enable-gpu-rasterization --enable-zero-copy --ignore-gpu-blocklist"); + Instance = new Launcher(args); RegisterExtraEncodingProviders(); return LaunchMainWindow(); diff --git a/Daybreak/Services/ExceptionHandling/ExceptionHandler.cs b/Daybreak/Services/ExceptionHandling/ExceptionHandler.cs index 2022326c..72945d93 100644 --- a/Daybreak/Services/ExceptionHandling/ExceptionHandler.cs +++ b/Daybreak/Services/ExceptionHandling/ExceptionHandler.cs @@ -41,9 +41,10 @@ internal sealed class ExceptionHandler( public bool HandleException(Exception e) { + var exceptionDate = DateTime.UtcNow; if (this.logger is null) { - WriteCrashFiles(e); + WriteCrashFiles(e, exceptionDate); return false; } @@ -55,7 +56,7 @@ internal sealed class ExceptionHandler( case HandleResult.Handled: return true; case HandleResult.Fatal: - WriteCrashDump(); + WriteCrashFiles(e, exceptionDate); return false; case HandleResult.Unhandled: default: @@ -136,29 +137,29 @@ internal sealed class ExceptionHandler( return HandleResult.Unhandled; } - private static void WriteCrashFiles(Exception e) + private static void WriteCrashFiles(Exception e, DateTime crashTime) { - WriteCrashLog(e); - WriteCrashDump(); + WriteCrashLog(e, crashTime); + WriteCrashDump(crashTime); } - private static void WriteCrashLog(Exception? e) + private static void WriteCrashLog(Exception? e, DateTime crashTime) { - File.WriteAllText(GetCrashFileName("log"), e?.ToString() ?? "NULL EXCEPTION"); + File.WriteAllText(GetCrashFileName("log", crashTime), e?.ToString() ?? "NULL EXCEPTION"); } - private static void WriteCrashDump() + private static void WriteCrashDump(DateTime crashTime) { - var dumpFilePath = GetCrashFileName("dmp"); + var dumpFilePath = GetCrashFileName("dmp", crashTime); using var fs = new FileStream(dumpFilePath, FileMode.Create, FileAccess.Write); var process = Process.GetCurrentProcess(); NativeMethods.MiniDumpWriteDump(process.Handle, process.Id, fs.SafeFileHandle, NativeMethods.MinidumpType.MiniDumpWithFullMemory, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); } - private static string GetCrashFileName(string extension) + private static string GetCrashFileName(string extension, DateTime crashTime) { var directoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Crashes"); - var path = Path.Combine(directoryPath, $"crash-{DateTime.UtcNow.ToOADate()}.{extension}"); + var path = Path.Combine(directoryPath, $"crash-{crashTime.ToOADate()}.{extension}"); Directory.CreateDirectory(directoryPath); return path; } diff --git a/Daybreak/Services/Notifications/NotificationService.cs b/Daybreak/Services/Notifications/NotificationService.cs index f2107fab..a9b3d230 100644 --- a/Daybreak/Services/Notifications/NotificationService.cs +++ b/Daybreak/Services/Notifications/NotificationService.cs @@ -1,5 +1,6 @@ using Daybreak.Services.Notifications.Handlers; using Daybreak.Services.Notifications.Models; +using Daybreak.Shared; using Daybreak.Shared.Models.Notifications; using Daybreak.Shared.Models.Notifications.Handling; using Daybreak.Shared.Services.Notifications; @@ -11,6 +12,7 @@ using System.Core.Extensions; using System.Extensions; using System.Extensions.Core; using System.Runtime.CompilerServices; +using System.Windows; namespace Daybreak.Services.Notifications; @@ -162,7 +164,20 @@ internal sealed class NotificationService( Level = logLevel, }; - this.EnqueueNotification(notification, persistent); + if (Global.CoreWebView2 is null && logLevel is LogLevel.Error or LogLevel.Critical) + { + // If WebView2 is not initialized, we cannot show UI notifications. + MessageBox.Show( + description, + title, + MessageBoxButton.OK, + MessageBoxImage.Error); + } + else + { + this.EnqueueNotification(notification, persistent); + } + return new NotificationToken(notification); } diff --git a/Daybreak/Views/App.razor.cs b/Daybreak/Views/App.razor.cs index 0d2d22ff..b47c1972 100644 --- a/Daybreak/Views/App.razor.cs +++ b/Daybreak/Views/App.razor.cs @@ -5,6 +5,7 @@ using System.IO; using System.Windows; using System.Windows.Input; using System.Windows.Interop; +using System.Windows.Media; using Daybreak.Services.Logging; using Daybreak.Services.Notifications.Handlers; using Daybreak.Shared; @@ -29,6 +30,14 @@ public sealed class AppViewModel { private const string IssueUrl = "https://github.com/gwdevhub/Daybreak/issues/new"; + // WM_NCHITTEST constants + private const int WM_NCHITTEST = 0x0084; + private const int HTCLIENT = 1; + private const int HTCAPTION = 2; + private const int HTMINBUTTON = 8; + private const int HTMAXBUTTON = 9; + private const int HTCLOSE = 20; + private readonly IOptionsProvider optionsProvider; private readonly IMenuServiceProducer menuServiceProducer; private readonly IMenuServiceButtonHandler menuServiceButtonHandler; @@ -140,9 +149,46 @@ public sealed class AppViewModel this.hwndSource = HwndSource.FromHwnd( new WindowInteropHelper(this.blazorHostWindow).Handle ); + + // Hook WndProc to handle WM_NCHITTEST for custom title bar + this.hwndSource?.AddHook(this.WndProc); + this.isInitialized = true; } + /// + /// Handles Windows messages to ensure the custom title bar area is treated as client area, + /// preventing Windows from intercepting mouse events for native window buttons. + /// + private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + if (msg == WM_NCHITTEST) + { + // Get the mouse position from lParam + var x = (short)(lParam.ToInt32() & 0xFFFF); + var y = (short)((lParam.ToInt32() >> 16) & 0xFFFF); + + // Convert screen coordinates to window coordinates + var point = new System.Windows.Point(x, y); + point = this.blazorHostWindow.PointFromScreen(point); + + // Define the title bar height (should match the CSS title bar height of 40px) + // Account for DPI scaling + var dpi = VisualTreeHelper.GetDpi(this.blazorHostWindow); + var titleBarHeight = 40 * dpi.DpiScaleY; + + // If the mouse is in the title bar area, return HTCLIENT to let WebView2 handle it + // This prevents Windows from returning HTMINBUTTON, HTMAXBUTTON, or HTCLOSE + if (point.Y >= 0 && point.Y < titleBarHeight) + { + handled = true; + return new IntPtr(HTCLIENT); + } + } + + return IntPtr.Zero; + } + public void Drag() { /* diff --git a/Daybreak/Views/Components/NavigationMenu.razor.css b/Daybreak/Views/Components/NavigationMenu.razor.css index 25942a58..c0e5fcce 100644 --- a/Daybreak/Views/Components/NavigationMenu.razor.css +++ b/Daybreak/Views/Components/NavigationMenu.razor.css @@ -8,7 +8,8 @@ overflow: hidden; z-index: 1000; height: 100vh; - transition: width 0.15s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + /* Use transform instead of width for GPU-accelerated animation */ + transition: width 0.15s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.15s ease-out; opacity: 0; /* CSS custom properties for dynamic animation */ --stagger-delay: 0.05s; @@ -16,6 +17,9 @@ /* Custom properties for menu item animations */ --item-stagger-delay: 0.03s; --item-base-delay: 0.1s; + /* Promote to GPU layer */ + will-change: width, opacity; + transform: translateZ(0); } .navigation-panel.open { @@ -37,11 +41,15 @@ overflow-y: auto; box-sizing: border-box; counter-reset: menu-item; + /* Promote to GPU layer for smooth scrolling */ + transform: translateZ(0); } .menu-category { margin-bottom: 5px; counter-increment: menu-item; + /* Promote to GPU layer */ + will-change: transform, opacity; } .category-header { @@ -53,8 +61,10 @@ backdrop-filter: blur(10px); border-radius: 5px; cursor: pointer; - transition: background-color 0.2s; + transition: background-color 0.2s ease-out; border: 1px solid color-mix(in srgb, var(--accent-stroke-active) 60%, transparent); + /* Promote to GPU layer */ + transform: translateZ(0); } .category-header:hover { @@ -86,13 +96,15 @@ padding: 8px 10px; cursor: pointer; border-radius: 3px; - transition: background-color 0.2s; + transition: background-color 0.2s ease-out; margin: 2px 0; /* Increment counter for each menu item */ counter-increment: category-item; /* Initial state for animation */ opacity: 0; transform: translateY(-20px); + /* Promote to GPU layer */ + will-change: transform, opacity; } /* Animate menu items when category is expanded and navigation panel is open */ diff --git a/Daybreak/Views/Components/WindowTitlebar.razor.css b/Daybreak/Views/Components/WindowTitlebar.razor.css index 7dfee91b..9ff95264 100644 --- a/Daybreak/Views/Components/WindowTitlebar.razor.css +++ b/Daybreak/Views/Components/WindowTitlebar.razor.css @@ -21,22 +21,35 @@ bottom: 0; z-index: 0; cursor: default; + /* Ensure hitbox doesn't capture events when buttons are hovered */ + pointer-events: auto; } .title-bar-left { display: flex; align-items: center; position: relative; + z-index: 1; } .title-bar-center { flex: 1; text-align: center; position: relative; + z-index: 1; + /* Allow clicks to pass through to hitbox for dragging */ + pointer-events: none; } .title-bar-right { display: flex; align-items: center; position: relative; + z-index: 1; +} + +/* Ensure buttons receive pointer events */ +.title-bar-left .title-bar-btn, +.title-bar-right .title-bar-btn { + pointer-events: auto; } \ No newline at end of file diff --git a/Daybreak/wwwroot/css/site.css b/Daybreak/wwwroot/css/site.css index 228d196b..375bb9d7 100644 --- a/Daybreak/wwwroot/css/site.css +++ b/Daybreak/wwwroot/css/site.css @@ -20,6 +20,9 @@ html, body { position: relative; backdrop-filter: blur(2px); overflow: hidden; + /* Promote to GPU layer for better performance */ + will-change: contents; + transform: translateZ(0); } .app-container::before { @@ -36,6 +39,9 @@ html, body { background-attachment: fixed; filter: var(--backdrop-image-filter); z-index: -1; + /* Promote to its own GPU layer to prevent repainting */ + will-change: transform; + transform: translateZ(0); } /* Hide resize borders when maximized */ @@ -49,6 +55,8 @@ html, body { border: 1px solid var(--neutral-stroke-rest); backdrop-filter: blur(2px); border-radius: 6px; + /* Promote to GPU layer */ + transform: translateZ(0); } /* Display Box */ @@ -155,12 +163,16 @@ html, body { background: transparent; color: var(--accent-foreground-rest); cursor: pointer; - transition: background-color 0.2s; + /* Use GPU-accelerated properties only */ + transition: background-color 0.15s ease-out, color 0.15s ease-out; display: flex; align-items: center; justify-content: center; font-size: 20px; position: relative; + /* Promote to GPU layer for smooth hover transitions */ + will-change: background-color; + transform: translateZ(0); } .title-bar-btn:hover { diff --git a/Directory.Build.props b/Directory.Build.props index 2b6a059f..e08b4e8c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,7 @@ net10.0 - net10.0-windows + net10.0-windows10.0.17763.0 diff --git a/Directory.Packages.props b/Directory.Packages.props index ee62fa53..c12eb4f8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,63 +3,58 @@ true - - - + + - - - + + + - - - - - - + + + + - - - - - - - - + + + + + + + - - - - + + + - - - + + + - + - - - - + + + + @@ -69,9 +64,9 @@ - + - + \ No newline at end of file