committed by
GitHub
14 changed files with 440 additions and 135 deletions
@ -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="{Binding #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="{Binding #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="{Binding #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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
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>
|
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -1,41 +1,191 @@ |
|||||
using System; |
using System; |
||||
|
using System.Threading; |
||||
using Avalonia.Controls.Platform.Surfaces; |
using Avalonia.Controls.Platform.Surfaces; |
||||
using Avalonia.Platform; |
using Avalonia.Platform; |
||||
using Avalonia.Win32.Interop; |
using Avalonia.Win32.Interop; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
namespace Avalonia.Win32 |
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 readonly IntPtr _hwnd; |
||||
private WindowFramebuffer _fb; |
private readonly object _lock; |
||||
|
private readonly Action _onDisposeAction; |
||||
|
|
||||
|
private FramebufferData? _framebufferData; |
||||
|
|
||||
public FramebufferManager(IntPtr hwnd) |
public FramebufferManager(IntPtr hwnd) |
||||
{ |
{ |
||||
_hwnd = hwnd; |
_hwnd = hwnd; |
||||
|
_lock = new object(); |
||||
|
_onDisposeAction = DrawAndUnlock; |
||||
} |
} |
||||
|
|
||||
public ILockedFramebuffer Lock() |
public ILockedFramebuffer Lock() |
||||
{ |
{ |
||||
UnmanagedMethods.GetClientRect(_hwnd, out var rc); |
Monitor.Enter(_lock); |
||||
|
|
||||
var width = Math.Max(1, rc.right - rc.left); |
LockedFramebuffer? fb = null; |
||||
var height = Math.Max(1, rc.bottom - rc.top); |
|
||||
|
|
||||
if ((_fb == null || _fb.Size.Width != width || _fb.Size.Height != height)) |
try |
||||
{ |
{ |
||||
_fb?.Deallocate(); |
UnmanagedMethods.GetClientRect(_hwnd, out var rc); |
||||
_fb = null; |
|
||||
_fb = new WindowFramebuffer(_hwnd, new PixelSize(width, height)); |
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() |
public void Dispose() |
||||
{ |
{ |
||||
_fb?.Deallocate(); |
lock (_lock) |
||||
_fb = null; |
{ |
||||
|
_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(); |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue