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"> |
|||
<ItemGroup> |
|||
<PackageReference Include="SkiaSharp" Version="1.68.0" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.2" /> |
|||
<PackageReference Include="SkiaSharp" Version="1.68.1-rc.153" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1-rc.153" /> |
|||
</ItemGroup> |
|||
</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.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
// This source file is adapted from the Windows Presentation Foundation project.
|
|||
// (https://github.com/dotnet/wpf/)
|
|||
//
|
|||
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Diagnostics; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Layout; |
|||
using Avalonia.VisualTree; |
|||
using Avalonia.Media; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <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>
|
|||
/// <remarks>
|
|||
/// Unlike WPF GridSplitter, Avalonia GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext.
|
|||
/// </remarks>
|
|||
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); |
|||
var prevDefinitionMin = GetMinLength(_prevDefinition); |
|||
var prevDefinitionMax = GetMaxLength(_prevDefinition); |
|||
get => GetValue(ResizeDirectionProperty); |
|||
set => SetValue(ResizeDirectionProperty, value); |
|||
} |
|||
|
|||
var nextDefinitionLen = GetActualLength(_nextDefinition); |
|||
var nextDefinitionMin = GetMinLength(_nextDefinition); |
|||
var nextDefinitionMax = GetMaxLength(_nextDefinition); |
|||
// Determine the minimum and maximum the columns can be resized
|
|||
min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen); |
|||
max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin); |
|||
/// <summary>
|
|||
/// Indicates which Columns or Rows the Splitter resizes.
|
|||
/// </summary>
|
|||
public GridResizeBehavior ResizeBehavior |
|||
{ |
|||
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
|
|||
// but resizes the splitter row/column when it's the first one.
|
|||
// this is different, but more internally consistent.
|
|||
if (_prevDefinition == null || _nextDefinition == null) |
|||
return; |
|||
get => GetValue(ShowsPreviewProperty); |
|||
set => SetValue(ShowsPreviewProperty, value); |
|||
} |
|||
|
|||
/// <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; |
|||
double max; |
|||
double min; |
|||
GetDeltaConstraints(out min, out max); |
|||
delta = Math.Min(Math.Max(delta, min), max); |
|||
return direction; |
|||
} |
|||
|
|||
var prevIsStar = IsStar(_prevDefinition); |
|||
var nextIsStar = IsStar(_nextDefinition); |
|||
/// <summary>
|
|||
/// 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); |
|||
SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta); |
|||
CancelResize(); |
|||
} |
|||
} |
|||
|
|||
private double GetActualLength(DefinitionBase definition) |
|||
protected override void OnDragStarted(VectorEventArgs e) |
|||
{ |
|||
if (definition == null) |
|||
return 0; |
|||
var columnDefinition = definition as ColumnDefinition; |
|||
return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight; |
|||
base.OnDragStarted(e); |
|||
|
|||
// TODO: Looks like that sometimes thumb will raise multiple drag started events.
|
|||
// 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) |
|||
return 0; |
|||
var columnDefinition = definition as ColumnDefinition; |
|||
return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight; |
|||
base.OnDragDelta(e); |
|||
|
|||
if (_resizeData != null) |
|||
{ |
|||
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) |
|||
return 0; |
|||
var columnDefinition = definition as ColumnDefinition; |
|||
return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight; |
|||
base.OnDragCompleted(e); |
|||
|
|||
if (_resizeData != null) |
|||
{ |
|||
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; |
|||
return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar; |
|||
Key key = e.Key; |
|||
|
|||
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; |
|||
if (columnDefinition != null) |
|||
// Restore original column/row lengths.
|
|||
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; |
|||
if (columnDefinition != null) |
|||
return direction == GridResizeDirection.Columns ? |
|||
(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); |
|||
_grid = this.GetVisualParent<Grid>(); |
|||
// For the case where both definition1 and 2 are stars, update all star values to match their current pixel values.
|
|||
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
|
|||
if (_orientation == Orientation.Vertical) |
|||
// For each definition, if it is a star, set is value to ActualLength in stars
|
|||
// 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); |
|||
_definitions = _grid.ColumnDefinitions.Cast<DefinitionBase>().ToList(); |
|||
definitionIndex = GetValue(Grid.ColumnProperty); |
|||
PseudoClasses.Add(":vertical"); |
|||
SetDefinitionLength(_resizeData.Definition1, new GridLength(definition1Pixels)); |
|||
} |
|||
else |
|||
{ |
|||
Cursor = new Cursor(StandardCursorType.SizeNorthSouth); |
|||
definitionIndex = GetValue(Grid.RowProperty); |
|||
_definitions = _grid.RowDefinitions.Cast<DefinitionBase>().ToList(); |
|||
PseudoClasses.Add(":horizontal"); |
|||
SetDefinitionLength(_resizeData.Definition2, new GridLength(definition2Pixels)); |
|||
} |
|||
} |
|||
|
|||
/// <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) |
|||
_prevDefinition = _definitions[definitionIndex - 1]; |
|||
/// <summary>
|
|||
/// 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) |
|||
_nextDefinition = _definitions[definitionIndex + 1]; |
|||
MoveSplitter(horizontalChange, verticalChange); |
|||
|
|||
_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()) |
|||
return Orientation.Horizontal; |
|||
if (!_grid.RowDefinitions.Any()) |
|||
return Orientation.Vertical; |
|||
private readonly TranslateTransform _translation; |
|||
private readonly Decorator _decorator; |
|||
|
|||
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); |
|||
var row = GetValue(Grid.RowProperty); |
|||
var width = _grid.ColumnDefinitions[col].Width; |
|||
var height = _grid.RowDefinitions[row].Height; |
|||
if (width.IsAuto && !height.IsAuto) |
|||
/// <summary>
|
|||
/// The Preview's Offset in the X direction from the GridSplitter.
|
|||
/// </summary>
|
|||
public double OffsetX |
|||
{ |
|||
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) |
|||
.Any(c => c.GetType() != typeof(GridSplitter))) |
|||
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
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.
|
|||
// 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 |
|||
{ |
|||
public interface IItemsPresenter : IPresenter |
|||
{ |
|||
IEnumerable Items { get; set; } |
|||
|
|||
IPanel Panel { get; } |
|||
|
|||
void ItemsChanged(NotifyCollectionChangedEventArgs e); |
|||
|
|||
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"> |
|||
<Style Selector="GridSplitter:vertical"> |
|||
<Setter Property="Width" Value="6"/> |
|||
<Setter Property="Background" Value="{DynamicResource ThemeControlLowBrush}"/> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Border Background="{TemplateBinding Background}"> |
|||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"> |
|||
<StackPanel.Styles> |
|||
<Style Selector="Ellipse"> |
|||
<Setter Property="HorizontalAlignment" 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> |
|||
|
|||
<Style Selector="GridSplitter"> |
|||
<Setter Property="Focusable" Value="True" /> |
|||
<Setter Property="MinWidth" Value="6" /> |
|||
<Setter Property="MinHeight" Value="6" /> |
|||
<Setter Property="Background" Value="{DynamicResource ThemeControlMidBrush}" /> |
|||
<Setter Property="PreviewContent"> |
|||
<Template> |
|||
<Rectangle Fill="{DynamicResource HighlightBrush}" /> |
|||
</Template> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="GridSplitter:horizontal"> |
|||
<Setter Property="Height" Value="6"/> |
|||
<Setter Property="Background" Value="{DynamicResource ThemeControlLowBrush}"/> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Border Background="{TemplateBinding Background}"> |
|||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> |
|||
<StackPanel.Styles> |
|||
<Style Selector="Ellipse"> |
|||
<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> |
|||
<Border |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
Background="{TemplateBinding Background}"/> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</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 Avalonia.Media; |
|||
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 |
|||
{ |
|||
internal static class Direct2D1FontCollectionCache |
|||
{ |
|||
private static readonly ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection> s_cachedCollections; |
|||
internal static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; |
|||
private static readonly ConcurrentDictionary<FontFamilyKey, FontCollection> s_cachedCollections; |
|||
internal static readonly FontCollection InstalledFontCollection; |
|||
|
|||
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 fontCollection = GetOrAddFontCollection(fontFamily); |
|||
var fontFamilyName = FontFamily.Default.Name; |
|||
|
|||
// Should this be cached?
|
|||
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( |
|||
Direct2D1Platform.DirectWriteFactory, |
|||
fontFamilyName, |
|||
fontCollection, |
|||
(SharpDX.DirectWrite.FontWeight)typeface.Weight, |
|||
(SharpDX.DirectWrite.FontStyle)typeface.Style, |
|||
SharpDX.DirectWrite.FontStretch.Normal, |
|||
(float)typeface.FontSize); |
|||
InstalledFontCollection.FindFamilyName(FontFamily.Default.Name, out var i); |
|||
|
|||
return InstalledFontCollection.GetFontFamily(i).GetFirstMatchingFont( |
|||
(FontWeight)typeface.Weight, |
|||
FontStretch.Normal, |
|||
(FontStyle)typeface.Style); |
|||
} |
|||
|
|||
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 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