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