2025-12-19 09:30:16 +00:00
|
|
|
// Licensed to the .NET Foundation under one or more agreements.
|
|
|
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
|
|
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Net.Http;
|
|
|
|
|
using System.Threading.Tasks;
|
2025-12-19 09:30:16 +00:00
|
|
|
using SkiaSharp;
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
using Svg.Skia;
|
2025-12-19 09:30:16 +00:00
|
|
|
|
|
|
|
|
namespace Microsoft.Maui.Platform;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Skia-rendered image button control.
|
|
|
|
|
/// Combines button behavior with image display.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class SkiaImageButton : SkiaView
|
|
|
|
|
{
|
|
|
|
|
private SKBitmap? _bitmap;
|
|
|
|
|
private SKImage? _image;
|
|
|
|
|
private bool _isLoading;
|
|
|
|
|
|
|
|
|
|
public SKBitmap? Bitmap
|
|
|
|
|
{
|
|
|
|
|
get => _bitmap;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
_bitmap?.Dispose();
|
|
|
|
|
_bitmap = value;
|
|
|
|
|
_image?.Dispose();
|
|
|
|
|
_image = value != null ? SKImage.FromBitmap(value) : null;
|
|
|
|
|
Invalidate();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Image properties
|
|
|
|
|
public Aspect Aspect { get; set; } = Aspect.AspectFit;
|
|
|
|
|
public bool IsOpaque { get; set; }
|
|
|
|
|
public bool IsLoading => _isLoading;
|
|
|
|
|
|
|
|
|
|
// Button stroke properties
|
|
|
|
|
public SKColor StrokeColor { get; set; } = SKColors.Transparent;
|
|
|
|
|
public float StrokeThickness { get; set; } = 0;
|
|
|
|
|
public float CornerRadius { get; set; } = 0;
|
|
|
|
|
|
|
|
|
|
// Button state
|
|
|
|
|
public bool IsPressed { get; private set; }
|
|
|
|
|
public bool IsHovered { get; private set; }
|
|
|
|
|
|
|
|
|
|
// Visual state colors
|
|
|
|
|
public SKColor PressedBackgroundColor { get; set; } = new SKColor(0, 0, 0, 30);
|
|
|
|
|
public SKColor HoveredBackgroundColor { get; set; } = new SKColor(0, 0, 0, 15);
|
|
|
|
|
|
|
|
|
|
// Padding for the image content
|
|
|
|
|
public float PaddingLeft { get; set; }
|
|
|
|
|
public float PaddingTop { get; set; }
|
|
|
|
|
public float PaddingRight { get; set; }
|
|
|
|
|
public float PaddingBottom { get; set; }
|
|
|
|
|
|
|
|
|
|
public event EventHandler? Clicked;
|
|
|
|
|
public event EventHandler? Pressed;
|
|
|
|
|
public event EventHandler? Released;
|
|
|
|
|
public event EventHandler? ImageLoaded;
|
|
|
|
|
public event EventHandler<ImageLoadingErrorEventArgs>? ImageLoadingError;
|
|
|
|
|
|
|
|
|
|
public SkiaImageButton()
|
|
|
|
|
{
|
|
|
|
|
IsFocusable = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
|
|
|
|
{
|
|
|
|
|
// Apply padding
|
|
|
|
|
var contentBounds = new SKRect(
|
|
|
|
|
bounds.Left + PaddingLeft,
|
|
|
|
|
bounds.Top + PaddingTop,
|
|
|
|
|
bounds.Right - PaddingRight,
|
|
|
|
|
bounds.Bottom - PaddingBottom);
|
|
|
|
|
|
|
|
|
|
// Draw background based on state
|
|
|
|
|
if (IsPressed || IsHovered || !IsOpaque && BackgroundColor != SKColors.Transparent)
|
|
|
|
|
{
|
|
|
|
|
var bgColor = IsPressed ? PressedBackgroundColor
|
|
|
|
|
: IsHovered ? HoveredBackgroundColor
|
|
|
|
|
: BackgroundColor;
|
|
|
|
|
|
|
|
|
|
using var bgPaint = new SKPaint
|
|
|
|
|
{
|
|
|
|
|
Color = bgColor,
|
|
|
|
|
Style = SKPaintStyle.Fill,
|
|
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (CornerRadius > 0)
|
|
|
|
|
{
|
|
|
|
|
var roundRect = new SKRoundRect(bounds, CornerRadius);
|
|
|
|
|
canvas.DrawRoundRect(roundRect, bgPaint);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
canvas.DrawRect(bounds, bgPaint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw image
|
|
|
|
|
if (_image != null)
|
|
|
|
|
{
|
|
|
|
|
var imageWidth = _image.Width;
|
|
|
|
|
var imageHeight = _image.Height;
|
|
|
|
|
|
|
|
|
|
if (imageWidth > 0 && imageHeight > 0)
|
|
|
|
|
{
|
|
|
|
|
var destRect = CalculateDestRect(contentBounds, imageWidth, imageHeight);
|
|
|
|
|
|
|
|
|
|
using var paint = new SKPaint
|
|
|
|
|
{
|
|
|
|
|
IsAntialias = true,
|
|
|
|
|
FilterQuality = SKFilterQuality.High
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Apply opacity when disabled
|
|
|
|
|
if (!IsEnabled)
|
|
|
|
|
{
|
|
|
|
|
paint.Color = paint.Color.WithAlpha(128);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
canvas.DrawImage(_image, destRect, paint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw stroke/border
|
|
|
|
|
if (StrokeThickness > 0 && StrokeColor != SKColors.Transparent)
|
|
|
|
|
{
|
|
|
|
|
using var strokePaint = new SKPaint
|
|
|
|
|
{
|
|
|
|
|
Color = StrokeColor,
|
|
|
|
|
Style = SKPaintStyle.Stroke,
|
|
|
|
|
StrokeWidth = StrokeThickness,
|
|
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (CornerRadius > 0)
|
|
|
|
|
{
|
|
|
|
|
var roundRect = new SKRoundRect(bounds, CornerRadius);
|
|
|
|
|
canvas.DrawRoundRect(roundRect, strokePaint);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
canvas.DrawRect(bounds, strokePaint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw focus ring
|
|
|
|
|
if (IsFocused)
|
|
|
|
|
{
|
|
|
|
|
using var focusPaint = new SKPaint
|
|
|
|
|
{
|
|
|
|
|
Color = new SKColor(0x00, 0x00, 0x00, 0x40),
|
|
|
|
|
Style = SKPaintStyle.Stroke,
|
|
|
|
|
StrokeWidth = 2,
|
|
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var focusBounds = new SKRect(bounds.Left - 2, bounds.Top - 2, bounds.Right + 2, bounds.Bottom + 2);
|
|
|
|
|
if (CornerRadius > 0)
|
|
|
|
|
{
|
|
|
|
|
var focusRect = new SKRoundRect(focusBounds, CornerRadius + 2);
|
|
|
|
|
canvas.DrawRoundRect(focusRect, focusPaint);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
canvas.DrawRect(focusBounds, focusPaint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private SKRect CalculateDestRect(SKRect bounds, float imageWidth, float imageHeight)
|
|
|
|
|
{
|
|
|
|
|
float destX, destY, destWidth, destHeight;
|
|
|
|
|
|
|
|
|
|
switch (Aspect)
|
|
|
|
|
{
|
|
|
|
|
case Aspect.Fill:
|
|
|
|
|
return bounds;
|
|
|
|
|
|
|
|
|
|
case Aspect.AspectFit:
|
|
|
|
|
var fitScale = Math.Min(bounds.Width / imageWidth, bounds.Height / imageHeight);
|
|
|
|
|
destWidth = imageWidth * fitScale;
|
|
|
|
|
destHeight = imageHeight * fitScale;
|
|
|
|
|
destX = bounds.Left + (bounds.Width - destWidth) / 2;
|
|
|
|
|
destY = bounds.Top + (bounds.Height - destHeight) / 2;
|
|
|
|
|
return new SKRect(destX, destY, destX + destWidth, destY + destHeight);
|
|
|
|
|
|
|
|
|
|
case Aspect.AspectFill:
|
|
|
|
|
var fillScale = Math.Max(bounds.Width / imageWidth, bounds.Height / imageHeight);
|
|
|
|
|
destWidth = imageWidth * fillScale;
|
|
|
|
|
destHeight = imageHeight * fillScale;
|
|
|
|
|
destX = bounds.Left + (bounds.Width - destWidth) / 2;
|
|
|
|
|
destY = bounds.Top + (bounds.Height - destHeight) / 2;
|
|
|
|
|
return new SKRect(destX, destY, destX + destWidth, destY + destHeight);
|
|
|
|
|
|
|
|
|
|
case Aspect.Center:
|
|
|
|
|
destX = bounds.Left + (bounds.Width - imageWidth) / 2;
|
|
|
|
|
destY = bounds.Top + (bounds.Height - imageHeight) / 2;
|
|
|
|
|
return new SKRect(destX, destY, destX + imageWidth, destY + imageHeight);
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return bounds;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Image loading methods
|
|
|
|
|
public async Task LoadFromFileAsync(string filePath)
|
|
|
|
|
{
|
|
|
|
|
_isLoading = true;
|
|
|
|
|
Invalidate();
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
Console.WriteLine("[SkiaImageButton] LoadFromFileAsync: " + filePath);
|
2025-12-19 09:30:16 +00:00
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
var searchPaths = new List<string>
|
|
|
|
|
{
|
|
|
|
|
filePath,
|
|
|
|
|
Path.Combine(AppContext.BaseDirectory, filePath),
|
|
|
|
|
Path.Combine(AppContext.BaseDirectory, "Resources", "Images", filePath),
|
|
|
|
|
Path.Combine(AppContext.BaseDirectory, "Resources", filePath)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Also check for SVG version if PNG was requested
|
|
|
|
|
if (filePath.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
var svgPath = Path.ChangeExtension(filePath, ".svg");
|
|
|
|
|
searchPaths.Add(svgPath);
|
|
|
|
|
searchPaths.Add(Path.Combine(AppContext.BaseDirectory, svgPath));
|
|
|
|
|
searchPaths.Add(Path.Combine(AppContext.BaseDirectory, "Resources", "Images", svgPath));
|
|
|
|
|
searchPaths.Add(Path.Combine(AppContext.BaseDirectory, "Resources", svgPath));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string? foundPath = null;
|
|
|
|
|
foreach (var path in searchPaths)
|
|
|
|
|
{
|
|
|
|
|
if (File.Exists(path))
|
|
|
|
|
{
|
|
|
|
|
foundPath = path;
|
|
|
|
|
Console.WriteLine("[SkiaImageButton] Found file at: " + path);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (foundPath == null)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("[SkiaImageButton] File not found: " + filePath);
|
|
|
|
|
Console.WriteLine("[SkiaImageButton] Searched paths: " + string.Join(", ", searchPaths));
|
|
|
|
|
_isLoading = false;
|
|
|
|
|
ImageLoadingError?.Invoke(this, new ImageLoadingErrorEventArgs(new FileNotFoundException(filePath)));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
await Task.Run(() =>
|
|
|
|
|
{
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
if (foundPath.EndsWith(".svg", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
using var svg = new SKSvg();
|
|
|
|
|
svg.Load(foundPath);
|
|
|
|
|
if (svg.Picture != null)
|
|
|
|
|
{
|
|
|
|
|
var cullRect = svg.Picture.CullRect;
|
|
|
|
|
bool hasWidth = WidthRequest > 0;
|
|
|
|
|
bool hasHeight = HeightRequest > 0;
|
|
|
|
|
|
2026-01-16 03:40:47 +00:00
|
|
|
// Default to 24x24 for icons when no size specified
|
|
|
|
|
const float DefaultIconSize = 24f;
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
float targetWidth = hasWidth
|
|
|
|
|
? (float)(WidthRequest - PaddingLeft - PaddingRight)
|
2026-01-16 03:40:47 +00:00
|
|
|
: DefaultIconSize;
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
float targetHeight = hasHeight
|
|
|
|
|
? (float)(HeightRequest - PaddingTop - PaddingBottom)
|
2026-01-16 03:40:47 +00:00
|
|
|
: DefaultIconSize;
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
|
|
|
|
|
float scale = Math.Min(targetWidth / cullRect.Width, targetHeight / cullRect.Height);
|
|
|
|
|
int width = Math.Max(1, (int)(cullRect.Width * scale));
|
|
|
|
|
int height = Math.Max(1, (int)(cullRect.Height * scale));
|
|
|
|
|
|
|
|
|
|
var bitmap = new SKBitmap(width, height, false);
|
|
|
|
|
using var canvas = new SKCanvas(bitmap);
|
|
|
|
|
canvas.Clear(SKColors.Transparent);
|
|
|
|
|
canvas.Scale(scale);
|
|
|
|
|
canvas.DrawPicture(svg.Picture);
|
|
|
|
|
Bitmap = bitmap;
|
|
|
|
|
Console.WriteLine($"[SkiaImageButton] Loaded SVG: {foundPath} ({width}x{height})");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2025-12-19 09:30:16 +00:00
|
|
|
{
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
using var stream = File.OpenRead(foundPath);
|
|
|
|
|
var bitmap = SKBitmap.Decode(stream);
|
|
|
|
|
if (bitmap != null)
|
|
|
|
|
{
|
|
|
|
|
Bitmap = bitmap;
|
|
|
|
|
Console.WriteLine("[SkiaImageButton] Loaded image: " + foundPath);
|
|
|
|
|
}
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
_isLoading = false;
|
|
|
|
|
ImageLoaded?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_isLoading = false;
|
|
|
|
|
ImageLoadingError?.Invoke(this, new ImageLoadingErrorEventArgs(ex));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Invalidate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task LoadFromStreamAsync(Stream stream)
|
|
|
|
|
{
|
|
|
|
|
_isLoading = true;
|
|
|
|
|
Invalidate();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await Task.Run(() =>
|
|
|
|
|
{
|
|
|
|
|
var bitmap = SKBitmap.Decode(stream);
|
|
|
|
|
if (bitmap != null)
|
|
|
|
|
{
|
|
|
|
|
Bitmap = bitmap;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
_isLoading = false;
|
|
|
|
|
ImageLoaded?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_isLoading = false;
|
|
|
|
|
ImageLoadingError?.Invoke(this, new ImageLoadingErrorEventArgs(ex));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Invalidate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task LoadFromUriAsync(Uri uri)
|
|
|
|
|
{
|
|
|
|
|
_isLoading = true;
|
|
|
|
|
Invalidate();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
using var httpClient = new HttpClient();
|
|
|
|
|
var data = await httpClient.GetByteArrayAsync(uri);
|
|
|
|
|
|
|
|
|
|
using var stream = new MemoryStream(data);
|
|
|
|
|
var bitmap = SKBitmap.Decode(stream);
|
|
|
|
|
if (bitmap != null)
|
|
|
|
|
{
|
|
|
|
|
Bitmap = bitmap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_isLoading = false;
|
|
|
|
|
ImageLoaded?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_isLoading = false;
|
|
|
|
|
ImageLoadingError?.Invoke(this, new ImageLoadingErrorEventArgs(ex));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Invalidate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadFromData(byte[] data)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
using var stream = new MemoryStream(data);
|
|
|
|
|
var bitmap = SKBitmap.Decode(stream);
|
|
|
|
|
if (bitmap != null)
|
|
|
|
|
{
|
|
|
|
|
Bitmap = bitmap;
|
|
|
|
|
}
|
|
|
|
|
ImageLoaded?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
ImageLoadingError?.Invoke(this, new ImageLoadingErrorEventArgs(ex));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pointer event handlers
|
|
|
|
|
public override void OnPointerEntered(PointerEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (!IsEnabled) return;
|
|
|
|
|
IsHovered = true;
|
RC1: Full XAML support with BindableProperty, VSM, and data binding
Phase 1 - BindableProperty Foundation:
- SkiaLayoutView: Convert Spacing, Padding, ClipToBounds to BindableProperty
- SkiaStackLayout: Convert Orientation to BindableProperty
- SkiaGrid: Convert RowSpacing, ColumnSpacing to BindableProperty
- SkiaCollectionView: Convert all 12 properties to BindableProperty
- SkiaShell: Convert all 12 properties to BindableProperty
Phase 2 - Visual State Manager:
- Add VSM integration to SkiaImageButton pointer handlers
- Support Normal, PointerOver, Pressed, Disabled states
Phase 3-4 - XAML/Data Binding:
- Type converters for SKColor, SKRect, SKSize, SKPoint
- BindingContext propagation through visual tree
- Full handler registration for all MAUI controls
Documentation:
- README: Add styling/binding examples, update roadmap
- Add RC1-ROADMAP.md with implementation details
Version: 1.0.0-rc.1
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 09:26:04 -05:00
|
|
|
SkiaVisualStateManager.GoToState(this, SkiaVisualStateManager.CommonStates.PointerOver);
|
2025-12-19 09:30:16 +00:00
|
|
|
Invalidate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnPointerExited(PointerEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
IsHovered = false;
|
|
|
|
|
if (IsPressed)
|
|
|
|
|
{
|
|
|
|
|
IsPressed = false;
|
|
|
|
|
}
|
RC1: Full XAML support with BindableProperty, VSM, and data binding
Phase 1 - BindableProperty Foundation:
- SkiaLayoutView: Convert Spacing, Padding, ClipToBounds to BindableProperty
- SkiaStackLayout: Convert Orientation to BindableProperty
- SkiaGrid: Convert RowSpacing, ColumnSpacing to BindableProperty
- SkiaCollectionView: Convert all 12 properties to BindableProperty
- SkiaShell: Convert all 12 properties to BindableProperty
Phase 2 - Visual State Manager:
- Add VSM integration to SkiaImageButton pointer handlers
- Support Normal, PointerOver, Pressed, Disabled states
Phase 3-4 - XAML/Data Binding:
- Type converters for SKColor, SKRect, SKSize, SKPoint
- BindingContext propagation through visual tree
- Full handler registration for all MAUI controls
Documentation:
- README: Add styling/binding examples, update roadmap
- Add RC1-ROADMAP.md with implementation details
Version: 1.0.0-rc.1
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 09:26:04 -05:00
|
|
|
SkiaVisualStateManager.GoToState(this, IsEnabled
|
|
|
|
|
? SkiaVisualStateManager.CommonStates.Normal
|
|
|
|
|
: SkiaVisualStateManager.CommonStates.Disabled);
|
2025-12-19 09:30:16 +00:00
|
|
|
Invalidate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnPointerPressed(PointerEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (!IsEnabled) return;
|
|
|
|
|
|
|
|
|
|
IsPressed = true;
|
RC1: Full XAML support with BindableProperty, VSM, and data binding
Phase 1 - BindableProperty Foundation:
- SkiaLayoutView: Convert Spacing, Padding, ClipToBounds to BindableProperty
- SkiaStackLayout: Convert Orientation to BindableProperty
- SkiaGrid: Convert RowSpacing, ColumnSpacing to BindableProperty
- SkiaCollectionView: Convert all 12 properties to BindableProperty
- SkiaShell: Convert all 12 properties to BindableProperty
Phase 2 - Visual State Manager:
- Add VSM integration to SkiaImageButton pointer handlers
- Support Normal, PointerOver, Pressed, Disabled states
Phase 3-4 - XAML/Data Binding:
- Type converters for SKColor, SKRect, SKSize, SKPoint
- BindingContext propagation through visual tree
- Full handler registration for all MAUI controls
Documentation:
- README: Add styling/binding examples, update roadmap
- Add RC1-ROADMAP.md with implementation details
Version: 1.0.0-rc.1
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 09:26:04 -05:00
|
|
|
SkiaVisualStateManager.GoToState(this, SkiaVisualStateManager.CommonStates.Pressed);
|
2025-12-19 09:30:16 +00:00
|
|
|
Invalidate();
|
|
|
|
|
Pressed?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnPointerReleased(PointerEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (!IsEnabled) return;
|
|
|
|
|
|
|
|
|
|
var wasPressed = IsPressed;
|
|
|
|
|
IsPressed = false;
|
RC1: Full XAML support with BindableProperty, VSM, and data binding
Phase 1 - BindableProperty Foundation:
- SkiaLayoutView: Convert Spacing, Padding, ClipToBounds to BindableProperty
- SkiaStackLayout: Convert Orientation to BindableProperty
- SkiaGrid: Convert RowSpacing, ColumnSpacing to BindableProperty
- SkiaCollectionView: Convert all 12 properties to BindableProperty
- SkiaShell: Convert all 12 properties to BindableProperty
Phase 2 - Visual State Manager:
- Add VSM integration to SkiaImageButton pointer handlers
- Support Normal, PointerOver, Pressed, Disabled states
Phase 3-4 - XAML/Data Binding:
- Type converters for SKColor, SKRect, SKSize, SKPoint
- BindingContext propagation through visual tree
- Full handler registration for all MAUI controls
Documentation:
- README: Add styling/binding examples, update roadmap
- Add RC1-ROADMAP.md with implementation details
Version: 1.0.0-rc.1
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 09:26:04 -05:00
|
|
|
SkiaVisualStateManager.GoToState(this, IsHovered
|
|
|
|
|
? SkiaVisualStateManager.CommonStates.PointerOver
|
|
|
|
|
: SkiaVisualStateManager.CommonStates.Normal);
|
2025-12-19 09:30:16 +00:00
|
|
|
Invalidate();
|
|
|
|
|
|
|
|
|
|
Released?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
|
|
|
|
|
if (wasPressed && Bounds.Contains(new SKPoint(e.X, e.Y)))
|
|
|
|
|
{
|
|
|
|
|
Clicked?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Keyboard event handlers
|
|
|
|
|
public override void OnKeyDown(KeyEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (!IsEnabled) return;
|
|
|
|
|
|
|
|
|
|
if (e.Key == Key.Enter || e.Key == Key.Space)
|
|
|
|
|
{
|
|
|
|
|
IsPressed = true;
|
|
|
|
|
Invalidate();
|
|
|
|
|
Pressed?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnKeyUp(KeyEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (!IsEnabled) return;
|
|
|
|
|
|
|
|
|
|
if (e.Key == Key.Enter || e.Key == Key.Space)
|
|
|
|
|
{
|
|
|
|
|
if (IsPressed)
|
|
|
|
|
{
|
|
|
|
|
IsPressed = false;
|
|
|
|
|
Invalidate();
|
|
|
|
|
Released?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
Clicked?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override SKSize MeasureOverride(SKSize availableSize)
|
|
|
|
|
{
|
2026-01-16 03:40:47 +00:00
|
|
|
// Respect explicit WidthRequest/HeightRequest first (MAUI standard behavior)
|
|
|
|
|
if (WidthRequest > 0 && HeightRequest > 0)
|
|
|
|
|
{
|
|
|
|
|
return new SKSize((float)WidthRequest, (float)HeightRequest);
|
|
|
|
|
}
|
|
|
|
|
if (WidthRequest > 0)
|
|
|
|
|
{
|
|
|
|
|
// Fixed width, calculate height from aspect ratio or use width
|
|
|
|
|
float height = HeightRequest > 0 ? (float)HeightRequest
|
|
|
|
|
: _image != null ? (float)WidthRequest * _image.Height / _image.Width
|
|
|
|
|
: (float)WidthRequest;
|
|
|
|
|
return new SKSize((float)WidthRequest, height);
|
|
|
|
|
}
|
|
|
|
|
if (HeightRequest > 0)
|
|
|
|
|
{
|
|
|
|
|
// Fixed height, calculate width from aspect ratio or use height
|
|
|
|
|
float width = WidthRequest > 0 ? (float)WidthRequest
|
|
|
|
|
: _image != null ? (float)HeightRequest * _image.Width / _image.Height
|
|
|
|
|
: (float)HeightRequest;
|
|
|
|
|
return new SKSize(width, (float)HeightRequest);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No explicit size - calculate from content
|
2025-12-19 09:30:16 +00:00
|
|
|
var padding = new SKSize(PaddingLeft + PaddingRight, PaddingTop + PaddingBottom);
|
|
|
|
|
|
|
|
|
|
if (_image == null)
|
|
|
|
|
return new SKSize(44 + padding.Width, 44 + padding.Height); // Default touch target size
|
|
|
|
|
|
|
|
|
|
var imageWidth = _image.Width;
|
|
|
|
|
var imageHeight = _image.Height;
|
|
|
|
|
|
|
|
|
|
if (availableSize.Width < float.MaxValue && availableSize.Height < float.MaxValue)
|
|
|
|
|
{
|
|
|
|
|
var availableContent = new SKSize(
|
|
|
|
|
availableSize.Width - padding.Width,
|
|
|
|
|
availableSize.Height - padding.Height);
|
|
|
|
|
var scale = Math.Min(availableContent.Width / imageWidth, availableContent.Height / imageHeight);
|
|
|
|
|
return new SKSize(imageWidth * scale + padding.Width, imageHeight * scale + padding.Height);
|
|
|
|
|
}
|
|
|
|
|
else if (availableSize.Width < float.MaxValue)
|
|
|
|
|
{
|
|
|
|
|
var availableWidth = availableSize.Width - padding.Width;
|
|
|
|
|
var scale = availableWidth / imageWidth;
|
|
|
|
|
return new SKSize(availableSize.Width, imageHeight * scale + padding.Height);
|
|
|
|
|
}
|
|
|
|
|
else if (availableSize.Height < float.MaxValue)
|
|
|
|
|
{
|
|
|
|
|
var availableHeight = availableSize.Height - padding.Height;
|
|
|
|
|
var scale = availableHeight / imageHeight;
|
|
|
|
|
return new SKSize(imageWidth * scale + padding.Width, availableSize.Height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new SKSize(imageWidth + padding.Width, imageHeight + padding.Height);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 03:40:47 +00:00
|
|
|
protected override SKRect ArrangeOverride(SKRect bounds)
|
|
|
|
|
{
|
|
|
|
|
// If we have explicit size requests, constrain to desired size
|
|
|
|
|
// This follows MAUI standard behavior - controls respect WidthRequest/HeightRequest
|
|
|
|
|
var desiredWidth = DesiredSize.Width;
|
|
|
|
|
var desiredHeight = DesiredSize.Height;
|
|
|
|
|
|
|
|
|
|
// If desired size is smaller than available bounds, align within bounds
|
|
|
|
|
if (desiredWidth > 0 && desiredHeight > 0 &&
|
|
|
|
|
(desiredWidth < bounds.Width || desiredHeight < bounds.Height))
|
|
|
|
|
{
|
|
|
|
|
float finalWidth = Math.Min(desiredWidth, bounds.Width);
|
|
|
|
|
float finalHeight = Math.Min(desiredHeight, bounds.Height);
|
|
|
|
|
|
|
|
|
|
// Calculate position based on HorizontalOptions
|
|
|
|
|
// LayoutAlignment: Start=0, Center=1, End=2, Fill=3
|
|
|
|
|
float x = bounds.Left;
|
|
|
|
|
var hAlignValue = (int)HorizontalOptions.Alignment;
|
|
|
|
|
if (hAlignValue == 1) // Center
|
|
|
|
|
{
|
|
|
|
|
x = bounds.Left + (bounds.Width - finalWidth) / 2;
|
|
|
|
|
}
|
|
|
|
|
else if (hAlignValue == 2) // End
|
|
|
|
|
{
|
|
|
|
|
x = bounds.Right - finalWidth;
|
|
|
|
|
}
|
|
|
|
|
// Fill (3) and Start (0) both use x = bounds.Left
|
|
|
|
|
|
|
|
|
|
// Calculate position based on VerticalOptions
|
|
|
|
|
float y = bounds.Top;
|
|
|
|
|
var vAlignValue = (int)VerticalOptions.Alignment;
|
|
|
|
|
if (vAlignValue == 1) // Center
|
|
|
|
|
{
|
|
|
|
|
y = bounds.Top + (bounds.Height - finalHeight) / 2;
|
|
|
|
|
}
|
|
|
|
|
else if (vAlignValue == 2) // End
|
|
|
|
|
{
|
|
|
|
|
y = bounds.Bottom - finalHeight;
|
|
|
|
|
}
|
|
|
|
|
// Fill (3) and Start (0) both use y = bounds.Top
|
|
|
|
|
|
|
|
|
|
return new SKRect(x, y, x + finalWidth, y + finalHeight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bounds;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
protected override void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (disposing)
|
|
|
|
|
{
|
|
|
|
|
_bitmap?.Dispose();
|
|
|
|
|
_image?.Dispose();
|
|
|
|
|
}
|
|
|
|
|
base.Dispose(disposing);
|
|
|
|
|
}
|
|
|
|
|
}
|