This commit is contained in:
TheXamlGuy
2024-10-09 14:57:57 +01:00
parent f10d028558
commit 4469a9b0e8
3 changed files with 146 additions and 39 deletions
@@ -3,6 +3,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Toolkit.UI.Controls.Avalonia"> xmlns:controls="using:Toolkit.UI.Controls.Avalonia">
<ControlTheme x:Key="{x:Type ContentColorPicker}" TargetType="ContentColorPicker"> <ControlTheme x:Key="{x:Type ContentColorPicker}" TargetType="ContentColorPicker">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border <Border
@@ -13,17 +18,28 @@
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<Grid> <Grid>
<ContentPresenter <ZoomBorder
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" x:Name="ZoomBorder"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" ClipToBounds="True"
Content="{TemplateBinding Content}" PanButton="Left">
ContentTemplate="{TemplateBinding ContentTemplate}" /> <ContentPresenter
<Canvas x:Name="Canvas" Background="Transparent"> HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</ZoomBorder>
<Canvas
x:Name="Canvas"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
ClipToBounds="False">
<Border <Border
x:Name="Preview" x:Name="PeekBorder"
Width="100" Width="100"
Height="100" Height="100"
Background="Pink" /> Background="Pink"
ClipToBounds="False"
IsVisible="False" />
</Canvas> </Canvas>
</Grid> </Grid>
</Border> </Border>
@@ -1,73 +1,163 @@
using Avalonia.Controls; using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.PanAndZoom;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Media.Imaging;
namespace Toolkit.UI.Controls.Avalonia; namespace Toolkit.UI.Controls.Avalonia;
public class ContentColorPicker : ContentControl public class ContentColorPicker :
ContentControl
{ {
public static readonly StyledProperty<double> PeekOffsetProperty =
AvaloniaProperty.Register<ContentColorPicker, double>(nameof(PeekOffset), 20);
public static readonly StyledProperty<int> PeekPixelsProperty =
AvaloniaProperty.Register<ContentColorPicker, int>(nameof(PeekPixels), 20);
private readonly Image image = new();
private Canvas? canvas; private Canvas? canvas;
private Border? preview; private (double X, double Y) lastPointerPosition;
private Border? peekBorder;
private ZoomBorder? zoomBorder;
public double PeekOffset
{
get => GetValue(PeekOffsetProperty);
set => SetValue(PeekOffsetProperty, value);
}
public int PeekPixels
{
get => GetValue(PeekPixelsProperty);
set => SetValue(PeekPixelsProperty, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs args) protected override void OnApplyTemplate(TemplateAppliedEventArgs args)
{ {
base.OnApplyTemplate(args); base.OnApplyTemplate(args);
PointerMoved -= OnPointerMoved;
PointerExited -= OnPointerExited;
PointerEntered -= OnPointerEntered;
PointerMoved += OnPointerMoved;
PointerExited += OnPointerExited;
PointerEntered += OnPointerEntered;
canvas = args.NameScope.Find<Canvas>("Canvas"); canvas = args.NameScope.Find<Canvas>("Canvas");
if (canvas is not null) zoomBorder = args.NameScope.Find<ZoomBorder>("ZoomBorder");
if (zoomBorder is not null)
{ {
canvas.PointerMoved += OnPointerMoved; zoomBorder.ZoomChanged += OnZoomChanged;
canvas.PointerExited += OnPointerExited;
canvas.PointerEntered += OnPointerEntered;
} }
preview = args.NameScope.Find<Border>("Preview"); peekBorder = args.NameScope.Find<Border>("PeekBorder");
if (peekBorder is not null)
{
peekBorder.Child = image;
}
} }
private void OnPointerMoved(object? sender, private void OnPointerEntered(object? sender,
PointerEventArgs args) PointerEventArgs args)
{ {
if (canvas is null || preview is null) if (peekBorder is not null)
{ {
return; peekBorder.IsVisible = true;
} }
}
private void OnPointerExited(object? sender,
PointerEventArgs args)
{
if (peekBorder is not null)
{
peekBorder.IsVisible = false;
}
}
private void OnPointerMoved(object? sender,
PointerEventArgs args)
{
double relativeX = args.GetPosition(canvas).X; double relativeX = args.GetPosition(canvas).X;
double relativeY = args.GetPosition(canvas).Y; double relativeY = args.GetPosition(canvas).Y;
double newX = relativeX < 0 ? 0 : (relativeX > canvas.Bounds.Width ? canvas.Bounds.Width : relativeX); lastPointerPosition = (relativeX, relativeY);
double newY = relativeY < 0 ? 0 : (relativeY > canvas.Bounds.Height ? canvas.Bounds.Height : relativeY);
if (newX < 0 || newX > canvas.Bounds.Width || newY < 0 || newY > canvas.Bounds.Height) UpdatePeekPosition(relativeX, relativeY);
{
preview.IsVisible = false;
return;
}
Canvas.SetLeft(preview, newX);
Canvas.SetTop(preview, newY);
} }
private void OnPointerEntered(object? sender, private void OnZoomChanged(object sender,
PointerEventArgs args) ZoomChangedEventArgs args) => UpdatePeekPreview(lastPointerPosition.X, lastPointerPosition.Y);
private Bitmap RenderToBitmap(Visual visual,
double centreX,
double centreY)
{ {
if (preview is null) int width = PeekPixels;
int height = PeekPixels;
double x = Math.Max(centreX - width / 2, 0);
double y = Math.Max(centreY - height / 2, 0);
x = Math.Min(x, visual.Bounds.Width - width);
y = Math.Min(y, visual.Bounds.Height - height);
PixelSize pixelSize = new(width, height);
RenderTargetBitmap renderTarget = new(pixelSize);
using (DrawingContext drawingContext = renderTarget.CreateDrawingContext())
{ {
return; drawingContext.PushClip(new Rect(0, 0, width, height));
drawingContext.FillRectangle(new VisualBrush(visual), new Rect(-x, -y,
visual.Bounds.Width, visual.Bounds.Height));
} }
preview.IsVisible = true; return renderTarget;
} }
private void OnPointerExited(object? sender, private void UpdatePeekPosition(double relativeX,
PointerEventArgs args) double relativeY)
{ {
if (preview is null) if (canvas is null || peekBorder is null)
{ {
return; return;
} }
preview.IsVisible = false; double peekOffset = PeekOffset;
double newX = relativeX + peekOffset;
double newY = relativeY + peekOffset;
newX = Math.Clamp(newX, -peekBorder.Bounds.Width, canvas.Bounds.Width);
newY = Math.Clamp(newY, -peekBorder.Bounds.Height, canvas.Bounds.Height);
Canvas.SetLeft(peekBorder, newX);
Canvas.SetTop(peekBorder, newY);
bool isPointerInside = relativeX >= -peekOffset && relativeX <= canvas.Bounds.Width + peekOffset &&
relativeY >= -peekOffset && relativeY <= canvas.Bounds.Height + peekOffset;
peekBorder.IsVisible = isPointerInside;
if (isPointerInside)
{
UpdatePeekPreview(relativeX, relativeY);
}
} }
}
private void UpdatePeekPreview(double relativeX,
double relativeY)
{
if (zoomBorder is not null)
{
Bitmap bitmap = RenderToBitmap(zoomBorder, relativeX, relativeY);
image.Source = bitmap;
}
}
}
@@ -13,6 +13,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.0-rc1" /> <PackageReference Include="Avalonia" Version="11.2.0-rc1" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.1.0.1" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.1.0.4" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.1.0.4" />
<PackageReference Include="Avalonia.Labs.Controls" Version="11.1.0" /> <PackageReference Include="Avalonia.Labs.Controls" Version="11.1.0" />
<PackageReference Include="FluentAvaloniaUI" Version="2.1.0" /> <PackageReference Include="FluentAvaloniaUI" Version="2.1.0" />