committed by
GitHub
139 changed files with 3385 additions and 552 deletions
@ -1,17 +1,31 @@ |
|||||
This template is not intended to be prescriptive, but to help us review pull requests it would be useful if you included as much of the following information as possible: |
## What does the pull request do? |
||||
|
<!--- Give a bit of background on the PR here, together with links to with related issues etc. --> |
||||
|
|
||||
- What does the pull request do? |
|
||||
- What is the current behavior? |
|
||||
- What is the updated/expected behavior with this PR? |
|
||||
- How was the solution implemented (if it's not obvious)? |
|
||||
|
|
||||
Checklist: |
## What is the current behavior? |
||||
|
<!--- If the PR is a fix, describe the current incorrect behavior, otherwise delete this section. --> |
||||
|
|
||||
|
|
||||
|
## What is the updated/expected behavior with this PR? |
||||
|
<!--- Describe how to test the PR. --> |
||||
|
|
||||
|
|
||||
|
## How was the solution implemented (if it's not obvious)? |
||||
|
<!--- Include any information that might be of use to a reviewer here. --> |
||||
|
|
||||
|
|
||||
|
## Checklist |
||||
|
|
||||
- [ ] Added unit tests (if possible)? |
- [ ] Added unit tests (if possible)? |
||||
- [ ] Added XML documentation to any related classes? |
- [ ] Added XML documentation to any related classes? |
||||
- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation |
- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation |
||||
|
|
||||
If the pull request fixes issue(s) list them like this: |
## Breaking changes |
||||
|
<!--- List any breaking changes here. When the PR is merged please add an entry to https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes --> |
||||
|
|
||||
|
|
||||
|
## Fixed issues |
||||
|
<!--- If the pull request fixes issue(s) list them like this: |
||||
Fixes #123 |
Fixes #123 |
||||
Fixes #456 |
Fixes #456 |
||||
|
--> |
||||
|
|||||
@ -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.0" /> |
||||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.0" /> |
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.1" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -1,6 +1,5 @@ |
|||||
copy ..\samples\ControlCatalog.Desktop\bin\Debug\net461\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\net461\ |
|
||||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\ |
||||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ |
||||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Gtk3.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\ |
|
||||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ |
||||
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\ |
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\ |
||||
|
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\ |
||||
|
|||||
@ -0,0 +1,24 @@ |
|||||
|
// 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.
|
||||
|
|
||||
|
|
||||
|
namespace Avalonia.Data.Converters |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides a set of useful <see cref="IValueConverter"/>s for working with objects.
|
||||
|
/// </summary>
|
||||
|
public static class ObjectConverters |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A value converter that returns true if the input object is a null reference.
|
||||
|
/// </summary>
|
||||
|
public static readonly IValueConverter IsNull = |
||||
|
new FuncValueConverter<object, bool>(x => x is null); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A value converter that returns true if the input object is not null.
|
||||
|
/// </summary>
|
||||
|
public static readonly IValueConverter IsNotNull = |
||||
|
new FuncValueConverter<object, bool>(x => !(x is null)); |
||||
|
} |
||||
|
} |
||||
@ -1,22 +0,0 @@ |
|||||
// 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.
|
|
||||
|
|
||||
namespace Avalonia |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Specifies that this object supports a simple, transacted notification for batch
|
|
||||
/// initialization.
|
|
||||
/// </summary>
|
|
||||
public interface ISupportInitialize |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Signals the object that initialization is starting.
|
|
||||
/// </summary>
|
|
||||
void BeginInit(); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Signals the object that initialization is complete.
|
|
||||
/// </summary>
|
|
||||
void EndInit(); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,27 @@ |
|||||
|
// 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.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides <see cref="PixelPoint"/> data for events.
|
||||
|
/// </summary>
|
||||
|
public class PixelPointEventArgs : EventArgs |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PixelPointEventArgs"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="point">The <see cref="PixelPoint"/> data.</param>
|
||||
|
public PixelPointEventArgs(PixelPoint point) |
||||
|
{ |
||||
|
Point = point; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the <see cref="PixelPoint"/> data.
|
||||
|
/// </summary>
|
||||
|
public PixelPoint Point { get; } |
||||
|
} |
||||
|
} |
||||
@ -1,27 +0,0 @@ |
|||||
// 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.Controls |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Provides <see cref="Point"/> data for events.
|
|
||||
/// </summary>
|
|
||||
public class PointEventArgs : EventArgs |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="PointEventArgs"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="point">The <see cref="Point"/> data.</param>
|
|
||||
public PointEventArgs(Point point) |
|
||||
{ |
|
||||
Point = point; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the <see cref="Point"/> data.
|
|
||||
/// </summary>
|
|
||||
public Point Point { get; } |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,72 @@ |
|||||
|
// 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.Reactive.Linq; |
||||
|
|
||||
|
namespace Avalonia.Styling |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The `:not()` style selector.
|
||||
|
/// </summary>
|
||||
|
internal class NotSelector : Selector |
||||
|
{ |
||||
|
private readonly Selector _previous; |
||||
|
private readonly Selector _argument; |
||||
|
private string _selectorString; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="NotSelector"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="previous">The previous selector.</param>
|
||||
|
/// <param name="argument">The selector to be not-ed.</param>
|
||||
|
public NotSelector(Selector previous, Selector argument) |
||||
|
{ |
||||
|
_previous = previous; |
||||
|
_argument = argument ?? throw new InvalidOperationException("Not selector must have a selector argument."); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool InTemplate => _argument.InTemplate; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool IsCombinator => false; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override Type TargetType => _previous?.TargetType; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
if (_selectorString == null) |
||||
|
{ |
||||
|
_selectorString = ":not(" + _argument.ToString() + ")"; |
||||
|
} |
||||
|
|
||||
|
return _selectorString; |
||||
|
} |
||||
|
|
||||
|
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) |
||||
|
{ |
||||
|
var innerResult = _argument.Match(control, subscribe); |
||||
|
|
||||
|
switch (innerResult.Result) |
||||
|
{ |
||||
|
case SelectorMatchResult.AlwaysThisInstance: |
||||
|
return SelectorMatch.NeverThisInstance; |
||||
|
case SelectorMatchResult.AlwaysThisType: |
||||
|
return SelectorMatch.NeverThisType; |
||||
|
case SelectorMatchResult.NeverThisInstance: |
||||
|
return SelectorMatch.AlwaysThisInstance; |
||||
|
case SelectorMatchResult.NeverThisType: |
||||
|
return SelectorMatch.AlwaysThisType; |
||||
|
case SelectorMatchResult.Sometimes: |
||||
|
return new SelectorMatch(innerResult.Activator.Select(x => !x)); |
||||
|
default: |
||||
|
throw new InvalidOperationException("Invalid SelectorMatchResult."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override Selector MovePrevious() => _previous; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,201 @@ |
|||||
|
// 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.Globalization; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a point in device pixels.
|
||||
|
/// </summary>
|
||||
|
public readonly struct PixelPoint |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A point representing 0,0.
|
||||
|
/// </summary>
|
||||
|
public static readonly PixelPoint Origin = new PixelPoint(0, 0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PixelPoint"/> structure.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The X co-ordinate.</param>
|
||||
|
/// <param name="y">The Y co-ordinate.</param>
|
||||
|
public PixelPoint(int x, int y) |
||||
|
{ |
||||
|
X = x; |
||||
|
Y = y; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the X co-ordinate.
|
||||
|
/// </summary>
|
||||
|
public int X { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the Y co-ordinate.
|
||||
|
/// </summary>
|
||||
|
public int Y { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Checks for equality between two <see cref="PixelPoint"/>s.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The first point.</param>
|
||||
|
/// <param name="right">The second point.</param>
|
||||
|
/// <returns>True if the points are equal; otherwise false.</returns>
|
||||
|
public static bool operator ==(PixelPoint left, PixelPoint right) |
||||
|
{ |
||||
|
return left.X == right.X && left.Y == right.Y; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Checks for inequality between two <see cref="PixelPoint"/>s.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The first point.</param>
|
||||
|
/// <param name="right">The second point.</param>
|
||||
|
/// <returns>True if the points are unequal; otherwise false.</returns>
|
||||
|
public static bool operator !=(PixelPoint left, PixelPoint right) |
||||
|
{ |
||||
|
return !(left == right); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Parses a <see cref="PixelPoint"/> string.
|
||||
|
/// </summary>
|
||||
|
/// <param name="s">The string.</param>
|
||||
|
/// <returns>The <see cref="PixelPoint"/>.</returns>
|
||||
|
public static PixelPoint Parse(string s) |
||||
|
{ |
||||
|
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint")) |
||||
|
{ |
||||
|
return new PixelPoint( |
||||
|
tokenizer.ReadInt32(), |
||||
|
tokenizer.ReadInt32()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Checks for equality between a point and an object.
|
||||
|
/// </summary>
|
||||
|
/// <param name="obj">The object.</param>
|
||||
|
/// <returns>
|
||||
|
/// True if <paramref name="obj"/> is a point that equals the current point.
|
||||
|
/// </returns>
|
||||
|
public override bool Equals(object obj) |
||||
|
{ |
||||
|
if (obj is PixelPoint other) |
||||
|
{ |
||||
|
return this == other; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a hash code for a <see cref="PixelPoint"/>.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The hash code.</returns>
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
unchecked |
||||
|
{ |
||||
|
int hash = 17; |
||||
|
hash = (hash * 23) + X.GetHashCode(); |
||||
|
hash = (hash * 23) + Y.GetHashCode(); |
||||
|
return hash; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a new <see cref="PixelPoint"/> with the same Y co-ordinate and the specified X co-ordinate.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The X co-ordinate.</param>
|
||||
|
/// <returns>The new <see cref="PixelPoint"/>.</returns>
|
||||
|
public PixelPoint WithX(int x) => new PixelPoint(x, Y); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a new <see cref="PixelPoint"/> with the same X co-ordinate and the specified Y co-ordinate.
|
||||
|
/// </summary>
|
||||
|
/// <param name="y">The Y co-ordinate.</param>
|
||||
|
/// <returns>The new <see cref="PixelPoint"/>.</returns>
|
||||
|
public PixelPoint WithY(int y) => new PixelPoint(X, y); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
|
||||
|
/// specified scaling factor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scale">The scaling factor.</param>
|
||||
|
/// <returns>The device-independent point.</returns>
|
||||
|
public Point ToPoint(double scale) => new Point(X / scale, Y / scale); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
|
||||
|
/// specified scaling factor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scale">The scaling factor.</param>
|
||||
|
/// <returns>The device-independent point.</returns>
|
||||
|
public Point ToPoint(Vector scale) => new Point(X / scale.X, Y / scale.Y); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
|
||||
|
/// specified dots per inch (DPI).
|
||||
|
/// </summary>
|
||||
|
/// <param name="dpi">The dots per inch of the device.</param>
|
||||
|
/// <returns>The device-independent point.</returns>
|
||||
|
public Point ToPointWithDpi(double dpi) => ToPoint(dpi / 96); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
|
||||
|
/// specified dots per inch (DPI).
|
||||
|
/// </summary>
|
||||
|
/// <param name="dpi">The dots per inch of the device.</param>
|
||||
|
/// <returns>The device-independent point.</returns>
|
||||
|
public Point ToPointWithDpi(Vector dpi) => ToPoint(new Vector(dpi.X / 96, dpi.Y / 96)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a <see cref="Point"/> to device pixels using the specified scaling factor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="point">The point.</param>
|
||||
|
/// <param name="scale">The scaling factor.</param>
|
||||
|
/// <returns>The device-independent point.</returns>
|
||||
|
public static PixelPoint FromPoint(Point point, double scale) => new PixelPoint( |
||||
|
(int)(point.X * scale), |
||||
|
(int)(point.Y * scale)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a <see cref="Point"/> to device pixels using the specified scaling factor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="point">The point.</param>
|
||||
|
/// <param name="scale">The scaling factor.</param>
|
||||
|
/// <returns>The device-independent point.</returns>
|
||||
|
public static PixelPoint FromPoint(Point point, Vector scale) => new PixelPoint( |
||||
|
(int)(point.X * scale.X), |
||||
|
(int)(point.Y * scale.Y)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a <see cref="Point"/> to device pixels using the specified dots per inch (DPI).
|
||||
|
/// </summary>
|
||||
|
/// <param name="point">The point.</param>
|
||||
|
/// <param name="dpi">The dots per inch of the device.</param>
|
||||
|
/// <returns>The device-independent point.</returns>
|
||||
|
public static PixelPoint FromPointWithDpi(Point point, double dpi) => FromPoint(point, dpi / 96); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a <see cref="Point"/> to device pixels using the specified dots per inch (DPI).
|
||||
|
/// </summary>
|
||||
|
/// <param name="point">The point.</param>
|
||||
|
/// <param name="dpi">The dots per inch of the device.</param>
|
||||
|
/// <returns>The device-independent point.</returns>
|
||||
|
public static PixelPoint FromPointWithDpi(Point point, Vector dpi) => FromPoint(point, new Vector(dpi.X / 96, dpi.Y / 96)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the string representation of the point.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The string representation of the point.</returns>
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", X, Y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,436 @@ |
|||||
|
// 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.Globalization; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a rectangle in device pixels.
|
||||
|
/// </summary>
|
||||
|
public readonly struct PixelRect |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An empty rectangle.
|
||||
|
/// </summary>
|
||||
|
public static readonly PixelRect Empty = default; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The X position.</param>
|
||||
|
/// <param name="y">The Y position.</param>
|
||||
|
/// <param name="width">The width.</param>
|
||||
|
/// <param name="height">The height.</param>
|
||||
|
public PixelRect(int x, int y, int width, int height) |
||||
|
{ |
||||
|
X = x; |
||||
|
Y = y; |
||||
|
Width = width; |
||||
|
Height = height; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
|
||||
|
/// </summary>
|
||||
|
/// <param name="size">The size of the rectangle.</param>
|
||||
|
public PixelRect(PixelSize size) |
||||
|
{ |
||||
|
X = 0; |
||||
|
Y = 0; |
||||
|
Width = size.Width; |
||||
|
Height = size.Height; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
|
||||
|
/// </summary>
|
||||
|
/// <param name="position">The position of the rectangle.</param>
|
||||
|
/// <param name="size">The size of the rectangle.</param>
|
||||
|
public PixelRect(PixelPoint position, PixelSize size) |
||||
|
{ |
||||
|
X = position.X; |
||||
|
Y = position.Y; |
||||
|
Width = size.Width; |
||||
|
Height = size.Height; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
|
||||
|
/// </summary>
|
||||
|
/// <param name="topLeft">The top left position of the rectangle.</param>
|
||||
|
/// <param name="bottomRight">The bottom right position of the rectangle.</param>
|
||||
|
public PixelRect(PixelPoint topLeft, PixelPoint bottomRight) |
||||
|
{ |
||||
|
X = topLeft.X; |
||||
|
Y = topLeft.Y; |
||||
|
Width = bottomRight.X - topLeft.X; |
||||
|
Height = bottomRight.Y - topLeft.Y; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the X position.
|
||||
|
/// </summary>
|
||||
|
public int X { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the Y position.
|
||||
|
/// </summary>
|
||||
|
public int Y { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the width.
|
||||
|
/// </summary>
|
||||
|
public int Width { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the height.
|
||||
|
/// </summary>
|
||||
|
public int Height { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the position of the rectangle.
|
||||
|
/// </summary>
|
||||
|
public PixelPoint Position => new PixelPoint(X, Y); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the size of the rectangle.
|
||||
|
/// </summary>
|
||||
|
public PixelSize Size => new PixelSize(Width, Height); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the right position of the rectangle.
|
||||
|
/// </summary>
|
||||
|
public int Right => X + Width; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the bottom position of the rectangle.
|
||||
|
/// </summary>
|
||||
|
public int Bottom => Y + Height; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the top left point of the rectangle.
|
||||
|
/// </summary>
|
||||
|
public PixelPoint TopLeft => new PixelPoint(X, Y); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the top right point of the rectangle.
|
||||
|
/// </summary>
|
||||
|
public PixelPoint TopRight => new PixelPoint(Right, Y); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the bottom left point of the rectangle.
|
||||
|
/// </summary>
|
||||
|
public PixelPoint BottomLeft => new PixelPoint(X, Bottom); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the bottom right point of the rectangle.
|
||||
|
/// </summary>
|
||||
|
public PixelPoint BottomRight => new PixelPoint(Right, Bottom); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the center point of the rectangle.
|
||||
|
/// </summary>
|
||||
|
public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the rectangle is empty.
|
||||
|
/// </summary>
|
||||
|
public bool IsEmpty => Width == 0 && Height == 0; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Checks for equality between two <see cref="PixelRect"/>s.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The first rect.</param>
|
||||
|
/// <param name="right">The second rect.</param>
|
||||
|
/// <returns>True if the rects are equal; otherwise false.</returns>
|
||||
|
public static bool operator ==(PixelRect left, PixelRect right) |
||||
|
{ |
||||
|
return left.Position == right.Position && left.Size == right.Size; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Checks for inequality between two <see cref="PixelRect"/>s.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The first rect.</param>
|
||||
|
/// <param name="right">The second rect.</param>
|
||||
|
/// <returns>True if the rects are unequal; otherwise false.</returns>
|
||||
|
public static bool operator !=(PixelRect left, PixelRect right) |
||||
|
{ |
||||
|
return !(left == right); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Determines whether a point in in the bounds of the rectangle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="p">The point.</param>
|
||||
|
/// <returns>true if the point is in the bounds of the rectangle; otherwise false.</returns>
|
||||
|
public bool Contains(PixelPoint p) |
||||
|
{ |
||||
|
return p.X >= X && p.X <= Right && p.Y >= Y && p.Y <= Bottom; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Determines whether the rectangle fully contains another rectangle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="r">The rectangle.</param>
|
||||
|
/// <returns>true if the rectangle is fully contained; otherwise false.</returns>
|
||||
|
public bool Contains(PixelRect r) |
||||
|
{ |
||||
|
return Contains(r.TopLeft) && Contains(r.BottomRight); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Centers another rectangle in this rectangle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rect">The rectangle to center.</param>
|
||||
|
/// <returns>The centered rectangle.</returns>
|
||||
|
public PixelRect CenterRect(PixelRect rect) |
||||
|
{ |
||||
|
return new PixelRect( |
||||
|
X + ((Width - rect.Width) / 2), |
||||
|
Y + ((Height - rect.Height) / 2), |
||||
|
rect.Width, |
||||
|
rect.Height); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a boolean indicating whether the given object is equal to this rectangle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="obj">The object to compare against.</param>
|
||||
|
/// <returns>True if the object is equal to this rectangle; false otherwise.</returns>
|
||||
|
public override bool Equals(object obj) |
||||
|
{ |
||||
|
if (obj is PixelRect other) |
||||
|
{ |
||||
|
return this == other; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the hash code for this instance.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The hash code.</returns>
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
unchecked |
||||
|
{ |
||||
|
int hash = 17; |
||||
|
hash = (hash * 23) + X.GetHashCode(); |
||||
|
hash = (hash * 23) + Y.GetHashCode(); |
||||
|
hash = (hash * 23) + Width.GetHashCode(); |
||||
|
hash = (hash * 23) + Height.GetHashCode(); |
||||
|
return hash; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the intersection of two rectangles.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rect">The other rectangle.</param>
|
||||
|
/// <returns>The intersection.</returns>
|
||||
|
public PixelRect Intersect(PixelRect rect) |
||||
|
{ |
||||
|
var newLeft = (rect.X > X) ? rect.X : X; |
||||
|
var newTop = (rect.Y > Y) ? rect.Y : Y; |
||||
|
var newRight = (rect.Right < Right) ? rect.Right : Right; |
||||
|
var newBottom = (rect.Bottom < Bottom) ? rect.Bottom : Bottom; |
||||
|
|
||||
|
if ((newRight > newLeft) && (newBottom > newTop)) |
||||
|
{ |
||||
|
return new PixelRect(newLeft, newTop, newRight - newLeft, newBottom - newTop); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return Empty; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Determines whether a rectangle intersects with this rectangle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rect">The other rectangle.</param>
|
||||
|
/// <returns>
|
||||
|
/// True if the specified rectangle intersects with this one; otherwise false.
|
||||
|
/// </returns>
|
||||
|
public bool Intersects(PixelRect rect) |
||||
|
{ |
||||
|
return (rect.X < Right) && (X < rect.Right) && (rect.Y < Bottom) && (Y < rect.Bottom); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the union of two rectangles.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rect">The other rectangle.</param>
|
||||
|
/// <returns>The union.</returns>
|
||||
|
public PixelRect Union(PixelRect rect) |
||||
|
{ |
||||
|
if (IsEmpty) |
||||
|
{ |
||||
|
return rect; |
||||
|
} |
||||
|
else if (rect.IsEmpty) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var x1 = Math.Min(X, rect.X); |
||||
|
var x2 = Math.Max(Right, rect.Right); |
||||
|
var y1 = Math.Min(Y, rect.Y); |
||||
|
var y2 = Math.Max(Bottom, rect.Bottom); |
||||
|
|
||||
|
return new PixelRect(new PixelPoint(x1, y1), new PixelPoint(x2, y2)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a new <see cref="PixelRect"/> with the specified X position.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The x position.</param>
|
||||
|
/// <returns>The new <see cref="PixelRect"/>.</returns>
|
||||
|
public PixelRect WithX(int x) |
||||
|
{ |
||||
|
return new PixelRect(x, Y, Width, Height); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a new <see cref="PixelRect"/> with the specified Y position.
|
||||
|
/// </summary>
|
||||
|
/// <param name="y">The y position.</param>
|
||||
|
/// <returns>The new <see cref="PixelRect"/>.</returns>
|
||||
|
public PixelRect WithY(int y) |
||||
|
{ |
||||
|
return new PixelRect(X, y, Width, Height); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a new <see cref="PixelRect"/> with the specified width.
|
||||
|
/// </summary>
|
||||
|
/// <param name="width">The width.</param>
|
||||
|
/// <returns>The new <see cref="PixelRect"/>.</returns>
|
||||
|
public PixelRect WithWidth(int width) |
||||
|
{ |
||||
|
return new PixelRect(X, Y, width, Height); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a new <see cref="PixelRect"/> with the specified height.
|
||||
|
/// </summary>
|
||||
|
/// <param name="height">The height.</param>
|
||||
|
/// <returns>The new <see cref="PixelRect"/>.</returns>
|
||||
|
public PixelRect WithHeight(int height) |
||||
|
{ |
||||
|
return new PixelRect(X, Y, Width, Height); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
|
||||
|
/// specified scaling factor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scale">The scaling factor.</param>
|
||||
|
/// <returns>The device-independent rect.</returns>
|
||||
|
public Rect ToRect(double scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
|
||||
|
/// specified scaling factor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="scale">The scaling factor.</param>
|
||||
|
/// <returns>The device-independent rect.</returns>
|
||||
|
public Rect ToRect(Vector scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
|
||||
|
/// specified dots per inch (DPI).
|
||||
|
/// </summary>
|
||||
|
/// <param name="dpi">The dots per inch of the device.</param>
|
||||
|
/// <returns>The device-independent rect.</returns>
|
||||
|
public Rect ToRectWithDpi(double dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
|
||||
|
/// specified dots per inch (DPI).
|
||||
|
/// </summary>
|
||||
|
/// <param name="dpi">The dots per inch of the device.</param>
|
||||
|
/// <returns>The device-independent rect.</returns>
|
||||
|
public Rect ToRectWithDpi(Vector dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a <see cref="Rect"/> to device pixels using the specified scaling factor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rect">The rect.</param>
|
||||
|
/// <param name="scale">The scaling factor.</param>
|
||||
|
/// <returns>The device-independent rect.</returns>
|
||||
|
public static PixelRect FromRect(Rect rect, double scale) => new PixelRect( |
||||
|
PixelPoint.FromPoint(rect.Position, scale), |
||||
|
PixelSize.FromSize(rect.Size, scale)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a <see cref="Rect"/> to device pixels using the specified scaling factor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rect">The rect.</param>
|
||||
|
/// <param name="scale">The scaling factor.</param>
|
||||
|
/// <returns>The device-independent point.</returns>
|
||||
|
public static PixelRect FromRect(Rect rect, Vector scale) => new PixelRect( |
||||
|
PixelPoint.FromPoint(rect.Position, scale), |
||||
|
PixelSize.FromSize(rect.Size, scale)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a <see cref="Rect"/> to device pixels using the specified dots per inch (DPI).
|
||||
|
/// </summary>
|
||||
|
/// <param name="rect">The rect.</param>
|
||||
|
/// <param name="dpi">The dots per inch of the device.</param>
|
||||
|
/// <returns>The device-independent point.</returns>
|
||||
|
public static PixelRect FromRectWithDpi(Rect rect, double dpi) => new PixelRect( |
||||
|
PixelPoint.FromPointWithDpi(rect.Position, dpi), |
||||
|
PixelSize.FromSizeWithDpi(rect.Size, dpi)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a <see cref="Rect"/> to device pixels using the specified dots per inch (DPI).
|
||||
|
/// </summary>
|
||||
|
/// <param name="rect">The rect.</param>
|
||||
|
/// <param name="dpi">The dots per inch of the device.</param>
|
||||
|
/// <returns>The device-independent point.</returns>
|
||||
|
public static PixelRect FromRectWithDpi(Rect rect, Vector dpi) => new PixelRect( |
||||
|
PixelPoint.FromPointWithDpi(rect.Position, dpi), |
||||
|
PixelSize.FromSizeWithDpi(rect.Size, dpi)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the string representation of the rectangle.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The string representation of the rectangle.</returns>
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return string.Format( |
||||
|
CultureInfo.InvariantCulture, |
||||
|
"{0}, {1}, {2}, {3}", |
||||
|
X, |
||||
|
Y, |
||||
|
Width, |
||||
|
Height); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Parses a <see cref="PixelRect"/> string.
|
||||
|
/// </summary>
|
||||
|
/// <param name="s">The string.</param>
|
||||
|
/// <returns>The parsed <see cref="PixelRect"/>.</returns>
|
||||
|
public static PixelRect Parse(string s) |
||||
|
{ |
||||
|
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect")) |
||||
|
{ |
||||
|
return new PixelRect( |
||||
|
tokenizer.ReadInt32(), |
||||
|
tokenizer.ReadInt32(), |
||||
|
tokenizer.ReadInt32(), |
||||
|
tokenizer.ReadInt32() |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// 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.Rendering |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides data for the <see cref="IRenderer.SceneInvalidated"/> event.
|
||||
|
/// </summary>
|
||||
|
public class SceneInvalidatedEventArgs : EventArgs |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="SceneInvalidatedEventArgs"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="root">The render root that has been updated.</param>
|
||||
|
/// <param name="dirtyRect">The updated area.</param>
|
||||
|
public SceneInvalidatedEventArgs( |
||||
|
IRenderRoot root, |
||||
|
Rect dirtyRect) |
||||
|
{ |
||||
|
RenderRoot = root; |
||||
|
DirtyRect = dirtyRect; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the invalidated area.
|
||||
|
/// </summary>
|
||||
|
public Rect DirtyRect { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the render root that has been invalidated.
|
||||
|
/// </summary>
|
||||
|
public IRenderRoot RenderRoot { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,91 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.OpenGL; |
||||
|
using Avalonia.Platform.Interop; |
||||
|
// ReSharper disable UnassignedGetOnlyAutoProperty
|
||||
|
|
||||
|
namespace Avalonia.X11.Glx |
||||
|
{ |
||||
|
unsafe class GlxInterface : GlInterfaceBase |
||||
|
{ |
||||
|
private const string libGL = "libGL.so.1"; |
||||
|
[GlEntryPointAttribute("glXMakeContextCurrent")] |
||||
|
public GlxMakeContextCurrent MakeContextCurrent { get; } |
||||
|
public delegate bool GlxMakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context); |
||||
|
|
||||
|
[GlEntryPoint("glXCreatePbuffer")] |
||||
|
public GlxCreatePbuffer CreatePbuffer { get; } |
||||
|
|
||||
|
public delegate IntPtr GlxCreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list); |
||||
|
|
||||
|
[GlEntryPointAttribute("glXChooseVisual")] |
||||
|
public GlxChooseVisual ChooseVisual { get; } |
||||
|
public delegate XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList); |
||||
|
|
||||
|
|
||||
|
[GlEntryPointAttribute("glXCreateContext")] |
||||
|
public GlxCreateContext CreateContext { get; } |
||||
|
public delegate IntPtr GlxCreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct); |
||||
|
|
||||
|
|
||||
|
[GlEntryPointAttribute("glXCreateContextAttribsARB")] |
||||
|
public GlxCreateContextAttribsARB CreateContextAttribsARB { get; } |
||||
|
public delegate IntPtr GlxCreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList, |
||||
|
bool direct, int[] attribs); |
||||
|
|
||||
|
|
||||
|
[DllImport(libGL, EntryPoint = "glXGetProcAddress")] |
||||
|
public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer); |
||||
|
|
||||
|
|
||||
|
[GlEntryPointAttribute("glXDestroyContext")] |
||||
|
public GlxDestroyContext DestroyContext { get; } |
||||
|
public delegate void GlxDestroyContext(IntPtr dpy, IntPtr ctx); |
||||
|
|
||||
|
|
||||
|
[GlEntryPointAttribute("glXChooseFBConfig")] |
||||
|
public GlxChooseFBConfig ChooseFBConfig { get; } |
||||
|
public delegate IntPtr* GlxChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements); |
||||
|
|
||||
|
|
||||
|
public IntPtr* GlxChooseFbConfig(IntPtr dpy, int screen, IEnumerable<int> attribs, out int nelements) |
||||
|
{ |
||||
|
var arr = attribs.Concat(new[]{0}).ToArray(); |
||||
|
return ChooseFBConfig(dpy, screen, arr, out nelements); |
||||
|
} |
||||
|
|
||||
|
[GlEntryPointAttribute("glXGetVisualFromFBConfig")] |
||||
|
public GlxGetVisualFromFBConfig GetVisualFromFBConfig { get; } |
||||
|
public delegate XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy, IntPtr config); |
||||
|
|
||||
|
|
||||
|
[GlEntryPointAttribute("glXGetFBConfigAttrib")] |
||||
|
public GlxGetFBConfigAttrib GetFBConfigAttrib { get; } |
||||
|
public delegate int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value); |
||||
|
|
||||
|
|
||||
|
[GlEntryPointAttribute("glXSwapBuffers")] |
||||
|
public GlxSwapBuffers SwapBuffers { get; } |
||||
|
public delegate void GlxSwapBuffers(IntPtr dpy, IntPtr drawable); |
||||
|
|
||||
|
|
||||
|
[GlEntryPointAttribute("glXWaitX")] |
||||
|
public GlxWaitX WaitX { get; } |
||||
|
public delegate void GlxWaitX(); |
||||
|
|
||||
|
|
||||
|
[GlEntryPointAttribute("glXWaitGL")] |
||||
|
public GlxWaitGL WaitGL { get; } |
||||
|
public delegate void GlxWaitGL(); |
||||
|
|
||||
|
public delegate int GlGetError(); |
||||
|
[GlEntryPoint("glGetError")] |
||||
|
public GlGetError GetError { get; } |
||||
|
|
||||
|
public GlxInterface() : base(GlxGetProcAddress) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
// ReSharper disable InconsistentNaming
|
||||
|
// ReSharper disable IdentifierTypo
|
||||
|
// ReSharper disable UnusedMember.Global
|
||||
|
#pragma warning disable 414
|
||||
|
namespace Avalonia.X11.Glx |
||||
|
{ |
||||
|
class GlxConsts |
||||
|
{ |
||||
|
public const int GLX_USE_GL = 1; |
||||
|
public const int GLX_BUFFER_SIZE = 2; |
||||
|
public const int GLX_LEVEL = 3; |
||||
|
public const int GLX_RGBA = 4; |
||||
|
public const int GLX_DOUBLEBUFFER = 5; |
||||
|
public const int GLX_STEREO = 6; |
||||
|
public const int GLX_AUX_BUFFERS = 7; |
||||
|
public const int GLX_RED_SIZE = 8; |
||||
|
public const int GLX_GREEN_SIZE = 9; |
||||
|
public const int GLX_BLUE_SIZE = 10; |
||||
|
public const int GLX_ALPHA_SIZE = 11; |
||||
|
public const int GLX_DEPTH_SIZE = 12; |
||||
|
public const int GLX_STENCIL_SIZE = 13; |
||||
|
public const int GLX_ACCUM_RED_SIZE = 14; |
||||
|
public const int GLX_ACCUM_GREEN_SIZE = 15; |
||||
|
public const int GLX_ACCUM_BLUE_SIZE = 16; |
||||
|
public const int GLX_ACCUM_ALPHA_SIZE = 17; |
||||
|
public const int GLX_BAD_SCREEN = 1; |
||||
|
public const int GLX_BAD_ATTRIBUTE = 2; |
||||
|
public const int GLX_NO_EXTENSION = 3; |
||||
|
public const int GLX_BAD_VISUAL = 4; |
||||
|
public const int GLX_BAD_CONTEXT = 5; |
||||
|
public const int GLX_BAD_VALUE = 6; |
||||
|
public const int GLX_BAD_ENUM = 7; |
||||
|
public const int GLX_VENDOR = 1; |
||||
|
public const int GLX_VERSION = 2; |
||||
|
public const int GLX_EXTENSIONS= 3; |
||||
|
public const int GLX_CONFIG_CAVEAT = 0x20; |
||||
|
public const int GLX_DONT_CARE = unchecked((int)0xFFFFFFFF); |
||||
|
public const int GLX_X_VISUAL_TYPE = 0x22; |
||||
|
public const int GLX_TRANSPARENT_TYPE = 0x23; |
||||
|
public const int GLX_TRANSPARENT_INDEX_VALUE = 0x24; |
||||
|
public const int GLX_TRANSPARENT_RED_VALUE = 0x25; |
||||
|
public const int GLX_TRANSPARENT_GREEN_VALUE = 0x26; |
||||
|
public const int GLX_TRANSPARENT_BLUE_VALUE = 0x27; |
||||
|
public const int GLX_TRANSPARENT_ALPHA_VALUE = 0x28; |
||||
|
public const int GLX_WINDOW_BIT = 0x00000001; |
||||
|
public const int GLX_PIXMAP_BIT = 0x00000002; |
||||
|
public const int GLX_PBUFFER_BIT = 0x00000004; |
||||
|
public const int GLX_AUX_BUFFERS_BIT = 0x00000010; |
||||
|
public const int GLX_FRONT_LEFT_BUFFER_BIT = 0x00000001; |
||||
|
public const int GLX_FRONT_RIGHT_BUFFER_BIT = 0x00000002; |
||||
|
public const int GLX_BACK_LEFT_BUFFER_BIT = 0x00000004; |
||||
|
public const int GLX_BACK_RIGHT_BUFFER_BIT = 0x00000008; |
||||
|
public const int GLX_DEPTH_BUFFER_BIT = 0x00000020; |
||||
|
public const int GLX_STENCIL_BUFFER_BIT = 0x00000040; |
||||
|
public const int GLX_ACCUM_BUFFER_BIT = 0x00000080; |
||||
|
public const int GLX_NONE = 0x8000; |
||||
|
public const int GLX_SLOW_CONFIG = 0x8001; |
||||
|
public const int GLX_TRUE_COLOR = 0x8002; |
||||
|
public const int GLX_DIRECT_COLOR = 0x8003; |
||||
|
public const int GLX_PSEUDO_COLOR = 0x8004; |
||||
|
public const int GLX_STATIC_COLOR = 0x8005; |
||||
|
public const int GLX_GRAY_SCALE = 0x8006; |
||||
|
public const int GLX_STATIC_GRAY = 0x8007; |
||||
|
public const int GLX_TRANSPARENT_RGB = 0x8008; |
||||
|
public const int GLX_TRANSPARENT_INDEX = 0x8009; |
||||
|
public const int GLX_VISUAL_ID = 0x800B; |
||||
|
public const int GLX_SCREEN = 0x800C; |
||||
|
public const int GLX_NON_CONFORMANT_CONFIG = 0x800D; |
||||
|
public const int GLX_DRAWABLE_TYPE = 0x8010; |
||||
|
public const int GLX_RENDER_TYPE = 0x8011; |
||||
|
public const int GLX_X_RENDERABLE = 0x8012; |
||||
|
public const int GLX_FBCONFIG_ID = 0x8013; |
||||
|
public const int GLX_RGBA_TYPE = 0x8014; |
||||
|
public const int GLX_COLOR_INDEX_TYPE = 0x8015; |
||||
|
public const int GLX_MAX_PBUFFER_WIDTH = 0x8016; |
||||
|
public const int GLX_MAX_PBUFFER_HEIGHT = 0x8017; |
||||
|
public const int GLX_MAX_PBUFFER_PIXELS = 0x8018; |
||||
|
public const int GLX_PRESERVED_CONTENTS = 0x801B; |
||||
|
public const int GLX_LARGEST_PBUFFER = 0x801C; |
||||
|
public const int GLX_WIDTH = 0x801D; |
||||
|
public const int GLX_HEIGHT = 0x801E; |
||||
|
public const int GLX_EVENT_MASK = 0x801F; |
||||
|
public const int GLX_DAMAGED = 0x8020; |
||||
|
public const int GLX_SAVED = 0x8021; |
||||
|
public const int GLX_WINDOW = 0x8022; |
||||
|
public const int GLX_PBUFFER = 0x8023; |
||||
|
public const int GLX_PBUFFER_HEIGHT = 0x8040; |
||||
|
public const int GLX_PBUFFER_WIDTH = 0x8041; |
||||
|
public const int GLX_RGBA_BIT = 0x00000001; |
||||
|
public const int GLX_COLOR_INDEX_BIT = 0x00000002; |
||||
|
public const int GLX_PBUFFER_CLOBBER_MASK = 0x08000000; |
||||
|
public const int GLX_SAMPLE_BUFFERS = 0x186a0 /*100000*/; |
||||
|
public const int GLX_SAMPLES = 0x186a1 /*100001*/; |
||||
|
public const int GLX_PbufferClobber = 0; |
||||
|
public const int GLX_BufferSwapComplete = 1; |
||||
|
public const int GLX_CONTEXT_DEBUG_BIT_ARB = 0x00000001; |
||||
|
public const int GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x00000002; |
||||
|
public const int GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; |
||||
|
public const int GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; |
||||
|
public const int GLX_CONTEXT_FLAGS_ARB = 0x2094; |
||||
|
public const int GLX_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; |
||||
|
public const int GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; |
||||
|
public const int GLX_CONTEXT_PROFILE_MASK_ARB = 0x9126; |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
using System.Reactive.Disposables; |
||||
|
using System.Threading; |
||||
|
using Avalonia.OpenGL; |
||||
|
namespace Avalonia.X11.Glx |
||||
|
{ |
||||
|
class GlxContext : IGlContext |
||||
|
{ |
||||
|
public IntPtr Handle { get; } |
||||
|
public GlxInterface Glx { get; } |
||||
|
private readonly X11Info _x11; |
||||
|
private readonly IntPtr _defaultXid; |
||||
|
private readonly object _lock = new object(); |
||||
|
|
||||
|
public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, X11Info x11, IntPtr defaultXid) |
||||
|
{ |
||||
|
Handle = handle; |
||||
|
Glx = glx; |
||||
|
_x11 = x11; |
||||
|
_defaultXid = defaultXid; |
||||
|
Display = display; |
||||
|
} |
||||
|
|
||||
|
public GlxDisplay Display { get; } |
||||
|
IGlDisplay IGlContext.Display => Display; |
||||
|
|
||||
|
public IDisposable Lock() |
||||
|
{ |
||||
|
Monitor.Enter(_lock); |
||||
|
return Disposable.Create(() => Monitor.Exit(_lock)); |
||||
|
} |
||||
|
|
||||
|
public void MakeCurrent() => MakeCurrent(_defaultXid); |
||||
|
|
||||
|
public void MakeCurrent(IntPtr xid) |
||||
|
{ |
||||
|
if (!Glx.MakeContextCurrent(_x11.Display, xid, xid, Handle)) |
||||
|
throw new OpenGlException("glXMakeContextCurrent failed "); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,136 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Avalonia.OpenGL; |
||||
|
using static Avalonia.X11.Glx.GlxConsts; |
||||
|
|
||||
|
namespace Avalonia.X11.Glx |
||||
|
{ |
||||
|
unsafe class GlxDisplay : IGlDisplay |
||||
|
{ |
||||
|
private readonly X11Info _x11; |
||||
|
private readonly IntPtr _fbconfig; |
||||
|
private readonly XVisualInfo* _visual; |
||||
|
public GlDisplayType Type => GlDisplayType.OpenGL2; |
||||
|
public GlInterface GlInterface { get; } |
||||
|
|
||||
|
public XVisualInfo* VisualInfo => _visual; |
||||
|
public int SampleCount { get; } |
||||
|
public int StencilSize { get; } |
||||
|
|
||||
|
public GlxContext ImmediateContext { get; } |
||||
|
public GlxContext DeferredContext { get; } |
||||
|
public GlxInterface Glx { get; } = new GlxInterface(); |
||||
|
public GlxDisplay(X11Info x11) |
||||
|
{ |
||||
|
_x11 = x11; |
||||
|
|
||||
|
var baseAttribs = new[] |
||||
|
{ |
||||
|
GLX_X_RENDERABLE, 1, |
||||
|
GLX_RENDER_TYPE, GLX_RGBA_BIT, |
||||
|
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER_BIT, |
||||
|
GLX_DOUBLEBUFFER, 1, |
||||
|
GLX_RED_SIZE, 8, |
||||
|
GLX_GREEN_SIZE, 8, |
||||
|
GLX_BLUE_SIZE, 8, |
||||
|
GLX_ALPHA_SIZE, 8, |
||||
|
GLX_DEPTH_SIZE, 1, |
||||
|
GLX_STENCIL_SIZE, 8, |
||||
|
|
||||
|
}; |
||||
|
|
||||
|
foreach (var attribs in new[] |
||||
|
{ |
||||
|
//baseAttribs.Concat(multiattribs),
|
||||
|
baseAttribs, |
||||
|
}) |
||||
|
{ |
||||
|
var ptr = Glx.ChooseFBConfig(_x11.Display, x11.DefaultScreen, |
||||
|
attribs, out var count); |
||||
|
for (var c = 0 ; c < count; c++) |
||||
|
{ |
||||
|
|
||||
|
var visual = Glx.GetVisualFromFBConfig(_x11.Display, ptr[c]); |
||||
|
// We prefer 32 bit visuals
|
||||
|
if (_fbconfig == IntPtr.Zero || visual->depth == 32) |
||||
|
{ |
||||
|
_fbconfig = ptr[c]; |
||||
|
_visual = visual; |
||||
|
if(visual->depth == 32) |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (_fbconfig != IntPtr.Zero) |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (_fbconfig == IntPtr.Zero) |
||||
|
throw new OpenGlException("Unable to choose FBConfig"); |
||||
|
|
||||
|
if (_visual == null) |
||||
|
throw new OpenGlException("Unable to get visual info from FBConfig"); |
||||
|
if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_SAMPLES, out var samples) == 0) |
||||
|
SampleCount = samples; |
||||
|
if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_STENCIL_SIZE, out var stencil) == 0) |
||||
|
StencilSize = stencil; |
||||
|
|
||||
|
var pbuffers = Enumerable.Range(0, 2).Select(_ => Glx.CreatePbuffer(_x11.Display, _fbconfig, new[] |
||||
|
{ |
||||
|
GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 |
||||
|
})).ToList(); |
||||
|
|
||||
|
XLib.XFlush(_x11.Display); |
||||
|
|
||||
|
ImmediateContext = CreateContext(pbuffers[0],null); |
||||
|
DeferredContext = CreateContext(pbuffers[1], ImmediateContext); |
||||
|
ImmediateContext.MakeCurrent(); |
||||
|
var err = Glx.GetError(); |
||||
|
|
||||
|
GlInterface = new GlInterface(GlxInterface.GlxGetProcAddress); |
||||
|
if (GlInterface.Version == null) |
||||
|
throw new OpenGlException("GL version string is null, aborting"); |
||||
|
} |
||||
|
|
||||
|
public void ClearContext() => Glx.MakeContextCurrent(_x11.Display, |
||||
|
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); |
||||
|
|
||||
|
public GlxContext CreateContext(IGlContext share) => CreateContext(IntPtr.Zero, share); |
||||
|
public GlxContext CreateContext(IntPtr defaultXid, IGlContext share) |
||||
|
{ |
||||
|
var sharelist = ((GlxContext)share)?.Handle ?? IntPtr.Zero; |
||||
|
IntPtr handle = default; |
||||
|
foreach (var ver in new[] |
||||
|
{ |
||||
|
new Version(4, 0), new Version(3, 2), |
||||
|
new Version(3, 0), new Version(2, 0) |
||||
|
}) |
||||
|
{ |
||||
|
|
||||
|
var attrs = new[] |
||||
|
{ |
||||
|
GLX_CONTEXT_MAJOR_VERSION_ARB, ver.Major, |
||||
|
GLX_CONTEXT_MINOR_VERSION_ARB, ver.Minor, |
||||
|
GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, |
||||
|
0 |
||||
|
}; |
||||
|
try |
||||
|
{ |
||||
|
handle = Glx.CreateContextAttribsARB(_x11.Display, _fbconfig, sharelist, true, attrs); |
||||
|
if (handle != IntPtr.Zero) |
||||
|
break; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (handle == IntPtr.Zero) |
||||
|
throw new OpenGlException("Unable to create direct GLX context"); |
||||
|
return new GlxContext(new GlxInterface(), handle, this, _x11, defaultXid); |
||||
|
} |
||||
|
|
||||
|
public void SwapBuffers(IntPtr xid) => Glx.SwapBuffers(_x11.Display, xid); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
using System; |
||||
|
using Avalonia.OpenGL; |
||||
|
|
||||
|
namespace Avalonia.X11.Glx |
||||
|
{ |
||||
|
class GlxGlPlatformSurface: IGlPlatformSurface |
||||
|
{ |
||||
|
|
||||
|
private readonly GlxDisplay _display; |
||||
|
private readonly GlxContext _context; |
||||
|
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; |
||||
|
|
||||
|
public GlxGlPlatformSurface(GlxDisplay display, GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) |
||||
|
{ |
||||
|
_display = display; |
||||
|
_context = context; |
||||
|
_info = info; |
||||
|
} |
||||
|
|
||||
|
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() |
||||
|
{ |
||||
|
return new RenderTarget(_context, _info); |
||||
|
} |
||||
|
|
||||
|
class RenderTarget : IGlPlatformSurfaceRenderTarget |
||||
|
{ |
||||
|
private readonly GlxContext _context; |
||||
|
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; |
||||
|
|
||||
|
public RenderTarget(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) |
||||
|
{ |
||||
|
_context = context; |
||||
|
_info = info; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
// No-op
|
||||
|
} |
||||
|
|
||||
|
public IGlPlatformSurfaceRenderingSession BeginDraw() |
||||
|
{ |
||||
|
var l = _context.Lock(); |
||||
|
try |
||||
|
{ |
||||
|
_context.MakeCurrent(_info.Handle); |
||||
|
return new Session(_context, _info, l); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
l.Dispose(); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class Session : IGlPlatformSurfaceRenderingSession |
||||
|
{ |
||||
|
private readonly GlxContext _context; |
||||
|
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; |
||||
|
private IDisposable _lock; |
||||
|
|
||||
|
public Session(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info, |
||||
|
IDisposable @lock) |
||||
|
{ |
||||
|
_context = context; |
||||
|
_info = info; |
||||
|
_lock = @lock; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_context.Display.GlInterface.Flush(); |
||||
|
_context.Glx.WaitGL(); |
||||
|
_context.Display.SwapBuffers(_info.Handle); |
||||
|
_context.Glx.WaitX(); |
||||
|
_context.Display.ClearContext(); |
||||
|
_lock.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public IGlDisplay Display => _context.Display; |
||||
|
public PixelSize Size => _info.Size; |
||||
|
public double Scaling => _info.Scaling; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Logging; |
||||
|
using Avalonia.OpenGL; |
||||
|
|
||||
|
namespace Avalonia.X11.Glx |
||||
|
{ |
||||
|
class GlxGlPlatformFeature : IWindowingPlatformGlFeature |
||||
|
{ |
||||
|
public GlxDisplay Display { get; private set; } |
||||
|
public IGlContext ImmediateContext { get; private set; } |
||||
|
public GlxContext DeferredContext { get; private set; } |
||||
|
|
||||
|
public static bool TryInitialize(X11Info x11) |
||||
|
{ |
||||
|
var feature = TryCreate(x11); |
||||
|
if (feature != null) |
||||
|
{ |
||||
|
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>().ToConstant(feature); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public static GlxGlPlatformFeature TryCreate(X11Info x11) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var disp = new GlxDisplay(x11); |
||||
|
return new GlxGlPlatformFeature |
||||
|
{ |
||||
|
Display = disp, |
||||
|
ImmediateContext = disp.ImmediateContext, |
||||
|
DeferredContext = disp.DeferredContext |
||||
|
}; |
||||
|
} |
||||
|
catch(Exception e) |
||||
|
{ |
||||
|
Logger.Error("OpenGL", null, "Unable to initialize GLX-based rendering: {0}", e); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,263 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Platform.Interop; |
||||
|
// ReSharper disable IdentifierTypo
|
||||
|
namespace Avalonia.X11.NativeDialogs |
||||
|
{ |
||||
|
|
||||
|
static unsafe class Glib |
||||
|
{ |
||||
|
private const string GlibName = "libglib-2.0.so.0"; |
||||
|
private const string GObjectName = "libgobject-2.0.so.0"; |
||||
|
|
||||
|
[DllImport(GlibName)] |
||||
|
public static extern void g_slist_free(GSList* data); |
||||
|
|
||||
|
[DllImport(GObjectName)] |
||||
|
private static extern void g_object_ref(IntPtr instance); |
||||
|
|
||||
|
[DllImport(GObjectName)] |
||||
|
private static extern ulong g_signal_connect_object(IntPtr instance, Utf8Buffer signal, |
||||
|
IntPtr handler, IntPtr userData, int flags); |
||||
|
|
||||
|
[DllImport(GObjectName)] |
||||
|
private static extern void g_object_unref(IntPtr instance); |
||||
|
|
||||
|
[DllImport(GObjectName)] |
||||
|
private static extern ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId); |
||||
|
|
||||
|
private delegate bool timeout_callback(IntPtr data); |
||||
|
|
||||
|
[DllImport(GlibName)] |
||||
|
private static extern ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data, |
||||
|
IntPtr destroy); |
||||
|
|
||||
|
|
||||
|
class ConnectedSignal : IDisposable |
||||
|
{ |
||||
|
private readonly IntPtr _instance; |
||||
|
private GCHandle _handle; |
||||
|
private readonly ulong _id; |
||||
|
|
||||
|
public ConnectedSignal(IntPtr instance, GCHandle handle, ulong id) |
||||
|
{ |
||||
|
_instance = instance; |
||||
|
g_object_ref(instance); |
||||
|
_handle = handle; |
||||
|
_id = id; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
if (_handle.IsAllocated) |
||||
|
{ |
||||
|
g_signal_handler_disconnect(_instance, _id); |
||||
|
g_object_unref(_instance); |
||||
|
_handle.Free(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static IDisposable ConnectSignal<T>(IntPtr obj, string name, T handler) |
||||
|
{ |
||||
|
var handle = GCHandle.Alloc(handler); |
||||
|
var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler); |
||||
|
using (var utf = new Utf8Buffer(name)) |
||||
|
{ |
||||
|
var id = g_signal_connect_object(obj, utf, ptr, IntPtr.Zero, 0); |
||||
|
if (id == 0) |
||||
|
throw new ArgumentException("Unable to connect to signal " + name); |
||||
|
return new ConnectedSignal(obj, handle, id); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
static bool TimeoutHandler(IntPtr data) |
||||
|
{ |
||||
|
var handle = GCHandle.FromIntPtr(data); |
||||
|
var cb = (Func<bool>)handle.Target; |
||||
|
if (!cb()) |
||||
|
{ |
||||
|
handle.Free(); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private static readonly timeout_callback s_pinnedHandler; |
||||
|
|
||||
|
static Glib() |
||||
|
{ |
||||
|
s_pinnedHandler = TimeoutHandler; |
||||
|
} |
||||
|
|
||||
|
static void AddTimeout(int priority, uint interval, Func<bool> callback) |
||||
|
{ |
||||
|
var handle = GCHandle.Alloc(callback); |
||||
|
g_timeout_add_full(priority, interval, s_pinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero); |
||||
|
} |
||||
|
|
||||
|
public static Task<T> RunOnGlibThread<T>(Func<T> action) |
||||
|
{ |
||||
|
var tcs = new TaskCompletionSource<T>(); |
||||
|
AddTimeout(0, 0, () => |
||||
|
{ |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
tcs.SetResult(action()); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
tcs.TrySetException(e); |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
}); |
||||
|
return tcs.Task; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
unsafe struct GSList |
||||
|
{ |
||||
|
public readonly IntPtr Data; |
||||
|
public readonly GSList* Next; |
||||
|
} |
||||
|
|
||||
|
enum GtkFileChooserAction |
||||
|
{ |
||||
|
Open, |
||||
|
Save, |
||||
|
SelectFolder, |
||||
|
} |
||||
|
|
||||
|
// ReSharper disable UnusedMember.Global
|
||||
|
enum GtkResponseType |
||||
|
{ |
||||
|
Help = -11, |
||||
|
Apply = -10, |
||||
|
No = -9, |
||||
|
Yes = -8, |
||||
|
Close = -7, |
||||
|
Cancel = -6, |
||||
|
Ok = -5, |
||||
|
DeleteEvent = -4, |
||||
|
Accept = -3, |
||||
|
Reject = -2, |
||||
|
None = -1, |
||||
|
} |
||||
|
// ReSharper restore UnusedMember.Global
|
||||
|
|
||||
|
static unsafe class Gtk |
||||
|
{ |
||||
|
private static IntPtr s_display; |
||||
|
private const string GdkName = "libgdk-3.so.0"; |
||||
|
private const string GtkName = "libgtk-3.so.0"; |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
static extern void gtk_main_iteration(); |
||||
|
|
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
public static extern void gtk_window_set_modal(IntPtr window, bool modal); |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
public static extern void gtk_window_present(IntPtr gtkWindow); |
||||
|
|
||||
|
|
||||
|
public delegate bool signal_generic(IntPtr gtkWidget, IntPtr userData); |
||||
|
|
||||
|
public delegate bool signal_dialog_response(IntPtr gtkWidget, GtkResponseType response, IntPtr userData); |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
public static extern IntPtr gtk_file_chooser_dialog_new(Utf8Buffer title, IntPtr parent, |
||||
|
GtkFileChooserAction action, IntPtr ignore); |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
public static extern void gtk_file_chooser_set_select_multiple(IntPtr chooser, bool allow); |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
public static extern void |
||||
|
gtk_dialog_add_button(IntPtr raw, Utf8Buffer button_text, GtkResponseType response_id); |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
public static extern GSList* gtk_file_chooser_get_filenames(IntPtr chooser); |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
public static extern void gtk_file_chooser_set_filename(IntPtr chooser, Utf8Buffer file); |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
public static extern void gtk_widget_realize(IntPtr gtkWidget); |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
public static extern IntPtr gtk_widget_get_window(IntPtr gtkWidget); |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
public static extern void gtk_widget_hide(IntPtr gtkWidget); |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
static extern bool gtk_init_check(int argc, IntPtr argv); |
||||
|
|
||||
|
[DllImport(GdkName)] |
||||
|
static extern IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid); |
||||
|
|
||||
|
[DllImport(GdkName)] |
||||
|
static extern IntPtr gdk_set_allowed_backends(Utf8Buffer backends); |
||||
|
|
||||
|
[DllImport(GdkName)] |
||||
|
static extern IntPtr gdk_display_get_default(); |
||||
|
|
||||
|
[DllImport(GtkName)] |
||||
|
static extern IntPtr gtk_application_new(Utf8Buffer appId, int flags); |
||||
|
|
||||
|
[DllImport(GdkName)] |
||||
|
public static extern void gdk_window_set_transient_for(IntPtr window, IntPtr parent); |
||||
|
|
||||
|
public static IntPtr GetForeignWindow(IntPtr xid) => gdk_x11_window_foreign_new_for_display(s_display, xid); |
||||
|
|
||||
|
public static Task<bool> StartGtk() |
||||
|
{ |
||||
|
var tcs = new TaskCompletionSource<bool>(); |
||||
|
new Thread(() => |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
using (var backends = new Utf8Buffer("x11")) |
||||
|
gdk_set_allowed_backends(backends); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//Ignore
|
||||
|
} |
||||
|
|
||||
|
Environment.SetEnvironmentVariable("WAYLAND_DISPLAY", |
||||
|
"/proc/fake-display-to-prevent-wayland-initialization-by-gtk3"); |
||||
|
|
||||
|
if (!gtk_init_check(0, IntPtr.Zero)) |
||||
|
{ |
||||
|
tcs.SetResult(false); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
IntPtr app; |
||||
|
using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid():N}")) |
||||
|
app = gtk_application_new(utf, 0); |
||||
|
if (app == IntPtr.Zero) |
||||
|
{ |
||||
|
tcs.SetResult(false); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
s_display = gdk_display_get_default(); |
||||
|
tcs.SetResult(true); |
||||
|
while (true) |
||||
|
gtk_main_iteration(); |
||||
|
}) {Name = "GTK3THREAD", IsBackground = true}.Start(); |
||||
|
return tcs.Task; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,122 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Platform; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Platform.Interop; |
||||
|
using static Avalonia.X11.NativeDialogs.Glib; |
||||
|
using static Avalonia.X11.NativeDialogs.Gtk; |
||||
|
// ReSharper disable AccessToModifiedClosure
|
||||
|
namespace Avalonia.X11.NativeDialogs |
||||
|
{ |
||||
|
class GtkSystemDialog : ISystemDialogImpl |
||||
|
{ |
||||
|
private Task<bool> _initialized; |
||||
|
private unsafe Task<string[]> ShowDialog(string title, IWindowImpl parent, GtkFileChooserAction action, |
||||
|
bool multiSelect, string initialFileName) |
||||
|
{ |
||||
|
IntPtr dlg; |
||||
|
using (var name = new Utf8Buffer(title)) |
||||
|
dlg = gtk_file_chooser_dialog_new(name, IntPtr.Zero, action, IntPtr.Zero); |
||||
|
UpdateParent(dlg, parent); |
||||
|
if (multiSelect) |
||||
|
gtk_file_chooser_set_select_multiple(dlg, true); |
||||
|
|
||||
|
gtk_window_set_modal(dlg, true); |
||||
|
var tcs = new TaskCompletionSource<string[]>(); |
||||
|
List<IDisposable> disposables = null; |
||||
|
|
||||
|
void Dispose() |
||||
|
{ |
||||
|
// ReSharper disable once PossibleNullReferenceException
|
||||
|
foreach (var d in disposables) d.Dispose(); |
||||
|
disposables.Clear(); |
||||
|
} |
||||
|
|
||||
|
disposables = new List<IDisposable> |
||||
|
{ |
||||
|
ConnectSignal<signal_generic>(dlg, "close", delegate |
||||
|
{ |
||||
|
tcs.TrySetResult(null); |
||||
|
Dispose(); |
||||
|
return false; |
||||
|
}), |
||||
|
ConnectSignal<signal_dialog_response>(dlg, "response", (_, resp, __) => |
||||
|
{ |
||||
|
string[] result = null; |
||||
|
if (resp == GtkResponseType.Accept) |
||||
|
{ |
||||
|
var resultList = new List<string>(); |
||||
|
var gs = gtk_file_chooser_get_filenames(dlg); |
||||
|
var cgs = gs; |
||||
|
while (cgs != null) |
||||
|
{ |
||||
|
if (cgs->Data != IntPtr.Zero) |
||||
|
resultList.Add(Utf8Buffer.StringFromPtr(cgs->Data)); |
||||
|
cgs = cgs->Next; |
||||
|
} |
||||
|
g_slist_free(gs); |
||||
|
result = resultList.ToArray(); |
||||
|
} |
||||
|
|
||||
|
gtk_widget_hide(dlg); |
||||
|
Dispose(); |
||||
|
tcs.TrySetResult(result); |
||||
|
return false; |
||||
|
}) |
||||
|
}; |
||||
|
using (var open = new Utf8Buffer("Open")) |
||||
|
gtk_dialog_add_button(dlg, open, GtkResponseType.Accept); |
||||
|
using (var open = new Utf8Buffer("Cancel")) |
||||
|
gtk_dialog_add_button(dlg, open, GtkResponseType.Cancel); |
||||
|
if (initialFileName != null) |
||||
|
using (var fn = new Utf8Buffer(initialFileName)) |
||||
|
gtk_file_chooser_set_filename(dlg, fn); |
||||
|
gtk_window_present(dlg); |
||||
|
return tcs.Task; |
||||
|
} |
||||
|
|
||||
|
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) |
||||
|
{ |
||||
|
await EnsureInitialized(); |
||||
|
return await await RunOnGlibThread( |
||||
|
() => ShowDialog(dialog.Title, parent, |
||||
|
dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save, |
||||
|
(dialog as OpenFileDialog)?.AllowMultiple ?? false, |
||||
|
Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory, |
||||
|
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName))); |
||||
|
} |
||||
|
|
||||
|
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) |
||||
|
{ |
||||
|
await EnsureInitialized(); |
||||
|
return await await RunOnGlibThread(async () => |
||||
|
{ |
||||
|
var res = await ShowDialog(dialog.Title, parent, |
||||
|
GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory); |
||||
|
return res?.FirstOrDefault(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async Task EnsureInitialized() |
||||
|
{ |
||||
|
if (_initialized == null) _initialized = StartGtk(); |
||||
|
|
||||
|
if (!(await _initialized)) |
||||
|
throw new Exception("Unable to initialize GTK on separate thread"); |
||||
|
} |
||||
|
|
||||
|
void UpdateParent(IntPtr chooser, IWindowImpl parentWindow) |
||||
|
{ |
||||
|
var xid = parentWindow.Handle.Handle; |
||||
|
gtk_widget_realize(chooser); |
||||
|
var window = gtk_widget_get_window(chooser); |
||||
|
var parent = GetForeignWindow(xid); |
||||
|
if (window != IntPtr.Zero && parent != IntPtr.Zero) |
||||
|
gdk_window_set_transient_for(window, parent); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue