diff --git a/LinuxApplication.cs b/LinuxApplication.cs
index 152caf2..a0867a9 100644
--- a/LinuxApplication.cs
+++ b/LinuxApplication.cs
@@ -761,12 +761,17 @@ public class LinuxApplication : IDisposable
if (view is SkiaNavigationPage navPage && navPage.CurrentPage != null)
{
RefreshViewTheme(navPage.CurrentPage);
+ navPage.Invalidate(); // Force redraw of navigation page
}
// Special handling for ContentPage - it stores content in Content property
- if (view is SkiaPage page && page.Content != null)
+ if (view is SkiaPage page)
{
- RefreshViewTheme(page.Content);
+ page.Invalidate(); // Force redraw to pick up theme-aware background
+ if (page.Content != null)
+ {
+ RefreshViewTheme(page.Content);
+ }
}
// Recursively process children
diff --git a/Views/SkiaButton.cs b/Views/SkiaButton.cs
index 8bdbfdb..058f41a 100644
--- a/Views/SkiaButton.cs
+++ b/Views/SkiaButton.cs
@@ -558,7 +558,7 @@ public class SkiaButton : SkiaView, IButtonController
// This ensures buttons are visible even without explicit styling
if (!hasExplicitBackground)
{
- bgColor = SkiaTheme.Gray200SK; // Default button background
+ bgColor = SkiaTheme.ButtonBackgroundSK; // Theme-aware default button background
}
bool hasBackground = hasExplicitBackground ? bgColor.Alpha > 0 : true;
@@ -688,8 +688,8 @@ public class SkiaButton : SkiaView, IButtonController
}
else
{
- // Default button (gray background) - use dark text for contrast
- textColor = SkiaTheme.Gray800SK;
+ // Default button - use theme-appropriate text color for contrast
+ textColor = SkiaTheme.CurrentTextSK;
}
if (!IsEnabled)
diff --git a/Views/SkiaCheckBox.cs b/Views/SkiaCheckBox.cs
index d2b27fc..2ce08fc 100644
--- a/Views/SkiaCheckBox.cs
+++ b/Views/SkiaCheckBox.cs
@@ -289,7 +289,10 @@ public class SkiaCheckBox : SkiaView
// Get colors as SKColor
var colorSK = ToSKColor(Color);
var checkColorSK = ToSKColor(CheckColor);
- var uncheckedBoxColorSK = ToSKColor(UncheckedBoxColor);
+ // Use theme-aware color for unchecked box if default white
+ var uncheckedBoxColorSK = UncheckedBoxColor == Colors.White
+ ? SkiaTheme.CurrentSurfaceSK
+ : ToSKColor(UncheckedBoxColor);
var borderColorSK = ToSKColor(BorderColor);
var disabledColorSK = ToSKColor(DisabledColor);
var hoveredBorderColorSK = ToSKColor(HoveredBorderColor);
diff --git a/Views/SkiaCollectionView.cs b/Views/SkiaCollectionView.cs
index d60a39b..4219918 100644
--- a/Views/SkiaCollectionView.cs
+++ b/Views/SkiaCollectionView.cs
@@ -429,14 +429,19 @@ public class SkiaCollectionView : SkiaItemsView
rawHeight = ItemHeight;
}
- var measuredHeight = Math.Max(rawHeight, ItemHeight);
- if (!_itemHeights.TryGetValue(index, out var cachedHeight) || Math.Abs(cachedHeight - measuredHeight) > 1f)
+ // Store the actual measured height for row sizing
+ var cellHeight = Math.Max(rawHeight, ItemHeight);
+ if (!_itemHeights.TryGetValue(index, out var cachedHeight) || Math.Abs(cachedHeight - cellHeight) > 1f)
{
- _itemHeights[index] = measuredHeight;
+ _itemHeights[index] = cellHeight;
_heightsChangedDuringDraw = true;
}
- var actualBounds = new SKRect(bounds.Left, bounds.Top, bounds.Right, bounds.Top + measuredHeight);
+ // Vertically center the content within the cell bounds
+ // Use rawHeight (actual content height) for centering, not cellHeight
+ var contentHeight = Math.Min(rawHeight, bounds.Height);
+ var verticalOffset = Math.Max(0, (bounds.Height - contentHeight) / 2);
+ var actualBounds = new SKRect(bounds.Left, bounds.Top + verticalOffset, bounds.Right, bounds.Top + verticalOffset + contentHeight);
itemView.Arrange(new Rect(actualBounds.Left, actualBounds.Top, actualBounds.Width, actualBounds.Height));
itemView.Draw(canvas);
diff --git a/Views/SkiaItemsView.cs b/Views/SkiaItemsView.cs
index c17d0b3..172d5a4 100644
--- a/Views/SkiaItemsView.cs
+++ b/Views/SkiaItemsView.cs
@@ -149,10 +149,12 @@ public class SkiaItemsView : SkiaView
///
/// Gets the height for a specific item, using cached height or default.
+ /// Always returns at least ItemHeight to allow vertical centering of smaller content.
///
protected float GetItemHeight(int index)
{
- return _itemHeights.TryGetValue(index, out var height) ? height : _itemHeight;
+ var cached = _itemHeights.TryGetValue(index, out var height) ? height : _itemHeight;
+ return Math.Max(cached, _itemHeight);
}
///
diff --git a/Views/SkiaLabel.cs b/Views/SkiaLabel.cs
index ceb6c70..592a16e 100644
--- a/Views/SkiaLabel.cs
+++ b/Views/SkiaLabel.cs
@@ -711,10 +711,6 @@ public class SkiaLabel : SkiaView
if (needsMultiLine)
{
- var textBoundsDbg = new SKRect();
- paint.MeasureText(displayText, ref textBoundsDbg);
- if (displayText.StartsWith("Full XAML") || displayText.StartsWith("Shell nav"))
- Console.WriteLine($"[Label OnDraw] '{displayText.Substring(0, Math.Min(15, displayText.Length))}' textW={textBoundsDbg.Width:F0} boundsW={contentBounds.Width:F0} LineBreakMode={LineBreakMode}");
DrawMultiLineText(canvas, paint, font, contentBounds, displayText);
}
else
@@ -1126,6 +1122,15 @@ public class SkiaLabel : SkiaView
continue;
}
+ // Check if the entire paragraph fits on one line - no need to wrap
+ // Use small tolerance to account for floating point precision
+ float paragraphWidth = paint.MeasureText(paragraph);
+ if (paragraphWidth <= maxWidth + 1.0f)
+ {
+ lines.Add(paragraph);
+ continue;
+ }
+
if (LineBreakMode == LineBreakMode.CharacterWrap)
{
WrapByCharacter(paragraph, paint, maxWidth, lines);
@@ -1236,9 +1241,11 @@ public class SkiaLabel : SkiaView
}
else
{
+ // Use advance width (paint.MeasureText return value) not bounding box width
+ // This must match what WrapText uses for consistency
var textBounds = new SKRect();
paint.MeasureText(displayText, ref textBounds);
- width = textBounds.Width;
+ width = paint.MeasureText(displayText); // Advance width, not textBounds.Width
height = textBounds.Height;
// Account for character spacing
diff --git a/Views/SkiaLayoutView.cs b/Views/SkiaLayoutView.cs
index 96d386c..6634eb0 100644
--- a/Views/SkiaLayoutView.cs
+++ b/Views/SkiaLayoutView.cs
@@ -454,8 +454,6 @@ public class SkiaStackLayout : SkiaLayoutView
// Horizontal stack: give each child its measured width
// Don't constrain - let content overflow if needed (parent clips)
var useWidth = childWidth;
- if (child is SkiaLabel lbl)
- Console.WriteLine($"[HStack Arrange] Label '{lbl.Text?.Substring(0, Math.Min(15, lbl.Text?.Length ?? 0))}' childWidth={childWidth:F0} contentWidth={contentWidth:F0} offset={offset:F0}");
// Respect child's VerticalOptions for horizontal layouts
var useHeight = Math.Min(childHeight, contentHeight);
diff --git a/Views/SkiaPage.cs b/Views/SkiaPage.cs
index 22b8f14..989bb74 100644
--- a/Views/SkiaPage.cs
+++ b/Views/SkiaPage.cs
@@ -141,16 +141,24 @@ public class SkiaPage : SkiaView
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
{
- // Draw background color
+ // Draw background color - use theme-aware default if not set
+ SKColor bgColor;
if (BackgroundColor != null && BackgroundColor != Colors.Transparent)
{
- using var bgPaint = new SKPaint
- {
- Color = GetEffectiveBackgroundColor(),
- Style = SKPaintStyle.Fill
- };
- canvas.DrawRect(bounds, bgPaint);
+ bgColor = GetEffectiveBackgroundColor();
}
+ else
+ {
+ // Use theme-aware page background when no explicit color is set
+ bgColor = SkiaTheme.CurrentPageBackgroundSK;
+ }
+
+ using var bgPaint = new SKPaint
+ {
+ Color = bgColor,
+ Style = SKPaintStyle.Fill
+ };
+ canvas.DrawRect(bounds, bgPaint);
// Draw background image if set
if (BackgroundImage != null)
diff --git a/Views/SkiaSearchBar.cs b/Views/SkiaSearchBar.cs
index 8655b2e..e6e1a6f 100644
--- a/Views/SkiaSearchBar.cs
+++ b/Views/SkiaSearchBar.cs
@@ -143,10 +143,16 @@ public class SkiaSearchBar : SkiaView
float cornerRadius = (float)CornerRadius;
float iconSize = (float)IconSize;
- // Draw background
+ // Draw background - use theme-aware color if not explicitly set
+ var bgColor = SearchBarBackgroundColor.ToSKColor();
+ // If using default light color, check for dark mode
+ if (SearchBarBackgroundColor.Red > 0.9f && SearchBarBackgroundColor.Green > 0.9f && SearchBarBackgroundColor.Blue > 0.9f)
+ {
+ bgColor = SkiaTheme.InputBackgroundSK;
+ }
using var bgPaint = new SKPaint
{
- Color = SearchBarBackgroundColor.ToSKColor(),
+ Color = bgColor,
IsAntialias = true,
Style = SKPaintStyle.Fill
};
diff --git a/Views/SkiaTheme.cs b/Views/SkiaTheme.cs
index 6ec70f4..6bec6b8 100644
--- a/Views/SkiaTheme.cs
+++ b/Views/SkiaTheme.cs
@@ -317,4 +317,43 @@ public static class SkiaTheme
internal static readonly SKColor DarkHoverSK = DarkHover.ToSKColor();
#endregion
+
+ #region Theme-Aware Helpers
+
+ ///
+ /// Returns true if dark mode is currently active.
+ ///
+ public static bool IsDarkMode => Application.Current?.UserAppTheme == AppTheme.Dark;
+
+ ///
+ /// Gets the appropriate button background color for the current theme.
+ ///
+ public static SKColor ButtonBackgroundSK => IsDarkMode ? DarkSurfaceSK : Gray200SK;
+
+ ///
+ /// Gets the appropriate text color for the current theme.
+ ///
+ public static SKColor CurrentTextSK => IsDarkMode ? DarkTextSK : TextPrimarySK;
+
+ ///
+ /// Gets the appropriate background color for input controls in the current theme.
+ ///
+ public static SKColor InputBackgroundSK => IsDarkMode ? DarkBackgroundSK : BackgroundWhiteSK;
+
+ ///
+ /// Gets the appropriate border color for the current theme.
+ ///
+ public static SKColor CurrentBorderSK => IsDarkMode ? Gray600SK : BorderLightSK;
+
+ ///
+ /// Gets the appropriate surface background for the current theme.
+ ///
+ public static SKColor CurrentSurfaceSK => IsDarkMode ? DarkSurfaceSK : BackgroundWhiteSK;
+
+ ///
+ /// Gets the appropriate page background for the current theme.
+ ///
+ public static SKColor CurrentPageBackgroundSK => IsDarkMode ? DarkBackgroundSK : BackgroundWhiteSK;
+
+ #endregion
}