Skip to content
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable disable
using System;
using Android.Graphics.Drawables;
using AndroidX.AppCompat.Widget;
using Microsoft.Maui.Controls.Handlers.Compatibility;
Expand Down Expand Up @@ -26,18 +27,22 @@ public virtual void SetAppearance(AToolbar toolbar, IShellToolbarTracker toolbar
}

var foreground = appearance.ForegroundColor;
var background = appearance.BackgroundColor;
var background = !Brush.IsNullOrEmpty(appearance.Background)
? appearance.Background
: appearance.BackgroundColor is not null
? new SolidColorBrush(appearance.BackgroundColor)
: null;
var titleColor = appearance.TitleColor;

SetColors(toolbar, toolbarTracker, foreground, background, titleColor);
}

public virtual void ResetAppearance(AToolbar toolbar, IShellToolbarTracker toolbarTracker)
{
SetColors(toolbar, toolbarTracker, ShellRenderer.DefaultForegroundColor, ShellRenderer.DefaultBackgroundColor, ShellRenderer.DefaultTitleColor);
SetColors(toolbar, toolbarTracker, ShellRenderer.DefaultForegroundColor, new SolidColorBrush(ShellRenderer.DefaultBackgroundColor), ShellRenderer.DefaultTitleColor);
}

protected virtual void SetColors(AToolbar toolbar, IShellToolbarTracker toolbarTracker, Color foreground, Color background, Color title)
protected virtual void SetColors(AToolbar toolbar, IShellToolbarTracker toolbarTracker, Color foreground, Brush background, Color title)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the protected virtual signature from Color background to Brush background. It is correctly tracked in PublicAPI with a *REMOVED*/added pair and is reasonable for the net11.0 target, but it is binary/source-breaking for subclasses that override the old Color overload: after this change SetAppearance calls the Brush overload, so an existing override of the Color overload silently stops being invoked. Worth confirming during API review that dropping the old overload (vs. keeping it as an [Obsolete] overload that delegates to the new one) is the intended net11 break — this is the kind of decision that pairs with the broader Shell.Background API-design discussion on this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for flagging this! Yes, this is an intentional change for .NET 11 — we've updated the signature from Color to Brush to enable gradient background support. It's properly tracked in PublicAPI.Unshipped.txt with the REMOVED/added pair. For anyone with a custom subclass overriding the old Color overload, the migration is straightforward — just update the parameter type to Brush. Happy to discuss further if there are concerns!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Warning: SetColors signature change is a binary/source-breaking change to a shipped protected virtual API (intentional — confirm API-review sign-off)

This changes the shipped protected virtual void SetColors(…, Color background, …) to take a Brush background. Verified at HEAD: the old Color-based overload is in PublicAPI.Shipped.txt (net-android line 4422), and the PR removes it via *REMOVED* and adds the Brush overload in PublicAPI.Unshipped.txt. External code that subclasses ShellToolbarAppearanceTracker and overrides SetColors will no longer compile, or its override will silently stop being called.

This is intentional and correctly tracked, targets net11.0 (where breaks are allowed), and the author explicitly chose net11.0 for this reason — so it is a heads-up for API-review sign-off, not a code defect. If avoiding the break is preferred, keep the Color overload and add a separate Brush-based virtual that SetAppearance calls, with the legacy overload delegating to it.

