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 XML documentation to any related classes? |
|||
- [ ] 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 #456 |
|||
Fixes #456 |
|||
--> |
|||
|
|||
@ -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="SkiaSharp.NativeAssets.Linux" Version="1.68.0" /> |
|||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.1" /> |
|||
</ItemGroup> |
|||
</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\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.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