diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 7e2bbc13bc..097815cc69 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -138,9 +138,19 @@ partial class Build : NukeBuild .SetWorkingDirectory(webappDir) .SetCommand("dist")); }); - - Target Compile => _ => _ + + Target CompileNative => _ => _ .DependsOn(Clean) + .OnlyWhenStatic(() => EnvironmentInfo.IsOsx) + .Executes(() => + { + var project = $"{RootDirectory}/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/"; + var args = $"-project {project} -configuration {Parameters.Configuration} CONFIGURATION_BUILD_DIR={RootDirectory}/Build/Products/Release"; + ProcessTasks.StartProcess("xcodebuild", args).AssertZeroExitCode(); + }); + + Target Compile => _ => _ + .DependsOn(Clean, CompileNative) .DependsOn(CompileHtmlPreviewer) .Executes(async () => { diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml index f90a0c4658..a49616e543 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml @@ -51,6 +51,11 @@ Width="200" Margin="0,0,0,8" FilterMode="None"/> + + diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs index f9d6a72a3a..574cc79a7d 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs @@ -92,13 +92,28 @@ namespace ControlCatalog.Pages } public StateData[] States { get; private set; } + private LinkedList[] BuildAllSentences() + { + return new string[] + { + "Hello world", + "No this is Patrick", + "Never gonna give you up", + "How does one patch KDE2 under FreeBSD" + } + .Select(x => new LinkedList(x.Split(' '))) + .ToArray(); + } + public LinkedList[] Sentences { get; private set; } + public AutoCompleteBoxPage() { this.InitializeComponent(); States = BuildAllStates(); + Sentences = BuildAllSentences(); - foreach (AutoCompleteBox box in GetAllAutoCompleteBox()) + foreach (AutoCompleteBox box in GetAllAutoCompleteBox().Where(x => x.Name != "CustomAutocompleteBox")) { box.Items = States; } @@ -116,6 +131,11 @@ namespace ControlCatalog.Pages var asyncBox = this.FindControl("AsyncBox"); asyncBox.AsyncPopulator = PopulateAsync; + + var customAutocompleteBox = this.FindControl("CustomAutocompleteBox"); + customAutocompleteBox.Items = Sentences.SelectMany(x => x); + customAutocompleteBox.TextFilter = LastWordContains; + customAutocompleteBox.TextSelector = AppendWord; } private IEnumerable GetAllAutoCompleteBox() { @@ -137,6 +157,42 @@ namespace ControlCatalog.Pages .ToList(); } + private bool LastWordContains(string searchText, string item) + { + var words = searchText.Split(' '); + var options = Sentences.Select(x => x.First).ToArray(); + for (var i = 0; i < words.Length; ++i) + { + var word = words[i]; + for (var j = 0; j < options.Length; ++j) + { + var option = options[j]; + if (option == null) + continue; + + if (i == words.Length - 1) + { + options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null; + } + else + { + options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null; + } + } + } + + return options.Any(x => x != null && x.Value == item); + } + private string AppendWord(string text, string item) + { + string[] parts = text.Split(' '); + if (parts.Length == 0) + return item; + + parts[parts.Length - 1] = item; + return string.Join(" ", parts); + } + private void InitializeComponent() { AvaloniaXamlLoader.Load(this); diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs index 6c13a5ac22..cb79bf219a 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using Avalonia; using Avalonia.Controls; using Avalonia.OpenGL; +using Avalonia.OpenGL.Controls; using Avalonia.Platform.Interop; using Avalonia.Threading; using static Avalonia.OpenGL.GlConsts; diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index 770960d7c4..93fbe5e412 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -3,8 +3,8 @@ x:Class="RenderDemo.MainWindow" Title="AvaloniaUI Rendering Test" xmlns:pages="clr-namespace:RenderDemo.Pages" - Width="800" - Height="600"> + Width="{Binding Width, Mode=TwoWay}" + Height="{Binding Height, Mode=TwoWay}"> @@ -24,6 +24,10 @@ + + + diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs index 7f15845596..ddee880288 100644 --- a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs +++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs @@ -61,7 +61,6 @@ namespace RenderDemo.Pages { Foreground = Brushes.Black, GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _glyphIndices), - BaselineOrigin = new Point(0, -_glyphTypeface.Ascent * scale) }; drawingGroup.Children.Add(glyphRunDrawing); @@ -69,7 +68,7 @@ namespace RenderDemo.Pages var geometryDrawing = new GeometryDrawing { Pen = new Pen(Brushes.Black), - Geometry = new RectangleGeometry { Rect = glyphRunDrawing.GlyphRun.Bounds } + Geometry = new RectangleGeometry { Rect = new Rect(glyphRunDrawing.GlyphRun.Size) } }; drawingGroup.Children.Add(geometryDrawing); diff --git a/samples/RenderDemo/ViewModels/MainWindowViewModel.cs b/samples/RenderDemo/ViewModels/MainWindowViewModel.cs index d2d789a687..eda5e80530 100644 --- a/samples/RenderDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/RenderDemo/ViewModels/MainWindowViewModel.cs @@ -1,5 +1,6 @@ -using System; -using System.Reactive; +using System.Reactive; +using System.Threading.Tasks; + using ReactiveUI; namespace RenderDemo.ViewModels @@ -8,26 +9,61 @@ namespace RenderDemo.ViewModels { private bool drawDirtyRects = false; private bool drawFps = true; + private double width = 800; + private double height = 600; public MainWindowViewModel() { ToggleDrawDirtyRects = ReactiveCommand.Create(() => DrawDirtyRects = !DrawDirtyRects); ToggleDrawFps = ReactiveCommand.Create(() => DrawFps = !DrawFps); + ResizeWindow = ReactiveCommand.CreateFromTask(ResizeWindowAsync); } public bool DrawDirtyRects { - get { return drawDirtyRects; } - set { this.RaiseAndSetIfChanged(ref drawDirtyRects, value); } + get => drawDirtyRects; + set => this.RaiseAndSetIfChanged(ref drawDirtyRects, value); } public bool DrawFps { - get { return drawFps; } - set { this.RaiseAndSetIfChanged(ref drawFps, value); } + get => drawFps; + set => this.RaiseAndSetIfChanged(ref drawFps, value); + } + + public double Width + { + get => width; + set => this.RaiseAndSetIfChanged(ref width, value); + } + + public double Height + { + get => height; + set => this.RaiseAndSetIfChanged(ref height, value); } public ReactiveCommand ToggleDrawDirtyRects { get; } public ReactiveCommand ToggleDrawFps { get; } + public ReactiveCommand ResizeWindow { get; } + + private async Task ResizeWindowAsync() + { + for (int i = 0; i < 30; i++) + { + Width += 10; + Height += 5; + await Task.Delay(10); + } + + await Task.Delay(10); + + for (int i = 0; i < 30; i++) + { + Width -= 10; + Height -= 5; + await Task.Delay(10); + } + } } } diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt new file mode 100644 index 0000000000..4668a572c5 --- /dev/null +++ b/src/Avalonia.Base/ApiCompatBaseline.txt @@ -0,0 +1,3 @@ +Compat issues with assembly Avalonia.Base: +CannotAddAbstractMembers : Member 'protected System.IObservable Avalonia.AvaloniaProperty.GetChanged()' is abstract in the implementation but is missing in the contract. +Total Issues: 1 diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 39391490b0..3ae0445e9b 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reactive.Subjects; using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Utilities; @@ -18,7 +17,6 @@ namespace Avalonia public static readonly object UnsetValue = new UnsetValueType(); private static int s_nextId; - private readonly Subject _changed; private readonly PropertyMetadata _defaultMetadata; private readonly Dictionary _metadata; private readonly Dictionary _metadataCache = new Dictionary(); @@ -50,7 +48,6 @@ namespace Avalonia throw new ArgumentException("'name' may not contain periods."); } - _changed = new Subject(); _metadata = new Dictionary(); Name = name; @@ -77,7 +74,6 @@ namespace Avalonia Contract.Requires(source != null); Contract.Requires(ownerType != null); - _changed = source._changed; _metadata = new Dictionary(); Name = source.Name; @@ -139,7 +135,7 @@ namespace Avalonia /// An observable that is fired when this property changes on any /// instance. /// - public IObservable Changed => _changed; + public IObservable Changed => GetChanged(); /// /// Gets a method that gets called before and after the property starts being notified on an @@ -474,15 +470,6 @@ namespace Avalonia public abstract void Accept(IAvaloniaPropertyVisitor vistor, ref TData data) where TData : struct; - /// - /// Notifies the observable. - /// - /// The observable arguments. - internal void NotifyChanged(AvaloniaPropertyChangedEventArgs e) - { - _changed.OnNext(e); - } - /// /// Routes an untyped ClearValue call to a typed call. /// @@ -553,6 +540,8 @@ namespace Avalonia _hasMetadataOverrides = true; } + protected abstract IObservable GetChanged(); + private PropertyMetadata GetMetadataWithOverrides(Type type) { if (type is null) diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index 2f26d855f2..d5549e979b 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/src/Avalonia.Base/AvaloniaProperty`1.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Subjects; using Avalonia.Data; using Avalonia.Utilities; @@ -10,6 +11,8 @@ namespace Avalonia /// The value type of the property. public abstract class AvaloniaProperty : AvaloniaProperty { + private readonly Subject> _changed; + /// /// Initializes a new instance of the class. /// @@ -24,22 +27,61 @@ namespace Avalonia Action notifying = null) : base(name, typeof(TValue), ownerType, metadata, notifying) { + _changed = new Subject>(); } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The property to copy. /// The new owner type. /// Optional overridden metadata. + [Obsolete("Use constructor with AvaloniaProperty instead.", true)] protected AvaloniaProperty( - AvaloniaProperty source, - Type ownerType, + AvaloniaProperty source, + Type ownerType, + PropertyMetadata metadata) + : this(source as AvaloniaProperty ?? throw new InvalidOperationException(), ownerType, metadata) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The property to copy. + /// The new owner type. + /// Optional overridden metadata. + protected AvaloniaProperty( + AvaloniaProperty source, + Type ownerType, PropertyMetadata metadata) : base(source, ownerType, metadata) { + _changed = source._changed; + } + + /// + /// Gets an observable that is fired when this property changes on any + /// instance. + /// + /// + /// An observable that is fired when this property changes on any + /// instance. + /// + + public new IObservable> Changed => _changed; + + /// + /// Notifies the observable. + /// + /// The observable arguments. + internal void NotifyChanged(AvaloniaPropertyChangedEventArgs e) + { + _changed.OnNext(e); } + protected override IObservable GetChanged() => Changed; + protected BindingValue TryConvert(object value) { if (value == UnsetValue) diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index dbc2625b86..a2f113adb7 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -32,15 +32,30 @@ namespace Avalonia } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The property to copy. /// The new owner type. /// Optional overridden metadata. + [Obsolete("Use constructor with DirectPropertyBase instead.", true)] protected DirectPropertyBase( AvaloniaProperty source, Type ownerType, PropertyMetadata metadata) + : this(source as DirectPropertyBase ?? throw new InvalidOperationException(), ownerType, metadata) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The property to copy. + /// The new owner type. + /// Optional overridden metadata. + protected DirectPropertyBase( + DirectPropertyBase source, + Type ownerType, + PropertyMetadata metadata) : base(source, ownerType, metadata) { } diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 9ca0b91523..7c57ea3db9 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -24,12 +24,14 @@ using Avalonia.Input.Platform; using System.ComponentModel.DataAnnotations; using Avalonia.Controls.Utils; using Avalonia.Layout; +using Avalonia.Controls.Metadata; namespace Avalonia.Controls { /// /// Displays data in a customizable grid. /// + [PseudoClasses(":invalid")] public partial class DataGrid : TemplatedControl { private const string DATAGRID_elementRowsPresenterName = "PART_RowsPresenter"; diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs index e5fbfa1a81..445dc541a7 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs @@ -3,6 +3,7 @@ // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Input; @@ -12,6 +13,7 @@ namespace Avalonia.Controls /// /// Represents an individual cell. /// + [PseudoClasses(":selected", ":current", ":edited", ":invalid")] public class DataGridCell : ContentControl { private const string DATAGRIDCELL_elementRightGridLine = "PART_RightGridLine"; diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 25aae99942..856d1f6566 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -14,12 +14,14 @@ using Avalonia.Utilities; using System; using Avalonia.Controls.Utils; using Avalonia.Controls.Mixins; +using Avalonia.Controls.Metadata; namespace Avalonia.Controls { /// /// Represents an individual column header. /// + [PseudoClasses(":dragIndicator", ":pressed", ":sortascending", ":sortdescending")] public class DataGridColumnHeader : ContentControl { private enum DragMode diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index d5ce8dba75..c3562c53a4 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -3,6 +3,7 @@ // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; @@ -20,6 +21,7 @@ namespace Avalonia.Controls /// /// Represents a row. /// + [PseudoClasses(":selected", ":editing", ":invalid")] public class DataGridRow : TemplatedControl { diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index 0833247439..1e03b134b1 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -3,6 +3,7 @@ // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. +using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Input; @@ -13,6 +14,7 @@ using System.Reactive.Linq; namespace Avalonia.Controls { + [PseudoClasses(":pressed", ":current", ":expanded")] public class DataGridRowGroupHeader : TemplatedControl { private const string DATAGRIDROWGROUPHEADER_expanderButton = "ExpanderButton"; diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs index 8f8b1742ba..0cd3589a57 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs @@ -3,6 +3,7 @@ // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. +using Avalonia.Controls.Metadata; using Avalonia.Input; using Avalonia.Media; using System.Diagnostics; @@ -12,6 +13,7 @@ namespace Avalonia.Controls.Primitives /// /// Represents an individual row header. /// + [PseudoClasses(":invalid", ":selected", ":editing", ":current")] public class DataGridRowHeader : ContentControl { private const string DATAGRIDROWHEADER_elementRootName = "PART_Root"; diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index c164f282e8..bfd633c947 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -14,6 +14,7 @@ using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using Avalonia.Collections; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Controls.Utils; @@ -30,6 +31,7 @@ namespace Avalonia.Controls /// /// event. /// + [PseudoClasses(":dropdownopen")] public class PopulatedEventArgs : EventArgs { /// @@ -225,6 +227,27 @@ namespace Avalonia.Controls Custom = 13, } + /// + /// Represents the selector used by the + /// control to + /// determine how the specified text should be modified with an item. + /// + /// + /// Modified text that will be used by the + /// . + /// + /// The string used as the basis for filtering. + /// + /// The selected item that should be combined with the + /// parameter. + /// + /// + /// The type used for filtering the + /// . + /// This type can be either a string or an object. + /// + public delegate string AutoCompleteSelector(string search, T item); + /// /// Represents a control that provides a text box for user input and a /// drop-down that contains possible matches based on the input in the text @@ -362,6 +385,9 @@ namespace Avalonia.Controls private AutoCompleteFilterPredicate _itemFilter; private AutoCompleteFilterPredicate _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith); + private AutoCompleteSelector _itemSelector; + private AutoCompleteSelector _textSelector; + public static readonly RoutedEvent SelectionChangedEvent = RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox)); @@ -528,6 +554,34 @@ namespace Avalonia.Controls (o, v) => o.TextFilter = v, unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith)); + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DirectProperty> ItemSelectorProperty = + AvaloniaProperty.RegisterDirect>( + nameof(ItemSelector), + o => o.ItemSelector, + (o, v) => o.ItemSelector = v); + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DirectProperty> TextSelectorProperty = + AvaloniaProperty.RegisterDirect>( + nameof(TextSelector), + o => o.TextSelector, + (o, v) => o.TextSelector = v); + /// /// Identifies the /// @@ -1061,6 +1115,40 @@ namespace Avalonia.Controls set { SetAndRaise(TextFilterProperty, ref _textFilter, value); } } + /// + /// Gets or sets the custom method that combines the user-entered + /// text and one of the items specified by the + /// . + /// + /// + /// The custom method that combines the user-entered + /// text and one of the items specified by the + /// . + /// + public AutoCompleteSelector ItemSelector + { + get { return _itemSelector; } + set { SetAndRaise(ItemSelectorProperty, ref _itemSelector, value); } + } + + /// + /// Gets or sets the custom method that combines the user-entered + /// text and one of the items specified by the + /// + /// in a text-based way. + /// + /// + /// The custom method that combines the user-entered + /// text and one of the items specified by the + /// + /// in a text-based way. + /// + public AutoCompleteSelector TextSelector + { + get { return _textSelector; } + set { SetAndRaise(TextSelectorProperty, ref _textSelector, value); } + } + public Func>> AsyncPopulator { get { return _asyncPopulator; } @@ -2329,6 +2417,14 @@ namespace Avalonia.Controls { text = SearchText; } + else if (TextSelector != null) + { + text = TextSelector(SearchText, FormatValue(newItem, true)); + } + else if (ItemSelector != null) + { + text = ItemSelector(SearchText, newItem); + } else { text = FormatValue(newItem, true); diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index b54eb2ac57..e94d00b2ff 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Windows.Input; +using Avalonia.Controls.Metadata; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; @@ -28,6 +29,7 @@ namespace Avalonia.Controls /// /// A button control. /// + [PseudoClasses(":pressed")] public class Button : ContentControl { /// diff --git a/src/Avalonia.Controls/ButtonSpinner.cs b/src/Avalonia.Controls/ButtonSpinner.cs index 44f66d397a..5fe2cf3704 100644 --- a/src/Avalonia.Controls/ButtonSpinner.cs +++ b/src/Avalonia.Controls/ButtonSpinner.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; @@ -15,6 +16,7 @@ namespace Avalonia.Controls /// /// Represents a spinner control that includes two Buttons. /// + [PseudoClasses(":left", ":right")] public class ButtonSpinner : Spinner { /// diff --git a/src/Avalonia.Controls/Calendar/CalendarButton.cs b/src/Avalonia.Controls/Calendar/CalendarButton.cs index 80370df145..76af933b55 100644 --- a/src/Avalonia.Controls/Calendar/CalendarButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarButton.cs @@ -3,6 +3,7 @@ // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. +using Avalonia.Controls.Metadata; using Avalonia.Input; using System; @@ -12,6 +13,7 @@ namespace Avalonia.Controls.Primitives /// Represents a button on a /// . /// + [PseudoClasses(":selected", ":inactive", ":btnfocused")] public sealed class CalendarButton : Button { /// diff --git a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs index 3a39bd10fa..d5748bb9e4 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs @@ -5,10 +5,12 @@ using System; using System.Globalization; +using Avalonia.Controls.Metadata; using Avalonia.Input; namespace Avalonia.Controls.Primitives { + [PseudoClasses(":pressed", ":disabled", ":selected", ":inactive", ":today", ":blackout", ":dayfocused")] public sealed class CalendarDayButton : Button { /// diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index 0be7c4f67e..e9ea942142 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using Avalonia.Controls.Metadata; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; @@ -18,6 +19,7 @@ namespace Avalonia.Controls.Primitives /// Represents the currently displayed month or year on a /// . /// + [PseudoClasses(":calendardisabled")] public sealed class CalendarItem : TemplatedControl { /// diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index a86cbc271b..cd60130c5b 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Disposables; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; #nullable enable @@ -9,6 +10,7 @@ namespace Avalonia.Controls.Chrome /// /// Draws window minimize / maximize / close buttons in a when managed client decorations are enabled. /// + [PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")] public class CaptionButtons : TemplatedControl { private CompositeDisposable? _disposables; diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index c0c8076dd8..fbddb06952 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Disposables; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; #nullable enable @@ -9,6 +10,7 @@ namespace Avalonia.Controls.Chrome /// /// Draws a titlebar when managed client decorations are enabled. /// + [PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")] public class TitleBar : TemplatedControl { private CompositeDisposable? _disposables; diff --git a/src/Avalonia.Controls/DataValidationErrors.cs b/src/Avalonia.Controls/DataValidationErrors.cs index dfe9a16532..3c64691816 100644 --- a/src/Avalonia.Controls/DataValidationErrors.cs +++ b/src/Avalonia.Controls/DataValidationErrors.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Templates; using Avalonia.Data; @@ -14,6 +15,7 @@ namespace Avalonia.Controls /// /// You will probably only want to create instances inside of control templates. /// + [PseudoClasses(":error")] public class DataValidationErrors : ContentControl { /// diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index a41c159980..8d893154eb 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -1,4 +1,5 @@ -using Avalonia.Controls.Primitives; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; using Avalonia.Interactivity; @@ -11,6 +12,7 @@ namespace Avalonia.Controls /// /// A control to allow the user to select a date /// + [PseudoClasses(":hasnodate")] public class DatePicker : TemplatedControl { /// diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index e54da1fb3a..e4ff5e9e5b 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -1,4 +1,5 @@ -using Avalonia.Controls.Primitives; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; using System; @@ -9,6 +10,7 @@ namespace Avalonia.Controls /// /// A control to allow the user to select a time /// + [PseudoClasses(":hasnotime")] public class TimePicker : TemplatedControl { /// diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs index 43882b70c8..9ff2e41fa9 100644 --- a/src/Avalonia.Controls/Expander.cs +++ b/src/Avalonia.Controls/Expander.cs @@ -1,6 +1,6 @@ using Avalonia.Animation; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; -using Avalonia.Data; namespace Avalonia.Controls { @@ -12,6 +12,7 @@ namespace Avalonia.Controls Right } + [PseudoClasses(":expanded", ":up", ":down", ":left", ":right")] public class Expander : HeaderedContentControl { public static readonly StyledProperty ContentTransitionProperty = diff --git a/src/Avalonia.Controls/IconElement.cs b/src/Avalonia.Controls/IconElement.cs new file mode 100644 index 0000000000..82b7a0660c --- /dev/null +++ b/src/Avalonia.Controls/IconElement.cs @@ -0,0 +1,9 @@ +using Avalonia.Controls.Primitives; + +namespace Avalonia.Controls +{ + public abstract class IconElement : TemplatedControl + { + + } +} diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index a3dfe33641..3aec06e4eb 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using Avalonia.Collections; using Avalonia.Controls.Generators; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; @@ -18,6 +19,7 @@ namespace Avalonia.Controls /// /// Displays a collection of items. /// + [PseudoClasses(":empty", ":singleitem")] public class ItemsControl : TemplatedControl, IItemsPresenterHost, ICollectionChangedListener { /// diff --git a/src/Avalonia.Controls/ListBoxItem.cs b/src/Avalonia.Controls/ListBoxItem.cs index e04c79987f..4fe5f4de40 100644 --- a/src/Avalonia.Controls/ListBoxItem.cs +++ b/src/Avalonia.Controls/ListBoxItem.cs @@ -1,3 +1,4 @@ +using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Input; @@ -6,6 +7,7 @@ namespace Avalonia.Controls /// /// A selectable item in a . /// + [PseudoClasses(":pressed", ":selected")] public class ListBoxItem : ContentControl, ISelectable { /// diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index b4d3272471..3d8ab3ae48 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reactive.Linq; using System.Windows.Input; using Avalonia.Controls.Generators; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; @@ -20,6 +21,7 @@ namespace Avalonia.Controls /// /// A menu item control. /// + [PseudoClasses(":separator", ":icon", ":open", ":pressed", ":selected")] public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable { /// diff --git a/src/Avalonia.Controls/Mixins/SelectableMixin.cs b/src/Avalonia.Controls/Mixins/SelectableMixin.cs index d2586ab6e8..c9e2b684cb 100644 --- a/src/Avalonia.Controls/Mixins/SelectableMixin.cs +++ b/src/Avalonia.Controls/Mixins/SelectableMixin.cs @@ -48,7 +48,7 @@ namespace Avalonia.Controls.Mixins if (sender != null) { - ((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue); + ((IPseudoClasses)sender.Classes).Set(":selected", x.NewValue.GetValueOrDefault()); sender.RaiseEvent(new RoutedEventArgs { @@ -58,4 +58,4 @@ namespace Avalonia.Controls.Mixins }); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index 776e9d2171..0349df842b 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/src/Avalonia.Controls/NativeMenu.Export.cs @@ -77,7 +77,7 @@ namespace Avalonia.Controls { if (args.Sender is TopLevel tl) { - GetInfo(tl).Exporter?.SetNativeMenu((NativeMenu)args.NewValue); + GetInfo(tl).Exporter?.SetNativeMenu(args.NewValue.GetValueOrDefault()); } }); } diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index 4c94d82eb4..a0fec9e677 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -23,7 +23,7 @@ namespace Avalonia.Controls MenuProperty.Changed.Subscribe(args => { var item = (NativeMenuItem)args.Sender; - var value = (NativeMenu)args.NewValue; + var value = args.NewValue.GetValueOrDefault(); if (value.Parent != null && value.Parent != item) throw new InvalidOperationException("NativeMenu already has a parent"); value.Parent = item; diff --git a/src/Avalonia.Controls/Notifications/NotificationCard.cs b/src/Avalonia.Controls/Notifications/NotificationCard.cs index f90746bf06..cdbace3ced 100644 --- a/src/Avalonia.Controls/Notifications/NotificationCard.cs +++ b/src/Avalonia.Controls/Notifications/NotificationCard.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Reactive.Linq; +using Avalonia.Controls.Metadata; using Avalonia.Interactivity; using Avalonia.LogicalTree; @@ -9,6 +10,7 @@ namespace Avalonia.Controls.Notifications /// /// Control that represents and displays a notification. /// + [PseudoClasses(":error", ":information", ":success", ":warning")] public class NotificationCard : ContentControl { private bool _isClosed; diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs index 6d9f6b8b77..8f5c6faf40 100644 --- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs +++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs @@ -7,12 +7,14 @@ using Avalonia.Controls.Primitives; using Avalonia.Rendering; using Avalonia.Data; using Avalonia.VisualTree; +using Avalonia.Controls.Metadata; namespace Avalonia.Controls.Notifications { /// /// An that displays notifications in a . /// + [PseudoClasses(":topleft", ":topright", ":bottomleft", ":bottomright")] public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest { private IList _items; diff --git a/src/Avalonia.Controls/PathIcon.cs b/src/Avalonia.Controls/PathIcon.cs new file mode 100644 index 0000000000..764f245249 --- /dev/null +++ b/src/Avalonia.Controls/PathIcon.cs @@ -0,0 +1,21 @@ +using Avalonia.Media; + +namespace Avalonia.Controls +{ + public class PathIcon : IconElement + { + static PathIcon() + { + AffectsRender(DataProperty); + } + + public static readonly StyledProperty DataProperty = + AvaloniaProperty.Register(nameof(Data)); + + public Geometry Data + { + get { return GetValue(DataProperty); } + set { SetValue(DataProperty, value); } + } + } +} diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index cb7bee1d33..f5115a2f7c 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -82,7 +82,7 @@ namespace Avalonia.Controls.Presenters TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty, TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty); - Observable.Merge(TextProperty.Changed, TextBlock.ForegroundProperty.Changed, + Observable.Merge(TextProperty.Changed, TextBlock.ForegroundProperty.Changed, TextAlignmentProperty.Changed, TextWrappingProperty.Changed, TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed, TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed, diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs index 89f672deaa..7a5e6ce426 100644 --- a/src/Avalonia.Controls/Primitives/AccessText.cs +++ b/src/Avalonia.Controls/Primitives/AccessText.cs @@ -126,7 +126,7 @@ namespace Avalonia.Controls.Primitives if (shapedTextCharacters.GlyphRun.Characters.End < textPosition) { - currentX += shapedTextCharacters.GlyphRun.Bounds.Width; + currentX += shapedTextCharacters.Size.Width; continue; } @@ -143,7 +143,7 @@ namespace Avalonia.Controls.Primitives width = 0.0; } - return new Rect(currentX, currentY, width, shapedTextCharacters.GlyphRun.Bounds.Height); + return new Rect(currentX, currentY, width, shapedTextCharacters.Size.Height); } } diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs index e424bf683d..82a49c4189 100644 --- a/src/Avalonia.Controls/Primitives/IPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Input; using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives @@ -13,7 +14,7 @@ namespace Avalonia.Controls.Primitives /// () or an which is created /// on an . /// - public interface IPopupHost : IDisposable + public interface IPopupHost : IDisposable, IFocusScope { /// /// Sets the control to display in the popup. diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs index 8c464c7aad..7f1dbdf592 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs @@ -221,7 +221,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning if (!FitsInBounds(unconstrainedRect, PopupAnchor.Bottom)) { - unconstrainedRect = unconstrainedRect.WithHeight(bounds.Height - unconstrainedRect.Y); + unconstrainedRect = unconstrainedRect.WithHeight(bounds.Bottom - unconstrainedRect.Y); } if (IsValid(unconstrainedRect)) diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index 477d24dc99..a7fb7ae08c 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs @@ -4,6 +4,7 @@ using Avalonia.Interactivity; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Threading; +using Avalonia.Controls.Metadata; namespace Avalonia.Controls.Primitives { @@ -21,6 +22,7 @@ namespace Avalonia.Controls.Primitives /// /// A scrollbar control. /// + [PseudoClasses(":vertical", ":horizontal")] public class ScrollBar : RangeBase { /// diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs index 96810ed01b..348922b71d 100644 --- a/src/Avalonia.Controls/Primitives/Thumb.cs +++ b/src/Avalonia.Controls/Primitives/Thumb.cs @@ -1,9 +1,11 @@ using System; +using Avalonia.Controls.Metadata; using Avalonia.Input; using Avalonia.Interactivity; namespace Avalonia.Controls.Primitives { + [PseudoClasses(":pressed")] public class Thumb : TemplatedControl { public static readonly RoutedEvent DragStartedEvent = diff --git a/src/Avalonia.Controls/Primitives/ToggleButton.cs b/src/Avalonia.Controls/Primitives/ToggleButton.cs index 13031ddad8..f96ca9310d 100644 --- a/src/Avalonia.Controls/Primitives/ToggleButton.cs +++ b/src/Avalonia.Controls/Primitives/ToggleButton.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Controls.Metadata; using Avalonia.Data; using Avalonia.Interactivity; @@ -7,6 +8,7 @@ namespace Avalonia.Controls.Primitives /// /// Represents a control that a user can select (check) or clear (uncheck). Base class for controls that can switch states. /// + [PseudoClasses(":checked", ":unchecked", ":indeterminate")] public class ToggleButton : Button { /// diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs index 29e7f28b44..9399f5fb31 100644 --- a/src/Avalonia.Controls/Primitives/Track.cs +++ b/src/Avalonia.Controls/Primitives/Track.cs @@ -4,6 +4,7 @@ // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. using System; +using Avalonia.Controls.Metadata; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; @@ -12,6 +13,7 @@ using Avalonia.Utilities; namespace Avalonia.Controls.Primitives { + [PseudoClasses(":vertical", ":horizontal")] public class Track : Control { public static readonly DirectProperty MinimumProperty = diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index a92f24a050..161f09d9b6 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Layout; using Avalonia.Media; @@ -8,6 +9,7 @@ namespace Avalonia.Controls /// /// A control used to indicate the progress of an operation. /// + [PseudoClasses(":vertical", ":horizontal", ":indeterminate")] public class ProgressBar : RangeBase { public class ProgressBarTemplateProperties : AvaloniaObject diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 293cbac82f..6e08e78813 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Collections; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Input; @@ -39,6 +40,7 @@ namespace Avalonia.Controls /// /// A control that lets the user select from a range of values by moving a Thumb control along a Track. /// + [PseudoClasses(":vertical", ":horizontal", ":pressed")] public class Slider : RangeBase { /// diff --git a/src/Avalonia.Controls/SplitView.cs b/src/Avalonia.Controls/SplitView.cs index b71858f796..8267efc466 100644 --- a/src/Avalonia.Controls/SplitView.cs +++ b/src/Avalonia.Controls/SplitView.cs @@ -1,4 +1,5 @@ -using Avalonia.Controls.Primitives; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Interactivity; @@ -73,6 +74,10 @@ namespace Avalonia.Controls /// /// A control with two views: A collapsible pane and an area for content /// + [PseudoClasses(":open", ":closed")] + [PseudoClasses(":compactoverlay", ":compactinline", ":overlay", ":inline")] + [PseudoClasses(":left", ":right")] + [PseudoClasses(":lightdismiss")] public class SplitView : TemplatedControl { /* diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 6320443a13..593643a1eb 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -1,3 +1,4 @@ +using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; @@ -6,6 +7,7 @@ namespace Avalonia.Controls /// /// An item in a or . /// + [PseudoClasses(":pressed", ":selected")] public class TabItem : HeaderedContentControl, ISelectable { /// diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 3b9e9c4751..d61519e697 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -138,7 +138,7 @@ namespace Avalonia.Controls FontStyleProperty, TextWrappingProperty, FontFamilyProperty, TextTrimmingProperty, TextProperty, PaddingProperty, LineHeightProperty, MaxLinesProperty); - Observable.Merge(TextProperty.Changed, ForegroundProperty.Changed, + Observable.Merge(TextProperty.Changed, ForegroundProperty.Changed, TextAlignmentProperty.Changed, TextWrappingProperty.Changed, TextTrimmingProperty.Changed, FontSizeProperty.Changed, FontStyleProperty.Changed, FontWeightProperty.Changed, @@ -434,7 +434,10 @@ namespace Avalonia.Controls var padding = Padding; - TextLayout.Draw(context, new Point(padding.Left + offsetX, padding.Top)); + using (context.PushPostTransform(Matrix.CreateTranslation(padding.Left + offsetX, padding.Top))) + { + TextLayout.Draw(context); + } } /// diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 73a1ae3335..0fe3ac62e4 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -13,9 +13,11 @@ using Avalonia.Metadata; using Avalonia.Data; using Avalonia.Layout; using Avalonia.Utilities; +using Avalonia.Controls.Metadata; namespace Avalonia.Controls { + [PseudoClasses(":empty")] public class TextBox : TemplatedControl, UndoRedoHelper.IUndoRedoHost { public static KeyGesture CutGesture { get; } = AvaloniaLocator.Current diff --git a/src/Avalonia.Controls/ToggleSwitch.cs b/src/Avalonia.Controls/ToggleSwitch.cs index c32f2d8102..662a355dac 100644 --- a/src/Avalonia.Controls/ToggleSwitch.cs +++ b/src/Avalonia.Controls/ToggleSwitch.cs @@ -1,4 +1,5 @@ -using Avalonia.Controls.Presenters; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.LogicalTree; @@ -8,6 +9,7 @@ namespace Avalonia.Controls /// /// A Toggle Switch control. /// + [PseudoClasses(":dragging")] public class ToggleSwitch : ToggleButton { private Panel _knobsPanel; diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index d56ff5752f..71bd0726d4 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Linq; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.VisualTree; @@ -14,6 +15,7 @@ namespace Avalonia.Controls /// To add a tooltip to a control, use the attached property, /// assigning the content that you want displayed. /// + [PseudoClasses(":open")] public class ToolTip : ContentControl { /// diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index 4942d4d313..8ce258b546 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -1,5 +1,6 @@ using System.Linq; using Avalonia.Controls.Generators; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; @@ -11,6 +12,7 @@ namespace Avalonia.Controls /// /// An item in a . /// + [PseudoClasses(":pressed", ":selected")] public class TreeViewItem : HeaderedItemsControl, ISelectable { /// diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 3ae6c8c30e..4f6af0a41b 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -385,7 +385,7 @@ namespace Avalonia.Headless } - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin) + public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { } diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 25f2d553d7..9ace7fd92d 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Avalonia.Controls; +using Avalonia.Controls.Metadata; using Avalonia.Data; using Avalonia.Input.GestureRecognizers; using Avalonia.Interactivity; @@ -12,6 +13,7 @@ namespace Avalonia.Input /// /// Implements input-related functionality for a control. /// + [PseudoClasses(":disabled", ":focus", ":focus-visible", ":pointerover")] public class InputElement : Interactive, IInputElement { /// diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index f084411c2f..49bd578290 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -1,7 +1,8 @@  - false + $([MSBuild]::IsOSPlatform(OSX)) + $(PackAvaloniaNative) true netstandard2.0 /usr/bin/castxml @@ -10,8 +11,9 @@ false - + + libAvaloniaNative.dylib runtimes/osx/native/libAvaloniaNative.dylib true PreserveNewest @@ -26,4 +28,4 @@ - + \ No newline at end of file diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 804cf7f8ac..e8b2f065c7 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -16,7 +16,7 @@ namespace Avalonia.Native { private readonly IAvaloniaNativeFactory _factory; private AvaloniaNativePlatformOptions _options; - private GlPlatformFeature _glFeature; + private AvaloniaNativePlatformOpenGlInterface _platformGl; [DllImport("libAvaloniaNative")] static extern IntPtr CreateAvaloniaNative(); @@ -116,8 +116,8 @@ namespace Avalonia.Native { try { - AvaloniaLocator.CurrentMutable.Bind() - .ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay())); + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(_platformGl = new AvaloniaNativePlatformOpenGlInterface(_factory.ObtainGlDisplay())); } catch (Exception) { @@ -128,7 +128,7 @@ namespace Avalonia.Native public IWindowImpl CreateWindow() { - return new WindowImpl(_factory, _options, _glFeature); + return new WindowImpl(_factory, _options, _platformGl); } public IWindowImpl CreateEmbeddableWindow() diff --git a/src/Avalonia.Native/GlPlatformFeature.cs b/src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs similarity index 76% rename from src/Avalonia.Native/GlPlatformFeature.cs rename to src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs index e321db6eda..dbe968b82f 100644 --- a/src/Avalonia.Native/GlPlatformFeature.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs @@ -2,21 +2,20 @@ using Avalonia.OpenGL; using Avalonia.Native.Interop; using System.Drawing; +using Avalonia.OpenGL.Surfaces; using Avalonia.Threading; namespace Avalonia.Native { - class GlPlatformFeature : IWindowingPlatformGlFeature + class AvaloniaNativePlatformOpenGlInterface : IPlatformOpenGlInterface { private readonly IAvnGlDisplay _display; - public GlPlatformFeature(IAvnGlDisplay display) + public AvaloniaNativePlatformOpenGlInterface(IAvnGlDisplay display) { _display = display; var immediate = display.CreateContext(null); - var deferred = display.CreateContext(immediate); - int major, minor; GlInterface glInterface; using (immediate.MakeCurrent()) @@ -33,19 +32,22 @@ namespace Avalonia.Native } GlDisplay = new GlDisplay(display, glInterface, immediate.SampleCount, immediate.StencilSize); - - ImmediateContext = new GlContext(GlDisplay, immediate, _version); - DeferredContext = new GlContext(GlDisplay, deferred, _version); + MainContext = new GlContext(GlDisplay, null, immediate, _version); } - internal IGlContext ImmediateContext { get; } - public IGlContext MainContext => DeferredContext; - internal GlContext DeferredContext { get; } + internal GlContext MainContext { get; } + public IGlContext PrimaryContext => MainContext; + + public bool CanShareContexts => true; + public bool CanCreateContexts => true; internal GlDisplay GlDisplay; private readonly GlVersion _version; + public IGlContext CreateSharedContext() => new GlContext(GlDisplay, + MainContext, _display.CreateContext(MainContext.Context), _version); + public IGlContext CreateContext() => new GlContext(GlDisplay, - _display.CreateContext(((GlContext)ImmediateContext).Context), _version); + null, _display.CreateContext(null), _version); } class GlDisplay @@ -72,11 +74,13 @@ namespace Avalonia.Native class GlContext : IGlContext { private readonly GlDisplay _display; + private readonly GlContext _sharedWith; public IAvnGlContext Context { get; private set; } - public GlContext(GlDisplay display, IAvnGlContext context, GlVersion version) + public GlContext(GlDisplay display, GlContext sharedWith, IAvnGlContext context, GlVersion version) { _display = display; + _sharedWith = sharedWith; Context = context; Version = version; } @@ -86,6 +90,17 @@ namespace Avalonia.Native public int SampleCount => _display.SampleCount; public int StencilSize => _display.StencilSize; public IDisposable MakeCurrent() => Context.MakeCurrent(); + public IDisposable EnsureCurrent() => MakeCurrent(); + + public bool IsSharedWith(IGlContext context) + { + var c = (GlContext)context; + return c == this + || c._sharedWith == this + || _sharedWith == context + || _sharedWith != null && _sharedWith == c._sharedWith; + } + public void Dispose() { @@ -108,7 +123,7 @@ namespace Avalonia.Native public IGlPlatformSurfaceRenderingSession BeginDraw() { - var feature = (GlPlatformFeature)AvaloniaLocator.Current.GetService(); + var feature = (AvaloniaNativePlatformOpenGlInterface)AvaloniaLocator.Current.GetService(); return new GlPlatformSurfaceRenderingSession(_context, _target.BeginDrawing()); } diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index 2d246e08d2..2f98385038 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -9,12 +9,12 @@ namespace Avalonia.Native { private readonly IAvaloniaNativeFactory _factory; private readonly AvaloniaNativePlatformOptions _opts; - private readonly GlPlatformFeature _glFeature; + private readonly AvaloniaNativePlatformOpenGlInterface _glFeature; private readonly IWindowBaseImpl _parent; public PopupImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, - GlPlatformFeature glFeature, + AvaloniaNativePlatformOpenGlInterface glFeature, IWindowBaseImpl parent) : base(opts, glFeature) { _factory = factory; @@ -23,7 +23,7 @@ namespace Avalonia.Native _parent = parent; using (var e = new PopupEvents(this)) { - var context = _opts.UseGpu ? glFeature?.DeferredContext : null; + var context = _opts.UseGpu ? glFeature?.MainContext : null; Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context); } PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 885591495b..11a0ebce61 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -14,19 +14,19 @@ namespace Avalonia.Native { private readonly IAvaloniaNativeFactory _factory; private readonly AvaloniaNativePlatformOptions _opts; - private readonly GlPlatformFeature _glFeature; + private readonly AvaloniaNativePlatformOpenGlInterface _glFeature; IAvnWindow _native; private double _extendTitleBarHeight = -1; internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, - GlPlatformFeature glFeature) : base(opts, glFeature) + AvaloniaNativePlatformOpenGlInterface glFeature) : base(opts, glFeature) { _factory = factory; _opts = opts; _glFeature = glFeature; using (var e = new WindowEvents(this)) { - var context = _opts.UseGpu ? glFeature?.DeferredContext : null; + var context = _opts.UseGpu ? glFeature?.MainContext : null; Init(_native = factory.CreateWindow(e, context?.Context), factory.CreateScreens(), context); } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 56cf544d9d..5d35c773d7 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -61,7 +61,7 @@ namespace Avalonia.Native private NativeControlHostImpl _nativeControlHost; private IGlContext _glContext; - internal WindowBaseImpl(AvaloniaNativePlatformOptions opts, GlPlatformFeature glFeature) + internal WindowBaseImpl(AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOpenGlInterface glFeature) { _gpu = opts.UseGpu && glFeature != null; _deferredRendering = opts.UseDeferredRendering; diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs index 8565d99b45..8c9b028164 100644 --- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs +++ b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Platform.Interop; diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs index 1a42ed90c2..191fb53204 100644 --- a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs +++ b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; - -using static Avalonia.OpenGL.EglConsts; +using Avalonia.OpenGL.Egl; +using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Angle { @@ -52,7 +52,7 @@ namespace Avalonia.OpenGL.Angle } } - private AngleWin32EglDisplay(EglInterface egl, AngleInfo info) : base(egl, info.Display) + private AngleWin32EglDisplay(EglInterface egl, AngleInfo info) : base(egl, false, info.Display) { PlatformApi = info.PlatformApi; } @@ -78,11 +78,11 @@ namespace Avalonia.OpenGL.Angle return d3dDeviceHandle; } - public EglSurface WrapDirect3D11Texture(IntPtr handle) + public EglSurface WrapDirect3D11Texture(EglPlatformOpenGlInterface egl, IntPtr handle) { if (PlatformApi != AngleOptions.PlatformApi.DirectX11) throw new InvalidOperationException("Current platform API is " + PlatformApi); - return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_NONE, EGL_NONE }); + return egl.CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_NONE, EGL_NONE }); } } } diff --git a/src/Avalonia.OpenGL/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs similarity index 50% rename from src/Avalonia.OpenGL/OpenGlControlBase.cs rename to src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index 8567dcae20..33773ed8e2 100644 --- a/src/Avalonia.OpenGL/OpenGlControlBase.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs @@ -3,44 +3,83 @@ using Avalonia.Controls; using Avalonia.Logging; using Avalonia.Media; using Avalonia.OpenGL.Imaging; -using Avalonia.Rendering; -using Avalonia.VisualTree; using static Avalonia.OpenGL.GlConsts; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Controls { public abstract class OpenGlControlBase : Control { private IGlContext _context; - private int _fb, _texture, _renderBuffer; - private OpenGlTextureBitmap _bitmap; - private PixelSize _oldSize; + private int _fb, _depthBuffer; + private OpenGlBitmap _bitmap; + private IOpenGlBitmapAttachment _attachment; + private PixelSize _depthBufferSize; private bool _glFailed; + private bool _initialized; protected GlVersion GlVersion { get; private set; } public sealed override void Render(DrawingContext context) { if(!EnsureInitialized()) return; - + using (_context.MakeCurrent()) { - using (_bitmap.Lock()) - { - var gl = _context.GlInterface; - gl.BindFramebuffer(GL_FRAMEBUFFER, _fb); - if (_oldSize != GetPixelSize()) - ResizeTexture(gl); - - OnOpenGlRender(gl, _fb); - gl.Flush(); - } + _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb); + EnsureTextureAttachment(); + EnsureDepthBufferAttachment(_context.GlInterface); + if(!CheckFramebufferStatus(_context.GlInterface)) + return; + + OnOpenGlRender(_context.GlInterface, _fb); + _attachment.Present(); } context.DrawImage(_bitmap, new Rect(_bitmap.Size), Bounds); base.Render(context); } + + private void CheckError(GlInterface gl) + { + int err; + while ((err = gl.GetError()) != GL_NO_ERROR) + Console.WriteLine(err); + } + + void EnsureTextureAttachment() + { + _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb); + if (_bitmap == null || _attachment == null || _bitmap.PixelSize != GetPixelSize()) + { + _attachment?.Dispose(); + _attachment = null; + _bitmap?.Dispose(); + _bitmap = null; + _bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96)); + _attachment = _bitmap.CreateFramebufferAttachment(_context); + } + } + + void EnsureDepthBufferAttachment(GlInterface gl) + { + var size = GetPixelSize(); + if (size == _depthBufferSize && _depthBuffer != 0) + return; + + gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer); + if (_depthBuffer != 0) gl.DeleteRenderbuffers(1, new[] { _depthBuffer }); + + var oneArr = new int[1]; + gl.GenRenderbuffers(1, oneArr); + _depthBuffer = oneArr[0]; + gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer); + gl.RenderbufferStorage(GL_RENDERBUFFER, + GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT, + size.Width, size.Height); + gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer); + gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer); + } - void DoCleanup(bool callUserDeinit) + void DoCleanup() { if (_context != null) { @@ -50,16 +89,19 @@ namespace Avalonia.OpenGL gl.BindTexture(GL_TEXTURE_2D, 0); gl.BindFramebuffer(GL_FRAMEBUFFER, 0); gl.DeleteFramebuffers(1, new[] { _fb }); - using (_bitmap.Lock()) - _bitmap.SetTexture(0, 0, new PixelSize(1, 1), 1); - gl.DeleteTextures(1, new[] { _texture }); - gl.DeleteRenderbuffers(1, new[] { _renderBuffer }); - _bitmap.Dispose(); + gl.DeleteRenderbuffers(1, new[] { _depthBuffer }); + _attachment?.Dispose(); + _attachment = null; + _bitmap?.Dispose(); + _bitmap = null; try { - if (callUserDeinit) + if (_initialized) + { + _initialized = false; OnOpenGlDeinit(_context.GlInterface, _fb); + } } finally { @@ -72,11 +114,11 @@ namespace Avalonia.OpenGL protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { - DoCleanup(true); + DoCleanup(); base.OnDetachedFromVisualTree(e); } - bool EnsureInitialized() + private bool EnsureInitializedCore() { if (_context != null) return true; @@ -84,34 +126,43 @@ namespace Avalonia.OpenGL if (_glFailed) return false; - var feature = AvaloniaLocator.Current.GetService(); + var feature = AvaloniaLocator.Current.GetService(); if (feature == null) return false; + if (!feature.CanShareContexts) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", + "Unable to initialize OpenGL: current platform does not support multithreaded context sharing"); + return false; + } try { - _context = feature.CreateContext(); - + _context = feature.CreateSharedContext(); } catch (Exception e) { Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", "Unable to initialize OpenGL: unable to create additional OpenGL context: {exception}", e); - _glFailed = true; return false; } GlVersion = _context.Version; try { - _bitmap = new OpenGlTextureBitmap(); + _bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96)); + if (!_bitmap.SupportsContext(_context)) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", + "Unable to initialize OpenGL: unable to create OpenGlBitmap: OpenGL context is not compatible"); + return false; + } } catch (Exception e) { _context.Dispose(); _context = null; Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", - "Unable to initialize OpenGL: unable to create OpenGlTextureBitmap: {exception}", e); - _glFailed = true; + "Unable to initialize OpenGL: unable to create OpenGlBitmap: {exception}", e); return false; } @@ -119,80 +170,55 @@ namespace Avalonia.OpenGL { try { - _oldSize = GetPixelSize(); + _depthBufferSize = GetPixelSize(); var gl = _context.GlInterface; var oneArr = new int[1]; gl.GenFramebuffers(1, oneArr); _fb = oneArr[0]; gl.BindFramebuffer(GL_FRAMEBUFFER, _fb); - - gl.GenTextures(1, oneArr); - _texture = oneArr[0]; - ResizeTexture(gl); - - gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); + EnsureDepthBufferAttachment(gl); + EnsureTextureAttachment(); - var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) - { - int code; - while ((code = gl.GetError()) != 0) - Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", - "Unable to initialize OpenGL FBO: {code}", code); - - _glFailed = true; - return false; - } + return CheckFramebufferStatus(gl); } catch(Exception e) { Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", "Unable to initialize OpenGL FBO: {exception}", e); - _glFailed = true; + return false; } - - if (!_glFailed) - OnOpenGlInit(_context.GlInterface, _fb); } + } - if (_glFailed) + private bool CheckFramebufferStatus(GlInterface gl) + { + var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { - DoCleanup(false); + int code; + while ((code = gl.GetError()) != 0) + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", + "Unable to initialize OpenGL FBO: {code}", code); + return false; } return true; } - void ResizeTexture(GlInterface gl) + private bool EnsureInitialized() { - var size = GetPixelSize(); - - gl.GetIntegerv( GL_TEXTURE_BINDING_2D, out var oldTexture); - gl.BindTexture(GL_TEXTURE_2D, _texture); - gl.TexImage2D(GL_TEXTURE_2D, 0, - GlVersion.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8, - size.Width, size.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero); - gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - gl.BindTexture(GL_TEXTURE_2D, oldTexture); - - gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer); - gl.DeleteRenderbuffers(1, new[] { _renderBuffer }); - var oneArr = new int[1]; - gl.GenRenderbuffers(1, oneArr); - _renderBuffer = oneArr[0]; - gl.BindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); - gl.RenderbufferStorage(GL_RENDERBUFFER, - GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT, - size.Width, size.Height); - gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderBuffer); - gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer); - using (_bitmap.Lock()) - _bitmap.SetTexture(_texture, GL_RGBA8, size, 1); + if (_initialized) + return true; + _glFailed = !(_initialized = EnsureInitializedCore()); + if (_glFailed) + return false; + using (_context.MakeCurrent()) + OnOpenGlInit(_context.GlInterface, _fb); + return true; } - PixelSize GetPixelSize() + private PixelSize GetPixelSize() { var scaling = VisualRoot.RenderScaling; return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)), diff --git a/src/Avalonia.OpenGL/EglConsts.cs b/src/Avalonia.OpenGL/Egl/EglConsts.cs similarity index 99% rename from src/Avalonia.OpenGL/EglConsts.cs rename to src/Avalonia.OpenGL/Egl/EglConsts.cs index 8e44004f2d..58f5f1cef5 100644 --- a/src/Avalonia.OpenGL/EglConsts.cs +++ b/src/Avalonia.OpenGL/Egl/EglConsts.cs @@ -1,6 +1,6 @@ // ReSharper disable UnusedMember.Global // ReSharper disable IdentifierTypo -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public static class EglConsts { diff --git a/src/Avalonia.OpenGL/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs similarity index 55% rename from src/Avalonia.OpenGL/EglContext.cs rename to src/Avalonia.OpenGL/Egl/EglContext.cs index 871665e857..5365354418 100644 --- a/src/Avalonia.OpenGL/EglContext.cs +++ b/src/Avalonia.OpenGL/Egl/EglContext.cs @@ -1,23 +1,25 @@ using System; using System.Reactive.Disposables; using System.Threading; -using static Avalonia.OpenGL.EglConsts; +using static Avalonia.OpenGL.Egl.EglConsts; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public class EglContext : IGlContext { private readonly EglDisplay _disp; private readonly EglInterface _egl; + private readonly EglContext _sharedWith; private readonly object _lock = new object(); - public EglContext(EglDisplay display, EglInterface egl, IntPtr ctx, EglSurface offscreenSurface, + public EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, Func offscreenSurface, GlVersion version, int sampleCount, int stencilSize) { _disp = display; _egl = egl; + _sharedWith = sharedWith; Context = ctx; - OffscreenSurface = offscreenSurface; + OffscreenSurface = offscreenSurface(this); Version = version; SampleCount = sampleCount; StencilSize = stencilSize; @@ -33,21 +35,17 @@ namespace Avalonia.OpenGL public int StencilSize { get; } public EglDisplay Display => _disp; - public IDisposable Lock() - { - Monitor.Enter(_lock); - return Disposable.Create(() => Monitor.Exit(_lock)); - } - class RestoreContext : IDisposable { private readonly EglInterface _egl; + private readonly object _l; private readonly IntPtr _display; private IntPtr _context, _read, _draw; - public RestoreContext(EglInterface egl, IntPtr defDisplay) + public RestoreContext(EglInterface egl, IntPtr defDisplay, object l) { _egl = egl; + _l = l; _display = _egl.GetCurrentDisplay(); if (_display == IntPtr.Zero) _display = defDisplay; @@ -59,29 +57,52 @@ namespace Avalonia.OpenGL public void Dispose() { _egl.MakeCurrent(_display, _draw, _read, _context); + Monitor.Exit(_l); } } - public IDisposable MakeCurrent() + public IDisposable MakeCurrent() => MakeCurrent(OffscreenSurface); + + public IDisposable MakeCurrent(EglSurface surface) { - var old = new RestoreContext(_egl, _disp.Handle); - _egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - if (!_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, Context)) - throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); - return old; + Monitor.Enter(_lock); + var success = false; + try + { + var old = new RestoreContext(_egl, _disp.Handle, _lock); + var surf = surface ?? OffscreenSurface; + _egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + if (!_egl.MakeCurrent(_disp.Handle, surf.DangerousGetHandle(), surf.DangerousGetHandle(), Context)) + throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); + success = true; + return old; + } + finally + { + if(!success) + Monitor.Enter(_lock); + } } - public IDisposable MakeCurrent(EglSurface surface) + public IDisposable EnsureCurrent() { - var old = new RestoreContext(_egl, _disp.Handle); - var surf = surface ?? OffscreenSurface; - _egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - if (!_egl.MakeCurrent(_disp.Handle, surf.DangerousGetHandle(), surf.DangerousGetHandle(), Context)) - throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); - return old; + if(IsCurrent) + return Disposable.Empty; + return MakeCurrent(); } + public bool IsSharedWith(IGlContext context) + { + var c = (EglContext)context; + return c == this + || c._sharedWith == this + || _sharedWith == context + || _sharedWith != null && _sharedWith == c._sharedWith; + } + + public bool IsCurrent => _egl.GetCurrentDisplay() == _disp.Handle && _egl.GetCurrentContext() == Context; + public void Dispose() { _egl.DestroyContext(_disp.Handle, Context); diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs similarity index 79% rename from src/Avalonia.OpenGL/EglDisplay.cs rename to src/Avalonia.OpenGL/Egl/EglDisplay.cs index 7f41e75d6a..fd3de854f5 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs @@ -1,26 +1,25 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; -using Avalonia.Platform.Interop; -using static Avalonia.OpenGL.EglConsts; +using static Avalonia.OpenGL.Egl.EglConsts; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public class EglDisplay { private readonly EglInterface _egl; + public bool SupportsSharing { get; } private readonly IntPtr _display; private readonly IntPtr _config; private readonly int[] _contextAttributes; private readonly int _surfaceType; public IntPtr Handle => _display; + public IntPtr Config => _config; private int _sampleCount; private int _stencilSize; private GlVersion _version; - public EglDisplay(EglInterface egl) : this(egl, -1, IntPtr.Zero, null) + public EglDisplay(EglInterface egl, bool supportsSharing) : this(egl, supportsSharing, -1, IntPtr.Zero, null) { } @@ -45,15 +44,16 @@ namespace Avalonia.OpenGL return display; } - public EglDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs) - : this(egl, CreateDisplay(egl, platformType, platformDisplay, attrs)) + public EglDisplay(EglInterface egl, bool supportsSharing, int platformType, IntPtr platformDisplay, int[] attrs) + : this(egl, supportsSharing, CreateDisplay(egl, platformType, platformDisplay, attrs)) { } - public EglDisplay(EglInterface egl, IntPtr display) + public EglDisplay(EglInterface egl, bool supportsSharing, IntPtr display) { _egl = egl; + SupportsSharing = supportsSharing; _display = display; if(_display == IntPtr.Zero) throw new ArgumentException(); @@ -136,7 +136,12 @@ namespace Avalonia.OpenGL throw new OpenGlException("No suitable EGL config was found"); } - public EglDisplay() : this(new EglInterface()) + public EglDisplay() : this(false) + { + + } + + public EglDisplay(bool supportsSharing) : this(new EglInterface(), supportsSharing) { } @@ -144,6 +149,9 @@ namespace Avalonia.OpenGL public EglInterface EglInterface => _egl; public EglContext CreateContext(IGlContext share) { + if (share != null && !SupportsSharing) + throw new NotSupportedException("Context sharing is not supported by this display"); + if((_surfaceType|EGL_PBUFFER_BIT) == 0) throw new InvalidOperationException("Platform doesn't support PBUFFER surfaces"); var shareCtx = (EglContext)share; @@ -158,37 +166,22 @@ namespace Avalonia.OpenGL }); if (surf == IntPtr.Zero) throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl); - var rv = new EglContext(this, _egl, ctx, new EglSurface(this, _egl, surf), + var rv = new EglContext(this, _egl, shareCtx, ctx, context => new EglSurface(this, context, surf), _version, _sampleCount, _stencilSize); return rv; } public EglContext CreateContext(EglContext share, EglSurface offscreenSurface) { + if (share != null && !SupportsSharing) + throw new NotSupportedException("Context sharing is not supported by this display"); + var ctx = _egl.CreateContext(_display, _config, share?.Context ?? IntPtr.Zero, _contextAttributes); if (ctx == IntPtr.Zero) throw OpenGlException.GetFormattedException("eglCreateContext", _egl); - var rv = new EglContext(this, _egl, ctx, offscreenSurface, _version, _sampleCount, _stencilSize); + var rv = new EglContext(this, _egl, share, ctx, _ => offscreenSurface, _version, _sampleCount, _stencilSize); rv.MakeCurrent(null); return rv; } - - public EglSurface CreateWindowSurface(IntPtr window) - { - var s = _egl.CreateWindowSurface(_display, _config, window, new[] {EGL_NONE, EGL_NONE}); - if (s == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglCreateWindowSurface", _egl); - return new EglSurface(this, _egl, s); - } - - public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs) - { - var s = _egl.CreatePbufferFromClientBuffer(_display, bufferType, handle, - _config, attribs); - - if (s == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", _egl); - return new EglSurface(this, _egl, s); - } } } diff --git a/src/Avalonia.OpenGL/EglErrors.cs b/src/Avalonia.OpenGL/Egl/EglErrors.cs similarity index 96% rename from src/Avalonia.OpenGL/EglErrors.cs rename to src/Avalonia.OpenGL/Egl/EglErrors.cs index bfe46f2b69..d89bbb499f 100644 --- a/src/Avalonia.OpenGL/EglErrors.cs +++ b/src/Avalonia.OpenGL/Egl/EglErrors.cs @@ -1,4 +1,4 @@ -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public enum EglErrors { diff --git a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs new file mode 100644 index 0000000000..3d58660d47 --- /dev/null +++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs @@ -0,0 +1,54 @@ +using Avalonia.OpenGL.Surfaces; + +namespace Avalonia.OpenGL.Egl +{ + public class EglGlPlatformSurface : EglGlPlatformSurfaceBase + { + private readonly EglPlatformOpenGlInterface _egl; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + + public EglGlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info) : base() + { + _egl = egl; + _info = info; + } + + public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + { + var glSurface = _egl.CreateWindowSurface(_info.Handle); + return new RenderTarget(_egl, glSurface, _info); + } + + class RenderTarget : EglPlatformSurfaceRenderTargetBase + { + private readonly EglPlatformOpenGlInterface _egl; + private EglSurface _glSurface; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + private PixelSize _currentSize; + + public RenderTarget(EglPlatformOpenGlInterface egl, + EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) : base(egl) + { + _egl = egl; + _glSurface = glSurface; + _info = info; + _currentSize = info.Size; + } + + public override void Dispose() => _glSurface.Dispose(); + + public override IGlPlatformSurfaceRenderingSession BeginDraw() + { + if (_info.Size != _currentSize || _glSurface == null) + { + _glSurface?.Dispose(); + _glSurface = null; + _glSurface = _egl.CreateWindowSurface(_info.Handle); + _currentSize = _info.Size; + } + return base.BeginDraw(_glSurface, _info); + } + } + } +} + diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs similarity index 67% rename from src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs rename to src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs index 00c7c4796c..4ea6766de2 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs +++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs @@ -1,6 +1,7 @@ using System; +using Avalonia.OpenGL.Surfaces; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public abstract class EglGlPlatformSurfaceBase : IGlPlatformSurface { @@ -14,19 +15,15 @@ namespace Avalonia.OpenGL public abstract IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(); } - public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTargetWithCorruptionInfo + public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTarget { - private readonly EglDisplay _display; - private readonly EglContext _context; + private readonly EglPlatformOpenGlInterface _egl; - protected EglPlatformSurfaceRenderTargetBase(EglDisplay display, EglContext context) + protected EglPlatformSurfaceRenderTargetBase(EglPlatformOpenGlInterface egl) { - _display = display; - _context = context; + _egl = egl; } - public abstract bool IsCorrupted { get; } - public virtual void Dispose() { @@ -37,22 +34,25 @@ namespace Avalonia.OpenGL protected IGlPlatformSurfaceRenderingSession BeginDraw(EglSurface surface, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, Action onFinish = null, bool isYFlipped = false) { - var l = _context.Lock(); + + var restoreContext = _egl.PrimaryEglContext.MakeCurrent(surface); + var success = false; try { - if (IsCorrupted) - throw new RenderTargetCorruptedException(); - var restoreContext = _context.MakeCurrent(surface); - _display.EglInterface.WaitClient(); - _display.EglInterface.WaitGL(); - _display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); - - return new Session(_display, _context, surface, info, l, restoreContext, onFinish, isYFlipped); + var egli = _egl.Display.EglInterface; + egli.WaitClient(); + egli.WaitGL(); + egli.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); + + _egl.PrimaryContext.GlInterface.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0); + + success = true; + return new Session(_egl.Display, _egl.PrimaryEglContext, surface, info, restoreContext, onFinish, isYFlipped); } - catch + finally { - l.Dispose(); - throw; + if(!success) + restoreContext.Dispose(); } } @@ -62,21 +62,19 @@ namespace Avalonia.OpenGL private readonly EglSurface _glSurface; private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; private readonly EglDisplay _display; - private readonly IDisposable _lock; private readonly IDisposable _restoreContext; private readonly Action _onFinish; public Session(EglDisplay display, EglContext context, EglSurface glSurface, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, - IDisposable @lock, IDisposable restoreContext, Action onFinish, bool isYFlipped) + IDisposable restoreContext, Action onFinish, bool isYFlipped) { IsYFlipped = isYFlipped; _context = context; _display = display; _glSurface = glSurface; _info = info; - _lock = @lock; _restoreContext = restoreContext; _onFinish = onFinish; } @@ -90,7 +88,6 @@ namespace Avalonia.OpenGL _display.EglInterface.WaitGL(); _display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); _restoreContext.Dispose(); - _lock.Dispose(); _onFinish?.Invoke(); } diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs similarity index 99% rename from src/Avalonia.OpenGL/EglInterface.cs rename to src/Avalonia.OpenGL/Egl/EglInterface.cs index 666c0d8351..8055226042 100644 --- a/src/Avalonia.OpenGL/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; using Avalonia.Platform; using Avalonia.Platform.Interop; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public class EglInterface : GlInterfaceBase { diff --git a/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs b/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs new file mode 100644 index 0000000000..476f65a774 --- /dev/null +++ b/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs @@ -0,0 +1,72 @@ +using System; +using Avalonia.Logging; +using static Avalonia.OpenGL.Egl.EglConsts; + +namespace Avalonia.OpenGL.Egl +{ + public class EglPlatformOpenGlInterface : IPlatformOpenGlInterface + { + public EglDisplay Display { get; private set; } + public bool CanCreateContexts => true; + public bool CanShareContexts => Display.SupportsSharing; + + public EglContext PrimaryEglContext { get; } + public IGlContext PrimaryContext => PrimaryEglContext; + + public EglPlatformOpenGlInterface(EglDisplay display) + { + Display = display; + PrimaryEglContext = display.CreateContext(null); + } + + public static void TryInitialize() + { + var feature = TryCreate(); + if (feature != null) + AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); + } + + public static EglPlatformOpenGlInterface TryCreate() => TryCreate(() => new EglDisplay()); + public static EglPlatformOpenGlInterface TryCreate(Func displayFactory) + { + try + { + return new EglPlatformOpenGlInterface(displayFactory()); + } + catch(Exception e) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(null, "Unable to initialize EGL-based rendering: {0}", e); + return null; + } + } + + public IGlContext CreateContext() => Display.CreateContext(null); + public IGlContext CreateSharedContext() => Display.CreateContext(PrimaryEglContext); + + + public EglSurface CreateWindowSurface(IntPtr window) + { + using (PrimaryContext.MakeCurrent()) + { + var s = Display.EglInterface.CreateWindowSurface(Display.Handle, Display.Config, window, + new[] { EGL_NONE, EGL_NONE }); + if (s == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglCreateWindowSurface", Display.EglInterface); + return new EglSurface(Display, PrimaryEglContext, s); + } + } + + public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs) + { + using (PrimaryContext.MakeCurrent()) + { + var s = Display.EglInterface.CreatePbufferFromClientBuffer(Display.Handle, bufferType, handle, + Display.Config, attribs); + + if (s == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", Display.EglInterface); + return new EglSurface(Display, PrimaryEglContext, s); + } + } + } +} diff --git a/src/Avalonia.OpenGL/EglSurface.cs b/src/Avalonia.OpenGL/Egl/EglSurface.cs similarity index 57% rename from src/Avalonia.OpenGL/EglSurface.cs rename to src/Avalonia.OpenGL/Egl/EglSurface.cs index 5ac56a00e3..a93751ca9e 100644 --- a/src/Avalonia.OpenGL/EglSurface.cs +++ b/src/Avalonia.OpenGL/Egl/EglSurface.cs @@ -1,22 +1,25 @@ using System; using System.Runtime.InteropServices; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public class EglSurface : SafeHandle { private readonly EglDisplay _display; + private readonly EglContext _context; private readonly EglInterface _egl; - public EglSurface(EglDisplay display, EglInterface egl, IntPtr surface) : base(surface, true) + public EglSurface(EglDisplay display, EglContext context, IntPtr surface) : base(surface, true) { _display = display; - _egl = egl; + _context = context; + _egl = display.EglInterface; } protected override bool ReleaseHandle() { - _egl.DestroySurface(_display.Handle, handle); + using (_context.MakeCurrent()) + _egl.DestroySurface(_display.Handle, handle); return true; } diff --git a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs deleted file mode 100644 index 7e9383432c..0000000000 --- a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Avalonia.Logging; - -namespace Avalonia.OpenGL -{ - public class EglGlPlatformFeature : IWindowingPlatformGlFeature - { - private EglDisplay _display; - public EglDisplay Display => _display; - public IGlContext CreateContext() - { - return _display.CreateContext(DeferredContext); - } - public EglContext DeferredContext { get; private set; } - public IGlContext MainContext => DeferredContext; - - public static void TryInitialize() - { - var feature = TryCreate(); - if (feature != null) - AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); - } - - public static EglGlPlatformFeature TryCreate() => TryCreate(() => new EglDisplay()); - public static EglGlPlatformFeature TryCreate(Func displayFactory) - { - try - { - var disp = displayFactory(); - return new EglGlPlatformFeature - { - _display = disp, - DeferredContext = disp.CreateContext(null) - }; - } - catch(Exception e) - { - Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(null, "Unable to initialize EGL-based rendering: {0}", e); - return null; - } - } - } -} diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs deleted file mode 100644 index 21fadff19e..0000000000 --- a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Threading; - -namespace Avalonia.OpenGL -{ - public class EglGlPlatformSurface : EglGlPlatformSurfaceBase - { - private readonly EglDisplay _display; - private readonly EglContext _context; - private readonly IEglWindowGlPlatformSurfaceInfo _info; - - public EglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info) : base() - { - _display = context.Display; - _context = context; - _info = info; - } - - public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() - { - var glSurface = _display.CreateWindowSurface(_info.Handle); - return new RenderTarget(_display, _context, glSurface, _info); - } - - class RenderTarget : EglPlatformSurfaceRenderTargetBase - { - private readonly EglDisplay _display; - private readonly EglContext _context; - private readonly EglSurface _glSurface; - private readonly IEglWindowGlPlatformSurfaceInfo _info; - private PixelSize _initialSize; - - public RenderTarget(EglDisplay display, EglContext context, - EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) : base(display, context) - { - _display = display; - _context = context; - _glSurface = glSurface; - _info = info; - _initialSize = info.Size; - } - - public override void Dispose() => _glSurface.Dispose(); - - public override bool IsCorrupted => _initialSize != _info.Size; - - public override IGlPlatformSurfaceRenderingSession BeginDraw() => base.BeginDraw(_glSurface, _info); - } - } -} - diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 23188e7dbf..ea2fe0a99c 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -82,6 +82,9 @@ namespace Avalonia.OpenGL [GlEntryPoint("glFlush")] public Action Flush { get; } + + [GlEntryPoint("glFinish")] + public Action Finish { get; } public delegate IntPtr GlGetString(int v); [GlEntryPoint("glGetString")] @@ -144,6 +147,10 @@ namespace Avalonia.OpenGL [GlEntryPoint("glBindTexture")] public GlBindTexture BindTexture { get; } + public delegate void GlActiveTexture(int texture); + [GlEntryPoint("glActiveTexture")] + public GlActiveTexture ActiveTexture { get; } + public delegate void GlDeleteTextures(int count, int[] textures); [GlEntryPoint("glDeleteTextures")] public GlDeleteTextures DeleteTextures { get; } @@ -154,6 +161,12 @@ namespace Avalonia.OpenGL [GlEntryPoint("glTexImage2D")] public GlTexImage2D TexImage2D { get; } + public delegate void GlCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, + int width, int height); + + [GlEntryPoint("glCopyTexSubImage2D")] + public GlCopyTexSubImage2D CopyTexSubImage2D { get; } + public delegate void GlTexParameteri(int target, int name, int value); [GlEntryPoint("glTexParameteri")] public GlTexParameteri TexParameteri { get; } diff --git a/src/Avalonia.OpenGL/IGlContext.cs b/src/Avalonia.OpenGL/IGlContext.cs index eb4313fba9..50868db873 100644 --- a/src/Avalonia.OpenGL/IGlContext.cs +++ b/src/Avalonia.OpenGL/IGlContext.cs @@ -9,5 +9,7 @@ namespace Avalonia.OpenGL int SampleCount { get; } int StencilSize { get; } IDisposable MakeCurrent(); + IDisposable EnsureCurrent(); + bool IsSharedWith(IGlContext context); } } diff --git a/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs b/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs index 30f83745ad..fdb9162164 100644 --- a/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs +++ b/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs @@ -4,6 +4,6 @@ namespace Avalonia.OpenGL { public interface IOpenGlAwarePlatformRenderInterface { - IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap(); + IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi); } } diff --git a/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs b/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs new file mode 100644 index 0000000000..5ee5df1e85 --- /dev/null +++ b/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs @@ -0,0 +1,13 @@ +namespace Avalonia.OpenGL +{ + public interface IPlatformOpenGlInterface + { + IGlContext PrimaryContext { get; } + IGlContext CreateSharedContext(); + bool CanShareContexts { get; } + bool CanCreateContexts { get; } + IGlContext CreateContext(); + /*IGlContext TryCreateContext(GlVersion version); + */ + } +} diff --git a/src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs b/src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs deleted file mode 100644 index b91496f42b..0000000000 --- a/src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Avalonia.OpenGL -{ - public interface IWindowingPlatformGlFeature - { - IGlContext CreateContext(); - IGlContext MainContext { get; } - } -} diff --git a/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs b/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs new file mode 100644 index 0000000000..aef4f601be --- /dev/null +++ b/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Media.Imaging; +using Avalonia.Platform; + +namespace Avalonia.OpenGL.Imaging +{ + public interface IOpenGlBitmapImpl : IBitmapImpl + { + IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context, Action presentCallback); + bool SupportsContext(IGlContext context); + } + + public interface IOpenGlBitmapAttachment : IDisposable + { + void Present(); + } +} diff --git a/src/Avalonia.OpenGL/Imaging/IOpenGlTextureBitmapImpl.cs b/src/Avalonia.OpenGL/Imaging/IOpenGlTextureBitmapImpl.cs deleted file mode 100644 index e5f3691569..0000000000 --- a/src/Avalonia.OpenGL/Imaging/IOpenGlTextureBitmapImpl.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Avalonia.Media.Imaging; -using Avalonia.Platform; - -namespace Avalonia.OpenGL.Imaging -{ - public interface IOpenGlTextureBitmapImpl : IBitmapImpl - { - IDisposable Lock(); - void SetBackBuffer(int textureId, int internalFormat, PixelSize pixelSize, double dpiScaling); - void SetDirty(); - } -} diff --git a/src/Avalonia.OpenGL/Imaging/OpenGlTextureBitmap.cs b/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs similarity index 54% rename from src/Avalonia.OpenGL/Imaging/OpenGlTextureBitmap.cs rename to src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs index 558eae8fdf..7af44cd624 100644 --- a/src/Avalonia.OpenGL/Imaging/OpenGlTextureBitmap.cs +++ b/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs @@ -6,32 +6,30 @@ using Avalonia.Threading; namespace Avalonia.OpenGL.Imaging { - public class OpenGlTextureBitmap : Bitmap, IAffectsRender + public class OpenGlBitmap : Bitmap, IAffectsRender { - private IOpenGlTextureBitmapImpl _impl; - static IOpenGlTextureBitmapImpl CreateOrThrow() + private IOpenGlBitmapImpl _impl; + + public OpenGlBitmap(PixelSize size, Vector dpi) + : base(CreateOrThrow(size, dpi)) { - if (!(AvaloniaLocator.Current.GetService() is IOpenGlAwarePlatformRenderInterface - glAware)) - throw new PlatformNotSupportedException("Rendering platform does not support OpenGL integration"); - return glAware.CreateOpenGlTextureBitmap(); + _impl = (IOpenGlBitmapImpl)PlatformImpl.Item; } - public OpenGlTextureBitmap() - : base(CreateOrThrow()) + static IOpenGlBitmapImpl CreateOrThrow(PixelSize size, Vector dpi) { - _impl = (IOpenGlTextureBitmapImpl)PlatformImpl.Item; + if (!(AvaloniaLocator.Current.GetService() is IOpenGlAwarePlatformRenderInterface + glAware)) + throw new PlatformNotSupportedException("Rendering platform does not support OpenGL integration"); + return glAware.CreateOpenGlBitmap(size, dpi); } - public IDisposable Lock() => _impl.Lock(); + public IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context) => + _impl.CreateFramebufferAttachment(context, SetIsDirty); - public void SetTexture(int textureId, int internalFormat, PixelSize size, double dpiScaling) - { - _impl.SetBackBuffer(textureId, internalFormat, size, dpiScaling); - SetIsDirty(); - } - - public void SetIsDirty() + public bool SupportsContext(IGlContext context) => _impl.SupportsContext(context); + + void SetIsDirty() { if (Dispatcher.UIThread.CheckAccess()) CallInvalidated(); diff --git a/src/Avalonia.OpenGL/OpenGlException.cs b/src/Avalonia.OpenGL/OpenGlException.cs index d3cd7d059e..196f507ad8 100644 --- a/src/Avalonia.OpenGL/OpenGlException.cs +++ b/src/Avalonia.OpenGL/OpenGlException.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.OpenGL.Egl; namespace Avalonia.OpenGL { diff --git a/src/Avalonia.OpenGL/IGlPlatformSurface.cs b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs similarity index 77% rename from src/Avalonia.OpenGL/IGlPlatformSurface.cs rename to src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs index 22d36b4472..875c215336 100644 --- a/src/Avalonia.OpenGL/IGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs @@ -1,4 +1,4 @@ -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Surfaces { public interface IGlPlatformSurface { diff --git a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderTarget.cs similarity index 89% rename from src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs rename to src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderTarget.cs index d198d46e5c..f89b6f04f5 100644 --- a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs +++ b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderTarget.cs @@ -1,6 +1,6 @@ using System; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Surfaces { public interface IGlPlatformSurfaceRenderTarget : IDisposable { diff --git a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderingSession.cs similarity index 86% rename from src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs rename to src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderingSession.cs index 89911a20a8..da06eab1e7 100644 --- a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs +++ b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderingSession.cs @@ -1,6 +1,6 @@ using System; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Surfaces { public interface IGlPlatformSurfaceRenderingSession : IDisposable { diff --git a/src/Avalonia.Styling/Controls/Metadata/PseudoClassesAttribute.cs b/src/Avalonia.Styling/Controls/Metadata/PseudoClassesAttribute.cs new file mode 100644 index 0000000000..0060767565 --- /dev/null +++ b/src/Avalonia.Styling/Controls/Metadata/PseudoClassesAttribute.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +#nullable enable + +namespace Avalonia.Controls.Metadata +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class PseudoClassesAttribute : Attribute + { + public PseudoClassesAttribute(params string[] pseudoClasses) + { + PseudoClasses = pseudoClasses; + } + + public IReadOnlyList PseudoClasses { get; } + } +} diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml index 44dfb9ea48..5f18bac44a 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml @@ -72,5 +72,8 @@ 18 8 + + 20 + 20 diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index 9ed3207235..30c6d39856 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -75,5 +75,8 @@ 18 8 + + 20 + 20 diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index e5b654b490..625b409598 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -12,7 +12,7 @@ - + @@ -21,6 +21,7 @@ + diff --git a/src/Avalonia.Themes.Default/PathIcon.xaml b/src/Avalonia.Themes.Default/PathIcon.xaml new file mode 100644 index 0000000000..2c9d1baeb4 --- /dev/null +++ b/src/Avalonia.Themes.Default/PathIcon.xaml @@ -0,0 +1,17 @@ + + + diff --git a/src/Avalonia.Themes.Fluent/Accents/Base.xaml b/src/Avalonia.Themes.Fluent/Accents/Base.xaml index 46488c1c57..134e804c53 100644 --- a/src/Avalonia.Themes.Fluent/Accents/Base.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/Base.xaml @@ -20,5 +20,7 @@ 1 2 10,6,6,5 + 20 + 20 diff --git a/src/Avalonia.Themes.Fluent/Button.xaml b/src/Avalonia.Themes.Fluent/Button.xaml index e58e8758d2..8522c933ae 100644 --- a/src/Avalonia.Themes.Fluent/Button.xaml +++ b/src/Avalonia.Themes.Fluent/Button.xaml @@ -77,7 +77,7 @@ - - diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml index 00ab9bc375..90c0f55b68 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml @@ -11,7 +11,7 @@ - + @@ -20,6 +20,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/PathIcon.xaml b/src/Avalonia.Themes.Fluent/PathIcon.xaml new file mode 100644 index 0000000000..4ec5b9aaa4 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/PathIcon.xaml @@ -0,0 +1,25 @@ + + + + + M14 9.50006C11.5147 9.50006 9.5 11.5148 9.5 14.0001C9.5 16.4853 11.5147 18.5001 14 18.5001C15.3488 18.5001 16.559 17.9066 17.3838 16.9666C18.0787 16.1746 18.5 15.1365 18.5 14.0001C18.5 13.5401 18.431 13.0963 18.3028 12.6784C17.7382 10.8381 16.0253 9.50006 14 9.50006ZM11 14.0001C11 12.3432 12.3431 11.0001 14 11.0001C15.6569 11.0001 17 12.3432 17 14.0001C17 15.6569 15.6569 17.0001 14 17.0001C12.3431 17.0001 11 15.6569 11 14.0001Z M21.7093 22.3948L19.9818 21.6364C19.4876 21.4197 18.9071 21.4515 18.44 21.7219C17.9729 21.9924 17.675 22.4693 17.6157 23.0066L17.408 24.8855C17.3651 25.273 17.084 25.5917 16.7055 25.682C14.9263 26.1061 13.0725 26.1061 11.2933 25.682C10.9148 25.5917 10.6336 25.273 10.5908 24.8855L10.3834 23.0093C10.3225 22.4731 10.0112 21.9976 9.54452 21.7281C9.07783 21.4586 8.51117 21.4269 8.01859 21.6424L6.29071 22.4009C5.93281 22.558 5.51493 22.4718 5.24806 22.1859C4.00474 20.8536 3.07924 19.2561 2.54122 17.5137C2.42533 17.1384 2.55922 16.7307 2.8749 16.4977L4.40219 15.3703C4.83721 15.0501 5.09414 14.5415 5.09414 14.0007C5.09414 13.4598 4.83721 12.9512 4.40162 12.6306L2.87529 11.5051C2.55914 11.272 2.42513 10.8638 2.54142 10.4882C3.08038 8.74734 4.00637 7.15163 5.24971 5.82114C5.51684 5.53528 5.93492 5.44941 6.29276 5.60691L8.01296 6.36404C8.50793 6.58168 9.07696 6.54881 9.54617 6.27415C10.0133 6.00264 10.3244 5.52527 10.3844 4.98794L10.5933 3.11017C10.637 2.71803 10.9245 2.39704 11.3089 2.31138C12.19 2.11504 13.0891 2.01071 14.0131 2.00006C14.9147 2.01047 15.8128 2.11485 16.6928 2.31149C17.077 2.39734 17.3643 2.71823 17.4079 3.11017L17.617 4.98937C17.7116 5.85221 18.4387 6.50572 19.3055 6.50663C19.5385 6.507 19.769 6.45838 19.9843 6.36294L21.7048 5.60568C22.0626 5.44818 22.4807 5.53405 22.7478 5.81991C23.9912 7.1504 24.9172 8.74611 25.4561 10.487C25.5723 10.8623 25.4386 11.2703 25.1228 11.5035L23.5978 12.6297C23.1628 12.95 22.9 13.4586 22.9 13.9994C22.9 14.5403 23.1628 15.0489 23.5988 15.3698L25.1251 16.4965C25.441 16.7296 25.5748 17.1376 25.4586 17.5131C24.9198 19.2536 23.9944 20.8492 22.7517 22.1799C22.4849 22.4657 22.0671 22.5518 21.7093 22.3948ZM16.263 22.1966C16.4982 21.4685 16.9889 20.8288 17.6884 20.4238C18.5702 19.9132 19.6536 19.8547 20.5841 20.2627L21.9281 20.8526C22.791 19.8538 23.4593 18.7013 23.8981 17.4552L22.7095 16.5778L22.7086 16.5771C21.898 15.98 21.4 15.0277 21.4 13.9994C21.4 12.9719 21.8974 12.0195 22.7073 11.4227L22.7085 11.4218L23.8957 10.545C23.4567 9.2988 22.7881 8.14636 21.9248 7.1477L20.5922 7.73425L20.5899 7.73527C20.1844 7.91463 19.7472 8.00722 19.3039 8.00663C17.6715 8.00453 16.3046 6.77431 16.1261 5.15465L16.1259 5.15291L15.9635 3.69304C15.3202 3.57328 14.6677 3.50872 14.013 3.50017C13.3389 3.50891 12.6821 3.57367 12.0377 3.69328L11.8751 5.15452C11.7625 6.16272 11.1793 7.05909 10.3019 7.56986C9.41937 8.0856 8.34453 8.14844 7.40869 7.73694L6.07273 7.14893C5.20949 8.14751 4.54092 9.29983 4.10196 10.5459L5.29181 11.4233C6.11115 12.0269 6.59414 12.9837 6.59414 14.0007C6.59414 15.0173 6.11142 15.9742 5.29237 16.5776L4.10161 17.4566C4.54002 18.7044 5.2085 19.8585 6.07205 20.8587L7.41742 20.2682C8.34745 19.8613 9.41573 19.9215 10.2947 20.4292C11.174 20.937 11.7593 21.832 11.8738 22.84L11.8744 22.8445L12.0362 24.3088C13.3326 24.5638 14.6662 24.5638 15.9626 24.3088L16.1247 22.8418C16.1491 22.6217 16.1955 22.4055 16.263 22.1966Z + + + + + + diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 5aa497861d..148916932f 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -1,15 +1,33 @@ Compat issues with assembly Avalonia.Visuals: +MembersMustExist : Member 'public void Avalonia.Media.DrawingContext.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.GetOrAddTypeface(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.MatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.GlyphRun.Bounds.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Media.GlyphRunDrawing.BaselineOriginProperty' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.Point Avalonia.Media.GlyphRunDrawing.BaselineOrigin.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.GlyphRunDrawing.BaselineOrigin.set(Avalonia.Point)' does not exist in the implementation but it does exist in the contract. CannotSealType : Type 'Avalonia.Media.Typeface' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. TypeCannotChangeClassification : Type 'Avalonia.Media.Typeface' is a 'struct' in the implementation but is a 'class' in the contract. CannotMakeMemberNonVirtual : Member 'public System.Boolean Avalonia.Media.Typeface.Equals(System.Object)' is non-virtual in the implementation but is virtual in the contract. CannotMakeMemberNonVirtual : Member 'public System.Int32 Avalonia.Media.Typeface.GetHashCode()' is non-virtual in the implementation but is virtual in the contract. TypesMustExist : Type 'Avalonia.Media.Fonts.FontKey' does not exist in the implementation but it does exist in the contract. +CannotAddAbstractMembers : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.DrawableTextRun.Size' is abstract in the implementation but is missing in the contract. +MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.TextFormatting.DrawableTextRun.Bounds.get()' does not exist in the implementation but it does exist in the contract. +CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' is abstract in the implementation but is missing in the contract. +MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. +CannotAddAbstractMembers : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.DrawableTextRun.Size.get()' is abstract in the implementation but is missing in the contract. +MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.TextFormatting.ShapedTextCharacters.Bounds.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.ShapedTextCharacters.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract. +CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext)' is abstract in the implementation but is missing in the contract. +MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation. MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract. -Total Issues: 13 +Total Issues: 31 diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index ba7191d7a6..ae4c927ae2 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -206,14 +206,13 @@ namespace Avalonia.Media /// /// The foreground brush. /// The glyph run. - /// The baseline origin of the glyph run. - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin) + public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { Contract.Requires(glyphRun != null); if (foreground != null) { - PlatformImpl.DrawGlyphRun(foreground, glyphRun, baselineOrigin); + PlatformImpl.DrawGlyphRun(foreground, glyphRun); } } diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs index da3a1f721c..14ab083b4f 100644 --- a/src/Avalonia.Visuals/Media/GlyphRun.cs +++ b/src/Avalonia.Visuals/Media/GlyphRun.cs @@ -16,8 +16,9 @@ namespace Avalonia.Media private IGlyphRunImpl _glyphRunImpl; private GlyphTypeface _glyphTypeface; private double _fontRenderingEmSize; - private Rect? _bounds; + private Size? _size; private int _biDiLevel; + private Point? _baselineOrigin; private ReadOnlySlice _glyphIndices; private ReadOnlySlice _glyphAdvances; @@ -89,6 +90,20 @@ namespace Avalonia.Media set => Set(ref _fontRenderingEmSize, value); } + /// + /// Gets or sets the baseline origin of the. + /// + public Point BaselineOrigin + { + get + { + _baselineOrigin ??= CalculateBaselineOrigin(); + + return _baselineOrigin.Value; + } + set => Set(ref _baselineOrigin, value); + } + /// /// Gets or sets an array of values that represent the glyph indices in the rendering physical font. /// @@ -156,16 +171,13 @@ namespace Avalonia.Media /// /// Gets or sets the conservative bounding box of the . /// - public Rect Bounds + public Size Size { get { - if (_bounds == null) - { - _bounds = CalculateBounds(); - } + _size ??= CalculateSize(); - return _bounds.Value; + return _size.Value; } } @@ -200,7 +212,7 @@ namespace Avalonia.Media if (characterHit.FirstCharacterIndex + characterHit.TrailingLength > Characters.End) { - return Bounds.Width; + return Size.Width; } var glyphIndex = FindGlyphIndex(characterHit.FirstCharacterIndex); @@ -257,7 +269,7 @@ namespace Avalonia.Media } //After - if (distance > Bounds.Size.Width) + if (distance > Size.Width) { isInside = false; @@ -529,12 +541,21 @@ namespace Avalonia.Media } /// - /// Calculates the bounds of the . + /// Calculates the default baseline origin of the . + /// + /// The baseline origin. + private Point CalculateBaselineOrigin() + { + return new Point(0, -GlyphTypeface.Ascent * Scale); + } + + /// + /// Calculates the size of the . /// /// /// The calculated bounds. /// - private Rect CalculateBounds() + private Size CalculateSize() { var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; @@ -555,7 +576,7 @@ namespace Avalonia.Media } } - return new Rect(0, GlyphTypeface.Ascent * Scale, width, height); + return new Size(width, height); } private void Set(ref T field, T value) @@ -590,11 +611,15 @@ namespace Avalonia.Media throw new InvalidOperationException(); } + _baselineOrigin = new Point(0, -GlyphTypeface.Ascent * Scale); + var platformRenderInterface = AvaloniaLocator.Current.GetService(); _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width); var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; + + _size = new Size(width, height); } void IDisposable.Dispose() diff --git a/src/Avalonia.Visuals/Media/GlyphRunDrawing.cs b/src/Avalonia.Visuals/Media/GlyphRunDrawing.cs index d0ea113a6f..7e0d5c3c81 100644 --- a/src/Avalonia.Visuals/Media/GlyphRunDrawing.cs +++ b/src/Avalonia.Visuals/Media/GlyphRunDrawing.cs @@ -8,9 +8,6 @@ public static readonly StyledProperty GlyphRunProperty = AvaloniaProperty.Register(nameof(GlyphRun)); - public static readonly StyledProperty BaselineOriginProperty = - AvaloniaProperty.Register(nameof(BaselineOrigin)); - public IBrush Foreground { get => GetValue(ForegroundProperty); @@ -23,12 +20,6 @@ set => SetValue(GlyphRunProperty, value); } - public Point BaselineOrigin - { - get => GetValue(BaselineOriginProperty); - set => SetValue(BaselineOriginProperty, value); - } - public override void Draw(DrawingContext context) { if (GlyphRun == null) @@ -36,12 +27,12 @@ return; } - context.DrawGlyphRun(Foreground, GlyphRun, BaselineOrigin); + context.DrawGlyphRun(Foreground, GlyphRun); } public override Rect GetBounds() { - return GlyphRun?.Bounds ?? default; + return GlyphRun != null ? new Rect(GlyphRun.Size) : Rect.Empty; } } } diff --git a/src/Avalonia.Visuals/Media/TextDecoration.cs b/src/Avalonia.Visuals/Media/TextDecoration.cs index 681fc5d499..d9b3f664ce 100644 --- a/src/Avalonia.Visuals/Media/TextDecoration.cs +++ b/src/Avalonia.Visuals/Media/TextDecoration.cs @@ -155,8 +155,7 @@ namespace Avalonia.Media /// /// The drawing context. /// The shaped characters that are decorated. - /// The origin. - internal void Draw(DrawingContext drawingContext, ShapedTextCharacters shapedTextCharacters, Point origin) + internal void Draw(DrawingContext drawingContext, ShapedTextCharacters shapedTextCharacters) { var fontRenderingEmSize = shapedTextCharacters.Properties.FontRenderingEmSize; var fontMetrics = shapedTextCharacters.FontMetrics; @@ -181,16 +180,20 @@ namespace Avalonia.Media break; } + var origin = new Point(); + switch (Location) { - case TextDecorationLocation.Overline: - origin += new Point(0, fontMetrics.Ascent); + case TextDecorationLocation.Baseline: + origin += shapedTextCharacters.GlyphRun.BaselineOrigin; break; case TextDecorationLocation.Strikethrough: - origin += new Point(0, -fontMetrics.StrikethroughPosition); + origin += new Point(shapedTextCharacters.GlyphRun.BaselineOrigin.X, + shapedTextCharacters.GlyphRun.BaselineOrigin.Y - fontMetrics.StrikethroughPosition); break; case TextDecorationLocation.Underline: - origin += new Point(0, -fontMetrics.UnderlinePosition); + origin += new Point(shapedTextCharacters.GlyphRun.BaselineOrigin.X, + shapedTextCharacters.GlyphRun.BaselineOrigin.Y - fontMetrics.UnderlinePosition); break; } @@ -207,7 +210,7 @@ namespace Avalonia.Media var pen = new Pen(Stroke ?? shapedTextCharacters.Properties.ForegroundBrush, thickness, new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap); - drawingContext.DrawLine(pen, origin, origin + new Point(shapedTextCharacters.Bounds.Width, 0)); + drawingContext.DrawLine(pen, origin, origin + new Point(shapedTextCharacters.Size.Width, 0)); } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs b/src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs index 56790cc0db..338c92f6b1 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs @@ -6,15 +6,14 @@ public abstract class DrawableTextRun : TextRun { /// - /// Gets the bounds. + /// Gets the size. /// - public abstract Rect Bounds { get; } + public abstract Size Size { get; } /// /// Draws the at the given origin. /// /// The drawing context. - /// The origin. - public abstract void Draw(DrawingContext drawingContext, Point origin); + public abstract void Draw(DrawingContext drawingContext); } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs index 9e67a03f45..09ecc0a026 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs @@ -26,7 +26,7 @@ namespace Avalonia.Media.TextFormatting public override int TextSourceLength { get; } /// - public override Rect Bounds => GlyphRun.Bounds; + public override Size Size => GlyphRun.Size; /// /// Gets the font metrics. @@ -45,7 +45,7 @@ namespace Avalonia.Media.TextFormatting public GlyphRun GlyphRun { get; } /// - public override void Draw(DrawingContext drawingContext, Point origin) + public override void Draw(DrawingContext drawingContext) { if (GlyphRun.GlyphIndices.Length == 0) { @@ -64,11 +64,10 @@ namespace Avalonia.Media.TextFormatting if (Properties.BackgroundBrush != null) { - drawingContext.DrawRectangle(Properties.BackgroundBrush, null, - new Rect(origin.X, origin.Y + FontMetrics.Ascent, Bounds.Width, Bounds.Height)); + drawingContext.DrawRectangle(Properties.BackgroundBrush, null, new Rect(Size)); } - drawingContext.DrawGlyphRun(Properties.ForegroundBrush, GlyphRun, origin); + drawingContext.DrawGlyphRun(Properties.ForegroundBrush, GlyphRun); if (Properties.TextDecorations == null) { @@ -77,7 +76,7 @@ namespace Avalonia.Media.TextFormatting foreach (var textDecoration in Properties.TextDecorations) { - textDecoration.Draw(drawingContext, this, origin); + textDecoration.Draw(drawingContext, this); } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index b116249fd4..3e85f0f6f0 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -52,7 +52,7 @@ namespace Avalonia.Media.TextFormatting { var glyphRun = textCharacters.GlyphRun; - if (glyphRun.Bounds.Width < availableWidth) + if (glyphRun.Size.Width < availableWidth) { return glyphRun.Characters.Length; } @@ -348,7 +348,7 @@ namespace Avalonia.Media.TextFormatting { var currentRun = textRuns[runIndex]; - if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth) + if (currentWidth + currentRun.Size.Width > availableWidth) { var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth); @@ -421,7 +421,7 @@ namespace Avalonia.Media.TextFormatting return new TextLineImpl(splitResult.First, textLineMetrics, lineBreak); } - currentWidth += currentRun.GlyphRun.Bounds.Width; + currentWidth += currentRun.Size.Width; currentLength += currentRun.GlyphRun.Characters.Length; diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index df1ecb4067..daa8807bf6 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs @@ -115,22 +115,24 @@ namespace Avalonia.Media.TextFormatting /// Draws the text layout. /// /// The drawing context. - /// The origin. - public void Draw(DrawingContext context, Point origin) + public void Draw(DrawingContext context) { if (!TextLines.Any()) { return; } - var currentY = origin.Y; + var currentY = 0.0; foreach (var textLine in TextLines) { var offsetX = TextLine.GetParagraphOffsetX(textLine.LineMetrics.Size.Width, Size.Width, _paragraphProperties.TextAlignment); - textLine.Draw(context, new Point(origin.X + offsetX, currentY)); + using (context.PushPostTransform(Matrix.CreateTranslation(offsetX, currentY))) + { + textLine.Draw(context); + } currentY += textLine.LineMetrics.Size.Height; } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs index c052fb8948..8a1efa0611 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs @@ -51,8 +51,7 @@ namespace Avalonia.Media.TextFormatting /// Draws the at the given origin. /// /// The drawing context. - /// The origin. - public abstract void Draw(DrawingContext drawingContext, Point origin); + public abstract void Draw(DrawingContext drawingContext); /// /// Create a collapsed line based on collapsed text properties. diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index 51092cddda..f5e87d097b 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -33,17 +33,18 @@ namespace Avalonia.Media.TextFormatting public override bool HasCollapsed { get; } /// - public override void Draw(DrawingContext drawingContext, Point origin) + public override void Draw(DrawingContext drawingContext) { - var currentX = origin.X; + var currentX = 0.0; foreach (var textRun in _textRuns) { - var baselineOrigin = new Point(currentX, origin.Y + LineMetrics.TextBaseline); - - textRun.Draw(drawingContext, baselineOrigin); + using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, 0))) + { + textRun.Draw(drawingContext); + } - currentX += textRun.Bounds.Width; + currentX += textRun.Size.Width; } } @@ -64,13 +65,13 @@ namespace Avalonia.Media.TextFormatting var shapedSymbol = CreateShapedSymbol(collapsingProperties.Symbol); - var availableWidth = collapsingProperties.Width - shapedSymbol.Bounds.Width; + var availableWidth = collapsingProperties.Width - shapedSymbol.Size.Width; while (runIndex < _textRuns.Count) { var currentRun = _textRuns[runIndex]; - currentWidth += currentRun.GlyphRun.Bounds.Width; + currentWidth += currentRun.Size.Width; if (currentWidth > availableWidth) { @@ -125,7 +126,7 @@ namespace Avalonia.Media.TextFormatting return new TextLineImpl(shapedTextCharacters, textLineMetrics, TextLineBreak, true); } - availableWidth -= currentRun.GlyphRun.Bounds.Width; + availableWidth -= currentRun.Size.Width; collapsedLength += currentRun.GlyphRun.Characters.Length; @@ -133,7 +134,7 @@ namespace Avalonia.Media.TextFormatting } textLineMetrics = - new TextLineMetrics(LineMetrics.Size.WithWidth(LineMetrics.Size.Width + shapedSymbol.Bounds.Width), + new TextLineMetrics(LineMetrics.Size.WithWidth(LineMetrics.Size.Width + shapedSymbol.Size.Width), LineMetrics.TextBaseline, TextRange, LineMetrics.HasOverflowed); return new TextLineImpl(new List(_textRuns) { shapedSymbol }, textLineMetrics, null, @@ -156,12 +157,12 @@ namespace Avalonia.Media.TextFormatting { characterHit = run.GlyphRun.GetCharacterHitFromDistance(distance, out _); - if (distance <= run.Bounds.Width) + if (distance <= run.Size.Width) { break; } - distance -= run.Bounds.Width; + distance -= run.Size.Width; } return characterHit; @@ -229,7 +230,7 @@ namespace Avalonia.Media.TextFormatting { if (codepointIndex > textRun.Text.End) { - currentDistance += textRun.Bounds.Width; + currentDistance += textRun.Size.Width; continue; } @@ -405,7 +406,7 @@ namespace Avalonia.Media.TextFormatting for (var i = 0; i < shapedTextCharacters.Count; i++) { - shapedWidth += shapedTextCharacters[i].Bounds.Width; + shapedWidth += shapedTextCharacters[i].Size.Width; } return shapedWidth; diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs index 6875cc1c04..c4d7527659 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs @@ -67,7 +67,7 @@ namespace Avalonia.Media.TextFormatting var fontMetrics = new FontMetrics(shapedRun.Properties.Typeface, shapedRun.Properties.FontRenderingEmSize); - lineWidth += shapedRun.Bounds.Width; + lineWidth += shapedRun.Size.Width; if (ascent > fontMetrics.Ascent) { diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index c87946b3ea..019614ae80 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -84,8 +84,7 @@ namespace Avalonia.Platform /// /// The foreground. /// The glyph run. - /// The baseline origin of the glyph run. - void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin); + void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun); /// /// Creates a new that can be used as a render layer diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 4a364998fd..cb6b1f59d4 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -204,13 +204,13 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin) + public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { var next = NextDrawAs(); if (next == null || !next.Item.Equals(Transform, foreground, glyphRun)) { - Add(new GlyphRunNode(Transform, foreground, glyphRun, baselineOrigin, CreateChildScene(foreground))); + Add(new GlyphRunNode(Transform, foreground, glyphRun, CreateChildScene(foreground))); } else diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs index bdf05c4f86..a6dba1bd32 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Platform; using Avalonia.VisualTree; @@ -17,20 +18,17 @@ namespace Avalonia.Rendering.SceneGraph /// The transform. /// The foreground brush. /// The glyph run to draw. - /// The baseline origin of the glyph run. /// Child scenes for drawing visual brushes. public GlyphRunNode( Matrix transform, IBrush foreground, GlyphRun glyphRun, - Point baselineOrigin, IDictionary childScenes = null) - : base(glyphRun.Bounds.Translate(baselineOrigin), transform) + : base(new Rect(glyphRun.Size), transform) { Transform = transform; Foreground = foreground?.ToImmutable(); GlyphRun = glyphRun; - BaselineOrigin = baselineOrigin; ChildScenes = childScenes; } @@ -49,11 +47,6 @@ namespace Avalonia.Rendering.SceneGraph /// public GlyphRun GlyphRun { get; } - /// - /// Gets the baseline origin. - /// - public Point BaselineOrigin { get; set; } - /// public override IDictionary ChildScenes { get; } @@ -61,7 +54,7 @@ namespace Avalonia.Rendering.SceneGraph public override void Render(IDrawingContextImpl context) { context.Transform = Transform; - context.DrawGlyphRun(Foreground, GlyphRun, BaselineOrigin); + context.DrawGlyphRun(Foreground, GlyphRun); } /// diff --git a/src/Avalonia.X11/Glx/GlxContext.cs b/src/Avalonia.X11/Glx/GlxContext.cs index 0349a6e26e..e9cb88cb8f 100644 --- a/src/Avalonia.X11/Glx/GlxContext.cs +++ b/src/Avalonia.X11/Glx/GlxContext.cs @@ -8,18 +8,21 @@ namespace Avalonia.X11.Glx { public IntPtr Handle { get; } public GlxInterface Glx { get; } + private readonly GlxContext _sharedWith; private readonly X11Info _x11; private readonly IntPtr _defaultXid; private readonly bool _ownsPBuffer; private readonly object _lock = new object(); - public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, + public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, + GlxContext sharedWith, GlVersion version, int sampleCount, int stencilSize, X11Info x11, IntPtr defaultXid, bool ownsPBuffer) { Handle = handle; Glx = glx; + _sharedWith = sharedWith; _x11 = x11; _defaultXid = defaultXid; _ownsPBuffer = ownsPBuffer; @@ -37,25 +40,21 @@ namespace Avalonia.X11.Glx public int SampleCount { get; } public int StencilSize { get; } - public IDisposable Lock() - { - Monitor.Enter(_lock); - return Disposable.Create(() => Monitor.Exit(_lock)); - } - class RestoreContext : IDisposable { private GlxInterface _glx; private IntPtr _defaultDisplay; + private readonly object _l; private IntPtr _display; private IntPtr _context; private IntPtr _read; private IntPtr _draw; - public RestoreContext(GlxInterface glx, IntPtr defaultDisplay) + public RestoreContext(GlxInterface glx, IntPtr defaultDisplay, object l) { _glx = glx; _defaultDisplay = defaultDisplay; + _l = l; _display = _glx.GetCurrentDisplay(); _context = _glx.GetCurrentContext(); _read = _glx.GetCurrentReadDrawable(); @@ -66,19 +65,49 @@ namespace Avalonia.X11.Glx { var disp = _display == IntPtr.Zero ? _defaultDisplay : _display; _glx.MakeContextCurrent(disp, _draw, _read, _context); + Monitor.Exit(_l); } } public IDisposable MakeCurrent() => MakeCurrent(_defaultXid); + public IDisposable EnsureCurrent() + { + if(IsCurrent) + return Disposable.Empty; + return MakeCurrent(); + } + + public bool IsSharedWith(IGlContext context) + { + var c = (GlxContext)context; + return c == this + || c._sharedWith == this + || _sharedWith == context + || _sharedWith != null && _sharedWith == c._sharedWith; + } public IDisposable MakeCurrent(IntPtr xid) { - var old = new RestoreContext(Glx, _x11.Display); - if (!Glx.MakeContextCurrent(_x11.Display, xid, xid, Handle)) - throw new OpenGlException("glXMakeContextCurrent failed "); - return old; + Monitor.Enter(_lock); + var success = false; + try + { + var old = new RestoreContext(Glx, _x11.Display, _lock); + if (!Glx.MakeContextCurrent(_x11.Display, xid, xid, Handle)) + throw new OpenGlException("glXMakeContextCurrent failed "); + + success = true; + return old; + } + finally + { + if (!success) + Monitor.Exit(_lock); + } } + public bool IsCurrent => Glx.GetCurrentContext() == Handle; + public void Dispose() { Glx.DestroyContext(_x11.Display, Handle); diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs index b82895d12c..fa8c866c09 100644 --- a/src/Avalonia.X11/Glx/GlxDisplay.cs +++ b/src/Avalonia.X11/Glx/GlxDisplay.cs @@ -113,9 +113,9 @@ namespace Avalonia.X11.Glx } - public GlxContext CreateContext() => CreateContext(DeferredContext); - - GlxContext CreateContext(IGlContext share) => CreateContext(CreatePBuffer(), share, + public GlxContext CreateContext() => CreateContext(); + + public GlxContext CreateContext(IGlContext share) => CreateContext(CreatePBuffer(), share, share.SampleCount, share.StencilSize, true); GlxContext CreateContext(IntPtr defaultXid, IGlContext share, @@ -144,7 +144,7 @@ namespace Avalonia.X11.Glx if (handle != IntPtr.Zero) { _version = profile; - return new GlxContext(new GlxInterface(), handle, this, profile, + return new GlxContext(new GlxInterface(), handle, this, (GlxContext)share, profile, sampleCount, stencilSize, _x11, defaultXid, ownsPBuffer); } diff --git a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs index ae6b0eb353..cb4ab4aca0 100644 --- a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs +++ b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs @@ -1,5 +1,8 @@ using System; using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Surfaces; +using static Avalonia.OpenGL.GlConsts; namespace Avalonia.X11.Glx { @@ -40,33 +43,26 @@ namespace Avalonia.X11.Glx public IGlPlatformSurfaceRenderingSession BeginDraw() { - var l = _context.Lock(); - try - { - - return new Session(_context, _info, l, _context.MakeCurrent(_info.Handle)); - } - catch - { - l.Dispose(); - throw; - } + var oldContext = _context.MakeCurrent(_info.Handle); + + // Reset to default FBO first + _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, 0); + + return new Session(_context, _info, oldContext); } class Session : IGlPlatformSurfaceRenderingSession { private readonly GlxContext _context; private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; - private IDisposable _lock; private readonly IDisposable _clearContext; public IGlContext Context => _context; public Session(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info, - IDisposable @lock, IDisposable clearContext) + IDisposable clearContext) { _context = context; _info = info; - _lock = @lock; _clearContext = clearContext; } @@ -77,7 +73,6 @@ namespace Avalonia.X11.Glx _context.Display.SwapBuffers(_info.Handle); _context.Glx.WaitX(); _clearContext.Dispose(); - _lock.Dispose(); } public PixelSize Size => _info.Size; diff --git a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs index ad3a54bcc1..6735a32ffe 100644 --- a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs +++ b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs @@ -5,31 +5,34 @@ using Avalonia.OpenGL; namespace Avalonia.X11.Glx { - class GlxGlPlatformFeature : IWindowingPlatformGlFeature + class GlxPlatformOpenGlInterface : IPlatformOpenGlInterface { public GlxDisplay Display { get; private set; } + public bool CanCreateContexts => true; + public bool CanShareContexts => true; public IGlContext CreateContext() => Display.CreateContext(); + public IGlContext CreateSharedContext() => Display.CreateContext(PrimaryContext); public GlxContext DeferredContext { get; private set; } - public IGlContext MainContext => DeferredContext; + public IGlContext PrimaryContext => DeferredContext; public static bool TryInitialize(X11Info x11, IList glProfiles) { var feature = TryCreate(x11, glProfiles); if (feature != null) { - AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); return true; } return false; } - public static GlxGlPlatformFeature TryCreate(X11Info x11, IList glProfiles) + public static GlxPlatformOpenGlInterface TryCreate(X11Info x11, IList glProfiles) { try { var disp = new GlxDisplay(x11, glProfiles); - return new GlxGlPlatformFeature + return new GlxPlatformOpenGlInterface { Display = disp, DeferredContext = disp.DeferredContext diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index d7bd81db98..c6db146f7b 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -7,6 +7,7 @@ using Avalonia.FreeDesktop; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.X11; @@ -70,9 +71,9 @@ namespace Avalonia.X11 if (options.UseGpu) { if (options.UseEGL) - EglGlPlatformFeature.TryInitialize(); + EglPlatformOpenGlInterface.TryInitialize(); else - GlxGlPlatformFeature.TryInitialize(Info, Options.GlProfiles); + GlxPlatformOpenGlInterface.TryInitialize(Info, Options.GlProfiles); } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 0c0b942bcd..2cd3b973d8 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -12,6 +12,7 @@ using Avalonia.FreeDesktop; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; @@ -65,7 +66,7 @@ namespace Avalonia.X11 _touch = new TouchDevice(); _keyboard = platform.KeyboardDevice; - var glfeature = AvaloniaLocator.Current.GetService(); + var glfeature = AvaloniaLocator.Current.GetService(); XSetWindowAttributes attr = new XSetWindowAttributes(); var valueMask = default(SetWindowValuemask); @@ -87,13 +88,13 @@ namespace Avalonia.X11 // OpenGL seems to be do weird things to it's current window which breaks resize sometimes _useRenderWindow = glfeature != null; - var glx = glfeature as GlxGlPlatformFeature; + var glx = glfeature as GlxPlatformOpenGlInterface; if (glx != null) visualInfo = *glx.Display.VisualInfo; else if (glfeature == null) visualInfo = _x11.TransparentVisualInfo; - var egl = glfeature as EglGlPlatformFeature; + var egl = glfeature as EglPlatformOpenGlInterface; var visual = IntPtr.Zero; var depth = 24; @@ -168,7 +169,7 @@ namespace Avalonia.X11 if (egl != null) surfaces.Insert(0, - new EglGlPlatformSurface(egl.DeferredContext, + new EglGlPlatformSurface(egl, new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle))); if (glx != null) surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext, diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index db37e4af0b..8801f71f9a 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -32,8 +32,8 @@ namespace Avalonia.LinuxFramebuffer void Initialize() { Threading = new InternalPlatformThreadingInterface(); - if (_fb is IWindowingPlatformGlFeature glFeature) - AvaloniaLocator.CurrentMutable.Bind().ToConstant(glFeature); + if (_fb is IGlOutputBackend gl) + AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl.PlatformOpenGlInterface); AvaloniaLocator.CurrentMutable .Bind().ToConstant(Threading) .Bind().ToConstant(new DefaultRenderTimer(60)) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 7a5d20fc83..72eed9e543 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -4,6 +4,8 @@ using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Surfaces; using Avalonia.Platform.Interop; using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods; using static Avalonia.LinuxFramebuffer.Output.LibDrm; @@ -11,13 +13,16 @@ using static Avalonia.LinuxFramebuffer.Output.LibDrm.GbmColorFormats; namespace Avalonia.LinuxFramebuffer.Output { - public unsafe class DrmOutput : IOutputBackend, IGlPlatformSurface, IWindowingPlatformGlFeature + public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface { private DrmCard _card; private readonly EglGlPlatformSurface _eglPlatformSurface; public PixelSize PixelSize => _mode.Resolution; public double Scaling { get; set; } - public IGlContext MainContext => _deferredContext; + public IGlContext PrimaryContext => _deferredContext; + + private EglPlatformOpenGlInterface _platformGl; + public IPlatformOpenGlInterface PlatformOpenGlInterface => _platformGl; public DrmOutput(string path = null) { @@ -132,10 +137,9 @@ namespace Avalonia.LinuxFramebuffer.Output if(_gbmTargetSurface == null) throw new InvalidOperationException("Unable to create GBM surface"); - - - _eglDisplay = new EglDisplay(new EglInterface(eglGetProcAddress), 0x31D7, device, null); - _eglSurface = _eglDisplay.CreateWindowSurface(_gbmTargetSurface); + _eglDisplay = new EglDisplay(new EglInterface(eglGetProcAddress), false, 0x31D7, device, null); + _platformGl = new EglPlatformOpenGlInterface(_eglDisplay); + _eglSurface = _platformGl.CreateWindowSurface(_gbmTargetSurface); EglContext CreateContext(EglContext share) @@ -144,7 +148,7 @@ namespace Avalonia.LinuxFramebuffer.Output GbmBoFlags.GBM_BO_USE_RENDERING); if (offSurf == null) throw new InvalidOperationException("Unable to create 1x1 sized GBM surface"); - return _eglDisplay.CreateContext(share, _eglDisplay.CreateWindowSurface(offSurf)); + return _eglDisplay.CreateContext(share, _platformGl.CreateWindowSurface(offSurf)); } _deferredContext = CreateContext(null); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs new file mode 100644 index 0000000000..7bc73d590c --- /dev/null +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs @@ -0,0 +1,9 @@ +using Avalonia.OpenGL; + +namespace Avalonia.LinuxFramebuffer.Output +{ + public interface IGlOutputBackend : IOutputBackend + { + public IPlatformOpenGlInterface PlatformOpenGlInterface { get; } + } +} diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index a155fd863b..98528a128a 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -401,16 +401,16 @@ namespace Avalonia.Skia } /// - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin) + public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { - using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Bounds.Size)) + using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size)) { var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; ConfigureTextRendering(paintWrapper); - Canvas.DrawText(glyphRunImpl.TextBlob, (float)baselineOrigin.X, - (float)baselineOrigin.Y, paintWrapper.Paint); + Canvas.DrawText(glyphRunImpl.TextBlob, (float)glyphRun.BaselineOrigin.X, + (float)glyphRun.BaselineOrigin.Y, paintWrapper.Paint); } } diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index 987e2c089c..1a7a9b75cf 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs @@ -19,6 +19,6 @@ namespace Avalonia.Skia public interface IOpenGlAwareSkiaGpu : ISkiaGpu { - IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap(); + IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi); } } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index 081db5d26a..6df8df9a4c 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -1,6 +1,7 @@ using System; using System.Reactive.Disposables; using Avalonia.OpenGL; +using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Rendering; using SkiaSharp; diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index 9278de2137..46d42dfdab 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Avalonia.OpenGL; using Avalonia.OpenGL.Imaging; +using Avalonia.OpenGL.Surfaces; using SkiaSharp; namespace Avalonia.Skia @@ -8,10 +9,12 @@ namespace Avalonia.Skia class GlSkiaGpu : IOpenGlAwareSkiaGpu { private GRContext _grContext; + private IGlContext _glContext; - public GlSkiaGpu(IWindowingPlatformGlFeature gl, long? maxResourceBytes) + public GlSkiaGpu(IPlatformOpenGlInterface openGl, long? maxResourceBytes) { - var context = gl.MainContext; + var context = openGl.PrimaryContext; + _glContext = context; using (context.MakeCurrent()) { using (var iface = context.Version.Type == GlProfileType.OpenGL ? @@ -40,6 +43,6 @@ namespace Avalonia.Skia return null; } - public IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap() => new OpenGlTextureBitmapImpl(); + public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi); } } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs new file mode 100644 index 0000000000..2ebf7c680b --- /dev/null +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Avalonia.OpenGL; +using Avalonia.OpenGL.Imaging; +using Avalonia.Utilities; +using SkiaSharp; +using static Avalonia.OpenGL.GlConsts; + +namespace Avalonia.Skia +{ + class GlOpenGlBitmapImpl : IOpenGlBitmapImpl, IDrawableBitmapImpl + { + private readonly IGlContext _context; + private readonly object _lock = new object(); + private IGlPresentableOpenGlSurface _surface; + + public GlOpenGlBitmapImpl(IGlContext context, PixelSize pixelSize, Vector dpi) + { + _context = context; + PixelSize = pixelSize; + Dpi = dpi; + } + + public Vector Dpi { get; } + public PixelSize PixelSize { get; } + public int Version { get; private set; } + public void Save(string fileName) => throw new NotSupportedException(); + + public void Save(Stream stream) => throw new NotSupportedException(); + + public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) + { + lock (_lock) + { + if (_surface == null) + return; + using (_surface.Lock()) + { + using (var backendTexture = new GRBackendTexture(PixelSize.Width, PixelSize.Height, false, + new GRGlTextureInfo( + GlConsts.GL_TEXTURE_2D, (uint)_surface.GetTextureId(), + (uint)_surface.InternalFormat))) + using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.TopLeft, + SKColorType.Rgba8888)) + { + // Again, silently ignore, if something went wrong it's not our fault + if (surface == null) + return; + + using (var snapshot = surface.Snapshot()) + context.Canvas.DrawImage(snapshot, sourceRect, destRect, paint); + } + + } + } + } + + public IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context, Action presentCallback) + { + if (!SupportsContext(context)) + throw new OpenGlException("Context is not supported for texture sharing"); + return new SharedOpenGlBitmapAttachment(this, context, presentCallback); + } + + public bool SupportsContext(IGlContext context) + { + // TODO: negotiated platform surface sharing + return _context.IsSharedWith(context); + } + + public void Dispose() + { + + } + + internal void Present(IGlPresentableOpenGlSurface surface) + { + lock (_lock) + { + _surface = surface; + } + } + } + + interface IGlPresentableOpenGlSurface : IDisposable + { + int GetTextureId(); + int InternalFormat { get; } + IDisposable Lock(); + } + + class SharedOpenGlBitmapAttachment : IOpenGlBitmapAttachment, IGlPresentableOpenGlSurface + { + private readonly GlOpenGlBitmapImpl _bitmap; + private readonly IGlContext _context; + private readonly Action _presentCallback; + private readonly int _fbo; + private readonly int _texture; + private readonly int _frontBuffer; + private bool _disposed; + private readonly DisposableLock _lock = new DisposableLock(); + + public SharedOpenGlBitmapAttachment(GlOpenGlBitmapImpl bitmap, IGlContext context, Action presentCallback) + { + _bitmap = bitmap; + _context = context; + _presentCallback = presentCallback; + using (_context.EnsureCurrent()) + { + var glVersion = _context.Version; + InternalFormat = glVersion.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8; + + _context.GlInterface.GetIntegerv(GL_FRAMEBUFFER_BINDING, out _fbo); + if (_fbo == 0) + throw new OpenGlException("Current FBO is 0"); + + { + var gl = _context.GlInterface; + + var textures = new int[2]; + gl.GenTextures(2, textures); + _texture = textures[0]; + _frontBuffer = textures[1]; + + gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture); + foreach (var t in textures) + { + gl.BindTexture(GL_TEXTURE_2D, t); + gl.TexImage2D(GL_TEXTURE_2D, 0, + InternalFormat, + _bitmap.PixelSize.Width, _bitmap.PixelSize.Height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero); + + gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); + gl.BindTexture(GL_TEXTURE_2D, oldTexture); + + } + } + } + + public void Present() + { + using (_context.MakeCurrent()) + { + if (_disposed) + throw new ObjectDisposedException(nameof(SharedOpenGlBitmapAttachment)); + + var gl = _context.GlInterface; + + gl.Finish(); + using (Lock()) + { + gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var oldFbo); + gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture); + gl.GetIntegerv(GL_ACTIVE_TEXTURE, out var oldActive); + + gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo); + gl.BindTexture(GL_TEXTURE_2D, _frontBuffer); + gl.ActiveTexture(GL_TEXTURE0); + + gl.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, _bitmap.PixelSize.Width, + _bitmap.PixelSize.Height); + + gl.BindFramebuffer(GL_FRAMEBUFFER, oldFbo); + gl.BindTexture(GL_TEXTURE_2D, oldTexture); + gl.ActiveTexture(oldActive); + + gl.Finish(); + } + } + + _bitmap.Present(this); + _presentCallback(); + } + + public void Dispose() + { + var gl = _context.GlInterface; + _bitmap.Present(null); + + if(_disposed) + return; + using (_context.MakeCurrent()) + using (Lock()) + { + if(_disposed) + return; + _disposed = true; + gl.DeleteTextures(2, new[] { _texture, _frontBuffer }); + } + } + + int IGlPresentableOpenGlSurface.GetTextureId() + { + return _frontBuffer; + } + + public int InternalFormat { get; } + + public IDisposable Lock() => _lock.Lock(); + } +} diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGlTextureBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGlTextureBitmapImpl.cs deleted file mode 100644 index 8d007e35f3..0000000000 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGlTextureBitmapImpl.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.IO; -using Avalonia.OpenGL; -using Avalonia.OpenGL.Imaging; -using Avalonia.Skia.Helpers; -using Avalonia.Utilities; -using SkiaSharp; - -namespace Avalonia.Skia -{ - class OpenGlTextureBitmapImpl : IOpenGlTextureBitmapImpl, IDrawableBitmapImpl - { - private DisposableLock _lock = new DisposableLock(); - private int _textureId; - private int _internalFormat; - - public void Dispose() - { - using (Lock()) - { - _textureId = 0; - PixelSize = new PixelSize(1, 1); - Version++; - } - } - - public Vector Dpi { get; private set; } = new Vector(96, 96); - public PixelSize PixelSize { get; private set; } = new PixelSize(1, 1); - public int Version { get; private set; } = 0; - - public void Save(string fileName) => throw new System.NotSupportedException(); - public void Save(Stream stream) => throw new System.NotSupportedException(); - - public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) - { - // For now silently ignore - if (context.GrContext == null) - return; - - using (Lock()) - { - if (_textureId == 0) - return; - using (var backendTexture = new GRBackendTexture(PixelSize.Width, PixelSize.Height, false, - new GRGlTextureInfo( - GlConsts.GL_TEXTURE_2D, (uint)_textureId, - (uint)_internalFormat))) - using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.TopLeft, - SKColorType.Rgba8888)) - { - // Again, silently ignore, if something went wrong it's not our fault - if (surface == null) - return; - - using (var snapshot = surface.Snapshot()) - context.Canvas.DrawImage(snapshot, sourceRect, destRect, paint); - } - } - } - - public IDisposable Lock() => _lock.Lock(); - - public void SetBackBuffer(int textureId, int internalFormat, PixelSize pixelSize, double dpiScaling) - { - using (_lock.Lock()) - { - _textureId = textureId; - _internalFormat = internalFormat; - PixelSize = pixelSize; - Dpi = new Vector(96 * dpiScaling, 96 * dpiScaling); - Version++; - } - } - - public void SetDirty() - { - using (_lock.Lock()) - Version++; - } - } -} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index c4f70df7c0..b9c1cbc673 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -30,7 +30,7 @@ namespace Avalonia.Skia return; } - var gl = AvaloniaLocator.Current.GetService(); + var gl = AvaloniaLocator.Current.GetService(); if (gl != null) _skiaGpu = new GlSkiaGpu(gl, maxResourceBytes); } @@ -256,10 +256,10 @@ namespace Avalonia.Skia } - public IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap() + public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) { if (_skiaGpu is IOpenGlAwareSkiaGpu glAware) - return glAware.CreateOpenGlTextureBitmap(); + return glAware.CreateOpenGlBitmap(size, dpi); if (_skiaGpu == null) throw new PlatformNotSupportedException("GPU acceleration is not available"); throw new PlatformNotSupportedException( diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 27b29c6e1e..428087ac56 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -109,10 +109,16 @@ namespace Avalonia.Skia /// public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) { - using (var image = SnapshotImage()) + if (sourceRect.Left == 0 && sourceRect.Top == 0 && sourceRect.Size == destRect.Size) { - context.Canvas.DrawImage(image, sourceRect, destRect, paint); + _surface.Canvas.Flush(); + _surface.Draw(context.Canvas, destRect.Left, destRect.Top, paint); } + else + using (var image = SnapshotImage()) + { + context.Canvas.DrawImage(image, sourceRect, destRect, paint); + } } /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index e0de40525f..258a51db5a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -324,13 +324,14 @@ namespace Avalonia.Direct2D1.Media /// The foreground. /// The glyph run. /// - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin) + public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { - using (var brush = CreateBrush(foreground, glyphRun.Bounds.Size)) + using (var brush = CreateBrush(foreground, glyphRun.Size)) { var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; - _renderTarget.DrawGlyphRun(baselineOrigin.ToSharpDX(), glyphRunImpl.GlyphRun, brush.PlatformBrush, MeasuringMode.Natural); + _renderTarget.DrawGlyphRun(glyphRun.BaselineOrigin.ToSharpDX(), glyphRunImpl.GlyphRun, + brush.PlatformBrush, MeasuringMode.Natural); } } diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index bd188ad53a..fbc56e7703 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -1,26 +1,27 @@ using Avalonia.OpenGL; using Avalonia.OpenGL.Angle; +using Avalonia.OpenGL.Egl; namespace Avalonia.Win32 { static class Win32GlManager { /// This property is initialized if drawing platform requests OpenGL support - public static EglGlPlatformFeature EglFeature { get; private set; } + public static EglPlatformOpenGlInterface EglPlatformInterface { get; private set; } private static bool s_attemptedToInitialize; public static void Initialize() { - AvaloniaLocator.CurrentMutable.Bind().ToFunc(() => + AvaloniaLocator.CurrentMutable.Bind().ToFunc(() => { if (!s_attemptedToInitialize) { - EglFeature = EglGlPlatformFeature.TryCreate(() => new AngleWin32EglDisplay()); + EglPlatformInterface = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay()); s_attemptedToInitialize = true; } - return EglFeature; + return EglPlatformInterface; }); } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9c6bce1c90..cb85e14e5a 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -7,6 +7,8 @@ using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Win32.Input; @@ -103,8 +105,8 @@ namespace Avalonia.Win32 CreateWindow(); _framebuffer = new FramebufferManager(_hwnd); - if (Win32GlManager.EglFeature != null) - _gl = new EglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); + if (Win32GlManager.EglPlatformInterface != null) + _gl = new EglGlPlatformSurface(Win32GlManager.EglPlatformInterface, this); Screen = new ScreenImpl(); diff --git a/src/iOS/Avalonia.iOS/EaglDisplay.cs b/src/iOS/Avalonia.iOS/EaglDisplay.cs index 635df43407..f9c787b6a8 100644 --- a/src/iOS/Avalonia.iOS/EaglDisplay.cs +++ b/src/iOS/Avalonia.iOS/EaglDisplay.cs @@ -1,15 +1,18 @@ using System; +using System.Reactive.Disposables; using Avalonia.OpenGL; using OpenGLES; using OpenTK.Graphics.ES30; namespace Avalonia.iOS { - class EaglFeature : IWindowingPlatformGlFeature + class EaglFeature : IPlatformOpenGlInterface { + public IGlContext PrimaryContext => Context; + public IGlContext CreateSharedContext() => throw new NotSupportedException(); + public bool CanShareContexts => false; + public bool CanCreateContexts => false; public IGlContext CreateContext() => throw new System.NotSupportedException(); - - public IGlContext MainContext => Context; public GlContext Context { get; } = new GlContext(); } @@ -61,9 +64,18 @@ namespace Avalonia.iOS return new ResetContext(old); } + public IDisposable EnsureCurrent() + { + if(EAGLContext.CurrentContext == Context) + return Disposable.Empty; + return MakeCurrent(); + } + + public bool IsSharedWith(IGlContext context) => false; + public GlVersion Version { get; } = new GlVersion(GlProfileType.OpenGLES, 3, 0); public GlInterface GlInterface { get; } public int SampleCount { get; } = 0; public int StencilSize { get; } = 9; } -} \ No newline at end of file +} diff --git a/src/iOS/Avalonia.iOS/EaglLayerSurface.cs b/src/iOS/Avalonia.iOS/EaglLayerSurface.cs index 64912b8ae3..5e5e1da949 100644 --- a/src/iOS/Avalonia.iOS/EaglLayerSurface.cs +++ b/src/iOS/Avalonia.iOS/EaglLayerSurface.cs @@ -2,6 +2,7 @@ using System; using System.Threading; using Avalonia.OpenGL; +using Avalonia.OpenGL.Surfaces; using CoreAnimation; using OpenTK.Graphics.ES30; @@ -91,4 +92,4 @@ namespace Avalonia.iOS } } } -} \ No newline at end of file +} diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index b484559ff3..28bccb6637 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -26,7 +26,7 @@ namespace Avalonia.iOS var keyboard = new KeyboardDevice(); var softKeyboard = new SoftKeyboardHelper(); AvaloniaLocator.CurrentMutable - .Bind().ToConstant(GlFeature) + .Bind().ToConstant(GlFeature) .Bind().ToConstant(new CursorFactoryStub()) .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToConstant(new ClipboardImpl()) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 81a8de1046..20172eea88 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -94,8 +94,8 @@ namespace Avalonia.Base.UnitTests Class1.FooProperty.Changed.Subscribe(e => raised = e.Property == Class1.FooProperty && - (string)e.OldValue == "initial" && - (string)e.NewValue == "newvalue" && + e.OldValue.GetValueOrDefault() == "initial" && + e.NewValue.GetValueOrDefault() == "newvalue" && e.Priority == BindingPriority.LocalValue); target.SetValue(Class1.FooProperty, "newvalue"); diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs index d7f927372e..8e5d8b7be2 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs @@ -83,7 +83,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); string value = null; - Class1.FooProperty.Changed.Subscribe(x => value = (string)x.NewValue); + Class1.FooProperty.Changed.Subscribe(x => value = x.NewValue.GetValueOrDefault()); target.SetValue(Class1.FooProperty, "newvalue"); Assert.Equal("newvalue", value); @@ -95,7 +95,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var result = new List(); - Class1.FooProperty.Changed.Subscribe(x => result.Add((string)x.NewValue)); + Class1.FooProperty.Changed.Subscribe(x => result.Add(x.NewValue.GetValueOrDefault())); target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); target.SetValue(Class1.FooProperty, "local"); diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index 57cea91834..3e78e951e2 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -363,6 +363,40 @@ namespace Avalonia.Controls.UnitTests }); } + [Fact] + public void Custom_TextSelector() + { + RunTest((control, textbox) => + { + object selectedItem = control.Items.Cast().First(); + string input = "42"; + + control.TextSelector = (text, item) => text + item; + Assert.Equal(control.TextSelector("4", "2"), "42"); + + control.Text = input; + control.SelectedItem = selectedItem; + Assert.Equal(control.Text, control.TextSelector(input, selectedItem.ToString())); + }); + } + + [Fact] + public void Custom_ItemSelector() + { + RunTest((control, textbox) => + { + object selectedItem = control.Items.Cast().First(); + string input = "42"; + + control.ItemSelector = (text, item) => text + item; + Assert.Equal(control.ItemSelector("4", 2), "42"); + + control.Text = input; + control.SelectedItem = selectedItem; + Assert.Equal(control.Text, control.ItemSelector(input, selectedItem)); + }); + } + /// /// Retrieves a defined predicate filter through a new AutoCompleteBox /// control instance. diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index d9176ca55d..53a8db2176 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -395,6 +395,53 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + [Fact] + public void Focusable_Controls_In_Popup_Should_Get_Focus() + { + using (CreateServicesWithFocus()) + { + var window = PreparedWindow(); + + var tb = new TextBox(); + var b = new Button(); + var p = new Popup + { + PlacementTarget = window, + Child = new StackPanel + { + Children = + { + tb, + b + } + } + }; + ((ISetLogicalParent)p).SetParent(p.PlacementTarget); + window.Show(); + + p.Open(); + + if(p.Host is OverlayPopupHost host) + { + //Need to measure/arrange for visual children to show up + //in OverlayPopupHost + host.Measure(Size.Infinity); + host.Arrange(new Rect(host.DesiredSize)); + } + + tb.Focus(); + + Assert.True(FocusManager.Instance?.Current == tb); + + //Ensure focus remains in the popup + var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next); + + Assert.True(nextFocus == b); + + p.Close(); + } + } + private IDisposable CreateServices() { return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: @@ -407,6 +454,21 @@ namespace Avalonia.Controls.UnitTests.Primitives }))); } + private IDisposable CreateServicesWithFocus() + { + return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: + new MockWindowingPlatform(null, + x => + { + if (UsePopupHost) + return null; + return MockWindowingPlatform.CreatePopupMock(x).Object; + }), + focusManager: new FocusManager(), + keyboardDevice: () => new KeyboardDevice())); + } + + private PointerPressedEventArgs CreatePointerPressedEventArgs(Window source, Point p) { var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index bf41381b52..f3e1c37705 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -369,7 +369,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var glyphRun = shapedRun.GlyphRun; - var width = glyphRun.Bounds.Width; + var width = glyphRun.Size.Width; var characterHit = glyphRun.GetCharacterHitFromDistance(width, out _);