@ -0,0 +1,42 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="250" |
|||
x:Class="ControlCatalog.Pages.LabelsPage" |
|||
x:Name="_labelsPage"> |
|||
<UserControl.Styles> |
|||
<Style Selector="Label"> |
|||
<Setter Property="VerticalAlignment" Value="Center"/> |
|||
<Setter Property="Margin" Value="6,3,0,3"/> |
|||
</Style> |
|||
<Style Selector="TextBox"> |
|||
<Setter Property="VerticalAlignment" Value="Center"/> |
|||
<Setter Property="Margin" Value="0,3,6,3"/> |
|||
</Style> |
|||
<Style Selector="CheckBox"> |
|||
<Setter Property="VerticalAlignment" Value="Center"/> |
|||
<Setter Property="Margin" Value="0,3,6,3"/> |
|||
</Style> |
|||
<Style Selector="Button[IsDefault=true]"> |
|||
<Setter Property="Background" Value="{DynamicResource HighlightBrush}"/> |
|||
</Style> |
|||
</UserControl.Styles> |
|||
<ScrollViewer VerticalScrollBarVisibility="Auto" |
|||
HorizontalScrollBarVisibility="Hidden"> |
|||
<Grid HorizontalAlignment="Left" VerticalAlignment="Top" RowDefinitions="Auto,Auto,Auto,Auto,Auto,*" ColumnDefinitions="Auto,6,*" Width="246"> |
|||
<Label Target="firstNameEdit" Grid.Row="0" Grid.Column="0">_First name</Label> |
|||
<TextBox Name="firstNameEdit" Grid.Column="2" Grid.Row="0" Text="{Binding FirstName}"></TextBox> |
|||
<Label Target="lastNameEdit" Grid.Row="1" Grid.Column="0">_Last name</Label> |
|||
<TextBox Name="lastNameEdit" Grid.Column="2" Grid.Row="1" Text="{Binding LastName}"></TextBox> |
|||
<Label Target="bannedCheck" Grid.Row="2" Grid.Column="0">_Banned</Label> |
|||
<CheckBox Name="bannedCheck" Grid.Column="2" Grid.Row="2" IsChecked="{Binding IsBanned}"></CheckBox> |
|||
<GridSplitter Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.RowSpan="3" > |
|||
</GridSplitter> |
|||
<StackPanel Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="3" Orientation="Horizontal" HorizontalAlignment="Right"> |
|||
<Button IsCancel="True" Command="{Binding #_labelsPage.DoCancel}">Cancel</Button> |
|||
<Button IsDefault="True" Command="{Binding #_labelsPage.DoSave}">Save</Button> |
|||
</StackPanel> |
|||
</Grid> |
|||
</ScrollViewer> |
|||
</UserControl> |
|||
@ -0,0 +1,43 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
using ControlCatalog.Models; |
|||
using ReactiveUI; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class LabelsPage : UserControl |
|||
{ |
|||
private Person _person; |
|||
|
|||
public LabelsPage() |
|||
{ |
|||
CreateDefaultPerson(); |
|||
this.InitializeComponent(); |
|||
} |
|||
|
|||
private void CreateDefaultPerson() |
|||
{ |
|||
DataContext = _person = new Person |
|||
{ |
|||
FirstName = "John", |
|||
LastName = "Doe", |
|||
IsBanned = true, |
|||
}; |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
|
|||
public void DoSave() |
|||
{ |
|||
|
|||
} |
|||
public void DoCancel() |
|||
{ |
|||
CreateDefaultPerson(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,10 +1,13 @@ |
|||
using System.Reflection; |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Metadata; |
|||
|
|||
#if SIGNED_BUILD
|
|||
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] |
|||
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] |
|||
|
|||
#else
|
|||
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests")] |
|||
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport")] |
|||
#endif
|
|||
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] |
|||
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Collections")] |
|||
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")] |
|||
|
|||
@ -0,0 +1,75 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Text; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Data; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Label control. Focuses <see cref="Target"/> on pointer click or access key press (Alt + accessKey)
|
|||
/// </summary>
|
|||
public class Label : ContentControl |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Target"/> Direct property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<Label, IInputElement> TargetProperty = |
|||
AvaloniaProperty.RegisterDirect<Label, IInputElement>(nameof(Target), lbl => lbl.Target, (lbl, inp) => lbl.Target = inp); |
|||
|
|||
/// <summary>
|
|||
/// Label focus target storage field
|
|||
/// </summary>
|
|||
private IInputElement _target; |
|||
|
|||
/// <summary>
|
|||
/// Label focus Target
|
|||
/// </summary>
|
|||
[ResolveByName] |
|||
public IInputElement Target |
|||
{ |
|||
get => _target; |
|||
set => SetAndRaise(TargetProperty, ref _target, value); |
|||
} |
|||
|
|||
static Label() |
|||
{ |
|||
AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<Label>((lbl, args) => lbl.LabelActivated(args)); |
|||
// IsTabStopProperty.OverrideDefaultValue<Label>(false)
|
|||
FocusableProperty.OverrideDefaultValue<Label>(false); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes instance of <see cref="Label"/> control
|
|||
/// </summary>
|
|||
public Label() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Method which focuses <see cref="Target"/> input element
|
|||
/// </summary>
|
|||
private void LabelActivated(RoutedEventArgs args) |
|||
{ |
|||
Target?.Focus(); |
|||
args.Handled = Target != null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Handler of <see cref="IInputElement.PointerPressed"/> event
|
|||
/// </summary>
|
|||
/// <param name="e">Event Arguments</param>
|
|||
protected override void OnPointerPressed(PointerPressedEventArgs e) |
|||
{ |
|||
if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonPressed) |
|||
{ |
|||
LabelActivated(e); |
|||
} |
|||
base.OnPointerPressed(e); |
|||
} |
|||
} |
|||
} |
|||
@ -1,7 +1,9 @@ |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Metadata; |
|||
|
|||
#if SIGNED_BUILD
|
|||
[assembly: InternalsVisibleTo("Avalonia.Layout.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] |
|||
|
|||
#else
|
|||
[assembly: InternalsVisibleTo("Avalonia.Layout.UnitTests")] |
|||
#endif
|
|||
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Layout")] |
|||
|
|||
|
|||
@ -0,0 +1,17 @@ |
|||
<Style xmlns="https://github.com/avaloniaui" Selector="Label"> |
|||
<Setter Property="Padding" Value="3"/> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<ContentPresenter Name="PART_ContentPresenter" |
|||
Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
ContentTemplate="{TemplateBinding ContentTemplate}" |
|||
Content="{TemplateBinding Content}" |
|||
Padding="{TemplateBinding Padding}" |
|||
RecognizesAccessKey="True" |
|||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" |
|||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
@ -0,0 +1,17 @@ |
|||
<Style xmlns="https://github.com/avaloniaui" Selector="Label"> |
|||
<Setter Property="Padding" Value="3"/> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<ContentPresenter Name="PART_ContentPresenter" |
|||
Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
ContentTemplate="{TemplateBinding ContentTemplate}" |
|||
Content="{TemplateBinding Content}" |
|||
Padding="{TemplateBinding Padding}" |
|||
RecognizesAccessKey="True" |
|||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" |
|||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
@ -0,0 +1,55 @@ |
|||
using Avalonia.Media.Immutable; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// Paints an area with a swept circular gradient.
|
|||
/// </summary>
|
|||
public sealed class ConicGradientBrush : GradientBrush, IConicGradientBrush |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Center"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<RelativePoint> CenterProperty = |
|||
AvaloniaProperty.Register<RadialGradientBrush, RelativePoint>( |
|||
nameof(Center), |
|||
RelativePoint.Center); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Angle"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> AngleProperty = |
|||
AvaloniaProperty.Register<RadialGradientBrush, double>( |
|||
nameof(Angle), |
|||
0); |
|||
|
|||
static ConicGradientBrush() |
|||
{ |
|||
AffectsRender<ConicGradientBrush>(CenterProperty, AngleProperty); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the center point of the gradient.
|
|||
/// </summary>
|
|||
public RelativePoint Center |
|||
{ |
|||
get { return GetValue(CenterProperty); } |
|||
set { SetValue(CenterProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the angle of the start and end of the sweep, measured from above the center point.
|
|||
/// </summary>
|
|||
public double Angle |
|||
{ |
|||
get { return GetValue(AngleProperty); } |
|||
set { SetValue(AngleProperty, value); } |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override IBrush ToImmutable() |
|||
{ |
|||
return new ImmutableConicGradientBrush(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// Paints an area with a conic gradient.
|
|||
/// </summary>
|
|||
public interface IConicGradientBrush : IGradientBrush |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the center point for the gradient.
|
|||
/// </summary>
|
|||
RelativePoint Center { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the starting angle for the gradient in degrees, measured from
|
|||
/// the point above the center point.
|
|||
/// </summary>
|
|||
double Angle { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Media.Immutable |
|||
{ |
|||
/// <summary>
|
|||
/// A brush that draws with a sweep gradient.
|
|||
/// </summary>
|
|||
public class ImmutableConicGradientBrush : ImmutableGradientBrush, IConicGradientBrush |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ImmutableConicGradientBrush"/> class.
|
|||
/// </summary>
|
|||
/// <param name="gradientStops">The gradient stops.</param>
|
|||
/// <param name="opacity">The opacity of the brush.</param>
|
|||
/// <param name="spreadMethod">The spread method.</param>
|
|||
/// <param name="center">The center point for the gradient.</param>
|
|||
/// <param name="angle">The starting angle for the gradient.</param>
|
|||
public ImmutableConicGradientBrush( |
|||
IReadOnlyList<ImmutableGradientStop> gradientStops, |
|||
double opacity = 1, |
|||
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, |
|||
RelativePoint? center = null, |
|||
double angle = 0) |
|||
: base(gradientStops, opacity, spreadMethod) |
|||
{ |
|||
Center = center ?? RelativePoint.Center; |
|||
Angle = angle; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ImmutableConicGradientBrush"/> class.
|
|||
/// </summary>
|
|||
/// <param name="source">The brush from which this brush's properties should be copied.</param>
|
|||
public ImmutableConicGradientBrush(ConicGradientBrush source) |
|||
: base(source) |
|||
{ |
|||
Center = source.Center; |
|||
Angle = source.Angle; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public RelativePoint Center { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public double Angle { get; } |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 7d15a9227461cf4d62bc2ed251fcc25c96b28aaf |
|||
Subproject commit 5420df861ce6f2be5ead9efa078fe7242ce88f18 |
|||
@ -1,4 +1,8 @@ |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
#if SIGNED_BUILD
|
|||
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] |
|||
[assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] |
|||
#else
|
|||
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")] |
|||
[assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests")] |
|||
#endif
|
|||
|
|||
@ -1,41 +1,191 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using Avalonia.Controls.Platform.Surfaces; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Win32.Interop; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Win32 |
|||
{ |
|||
class FramebufferManager : IFramebufferPlatformSurface, IDisposable |
|||
internal class FramebufferManager : IFramebufferPlatformSurface, IDisposable |
|||
{ |
|||
private const int _bytesPerPixel = 4; |
|||
private const PixelFormat _format = PixelFormat.Bgra8888; |
|||
|
|||
private readonly IntPtr _hwnd; |
|||
private WindowFramebuffer _fb; |
|||
private readonly object _lock; |
|||
private readonly Action _onDisposeAction; |
|||
|
|||
private FramebufferData? _framebufferData; |
|||
|
|||
public FramebufferManager(IntPtr hwnd) |
|||
{ |
|||
_hwnd = hwnd; |
|||
_lock = new object(); |
|||
_onDisposeAction = DrawAndUnlock; |
|||
} |
|||
|
|||
public ILockedFramebuffer Lock() |
|||
{ |
|||
UnmanagedMethods.GetClientRect(_hwnd, out var rc); |
|||
Monitor.Enter(_lock); |
|||
|
|||
var width = Math.Max(1, rc.right - rc.left); |
|||
var height = Math.Max(1, rc.bottom - rc.top); |
|||
LockedFramebuffer? fb = null; |
|||
|
|||
if ((_fb == null || _fb.Size.Width != width || _fb.Size.Height != height)) |
|||
try |
|||
{ |
|||
_fb?.Deallocate(); |
|||
_fb = null; |
|||
_fb = new WindowFramebuffer(_hwnd, new PixelSize(width, height)); |
|||
} |
|||
UnmanagedMethods.GetClientRect(_hwnd, out var rc); |
|||
|
|||
var width = Math.Max(1, rc.right - rc.left); |
|||
var height = Math.Max(1, rc.bottom - rc.top); |
|||
|
|||
if (_framebufferData is null || _framebufferData?.Size.Width != width || _framebufferData?.Size.Height != height) |
|||
{ |
|||
_framebufferData?.Dispose(); |
|||
|
|||
_framebufferData = AllocateFramebufferData(width, height); |
|||
} |
|||
|
|||
var framebufferData = _framebufferData.Value; |
|||
|
|||
return _fb; |
|||
return fb = new LockedFramebuffer( |
|||
framebufferData.Data.Address, framebufferData.Size, framebufferData.RowBytes, |
|||
GetCurrentDpi(), _format, _onDisposeAction); |
|||
} |
|||
finally |
|||
{ |
|||
// We free the lock when for whatever reason framebuffer was not created.
|
|||
// This allows for a potential retry later.
|
|||
if (fb is null) |
|||
{ |
|||
Monitor.Exit(_lock); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_fb?.Deallocate(); |
|||
_fb = null; |
|||
lock (_lock) |
|||
{ |
|||
_framebufferData?.Dispose(); |
|||
_framebufferData = null; |
|||
} |
|||
} |
|||
|
|||
private void DrawAndUnlock() |
|||
{ |
|||
try |
|||
{ |
|||
if (_framebufferData.HasValue) |
|||
DrawToWindow(_hwnd, _framebufferData.Value); |
|||
} |
|||
finally |
|||
{ |
|||
Monitor.Exit(_lock); |
|||
} |
|||
} |
|||
|
|||
private Vector GetCurrentDpi() |
|||
{ |
|||
if (UnmanagedMethods.ShCoreAvailable) |
|||
{ |
|||
var monitor = |
|||
UnmanagedMethods.MonitorFromWindow(_hwnd, UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); |
|||
|
|||
if (UnmanagedMethods.GetDpiForMonitor( |
|||
monitor, |
|||
UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, |
|||
out var dpix, |
|||
out var dpiy) == 0) |
|||
{ |
|||
return new Vector(dpix, dpiy); |
|||
} |
|||
} |
|||
|
|||
return new Vector(96, 96); |
|||
} |
|||
|
|||
private static FramebufferData AllocateFramebufferData(int width, int height) |
|||
{ |
|||
var bitmapBlob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(width * height * _bytesPerPixel); |
|||
|
|||
return new FramebufferData(bitmapBlob, width, height); |
|||
} |
|||
|
|||
private static void DrawToDevice(FramebufferData framebufferData, IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, |
|||
int srcY = 0, int width = -1, |
|||
int height = -1) |
|||
{ |
|||
if (width == -1) |
|||
width = framebufferData.Size.Width; |
|||
if (height == -1) |
|||
height = framebufferData.Size.Height; |
|||
|
|||
var bmpInfo = framebufferData.Header; |
|||
|
|||
UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint)width, (uint)height, srcX, srcY, |
|||
0, (uint)framebufferData.Size.Height, framebufferData.Data.Address, ref bmpInfo, 0); |
|||
} |
|||
|
|||
private static bool DrawToWindow(IntPtr hWnd, FramebufferData framebufferData, int destX = 0, int destY = 0, int srcX = 0, |
|||
int srcY = 0, int width = -1, |
|||
int height = -1) |
|||
{ |
|||
if (framebufferData.Data.IsDisposed) |
|||
throw new ObjectDisposedException("Framebuffer"); |
|||
|
|||
if (hWnd == IntPtr.Zero) |
|||
return false; |
|||
|
|||
var hDC = UnmanagedMethods.GetDC(hWnd); |
|||
|
|||
if (hDC == IntPtr.Zero) |
|||
return false; |
|||
|
|||
try |
|||
{ |
|||
DrawToDevice(framebufferData, hDC, destX, destY, srcX, srcY, width, height); |
|||
} |
|||
finally |
|||
{ |
|||
UnmanagedMethods.ReleaseDC(hWnd, hDC); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private readonly struct FramebufferData |
|||
{ |
|||
public IUnmanagedBlob Data { get; } |
|||
|
|||
public PixelSize Size { get; } |
|||
|
|||
public int RowBytes => Size.Width * _bytesPerPixel; |
|||
|
|||
public UnmanagedMethods.BITMAPINFOHEADER Header { get; } |
|||
|
|||
public FramebufferData(IUnmanagedBlob data, int width, int height) |
|||
{ |
|||
Data = data; |
|||
Size = new PixelSize(width, height); |
|||
|
|||
var header = new UnmanagedMethods.BITMAPINFOHEADER(); |
|||
header.Init(); |
|||
|
|||
header.biPlanes = 1; |
|||
header.biBitCount = _bytesPerPixel * 8; |
|||
header.Init(); |
|||
|
|||
header.biWidth = width; |
|||
header.biHeight = -height; |
|||
|
|||
Header = header; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
Data.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,100 +0,0 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Win32.Interop; |
|||
using PixelFormat = Avalonia.Platform.PixelFormat; |
|||
|
|||
namespace Avalonia.Win32 |
|||
{ |
|||
public class WindowFramebuffer : ILockedFramebuffer |
|||
{ |
|||
private readonly IntPtr _handle; |
|||
private IUnmanagedBlob _bitmapBlob; |
|||
private UnmanagedMethods.BITMAPINFOHEADER _bmpInfo; |
|||
|
|||
public WindowFramebuffer(IntPtr handle, PixelSize size) |
|||
{ |
|||
|
|||
if (size.Width <= 0) |
|||
throw new ArgumentException("Width is less than zero"); |
|||
if (size.Height <= 0) |
|||
throw new ArgumentException("Height is less than zero"); |
|||
_handle = handle; |
|||
_bmpInfo.Init(); |
|||
_bmpInfo.biPlanes = 1; |
|||
_bmpInfo.biBitCount = 32; |
|||
_bmpInfo.Init(); |
|||
_bmpInfo.biWidth = size.Width; |
|||
_bmpInfo.biHeight = -size.Height; |
|||
_bitmapBlob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(size.Width * size.Height * 4); |
|||
} |
|||
|
|||
~WindowFramebuffer() |
|||
{ |
|||
Deallocate(); |
|||
} |
|||
|
|||
public IntPtr Address => _bitmapBlob.Address; |
|||
public int RowBytes => Size.Width * 4; |
|||
public PixelFormat Format => PixelFormat.Bgra8888; |
|||
|
|||
public Vector Dpi |
|||
{ |
|||
get |
|||
{ |
|||
if (UnmanagedMethods.ShCoreAvailable) |
|||
{ |
|||
uint dpix, dpiy; |
|||
|
|||
var monitor = UnmanagedMethods.MonitorFromWindow(_handle, |
|||
UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); |
|||
|
|||
if (UnmanagedMethods.GetDpiForMonitor( |
|||
monitor, |
|||
UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, |
|||
out dpix, |
|||
out dpiy) == 0) |
|||
{ |
|||
return new Vector(dpix, dpiy); |
|||
} |
|||
} |
|||
return new Vector(96, 96); |
|||
} |
|||
} |
|||
|
|||
public PixelSize Size => new PixelSize(_bmpInfo.biWidth, -_bmpInfo.biHeight); |
|||
|
|||
public void DrawToDevice(IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, |
|||
int height = -1) |
|||
{ |
|||
if (width == -1) |
|||
width = Size.Width; |
|||
if (height == -1) |
|||
height = Size.Height; |
|||
UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint) width, (uint) height, srcX, srcY, |
|||
0, (uint)Size.Height, _bitmapBlob.Address, ref _bmpInfo, 0); |
|||
} |
|||
|
|||
public bool DrawToWindow(IntPtr hWnd, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, |
|||
int height = -1) |
|||
{ |
|||
if (_bitmapBlob.IsDisposed) |
|||
throw new ObjectDisposedException("Framebuffer"); |
|||
if (hWnd == IntPtr.Zero) |
|||
return false; |
|||
IntPtr hDC = UnmanagedMethods.GetDC(hWnd); |
|||
if (hDC == IntPtr.Zero) |
|||
return false; |
|||
DrawToDevice(hDC, destX, destY, srcX, srcY, width, height); |
|||
UnmanagedMethods.ReleaseDC(hWnd, hDC); |
|||
return true; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
//It's not an *actual* dispose. This call means "We are done drawing"
|
|||
DrawToWindow(_handle); |
|||
} |
|||
|
|||
public void Deallocate() => _bitmapBlob.Dispose(); |
|||
} |
|||
} |
|||
@ -0,0 +1,178 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Media; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
#if AVALONIA_SKIA
|
|||
namespace Avalonia.Skia.RenderTests |
|||
#else
|
|||
namespace Avalonia.Direct2D1.RenderTests.Media |
|||
#endif
|
|||
{ |
|||
public class ConicGradientBrushTests : TestBase |
|||
{ |
|||
public ConicGradientBrushTests() : base(@"Media\ConicGradientBrush") |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ConicGradientBrush_RedBlue() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Border |
|||
{ |
|||
Background = new ConicGradientBrush |
|||
{ |
|||
GradientStops = |
|||
{ |
|||
new GradientStop { Color = Colors.Red, Offset = 0 }, |
|||
new GradientStop { Color = Colors.Blue, Offset = 1 } |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
await RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ConicGradientBrush_RedBlue_Rotation() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Border |
|||
{ |
|||
Background = new ConicGradientBrush |
|||
{ |
|||
GradientStops = |
|||
{ |
|||
new GradientStop { Color = Colors.Red, Offset = 0 }, |
|||
new GradientStop { Color = Colors.Blue, Offset = 1 } |
|||
}, |
|||
Angle = 90 |
|||
} |
|||
} |
|||
}; |
|||
|
|||
await RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ConicGradientBrush_RedBlue_Center() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Border |
|||
{ |
|||
Background = new ConicGradientBrush |
|||
{ |
|||
GradientStops = |
|||
{ |
|||
new GradientStop { Color = Colors.Red, Offset = 0 }, |
|||
new GradientStop { Color = Colors.Blue, Offset = 1 } |
|||
}, |
|||
Center = new RelativePoint(0.25, 0.25, RelativeUnit.Relative) |
|||
} |
|||
} |
|||
}; |
|||
|
|||
await RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ConicGradientBrush_RedBlue_Center_and_Rotation() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Border |
|||
{ |
|||
Background = new ConicGradientBrush |
|||
{ |
|||
GradientStops = |
|||
{ |
|||
new GradientStop { Color = Colors.Red, Offset = 0 }, |
|||
new GradientStop { Color = Colors.Blue, Offset = 1 } |
|||
}, |
|||
Center = new RelativePoint(0.25, 0.25, RelativeUnit.Relative), |
|||
Angle = 90 |
|||
} |
|||
} |
|||
}; |
|||
|
|||
await RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ConicGradientBrush_RedBlue_SoftEdge() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Border |
|||
{ |
|||
Background = new ConicGradientBrush |
|||
{ |
|||
GradientStops = |
|||
{ |
|||
new GradientStop { Color = Colors.Red, Offset = 0 }, |
|||
new GradientStop { Color = Colors.Blue, Offset = 0.5 }, |
|||
new GradientStop { Color = Colors.Red, Offset = 1 }, |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
await RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ConicGradientBrush_Umbrella() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Border |
|||
{ |
|||
Background = new ConicGradientBrush |
|||
{ |
|||
GradientStops = |
|||
{ |
|||
new GradientStop { Color = Colors.Red, Offset = 0 }, |
|||
new GradientStop { Color = Colors.Yellow, Offset = 0.1667 }, |
|||
new GradientStop { Color = Colors.Lime, Offset = 0.3333 }, |
|||
new GradientStop { Color = Colors.Aqua, Offset = 0.5000 }, |
|||
new GradientStop { Color = Colors.Blue, Offset = 0.6667 }, |
|||
new GradientStop { Color = Colors.Magenta, Offset = 0.8333 }, |
|||
new GradientStop { Color = Colors.Red, Offset = 1 }, |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
await RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 527 B |
|
After Width: | Height: | Size: 601 B |
|
After Width: | Height: | Size: 601 B |
|
After Width: | Height: | Size: 601 B |
|
After Width: | Height: | Size: 601 B |
|
After Width: | Height: | Size: 601 B |
|
After Width: | Height: | Size: 601 B |
|
After Width: | Height: | Size: 527 B |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 11 KiB |