diff --git a/native/WinToastWrapper/WinToastWrapper.cpp b/native/WinToastWrapper/WinToastWrapper.cpp index dcf0097..a82b04b 100644 --- a/native/WinToastWrapper/WinToastWrapper.cpp +++ b/native/WinToastWrapper/WinToastWrapper.cpp @@ -237,8 +237,9 @@ NOTIFYAPI INT64 WNT_ShowToast( if (!descriptor || !handler) return static_cast(WinToast::WinToastError::InvalidParameters); - bool hasBody = descriptor->body != nullptr && descriptor->body[0] != L'\0'; - bool hasImage = descriptor->imagePath != nullptr && descriptor->imagePath[0] != L'\0'; + bool hasBody = descriptor->body != nullptr && descriptor->body[0] != L'\0'; + bool hasImage = descriptor->imagePath != nullptr && descriptor->imagePath[0] != L'\0'; + bool hasHeroImage = descriptor->heroImagePath != nullptr && descriptor->heroImagePath[0] != L'\0'; WinToastTemplate tmpl(SelectTemplateType(hasImage, hasBody)); @@ -249,6 +250,9 @@ NOTIFYAPI INT64 WNT_ShowToast( if (hasImage) tmpl.setImagePath(descriptor->imagePath); + if (hasHeroImage) + tmpl.setHeroImagePath(descriptor->heroImagePath); + for (int i = 0; i < descriptor->buttonCount; ++i) { if (descriptor->buttonLabels && descriptor->buttonLabels[i]) diff --git a/native/WinToastWrapper/WinToastWrapper.h b/native/WinToastWrapper/WinToastWrapper.h index 034d77c..3e58c32 100644 --- a/native/WinToastWrapper/WinToastWrapper.h +++ b/native/WinToastWrapper/WinToastWrapper.h @@ -70,7 +70,8 @@ typedef enum _WNT_AudioOption { typedef struct _WNT_ToastDescriptor { const wchar_t* title; /* required */ const wchar_t* body; /* nullable */ - const wchar_t* imagePath; /* nullable — absolute path to an image file */ + const wchar_t* imagePath; /* nullable — absolute path; displayed as a square thumbnail */ + const wchar_t* heroImagePath; /* nullable — absolute path; displayed full-width, aspect ratio preserved */ const wchar_t** buttonLabels; /* nullable — array of buttonCount wchar_t* */ int buttonCount; long long expirationMs; /* 0 = platform default */ diff --git a/src/Notify.NET/Abstractions/NotificationRequest.cs b/src/Notify.NET/Abstractions/NotificationRequest.cs index 9cce3fa..7db9303 100644 --- a/src/Notify.NET/Abstractions/NotificationRequest.cs +++ b/src/Notify.NET/Abstractions/NotificationRequest.cs @@ -16,9 +16,15 @@ namespace Notify.NET.Abstractions /// Optional body text shown beneath the title. public string? Body { get; } - /// Absolute path to an image file to display in the notification. + /// Absolute path to an image file displayed as a square thumbnail. public string? ImagePath { get; } + /// + /// Absolute path to an image file displayed full-width above the title, preserving aspect ratio. + /// Windows only — ignored on Linux and macOS. + /// + public string? HeroImagePath { get; } + /// Action buttons to display. Maximum platform limits apply (typically 5 on Windows, varies on Linux). public IReadOnlyList Buttons { get; } @@ -38,6 +44,7 @@ namespace Notify.NET.Abstractions string title, string? body, string? imagePath, + string? heroImagePath, IReadOnlyList buttons, INotificationHandler? handler, TimeSpan? expiration, @@ -50,6 +57,7 @@ namespace Notify.NET.Abstractions Title = title; Body = body; ImagePath = imagePath; + HeroImagePath = heroImagePath; Buttons = buttons; Handler = handler; Expiration = expiration; diff --git a/src/Notify.NET/Builder/NotificationBuilder.cs b/src/Notify.NET/Builder/NotificationBuilder.cs index 6117fc6..75b964d 100644 --- a/src/Notify.NET/Builder/NotificationBuilder.cs +++ b/src/Notify.NET/Builder/NotificationBuilder.cs @@ -26,6 +26,7 @@ namespace Notify.NET.Builder private string _title = string.Empty; private string? _body; private string? _imagePath; + private string? _heroImagePath; private readonly List _buttons = new List(); private INotificationHandler? _handler; private TimeSpan? _expiration; @@ -58,13 +59,23 @@ namespace Notify.NET.Builder return this; } - /// Sets the absolute path of an image to display in the notification. + /// Sets the absolute path of an image to display as a square thumbnail. public NotificationBuilder WithImage(string imagePath) { _imagePath = imagePath; return this; } + /// + /// Sets the absolute path of an image to display full-width above the notification title, + /// preserving the image's aspect ratio. Windows only — ignored on Linux and macOS. + /// + public NotificationBuilder WithHeroImage(string imagePath) + { + _heroImagePath = imagePath; + return this; + } + /// Adds an action button with an optional click callback. /// Text shown on the button. /// Called with the notification ID when the button is clicked. @@ -166,6 +177,7 @@ namespace Notify.NET.Builder title: _title, body: _body, imagePath: _imagePath, + heroImagePath: _heroImagePath, buttons: _buttons.AsReadOnly(), handler: handler, expiration: _expiration, diff --git a/src/Notify.NET/Platform/Windows/WinToastNative.cs b/src/Notify.NET/Platform/Windows/WinToastNative.cs index e78be98..c28d3df 100644 --- a/src/Notify.NET/Platform/Windows/WinToastNative.cs +++ b/src/Notify.NET/Platform/Windows/WinToastNative.cs @@ -45,7 +45,8 @@ namespace Notify.NET.Platform.Windows { public IntPtr title; // wchar_t* public IntPtr body; // wchar_t* (may be IntPtr.Zero) - public IntPtr imagePath; // wchar_t* (may be IntPtr.Zero) + public IntPtr imagePath; // wchar_t* (may be IntPtr.Zero) — square thumbnail + public IntPtr heroImagePath; // wchar_t* (may be IntPtr.Zero) — full-width, aspect ratio preserved public IntPtr buttonLabels; // wchar_t** (array of pointers, may be IntPtr.Zero) public int buttonCount; public long expirationMs; // 0 = platform default diff --git a/src/Notify.NET/Platform/Windows/WindowsNotificationService.cs b/src/Notify.NET/Platform/Windows/WindowsNotificationService.cs index a3e3a5d..a16a321 100644 --- a/src/Notify.NET/Platform/Windows/WindowsNotificationService.cs +++ b/src/Notify.NET/Platform/Windows/WindowsNotificationService.cs @@ -214,9 +214,10 @@ namespace Notify.NET.Platform.Windows // Pin managed strings as unmanaged UTF-16 memory for the duration of the call. // button label pointers are pinned in the IntPtr[] and that array is pinned too. - using var titlePin = new PinnedString(request.Title); - using var bodyPin = new PinnedString(request.Body); - using var imagePin = new PinnedString(ResolveImagePath(request.ImagePath)); + using var titlePin = new PinnedString(request.Title); + using var bodyPin = new PinnedString(request.Body); + using var imagePin = new PinnedString(ResolveImagePath(request.ImagePath)); + using var heroImagePin = new PinnedString(ResolveImagePath(request.HeroImagePath)); // Build array of pinned button label pointers. var buttonPins = new PinnedString[request.Buttons.Count]; @@ -244,6 +245,7 @@ namespace Notify.NET.Platform.Windows title = titlePin.Pointer, body = bodyPin.Pointer, imagePath = imagePin.Pointer, + heroImagePath = heroImagePin.Pointer, buttonLabels = buttonArrayPtr, buttonCount = request.Buttons.Count, expirationMs = request.Expiration.HasValue ? (long)request.Expiration.Value.TotalMilliseconds : 0L,