Fixes for handlers
This commit is contained in:
@@ -68,6 +68,13 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Explicitly map LineBreakMode on connect - MAUI may not trigger property change for defaults
|
||||||
|
if (VirtualView is Microsoft.Maui.Controls.Label mauiLabel)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[LabelHandler] ConnectHandler Text='{mauiLabel.Text?.Substring(0, Math.Min(20, mauiLabel.Text?.Length ?? 0))}...' LineBreakMode={mauiLabel.LineBreakMode} ({(int)mauiLabel.LineBreakMode})");
|
||||||
|
platformView.LineBreakMode = mauiLabel.LineBreakMode;
|
||||||
|
}
|
||||||
|
|
||||||
platformView.Tapped += OnPlatformViewTapped;
|
platformView.Tapped += OnPlatformViewTapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
63
Services/Browser.cs
Normal file
63
Services/Browser.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.Maui.ApplicationModel;
|
||||||
|
|
||||||
|
namespace Microsoft.Maui.Platform.Linux.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Static Browser class providing MAUI-compatible API for opening URLs.
|
||||||
|
/// </summary>
|
||||||
|
public static class Browser
|
||||||
|
{
|
||||||
|
private static IBrowser? _browser;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the browser implementation. Set during app initialization.
|
||||||
|
/// </summary>
|
||||||
|
public static IBrowser Default
|
||||||
|
{
|
||||||
|
get => _browser ??= new BrowserService();
|
||||||
|
set => _browser = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the specified URI in the default browser.
|
||||||
|
/// </summary>
|
||||||
|
public static Task<bool> OpenAsync(string uri)
|
||||||
|
{
|
||||||
|
return Default.OpenAsync(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the specified URI in the default browser with the specified launch mode.
|
||||||
|
/// </summary>
|
||||||
|
public static Task<bool> OpenAsync(string uri, BrowserLaunchMode launchMode)
|
||||||
|
{
|
||||||
|
return Default.OpenAsync(uri, launchMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the specified URI in the default browser.
|
||||||
|
/// </summary>
|
||||||
|
public static Task<bool> OpenAsync(Uri uri)
|
||||||
|
{
|
||||||
|
return Default.OpenAsync(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the specified URI in the default browser with the specified launch mode.
|
||||||
|
/// </summary>
|
||||||
|
public static Task<bool> OpenAsync(Uri uri, BrowserLaunchMode launchMode)
|
||||||
|
{
|
||||||
|
return Default.OpenAsync(uri, launchMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the specified URI in the default browser with the specified options.
|
||||||
|
/// </summary>
|
||||||
|
public static Task<bool> OpenAsync(Uri uri, BrowserLaunchOptions options)
|
||||||
|
{
|
||||||
|
return Default.OpenAsync(uri, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -536,11 +536,32 @@ public class SkiaButton : SkiaView, IButtonController
|
|||||||
|
|
||||||
#region Drawing
|
#region Drawing
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Override to prevent base class from drawing rectangular background.
|
||||||
|
/// Button draws its own rounded background in OnDraw.
|
||||||
|
/// </summary>
|
||||||
|
protected override void DrawBackground(SKCanvas canvas, SKRect bounds)
|
||||||
|
{
|
||||||
|
// Don't draw anything - OnDraw handles the rounded background
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
||||||
{
|
{
|
||||||
// BackgroundColor is inherited from SkiaView as MAUI Color - convert to SKColor for rendering
|
// BackgroundColor is inherited from SkiaView as MAUI Color - convert to SKColor for rendering
|
||||||
var bgColor = GetEffectiveBackgroundColor();
|
var bgColor = GetEffectiveBackgroundColor();
|
||||||
bool hasBackground = bgColor.Alpha > 0;
|
|
||||||
|
// Check if BackgroundColor was explicitly set (even if set to transparent)
|
||||||
|
// This distinguishes "no background specified" from "explicitly transparent"
|
||||||
|
bool hasExplicitBackground = BackgroundColor != null;
|
||||||
|
|
||||||
|
// If no background color is set, use a default button background (like other MAUI platforms)
|
||||||
|
// This ensures buttons are visible even without explicit styling
|
||||||
|
if (!hasExplicitBackground)
|
||||||
|
{
|
||||||
|
bgColor = SkiaTheme.Gray200SK; // Default button background
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasBackground = hasExplicitBackground ? bgColor.Alpha > 0 : true;
|
||||||
|
|
||||||
// Determine current state color
|
// Determine current state color
|
||||||
SKColor currentBgColor;
|
SKColor currentBgColor;
|
||||||
@@ -571,6 +592,10 @@ public class SkiaButton : SkiaView, IButtonController
|
|||||||
|
|
||||||
var roundRect = new SKRoundRect(bounds, cornerRadius);
|
var roundRect = new SKRoundRect(bounds, cornerRadius);
|
||||||
|
|
||||||
|
// Clip to rounded rectangle to prevent background bleeding in corners
|
||||||
|
canvas.Save();
|
||||||
|
canvas.ClipRoundRect(roundRect, antialias: true);
|
||||||
|
|
||||||
// Draw background
|
// Draw background
|
||||||
if (currentBgColor.Alpha > 0)
|
if (currentBgColor.Alpha > 0)
|
||||||
{
|
{
|
||||||
@@ -612,17 +637,26 @@ public class SkiaButton : SkiaView, IButtonController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw content (text and/or image)
|
// Draw content (text and/or image)
|
||||||
DrawContent(canvas, bounds);
|
DrawContent(canvas, bounds, hasExplicitBackground);
|
||||||
|
|
||||||
|
// Restore canvas state (undo clipping)
|
||||||
|
canvas.Restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawContent(SKCanvas canvas, SKRect bounds)
|
private void DrawContent(SKCanvas canvas, SKRect bounds, bool hasExplicitBackground)
|
||||||
{
|
{
|
||||||
var padding = Padding;
|
var padding = Padding;
|
||||||
|
// Handle NaN padding (default to 14, 10)
|
||||||
|
float padLeft = float.IsNaN((float)padding.Left) ? 14f : (float)padding.Left;
|
||||||
|
float padTop = float.IsNaN((float)padding.Top) ? 10f : (float)padding.Top;
|
||||||
|
float padRight = float.IsNaN((float)padding.Right) ? 14f : (float)padding.Right;
|
||||||
|
float padBottom = float.IsNaN((float)padding.Bottom) ? 10f : (float)padding.Bottom;
|
||||||
|
|
||||||
var contentBounds = new SKRect(
|
var contentBounds = new SKRect(
|
||||||
bounds.Left + (float)padding.Left,
|
bounds.Left + padLeft,
|
||||||
bounds.Top + (float)padding.Top,
|
bounds.Top + padTop,
|
||||||
bounds.Right - (float)padding.Right,
|
bounds.Right - padRight,
|
||||||
bounds.Bottom - (float)padding.Bottom);
|
bounds.Bottom - padBottom);
|
||||||
|
|
||||||
// Prepare font
|
// Prepare font
|
||||||
bool isBold = FontAttributes.HasFlag(FontAttributes.Bold);
|
bool isBold = FontAttributes.HasFlag(FontAttributes.Bold);
|
||||||
@@ -640,8 +674,24 @@ public class SkiaButton : SkiaView, IButtonController
|
|||||||
SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(fontFamily, fontStyle) ?? SKTypeface.Default,
|
SkiaRenderingEngine.Current?.ResourceCache.GetTypeface(fontFamily, fontStyle) ?? SKTypeface.Default,
|
||||||
fontSize);
|
fontSize);
|
||||||
|
|
||||||
// Prepare text color (null means use platform default: white for buttons)
|
// Prepare text color
|
||||||
var textColor = TextColor != null ? ToSKColor(TextColor) : SkiaTheme.BackgroundWhiteSK;
|
// If TextColor is set, use it; otherwise use a sensible default based on background
|
||||||
|
SKColor textColor;
|
||||||
|
if (TextColor != null)
|
||||||
|
{
|
||||||
|
textColor = ToSKColor(TextColor);
|
||||||
|
}
|
||||||
|
else if (hasExplicitBackground)
|
||||||
|
{
|
||||||
|
// Explicit background but no text color - use white (common for colored buttons)
|
||||||
|
textColor = SkiaTheme.BackgroundWhiteSK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default button (gray background) - use dark text for contrast
|
||||||
|
textColor = SkiaTheme.Gray800SK;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsEnabled)
|
if (!IsEnabled)
|
||||||
{
|
{
|
||||||
textColor = textColor.WithAlpha(128);
|
textColor = textColor.WithAlpha(128);
|
||||||
@@ -945,6 +995,11 @@ public class SkiaButton : SkiaView, IButtonController
|
|||||||
var padding = Padding;
|
var padding = Padding;
|
||||||
float paddingH = (float)(padding.Left + padding.Right);
|
float paddingH = (float)(padding.Left + padding.Right);
|
||||||
float paddingV = (float)(padding.Top + padding.Bottom);
|
float paddingV = (float)(padding.Top + padding.Bottom);
|
||||||
|
|
||||||
|
// Handle NaN padding (can happen with style resolution issues)
|
||||||
|
if (float.IsNaN(paddingH)) paddingH = 28f; // Default: 14 + 14
|
||||||
|
if (float.IsNaN(paddingV)) paddingV = 20f; // Default: 10 + 10
|
||||||
|
|
||||||
float fontSize = FontSize > 0 ? (float)FontSize : 14f;
|
float fontSize = FontSize > 0 ? (float)FontSize : 14f;
|
||||||
|
|
||||||
// Prepare font for measurement
|
// Prepare font for measurement
|
||||||
@@ -978,7 +1033,9 @@ public class SkiaButton : SkiaView, IButtonController
|
|||||||
{
|
{
|
||||||
textWidth += (float)(CharacterSpacing * (displayText.Length - 1));
|
textWidth += (float)(CharacterSpacing * (displayText.Length - 1));
|
||||||
}
|
}
|
||||||
textHeight = textBounds.Height;
|
// Use font metrics for proper line height (ascent is negative)
|
||||||
|
var metrics = font.Metrics;
|
||||||
|
textHeight = metrics.Descent - metrics.Ascent;
|
||||||
}
|
}
|
||||||
|
|
||||||
float imageWidth = 0, imageHeight = 0;
|
float imageWidth = 0, imageHeight = 0;
|
||||||
@@ -1037,7 +1094,10 @@ public class SkiaButton : SkiaView, IButtonController
|
|||||||
height = (float)HeightRequest;
|
height = (float)HeightRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Size(Math.Max(width, 44f), Math.Max(height, 30f));
|
var result = new Size(Math.Max(width, 44f), Math.Max(height, 36f));
|
||||||
|
if (Text == "Round")
|
||||||
|
Console.WriteLine($"[SkiaButton.Measure] Text='Round' WReq={WidthRequest} HReq={HeightRequest} width={width:F1} height={height:F1} result={result.Width:F0}x{result.Height:F0}");
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -921,7 +921,14 @@ public class SkiaEditor : SkiaView, IInputContext
|
|||||||
Color = GetEffectivePlaceholderColor(),
|
Color = GetEffectivePlaceholderColor(),
|
||||||
IsAntialias = true
|
IsAntialias = true
|
||||||
};
|
};
|
||||||
canvas.DrawText(Placeholder, contentRect.Left, contentRect.Top + fontSize, placeholderPaint);
|
// Handle multiline placeholder text by splitting on newlines
|
||||||
|
var placeholderLines = Placeholder.Split('\n');
|
||||||
|
var y = contentRect.Top + fontSize;
|
||||||
|
foreach (var line in placeholderLines)
|
||||||
|
{
|
||||||
|
canvas.DrawText(line, contentRect.Left, y, placeholderPaint);
|
||||||
|
y += lineSpacing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -361,7 +361,11 @@ public class SkiaStackLayout : SkiaLayoutView
|
|||||||
float maxWidth = 0;
|
float maxWidth = 0;
|
||||||
float maxHeight = 0;
|
float maxHeight = 0;
|
||||||
|
|
||||||
var childAvailable = new Size(contentWidth, contentHeight);
|
// For stack layouts, give children infinite size in the stacking direction
|
||||||
|
// so they can measure to their natural size
|
||||||
|
var childAvailable = Orientation == StackOrientation.Horizontal
|
||||||
|
? new Size(double.PositiveInfinity, contentHeight) // Horizontal: infinite width, constrained height
|
||||||
|
: new Size(contentWidth, double.PositiveInfinity); // Vertical: constrained width, infinite height
|
||||||
|
|
||||||
foreach (var child in Children)
|
foreach (var child in Children)
|
||||||
{
|
{
|
||||||
@@ -447,11 +451,9 @@ public class SkiaStackLayout : SkiaLayoutView
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// For ScrollView children, give them the remaining viewport width
|
// Horizontal stack: give each child its measured width
|
||||||
var remainingWidth = Math.Max(0, contentWidth - offset);
|
// Don't constrain - let content overflow if needed (parent clips)
|
||||||
var useWidth = child is SkiaScrollView
|
var useWidth = childWidth;
|
||||||
? remainingWidth
|
|
||||||
: Math.Min(childWidth, remainingWidth > 0 ? remainingWidth : childWidth);
|
|
||||||
|
|
||||||
// Respect child's VerticalOptions for horizontal layouts
|
// Respect child's VerticalOptions for horizontal layouts
|
||||||
var useHeight = Math.Min(childHeight, contentHeight);
|
var useHeight = Math.Min(childHeight, contentHeight);
|
||||||
|
|||||||
@@ -295,12 +295,36 @@ public class SkiaScrollView : SkiaView
|
|||||||
var contentDesired = _content.Measure(availableSize);
|
var contentDesired = _content.Measure(availableSize);
|
||||||
ContentSize = new SKSize((float)contentDesired.Width, (float)contentDesired.Height);
|
ContentSize = new SKSize((float)contentDesired.Width, (float)contentDesired.Height);
|
||||||
|
|
||||||
// Apply content's margin
|
// Apply content's margin and arrange based on scroll orientation
|
||||||
var margin = _content.Margin;
|
var margin = _content.Margin;
|
||||||
var contentLeft = bounds.Left + (float)margin.Left;
|
var contentLeft = bounds.Left + (float)margin.Left;
|
||||||
var contentTop = bounds.Top + (float)margin.Top;
|
var contentTop = bounds.Top + (float)margin.Top;
|
||||||
var contentWidth = Math.Max(bounds.Width, (float)_content.DesiredSize.Width) - (float)margin.Left - (float)margin.Right;
|
|
||||||
var contentHeight = Math.Max(bounds.Height, (float)_content.DesiredSize.Height) - (float)margin.Top - (float)margin.Bottom;
|
// Content dimensions depend on scroll orientation
|
||||||
|
float contentWidth, contentHeight;
|
||||||
|
switch (Orientation)
|
||||||
|
{
|
||||||
|
case ScrollOrientation.Horizontal:
|
||||||
|
contentWidth = Math.Max(bounds.Width, (float)_content.DesiredSize.Width);
|
||||||
|
contentHeight = bounds.Height;
|
||||||
|
break;
|
||||||
|
case ScrollOrientation.Neither:
|
||||||
|
contentWidth = bounds.Width;
|
||||||
|
contentHeight = bounds.Height;
|
||||||
|
break;
|
||||||
|
case ScrollOrientation.Both:
|
||||||
|
contentWidth = Math.Max(bounds.Width, (float)_content.DesiredSize.Width);
|
||||||
|
contentHeight = Math.Max(bounds.Height, (float)_content.DesiredSize.Height);
|
||||||
|
break;
|
||||||
|
case ScrollOrientation.Vertical:
|
||||||
|
default:
|
||||||
|
contentWidth = bounds.Width;
|
||||||
|
contentHeight = Math.Max(bounds.Height, (float)_content.DesiredSize.Height);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
contentWidth -= (float)margin.Left + (float)margin.Right;
|
||||||
|
contentHeight -= (float)margin.Top + (float)margin.Bottom;
|
||||||
var contentBounds = new Rect(contentLeft, contentTop, contentWidth, contentHeight);
|
var contentBounds = new Rect(contentLeft, contentTop, contentWidth, contentHeight);
|
||||||
_content.Arrange(contentBounds);
|
_content.Arrange(contentBounds);
|
||||||
|
|
||||||
@@ -858,12 +882,41 @@ public class SkiaScrollView : SkiaView
|
|||||||
|
|
||||||
if (_content != null)
|
if (_content != null)
|
||||||
{
|
{
|
||||||
// Apply content's margin and arrange content at its full size
|
// Apply content's margin and arrange content based on scroll orientation
|
||||||
var margin = _content.Margin;
|
var margin = _content.Margin;
|
||||||
var contentLeft = (float)actualBounds.Left + (float)margin.Left;
|
var contentLeft = (float)actualBounds.Left + (float)margin.Left;
|
||||||
var contentTop = (float)actualBounds.Top + (float)margin.Top;
|
var contentTop = (float)actualBounds.Top + (float)margin.Top;
|
||||||
var contentWidth = Math.Max((float)actualBounds.Width, ContentSize.Width) - (float)margin.Left - (float)margin.Right;
|
|
||||||
var contentHeight = Math.Max((float)actualBounds.Height, ContentSize.Height) - (float)margin.Top - (float)margin.Bottom;
|
// Content dimensions depend on scroll orientation:
|
||||||
|
// - Vertical: width constrained to viewport, height can expand
|
||||||
|
// - Horizontal: width can expand, height constrained to viewport
|
||||||
|
// - Both: both can expand
|
||||||
|
// - Neither: both constrained to viewport
|
||||||
|
float contentWidth, contentHeight;
|
||||||
|
switch (Orientation)
|
||||||
|
{
|
||||||
|
case ScrollOrientation.Horizontal:
|
||||||
|
contentWidth = Math.Max((float)actualBounds.Width, ContentSize.Width);
|
||||||
|
contentHeight = (float)actualBounds.Height;
|
||||||
|
break;
|
||||||
|
case ScrollOrientation.Neither:
|
||||||
|
contentWidth = (float)actualBounds.Width;
|
||||||
|
contentHeight = (float)actualBounds.Height;
|
||||||
|
break;
|
||||||
|
case ScrollOrientation.Both:
|
||||||
|
contentWidth = Math.Max((float)actualBounds.Width, ContentSize.Width);
|
||||||
|
contentHeight = Math.Max((float)actualBounds.Height, ContentSize.Height);
|
||||||
|
break;
|
||||||
|
case ScrollOrientation.Vertical:
|
||||||
|
default:
|
||||||
|
// Vertical scroll: constrain width to viewport, allow height to expand
|
||||||
|
contentWidth = (float)actualBounds.Width;
|
||||||
|
contentHeight = Math.Max((float)actualBounds.Height, ContentSize.Height);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
contentWidth -= (float)margin.Left + (float)margin.Right;
|
||||||
|
contentHeight -= (float)margin.Top + (float)margin.Bottom;
|
||||||
var contentBounds = new Rect(contentLeft, contentTop, contentWidth, contentHeight);
|
var contentBounds = new Rect(contentLeft, contentTop, contentWidth, contentHeight);
|
||||||
|
|
||||||
_content.Arrange(contentBounds);
|
_content.Arrange(contentBounds);
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ public class SkiaSearchBar : SkiaView
|
|||||||
BackgroundColor = Colors.Transparent,
|
BackgroundColor = Colors.Transparent,
|
||||||
BorderColor = Colors.Transparent,
|
BorderColor = Colors.Transparent,
|
||||||
FocusedBorderColor = Colors.Transparent,
|
FocusedBorderColor = Colors.Transparent,
|
||||||
BorderWidth = 0
|
BorderWidth = 0,
|
||||||
|
VerticalTextAlignment = TextAlignment.Center
|
||||||
};
|
};
|
||||||
|
|
||||||
_entry.TextChanged += (s, e) =>
|
_entry.TextChanged += (s, e) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user