(found by: gemini-3.1-pro-preview, claude-opus-4.8)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Public API Surface — This changes the shipped protected virtual SetColors(..., Color background, ...) signature to Brush background, which is a binary/source breaking change for Android custom ShellToolbarAppearanceTracker subclasses overriding the existing method. Keep the shipped Color overload and add a new Brush overload/helper instead of replacing the signature.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed the concern

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Public API Surface / Backward Compatibility — Changing this protected virtual from SetColors(..., Color background, ...) to SetColors(..., Brush background, ...) removes a shipped override point. Existing subclasses overriding the Color signature no longer override anything (and consumers calling it no longer compile). Keep the old Color overload as an obsolete forwarding shim, or explicitly document/justify the breaking change for the release.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Backward Compatibility / Handler MapperSetAppearance and ResetAppearance now dispatch to this new Brush overload, so existing Android Shell custom renderers that override the shipped SetColors(..., Color background, ...) method will silently stop receiving toolbar appearance updates. Keeping the old overload as obsolete does not preserve behavior because it is no longer the virtual method called by the framework. Preserve the old dispatch for solid-color backgrounds (or use a non-virtual core helper so both overloads remain compatible).

{
if (_disposed)
return;
Expand All @@ -48,10 +53,16 @@ protected virtual void SetColors(AToolbar toolbar, IShellToolbarTracker toolbarT
return;

shellToolbar.BarTextColor = title ?? ShellRenderer.DefaultTitleColor;
shellToolbar.BarBackground = new SolidColorBrush(background ?? ShellRenderer.DefaultBackgroundColor);
shellToolbar.BarBackground = background ?? new SolidColorBrush(ShellRenderer.DefaultBackgroundColor);
shellToolbar.IconColor = foreground ?? ShellRenderer.DefaultForegroundColor;
}

[Obsolete("Use SetColors(AToolbar, IShellToolbarTracker, Color, Brush, Color) instead.")]
protected virtual void SetColors(AToolbar toolbar, IShellToolbarTracker toolbarTracker, Color foreground, Color background, Color title)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Backward Compatibility / Handler MapperSetAppearance/ResetAppearance now call the new SetColors(..., Brush, ...) overload exclusively (see line 37/42). Any existing subclass that overrides the previously-shipped protected virtual SetColors(..., Color background, ...) (now obsoleted here) will silently stop being invoked for all toolbar appearance updates, including plain solid-color cases — there is no compile error, just dead code in the consumer's override. This is a real behavioral break for third-party/community Shell renderers. Consider having the Brush-based SetAppearance continue to route solid-color-only appearances through the old virtual (or otherwise explicitly preserve the override contract) instead of bypassing it entirely.

{
SetColors(toolbar, toolbarTracker, foreground, background is not null ? new SolidColorBrush(background) : null, title);
}

#region IDisposable

public void Dispose()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,26 @@ void UpdateiOS13NavigationBarAppearance(UINavigationController controller, Shell
navBar.TintColor = _defaultTint;
}

// Set BackgroundColor
var background = appearance.BackgroundColor;

if (background != null)
navigationBarAppearance.BackgroundColor = background.ToPlatform();
// Set Background (prefer Brush over Color for gradient support)
Comment thread
sheiksyedm marked this conversation as resolved.
if (!Brush.IsNullOrEmpty(appearance.Background))
Comment thread
sheiksyedm marked this conversation as resolved.
Comment thread
sheiksyedm marked this conversation as resolved.
Comment thread
sheiksyedm marked this conversation as resolved.
Comment thread
sheiksyedm marked this conversation as resolved.
{
if (appearance.Background is SolidColorBrush solidBrush && solidBrush.Color is not null)
{
navigationBarAppearance.BackgroundColor = solidBrush.Color.ToPlatform();
}
else
{
var backgroundImage = navBar.GetBackgroundImage(appearance.Background);
Comment thread
sheiksyedm marked this conversation as resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iOS Shell rendering — The gradient image is rendered once from the nav bar's current bounds, but UpdateLayout() is still empty. If SetAppearance runs before the nav bar has non-zero bounds, GetBackgroundImage returns null; if the bar width changes after rotation/resize/large-title transitions, the old image is stale. Cache the current appearance/brush and regenerate the UINavigationBarAppearance.BackgroundImage from UpdateLayout() when bounds change.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] iOS/MacCatalyst Platform SpecificsnavBar.GetBackgroundImage(appearance.Background) bakes a UIImage from the current navBar.Bounds, but ShellNavBarAppearanceTracker.UpdateLayout is still empty even though Shell renderers call it from ViewDidLayoutSubviews. After rotation or any nav-bar size change, the old portrait/landscape-sized image remains installed, so gradients can clip or scale incorrectly. Cache the current ShellAppearance and regenerate the background image from UpdateLayout when the brush is non-empty.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] iOS Layout / Navigation & Shell — The gradient is rendered once from navBar.Bounds into a static BackgroundImage, but UpdateLayout is empty and nothing regenerates the image when the navigation bar size changes. A real scenario is device rotation, split-view resizing, or initial appearance before the nav bar has non-zero bounds: the bitmap is missing, stretched, or clipped until another appearance update happens. Rebuild the background image on layout/size changes (or use a layer-backed background that follows bounds).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Layout Timing / Gradient RenderingGetBackgroundImage rasterizes the gradient using navBar.Bounds (via GetBackgroundLayer's Frame = control.Bounds) at the moment SetAppearance runs. Unlike the existing gradient-brush handling in NavigationRenderer.cs (which re-renders the background image from ViewWillLayoutSubviews/RefreshBarBackground and subscribes to InvalidateGradientBrushRequested), this code path only computes the image once, with no re-layout hook. If navBar.Bounds is zero-sized when this first runs (e.g., before the nav controller's first layout pass) GetBackgroundImage returns null and the gradient silently never appears; it also won't be regenerated to match new bounds after device rotation or size class changes, unlike the equivalent NavigationPage code.

if (backgroundImage is not null)
{
navigationBarAppearance.BackgroundImage = backgroundImage;
}
}
}
else if (appearance.BackgroundColor is not null)
{
navigationBarAppearance.BackgroundColor = appearance.BackgroundColor.ToPlatform();
}

// Set TitleColor
var titleColor = appearance.TitleColor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,9 @@ public static void UpdateBarBackground(this AToolbar nativeToolbar, Toolbar tool
}
else
{
nativeToolbar.BackgroundTintMode = null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Android Toolbar Rendering — This else branch correctly resets BackgroundTintList/BackgroundTintMode before applying a non-solid brush, but the sibling SolidColorBrush branch above (unchanged by this PR) never calls nativeToolbar.UpdateBackground(...) to clear a previously-set GradientStrokeDrawable. Since Toolbar.BarBackground can now flip between a GradientBrush (via the new Shell.Background) and a SolidColorBrush (e.g. navigating from a page with a gradient Shell background to one without), navigating to the solid-color page leaves the old gradient drawable as nativeToolbar.Background with only a tint applied on top — a real, newly-reachable regression enabled by adding gradient support here. The SolidColorBrush branch should also clear any previously-set gradient background drawable.

nativeToolbar.BackgroundTintList = null;
nativeToolbar.UpdateBackground(barBackground);

if (Brush.IsNullOrEmpty(barBackground))
nativeToolbar.BackgroundTintMode = null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ virtual Microsoft.Maui.Controls.LongPressingEventArgs.GetPosition(Microsoft.Maui
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextProperty -> Microsoft.Maui.Controls.BindableProperty
~virtual Microsoft.Maui.Controls.Platform.Compatibility.ShellItemRenderer.UpdateShellSectionBadge(Microsoft.Maui.Controls.ShellSection shellSection, int index) -> void
~Microsoft.Maui.Controls.ShellAppearance.Background.get -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.GetBackground(Microsoft.Maui.Controls.BindableObject obj) -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.SetBackground(Microsoft.Maui.Controls.BindableObject obj, Microsoft.Maui.Controls.Brush value) -> void
~static readonly Microsoft.Maui.Controls.Shell.BackgroundProperty -> Microsoft.Maui.Controls.BindableProperty
~virtual Microsoft.Maui.Controls.Platform.Compatibility.ShellToolbarAppearanceTracker.SetColors(AndroidX.AppCompat.Widget.Toolbar toolbar, Microsoft.Maui.Controls.Platform.Compatibility.IShellToolbarTracker toolbarTracker, Microsoft.Maui.Graphics.Color foreground, Microsoft.Maui.Controls.Brush background, Microsoft.Maui.Graphics.Color title) -> void
Microsoft.Maui.Controls.TitleBar.TitleFontAttributes.get -> Microsoft.Maui.Controls.FontAttributes
Microsoft.Maui.Controls.TitleBar.TitleFontAttributes.set -> void
static readonly Microsoft.Maui.Controls.TitleBar.TitleFontAttributesProperty -> Microsoft.Maui.Controls.BindableProperty!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ virtual Microsoft.Maui.Controls.LongPressingEventArgs.GetPosition(Microsoft.Maui
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextProperty -> Microsoft.Maui.Controls.BindableProperty
~Microsoft.Maui.Controls.ShellAppearance.Background.get -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.GetBackground(Microsoft.Maui.Controls.BindableObject obj) -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.SetBackground(Microsoft.Maui.Controls.BindableObject obj, Microsoft.Maui.Controls.Brush value) -> void
~static readonly Microsoft.Maui.Controls.Shell.BackgroundProperty -> Microsoft.Maui.Controls.BindableProperty
Microsoft.Maui.Controls.TitleBar.TitleFontAttributes.get -> Microsoft.Maui.Controls.FontAttributes
Microsoft.Maui.Controls.TitleBar.TitleFontAttributes.set -> void
static readonly Microsoft.Maui.Controls.TitleBar.TitleFontAttributesProperty -> Microsoft.Maui.Controls.BindableProperty!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ virtual Microsoft.Maui.Controls.LongPressingEventArgs.GetPosition(Microsoft.Maui
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextProperty -> Microsoft.Maui.Controls.BindableProperty
~Microsoft.Maui.Controls.ShellAppearance.Background.get -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.GetBackground(Microsoft.Maui.Controls.BindableObject obj) -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.SetBackground(Microsoft.Maui.Controls.BindableObject obj, Microsoft.Maui.Controls.Brush value) -> void
~static readonly Microsoft.Maui.Controls.Shell.BackgroundProperty -> Microsoft.Maui.Controls.BindableProperty
Microsoft.Maui.Controls.TitleBar.TitleFontAttributes.get -> Microsoft.Maui.Controls.FontAttributes
Microsoft.Maui.Controls.TitleBar.TitleFontAttributes.set -> void
static readonly Microsoft.Maui.Controls.TitleBar.TitleFontAttributesProperty -> Microsoft.Maui.Controls.BindableProperty!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ virtual Microsoft.Maui.Controls.LongPressingEventArgs.GetPosition(Microsoft.Maui
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextProperty -> Microsoft.Maui.Controls.BindableProperty
~Microsoft.Maui.Controls.ShellAppearance.Background.get -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.GetBackground(Microsoft.Maui.Controls.BindableObject obj) -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.SetBackground(Microsoft.Maui.Controls.BindableObject obj, Microsoft.Maui.Controls.Brush value) -> void
~static readonly Microsoft.Maui.Controls.Shell.BackgroundProperty -> Microsoft.Maui.Controls.BindableProperty
override Microsoft.Maui.Controls.BindablePropertyConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type! sourceType) -> bool
override Microsoft.Maui.Controls.BindablePropertyConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
override Microsoft.Maui.Controls.BindablePropertyConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ virtual Microsoft.Maui.Controls.Handlers.Items2.ItemsViewHandler2<TItemsView>.Up
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextProperty -> Microsoft.Maui.Controls.BindableProperty
~Microsoft.Maui.Controls.ShellAppearance.Background.get -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.GetBackground(Microsoft.Maui.Controls.BindableObject obj) -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.SetBackground(Microsoft.Maui.Controls.BindableObject obj, Microsoft.Maui.Controls.Brush value) -> void
~static readonly Microsoft.Maui.Controls.Shell.BackgroundProperty -> Microsoft.Maui.Controls.BindableProperty
override Microsoft.Maui.Controls.BindablePropertyConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type! sourceType) -> bool
override Microsoft.Maui.Controls.BindablePropertyConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
override Microsoft.Maui.Controls.BindablePropertyConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object?
Expand Down
6 changes: 5 additions & 1 deletion src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[moderate] Build & Public API — This file was rewritten with a UTF-8 BOM (\uFEFF) even though the PR only needs to add API entries. PublicAPI baselines are text-compared and the BOM changes the first token (#nullable) without an API change; the same encoding-only change appears in netstandard/PublicAPI.Unshipped.txt. Please save these baselines without BOM to avoid spurious API diffs/tooling failures.

*REMOVED*~static Microsoft.Maui.Controls.VisualStateManager.GetVisualStateGroups(Microsoft.Maui.Controls.VisualElement visualElement) -> System.Collections.Generic.IList<Microsoft.Maui.Controls.VisualStateGroup>
Microsoft.Maui.Controls.Window.StatusBarTheme.get -> Microsoft.Maui.StatusBarTheme
Microsoft.Maui.Controls.Window.StatusBarTheme.set -> void
static readonly Microsoft.Maui.Controls.Window.StatusBarThemeProperty -> Microsoft.Maui.Controls.BindableProperty!
Expand Down Expand Up @@ -115,7 +116,6 @@ static Microsoft.Maui.Controls.ViewExtensions.ScaleXToAsync(this Microsoft.Maui.
static Microsoft.Maui.Controls.ViewExtensions.ScaleYToAsync(this Microsoft.Maui.Controls.VisualElement! view, double scale, uint length, Microsoft.Maui.Easing? easing, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<bool>!
static Microsoft.Maui.Controls.ViewExtensions.TranslateToAsync(this Microsoft.Maui.Controls.VisualElement! view, double x, double y, uint length, Microsoft.Maui.Easing? easing, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<bool>!
~static Microsoft.Maui.Controls.VisualStateManager.GetVisualStateGroups(Microsoft.Maui.Controls.VisualElement visualElement) -> Microsoft.Maui.Controls.VisualStateGroupList
*REMOVED*~static Microsoft.Maui.Controls.VisualStateManager.GetVisualStateGroups(Microsoft.Maui.Controls.VisualElement visualElement) -> System.Collections.Generic.IList<Microsoft.Maui.Controls.VisualStateGroup>
~static Microsoft.Maui.Controls.VisualStateManager.InvalidateVisualStates(Microsoft.Maui.Controls.VisualElement visualElement) -> void
static Microsoft.Maui.Controls.Xaml.Diagnostics.HotReloadDiagnostics.CurrentVersion.get -> int
static Microsoft.Maui.Controls.Xaml.Diagnostics.HotReloadDiagnostics.UpdateApplied -> System.EventHandler<Microsoft.Maui.Controls.Xaml.Diagnostics.HotReloadAppliedEventArgs!>?
Expand All @@ -140,6 +140,10 @@ static readonly Microsoft.Maui.Controls.TitleBar.TitleFontAttributesProperty ->
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextProperty -> Microsoft.Maui.Controls.BindableProperty
~Microsoft.Maui.Controls.ShellAppearance.Background.get -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.GetBackground(Microsoft.Maui.Controls.BindableObject obj) -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.SetBackground(Microsoft.Maui.Controls.BindableObject obj, Microsoft.Maui.Controls.Brush value) -> void
~static readonly Microsoft.Maui.Controls.Shell.BackgroundProperty -> Microsoft.Maui.Controls.BindableProperty
override Microsoft.Maui.Controls.BindablePropertyConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type! sourceType) -> bool
override Microsoft.Maui.Controls.BindablePropertyConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
override Microsoft.Maui.Controls.BindablePropertyConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ static readonly Microsoft.Maui.Controls.TitleBar.TitleFontAttributesProperty ->
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextColorProperty -> Microsoft.Maui.Controls.BindableProperty
~static readonly Microsoft.Maui.Controls.ToolbarItem.BadgeTextProperty -> Microsoft.Maui.Controls.BindableProperty
~Microsoft.Maui.Controls.ShellAppearance.Background.get -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.GetBackground(Microsoft.Maui.Controls.BindableObject obj) -> Microsoft.Maui.Controls.Brush
~static Microsoft.Maui.Controls.Shell.SetBackground(Microsoft.Maui.Controls.BindableObject obj, Microsoft.Maui.Controls.Brush value) -> void
~static readonly Microsoft.Maui.Controls.Shell.BackgroundProperty -> Microsoft.Maui.Controls.BindableProperty
override Microsoft.Maui.Controls.BindablePropertyConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type! sourceType) -> bool
override Microsoft.Maui.Controls.BindablePropertyConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
override Microsoft.Maui.Controls.BindablePropertyConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object?
Expand Down
30 changes: 29 additions & 1 deletion src/Controls/src/Core/Shell/Shell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,13 @@ static void OnFlyoutBehaviorChanged(BindableObject bindable, object oldValue, ob
BindableProperty.CreateAttached("UnselectedColor", typeof(Color), typeof(Shell), null,
propertyChanged: OnShellAppearanceValueChanged);

/// <summary>
/// Defines the background brush for the Shell toolbar. Supports gradient brushes.
/// </summary>
public static readonly new BindableProperty BackgroundProperty =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Warning: new Shell.BackgroundProperty shadows the inherited VisualElement.BackgroundProperty — two same-named properties with different semantics (API design — needs review)

Shell.cs:535 declares public static readonly new BindableProperty BackgroundProperty as an attached Brush property, while Shell still inherits the instance VisualElement.Background (VisualElement.cs:289/:569, also a Brush named "Background"). Only the new attached property is ingested into ShellAppearance (ShellAppearance.cs s_ingestBrushArray).

Concrete footgun verified at HEAD: Shell.SetBackground(shell, brush) and XAML Background="…" write the attached property → reaches the toolbar; shell.Background = brush in code-behind writes the inherited VisualElement property → does not reach the toolbar (it paints the Shell body). Same name, two storages, two behaviors.

This is the dominant unresolved item on the PR (already raised in the PR discussion and a prior net11 fleet-review). It is an API-design decision that should go through API review before shipping, even on net11.0. Consider honoring/reusing VisualElement.Background, or naming the new member something toolbar-specific to remove the overlap.

(found by: gpt-5.5, claude-opus-4.6, claude-opus-4.8)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This follows the existing Shell pattern — Shell.BackgroundColorProperty (line 467) also uses public static readonly new to shadow VisualElement.BackgroundColorProperty. The same attached-vs-instance split already exists for BackgroundColor. This is established Shell architecture, not new to this PR.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Public API Surface — This adds public Shell.Background API, but src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt was not updated while the other TFMs were. API validation for the Tizen target will fail and the public surface will be inconsistent. Add the new Shell.BackgroundProperty, GetBackground, SetBackground, and ShellAppearance.Background entries to the Tizen PublicAPI file as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed the concern

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public API baselines — Adding Shell.BackgroundProperty introduces public API for every Controls target, but the PR updates no net-tizen/PublicAPI.Unshipped.txt entries. Tizen API validation will miss/fail this new Shell.Background surface. Please add the same ShellAppearance.Background, Shell.GetBackground, Shell.SetBackground, and Shell.BackgroundProperty entries to the Tizen unshipped baseline.

BindableProperty.CreateAttached("Background", typeof(Brush), typeof(Shell), Brush.Default,
propertyChanged: OnShellAppearanceValueChanged);
Comment on lines +482 to +487

/// <summary>
/// The backdrop of the flyout, which is the appearance of the flyout overlay.
/// </summary>
Expand Down Expand Up @@ -663,6 +670,20 @@ static void OnFlyoutBehaviorChanged(BindableObject bindable, object oldValue, ob
/// <param name="value">The brushed used in the backdrop of the flyout.</param>
public static void SetFlyoutBackdrop(BindableObject obj, Brush value) => obj.SetValue(FlyoutBackdropProperty, value);

/// <summary>
/// Gets the background brush for the Shell toolbar.
/// </summary>
/// <param name="obj">The object from which to get the background brush.</param>
/// <returns>The background brush for the Shell toolbar.</returns>
public static Brush GetBackground(BindableObject obj) => (Brush)obj.GetValue(BackgroundProperty);

/// <summary>
/// Sets the background brush for the Shell toolbar.
/// </summary>
/// <param name="obj">The object on which to set the background brush.</param>
/// <param name="value">The brush to use as the Shell toolbar background.</param>
public static void SetBackground(BindableObject obj, Brush value) => obj.SetValue(BackgroundProperty, value);

static void OnShellAppearanceValueChanged(BindableObject bindable, object oldValue, object newValue)
{
var item = (Element)bindable;
Expand Down Expand Up @@ -766,7 +787,14 @@ void UpdateToolbarAppearanceFeatures(Element pivot, ShellAppearance appearance)
{
appearance = appearance ?? GetAppearanceForPivot(pivot);
Toolbar.BarTextColor = appearance?.TitleColor ?? DefaultTitleColor;
Toolbar.BarBackground = appearance?.BackgroundColor ?? DefaultBackgroundColor;
if (!Brush.IsNullOrEmpty(appearance?.Background))
{
Toolbar.BarBackground = appearance.Background;
}
else
{
Toolbar.BarBackground = appearance?.BackgroundColor ?? DefaultBackgroundColor;
}
Toolbar.IconColor = appearance?.ForegroundColor ?? DefaultForegroundColor;
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/Controls/src/Core/Shell/ShellAppearance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public class ShellAppearance : IShellAppearanceElement

static readonly BindableProperty[] s_ingestBrushArray = new[]
{
Shell.FlyoutBackdropProperty
Shell.FlyoutBackdropProperty,
Shell.BackgroundProperty
};

static readonly BindableProperty[] s_ingestDoubleArray = new[]
Expand Down Expand Up @@ -71,6 +72,9 @@ public class ShellAppearance : IShellAppearanceElement

/// <summary>Gets the backdrop brush for the Shell flyout.</summary>
public Brush FlyoutBackdrop => _brushArray[0];

/// <summary>Gets the background brush of the Shell.</summary>
public Brush Background => _brushArray[1];
public double FlyoutWidth => _doubleArray[0];
public double FlyoutHeight => _doubleArray[1];

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue10445.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 10445, "Shell.Background does not support gradient brushes", PlatformAffected.All)]
public class Issue10445 : TestShell
{
protected override void Init()
{
FlyoutBehavior = FlyoutBehavior.Disabled;
var gradientBrush = new LinearGradientBrush
{
StartPoint = new Point(0, 0),
EndPoint = new Point(1, 1),
GradientStops = new GradientStopCollection
{
new GradientStop(Colors.Yellow, 0.0f),
new GradientStop(Colors.Green, 1.0f)
}
};

Shell.SetBackground(this, gradientBrush);

var page = new ContentPage
{
Title = "Gradient Shell",
Content = new VerticalStackLayout
{
Padding = 20,
Spacing = 10,
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
Children =
{
new Label
{
Text = "Shell.Background should display a gradient (Yellow to Green) in the navigation bar above.",
AutomationId = "GradientInfoLabel",
HorizontalTextAlignment = TextAlignment.Center
}
}
}
};

AddContentPage(page, "Home");
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading