committed by
GitHub
131 changed files with 4244 additions and 941 deletions
@ -0,0 +1,6 @@ |
|||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="HarfBuzzSharp" Version="2.6.1-rc.153" /> |
||||
|
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1-rc.153" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -1,6 +1,6 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="SkiaSharp" Version="1.68.0" /> |
<PackageReference Include="SkiaSharp" Version="1.68.1-rc.153" /> |
||||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.2" /> |
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1-rc.153" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -0,0 +1,23 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace Avalonia |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides extension methods for enums.
|
||||
|
/// </summary>
|
||||
|
public static class EnumExtensions |
||||
|
{ |
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static unsafe bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum |
||||
|
{ |
||||
|
var intValue = *(int*)&value; |
||||
|
var intFlag = *(int*)&flag; |
||||
|
|
||||
|
return (intValue & intFlag) == intFlag; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,210 +1,841 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
// This source file is adapted from the Windows Presentation Foundation project.
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
// (https://github.com/dotnet/wpf/)
|
||||
|
//
|
||||
|
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
|
||||
|
|
||||
using System; |
using System; |
||||
using System.Collections.Generic; |
using System.Diagnostics; |
||||
using System.Linq; |
using Avalonia.Collections; |
||||
using Avalonia.Controls.Primitives; |
using Avalonia.Controls.Primitives; |
||||
using Avalonia.Input; |
using Avalonia.Input; |
||||
|
using Avalonia.Interactivity; |
||||
using Avalonia.Layout; |
using Avalonia.Layout; |
||||
using Avalonia.VisualTree; |
using Avalonia.Media; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
namespace Avalonia.Controls |
namespace Avalonia.Controls |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Represents the control that redistributes space between columns or rows of a Grid control.
|
/// Represents the control that redistributes space between columns or rows of a <see cref="Grid"/> control.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <remarks>
|
|
||||
/// Unlike WPF GridSplitter, Avalonia GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext.
|
|
||||
/// </remarks>
|
|
||||
public class GridSplitter : Thumb |
public class GridSplitter : Thumb |
||||
{ |
{ |
||||
private List<DefinitionBase> _definitions; |
/// <summary>
|
||||
|
/// Defines the <see cref="ResizeDirection"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AvaloniaProperty<GridResizeDirection> ResizeDirectionProperty = |
||||
|
AvaloniaProperty.Register<GridSplitter, GridResizeDirection>(nameof(ResizeDirection)); |
||||
|
|
||||
private Grid _grid; |
/// <summary>
|
||||
|
/// Defines the <see cref="ResizeBehavior"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AvaloniaProperty<GridResizeBehavior> ResizeBehaviorProperty = |
||||
|
AvaloniaProperty.Register<GridSplitter, GridResizeBehavior>(nameof(ResizeBehavior)); |
||||
|
|
||||
private DefinitionBase _nextDefinition; |
/// <summary>
|
||||
|
/// Defines the <see cref="ShowsPreview"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AvaloniaProperty<bool> ShowsPreviewProperty = |
||||
|
AvaloniaProperty.Register<GridSplitter, bool>(nameof(ShowsPreview)); |
||||
|
|
||||
private Orientation _orientation; |
/// <summary>
|
||||
|
/// Defines the <see cref="KeyboardIncrement"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AvaloniaProperty<double> KeyboardIncrementProperty = |
||||
|
AvaloniaProperty.Register<GridSplitter, double>(nameof(KeyboardIncrement), 10d); |
||||
|
|
||||
private DefinitionBase _prevDefinition; |
/// <summary>
|
||||
|
/// Defines the <see cref="DragIncrement"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AvaloniaProperty<double> DragIncrementProperty = |
||||
|
AvaloniaProperty.Register<GridSplitter, double>(nameof(DragIncrement), 1d); |
||||
|
|
||||
private void GetDeltaConstraints(out double min, out double max) |
/// <summary>
|
||||
|
/// Defines the <see cref="PreviewContent"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AvaloniaProperty<ITemplate<IControl>> PreviewContentProperty = |
||||
|
AvaloniaProperty.Register<GridSplitter, ITemplate<IControl>>(nameof(PreviewContent)); |
||||
|
|
||||
|
private static readonly Cursor s_columnSplitterCursor = new Cursor(StandardCursorType.SizeWestEast); |
||||
|
private static readonly Cursor s_rowSplitterCursor = new Cursor(StandardCursorType.SizeNorthSouth); |
||||
|
|
||||
|
private ResizeData _resizeData; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates whether the Splitter resizes the Columns, Rows, or Both.
|
||||
|
/// </summary>
|
||||
|
public GridResizeDirection ResizeDirection |
||||
{ |
{ |
||||
var prevDefinitionLen = GetActualLength(_prevDefinition); |
get => GetValue(ResizeDirectionProperty); |
||||
var prevDefinitionMin = GetMinLength(_prevDefinition); |
set => SetValue(ResizeDirectionProperty, value); |
||||
var prevDefinitionMax = GetMaxLength(_prevDefinition); |
} |
||||
|
|
||||
var nextDefinitionLen = GetActualLength(_nextDefinition); |
/// <summary>
|
||||
var nextDefinitionMin = GetMinLength(_nextDefinition); |
/// Indicates which Columns or Rows the Splitter resizes.
|
||||
var nextDefinitionMax = GetMaxLength(_nextDefinition); |
/// </summary>
|
||||
// Determine the minimum and maximum the columns can be resized
|
public GridResizeBehavior ResizeBehavior |
||||
min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen); |
{ |
||||
max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin); |
get => GetValue(ResizeBehaviorProperty); |
||||
|
set => SetValue(ResizeBehaviorProperty, value); |
||||
} |
} |
||||
|
|
||||
protected override void OnDragDelta(VectorEventArgs e) |
/// <summary>
|
||||
|
/// Indicates whether to Preview the column resizing without updating layout.
|
||||
|
/// </summary>
|
||||
|
public bool ShowsPreview |
||||
{ |
{ |
||||
// WPF doesn't change anything when spliter is in the last row/column
|
get => GetValue(ShowsPreviewProperty); |
||||
// but resizes the splitter row/column when it's the first one.
|
set => SetValue(ShowsPreviewProperty, value); |
||||
// this is different, but more internally consistent.
|
} |
||||
if (_prevDefinition == null || _nextDefinition == null) |
|
||||
return; |
/// <summary>
|
||||
|
/// The Distance to move the splitter when pressing the keyboard arrow keys.
|
||||
|
/// </summary>
|
||||
|
public double KeyboardIncrement |
||||
|
{ |
||||
|
get => GetValue(KeyboardIncrementProperty); |
||||
|
set => SetValue(KeyboardIncrementProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Restricts splitter to move a multiple of the specified units.
|
||||
|
/// </summary>
|
||||
|
public double DragIncrement |
||||
|
{ |
||||
|
get => GetValue(DragIncrementProperty); |
||||
|
set => SetValue(DragIncrementProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets content that will be shown when <see cref="ShowsPreview"/> is enabled and user starts resize operation.
|
||||
|
/// </summary>
|
||||
|
public ITemplate<IControl> PreviewContent |
||||
|
{ |
||||
|
get => GetValue(PreviewContentProperty); |
||||
|
set => SetValue(PreviewContentProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts BasedOnAlignment direction to Rows, Columns, or Both depending on its width/height.
|
||||
|
/// </summary>
|
||||
|
internal GridResizeDirection GetEffectiveResizeDirection() |
||||
|
{ |
||||
|
GridResizeDirection direction = ResizeDirection; |
||||
|
|
||||
|
if (direction != GridResizeDirection.Auto) |
||||
|
{ |
||||
|
return direction; |
||||
|
} |
||||
|
|
||||
|
// When HorizontalAlignment is Left, Right or Center, resize Columns.
|
||||
|
if (HorizontalAlignment != HorizontalAlignment.Stretch) |
||||
|
{ |
||||
|
direction = GridResizeDirection.Columns; |
||||
|
} |
||||
|
else if (VerticalAlignment != VerticalAlignment.Stretch) |
||||
|
{ |
||||
|
direction = GridResizeDirection.Rows; |
||||
|
} |
||||
|
else if (Bounds.Width <= Bounds.Height) // Fall back to Width vs Height.
|
||||
|
{ |
||||
|
direction = GridResizeDirection.Columns; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
direction = GridResizeDirection.Rows; |
||||
|
} |
||||
|
|
||||
var delta = _orientation == Orientation.Vertical ? e.Vector.X : e.Vector.Y; |
return direction; |
||||
double max; |
} |
||||
double min; |
|
||||
GetDeltaConstraints(out min, out max); |
|
||||
delta = Math.Min(Math.Max(delta, min), max); |
|
||||
|
|
||||
var prevIsStar = IsStar(_prevDefinition); |
/// <summary>
|
||||
var nextIsStar = IsStar(_nextDefinition); |
/// Convert BasedOnAlignment to Next/Prev/Both depending on alignment and Direction.
|
||||
|
/// </summary>
|
||||
|
private GridResizeBehavior GetEffectiveResizeBehavior(GridResizeDirection direction) |
||||
|
{ |
||||
|
GridResizeBehavior resizeBehavior = ResizeBehavior; |
||||
|
|
||||
if (prevIsStar && nextIsStar) |
if (resizeBehavior == GridResizeBehavior.BasedOnAlignment) |
||||
{ |
{ |
||||
foreach (var definition in _definitions) |
if (direction == GridResizeDirection.Columns) |
||||
{ |
{ |
||||
if (definition == _prevDefinition) |
switch (HorizontalAlignment) |
||||
{ |
{ |
||||
SetLengthInStars(_prevDefinition, GetActualLength(_prevDefinition) + delta); |
case HorizontalAlignment.Left: |
||||
|
resizeBehavior = GridResizeBehavior.PreviousAndCurrent; |
||||
|
break; |
||||
|
case HorizontalAlignment.Right: |
||||
|
resizeBehavior = GridResizeBehavior.CurrentAndNext; |
||||
|
break; |
||||
|
default: |
||||
|
resizeBehavior = GridResizeBehavior.PreviousAndNext; |
||||
|
break; |
||||
} |
} |
||||
else if (definition == _nextDefinition) |
} |
||||
|
else |
||||
|
{ |
||||
|
switch (VerticalAlignment) |
||||
{ |
{ |
||||
SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta); |
case VerticalAlignment.Top: |
||||
|
resizeBehavior = GridResizeBehavior.PreviousAndCurrent; |
||||
|
break; |
||||
|
case VerticalAlignment.Bottom: |
||||
|
resizeBehavior = GridResizeBehavior.CurrentAndNext; |
||||
|
break; |
||||
|
default: |
||||
|
resizeBehavior = GridResizeBehavior.PreviousAndNext; |
||||
|
break; |
||||
} |
} |
||||
else if (IsStar(definition)) |
} |
||||
|
} |
||||
|
|
||||
|
return resizeBehavior; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Removes preview adorner from the grid.
|
||||
|
/// </summary>
|
||||
|
private void RemovePreviewAdorner() |
||||
|
{ |
||||
|
if (_resizeData.Adorner != null) |
||||
|
{ |
||||
|
AdornerLayer layer = AdornerLayer.GetAdornerLayer(this); |
||||
|
layer.Children.Remove(_resizeData.Adorner); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initialize the data needed for resizing.
|
||||
|
/// </summary>
|
||||
|
private void InitializeData(bool showsPreview) |
||||
|
{ |
||||
|
// If not in a grid or can't resize, do nothing.
|
||||
|
if (Parent is Grid grid) |
||||
|
{ |
||||
|
GridResizeDirection resizeDirection = GetEffectiveResizeDirection(); |
||||
|
|
||||
|
// Setup data used for resizing.
|
||||
|
_resizeData = new ResizeData |
||||
|
{ |
||||
|
Grid = grid, |
||||
|
ShowsPreview = showsPreview, |
||||
|
ResizeDirection = resizeDirection, |
||||
|
SplitterLength = Math.Min(Bounds.Width, Bounds.Height), |
||||
|
ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection) |
||||
|
}; |
||||
|
|
||||
|
// Store the rows and columns to resize on drag events.
|
||||
|
if (!SetupDefinitionsToResize()) |
||||
|
{ |
||||
|
// Unable to resize, clear data.
|
||||
|
_resizeData = null; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Setup the preview in the adorner if ShowsPreview is true.
|
||||
|
SetupPreviewAdorner(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns true if GridSplitter can resize rows/columns.
|
||||
|
/// </summary>
|
||||
|
private bool SetupDefinitionsToResize() |
||||
|
{ |
||||
|
int gridSpan = GetValue(_resizeData.ResizeDirection == GridResizeDirection.Columns ? |
||||
|
Grid.ColumnSpanProperty : |
||||
|
Grid.RowSpanProperty); |
||||
|
|
||||
|
if (gridSpan == 1) |
||||
|
{ |
||||
|
var splitterIndex = GetValue(_resizeData.ResizeDirection == GridResizeDirection.Columns ? |
||||
|
Grid.ColumnProperty : |
||||
|
Grid.RowProperty); |
||||
|
|
||||
|
// Select the columns based on behavior.
|
||||
|
int index1, index2; |
||||
|
|
||||
|
switch (_resizeData.ResizeBehavior) |
||||
|
{ |
||||
|
case GridResizeBehavior.PreviousAndCurrent: |
||||
|
// Get current and previous.
|
||||
|
index1 = splitterIndex - 1; |
||||
|
index2 = splitterIndex; |
||||
|
break; |
||||
|
case GridResizeBehavior.CurrentAndNext: |
||||
|
// Get current and next.
|
||||
|
index1 = splitterIndex; |
||||
|
index2 = splitterIndex + 1; |
||||
|
break; |
||||
|
default: // GridResizeBehavior.PreviousAndNext.
|
||||
|
// Get previous and next.
|
||||
|
index1 = splitterIndex - 1; |
||||
|
index2 = splitterIndex + 1; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
// Get count of rows/columns in the resize direction.
|
||||
|
int count = _resizeData.ResizeDirection == GridResizeDirection.Columns ? |
||||
|
_resizeData.Grid.ColumnDefinitions.Count : |
||||
|
_resizeData.Grid.RowDefinitions.Count; |
||||
|
|
||||
|
if (index1 >= 0 && index2 < count) |
||||
|
{ |
||||
|
_resizeData.SplitterIndex = splitterIndex; |
||||
|
|
||||
|
_resizeData.Definition1Index = index1; |
||||
|
_resizeData.Definition1 = GetGridDefinition(_resizeData.Grid, index1, _resizeData.ResizeDirection); |
||||
|
_resizeData.OriginalDefinition1Length = |
||||
|
_resizeData.Definition1.UserSizeValueCache; // Save Size if user cancels.
|
||||
|
_resizeData.OriginalDefinition1ActualLength = GetActualLength(_resizeData.Definition1); |
||||
|
|
||||
|
_resizeData.Definition2Index = index2; |
||||
|
_resizeData.Definition2 = GetGridDefinition(_resizeData.Grid, index2, _resizeData.ResizeDirection); |
||||
|
_resizeData.OriginalDefinition2Length = |
||||
|
_resizeData.Definition2.UserSizeValueCache; // Save Size if user cancels.
|
||||
|
_resizeData.OriginalDefinition2ActualLength = GetActualLength(_resizeData.Definition2); |
||||
|
|
||||
|
// Determine how to resize the columns.
|
||||
|
bool isStar1 = IsStar(_resizeData.Definition1); |
||||
|
bool isStar2 = IsStar(_resizeData.Definition2); |
||||
|
|
||||
|
if (isStar1 && isStar2) |
||||
{ |
{ |
||||
SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars.
|
// If they are both stars, resize both.
|
||||
|
_resizeData.SplitBehavior = SplitBehavior.Split; |
||||
} |
} |
||||
|
else |
||||
|
{ |
||||
|
// One column is fixed width, resize the first one that is fixed.
|
||||
|
_resizeData.SplitBehavior = !isStar1 ? SplitBehavior.Resize1 : SplitBehavior.Resize2; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
} |
} |
||||
} |
} |
||||
else if (prevIsStar) |
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create the preview adorner and add it to the adorner layer.
|
||||
|
/// </summary>
|
||||
|
private void SetupPreviewAdorner() |
||||
|
{ |
||||
|
if (_resizeData.ShowsPreview) |
||||
{ |
{ |
||||
SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta); |
// Get the adorner layer and add an adorner to it.
|
||||
|
var adornerLayer = AdornerLayer.GetAdornerLayer(_resizeData.Grid); |
||||
|
|
||||
|
var previewContent = PreviewContent; |
||||
|
|
||||
|
// Can't display preview.
|
||||
|
if (adornerLayer == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
IControl builtPreviewContent = previewContent?.Build(); |
||||
|
|
||||
|
_resizeData.Adorner = new PreviewAdorner(builtPreviewContent); |
||||
|
|
||||
|
AdornerLayer.SetAdornedElement(_resizeData.Adorner, this); |
||||
|
|
||||
|
adornerLayer.Children.Add(_resizeData.Adorner); |
||||
|
|
||||
|
// Get constraints on preview's translation.
|
||||
|
GetDeltaConstraints(out _resizeData.MinChange, out _resizeData.MaxChange); |
||||
} |
} |
||||
else if (nextIsStar) |
} |
||||
|
|
||||
|
protected override void OnPointerEnter(PointerEventArgs e) |
||||
|
{ |
||||
|
base.OnPointerEnter(e); |
||||
|
|
||||
|
GridResizeDirection direction = GetEffectiveResizeDirection(); |
||||
|
|
||||
|
switch (direction) |
||||
{ |
{ |
||||
SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta); |
case GridResizeDirection.Columns: |
||||
|
Cursor = s_columnSplitterCursor; |
||||
|
break; |
||||
|
case GridResizeDirection.Rows: |
||||
|
Cursor = s_rowSplitterCursor; |
||||
|
break; |
||||
} |
} |
||||
else |
} |
||||
|
|
||||
|
protected override void OnLostFocus(RoutedEventArgs e) |
||||
|
{ |
||||
|
base.OnLostFocus(e); |
||||
|
|
||||
|
if (_resizeData != null) |
||||
{ |
{ |
||||
SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta); |
CancelResize(); |
||||
SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta); |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
private double GetActualLength(DefinitionBase definition) |
protected override void OnDragStarted(VectorEventArgs e) |
||||
{ |
{ |
||||
if (definition == null) |
base.OnDragStarted(e); |
||||
return 0; |
|
||||
var columnDefinition = definition as ColumnDefinition; |
// TODO: Looks like that sometimes thumb will raise multiple drag started events.
|
||||
return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight; |
// Debug.Assert(_resizeData == null, "_resizeData is not null, DragCompleted was not called");
|
||||
|
|
||||
|
if (_resizeData != null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
InitializeData(ShowsPreview); |
||||
} |
} |
||||
|
|
||||
private double GetMinLength(DefinitionBase definition) |
protected override void OnDragDelta(VectorEventArgs e) |
||||
{ |
{ |
||||
if (definition == null) |
base.OnDragDelta(e); |
||||
return 0; |
|
||||
var columnDefinition = definition as ColumnDefinition; |
if (_resizeData != null) |
||||
return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight; |
{ |
||||
|
double horizontalChange = e.Vector.X; |
||||
|
double verticalChange = e.Vector.Y; |
||||
|
|
||||
|
// Round change to nearest multiple of DragIncrement.
|
||||
|
double dragIncrement = DragIncrement; |
||||
|
horizontalChange = Math.Round(horizontalChange / dragIncrement) * dragIncrement; |
||||
|
verticalChange = Math.Round(verticalChange / dragIncrement) * dragIncrement; |
||||
|
|
||||
|
if (_resizeData.ShowsPreview) |
||||
|
{ |
||||
|
// Set the Translation of the Adorner to the distance from the thumb.
|
||||
|
if (_resizeData.ResizeDirection == GridResizeDirection.Columns) |
||||
|
{ |
||||
|
_resizeData.Adorner.OffsetX = Math.Min( |
||||
|
Math.Max(horizontalChange, _resizeData.MinChange), |
||||
|
_resizeData.MaxChange); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_resizeData.Adorner.OffsetY = Math.Min( |
||||
|
Math.Max(verticalChange, _resizeData.MinChange), |
||||
|
_resizeData.MaxChange); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Directly update the grid.
|
||||
|
MoveSplitter(horizontalChange, verticalChange); |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
|
|
||||
private double GetMaxLength(DefinitionBase definition) |
protected override void OnDragCompleted(VectorEventArgs e) |
||||
{ |
{ |
||||
if (definition == null) |
base.OnDragCompleted(e); |
||||
return 0; |
|
||||
var columnDefinition = definition as ColumnDefinition; |
if (_resizeData != null) |
||||
return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight; |
{ |
||||
|
if (_resizeData.ShowsPreview) |
||||
|
{ |
||||
|
// Update the grid.
|
||||
|
MoveSplitter(_resizeData.Adorner.OffsetX, _resizeData.Adorner.OffsetY); |
||||
|
RemovePreviewAdorner(); |
||||
|
} |
||||
|
|
||||
|
_resizeData = null; |
||||
|
} |
||||
} |
} |
||||
|
|
||||
private bool IsStar(DefinitionBase definition) |
protected override void OnKeyDown(KeyEventArgs e) |
||||
{ |
{ |
||||
var columnDefinition = definition as ColumnDefinition; |
Key key = e.Key; |
||||
return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar; |
|
||||
|
switch (key) |
||||
|
{ |
||||
|
case Key.Escape: |
||||
|
if (_resizeData != null) |
||||
|
{ |
||||
|
CancelResize(); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
|
||||
|
case Key.Left: |
||||
|
e.Handled = KeyboardMoveSplitter(-KeyboardIncrement, 0); |
||||
|
break; |
||||
|
case Key.Right: |
||||
|
e.Handled = KeyboardMoveSplitter(KeyboardIncrement, 0); |
||||
|
break; |
||||
|
case Key.Up: |
||||
|
e.Handled = KeyboardMoveSplitter(0, -KeyboardIncrement); |
||||
|
break; |
||||
|
case Key.Down: |
||||
|
e.Handled = KeyboardMoveSplitter(0, KeyboardIncrement); |
||||
|
break; |
||||
|
} |
||||
} |
} |
||||
|
|
||||
private void SetLengthInStars(DefinitionBase definition, double value) |
/// <summary>
|
||||
|
/// Cancels the resize operation.
|
||||
|
/// </summary>
|
||||
|
private void CancelResize() |
||||
{ |
{ |
||||
var columnDefinition = definition as ColumnDefinition; |
// Restore original column/row lengths.
|
||||
if (columnDefinition != null) |
if (_resizeData.ShowsPreview) |
||||
{ |
{ |
||||
columnDefinition.Width = new GridLength(value, GridUnitType.Star); |
RemovePreviewAdorner(); |
||||
} |
} |
||||
else |
else // Reset the columns/rows lengths to the saved values.
|
||||
{ |
{ |
||||
((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star); |
SetDefinitionLength(_resizeData.Definition1, _resizeData.OriginalDefinition1Length); |
||||
|
SetDefinitionLength(_resizeData.Definition2, _resizeData.OriginalDefinition2Length); |
||||
} |
} |
||||
|
|
||||
|
_resizeData = null; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns true if the row/column has a star length.
|
||||
|
/// </summary>
|
||||
|
private static bool IsStar(DefinitionBase definition) |
||||
|
{ |
||||
|
return definition.UserSizeValueCache.IsStar; |
||||
} |
} |
||||
|
|
||||
private void SetLength(DefinitionBase definition, double value) |
/// <summary>
|
||||
|
/// Gets Column or Row definition at index from grid based on resize direction.
|
||||
|
/// </summary>
|
||||
|
private static DefinitionBase GetGridDefinition(Grid grid, int index, GridResizeDirection direction) |
||||
{ |
{ |
||||
var columnDefinition = definition as ColumnDefinition; |
return direction == GridResizeDirection.Columns ? |
||||
if (columnDefinition != null) |
(DefinitionBase)grid.ColumnDefinitions[index] : |
||||
|
(DefinitionBase)grid.RowDefinitions[index]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Retrieves the ActualWidth or ActualHeight of the definition depending on its type Column or Row.
|
||||
|
/// </summary>
|
||||
|
private double GetActualLength(DefinitionBase definition) |
||||
|
{ |
||||
|
var column = definition as ColumnDefinition; |
||||
|
|
||||
|
return column?.ActualWidth ?? ((RowDefinition)definition).ActualHeight; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets Column or Row definition at index from grid based on resize direction.
|
||||
|
/// </summary>
|
||||
|
private static void SetDefinitionLength(DefinitionBase definition, GridLength length) |
||||
|
{ |
||||
|
definition.SetValue( |
||||
|
definition is ColumnDefinition ? ColumnDefinition.WidthProperty : RowDefinition.HeightProperty, length); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get the minimum and maximum Delta can be given definition constraints (MinWidth/MaxWidth).
|
||||
|
/// </summary>
|
||||
|
private void GetDeltaConstraints(out double minDelta, out double maxDelta) |
||||
|
{ |
||||
|
double definition1Len = GetActualLength(_resizeData.Definition1); |
||||
|
double definition1Min = _resizeData.Definition1.UserMinSizeValueCache; |
||||
|
double definition1Max = _resizeData.Definition1.UserMaxSizeValueCache; |
||||
|
|
||||
|
double definition2Len = GetActualLength(_resizeData.Definition2); |
||||
|
double definition2Min = _resizeData.Definition2.UserMinSizeValueCache; |
||||
|
double definition2Max = _resizeData.Definition2.UserMaxSizeValueCache; |
||||
|
|
||||
|
// Set MinWidths to be greater than width of splitter.
|
||||
|
if (_resizeData.SplitterIndex == _resizeData.Definition1Index) |
||||
{ |
{ |
||||
columnDefinition.Width = new GridLength(value); |
definition1Min = Math.Max(definition1Min, _resizeData.SplitterLength); |
||||
} |
} |
||||
else |
else if (_resizeData.SplitterIndex == _resizeData.Definition2Index) |
||||
{ |
{ |
||||
((RowDefinition)definition).Height = new GridLength(value); |
definition2Min = Math.Max(definition2Min, _resizeData.SplitterLength); |
||||
} |
} |
||||
|
|
||||
|
// Determine the minimum and maximum the columns can be resized.
|
||||
|
minDelta = -Math.Min(definition1Len - definition1Min, definition2Max - definition2Len); |
||||
|
maxDelta = Math.Min(definition1Max - definition1Len, definition2Len - definition2Min); |
||||
} |
} |
||||
|
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
/// <summary>
|
||||
|
/// Sets the length of definition1 and definition2.
|
||||
|
/// </summary>
|
||||
|
private void SetLengths(double definition1Pixels, double definition2Pixels) |
||||
{ |
{ |
||||
base.OnAttachedToVisualTree(e); |
// For the case where both definition1 and 2 are stars, update all star values to match their current pixel values.
|
||||
_grid = this.GetVisualParent<Grid>(); |
if (_resizeData.SplitBehavior == SplitBehavior.Split) |
||||
|
{ |
||||
|
var definitions = _resizeData.ResizeDirection == GridResizeDirection.Columns ? |
||||
|
(IAvaloniaReadOnlyList<DefinitionBase>)_resizeData.Grid.ColumnDefinitions : |
||||
|
(IAvaloniaReadOnlyList<DefinitionBase>)_resizeData.Grid.RowDefinitions; |
||||
|
|
||||
_orientation = DetectOrientation(); |
var definitionsCount = definitions.Count; |
||||
|
|
||||
|
for (var i = 0; i < definitionsCount; i++) |
||||
|
{ |
||||
|
DefinitionBase definition = definitions[i]; |
||||
|
|
||||
int definitionIndex; //row or col
|
// For each definition, if it is a star, set is value to ActualLength in stars
|
||||
if (_orientation == Orientation.Vertical) |
// This makes 1 star == 1 pixel in length
|
||||
|
if (i == _resizeData.Definition1Index) |
||||
|
{ |
||||
|
SetDefinitionLength(definition, new GridLength(definition1Pixels, GridUnitType.Star)); |
||||
|
} |
||||
|
else if (i == _resizeData.Definition2Index) |
||||
|
{ |
||||
|
SetDefinitionLength(definition, new GridLength(definition2Pixels, GridUnitType.Star)); |
||||
|
} |
||||
|
else if (IsStar(definition)) |
||||
|
{ |
||||
|
SetDefinitionLength(definition, new GridLength(GetActualLength(definition), GridUnitType.Star)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else if (_resizeData.SplitBehavior == SplitBehavior.Resize1) |
||||
{ |
{ |
||||
Cursor = new Cursor(StandardCursorType.SizeWestEast); |
SetDefinitionLength(_resizeData.Definition1, new GridLength(definition1Pixels)); |
||||
_definitions = _grid.ColumnDefinitions.Cast<DefinitionBase>().ToList(); |
|
||||
definitionIndex = GetValue(Grid.ColumnProperty); |
|
||||
PseudoClasses.Add(":vertical"); |
|
||||
} |
} |
||||
else |
else |
||||
{ |
{ |
||||
Cursor = new Cursor(StandardCursorType.SizeNorthSouth); |
SetDefinitionLength(_resizeData.Definition2, new GridLength(definition2Pixels)); |
||||
definitionIndex = GetValue(Grid.RowProperty); |
} |
||||
_definitions = _grid.RowDefinitions.Cast<DefinitionBase>().ToList(); |
} |
||||
PseudoClasses.Add(":horizontal"); |
|
||||
|
/// <summary>
|
||||
|
/// Move the splitter by the given Delta's in the horizontal and vertical directions.
|
||||
|
/// </summary>
|
||||
|
private void MoveSplitter(double horizontalChange, double verticalChange) |
||||
|
{ |
||||
|
Debug.Assert(_resizeData != null, "_resizeData should not be null when calling MoveSplitter"); |
||||
|
|
||||
|
// Calculate the offset to adjust the splitter.
|
||||
|
var delta = _resizeData.ResizeDirection == GridResizeDirection.Columns ? horizontalChange : verticalChange; |
||||
|
|
||||
|
DefinitionBase definition1 = _resizeData.Definition1; |
||||
|
DefinitionBase definition2 = _resizeData.Definition2; |
||||
|
|
||||
|
if (definition1 != null && definition2 != null) |
||||
|
{ |
||||
|
double actualLength1 = GetActualLength(definition1); |
||||
|
double actualLength2 = GetActualLength(definition2); |
||||
|
|
||||
|
// When splitting, Check to see if the total pixels spanned by the definitions
|
||||
|
// is the same asbefore starting resize. If not cancel the drag
|
||||
|
if (_resizeData.SplitBehavior == SplitBehavior.Split && |
||||
|
!MathUtilities.AreClose( |
||||
|
actualLength1 + actualLength2, |
||||
|
_resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength)) |
||||
|
{ |
||||
|
CancelResize(); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
GetDeltaConstraints(out var min, out var max); |
||||
|
|
||||
|
// Constrain Delta to Min/MaxWidth of columns
|
||||
|
delta = Math.Min(Math.Max(delta, min), max); |
||||
|
|
||||
|
double definition1LengthNew = actualLength1 + delta; |
||||
|
double definition2LengthNew = actualLength1 + actualLength2 - definition1LengthNew; |
||||
|
|
||||
|
SetLengths(definition1LengthNew, definition2LengthNew); |
||||
} |
} |
||||
|
} |
||||
|
|
||||
if (definitionIndex > 0) |
/// <summary>
|
||||
_prevDefinition = _definitions[definitionIndex - 1]; |
/// Move the splitter using the Keyboard (Don't show preview).
|
||||
|
/// </summary>
|
||||
|
private bool KeyboardMoveSplitter(double horizontalChange, double verticalChange) |
||||
|
{ |
||||
|
// If moving with the mouse, ignore keyboard motion.
|
||||
|
if (_resizeData != null) |
||||
|
{ |
||||
|
return false; // Don't handle the event.
|
||||
|
} |
||||
|
|
||||
|
// Don't show preview.
|
||||
|
InitializeData(false); |
||||
|
|
||||
|
// Check that we are actually able to resize.
|
||||
|
if (_resizeData == null) |
||||
|
{ |
||||
|
return false; // Don't handle the event.
|
||||
|
} |
||||
|
|
||||
if (definitionIndex < _definitions.Count - 1) |
MoveSplitter(horizontalChange, verticalChange); |
||||
_nextDefinition = _definitions[definitionIndex + 1]; |
|
||||
|
_resizeData = null; |
||||
|
|
||||
|
return true; |
||||
} |
} |
||||
|
|
||||
private Orientation DetectOrientation() |
/// <summary>
|
||||
|
/// This adorner draws the preview for the <see cref="GridSplitter"/>.
|
||||
|
/// It also positions the adorner.
|
||||
|
/// </summary>
|
||||
|
private sealed class PreviewAdorner : Decorator |
||||
{ |
{ |
||||
if (!_grid.ColumnDefinitions.Any()) |
private readonly TranslateTransform _translation; |
||||
return Orientation.Horizontal; |
private readonly Decorator _decorator; |
||||
if (!_grid.RowDefinitions.Any()) |
|
||||
return Orientation.Vertical; |
public PreviewAdorner(IControl previewControl) |
||||
|
{ |
||||
|
// Add a decorator to perform translations.
|
||||
|
_translation = new TranslateTransform(); |
||||
|
|
||||
|
_decorator = new Decorator |
||||
|
{ |
||||
|
Child = previewControl, |
||||
|
RenderTransform = _translation |
||||
|
}; |
||||
|
|
||||
|
Child = _decorator; |
||||
|
} |
||||
|
|
||||
var col = GetValue(Grid.ColumnProperty); |
/// <summary>
|
||||
var row = GetValue(Grid.RowProperty); |
/// The Preview's Offset in the X direction from the GridSplitter.
|
||||
var width = _grid.ColumnDefinitions[col].Width; |
/// </summary>
|
||||
var height = _grid.RowDefinitions[row].Height; |
public double OffsetX |
||||
if (width.IsAuto && !height.IsAuto) |
|
||||
{ |
{ |
||||
return Orientation.Vertical; |
get => _translation.X; |
||||
|
set => _translation.X = value; |
||||
} |
} |
||||
if (!width.IsAuto && height.IsAuto) |
|
||||
|
/// <summary>
|
||||
|
/// The Preview's Offset in the Y direction from the GridSplitter.
|
||||
|
/// </summary>
|
||||
|
public double OffsetY |
||||
{ |
{ |
||||
return Orientation.Horizontal; |
get => _translation.Y; |
||||
|
set => _translation.Y = value; |
||||
} |
} |
||||
if (_grid.Children.OfType<Control>() // Decision based on other controls in the same column
|
|
||||
.Where(c => Grid.GetColumn(c) == col) |
protected override Size ArrangeOverride(Size finalSize) |
||||
.Any(c => c.GetType() != typeof(GridSplitter))) |
|
||||
{ |
{ |
||||
return Orientation.Horizontal; |
// Adorners always get clipped to the owner control. In this case we want
|
||||
|
// to constrain size to the splitter size but draw on top of the parent grid.
|
||||
|
Clip = null; |
||||
|
|
||||
|
return base.ArrangeOverride(finalSize); |
||||
} |
} |
||||
return Orientation.Vertical; |
|
||||
} |
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// <see cref="GridSplitter"/> has special Behavior when columns are fixed.
|
||||
|
/// If the left column is fixed, splitter will only resize that column.
|
||||
|
/// Else if the right column is fixed, splitter will only resize the right column.
|
||||
|
/// </summary>
|
||||
|
private enum SplitBehavior |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Both columns/rows are star lengths.
|
||||
|
/// </summary>
|
||||
|
Split, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize 1 only.
|
||||
|
/// </summary>
|
||||
|
Resize1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize 2 only.
|
||||
|
/// </summary>
|
||||
|
Resize2 |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores data during the resizing operation.
|
||||
|
/// </summary>
|
||||
|
private class ResizeData |
||||
|
{ |
||||
|
public bool ShowsPreview; |
||||
|
public PreviewAdorner Adorner; |
||||
|
|
||||
|
// The constraints to keep the Preview within valid ranges.
|
||||
|
public double MinChange; |
||||
|
public double MaxChange; |
||||
|
|
||||
|
// The grid to Resize.
|
||||
|
public Grid Grid; |
||||
|
|
||||
|
// Cache of Resize Direction and Behavior.
|
||||
|
public GridResizeDirection ResizeDirection; |
||||
|
public GridResizeBehavior ResizeBehavior; |
||||
|
|
||||
|
// The columns/rows to resize.
|
||||
|
public DefinitionBase Definition1; |
||||
|
public DefinitionBase Definition2; |
||||
|
|
||||
|
// Are the columns/rows star lengths.
|
||||
|
public SplitBehavior SplitBehavior; |
||||
|
|
||||
|
// The index of the splitter.
|
||||
|
public int SplitterIndex; |
||||
|
|
||||
|
// The indices of the columns/rows.
|
||||
|
public int Definition1Index; |
||||
|
public int Definition2Index; |
||||
|
|
||||
|
// The original lengths of Definition1 and Definition2 (to restore lengths if user cancels resize).
|
||||
|
public GridLength OriginalDefinition1Length; |
||||
|
public GridLength OriginalDefinition2Length; |
||||
|
public double OriginalDefinition1ActualLength; |
||||
|
public double OriginalDefinition2ActualLength; |
||||
|
|
||||
|
// The minimum of Width/Height of Splitter. Used to ensure splitter
|
||||
|
// isn't hidden by resizing a row/column smaller than the splitter.
|
||||
|
public double SplitterLength; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enum to indicate whether <see cref="GridSplitter"/> resizes Columns or Rows.
|
||||
|
/// </summary>
|
||||
|
public enum GridResizeDirection |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Determines whether to resize rows or columns based on its Alignment and
|
||||
|
/// width compared to height.
|
||||
|
/// </summary>
|
||||
|
Auto, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize columns when dragging Splitter.
|
||||
|
/// </summary>
|
||||
|
Columns, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize rows when dragging Splitter.
|
||||
|
/// </summary>
|
||||
|
Rows |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enum to indicate what Columns or Rows the <see cref="GridSplitter"/> resizes.
|
||||
|
/// </summary>
|
||||
|
public enum GridResizeBehavior |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Determine which columns or rows to resize based on its Alignment.
|
||||
|
/// </summary>
|
||||
|
BasedOnAlignment, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize the current and next Columns or Rows.
|
||||
|
/// </summary>
|
||||
|
CurrentAndNext, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize the previous and current Columns or Rows.
|
||||
|
/// </summary>
|
||||
|
PreviousAndCurrent, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize the previous and next Columns or Rows.
|
||||
|
/// </summary>
|
||||
|
PreviousAndNext |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,12 +1,19 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Collections; |
||||
|
using System.Collections.Specialized; |
||||
|
|
||||
namespace Avalonia.Controls.Presenters |
namespace Avalonia.Controls.Presenters |
||||
{ |
{ |
||||
public interface IItemsPresenter : IPresenter |
public interface IItemsPresenter : IPresenter |
||||
{ |
{ |
||||
|
IEnumerable Items { get; set; } |
||||
|
|
||||
IPanel Panel { get; } |
IPanel Panel { get; } |
||||
|
|
||||
|
void ItemsChanged(NotifyCollectionChangedEventArgs e); |
||||
|
|
||||
void ScrollIntoView(object item); |
void ScrollIntoView(object item); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,105 @@ |
|||||
|
<Window xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
MaxWidth="400" |
||||
|
MaxHeight="475" |
||||
|
MinWidth="430" |
||||
|
MinHeight="475" |
||||
|
Title="About Avalonia" |
||||
|
Background="Purple" |
||||
|
FontFamily="/Assets/Roboto-Light.ttf#Roboto" |
||||
|
x:Class="Avalonia.Dialogs.AboutAvaloniaDialog"> |
||||
|
<Window.Styles> |
||||
|
<Style> |
||||
|
<Style.Resources> |
||||
|
<DrawingGroup x:Key="AvaloniaLogo"> |
||||
|
<GeometryDrawing Geometry="m 150.66581 0.66454769 c -54.77764 0 -101.0652 38.86360031 -112.62109 90.33008031 a 26.1 26.1 0 0 1 18.92187 25.070312 26.1 26.1 0 0 1 -18.91992 25.08202 c 11.56024 51.46073 57.8456 90.31837 112.61914 90.31837 63.37832 0 115.40039 -52.02207 115.40039 -115.40039 0 -63.378322 -52.02207 -115.40039231 -115.40039 -115.40039231 z m 0 60.00000031 c 30.95192 0 55.40039 24.44847 55.40039 55.400392 0 30.9519 -24.44847 55.40039 -55.40039 55.40039 -30.95191 0 -55.40039 -24.44848 -55.40039 -55.40039 0 -30.951922 24.44848 -55.400392 55.40039 -55.400392 z"> |
||||
|
<GeometryDrawing.Brush> |
||||
|
<LinearGradientBrush StartPoint="272,411" EndPoint="435,248"> |
||||
|
<LinearGradientBrush.GradientStops> |
||||
|
<GradientStop Color="#B0B0B0" Offset="0" /> |
||||
|
<GradientStop Color="#FFFFFF" Offset="0.6784" /> |
||||
|
</LinearGradientBrush.GradientStops> |
||||
|
</LinearGradientBrush> |
||||
|
</GeometryDrawing.Brush> |
||||
|
</GeometryDrawing> |
||||
|
<GeometryDrawing Brush="#B0B0B0"> |
||||
|
<GeometryDrawing.Geometry> |
||||
|
<EllipseGeometry Rect="9.6,95.8,40.6,40.6" /> |
||||
|
</GeometryDrawing.Geometry> |
||||
|
</GeometryDrawing> |
||||
|
<GeometryDrawing Brush="White"> |
||||
|
<GeometryDrawing.Geometry> |
||||
|
<RectangleGeometry Rect="206.06355, 114.56503,60,116.2" /> |
||||
|
</GeometryDrawing.Geometry> |
||||
|
</GeometryDrawing> |
||||
|
</DrawingGroup> |
||||
|
</Style.Resources> |
||||
|
</Style> |
||||
|
<Style Selector="Rectangle.Abstract"> |
||||
|
<Setter Property="Fill" Value="White" /> |
||||
|
<Setter Property="Width" Value="750" /> |
||||
|
<Setter Property="Height" Value="700" /> |
||||
|
</Style> |
||||
|
<Style Selector="Button.Hyperlink"> |
||||
|
<Setter Property="Background" Value="Transparent" /> |
||||
|
<Setter Property="BorderThickness" Value="0" /> |
||||
|
<Setter Property="Margin" Value="-5"/> |
||||
|
<Setter Property="Foreground" Value="#419df2" /> |
||||
|
<Setter Property="Command" Value="{Binding OpenBrowser}" /> |
||||
|
<Setter Property="Content" Value="{Binding $self.CommandParameter}" /> |
||||
|
<Setter Property="HorizontalAlignment" Value="Center" /> |
||||
|
<Setter Property="Cursor" Value="Hand" /> |
||||
|
</Style> |
||||
|
</Window.Styles> |
||||
|
<Grid Background="#4A255D"> |
||||
|
<Canvas> |
||||
|
<Rectangle Classes="Abstract" Canvas.Top="90" Opacity="0.132"> |
||||
|
<Rectangle.RenderTransform> |
||||
|
<RotateTransform Angle="-2" /> |
||||
|
</Rectangle.RenderTransform> |
||||
|
</Rectangle> |
||||
|
<Rectangle Classes="Abstract" Canvas.Top="95" Opacity="0.3"> |
||||
|
<Rectangle.RenderTransform> |
||||
|
<RotateTransform Angle="-4" /> |
||||
|
</Rectangle.RenderTransform> |
||||
|
</Rectangle> |
||||
|
<Rectangle Classes="Abstract" Canvas.Top="100" Opacity="0.3"> |
||||
|
<Rectangle.RenderTransform> |
||||
|
<RotateTransform Angle="-8" /> |
||||
|
</Rectangle.RenderTransform> |
||||
|
</Rectangle> |
||||
|
<Rectangle Classes="Abstract" Canvas.Top="105" Opacity="0.7"> |
||||
|
<Rectangle.RenderTransform> |
||||
|
<RotateTransform Angle="-12" /> |
||||
|
</Rectangle.RenderTransform> |
||||
|
</Rectangle> |
||||
|
</Canvas> |
||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="18"> |
||||
|
<Border Height="70" Width="70"> |
||||
|
<DrawingPresenter Drawing="{DynamicResource AvaloniaLogo}" /> |
||||
|
</Border> |
||||
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,-10,0,0"> |
||||
|
<TextBlock Text="Avalonia 0.9" FontSize="40" Foreground="White" /> |
||||
|
<TextBlock Text="Development Build" Margin="0,-10,0,0" FontSize="15" Foreground="White" /> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Spacing="20" Margin="10 60 10 0"> |
||||
|
<TextBlock Text="This product is built with the Avalonia cross-platform UI Framework. 

Avalonia is made possible by the generous support of it's contributors and community." TextWrapping="Wrap" TextAlignment="Center" HorizontalAlignment="Center" /> |
||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" > |
||||
|
<TextBlock Text="Main source repository | " /> |
||||
|
<Button Classes="Hyperlink" CommandParameter="https://github.com/AvaloniaUI/Avalonia/" /> |
||||
|
</StackPanel> |
||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" > |
||||
|
<TextBlock Text="Documentation and Information | " /> |
||||
|
<Button Classes="Hyperlink" CommandParameter="https://avaloniaui.net/" /> |
||||
|
</StackPanel> |
||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" > |
||||
|
<TextBlock Text="Chat Room | " /> |
||||
|
<Button Classes="Hyperlink" CommandParameter="https://gitter.im/AvaloniaUI/Avalonia/" /> |
||||
|
</StackPanel> |
||||
|
</StackPanel> |
||||
|
<StackPanel VerticalAlignment="Bottom" Margin="10"> |
||||
|
<TextBlock Text="© 2019 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" /> |
||||
|
</StackPanel> |
||||
|
</Grid> |
||||
|
</Window> |
||||
@ -0,0 +1,62 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Diagnostics; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace Avalonia.Dialogs |
||||
|
{ |
||||
|
public class AboutAvaloniaDialog : Window |
||||
|
{ |
||||
|
public AboutAvaloniaDialog() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
DataContext = this; |
||||
|
} |
||||
|
|
||||
|
public static void OpenBrowser(string url) |
||||
|
{ |
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |
||||
|
{ |
||||
|
// If no associated application/json MimeType is found xdg-open opens retrun error
|
||||
|
// but it tries to open it anyway using the console editor (nano, vim, other..)
|
||||
|
ShellExec($"xdg-open {url}", waitForExit: false); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
using (Process process = Process.Start(new ProcessStartInfo |
||||
|
{ |
||||
|
FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? url : "open", |
||||
|
Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"-e {url}" : "", |
||||
|
CreateNoWindow = true, |
||||
|
UseShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) |
||||
|
})); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ShellExec(string cmd, bool waitForExit = true) |
||||
|
{ |
||||
|
var escapedArgs = cmd.Replace("\"", "\\\""); |
||||
|
|
||||
|
using (var process = Process.Start( |
||||
|
new ProcessStartInfo |
||||
|
{ |
||||
|
FileName = "/bin/sh", |
||||
|
Arguments = $"-c \"{escapedArgs}\"", |
||||
|
RedirectStandardOutput = true, |
||||
|
UseShellExecute = false, |
||||
|
CreateNoWindow = true, |
||||
|
WindowStyle = ProcessWindowStyle.Hidden |
||||
|
} |
||||
|
)) |
||||
|
{ |
||||
|
if (waitForExit) |
||||
|
{ |
||||
|
process.WaitForExit(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Binary file not shown.
@ -1,51 +1,23 @@ |
|||||
<Styles xmlns="https://github.com/avaloniaui"> |
<Styles xmlns="https://github.com/avaloniaui"> |
||||
<Style Selector="GridSplitter:vertical"> |
|
||||
<Setter Property="Width" Value="6"/> |
<Style Selector="GridSplitter"> |
||||
<Setter Property="Background" Value="{DynamicResource ThemeControlLowBrush}"/> |
<Setter Property="Focusable" Value="True" /> |
||||
<Setter Property="Template"> |
<Setter Property="MinWidth" Value="6" /> |
||||
<ControlTemplate> |
<Setter Property="MinHeight" Value="6" /> |
||||
<Border Background="{TemplateBinding Background}"> |
<Setter Property="Background" Value="{DynamicResource ThemeControlMidBrush}" /> |
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"> |
<Setter Property="PreviewContent"> |
||||
<StackPanel.Styles> |
<Template> |
||||
<Style Selector="Ellipse"> |
<Rectangle Fill="{DynamicResource HighlightBrush}" /> |
||||
<Setter Property="HorizontalAlignment" Value="Center"/> |
</Template> |
||||
<Setter Property="Width" Value="4"/> |
|
||||
<Setter Property="Height" Value="4"/> |
|
||||
<Setter Property="Fill" Value="{DynamicResource ThemeControlMidBrush}"/> |
|
||||
<Setter Property="Margin" Value="1"/> |
|
||||
</Style> |
|
||||
</StackPanel.Styles> |
|
||||
<Ellipse/> |
|
||||
<Ellipse/> |
|
||||
<Ellipse/> |
|
||||
</StackPanel> |
|
||||
</Border> |
|
||||
</ControlTemplate> |
|
||||
</Setter> |
</Setter> |
||||
</Style> |
|
||||
<Style Selector="GridSplitter:horizontal"> |
|
||||
<Setter Property="Height" Value="6"/> |
|
||||
<Setter Property="Background" Value="{DynamicResource ThemeControlLowBrush}"/> |
|
||||
<Setter Property="Template"> |
<Setter Property="Template"> |
||||
<ControlTemplate> |
<ControlTemplate> |
||||
<Border Background="{TemplateBinding Background}"> |
<Border |
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> |
BorderBrush="{TemplateBinding BorderBrush}" |
||||
<StackPanel.Styles> |
BorderThickness="{TemplateBinding BorderThickness}" |
||||
<Style Selector="Ellipse"> |
Background="{TemplateBinding Background}"/> |
||||
<Setter Property="VerticalAlignment" Value="Center"/> |
|
||||
<Setter Property="Width" Value="4"/> |
|
||||
<Setter Property="Height" Value="4"/> |
|
||||
<Setter Property="Fill" Value="{DynamicResource ThemeControlMidBrush}"/> |
|
||||
<Setter Property="Margin" Value="1"/> |
|
||||
</Style> |
|
||||
</StackPanel.Styles> |
|
||||
<Ellipse/> |
|
||||
<Ellipse/> |
|
||||
<Ellipse/> |
|
||||
</StackPanel> |
|
||||
</Border> |
|
||||
</ControlTemplate> |
</ControlTemplate> |
||||
</Setter> |
</Setter> |
||||
</Style> |
</Style> |
||||
</Styles> |
|
||||
|
|
||||
|
</Styles> |
||||
|
|||||
@ -0,0 +1,112 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Media |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The font manager is used to query the system's installed fonts and is responsible for caching loaded fonts.
|
||||
|
/// It is also responsible for the font fallback.
|
||||
|
/// </summary>
|
||||
|
public abstract class FontManager |
||||
|
{ |
||||
|
public static readonly FontManager Default = CreateDefault(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the system's default font family's name.
|
||||
|
/// </summary>
|
||||
|
public string DefaultFontFamilyName |
||||
|
{ |
||||
|
get; |
||||
|
protected set; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get all installed fonts in the system.
|
||||
|
/// <param name="checkForUpdates">If <c>true</c> the font collection is updated.</param>
|
||||
|
/// </summary>
|
||||
|
public abstract IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get a cached typeface from specified parameters.
|
||||
|
/// </summary>
|
||||
|
/// <param name="fontFamily">The font family.</param>
|
||||
|
/// <param name="fontWeight">The font weight.</param>
|
||||
|
/// <param name="fontStyle">The font style.</param>
|
||||
|
/// <returns>
|
||||
|
/// The cached typeface.
|
||||
|
/// </returns>
|
||||
|
public abstract Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to match a specified character to a typeface that supports specified font properties.
|
||||
|
/// Returns <c>null</c> if no fallback was found.
|
||||
|
/// </summary>
|
||||
|
/// <param name="codepoint">The codepoint to match against.</param>
|
||||
|
/// <param name="fontWeight">The font weight.</param>
|
||||
|
/// <param name="fontStyle">The font style.</param>
|
||||
|
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
|
||||
|
/// <param name="culture">The culture.</param>
|
||||
|
/// <returns>
|
||||
|
/// The matched typeface.
|
||||
|
/// </returns>
|
||||
|
public abstract Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, |
||||
|
FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null); |
||||
|
|
||||
|
public static FontManager CreateDefault() |
||||
|
{ |
||||
|
var platformImpl = AvaloniaLocator.Current.GetService<IFontManagerImpl>(); |
||||
|
|
||||
|
if (platformImpl != null) |
||||
|
{ |
||||
|
return new PlatformFontManager(platformImpl); |
||||
|
} |
||||
|
|
||||
|
return new EmptyFontManager(); |
||||
|
} |
||||
|
|
||||
|
private class PlatformFontManager : FontManager |
||||
|
{ |
||||
|
private readonly IFontManagerImpl _platformImpl; |
||||
|
|
||||
|
public PlatformFontManager(IFontManagerImpl platformImpl) |
||||
|
{ |
||||
|
_platformImpl = platformImpl; |
||||
|
|
||||
|
DefaultFontFamilyName = _platformImpl.DefaultFontFamilyName; |
||||
|
} |
||||
|
|
||||
|
public override IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) => |
||||
|
_platformImpl.GetInstalledFontFamilyNames(checkForUpdates); |
||||
|
|
||||
|
public override Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) => |
||||
|
_platformImpl.GetTypeface(fontFamily, fontWeight, fontStyle); |
||||
|
|
||||
|
public override Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, |
||||
|
FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null) => |
||||
|
_platformImpl.MatchCharacter(codepoint, fontWeight, fontStyle, fontFamily, culture); |
||||
|
} |
||||
|
|
||||
|
private class EmptyFontManager : FontManager |
||||
|
{ |
||||
|
public EmptyFontManager() |
||||
|
{ |
||||
|
DefaultFontFamilyName = "Empty"; |
||||
|
} |
||||
|
|
||||
|
public override IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) => |
||||
|
new[] { DefaultFontFamilyName }; |
||||
|
|
||||
|
public override Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) => new Typeface(fontFamily, fontWeight, fontStyle); |
||||
|
|
||||
|
public override Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, |
||||
|
FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null) => null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,111 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Media |
||||
|
{ |
||||
|
public sealed class GlyphTypeface : IDisposable |
||||
|
{ |
||||
|
private static readonly IPlatformRenderInterface s_platformRenderInterface = |
||||
|
AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); |
||||
|
|
||||
|
public GlyphTypeface(Typeface typeface) : this(s_platformRenderInterface.CreateGlyphTypeface(typeface)) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public GlyphTypeface(IGlyphTypefaceImpl platformImpl) |
||||
|
{ |
||||
|
PlatformImpl = platformImpl; |
||||
|
} |
||||
|
|
||||
|
public IGlyphTypefaceImpl PlatformImpl { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the font design units per em.
|
||||
|
/// </summary>
|
||||
|
public short DesignEmHeight => PlatformImpl.DesignEmHeight; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended distance above the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int Ascent => PlatformImpl.Ascent; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended distance under the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int Descent => PlatformImpl.Descent; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended additional space between two lines of text in design em size.
|
||||
|
/// </summary>
|
||||
|
public int LineGap => PlatformImpl.LineGap; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended line height.
|
||||
|
/// </summary>
|
||||
|
public int LineHeight => Descent - Ascent + LineGap; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int UnderlinePosition => PlatformImpl.UnderlinePosition; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the thickness of the underline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int UnderlineThickness => PlatformImpl.UnderlineThickness; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int StrikethroughPosition => PlatformImpl.StrikethroughPosition; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the thickness of the underline in design em size.
|
||||
|
/// </summary>
|
||||
|
public int StrikethroughThickness => PlatformImpl.StrikethroughThickness; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an glyph index for the specified codepoint.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Returns <c>0</c> if a glyph isn't found.
|
||||
|
/// </remarks>
|
||||
|
/// <param name="codepoint">The codepoint.</param>
|
||||
|
/// <returns>
|
||||
|
/// A glyph index.
|
||||
|
/// </returns>
|
||||
|
public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="codepoints">The codepoints to map.</param>
|
||||
|
/// <returns></returns>
|
||||
|
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) => PlatformImpl.GetGlyphs(codepoints); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the glyph advance for the specified glyph.
|
||||
|
/// </summary>
|
||||
|
/// <param name="glyph">The glyph.</param>
|
||||
|
/// <returns>
|
||||
|
/// The advance.
|
||||
|
/// </returns>
|
||||
|
public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an array of glyph advances in design em size.
|
||||
|
/// </summary>
|
||||
|
/// <param name="glyphs">The glyph indices.</param>
|
||||
|
/// <returns></returns>
|
||||
|
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) => PlatformImpl.GetGlyphAdvances(glyphs); |
||||
|
|
||||
|
void IDisposable.Dispose() |
||||
|
{ |
||||
|
PlatformImpl?.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Platform |
||||
|
{ |
||||
|
public interface IFontManagerImpl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the system's default font family's name.
|
||||
|
/// </summary>
|
||||
|
string DefaultFontFamilyName { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get all installed fonts in the system.
|
||||
|
/// <param name="checkForUpdates">If <c>true</c> the font collection is updated.</param>
|
||||
|
/// </summary>
|
||||
|
IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get a typeface from specified parameters.
|
||||
|
/// </summary>
|
||||
|
/// <param name="fontFamily">The font family.</param>
|
||||
|
/// <param name="fontWeight">The font weight.</param>
|
||||
|
/// <param name="fontStyle">The font style.</param>
|
||||
|
/// <returns>
|
||||
|
/// The typeface.
|
||||
|
/// </returns>
|
||||
|
Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to match a specified character to a typeface that supports specified font properties.
|
||||
|
/// </summary>
|
||||
|
/// <param name="codepoint">The codepoint to match against.</param>
|
||||
|
/// <param name="fontWeight">The font weight.</param>
|
||||
|
/// <param name="fontStyle">The font style.</param>
|
||||
|
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
|
||||
|
/// <param name="culture">The culture.</param>
|
||||
|
/// <returns>
|
||||
|
/// The typeface.
|
||||
|
/// </returns>
|
||||
|
Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Platform |
||||
|
{ |
||||
|
public interface IGlyphTypefaceImpl : IDisposable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the font design units per em.
|
||||
|
/// </summary>
|
||||
|
short DesignEmHeight { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended distance above the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
int Ascent { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended distance under the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
int Descent { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the recommended additional space between two lines of text in design em size.
|
||||
|
/// </summary>
|
||||
|
int LineGap { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
int UnderlinePosition { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the thickness of the underline in design em size.
|
||||
|
/// </summary>
|
||||
|
int UnderlineThickness { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
|
||||
|
/// </summary>
|
||||
|
int StrikethroughPosition { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates the thickness of the underline in design em size.
|
||||
|
/// </summary>
|
||||
|
int StrikethroughThickness { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an glyph index for the specified codepoint.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Returns <c>0</c> if a glyph isn't found.
|
||||
|
/// </remarks>
|
||||
|
/// <param name="codepoint">The codepoint.</param>
|
||||
|
/// <returns>
|
||||
|
/// A glyph index.
|
||||
|
/// </returns>
|
||||
|
ushort GetGlyph(uint codepoint); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="codepoints">The codepoints to map.</param>
|
||||
|
/// <returns>
|
||||
|
/// An array of glyph indices.
|
||||
|
/// </returns>
|
||||
|
ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the glyph advance for the specified glyph.
|
||||
|
/// </summary>
|
||||
|
/// <param name="glyph">The glyph.</param>
|
||||
|
/// <returns>
|
||||
|
/// The advance.
|
||||
|
/// </returns>
|
||||
|
int GetGlyphAdvance(ushort glyph); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an array of glyph advances in design em size.
|
||||
|
/// </summary>
|
||||
|
/// <param name="glyphs">The glyph indices.</param>
|
||||
|
/// <returns>
|
||||
|
/// An array of glyph advances.
|
||||
|
/// </returns>
|
||||
|
int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
internal readonly struct FontKey : IEquatable<FontKey> |
||||
|
{ |
||||
|
public readonly FontStyle Style; |
||||
|
public readonly FontWeight Weight; |
||||
|
|
||||
|
public FontKey(FontWeight weight, FontStyle style) |
||||
|
{ |
||||
|
Style = style; |
||||
|
Weight = weight; |
||||
|
} |
||||
|
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
var hash = 17; |
||||
|
hash = hash * 31 + (int)Style; |
||||
|
hash = hash * 31 + (int)Weight; |
||||
|
|
||||
|
return hash; |
||||
|
} |
||||
|
|
||||
|
public override bool Equals(object other) |
||||
|
{ |
||||
|
return other is FontKey key && Equals(key); |
||||
|
} |
||||
|
|
||||
|
public bool Equals(FontKey other) |
||||
|
{ |
||||
|
return Style == other.Style && |
||||
|
Weight == other.Weight; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
internal class FontManagerImpl : IFontManagerImpl |
||||
|
{ |
||||
|
private SKFontManager _skFontManager = SKFontManager.Default; |
||||
|
|
||||
|
public FontManagerImpl() |
||||
|
{ |
||||
|
DefaultFontFamilyName = SKTypeface.Default.FamilyName; |
||||
|
} |
||||
|
|
||||
|
public string DefaultFontFamilyName { get; } |
||||
|
|
||||
|
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) |
||||
|
{ |
||||
|
if (checkForUpdates) |
||||
|
{ |
||||
|
_skFontManager = SKFontManager.CreateDefault(); |
||||
|
} |
||||
|
|
||||
|
return _skFontManager.FontFamilies; |
||||
|
} |
||||
|
|
||||
|
public Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) |
||||
|
{ |
||||
|
return TypefaceCache.Get(fontFamily.Name, fontWeight, fontStyle).Typeface; |
||||
|
} |
||||
|
|
||||
|
public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null) |
||||
|
{ |
||||
|
var fontFamilyName = FontFamily.Default.Name; |
||||
|
|
||||
|
if (culture == null) |
||||
|
{ |
||||
|
culture = CultureInfo.CurrentUICulture; |
||||
|
} |
||||
|
|
||||
|
if (fontFamily != null) |
||||
|
{ |
||||
|
foreach (var familyName in fontFamily.FamilyNames) |
||||
|
{ |
||||
|
var skTypeface = _skFontManager.MatchCharacter(familyName, (SKFontStyleWeight)fontWeight, |
||||
|
SKFontStyleWidth.Normal, |
||||
|
(SKFontStyleSlant)fontStyle, |
||||
|
new[] { culture.TwoLetterISOLanguageName, culture.ThreeLetterISOLanguageName }, codepoint); |
||||
|
|
||||
|
if (skTypeface == null) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
fontFamilyName = familyName; |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var skTypeface = _skFontManager.MatchCharacter(null, (SKFontStyleWeight)fontWeight, SKFontStyleWidth.Normal, |
||||
|
(SKFontStyleSlant)fontStyle, |
||||
|
new[] { culture.TwoLetterISOLanguageName, culture.ThreeLetterISOLanguageName }, codepoint); |
||||
|
|
||||
|
if (skTypeface != null) |
||||
|
{ |
||||
|
fontFamilyName = skTypeface.FamilyName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return GetTypeface(fontFamilyName, fontWeight, fontStyle); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,179 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using HarfBuzzSharp; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
public class GlyphTypefaceImpl : IGlyphTypefaceImpl |
||||
|
{ |
||||
|
private bool _isDisposed; |
||||
|
|
||||
|
public GlyphTypefaceImpl(Typeface typeface) |
||||
|
{ |
||||
|
Typeface = TypefaceCache.Get(typeface.FontFamily, typeface.Weight, typeface.Style).SKTypeface; |
||||
|
|
||||
|
Face = new Face(GetTable) |
||||
|
{ |
||||
|
UnitsPerEm = Typeface.UnitsPerEm |
||||
|
}; |
||||
|
|
||||
|
Font = new Font(Face); |
||||
|
|
||||
|
Font.SetFunctionsOpenType(); |
||||
|
|
||||
|
Font.GetScale(out var xScale, out _); |
||||
|
|
||||
|
DesignEmHeight = (short)xScale; |
||||
|
|
||||
|
if (!Font.TryGetHorizontalFontExtents(out var fontExtents)) |
||||
|
{ |
||||
|
Font.TryGetVerticalFontExtents(out fontExtents); |
||||
|
} |
||||
|
|
||||
|
Ascent = -fontExtents.Ascender; |
||||
|
|
||||
|
Descent = -fontExtents.Descender; |
||||
|
|
||||
|
LineGap = fontExtents.LineGap; |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition)) |
||||
|
{ |
||||
|
UnderlinePosition = underlinePosition; |
||||
|
} |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness)) |
||||
|
{ |
||||
|
UnderlineThickness = underlineThickness; |
||||
|
} |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition)) |
||||
|
{ |
||||
|
StrikethroughPosition = strikethroughPosition; |
||||
|
} |
||||
|
|
||||
|
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness)) |
||||
|
{ |
||||
|
StrikethroughThickness = strikethroughThickness; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Face Face { get; } |
||||
|
|
||||
|
public Font Font { get; } |
||||
|
|
||||
|
public SKTypeface Typeface { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public short DesignEmHeight { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int Ascent { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int Descent { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int LineGap { get; } |
||||
|
|
||||
|
//ToDo: Get these values from HarfBuzz
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int UnderlinePosition { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int UnderlineThickness { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int StrikethroughPosition { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int StrikethroughThickness { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public ushort GetGlyph(uint codepoint) |
||||
|
{ |
||||
|
if (Font.TryGetGlyph(codepoint, out var glyph)) |
||||
|
{ |
||||
|
return (ushort)glyph; |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) |
||||
|
{ |
||||
|
var glyphs = new ushort[codepoints.Length]; |
||||
|
|
||||
|
for (var i = 0; i < codepoints.Length; i++) |
||||
|
{ |
||||
|
if (Font.TryGetGlyph(codepoints[i], out var glyph)) |
||||
|
{ |
||||
|
glyphs[i] = (ushort)glyph; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return glyphs; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int GetGlyphAdvance(ushort glyph) |
||||
|
{ |
||||
|
return Font.GetHorizontalGlyphAdvance(glyph); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
|
||||
|
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) |
||||
|
{ |
||||
|
var glyphIndices = new uint[glyphs.Length]; |
||||
|
|
||||
|
for (var i = 0; i < glyphs.Length; i++) |
||||
|
{ |
||||
|
glyphIndices[i] = glyphs[i]; |
||||
|
} |
||||
|
|
||||
|
return Font.GetHorizontalGlyphAdvances(glyphIndices); |
||||
|
} |
||||
|
|
||||
|
private Blob GetTable(Face face, Tag tag) |
||||
|
{ |
||||
|
var size = Typeface.GetTableSize(tag); |
||||
|
|
||||
|
var data = Marshal.AllocCoTaskMem(size); |
||||
|
|
||||
|
var releaseDelegate = new ReleaseDelegate(() => Marshal.FreeCoTaskMem(data)); |
||||
|
|
||||
|
return Typeface.TryGetTableData(tag, 0, size, data) ? |
||||
|
new Blob(data, size, MemoryMode.ReadOnly, releaseDelegate) : null; |
||||
|
} |
||||
|
|
||||
|
private void Dispose(bool disposing) |
||||
|
{ |
||||
|
if (_isDisposed) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
_isDisposed = true; |
||||
|
|
||||
|
if (!disposing) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Font?.Dispose(); |
||||
|
Face?.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
Dispose(true); |
||||
|
GC.SuppressFinalize(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using Avalonia.Media; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
internal class TypefaceCollectionEntry |
||||
|
{ |
||||
|
public TypefaceCollectionEntry(Typeface typeface, SKTypeface skTypeface) |
||||
|
{ |
||||
|
Typeface = typeface; |
||||
|
SKTypeface = skTypeface; |
||||
|
} |
||||
|
public Typeface Typeface { get; } |
||||
|
public SKTypeface SKTypeface { get; } |
||||
|
} |
||||
|
} |
||||
@ -1,62 +1,61 @@ |
|||||
using System.Collections.Concurrent; |
using System.Collections.Concurrent; |
||||
using Avalonia.Media; |
using Avalonia.Media; |
||||
using Avalonia.Media.Fonts; |
using Avalonia.Media.Fonts; |
||||
|
using SharpDX.DirectWrite; |
||||
|
using FontFamily = Avalonia.Media.FontFamily; |
||||
|
using FontStyle = SharpDX.DirectWrite.FontStyle; |
||||
|
using FontWeight = SharpDX.DirectWrite.FontWeight; |
||||
|
|
||||
namespace Avalonia.Direct2D1.Media |
namespace Avalonia.Direct2D1.Media |
||||
{ |
{ |
||||
internal static class Direct2D1FontCollectionCache |
internal static class Direct2D1FontCollectionCache |
||||
{ |
{ |
||||
private static readonly ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection> s_cachedCollections; |
private static readonly ConcurrentDictionary<FontFamilyKey, FontCollection> s_cachedCollections; |
||||
internal static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; |
internal static readonly FontCollection InstalledFontCollection; |
||||
|
|
||||
static Direct2D1FontCollectionCache() |
static Direct2D1FontCollectionCache() |
||||
{ |
{ |
||||
s_cachedCollections = new ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection>(); |
s_cachedCollections = new ConcurrentDictionary<FontFamilyKey, FontCollection>(); |
||||
|
|
||||
s_installedFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false); |
InstalledFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false); |
||||
} |
} |
||||
|
|
||||
public static SharpDX.DirectWrite.TextFormat GetTextFormat(Typeface typeface) |
public static Font GetFont(Typeface typeface) |
||||
{ |
{ |
||||
var fontFamily = typeface.FontFamily; |
var fontFamily = typeface.FontFamily; |
||||
var fontCollection = GetOrAddFontCollection(fontFamily); |
var fontCollection = GetOrAddFontCollection(fontFamily); |
||||
var fontFamilyName = FontFamily.Default.Name; |
|
||||
|
|
||||
// Should this be cached?
|
|
||||
foreach (var familyName in fontFamily.FamilyNames) |
foreach (var familyName in fontFamily.FamilyNames) |
||||
{ |
{ |
||||
if (!fontCollection.FindFamilyName(familyName, out _)) |
if (fontCollection.FindFamilyName(familyName, out var index)) |
||||
{ |
{ |
||||
continue; |
return fontCollection.GetFontFamily(index).GetFirstMatchingFont( |
||||
|
(FontWeight)typeface.Weight, |
||||
|
FontStretch.Normal, |
||||
|
(FontStyle)typeface.Style); |
||||
} |
} |
||||
|
|
||||
fontFamilyName = familyName; |
|
||||
|
|
||||
break; |
|
||||
} |
} |
||||
|
|
||||
return new SharpDX.DirectWrite.TextFormat( |
InstalledFontCollection.FindFamilyName(FontFamily.Default.Name, out var i); |
||||
Direct2D1Platform.DirectWriteFactory, |
|
||||
fontFamilyName, |
return InstalledFontCollection.GetFontFamily(i).GetFirstMatchingFont( |
||||
fontCollection, |
(FontWeight)typeface.Weight, |
||||
(SharpDX.DirectWrite.FontWeight)typeface.Weight, |
FontStretch.Normal, |
||||
(SharpDX.DirectWrite.FontStyle)typeface.Style, |
(FontStyle)typeface.Style); |
||||
SharpDX.DirectWrite.FontStretch.Normal, |
|
||||
(float)typeface.FontSize); |
|
||||
} |
} |
||||
|
|
||||
private static SharpDX.DirectWrite.FontCollection GetOrAddFontCollection(FontFamily fontFamily) |
private static FontCollection GetOrAddFontCollection(FontFamily fontFamily) |
||||
{ |
{ |
||||
return fontFamily.Key == null ? s_installedFontCollection : s_cachedCollections.GetOrAdd(fontFamily.Key, CreateFontCollection); |
return fontFamily.Key == null ? InstalledFontCollection : s_cachedCollections.GetOrAdd(fontFamily.Key, CreateFontCollection); |
||||
} |
} |
||||
|
|
||||
private static SharpDX.DirectWrite.FontCollection CreateFontCollection(FontFamilyKey key) |
private static FontCollection CreateFontCollection(FontFamilyKey key) |
||||
{ |
{ |
||||
var assets = FontFamilyLoader.LoadFontAssets(key); |
var assets = FontFamilyLoader.LoadFontAssets(key); |
||||
|
|
||||
var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets); |
var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets); |
||||
|
|
||||
return new SharpDX.DirectWrite.FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key); |
return new FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,71 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using SharpDX.DirectWrite; |
||||
|
using FontFamily = Avalonia.Media.FontFamily; |
||||
|
using FontStyle = Avalonia.Media.FontStyle; |
||||
|
using FontWeight = Avalonia.Media.FontWeight; |
||||
|
|
||||
|
namespace Avalonia.Direct2D1.Media |
||||
|
{ |
||||
|
internal class FontManagerImpl : IFontManagerImpl |
||||
|
{ |
||||
|
public FontManagerImpl() |
||||
|
{ |
||||
|
//ToDo: Implement a real lookup of the system's default font.
|
||||
|
DefaultFontFamilyName = "segoe ui"; |
||||
|
} |
||||
|
|
||||
|
public string DefaultFontFamilyName { get; } |
||||
|
|
||||
|
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) |
||||
|
{ |
||||
|
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount; |
||||
|
|
||||
|
var fontFamilies = new string[familyCount]; |
||||
|
|
||||
|
for (var i = 0; i < familyCount; i++) |
||||
|
{ |
||||
|
fontFamilies[i] = Direct2D1FontCollectionCache.InstalledFontCollection.GetFontFamily(i).FamilyNames.GetString(0); |
||||
|
} |
||||
|
|
||||
|
return fontFamilies; |
||||
|
} |
||||
|
|
||||
|
public Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) |
||||
|
{ |
||||
|
//ToDo: Implement caching.
|
||||
|
return new Typeface(fontFamily, fontWeight, fontStyle); |
||||
|
} |
||||
|
|
||||
|
public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default, |
||||
|
FontFamily fontFamily = null, CultureInfo culture = null) |
||||
|
{ |
||||
|
var fontFamilyName = FontFamily.Default.Name; |
||||
|
|
||||
|
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount; |
||||
|
|
||||
|
for (var i = 0; i < familyCount; i++) |
||||
|
{ |
||||
|
var font = Direct2D1FontCollectionCache.InstalledFontCollection.GetFontFamily(i) |
||||
|
.GetMatchingFonts((SharpDX.DirectWrite.FontWeight)fontWeight, FontStretch.Normal, |
||||
|
(SharpDX.DirectWrite.FontStyle)fontStyle).GetFont(0); |
||||
|
|
||||
|
if (!font.HasCharacter(codepoint)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
fontFamilyName = font.FontFamily.FamilyNames.GetString(0); |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return GetTypeface(new FontFamily(fontFamilyName), fontWeight, fontStyle); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue