From 9cea09caa2b24097c9680b424c43ce44e4116f07 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Mon, 4 Jul 2022 14:58:25 +0200 Subject: [PATCH 01/97] Fix Intersect missing nullable return type --- src/Avalonia.Base/Platform/IGeometryImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Platform/IGeometryImpl.cs b/src/Avalonia.Base/Platform/IGeometryImpl.cs index c80f8923ef..8760f9ed8a 100644 --- a/src/Avalonia.Base/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Base/Platform/IGeometryImpl.cs @@ -39,7 +39,7 @@ namespace Avalonia.Platform /// /// The other geometry. /// A new representing the intersection. - IGeometryImpl Intersect(IGeometryImpl geometry); + IGeometryImpl? Intersect(IGeometryImpl geometry); /// /// Indicates whether the geometry's stroke contains the specified point. From 14a7ee7e319b7a26d156770fc2b6c0ccb5243a14 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Mon, 4 Jul 2022 15:07:23 +0200 Subject: [PATCH 02/97] Extend docs --- src/Avalonia.Base/Platform/IGeometryImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Platform/IGeometryImpl.cs b/src/Avalonia.Base/Platform/IGeometryImpl.cs index 8760f9ed8a..5826cfb2ff 100644 --- a/src/Avalonia.Base/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Base/Platform/IGeometryImpl.cs @@ -38,7 +38,7 @@ namespace Avalonia.Platform /// Intersects the geometry with another geometry. /// /// The other geometry. - /// A new representing the intersection. + /// A new representing the intersection or null when the operation failed. IGeometryImpl? Intersect(IGeometryImpl geometry); /// From 9ab31f60b40d676fbe1a876dbf70a5e498904ebf Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 5 Jul 2022 17:17:51 +0200 Subject: [PATCH 03/97] Fix button flyout toggle Expose OverlayDismissEventPassThrough on FlyoutBase --- src/Avalonia.Controls/Button.cs | 56 ++++++++++++++------- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 48 ++++++++++++++---- src/Avalonia.Controls/Primitives/Popup.cs | 2 +- 3 files changed, 77 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 6dba33516b..8e5d4e1e06 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Windows.Input; using Avalonia.Automation.Peers; @@ -281,24 +282,29 @@ namespace Avalonia.Controls /// protected override void OnKeyDown(KeyEventArgs e) { - if (e.Key == Key.Enter) + switch (e.Key) { - OnClick(); - e.Handled = true; - } - else if (e.Key == Key.Space) - { - if (ClickMode == ClickMode.Press) - { + case Key.Enter: OnClick(); + e.Handled = true; + break; + + case Key.Space: + { + if (ClickMode == ClickMode.Press) + { + OnClick(); + } + + IsPressed = true; + e.Handled = true; + break; } - IsPressed = true; - e.Handled = true; - } - else if (e.Key == Key.Escape && Flyout != null) - { - // If Flyout doesn't have focusable content, close the flyout here - Flyout.Hide(); + + case Key.Escape when Flyout != null: + // If Flyout doesn't have focusable content, close the flyout here + CloseFlyout(); + break; } base.OnKeyDown(e); @@ -327,7 +333,14 @@ namespace Avalonia.Controls { if (IsEffectivelyEnabled) { - OpenFlyout(); + if (_isFlyoutOpen) + { + CloseFlyout(); + } + else + { + OpenFlyout(); + } var e = new RoutedEventArgs(ClickEvent); RaiseEvent(e); @@ -348,6 +361,14 @@ namespace Avalonia.Controls Flyout?.ShowAt(this); } + /// + /// Closes the button's flyout. + /// + protected virtual void CloseFlyout() + { + Flyout?.Hide(); + } + /// /// Invoked when the button's flyout is opened. /// @@ -494,8 +515,7 @@ namespace Avalonia.Controls // If flyout is changed while one is already open, make sure we // close the old one first - if (oldFlyout != null && - oldFlyout.IsOpen) + if (oldFlyout != null && oldFlyout.IsOpen) { oldFlyout.Hide(); } diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 1504d2b25f..a0f3407b7a 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -12,11 +12,6 @@ namespace Avalonia.Controls.Primitives { public abstract class FlyoutBase : AvaloniaObject, IPopupHostProvider { - static FlyoutBase() - { - Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged); - } - /// /// Defines the property /// @@ -49,6 +44,12 @@ namespace Avalonia.Controls.Primitives public static readonly AttachedProperty AttachedFlyoutProperty = AvaloniaProperty.RegisterAttached("AttachedFlyout", null); + /// + /// Defines the OverlayDismissEventPassThrough property + /// + public static readonly StyledProperty OverlayDismissEventPassThroughProperty = + Popup.OverlayDismissEventPassThroughProperty.AddOwner(); + private readonly Lazy _popupLazy; private bool _isOpen; private Control? _target; @@ -58,6 +59,12 @@ namespace Avalonia.Controls.Primitives private IDisposable? _transientDisposable; private Action? _popupHostChangedHandler; + static FlyoutBase() + { + OverlayDismissEventPassThroughProperty.OverrideDefaultValue(true); + Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged); + } + public FlyoutBase() { _popupLazy = new Lazy(() => CreatePopup()); @@ -101,6 +108,21 @@ namespace Avalonia.Controls.Primitives private set => SetAndRaise(TargetProperty, ref _target, value); } + /// + /// Gets or sets a value indicating whether the event that closes the flyout is passed + /// through to the parent window. + /// + /// + /// Clicks outside the the flyout cause the flyout to close. When is set to + /// false, these clicks will be handled by the flyout and not be registered by the parent + /// window. When set to true, the events will be passed through to the parent window. + /// + public bool OverlayDismissEventPassThrough + { + get => GetValue(OverlayDismissEventPassThroughProperty); + set => SetValue(OverlayDismissEventPassThroughProperty, value); + } + IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host; event Action? IPopupHostProvider.PopupHostChanged @@ -175,6 +197,8 @@ namespace Avalonia.Controls.Primitives IsOpen = false; Popup.IsOpen = false; + Popup.OverlayInputPassThroughElement = null; + ((ISetLogicalParent)Popup).SetParent(null); // Ensure this isn't active @@ -231,6 +255,9 @@ namespace Avalonia.Controls.Primitives Popup.Child = CreatePresenter(); } + Popup.OverlayInputPassThroughElement = placementTarget; + Popup.OverlayDismissEventPassThrough = OverlayDismissEventPassThrough; + if (CancelOpening()) { return false; @@ -356,10 +383,11 @@ namespace Avalonia.Controls.Primitives private Popup CreatePopup() { - var popup = new Popup(); - popup.WindowManagerAddShadowHint = false; - popup.IsLightDismissEnabled = true; - popup.OverlayDismissEventPassThrough = true; + var popup = new Popup + { + WindowManagerAddShadowHint = false, + IsLightDismissEnabled = true + }; popup.Opened += OnPopupOpened; popup.Closed += OnPopupClosed; @@ -372,7 +400,7 @@ namespace Avalonia.Controls.Primitives { IsOpen = true; - _popupHostChangedHandler?.Invoke(Popup!.Host); + _popupHostChangedHandler?.Invoke(Popup.Host); } private void OnPopupClosing(object? sender, CancelEventArgs e) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 1501d97470..3573ad9aaa 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -501,7 +501,7 @@ namespace Avalonia.Controls.Primitives if (dismissLayer != null) { dismissLayer.IsVisible = true; - dismissLayer.InputPassThroughElement = _overlayInputPassThroughElement; + dismissLayer.InputPassThroughElement = OverlayInputPassThroughElement; Disposable.Create(() => { From 97a5a9e1f64110d01b8d8bf3937a3518f6fd567b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 6 Jul 2022 14:24:55 -0400 Subject: [PATCH 04/97] Add folder.GetItemsAsync API --- .../ControlCatalog/Pages/DialogsPage.xaml.cs | 16 ++++++- .../Platform/Storage/AndroidStorageItem.cs | 25 +++++++++++ .../Storage/FileIO/BclStorageFolder.cs | 12 +++++ .../Platform/Storage/IStorageFolder.cs | 11 ++++- .../Interop/Storage/StorageProviderInterop.cs | 25 +++++++++++ .../Interop/Typescript/StorageProvider.ts | 45 +++++++++++++------ .../Avalonia.iOS/Storage/IOSStorageItem.cs | 17 +++++++ 7 files changed, 134 insertions(+), 17 deletions(-) diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index f7b6db1255..e13c2052eb 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -243,8 +243,8 @@ namespace ControlCatalog.Pages async Task SetPickerResult(IReadOnlyCollection? items) { items ??= Array.Empty(); - var mappedResults = items.Select(FullPathOrName).ToList(); bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmark() : "Can't bookmark"; + var mappedResults = new List(); if (items.FirstOrDefault() is IStorageItem item) { @@ -293,7 +293,19 @@ Content: lastSelectedDirectory = await item.GetParentAsync(); if (lastSelectedDirectory is not null) { - mappedResults.Insert(0, "Parent: " + FullPathOrName(lastSelectedDirectory)); + mappedResults.Add(FullPathOrName(lastSelectedDirectory)); + } + + foreach (var selectedItem in items) + { + mappedResults.Add("+> " + FullPathOrName(selectedItem)); + if (selectedItem is IStorageFolder folder) + { + foreach (var innerItems in await folder.GetItemsAsync()) + { + mappedResults.Add("++> " + FullPathOrName(innerItems)); + } + } } } diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs index 50581d47b1..1e81642e15 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -106,6 +107,30 @@ internal sealed class AndroidStorageFolder : AndroidStorageItem, IStorageBookmar { return Task.FromResult(new StorageItemProperties()); } + + public async Task> GetItemsAsync() + { + using var javaFile = new JavaFile(Uri.Path!); + + // Java file represents files AND directories. Don't be confused. + var files = await javaFile.ListFilesAsync().ConfigureAwait(false); + if (files is null) + { + return Array.Empty(); + } + + return files + .Select(f => (file: f, uri: AndroidUri.FromFile(f))) + .Where(t => t.uri is not null) + .Select(t => t.file switch + { + { IsFile: true } => (IStorageItem)new AndroidStorageFile(Context, t.uri!), + { IsDirectory: true } => new AndroidStorageFolder(Context, t.uri!), + _ => null + }) + .Where(i => i is not null) + .ToArray()!; + } } internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkFile diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs index 7267017eaf..0a22f4bd03 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using System.Security; using System.Threading.Tasks; using Avalonia.Metadata; @@ -43,6 +45,16 @@ public class BclStorageFolder : IStorageBookmarkFolder return Task.FromResult(null); } + public Task> GetItemsAsync() + { + var items = _directoryInfo.GetDirectories() + .Select(d => (IStorageItem)new BclStorageFolder(d)) + .Concat(_directoryInfo.GetFiles().Select(f => new BclStorageFile(f))) + .ToArray(); + + return Task.FromResult>(items); + } + public virtual Task SaveBookmark() { return Task.FromResult(_directoryInfo.FullName); diff --git a/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs index 25b9f01a92..0ffb9f41c6 100644 --- a/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs +++ b/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs @@ -1,4 +1,6 @@ -using Avalonia.Metadata; +using System.Collections.Generic; +using System.Threading.Tasks; +using Avalonia.Metadata; namespace Avalonia.Platform.Storage; @@ -8,4 +10,11 @@ namespace Avalonia.Platform.Storage; [NotClientImplementable] public interface IStorageFolder : IStorageItem { + /// + /// Gets the files and subfolders in the current folder. + /// + /// + /// When this method completes successfully, it returns a list of the files and folders in the current folder. Each item in the list is represented by an implementation object. + /// + Task> GetItemsAsync(); } diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs index 14dc53d7b5..129463774c 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs @@ -196,5 +196,30 @@ namespace Avalonia.Web.Blazor.Interop.Storage public JSStorageFolder(IJSInProcessObjectReference fileHandle) : base(fileHandle) { } + + public async Task> GetItemsAsync() + { + var items = await FileHandle.InvokeAsync("getItems"); + if (items is null) + { + return Array.Empty(); + } + + var count = items.Invoke("count"); + + return Enumerable.Range(0, count) + .Select(index => + { + var reference = items.Invoke("at", index); + return reference.Invoke("getKind") switch + { + "directory" => (IStorageItem)new JSStorageFolder(reference), + "file" => new JSStorageFile(reference), + _ => null + }; + }) + .Where(i => i is not null) + .ToArray()!; + } } } diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Typescript/StorageProvider.ts b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/StorageProvider.ts index c32eef3226..aee74b9067 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/Typescript/StorageProvider.ts +++ b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/StorageProvider.ts @@ -14,6 +14,8 @@ declare global { queryPermission(options?: { mode: PermissionsMode }): Promise<"granted" | "denied" | "prompt">; requestPermission(options?: { mode: PermissionsMode }): Promise<"granted" | "denied" | "prompt">; + + entries(): AsyncIterableIterator<[string, FileSystemFileHandle]>; } type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; type StartInDirectory = WellKnownDirectory | FileSystemFileHandle; @@ -53,7 +55,7 @@ class IndexedDbWrapper { } public connect(): Promise { - var conn = window.indexedDB.open(this.databaseName, 1); + const conn = window.indexedDB.open(this.databaseName, 1); conn.onupgradeneeded = event => { const db = (>event.target).result; @@ -85,7 +87,7 @@ class InnerDbConnection { const os = this.openStore(store, "readwrite"); return new Promise((resolve, reject) => { - var response = os.put(obj, key); + const response = os.put(obj, key); response.onsuccess = () => { resolve(response.result); }; @@ -99,7 +101,7 @@ class InnerDbConnection { const os = this.openStore(store, "readonly"); return new Promise((resolve, reject) => { - var response = os.get(key); + const response = os.get(key); response.onsuccess = () => { resolve(response.result); }; @@ -113,7 +115,7 @@ class InnerDbConnection { const os = this.openStore(store, "readwrite"); return new Promise((resolve, reject) => { - var response = os.delete(key); + const response = os.delete(key); response.onsuccess = () => { resolve(); }; @@ -134,17 +136,20 @@ const avaloniaDb = new IndexedDbWrapper("AvaloniaDb", [ ]) class StorageItem { - constructor(private handle: FileSystemFileHandle, private bookmarkId?: string) { } + constructor(public handle: FileSystemFileHandle, private bookmarkId?: string) { } public getName(): string { return this.handle.name } + public getKind(): string { + return this.handle.kind; + } + public async openRead(): Promise { await this.verityPermissions('read'); - var file = await this.handle.getFile(); - return file; + return await this.handle.getFile(); } public async openWrite(): Promise { @@ -154,7 +159,7 @@ class StorageItem { } public async getProperties(): Promise<{ Size: number, LastModified: number, Type: string }> { - var file = this.handle.getFile && await this.handle.getFile(); + const file = this.handle.getFile && await this.handle.getFile(); return file && { Size: file.size, @@ -163,6 +168,18 @@ class StorageItem { } } + public async getItems(): Promise { + if (this.handle.kind !== "directory"){ + return new StorageItems([]); + } + + const items: StorageItem[] = []; + for await (const [key, value] of this.handle.entries()) { + items.push(new StorageItem(value)); + } + return new StorageItems(items); + } + private async verityPermissions(mode: PermissionsMode): Promise { if (await this.handle.queryPermission({ mode }) === 'granted') { return; @@ -235,12 +252,12 @@ export class StorageProvider { } public static async selectFolderDialog( - startIn: StartInDirectory | null) + startIn: StorageItem | null) : Promise { // 'Picker' API doesn't accept "null" as a parameter, so it should be set to undefined. const options: DirectoryPickerOptions = { - startIn: (startIn || undefined) + startIn: (startIn?.handle || undefined) }; const handle = await window.showDirectoryPicker(options); @@ -248,12 +265,12 @@ export class StorageProvider { } public static async openFileDialog( - startIn: StartInDirectory | null, multiple: boolean, + startIn: StorageItem | null, multiple: boolean, types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean) : Promise { const options: OpenFilePickerOptions = { - startIn: (startIn || undefined), + startIn: (startIn?.handle || undefined), multiple, excludeAcceptAllOption, types: (types || undefined) @@ -264,12 +281,12 @@ export class StorageProvider { } public static async saveFileDialog( - startIn: StartInDirectory | null, suggestedName: string | null, + startIn: StorageItem | null, suggestedName: string | null, types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean) : Promise { const options: SaveFilePickerOptions = { - startIn: (startIn || undefined), + startIn: (startIn?.handle || undefined), suggestedName: (suggestedName || undefined), excludeAcceptAllOption, types: (types || undefined) diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs index 6fb296d0e0..cfb2a497be 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using System.Threading.Tasks; using Avalonia.Logging; using Avalonia.Platform.Storage; @@ -118,4 +120,19 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder public IOSStorageFolder(NSUrl url) : base(url) { } + + public Task> GetItemsAsync() + { + var content = NSFileManager.DefaultManager.GetDirectoryContent(Url, null, NSDirectoryEnumerationOptions.None, out var error); + if (error is not null) + { + return Task.FromException>(new NSErrorException(error)); + } + + var items = content + .Select(u => u.HasDirectoryPath ? (IStorageItem)new IOSStorageFolder(u) : new IOSStorageFile(u)) + .ToArray(); + + return Task.FromResult>(items); + } } From 5a8c9f9c0904c83095291d9ae0c02abd8a9d259f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 6 Jul 2022 14:31:00 -0400 Subject: [PATCH 05/97] Use Async in naming --- samples/ControlCatalog/Pages/DialogsPage.xaml.cs | 10 +++++----- .../Platform/Storage/AndroidStorageItem.cs | 8 ++++---- .../Platform/Storage/FileIO/BclStorageFile.cs | 8 ++++---- .../Platform/Storage/FileIO/BclStorageFolder.cs | 4 ++-- .../Platform/Storage/IStorageBookmarkItem.cs | 2 +- src/Avalonia.Base/Platform/Storage/IStorageFile.cs | 4 ++-- src/Avalonia.Base/Platform/Storage/IStorageItem.cs | 2 +- .../Interop/Storage/StorageProviderInterop.cs | 8 ++++---- src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs | 8 ++++---- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index e13c2052eb..036dccde0e 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -195,10 +195,10 @@ namespace ControlCatalog.Pages { // Sync disposal of StreamWriter is not supported on WASM #if NET6_0_OR_GREATER - await using var stream = await file.OpenWrite(); + await using var stream = await file.OpenWriteAsync(); await using var reader = new System.IO.StreamWriter(stream); #else - using var stream = await file.OpenWrite(); + using var stream = await file.OpenWriteAsync(); using var reader = new System.IO.StreamWriter(stream); #endif await reader.WriteLineAsync(openedFileContent.Text); @@ -243,7 +243,7 @@ namespace ControlCatalog.Pages async Task SetPickerResult(IReadOnlyCollection? items) { items ??= Array.Empty(); - bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmark() : "Can't bookmark"; + bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmarkAsync() : "Can't bookmark"; var mappedResults = new List(); if (items.FirstOrDefault() is IStorageItem item) @@ -267,9 +267,9 @@ Content: if (file.CanOpenRead) { #if NET6_0_OR_GREATER - await using var stream = await file.OpenRead(); + await using var stream = await file.OpenReadAsync(); #else - using var stream = await file.OpenRead(); + using var stream = await file.OpenReadAsync(); #endif using var reader = new System.IO.StreamReader(stream); diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs index 1e81642e15..a9b2e16d43 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs @@ -36,13 +36,13 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem public bool CanBookmark => true; - public Task SaveBookmark() + public Task SaveBookmarkAsync() { Context.ContentResolver?.TakePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission); return Task.FromResult(Uri.ToString()); } - public Task ReleaseBookmark() + public Task ReleaseBookmarkAsync() { Context.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission); return Task.CompletedTask; @@ -143,10 +143,10 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF public bool CanOpenWrite => true; - public Task OpenRead() => Task.FromResult(OpenContentStream(Context, Uri, false) + public Task OpenReadAsync() => Task.FromResult(OpenContentStream(Context, Uri, false) ?? throw new InvalidOperationException("Failed to open content stream")); - public Task OpenWrite() => Task.FromResult(OpenContentStream(Context, Uri, true) + public Task OpenWriteAsync() => Task.FromResult(OpenContentStream(Context, Uri, true) ?? throw new InvalidOperationException("Failed to open content stream")); private Stream? OpenContentStream(Context context, AndroidUri uri, bool isOutput) diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs index 5af02219ce..cf21e9b8b5 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs @@ -47,22 +47,22 @@ public class BclStorageFile : IStorageBookmarkFile return Task.FromResult(null); } - public Task OpenRead() + public Task OpenReadAsync() { return Task.FromResult(_fileInfo.OpenRead()); } - public Task OpenWrite() + public Task OpenWriteAsync() { return Task.FromResult(_fileInfo.OpenWrite()); } - public virtual Task SaveBookmark() + public virtual Task SaveBookmarkAsync() { return Task.FromResult(_fileInfo.FullName); } - public Task ReleaseBookmark() + public Task ReleaseBookmarkAsync() { // No-op return Task.CompletedTask; diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs index 0a22f4bd03..cd6c8be1ae 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs @@ -55,12 +55,12 @@ public class BclStorageFolder : IStorageBookmarkFolder return Task.FromResult>(items); } - public virtual Task SaveBookmark() + public virtual Task SaveBookmarkAsync() { return Task.FromResult(_directoryInfo.FullName); } - public Task ReleaseBookmark() + public Task ReleaseBookmarkAsync() { // No-op return Task.CompletedTask; diff --git a/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs b/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs index d21c950862..40f2720ee8 100644 --- a/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs +++ b/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs @@ -6,7 +6,7 @@ namespace Avalonia.Platform.Storage; [NotClientImplementable] public interface IStorageBookmarkItem : IStorageItem { - Task ReleaseBookmark(); + Task ReleaseBookmarkAsync(); } [NotClientImplementable] diff --git a/src/Avalonia.Base/Platform/Storage/IStorageFile.cs b/src/Avalonia.Base/Platform/Storage/IStorageFile.cs index 965caf8216..46aa6efa72 100644 --- a/src/Avalonia.Base/Platform/Storage/IStorageFile.cs +++ b/src/Avalonia.Base/Platform/Storage/IStorageFile.cs @@ -18,7 +18,7 @@ public interface IStorageFile : IStorageItem /// /// Opens a stream for read access. /// - Task OpenRead(); + Task OpenReadAsync(); /// /// Returns true, if file is writeable. @@ -28,5 +28,5 @@ public interface IStorageFile : IStorageItem /// /// Opens stream for writing to the file. /// - Task OpenWrite(); + Task OpenWriteAsync(); } diff --git a/src/Avalonia.Base/Platform/Storage/IStorageItem.cs b/src/Avalonia.Base/Platform/Storage/IStorageItem.cs index 8513ebc7d9..f5469d31c9 100644 --- a/src/Avalonia.Base/Platform/Storage/IStorageItem.cs +++ b/src/Avalonia.Base/Platform/Storage/IStorageItem.cs @@ -44,7 +44,7 @@ public interface IStorageItem : IDisposable /// /// Returns identifier of a bookmark. Can be null if OS denied request. /// - Task SaveBookmark(); + Task SaveBookmarkAsync(); /// /// Gets the parent folder of the current storage item. diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs index 129463774c..2bc46e97b5 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs @@ -145,7 +145,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage public bool CanBookmark => true; - public Task SaveBookmark() + public Task SaveBookmarkAsync() { return FileHandle.InvokeAsync("saveBookmark").AsTask(); } @@ -155,7 +155,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage return Task.FromResult(null); } - public Task ReleaseBookmark() + public Task ReleaseBookmarkAsync() { return FileHandle.InvokeAsync("deleteBookmark").AsTask(); } @@ -174,7 +174,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage } public bool CanOpenRead => true; - public async Task OpenRead() + public async Task OpenReadAsync() { var stream = await FileHandle.InvokeAsync("openRead"); // Remove maxAllowedSize limit, as developer can decide if they read only small part or everything. @@ -182,7 +182,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage } public bool CanOpenWrite => true; - public async Task OpenWrite() + public async Task OpenWriteAsync() { var properties = await FileHandle.InvokeAsync("getProperties"); var streamWriter = await FileHandle.InvokeAsync("openWrite"); diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs index cfb2a497be..a801e83562 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs @@ -51,13 +51,13 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem return Task.FromResult(new IOSStorageFolder(Url.RemoveLastPathComponent())); } - public Task ReleaseBookmark() + public Task ReleaseBookmarkAsync() { // no-op return Task.CompletedTask; } - public Task SaveBookmark() + public Task SaveBookmarkAsync() { try { @@ -104,12 +104,12 @@ internal sealed class IOSStorageFile : IOSStorageItem, IStorageBookmarkFile public bool CanOpenWrite => true; - public Task OpenRead() + public Task OpenReadAsync() { return Task.FromResult(new IOSSecurityScopedStream(Url, FileAccess.Read)); } - public Task OpenWrite() + public Task OpenWriteAsync() { return Task.FromResult(new IOSSecurityScopedStream(Url, FileAccess.Write)); } From 7cbeb7ad3f4aac3412efd662d5dac4f6f984cdbb Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 8 Jul 2022 18:56:29 +0200 Subject: [PATCH 06/97] Rework TextLineImpl hit testing --- .../ControlCatalog/Pages/TextBlockPage.xaml | 2 +- samples/Sandbox/MainWindow.axaml | 1 + samples/Sandbox/Sandbox.csproj | 1 + src/Avalonia.Base/Media/GlyphRun.cs | 109 ++++- .../Media/TextFormatting/TextLineImpl.cs | 371 +++++++++--------- .../Media/TextFormatting/TextLayoutTests.cs | 84 +++- 6 files changed, 366 insertions(+), 202 deletions(-) diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml index cb49ba96c6..32914428ed 100644 --- a/samples/ControlCatalog/Pages/TextBlockPage.xaml +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml @@ -118,7 +118,7 @@ - + This is a TextBlock with several diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index 6929f192c7..0c5a7a11e3 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,4 +1,5 @@ + diff --git a/samples/Sandbox/Sandbox.csproj b/samples/Sandbox/Sandbox.csproj index eab654acb6..20c7f29201 100644 --- a/samples/Sandbox/Sandbox.csproj +++ b/samples/Sandbox/Sandbox.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index ac87d521a5..703b56b0e8 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -614,17 +614,29 @@ namespace Avalonia.Media private GlyphRunMetrics CreateGlyphRunMetrics() { + var firstCluster = 0; + var lastCluster = Characters.Length - 1; + + if (!IsLeftToRight) + { + var cluster = firstCluster; + firstCluster = lastCluster; + lastCluster = cluster; + } + if (GlyphClusters != null && GlyphClusters.Count > 0) { - var firstCluster = GlyphClusters[0]; + firstCluster = GlyphClusters[0]; + lastCluster = GlyphClusters[GlyphClusters.Count - 1]; _offsetToFirstCharacter = Math.Max(0, Characters.Start - firstCluster); } + var isReversed = firstCluster > lastCluster; var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; var widthIncludingTrailingWhitespace = 0d; - var trailingWhitespaceLength = GetTrailingWhitespaceLength(out var newLineLength, out var glyphCount); + var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount); for (var index = 0; index < GlyphIndices.Count; index++) { @@ -635,16 +647,16 @@ namespace Avalonia.Media var width = widthIncludingTrailingWhitespace; - if (IsLeftToRight) + if (isReversed) { - for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++) + for (var index = 0; index < glyphCount; index++) { width -= GetGlyphAdvance(index, out _); - } + } } else { - for (var index = 0; index < glyphCount; index++) + for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++) { width -= GetGlyphAdvance(index, out _); } @@ -654,16 +666,15 @@ namespace Avalonia.Media height); } - private int GetTrailingWhitespaceLength(out int newLineLength, out int glyphCount) - { - glyphCount = 0; - newLineLength = 0; - - if (Characters.IsEmpty) + private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount) + { + if (isReversed) { - return 0; + return GetTralingWhitespaceLengthRightToLeft(out newLineLength, out glyphCount); } + glyphCount = 0; + newLineLength = 0; var trailingWhitespaceLength = 0; if (GlyphClusters == null) @@ -732,6 +743,78 @@ namespace Avalonia.Media return trailingWhitespaceLength; } + private int GetTralingWhitespaceLengthRightToLeft(out int newLineLength, out int glyphCount) + { + glyphCount = 0; + newLineLength = 0; + var trailingWhitespaceLength = 0; + + if (GlyphClusters == null) + { + for (var i = 0; i < Characters.Length;) + { + var codepoint = Codepoint.ReadAt(_characters, i, out var count); + + if (!codepoint.IsWhiteSpace) + { + break; + } + + if (codepoint.IsBreakChar) + { + newLineLength++; + } + + trailingWhitespaceLength++; + + i += count; + glyphCount++; + } + } + else + { + for (var i = 0; i < GlyphClusters.Count; i++) + { + var currentCluster = GlyphClusters[i]; + var characterIndex = Math.Max(0, currentCluster - _characters.BufferOffset); + var codepoint = Codepoint.ReadAt(_characters, characterIndex, out _); + + if (!codepoint.IsWhiteSpace) + { + break; + } + + var clusterLength = 1; + + while (i - 1 >= 0) + { + var nextCluster = GlyphClusters[i - 1]; + + if (currentCluster == nextCluster) + { + clusterLength++; + i--; + + continue; + } + + break; + } + + if (codepoint.IsBreakChar) + { + newLineLength += clusterLength; + } + + trailingWhitespaceLength += clusterLength; + + glyphCount++; + } + } + + return trailingWhitespaceLength; + } + private void Set(ref T field, T value) { _glyphRunImpl?.Dispose(); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 7c686358e2..f4a0324d90 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -166,58 +166,74 @@ namespace Avalonia.Media.TextFormatting if (distance <= 0) { - // hit happens before the line, return the first position var firstRun = _textRuns[0]; - if (firstRun is ShapedTextCharacters shapedTextCharacters) - { - return shapedTextCharacters.GlyphRun.GetCharacterHitFromDistance(distance, out _); - } + return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0); + } - return _resolvedFlowDirection == FlowDirection.LeftToRight ? - new CharacterHit(FirstTextSourceIndex) : - new CharacterHit(FirstTextSourceIndex + Length); + if (distance > WidthIncludingTrailingWhitespace) + { + var lastRun = _textRuns[_textRuns.Count - 1]; + + return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.TextSourceLength, lastRun.Size.Width); } // process hit that happens within the line var characterHit = new CharacterHit(); var currentPosition = FirstTextSourceIndex; + var currentDistance = 0.0; foreach (var currentRun in _textRuns) { - switch (currentRun) + if (currentDistance + currentRun.Size.Width < distance) { - case ShapedTextCharacters shapedRun: - { - characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); + currentDistance += currentRun.Size.Width; + currentPosition += currentRun.TextSourceLength; - var offset = Math.Max(0, currentPosition - shapedRun.Text.Start); + continue; + } - characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); + characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance); - break; - } - default: + break; + } + + return characterHit; + } + + private static CharacterHit GetRunCharacterHit(DrawableTextRun run, int currentPosition, double distance) + { + CharacterHit characterHit; + + switch (run) + { + case ShapedTextCharacters shapedRun: + { + characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); + + var offset = Math.Max(0, currentPosition - shapedRun.Text.Start); + + if (!shapedRun.GlyphRun.IsLeftToRight) { - if (distance < currentRun.Size.Width / 2) - { - characterHit = new CharacterHit(currentPosition); - } - else - { - characterHit = new CharacterHit(currentPosition, currentRun.TextSourceLength); - } - break; + offset = Math.Max(0, offset - shapedRun.Text.End); } - } - if (distance <= currentRun.Size.Width) - { - break; - } + characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); - distance -= currentRun.Size.Width; - currentPosition += currentRun.TextSourceLength; + break; + } + default: + { + if (distance < run.Size.Width / 2) + { + characterHit = new CharacterHit(currentPosition); + } + else + { + characterHit = new CharacterHit(currentPosition, run.TextSourceLength); + } + break; + } } return characterHit; @@ -226,136 +242,122 @@ namespace Avalonia.Media.TextFormatting /// public override double GetDistanceFromCharacterHit(CharacterHit characterHit) { - var isTrailingHit = characterHit.TrailingLength > 0; + var flowDirection = _paragraphProperties.FlowDirection; var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - var currentDistance = Start; var currentPosition = FirstTextSourceIndex; var remainingLength = characterIndex - FirstTextSourceIndex; - GlyphRun? lastRun = null; + var currentDistance = Start; - for (var index = 0; index < _textRuns.Count; index++) + if (flowDirection == FlowDirection.LeftToRight) + { + for (var index = 0; index < _textRuns.Count; index++) + { + var currentRun = _textRuns[index]; + + if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, + flowDirection, out var distance, out _)) + { + return currentDistance + distance; + } + + //No hit hit found so we add the full width + currentDistance += currentRun.Size.Width; + currentPosition += currentRun.TextSourceLength; + remainingLength -= currentRun.TextSourceLength; + } + } + else { - var textRun = _textRuns[index]; + currentDistance += WidthIncludingTrailingWhitespace; - switch (textRun) + for (var index = _textRuns.Count - 1; index >= 0; index--) { - case ShapedTextCharacters shapedTextCharacters: + var currentRun = _textRuns[index]; + + if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, + flowDirection, out var distance, out var currentGlyphRun)) + { + if (currentGlyphRun != null) { - var currentRun = shapedTextCharacters.GlyphRun; + distance = currentGlyphRun.Size.Width - distance; + } - if (lastRun != null) - { - if (!lastRun.IsLeftToRight && currentRun.IsLeftToRight && - currentRun.Characters.Start == characterHit.FirstCharacterIndex && - characterHit.TrailingLength == 0) - { - return currentDistance; - } - } + return currentDistance - distance; + } - //Look for a hit in within the current run - if (currentPosition + remainingLength <= currentPosition + textRun.Text.Length) - { - characterHit = new CharacterHit(textRun.Text.Start + remainingLength); + //No hit hit found so we add the full width + currentDistance -= currentRun.Size.Width; + currentPosition += currentRun.TextSourceLength; + remainingLength -= currentRun.TextSourceLength; + } + } - var distance = currentRun.GetDistanceFromCharacterHit(characterHit); + return currentDistance; + } - return currentDistance + distance; - } + private static bool TryGetDistanceFromCharacterHit( + DrawableTextRun currentRun, + CharacterHit characterHit, + int currentPosition, + int remainingLength, + FlowDirection flowDirection, + out double distance, + out GlyphRun? currentGlyphRun) + { + var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; + var isTrailingHit = characterHit.TrailingLength > 0; - //Look at the left and right edge of the current run - if (currentRun.IsLeftToRight) - { - if (_resolvedFlowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight)) - { - if (characterIndex <= currentPosition) - { - return currentDistance; - } - } - else - { - if (characterIndex == currentPosition) - { - return currentDistance; - } - } + distance = 0; + currentGlyphRun = null; - if (characterIndex == currentPosition + textRun.Text.Length && isTrailingHit) - { - return currentDistance + currentRun.Size.Width; - } - } - else - { - if (characterIndex == currentPosition) - { - return currentDistance + currentRun.Size.Width; - } - - var nextRun = index + 1 < _textRuns.Count ? - _textRuns[index + 1] as ShapedTextCharacters : - null; + switch (currentRun) + { + case ShapedTextCharacters shapedTextCharacters: + { + currentGlyphRun = shapedTextCharacters.GlyphRun; - if (nextRun != null) - { - if (nextRun.ShapedBuffer.IsLeftToRight) - { - if (characterIndex == currentPosition + textRun.Text.Length) - { - return currentDistance; - } - } - else - { - if (currentPosition + nextRun.Text.Length == characterIndex) - { - return currentDistance; - } - } - } - else - { - if (characterIndex > currentPosition + textRun.Text.Length) - { - return currentDistance; - } - } - } + if (currentPosition + remainingLength < currentPosition + currentRun.Text.Length) + { + characterHit = new CharacterHit(currentRun.Text.Start + remainingLength); - lastRun = currentRun; + distance = currentGlyphRun.GetDistanceFromCharacterHit(characterHit); - break; + return true; } - default: + + if (currentPosition + remainingLength == currentPosition + currentRun.Text.Length && isTrailingHit) { - if (characterIndex == currentPosition) + if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft) { - return currentDistance; + distance = currentGlyphRun.Size.Width; } - if (characterIndex == currentPosition + textRun.TextSourceLength) - { - return currentDistance + textRun.Size.Width; - } + return true; + } - break; + break; + } + default: + { + if (characterIndex == currentPosition) + { + return true; } - } - //No hit hit found so we add the full width - currentDistance += textRun.Size.Width; - currentPosition += textRun.TextSourceLength; - remainingLength -= textRun.TextSourceLength; + if (characterIndex == currentPosition + currentRun.TextSourceLength) + { + distance = currentRun.Size.Width; - if (remainingLength <= 0) - { - break; - } + return true; + + } + + break; + } } - return currentDistance; + return false; } /// @@ -460,20 +462,33 @@ namespace Avalonia.Media.TextFormatting var startIndex = currentRun.Text.Start + offset; - var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( - currentShapedRun.ShapedBuffer.IsLeftToRight ? - new CharacterHit(startIndex + remainingLength) : - new CharacterHit(startIndex)); + double startOffset; + double endOffset; - endX += endOffset; + if (currentShapedRun.ShapedBuffer.IsLeftToRight) + { + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + } + else + { + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); - var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( - currentShapedRun.ShapedBuffer.IsLeftToRight ? - new CharacterHit(startIndex) : - new CharacterHit(startIndex + remainingLength)); + if (currentPosition < startIndex) + { + startOffset = endOffset; + } + else + { + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + } + } startX += startOffset; + endX += endOffset; + var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); @@ -504,7 +519,7 @@ namespace Avalonia.Media.TextFormatting } //Lines that only contain a linebreak need to be covered here - if(characterLength == 0) + if (characterLength == 0) { characterLength = NewLineLength; } @@ -532,19 +547,9 @@ namespace Avalonia.Media.TextFormatting currentWidth += runwidth; currentPosition += characterLength; - if (currentDirection == FlowDirection.LeftToRight) + if (currentPosition > characterIndex) { - if (currentPosition > characterIndex) - { - break; - } - } - else - { - if (currentPosition <= firstTextSourceIndex) - { - break; - } + break; } startX = endX; @@ -571,7 +576,7 @@ namespace Avalonia.Media.TextFormatting var currentPosition = FirstTextSourceIndex; var remainingLength = textLength; - var startX = Start + WidthIncludingTrailingWhitespace; + var startX = WidthIncludingTrailingWhitespace; double currentWidth = 0; var currentRect = Rect.Empty; @@ -582,7 +587,7 @@ namespace Avalonia.Media.TextFormatting continue; } - if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex) + if (currentPosition + currentRun.TextSourceLength < firstTextSourceIndex) { startX -= currentRun.Size.Width; @@ -601,20 +606,31 @@ namespace Avalonia.Media.TextFormatting currentPosition += offset; var startIndex = currentRun.Text.Start + offset; + double startOffset; + double endOffset; - var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( - currentShapedRun.ShapedBuffer.IsLeftToRight ? - new CharacterHit(startIndex + remainingLength) : - new CharacterHit(startIndex)); + if (currentShapedRun.ShapedBuffer.IsLeftToRight) + { + if (currentPosition < startIndex) + { + startOffset = endOffset = 0; + } + else + { + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); - endX += endOffset - currentShapedRun.Size.Width; + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + } + } + else + { + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); - var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( - currentShapedRun.ShapedBuffer.IsLeftToRight ? - new CharacterHit(startIndex) : - new CharacterHit(startIndex + remainingLength)); + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + } - startX += startOffset - currentShapedRun.Size.Width; + startX -= currentRun.Size.Width - startOffset; + endX -= currentRun.Size.Width - endOffset; var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); @@ -652,9 +668,10 @@ namespace Avalonia.Media.TextFormatting } var runWidth = endX - startX; - var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); - if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) + var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); + + if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX)) { currentRect = currentRect.WithWidth(currentWidth + runWidth); @@ -674,19 +691,9 @@ namespace Avalonia.Media.TextFormatting currentWidth += runWidth; currentPosition += characterLength; - if (currentDirection == FlowDirection.LeftToRight) + if (currentPosition > characterIndex) { - if (currentPosition > characterIndex) - { - break; - } - } - else - { - if (currentPosition <= firstTextSourceIndex) - { - break; - } + break; } lastDirection = currentDirection; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 7e1103d624..631d0881b0 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -154,7 +154,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { j += inner.Current.Text.Length; - if(j + i > text.Length) + if (j + i > text.Length) { break; } @@ -738,7 +738,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = layout.TextLines[0]; var start = textLine.GetDistanceFromCharacterHit(new CharacterHit(5, 1)); - + var end = textLine.GetDistanceFromCharacterHit(new CharacterHit(6, 1)); var rects = layout.HitTestTextRange(0, 7).ToArray(); @@ -746,7 +746,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(1, rects.Length); var expected = rects[0]; - + Assert.Equal(expected.Left, start); Assert.Equal(expected.Right, end); } @@ -818,11 +818,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var expected = text.Substring(textLine.FirstTextSourceIndex, textLine.Length); Assert.Equal(expected, actual); - } + } } } } - + [Fact] public void Should_Layout_Empty_String() { @@ -833,11 +833,83 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Typeface.Default, 12, Brushes.Black); - + Assert.True(layout.Bounds.Height > 0); } } + [Fact] + public void Should_HitTestPoint_RightToLeft() + { + using (Start()) + { + var text = "אאא AAA"; + + var layout = new TextLayout( + text, + Typeface.Default, + 12, + Brushes.Black, + flowDirection: FlowDirection.RightToLeft); + + var firstRun = layout.TextLines[0].TextRuns[0] as ShapedTextCharacters; + + var hit = layout.HitTestPoint(new Point()); + + Assert.Equal(4, hit.TextPosition); + + var currentX = 0.0; + + for (var i = 0; i < firstRun.GlyphRun.GlyphClusters.Count; i++) + { + var cluster = firstRun.GlyphRun.GlyphClusters[i]; + var advance = firstRun.GlyphRun.GlyphAdvances[i]; + + hit = layout.HitTestPoint(new Point(currentX, 0)); + + Assert.Equal(cluster, hit.TextPosition); + + var hitRange = layout.HitTestTextRange(hit.TextPosition, 1); + + var distance = hitRange.First().Left; + + Assert.Equal(currentX, distance); + + currentX += advance; + } + + var secondRun = layout.TextLines[0].TextRuns[1] as ShapedTextCharacters; + + hit = layout.HitTestPoint(new Point(firstRun.Size.Width, 0)); + + Assert.Equal(7, hit.TextPosition); + + hit = layout.HitTestPoint(new Point(layout.TextLines[0].WidthIncludingTrailingWhitespace, 0)); + + Assert.Equal(0, hit.TextPosition); + + currentX = firstRun.Size.Width + 0.5; + + for (var i = 0; i < secondRun.GlyphRun.GlyphClusters.Count; i++) + { + var cluster = secondRun.GlyphRun.GlyphClusters[i]; + var advance = secondRun.GlyphRun.GlyphAdvances[i]; + + hit = layout.HitTestPoint(new Point(currentX, 0)); + + Assert.Equal(cluster, hit.CharacterHit.FirstCharacterIndex); + + var hitRange = layout.HitTestTextRange(hit.CharacterHit.FirstCharacterIndex, hit.CharacterHit.TrailingLength); + + var distance = hitRange.First().Left + 0.5; + + Assert.Equal(currentX, distance); + + currentX += advance; + } + } + } + private static IDisposable Start() { var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface From eb627f393cbfa1441cbdcefb52d79b2a2e82f070 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 11 Jul 2022 15:27:59 +0200 Subject: [PATCH 07/97] More fixes --- samples/Sandbox/App.axaml | 2 +- samples/Sandbox/MainWindow.axaml | 14 ++++- .../Media/TextFormatting/TextLayout.cs | 46 +++++++-------- .../Media/TextFormatting/TextLineImpl.cs | 58 +++++++++++-------- src/Avalonia.Controls/RichTextBlock.cs | 57 +++++++++--------- src/Avalonia.Controls/TextBlock.cs | 7 ++- .../Media/TextFormatting/TextLayoutTests.cs | 45 ++++++++++++++ .../Media/TextFormatting/TextLineTests.cs | 17 +++--- 8 files changed, 156 insertions(+), 90 deletions(-) diff --git a/samples/Sandbox/App.axaml b/samples/Sandbox/App.axaml index f601f9f78f..1c74c07b1d 100644 --- a/samples/Sandbox/App.axaml +++ b/samples/Sandbox/App.axaml @@ -3,6 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Sandbox.App"> - + diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index 0c5a7a11e3..957616579a 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,5 +1,17 @@ - + + + أَبْجَدِيَّة عَرَبِيَّة + diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index f3af240c58..8ab9591faf 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -63,7 +63,7 @@ namespace Avalonia.Media.TextFormatting MaxHeight = maxHeight; - MaxLines = maxLines; + MaxLines = maxLines; TextLines = CreateTextLines(); } @@ -80,7 +80,7 @@ namespace Avalonia.Media.TextFormatting /// The maximum number of text lines. public TextLayout( ITextSource textSource, - TextParagraphProperties paragraphProperties, + TextParagraphProperties paragraphProperties, TextTrimming? textTrimming = null, double maxWidth = double.PositiveInfinity, double maxHeight = double.PositiveInfinity, @@ -178,24 +178,18 @@ namespace Avalonia.Media.TextFormatting return new Rect(); } - if (textPosition < 0 || textPosition >= _textSourceLength) + if (textPosition < 0) { - var lastLine = TextLines[TextLines.Count - 1]; - - var lineX = lastLine.Width; - - var lineY = Bounds.Bottom - lastLine.Height; - - return new Rect(lineX, lineY, 0, lastLine.Height); + textPosition = _textSourceLength; } var currentY = 0.0; foreach (var textLine in TextLines) { - var end = textLine.FirstTextSourceIndex + textLine.Length - 1; + var end = textLine.FirstTextSourceIndex + textLine.Length; - if (end < textPosition) + if (end <= textPosition && end < _textSourceLength) { currentY += textLine.Height; @@ -224,7 +218,7 @@ namespace Avalonia.Media.TextFormatting } var result = new List(TextLines.Count); - + var currentY = 0d; foreach (var textLine in TextLines) @@ -239,7 +233,7 @@ namespace Avalonia.Media.TextFormatting var textBounds = textLine.GetTextBounds(start, length); - if(textBounds.Count > 0) + if (textBounds.Count > 0) { foreach (var bounds in textBounds) { @@ -262,7 +256,7 @@ namespace Avalonia.Media.TextFormatting } } - if(textLine.FirstTextSourceIndex + textLine.Length >= start + length) + if (textLine.FirstTextSourceIndex + textLine.Length >= start + length) { break; } @@ -305,7 +299,7 @@ namespace Avalonia.Media.TextFormatting return GetHitTestResult(currentLine, characterHit, point); } - + public int GetLineIndexFromCharacterIndex(int charIndex, bool trailingEdge) { if (charIndex < 0) @@ -327,7 +321,7 @@ namespace Avalonia.Media.TextFormatting continue; } - if (charIndex >= textLine.FirstTextSourceIndex && + if (charIndex >= textLine.FirstTextSourceIndex && charIndex <= textLine.FirstTextSourceIndex + textLine.Length - (trailingEdge ? 0 : 1)) { return index; @@ -398,7 +392,7 @@ namespace Avalonia.Media.TextFormatting /// The current left. /// The current width. /// The current height. - private static void UpdateBounds(TextLine textLine,ref double left, ref double width, ref double height) + private static void UpdateBounds(TextLine textLine, ref double left, ref double width, ref double height) { var lineWidth = textLine.WidthIncludingTrailingWhitespace; @@ -421,7 +415,7 @@ namespace Avalonia.Media.TextFormatting { var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties); - Bounds = new Rect(0,0,0, textLine.Height); + Bounds = new Rect(0, 0, 0, textLine.Height); return new List { textLine }; } @@ -439,9 +433,9 @@ namespace Avalonia.Media.TextFormatting var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth, _paragraphProperties, previousLine?.TextLineBreak); - if(textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) + if (textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) { - if(previousLine != null && previousLine.NewLineLength > 0) + if (previousLine != null && previousLine.NewLineLength > 0) { var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, _paragraphProperties); @@ -454,7 +448,7 @@ namespace Avalonia.Media.TextFormatting } _textSourceLength += textLine.Length; - + //Fulfill max height constraint if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight) { @@ -490,7 +484,7 @@ namespace Avalonia.Media.TextFormatting } //Make sure the TextLayout always contains at least on empty line - if(textLines.Count == 0) + if (textLines.Count == 0) { var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties); @@ -501,7 +495,7 @@ namespace Avalonia.Media.TextFormatting Bounds = new Rect(left, 0, width, height); - if(_paragraphProperties.TextAlignment == TextAlignment.Justify) + if (_paragraphProperties.TextAlignment == TextAlignment.Justify) { var whitespaceWidth = 0d; @@ -509,7 +503,7 @@ namespace Avalonia.Media.TextFormatting { var lineWhitespaceWidth = line.Width - line.WidthIncludingTrailingWhitespace; - if(lineWhitespaceWidth > whitespaceWidth) + if (lineWhitespaceWidth > whitespaceWidth) { whitespaceWidth = lineWhitespaceWidth; } @@ -517,7 +511,7 @@ namespace Avalonia.Media.TextFormatting var justificationWidth = width - whitespaceWidth; - if(justificationWidth > 0) + if (justificationWidth > 0) { var justificationProperties = new InterWordJustification(justificationWidth); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index f4a0324d90..67c8f0c88c 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -317,7 +317,7 @@ namespace Avalonia.Media.TextFormatting { currentGlyphRun = shapedTextCharacters.GlyphRun; - if (currentPosition + remainingLength < currentPosition + currentRun.Text.Length) + if (currentPosition + remainingLength <= currentPosition + currentRun.Text.Length) { characterHit = new CharacterHit(currentRun.Text.Start + remainingLength); @@ -524,27 +524,30 @@ namespace Avalonia.Media.TextFormatting characterLength = NewLineLength; } - var runwidth = endX - startX; - var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runwidth, Height), currentPosition, characterLength, currentRun); + var runWidth = endX - startX; + var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); - if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) + if (!MathUtilities.IsZero(runWidth) || NewLineLength > 0) { - currentRect = currentRect.WithWidth(currentWidth + runwidth); + if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) + { + currentRect = currentRect.WithWidth(currentWidth + runWidth); - var textBounds = result[result.Count - 1]; + var textBounds = result[result.Count - 1]; - textBounds.Rectangle = currentRect; + textBounds.Rectangle = currentRect; - textBounds.TextRunBounds.Add(currentRunBounds); - } - else - { - currentRect = currentRunBounds.Rectangle; + textBounds.TextRunBounds.Add(currentRunBounds); + } + else + { + currentRect = currentRunBounds.Rectangle; - result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds })); + result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds })); + } } - currentWidth += runwidth; + currentWidth += runWidth; currentPosition += characterLength; if (currentPosition > characterIndex) @@ -671,22 +674,25 @@ namespace Avalonia.Media.TextFormatting var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); - if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX)) + if(!MathUtilities.IsZero(runWidth) || NewLineLength > 0) { - currentRect = currentRect.WithWidth(currentWidth + runWidth); + if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX)) + { + currentRect = currentRect.WithWidth(currentWidth + runWidth); - var textBounds = result[result.Count - 1]; + var textBounds = result[result.Count - 1]; - textBounds.Rectangle = currentRect; + textBounds.Rectangle = currentRect; - textBounds.TextRunBounds.Add(currentRunBounds); - } - else - { - currentRect = currentRunBounds.Rectangle; + textBounds.TextRunBounds.Add(currentRunBounds); + } + else + { + currentRect = currentRunBounds.Rectangle; - result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds })); - } + result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds })); + } + } currentWidth += runWidth; currentPosition += characterLength; @@ -705,6 +711,8 @@ namespace Avalonia.Media.TextFormatting } } + result.Reverse(); + return result; } diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 2b84113497..6a40144137 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -41,9 +41,6 @@ namespace Avalonia.Controls public static readonly StyledProperty SelectionBrushProperty = AvaloniaProperty.Register(nameof(SelectionBrush), Brushes.Blue); - public static readonly StyledProperty SelectionForegroundBrushProperty = - AvaloniaProperty.Register(nameof(SelectionForegroundBrush)); - /// /// Defines the property. /// @@ -68,7 +65,7 @@ namespace Avalonia.Controls { FocusableProperty.OverrideDefaultValue(typeof(RichTextBlock), true); - AffectsRender(SelectionStartProperty, SelectionEndProperty, SelectionForegroundBrushProperty, SelectionBrushProperty); + AffectsRender(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty); } public RichTextBlock() @@ -89,15 +86,6 @@ namespace Avalonia.Controls set => SetValue(SelectionBrushProperty, value); } - /// - /// Gets or sets a value that defines the brush used for selected text. - /// - public IBrush? SelectionForegroundBrush - { - get => GetValue(SelectionForegroundBrushProperty); - set => SetValue(SelectionForegroundBrushProperty, value); - } - /// /// Gets or sets a character index for the beginning of the current selection. /// @@ -198,7 +186,7 @@ namespace Avalonia.Controls } } - public override void Render(DrawingContext context) + protected override void RenderTextLayout(DrawingContext context, Point origin) { var selectionStart = SelectionStart; var selectionEnd = SelectionEnd; @@ -213,13 +201,16 @@ namespace Avalonia.Controls var rects = TextLayout.HitTestTextRange(start, length); - foreach (var rect in rects) + using (context.PushPostTransform(Matrix.CreateTranslation(origin))) { - context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1)); + foreach (var rect in rects) + { + context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1)); + } } } - base.Render(context); + base.RenderTextLayout(context, origin); } /// @@ -280,8 +271,9 @@ namespace Avalonia.Controls /// A object. protected override TextLayout CreateTextLayout(string? text) { + var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); var defaultProperties = new GenericTextRunProperties( - new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), + typeface, FontSize, TextDecorations, Foreground); @@ -328,6 +320,8 @@ namespace Avalonia.Controls protected override void OnKeyDown(KeyEventArgs e) { + base.OnKeyDown(e); + var handled = false; var modifiers = e.KeyModifiers; var keymap = AvaloniaLocator.Current.GetRequiredService(); @@ -346,6 +340,8 @@ namespace Avalonia.Controls protected override void OnPointerPressed(PointerPressedEventArgs e) { + base.OnPointerPressed(e); + if (!IsTextSelectionEnabled) { return; @@ -356,7 +352,9 @@ namespace Avalonia.Controls if (text != null && clickInfo.Properties.IsLeftButtonPressed) { - var point = e.GetPosition(this); + var padding = Padding; + + var point = e.GetPosition(this) - new Point(padding.Left, padding.Top); var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift); @@ -403,6 +401,8 @@ namespace Avalonia.Controls protected override void OnPointerMoved(PointerEventArgs e) { + base.OnPointerMoved(e); + if (!IsTextSelectionEnabled) { return; @@ -411,11 +411,13 @@ namespace Avalonia.Controls // selection should not change during pointer move if the user right clicks if (e.Pointer.Captured == this && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { - var point = e.GetPosition(this); + var padding = Padding; + + var point = e.GetPosition(this) - new Point(padding.Left, padding.Top); point = new Point( - MathUtilities.Clamp(point.X, 0, Math.Max(Bounds.Width - 1, 0)), - MathUtilities.Clamp(point.Y, 0, Math.Max(Bounds.Height - 1, 0))); + MathUtilities.Clamp(point.X, 0, Math.Max(TextLayout.Bounds.Width, 0)), + MathUtilities.Clamp(point.Y, 0, Math.Max(TextLayout.Bounds.Width, 0))); var hit = TextLayout.HitTestPoint(point); @@ -425,6 +427,8 @@ namespace Avalonia.Controls protected override void OnPointerReleased(PointerReleasedEventArgs e) { + base.OnPointerReleased(e); + if (!IsTextSelectionEnabled) { return; @@ -437,7 +441,9 @@ namespace Avalonia.Controls if (e.InitialPressMouseButton == MouseButton.Right) { - var point = e.GetPosition(this); + var padding = Padding; + + var point = e.GetPosition(this) - new Point(padding.Left, padding.Top); var hit = TextLayout.HitTestPoint(point); @@ -470,11 +476,6 @@ namespace Avalonia.Controls InvalidateTextLayout(); break; } - case nameof(TextProperty): - { - InvalidateTextLayout(); - break; - } } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 52261d1c76..baa722a8fe 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -509,7 +509,12 @@ namespace Avalonia.Controls } } - TextLayout.Draw(context, new Point(padding.Left, top)); + RenderTextLayout(context, new Point(padding.Left, top)); + } + + protected virtual void RenderTextLayout(DrawingContext context, Point origin) + { + TextLayout.Draw(context, origin); } protected virtual string? GetText() diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 631d0881b0..1241141ccf 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -910,6 +910,51 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [Fact] + public void Should_Get_CharacterHit_From_Distance_RTL() + { + using (Start()) + { + var text = "أَبْجَدِيَّة عَرَبِيَّة"; + + var layout = new TextLayout( + text, + Typeface.Default, + 12, + Brushes.Black); + + var textLine = layout.TextLines[0]; + + var firstRun = textLine.TextRuns[0] as ShapedTextCharacters; + + var firstCluster = firstRun.ShapedBuffer.GlyphClusters[0]; + + var characterHit = textLine.GetCharacterHitFromDistance(0); + + Assert.Equal(firstCluster, characterHit.FirstCharacterIndex); + + Assert.Equal(text.Length, characterHit.FirstCharacterIndex + characterHit.TrailingLength); + + var distance = textLine.GetDistanceFromCharacterHit(characterHit); + + Assert.Equal(0, distance); + + distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(characterHit.FirstCharacterIndex)); + + var firstAdvance = firstRun.ShapedBuffer.GlyphAdvances[0]; + + Assert.Equal(firstAdvance, distance); + + var rect = layout.HitTestTextPosition(22); + + Assert.Equal(firstAdvance, rect.Left); + + rect = layout.HitTestTextPosition(23); + + Assert.Equal(0, rect.Left); + } + } + private static IDisposable Start() { var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 58cb84c4a4..d744ede87d 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -867,28 +867,29 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textBounds = textLine.GetTextBounds(0, 4); - var firstRun = textLine.TextRuns[1] as ShapedTextCharacters; + var secondRun = textLine.TextRuns[1] as ShapedTextCharacters; Assert.Equal(1, textBounds.Count); - Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); + Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); textBounds = textLine.GetTextBounds(4, 3); - var secondRun = textLine.TextRuns[0] as ShapedTextCharacters; + var firstRun = textLine.TextRuns[0] as ShapedTextCharacters; Assert.Equal(1, textBounds.Count); Assert.Equal(3, textBounds[0].TextRunBounds.Sum(x=> x.Length)); - Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); + Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); textBounds = textLine.GetTextBounds(0, 5); Assert.Equal(2, textBounds.Count); Assert.Equal(5, textBounds.Sum(x=> x.TextRunBounds.Sum(x => x.Length))); - Assert.Equal(firstRun.Size.Width, textBounds[0].Rectangle.Width); - Assert.Equal(7.201171875, textBounds[1].Rectangle.Width); - Assert.Equal(textLine.Start + 7.201171875, textBounds[1].Rectangle.Right); + Assert.Equal(secondRun.Size.Width, textBounds[1].Rectangle.Width); + Assert.Equal(7.201171875, textBounds[0].Rectangle.Width); + Assert.Equal(textLine.Start + 7.201171875, textBounds[0].Rectangle.Right); + Assert.Equal(textLine.Start + firstRun.Size.Width, textBounds[1].Rectangle.Left); textBounds = textLine.GetTextBounds(0, text.Length); @@ -896,7 +897,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(7, textBounds.Sum(x => x.TextRunBounds.Sum(x => x.Length))); Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width)); } - } + } private class FixedRunsTextSource : ITextSource { From 7df34acb3d8e01c03fe39dc0fc3aec3c012c7b60 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 13 Jul 2022 12:14:10 +0200 Subject: [PATCH 08/97] Update src/Avalonia.Base/Media/GlyphRun.cs --- src/Avalonia.Base/Media/GlyphRun.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 703b56b0e8..da2143c188 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -652,7 +652,7 @@ namespace Avalonia.Media for (var index = 0; index < glyphCount; index++) { width -= GetGlyphAdvance(index, out _); - } + } } else { From 199d74332ae8cabb7c2ce3f56e5956372cd1d7d4 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 13 Jul 2022 12:26:58 +0200 Subject: [PATCH 09/97] Add arabic test typeface --- .../Assets/NotoKufiArabic-Regular.ttf | Bin 0 -> 122736 bytes .../Media/CustomFontManagerImpl.cs | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf diff --git a/tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf b/tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6d2ad86f947a530de5dec09b9a230948d0f512c3 GIT binary patch literal 122736 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 V75=m Date: Thu, 14 Jul 2022 06:22:53 +0200 Subject: [PATCH 10/97] Fix unit tests on mac --- .../Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs | 8 ++------ .../Media/TextFormatting/TextLayoutTests.cs | 8 ++++---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs index df286d709e..649e1fbf3d 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs @@ -33,15 +33,11 @@ namespace Avalonia.Skia.UnitTests.Media { var fontManager = new FontManagerImpl(); - //we need to have a valid font name different from the default one - string fontName = fontManager.GetInstalledFontFamilyNames().First(); - var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface( - new Typeface(new FontFamily($"A, B, {fontName}"), weight: FontWeight.Bold)); + new Typeface(new FontFamily($"A, B, Arial"), weight: FontWeight.Bold)); var skTypeface = glyphTypeface.Typeface; - - Assert.Equal(fontName, skTypeface.FamilyName); + Assert.True(skTypeface.FontWeight >= 600); } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 1241141ccf..7d33f094fa 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -925,7 +925,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = layout.TextLines[0]; - var firstRun = textLine.TextRuns[0] as ShapedTextCharacters; + var firstRun = (ShapedTextCharacters)textLine.TextRuns[0]; var firstCluster = firstRun.ShapedBuffer.GlyphClusters[0]; @@ -943,15 +943,15 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var firstAdvance = firstRun.ShapedBuffer.GlyphAdvances[0]; - Assert.Equal(firstAdvance, distance); + Assert.Equal(firstAdvance, distance, 5); var rect = layout.HitTestTextPosition(22); - Assert.Equal(firstAdvance, rect.Left); + Assert.Equal(firstAdvance, rect.Left, 5); rect = layout.HitTestTextPosition(23); - Assert.Equal(0, rect.Left); + Assert.Equal(0, rect.Left, 5); } } From c921d1e097c1dced3b0ded4deb267929876a2fd9 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 22 Jul 2022 15:34:14 +0200 Subject: [PATCH 11/97] Fix center alignment for RTL --- src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 67c8f0c88c..f3c62f4994 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1317,8 +1317,14 @@ namespace Avalonia.Media.TextFormatting switch (textAlignment) { case TextAlignment.Center: - return Math.Max(0, (_paragraphWidth - width) / 2); + var start = (_paragraphWidth - width) / 2; + if(paragraphFlowDirection == FlowDirection.RightToLeft) + { + start -= (widthIncludingTrailingWhitespace - width); + } + + return Math.Max(0, start); case TextAlignment.Right: return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace); From 6802722051223ed11055841fc39324db6f3c94ba Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 22 Jul 2022 16:13:21 +0200 Subject: [PATCH 12/97] Add whole word shift selection --- samples/Sandbox/Sandbox.csproj | 1 - src/Avalonia.Controls/RichTextBlock.cs | 33 ++++++++++++++++++++++---- src/Avalonia.Controls/TextBox.cs | 29 +++++++++++++++++++--- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/samples/Sandbox/Sandbox.csproj b/samples/Sandbox/Sandbox.csproj index 20c7f29201..eab654acb6 100644 --- a/samples/Sandbox/Sandbox.csproj +++ b/samples/Sandbox/Sandbox.csproj @@ -10,7 +10,6 @@ - diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 6a40144137..3011cc7bd9 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -338,6 +338,8 @@ namespace Avalonia.Controls e.Handled = handled; } + private bool _hasWordSelection; + protected override void OnPointerPressed(PointerPressedEventArgs e) { base.OnPointerPressed(e); @@ -363,8 +365,6 @@ namespace Avalonia.Controls var hit = TextLayout.HitTestPoint(point); var index = hit.TextPosition; - SelectionStart = SelectionEnd = index; - #pragma warning disable CS0618 // Type or member is obsolete switch (e.ClickCount) #pragma warning restore CS0618 // Type or member is obsolete @@ -372,16 +372,27 @@ namespace Avalonia.Controls case 1: if (clickToSelect) { - SelectionStart = Math.Min(oldIndex, index); - SelectionEnd = Math.Max(oldIndex, index); + if (_hasWordSelection) + { + SelectionEnd = StringUtils.NextWord(text, index); + } + else + { + SelectionStart = Math.Min(oldIndex, index); + SelectionEnd = Math.Max(oldIndex, index); + } } else { + _hasWordSelection = false; + SelectionStart = SelectionEnd = index; } break; case 2: + _hasWordSelection = true; + if (!StringUtils.IsStartOfWord(text, index)) { SelectionStart = StringUtils.PreviousWord(text, index); @@ -390,6 +401,8 @@ namespace Avalonia.Controls SelectionEnd = StringUtils.NextWord(text, index); break; case 3: + _hasWordSelection = false; + SelectAll(); break; } @@ -411,6 +424,7 @@ namespace Avalonia.Controls // selection should not change during pointer move if the user right clicks if (e.Pointer.Captured == this && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { + var text = Text; var padding = Padding; var point = e.GetPosition(this) - new Point(padding.Left, padding.Top); @@ -421,7 +435,16 @@ namespace Avalonia.Controls var hit = TextLayout.HitTestPoint(point); - SelectionEnd = hit.TextPosition; + if (text != null && _hasWordSelection) + { + SelectionEnd = StringUtils.NextWord(text, hit.TextPosition); + } + else + { + SelectionEnd = hit.TextPosition; + } + + } } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 9531f719b9..0946404f3b 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -202,6 +202,7 @@ namespace Avalonia.Controls private string _newLine = Environment.NewLine; private static readonly string[] invalidCharacters = new String[1] { "\u007f" }; + private bool _hasWordSelection; private int _selectedTextChangesMadeSinceLastUndoSnapshot; private bool _hasDoneSnapshotOnce; private const int _maxCharsBeforeUndoSnapshot = 7; @@ -1170,16 +1171,27 @@ namespace Avalonia.Controls case 1: if (clickToSelect) { - SelectionStart = Math.Min(oldIndex, index); - SelectionEnd = Math.Max(oldIndex, index); + if (_hasWordSelection) + { + SelectionEnd = StringUtils.NextWord(text, index); + } + else + { + SelectionStart = Math.Min(oldIndex, index); + SelectionEnd = Math.Max(oldIndex, index); + } } else { + _hasWordSelection = false; + SelectionStart = SelectionEnd = index; } break; case 2: + _hasWordSelection = true; + if (!StringUtils.IsStartOfWord(text, index)) { SelectionStart = StringUtils.PreviousWord(text, index); @@ -1188,6 +1200,8 @@ namespace Avalonia.Controls SelectionEnd = StringUtils.NextWord(text, index); break; case 3: + _hasWordSelection = false; + SelectAll(); break; } @@ -1215,7 +1229,16 @@ namespace Avalonia.Controls _presenter.MoveCaretToPoint(point); - SelectionEnd = _presenter.CaretIndex; + var text = Text; + + if (text != null && _hasWordSelection) + { + SelectionEnd = StringUtils.NextWord(text, _presenter.CaretIndex); + } + else + { + SelectionEnd = _presenter.CaretIndex; + } } } From c7182ae12adf019f9644a68c0361e7cc9987a0fc Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 22 Jul 2022 18:03:30 +0200 Subject: [PATCH 13/97] Improve advanced word selection --- src/Avalonia.Controls/RichTextBlock.cs | 53 +++-- src/Avalonia.Controls/TextBox.cs | 276 ++++++++++++++----------- 2 files changed, 193 insertions(+), 136 deletions(-) diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 3011cc7bd9..b7c5cfa3bd 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -60,6 +60,7 @@ namespace Avalonia.Controls private bool _canCopy; private int _selectionStart; private int _selectionEnd; + private int _wordSelectionStart = -1; static RichTextBlock() { @@ -338,8 +339,6 @@ namespace Avalonia.Controls e.Handled = handled; } - private bool _hasWordSelection; - protected override void OnPointerPressed(PointerPressedEventArgs e) { base.OnPointerPressed(e); @@ -372,9 +371,19 @@ namespace Avalonia.Controls case 1: if (clickToSelect) { - if (_hasWordSelection) + if (_wordSelectionStart >= 0) { - SelectionEnd = StringUtils.NextWord(text, index); + var previousWord = StringUtils.PreviousWord(text, index); + + if (index > _wordSelectionStart) + { + SelectionEnd = StringUtils.NextWord(text, index); + } + + if (index < _wordSelectionStart || previousWord == _wordSelectionStart) + { + SelectionStart = previousWord; + } } else { @@ -384,24 +393,27 @@ namespace Avalonia.Controls } else { - _hasWordSelection = false; + if (_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd) + { + SelectionStart = SelectionEnd = index; - SelectionStart = SelectionEnd = index; + _wordSelectionStart = -1; + } } break; case 2: - _hasWordSelection = true; - if (!StringUtils.IsStartOfWord(text, index)) { SelectionStart = StringUtils.PreviousWord(text, index); } + _wordSelectionStart = SelectionStart; + SelectionEnd = StringUtils.NextWord(text, index); break; case 3: - _hasWordSelection = false; + _wordSelectionStart = -1; SelectAll(); break; @@ -434,17 +446,32 @@ namespace Avalonia.Controls MathUtilities.Clamp(point.Y, 0, Math.Max(TextLayout.Bounds.Width, 0))); var hit = TextLayout.HitTestPoint(point); + var textPosition = hit.TextPosition; - if (text != null && _hasWordSelection) + if (text != null && _wordSelectionStart >= 0) { - SelectionEnd = StringUtils.NextWord(text, hit.TextPosition); + var distance = textPosition - _wordSelectionStart; + + if (distance <= 0) + { + SelectionStart = StringUtils.PreviousWord(text, textPosition); + } + + if (distance >= 0) + { + if (SelectionStart != _wordSelectionStart) + { + SelectionStart = _wordSelectionStart; + } + + SelectionEnd = StringUtils.NextWord(text, textPosition); + } } else { - SelectionEnd = hit.TextPosition; + SelectionEnd = textPosition; } - } } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 0946404f3b..1b268db2f7 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -53,7 +53,7 @@ namespace Avalonia.Controls public static readonly StyledProperty PasswordCharProperty = AvaloniaProperty.Register(nameof(PasswordChar)); - + public static readonly StyledProperty SelectionBrushProperty = AvaloniaProperty.Register(nameof(SelectionBrush)); @@ -80,7 +80,7 @@ namespace Avalonia.Controls public static readonly StyledProperty MaxLinesProperty = AvaloniaProperty.Register(nameof(MaxLines), defaultValue: 0); - + public static readonly DirectProperty TextProperty = TextBlock.TextProperty.AddOwnerWithDataValidation( o => o.Text, @@ -105,7 +105,7 @@ namespace Avalonia.Controls public static readonly StyledProperty TextWrappingProperty = TextBlock.TextWrappingProperty.AddOwner(); - + /// /// Defines see property. /// @@ -202,7 +202,7 @@ namespace Avalonia.Controls private string _newLine = Environment.NewLine; private static readonly string[] invalidCharacters = new String[1] { "\u007f" }; - private bool _hasWordSelection; + private int _wordSelectionStart = -1; private int _selectedTextChangesMadeSinceLastUndoSnapshot; private bool _hasDoneSnapshotOnce; private const int _maxCharsBeforeUndoSnapshot = 7; @@ -276,7 +276,7 @@ namespace Avalonia.Controls get => GetValue(IsReadOnlyProperty); set => SetValue(IsReadOnlyProperty, value); } - + public char PasswordChar { get => GetValue(PasswordCharProperty); @@ -308,7 +308,7 @@ namespace Avalonia.Controls { value = CoerceCaretIndex(value); var changed = SetAndRaise(SelectionStartProperty, ref _selectionStart, value); - + if (changed) { UpdateCommandStates(); @@ -328,12 +328,12 @@ namespace Avalonia.Controls { value = CoerceCaretIndex(value); var changed = SetAndRaise(SelectionEndProperty, ref _selectionEnd, value); - + if (changed) { UpdateCommandStates(); } - + if (SelectionStart == value && CaretIndex != value) { CaretIndex = value; @@ -352,7 +352,7 @@ namespace Avalonia.Controls get => GetValue(MaxLinesProperty); set => SetValue(MaxLinesProperty, value); } - + /// /// Gets or sets the line height. /// @@ -371,7 +371,7 @@ namespace Avalonia.Controls var caretIndex = CaretIndex; var selectionStart = SelectionStart; var selectionEnd = SelectionEnd; - + CaretIndex = CoerceCaretIndex(caretIndex, value); SelectionStart = CoerceCaretIndex(selectionStart, value); SelectionEnd = CoerceCaretIndex(selectionEnd, value); @@ -568,7 +568,7 @@ namespace Avalonia.Controls _presenter = e.NameScope.Get("PART_TextPresenter"); _imClient.SetPresenter(_presenter, this); - + if (IsFocused) { _presenter?.ShowCaret(); @@ -578,7 +578,7 @@ namespace Avalonia.Controls protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - + if (IsFocused) { _presenter?.ShowCaret(); @@ -588,7 +588,7 @@ namespace Avalonia.Controls protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); - + _imClient.SetPresenter(null, null); } @@ -638,7 +638,7 @@ namespace Avalonia.Controls } UpdateCommandStates(); - + _imClient.SetPresenter(_presenter, this); _presenter?.ShowCaret(); @@ -658,7 +658,7 @@ namespace Avalonia.Controls UpdateCommandStates(); _presenter?.HideCaret(); - + _imClient.SetPresenter(null, null); } @@ -701,14 +701,14 @@ namespace Avalonia.Controls if (grapheme.FirstCodepoint.IsBreakChar) { - if(lineCount + 1 > MaxLines) + if (lineCount + 1 > MaxLines) { break; } else { lineCount++; - } + } } length += grapheme.Text.Length; @@ -737,7 +737,7 @@ namespace Avalonia.Controls text = Text ?? string.Empty; SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex)); ClearSelection(); - + if (IsUndoEnabled) { _undoRedoHelper.DiscardRedo(); @@ -747,7 +747,7 @@ namespace Avalonia.Controls { RaisePropertyChanged(TextProperty, oldText, _text); } - + CaretIndex = caretIndex + input.Length; } } @@ -829,7 +829,7 @@ namespace Avalonia.Controls { return; } - + var text = Text ?? string.Empty; var caretIndex = CaretIndex; var movement = false; @@ -986,87 +986,87 @@ namespace Avalonia.Controls break; case Key.Up: - { - selection = DetectSelection(); - - _presenter.MoveCaretVertical(LogicalDirection.Backward); - - if (caretIndex != _presenter.CaretIndex) { - movement = true; - } + selection = DetectSelection(); - if (selection) - { - SelectionEnd = _presenter.CaretIndex; - } - else - { - CaretIndex = _presenter.CaretIndex; + _presenter.MoveCaretVertical(LogicalDirection.Backward); + + if (caretIndex != _presenter.CaretIndex) + { + movement = true; + } + + if (selection) + { + SelectionEnd = _presenter.CaretIndex; + } + else + { + CaretIndex = _presenter.CaretIndex; + } + + break; } - - break; - } case Key.Down: - { - selection = DetectSelection(); - - _presenter.MoveCaretVertical(); - - if (caretIndex != _presenter.CaretIndex) - { - movement = true; - } - - if (selection) { - SelectionEnd = _presenter.CaretIndex; - } - else - { - CaretIndex = _presenter.CaretIndex; + selection = DetectSelection(); + + _presenter.MoveCaretVertical(); + + if (caretIndex != _presenter.CaretIndex) + { + movement = true; + } + + if (selection) + { + SelectionEnd = _presenter.CaretIndex; + } + else + { + CaretIndex = _presenter.CaretIndex; + } + + break; } - - break; - } case Key.Back: - { - SnapshotUndoRedo(); - - if (hasWholeWordModifiers && SelectionStart == SelectionEnd) - { - SetSelectionForControlBackspace(); - } - - if (!DeleteSelection()) { - var characterHit = _presenter.GetNextCharacterHit(LogicalDirection.Backward); + SnapshotUndoRedo(); - var backspacePosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; + if (hasWholeWordModifiers && SelectionStart == SelectionEnd) + { + SetSelectionForControlBackspace(); + } - if (caretIndex != backspacePosition) + if (!DeleteSelection()) { - var start = Math.Min(backspacePosition, caretIndex); - var end = Math.Max(backspacePosition, caretIndex); + var characterHit = _presenter.GetNextCharacterHit(LogicalDirection.Backward); - var length = end - start; + var backspacePosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length)); + if (caretIndex != backspacePosition) + { + var start = Math.Min(backspacePosition, caretIndex); + var end = Math.Max(backspacePosition, caretIndex); - SetTextInternal(editedText); + var length = end - start; - CaretIndex = start; - } - } - - SnapshotUndoRedo(); + var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length)); - handled = true; - break; - } + SetTextInternal(editedText); + + CaretIndex = start; + } + } + + SnapshotUndoRedo(); + + handled = true; + break; + } case Key.Delete: SnapshotUndoRedo(); - + if (hasWholeWordModifiers && SelectionStart == SelectionEnd) { SetSelectionForControlDelete(); @@ -1078,7 +1078,7 @@ namespace Avalonia.Controls var nextPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - if(nextPosition != caretIndex) + if (nextPosition != caretIndex) { var start = Math.Min(nextPosition, caretIndex); var end = Math.Max(nextPosition, caretIndex); @@ -1145,7 +1145,7 @@ namespace Avalonia.Controls { return; } - + var text = Text; var clickInfo = e.GetCurrentPoint(this); @@ -1171,36 +1171,49 @@ namespace Avalonia.Controls case 1: if (clickToSelect) { - if (_hasWordSelection) + if (_wordSelectionStart >= 0) { - SelectionEnd = StringUtils.NextWord(text, index); + var previousWord = StringUtils.PreviousWord(text, index); + + if (index > _wordSelectionStart) + { + SelectionEnd = StringUtils.NextWord(text, index); + } + + if (index < _wordSelectionStart || previousWord == _wordSelectionStart) + { + SelectionStart = previousWord; + } } else { SelectionStart = Math.Min(oldIndex, index); SelectionEnd = Math.Max(oldIndex, index); - } + } } else { - _hasWordSelection = false; - - SelectionStart = SelectionEnd = index; + if(_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd) + { + SelectionStart = SelectionEnd = index; + _wordSelectionStart = -1; + } } break; - case 2: - _hasWordSelection = true; + case 2: if (!StringUtils.IsStartOfWord(text, index)) { SelectionStart = StringUtils.PreviousWord(text, index); } + _wordSelectionStart = SelectionStart; + SelectionEnd = StringUtils.NextWord(text, index); break; case 3: - _hasWordSelection = false; + _wordSelectionStart = -1; SelectAll(); break; @@ -1217,7 +1230,7 @@ namespace Avalonia.Controls { return; } - + // selection should not change during pointer move if the user right clicks if (e.Pointer.Captured == _presenter && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { @@ -1229,11 +1242,27 @@ namespace Avalonia.Controls _presenter.MoveCaretToPoint(point); + var caretIndex = _presenter.CaretIndex; var text = Text; - if (text != null && _hasWordSelection) + if (text != null && _wordSelectionStart >= 0) { - SelectionEnd = StringUtils.NextWord(text, _presenter.CaretIndex); + var distance = caretIndex - _wordSelectionStart; + + if (distance <= 0) + { + SelectionStart = StringUtils.PreviousWord(text, caretIndex); + } + + if (distance >= 0) + { + if(SelectionStart != _wordSelectionStart) + { + SelectionStart = _wordSelectionStart; + } + + SelectionEnd = StringUtils.NextWord(text, caretIndex); + } } else { @@ -1257,9 +1286,9 @@ namespace Avalonia.Controls if (e.InitialPressMouseButton == MouseButton.Right) { var point = e.GetPosition(_presenter); - + _presenter.MoveCaretToPoint(point); - + var caretIndex = _presenter.CaretIndex; // see if mouse clicked inside current selection @@ -1273,7 +1302,7 @@ namespace Avalonia.Controls CaretIndex = SelectionEnd = SelectionStart = caretIndex; } } - + e.Pointer.Capture(null); } @@ -1332,7 +1361,7 @@ namespace Avalonia.Controls { return; } - + var text = Text ?? string.Empty; var selectionStart = SelectionStart; var selectionEnd = SelectionEnd; @@ -1342,11 +1371,11 @@ namespace Avalonia.Controls if (isSelecting) { _presenter.MoveCaretToTextPosition(selectionEnd); - + _presenter.MoveCaretHorizontal(direction > 0 ? LogicalDirection.Forward : LogicalDirection.Backward); - + SelectionEnd = _presenter.CaretIndex; } else @@ -1370,7 +1399,7 @@ namespace Avalonia.Controls else { int offset; - + if (direction > 0) { offset = StringUtils.NextWord(text, selectionEnd) - selectionEnd; @@ -1379,7 +1408,7 @@ namespace Avalonia.Controls { offset = StringUtils.PreviousWord(text, selectionEnd) - selectionEnd; } - + SelectionEnd += offset; _presenter.MoveCaretToTextPosition(SelectionEnd); @@ -1401,7 +1430,7 @@ namespace Avalonia.Controls { return; } - + var caretIndex = CaretIndex; if (document) @@ -1424,7 +1453,7 @@ namespace Avalonia.Controls { return; } - + var text = Text ?? string.Empty; var caretIndex = CaretIndex; @@ -1455,8 +1484,9 @@ namespace Avalonia.Controls private bool DeleteSelection(bool raiseTextChanged = true) { - if (IsReadOnly) return true; - + if (IsReadOnly) + return true; + var selectionStart = SelectionStart; var selectionEnd = SelectionEnd; @@ -1467,40 +1497,40 @@ namespace Avalonia.Controls var text = Text!; SetTextInternal(text.Substring(0, start) + text.Substring(end), raiseTextChanged); - + _presenter?.MoveCaretToTextPosition(start); - - CaretIndex= start; - + + CaretIndex = start; + ClearSelection(); - + return true; } - + CaretIndex = SelectionStart; - + return false; } private string GetSelection() { var text = Text; - + if (string.IsNullOrEmpty(text)) { return ""; } - + var selectionStart = SelectionStart; var selectionEnd = SelectionEnd; var start = Math.Min(selectionStart, selectionEnd); var end = Math.Max(selectionStart, selectionEnd); - + if (start == end || (Text?.Length ?? 0) < end) { return ""; } - + return text.Substring(start, end - start); } @@ -1519,7 +1549,7 @@ namespace Avalonia.Controls private void SetSelectionForControlBackspace() { var selectionStart = CaretIndex; - + MoveHorizontal(-1, true, false); SelectionStart = selectionStart; @@ -1531,9 +1561,9 @@ namespace Avalonia.Controls { return; } - + SelectionStart = CaretIndex; - + MoveHorizontal(1, true, true); if (SelectionEnd < _text.Length && _text[SelectionEnd] == ' ') From 9f37910c97c8401e4694a7be9dbdbc1cf6890456 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 22 Jul 2022 18:43:49 +0200 Subject: [PATCH 14/97] Fix TextDecoration width Fix LineBreak length --- samples/Sandbox/MainWindow.axaml | 14 +------------- src/Avalonia.Base/Media/GlyphRun.cs | 2 +- src/Avalonia.Base/Media/TextDecoration.cs | 2 +- src/Avalonia.Controls/Documents/LineBreak.cs | 3 +-- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index 957616579a..c305c57efb 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,17 +1,5 @@ - - - أَبْجَدِيَّة عَرَبِيَّة - + diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index da2143c188..2a7f3360ad 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -445,7 +445,7 @@ namespace Avalonia.Media /// public int FindGlyphIndex(int characterIndex) { - if (GlyphClusters == null) + if (GlyphClusters == null || GlyphClusters.Count == 0) { return characterIndex; } diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs index 8eeb86c555..4c9764af96 100644 --- a/src/Avalonia.Base/Media/TextDecoration.cs +++ b/src/Avalonia.Base/Media/TextDecoration.cs @@ -209,7 +209,7 @@ namespace Avalonia.Media var pen = new Pen(Stroke ?? defaultBrush, thickness, new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap); - drawingContext.DrawLine(pen, origin, origin + new Point(glyphRun.Size.Width, 0)); + drawingContext.DrawLine(pen, origin, origin + new Point(glyphRun.Metrics.Width, 0)); } } } diff --git a/src/Avalonia.Controls/Documents/LineBreak.cs b/src/Avalonia.Controls/Documents/LineBreak.cs index aeb81f7313..ef8bd35556 100644 --- a/src/Avalonia.Controls/Documents/LineBreak.cs +++ b/src/Avalonia.Controls/Documents/LineBreak.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Text; -using Avalonia.LogicalTree; using Avalonia.Media.TextFormatting; using Avalonia.Metadata; @@ -22,7 +21,7 @@ namespace Avalonia.Controls.Documents internal override void BuildTextRun(IList textRuns) { - textRuns.Add(new TextEndOfLine()); + textRuns.Add(new TextEndOfLine(Environment.NewLine.Length)); } internal override void AppendText(StringBuilder stringBuilder) From 0372dd914cf45d70b21144ee046b58eddc432bea Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 22 Jul 2022 19:16:10 +0200 Subject: [PATCH 15/97] Show IBeam cursor for IsTextSelectionEnabled --- src/Avalonia.Controls/RichTextBlock.cs | 2 +- .../Avalonia.Themes.Default.csproj | 8 ++++++++ .../Controls/RichTextBlock.xaml | 10 ++++++++++ src/Avalonia.Themes.Default/DefaultTheme.xaml | 1 + .../Avalonia.Themes.Fluent.csproj | 8 ++++++++ .../Controls/FluentControls.xaml | 1 + src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml | 10 ++++++++++ 7 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml create mode 100644 src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 5006e8133b..0c8b1d125d 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -66,7 +66,7 @@ namespace Avalonia.Controls { FocusableProperty.OverrideDefaultValue(typeof(RichTextBlock), true); - AffectsRender(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty); + AffectsRender(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty, IsTextSelectionEnabledProperty); } public RichTextBlock() diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index 40ed4a0f87..ae0df89c60 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -9,6 +9,14 @@ + + + + + + MSBuild:Compile + + diff --git a/src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml new file mode 100644 index 0000000000..d7bf6e5cf9 --- /dev/null +++ b/src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 468b723f5b..f266402aef 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -66,4 +66,5 @@ + diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj index 35603fe216..8acf12a0ff 100644 --- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj +++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj @@ -10,6 +10,14 @@ + + + + + + MSBuild:Compile + + diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 5b217e4764..eb2d65150c 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -67,4 +67,5 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml new file mode 100644 index 0000000000..d7bf6e5cf9 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml @@ -0,0 +1,10 @@ + + + + + + + From 03b8b2eb5eb1e72523c7edbf6daf48f9f5e74839 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 22 Jul 2022 19:45:25 +0200 Subject: [PATCH 16/97] Show ellipsis when MaxLines are reached --- samples/Sandbox/MainWindow.axaml | 1 - src/Avalonia.Base/Media/TextFormatting/TextLayout.cs | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index c305c57efb..6929f192c7 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,5 +1,4 @@ - diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index 8ab9591faf..f3e8b5969c 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -479,6 +479,11 @@ namespace Avalonia.Media.TextFormatting //Fulfill max lines constraint if (MaxLines > 0 && textLines.Count >= MaxLines) { + if(textLine.TextLineBreak is TextLineBreak lineBreak && lineBreak.RemainingRuns != null) + { + textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width)); + } + break; } } From 6e59fe347e484fe758dcdcd27f5af68db6bc4fc7 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 21 Jul 2022 23:51:06 -0400 Subject: [PATCH 17/97] Add DataGrid fluent controltheme # Conflicts: # src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml --- .../ControlCatalog/Pages/DataGridPage.xaml | 9 +- .../DataGridCell.cs | 10 +- .../DataGridColumn.cs | 20 + .../DataGridRowGroupHeader.cs | 1 - .../DataGridTextColumn.cs | 28 +- .../Themes/Fluent.xaml | 1078 ++++++++--------- 6 files changed, 559 insertions(+), 587 deletions(-) diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index 31b4039d33..27272a9ff7 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -10,11 +10,11 @@ + + + - @@ -54,7 +54,8 @@ - diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs index f9685b3682..dd802678d4 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls /// Represents an individual cell. /// [TemplatePart(DATAGRIDCELL_elementRightGridLine, typeof(Rectangle))] - [PseudoClasses(":selected", ":current", ":edited", ":invalid")] + [PseudoClasses(":selected", ":current", ":edited", ":invalid", ":focus")] public class DataGridCell : ContentControl { private const string DATAGRIDCELL_elementRightGridLine = "PART_RightGridLine"; @@ -216,6 +216,8 @@ namespace Avalonia.Controls PseudoClasses.Set(":edited", IsEdited); PseudoClasses.Set(":invalid", !IsValid); + + PseudoClasses.Set(":focus", OwningGrid.IsFocused && IsCurrent); } // Makes sure the right gridline has the proper stroke and visibility. If lastVisibleColumn is specified, the @@ -245,9 +247,15 @@ namespace Avalonia.Controls if (column == null) { Classes.Clear(); + ClearValue(ThemeProperty); } else { + if (Theme != column.CellTheme) + { + Theme = column.CellTheme; + } + Classes.Replace(column.CellStyleClasses); } } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index e57d6bbde2..fbdb979e24 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -16,6 +16,7 @@ using Avalonia.Controls.Templates; using Avalonia.Controls.Utils; using Avalonia.Layout; using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Styling; namespace Avalonia.Controls { @@ -36,6 +37,7 @@ namespace Avalonia.Controls private IControl _editingElement; private ICellEditBinding _editBinding; private IBinding _clipboardContentBinding; + private ControlTheme _cellTheme; private readonly Classes _cellStyleClasses = new Classes(); /// @@ -399,6 +401,24 @@ namespace Avalonia.Controls } } } + + /// + /// Backing field for CellTheme property. + /// + public static readonly DirectProperty CellThemeProperty = + AvaloniaProperty.RegisterDirect( + nameof(CellTheme), + o => o.CellTheme, + (o, v) => o.CellTheme = v); + + /// + /// Gets or sets the cell theme. + /// + public ControlTheme CellTheme + { + get { return _cellTheme; } + set { SetAndRaise(CellThemeProperty, ref _cellTheme, value); } + } /// /// Backing field for Header property diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index 9f37e8a6aa..d63c738133 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -113,7 +113,6 @@ namespace Avalonia.Controls /// public DataGridRowGroupHeader() { - //DefaultStyleKey = typeof(DataGridRowGroupHeader); AddHandler(InputElement.PointerPressedEvent, (s, e) => DataGridRowGroupHeader_PointerPressed(e), handledEventsToo: true); } diff --git a/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs index bb8637cda2..79247aa87f 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs @@ -11,6 +11,7 @@ using System.ComponentModel; using Avalonia.Layout; using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Controls.Documents; +using Avalonia.Styling; namespace Avalonia.Controls { @@ -19,12 +20,20 @@ namespace Avalonia.Controls /// public class DataGridTextColumn : DataGridBoundColumn { + private readonly Lazy _cellTextBoxTheme; + private readonly Lazy _cellTextBlockTheme; + /// /// Initializes a new instance of the class. /// public DataGridTextColumn() { BindingTarget = TextBox.TextProperty; + + _cellTextBoxTheme = new Lazy(() => + OwningGrid.TryFindResource("DataGridCellTextBoxTheme", out var theme) ? (ControlTheme)theme : null); + _cellTextBlockTheme = new Lazy(() => + OwningGrid.TryFindResource("DataGridCellTextBlockTheme", out var theme) ? (ControlTheme)theme : null); } /// @@ -156,16 +165,19 @@ namespace Avalonia.Controls protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem) { var textBox = new TextBox - { - VerticalAlignment = VerticalAlignment.Stretch, - Background = new SolidColorBrush(Colors.Transparent) + { + Name = "CellTextBox" }; + if (_cellTextBoxTheme.Value is { } theme) + { + textBox.Theme = theme; + } SyncProperties(textBox); return textBox; } - + /// /// Gets a read-only element that is bound to the column's property value. /// @@ -174,10 +186,14 @@ namespace Avalonia.Controls /// A new, read-only element that is bound to the column's property value. protected override IControl GenerateElement(DataGridCell cell, object dataItem) { - TextBlock textBlockElement = new TextBlock + var textBlockElement = new TextBlock { Name = "CellTextBlock" }; + if (_cellTextBlockTheme.Value is { } theme) + { + textBlockElement.Theme = theme; + } SyncProperties(textBlockElement); @@ -227,7 +243,7 @@ namespace Avalonia.Controls { if (element == null) { - throw new ArgumentNullException("element"); + throw new ArgumentNullException(nameof(element)); } if (element is AvaloniaObject content) diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index fb032fec3e..a80cc2173c 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -1,8 +1,8 @@ - + 0.6 0.8 - 12,0,12,0 M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z @@ -13,10 +13,12 @@ - + - + @@ -27,14 +29,12 @@ - + - - - - + @@ -44,157 +44,425 @@ Opacity="0.4" Color="{DynamicResource SystemBaseMediumLowColor}" /> - + - - + + + + + + + + + + + + + + + - + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fill="{TemplateBinding SeparatorBrush}" + IsVisible="{TemplateBinding AreSeparatorsVisible}" /> - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + From f0f69f1b5efa0365d607a3b93c18490960f59c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Sun, 24 Jul 2022 17:53:56 +0200 Subject: [PATCH 18/97] Fix render demo theme --- samples/RenderDemo/App.xaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/samples/RenderDemo/App.xaml b/samples/RenderDemo/App.xaml index 66d97a6444..8ea792de19 100644 --- a/samples/RenderDemo/App.xaml +++ b/samples/RenderDemo/App.xaml @@ -3,6 +3,12 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - + + + + + + + From 11e3691b83d9bd3309b405fe485aa249fa8a471f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Sun, 24 Jul 2022 18:00:18 +0200 Subject: [PATCH 19/97] Use fluent theme for BindingDemo --- samples/BindingDemo/App.xaml | 4 ++-- samples/BindingDemo/BindingDemo.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/BindingDemo/App.xaml b/samples/BindingDemo/App.xaml index 9260dd280f..3e312c8685 100644 --- a/samples/BindingDemo/App.xaml +++ b/samples/BindingDemo/App.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="BindingDemo.App"> - + - \ No newline at end of file + diff --git a/samples/BindingDemo/BindingDemo.csproj b/samples/BindingDemo/BindingDemo.csproj index bd6054327f..056d3bf552 100644 --- a/samples/BindingDemo/BindingDemo.csproj +++ b/samples/BindingDemo/BindingDemo.csproj @@ -5,7 +5,7 @@ - + From df26a70594a20e59fe2ac80c9b140f71335d2fbc Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 24 Jul 2022 15:54:41 -0400 Subject: [PATCH 20/97] Convert ColorPicker Fluent styles to ControlThemes --- .../Themes/Default/Default.xaml | 4 + .../Themes/Fluent/ColorPicker.xaml | 25 +- .../Themes/Fluent/ColorPreviewer.xaml | 23 +- .../Themes/Fluent/ColorSlider.xaml | 346 +++++++++--------- .../Themes/Fluent/ColorSpectrum.xaml | 100 ++--- .../Themes/Fluent/ColorView.xaml | 165 +++++---- .../Themes/Fluent/Fluent.xaml | 76 ++-- 7 files changed, 375 insertions(+), 364 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml index ebecd65cc3..db1fa3ee4e 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml @@ -37,4 +37,8 @@ + + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index a4d5ff673f..907b00dfff 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -1,14 +1,13 @@ - + - - - 5,5,0,0 - + + 5,5,0,0 - + - + --> - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml index 74f33d1258..1c4a44d081 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml @@ -1,15 +1,14 @@ - + - - - 80 - 40 - + + 80 + 40 - + - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml index 162ac372de..eb67ada334 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml @@ -1,188 +1,190 @@ - + - + - + - + - - + + + + + + - - - - + + - - + - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml index dba4ad19f5..f95721c881 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml @@ -1,9 +1,10 @@ - + - - - - + + + - - - + + + - - - - - + + + + + - - + + - - - + + + - + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index e25e822f3f..993745b1e5 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -1,86 +1,85 @@ - + - - - - - - - 48 - 30 - 80 - - - M3 2C3.27614 2 3.5 2.22386 3.5 2.5V5.5C3.5 5.77614 3.72386 6 4 6H16C16.2761 6 16.5 5.77614 - 16.5 5.5V2.5C16.5 2.22386 16.7239 2 17 2C17.2761 2 17.5 2.22386 17.5 2.5V5.5C17.5 6.32843 - 16.8284 7 16 7H15.809L12.2236 14.1708C12.0615 14.4951 11.7914 14.7431 11.4695 - 14.8802C11.4905 15.0808 11.5 15.2891 11.5 15.5C11.5 16.0818 11.4278 16.6623 11.2268 - 17.1165C11.019 17.5862 10.6266 18 10 18C9.37343 18 8.98105 17.5862 8.77323 17.1165C8.57222 - 16.6623 8.5 16.0818 8.5 15.5C8.5 15.2891 8.50952 15.0808 8.53051 14.8802C8.20863 14.7431 - 7.93851 14.4951 7.77639 14.1708L4.19098 7H4C3.17157 7 2.5 6.32843 2.5 5.5V2.5C2.5 2.22386 - 2.72386 2 3 2ZM9.11803 14H10.882C11.0714 14 11.2445 13.893 11.3292 13.7236L14.691 - 7H5.30902L8.67082 13.7236C8.75552 13.893 8.92865 14 9.11803 14ZM9.52346 15C9.50787 15.1549 - 9.5 15.3225 9.5 15.5C9.5 16.0228 9.56841 16.4423 9.6877 16.7119C9.8002 16.9661 9.90782 17 - 10 17C10.0922 17 10.1998 16.9661 10.3123 16.7119C10.4316 16.4423 10.5 16.0228 10.5 - 15.5C10.5 15.3225 10.4921 15.1549 10.4765 15H9.52346Z - - - - M9.75003 6.5C10.1642 6.5 10.5 6.16421 10.5 5.75C10.5 5.33579 10.1642 5 9.75003 5C9.33582 - 5 9.00003 5.33579 9.00003 5.75C9.00003 6.16421 9.33582 6.5 9.75003 6.5ZM12.75 7.5C13.1642 - 7.5 13.5 7.16421 13.5 6.75C13.5 6.33579 13.1642 6 12.75 6C12.3358 6 12 6.33579 12 6.75C12 - 7.16421 12.3358 7.5 12.75 7.5ZM15.25 9C15.25 9.41421 14.9142 9.75 14.5 9.75C14.0858 9.75 - 13.75 9.41421 13.75 9C13.75 8.58579 14.0858 8.25 14.5 8.25C14.9142 8.25 15.25 8.58579 - 15.25 9ZM14.5 12.75C14.9142 12.75 15.25 12.4142 15.25 12C15.25 11.5858 14.9142 11.25 14.5 - 11.25C14.0858 11.25 13.75 11.5858 13.75 12C13.75 12.4142 14.0858 12.75 14.5 12.75ZM13.25 - 14C13.25 14.4142 12.9142 14.75 12.5 14.75C12.0858 14.75 11.75 14.4142 11.75 14C11.75 - 13.5858 12.0858 13.25 12.5 13.25C12.9142 13.25 13.25 13.5858 13.25 14ZM13.6972 - 2.99169C10.9426 1.57663 8.1432 1.7124 5.77007 3.16636C4.55909 3.9083 3.25331 5.46925 - 2.51605 7.05899C2.14542 7.85816 1.89915 8.70492 1.90238 9.49318C1.90566 10.2941 2.16983 - 11.0587 2.84039 11.6053C3.45058 12.1026 3.98165 12.353 4.49574 12.3784C5.01375 12.404 - 5.41804 12.1942 5.73429 12.0076C5.80382 11.9666 5.86891 11.927 5.93113 11.8892C6.17332 - 11.7421 6.37205 11.6214 6.62049 11.5426C6.90191 11.4534 7.2582 11.4205 7.77579 - 11.5787C7.96661 11.637 8.08161 11.7235 8.16212 11.8229C8.24792 11.9289 8.31662 12.0774 - 8.36788 12.2886C8.41955 12.5016 8.44767 12.7527 8.46868 13.0491C8.47651 13.1594 8.48379 - 13.2855 8.49142 13.4176C8.50252 13.6098 8.51437 13.8149 8.52974 14.0037C8.58435 14.6744 - 8.69971 15.4401 9.10362 16.1357C9.51764 16.8488 10.2047 17.439 11.307 17.8158C12.9093 - 18.3636 14.3731 17.9191 15.5126 17.0169C16.6391 16.125 17.4691 14.7761 17.8842 - 13.4272C19.1991 9.15377 17.6728 5.03394 13.6972 2.99169ZM6.29249 4.01905C8.35686 2.75426 - 10.7844 2.61959 13.2403 3.88119C16.7473 5.68275 18.1135 9.28161 16.9284 13.1332C16.5624 - 14.3227 15.8338 15.4871 14.8919 16.2329C13.963 16.9684 12.8486 17.286 11.6305 - 16.8696C10.7269 16.5607 10.2467 16.1129 9.96842 15.6336C9.68001 15.1369 9.57799 14.5556 - 9.52644 13.9225C9.51101 13.733 9.50132 13.5621 9.49147 13.3884C9.48399 13.2564 9.47642 - 13.1229 9.46618 12.9783C9.44424 12.669 9.41175 12.3499 9.33968 12.0529C9.26719 11.7541 - 9.14902 11.4527 8.93935 11.1937C8.72439 10.9282 8.43532 10.7346 8.06801 10.6223C7.36648 - 10.408 6.80266 10.4359 6.31839 10.5893C5.94331 10.7082 5.62016 10.9061 5.37179 - 11.0582C5.31992 11.0899 5.2713 11.1197 5.22616 11.1463C4.94094 11.3146 4.75357 11.39 - 4.54514 11.3796C4.33279 11.3691 4.00262 11.2625 3.47218 10.8301C3.0866 10.5158 2.90473 - 10.0668 2.90237 9.48908C2.89995 8.89865 3.08843 8.20165 3.42324 7.47971C4.09686 6.0272 - 5.28471 4.63649 6.29249 4.01905Z - - - - M14.95 5C14.7184 3.85888 13.7095 3 12.5 3C11.2905 3 10.2816 3.85888 10.05 5H2.5C2.22386 - 5 2 5.22386 2 5.5C2 5.77614 2.22386 6 2.5 6H10.05C10.2816 7.14112 11.2905 8 12.5 8C13.7297 - 8 14.752 7.11217 14.961 5.94254C14.9575 5.96177 14.9539 5.98093 14.95 6H17.5C17.7761 6 18 - 5.77614 18 5.5C18 5.22386 17.7761 5 17.5 5H14.95ZM12.5 7C11.6716 7 11 6.32843 11 5.5C11 - 4.67157 11.6716 4 12.5 4C13.3284 4 14 4.67157 14 5.5C14 6.32843 13.3284 7 12.5 7ZM9.94999 - 14C9.71836 12.8589 8.70948 12 7.5 12C6.29052 12 5.28164 12.8589 5.05001 14H2.5C2.22386 - 14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H5.05001C5.28164 16.1411 6.29052 17 7.5 - 17C8.70948 17 9.71836 16.1411 9.94999 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239 - 17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5 - 13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z - - + + + + + + 48 + 30 + 80 + + + M3 2C3.27614 2 3.5 2.22386 3.5 2.5V5.5C3.5 5.77614 3.72386 6 4 6H16C16.2761 6 16.5 5.77614 + 16.5 5.5V2.5C16.5 2.22386 16.7239 2 17 2C17.2761 2 17.5 2.22386 17.5 2.5V5.5C17.5 6.32843 + 16.8284 7 16 7H15.809L12.2236 14.1708C12.0615 14.4951 11.7914 14.7431 11.4695 + 14.8802C11.4905 15.0808 11.5 15.2891 11.5 15.5C11.5 16.0818 11.4278 16.6623 11.2268 + 17.1165C11.019 17.5862 10.6266 18 10 18C9.37343 18 8.98105 17.5862 8.77323 17.1165C8.57222 + 16.6623 8.5 16.0818 8.5 15.5C8.5 15.2891 8.50952 15.0808 8.53051 14.8802C8.20863 14.7431 + 7.93851 14.4951 7.77639 14.1708L4.19098 7H4C3.17157 7 2.5 6.32843 2.5 5.5V2.5C2.5 2.22386 + 2.72386 2 3 2ZM9.11803 14H10.882C11.0714 14 11.2445 13.893 11.3292 13.7236L14.691 + 7H5.30902L8.67082 13.7236C8.75552 13.893 8.92865 14 9.11803 14ZM9.52346 15C9.50787 15.1549 + 9.5 15.3225 9.5 15.5C9.5 16.0228 9.56841 16.4423 9.6877 16.7119C9.8002 16.9661 9.90782 17 + 10 17C10.0922 17 10.1998 16.9661 10.3123 16.7119C10.4316 16.4423 10.5 16.0228 10.5 + 15.5C10.5 15.3225 10.4921 15.1549 10.4765 15H9.52346Z + + + + M9.75003 6.5C10.1642 6.5 10.5 6.16421 10.5 5.75C10.5 5.33579 10.1642 5 9.75003 5C9.33582 + 5 9.00003 5.33579 9.00003 5.75C9.00003 6.16421 9.33582 6.5 9.75003 6.5ZM12.75 7.5C13.1642 + 7.5 13.5 7.16421 13.5 6.75C13.5 6.33579 13.1642 6 12.75 6C12.3358 6 12 6.33579 12 6.75C12 + 7.16421 12.3358 7.5 12.75 7.5ZM15.25 9C15.25 9.41421 14.9142 9.75 14.5 9.75C14.0858 9.75 + 13.75 9.41421 13.75 9C13.75 8.58579 14.0858 8.25 14.5 8.25C14.9142 8.25 15.25 8.58579 + 15.25 9ZM14.5 12.75C14.9142 12.75 15.25 12.4142 15.25 12C15.25 11.5858 14.9142 11.25 14.5 + 11.25C14.0858 11.25 13.75 11.5858 13.75 12C13.75 12.4142 14.0858 12.75 14.5 12.75ZM13.25 + 14C13.25 14.4142 12.9142 14.75 12.5 14.75C12.0858 14.75 11.75 14.4142 11.75 14C11.75 + 13.5858 12.0858 13.25 12.5 13.25C12.9142 13.25 13.25 13.5858 13.25 14ZM13.6972 + 2.99169C10.9426 1.57663 8.1432 1.7124 5.77007 3.16636C4.55909 3.9083 3.25331 5.46925 + 2.51605 7.05899C2.14542 7.85816 1.89915 8.70492 1.90238 9.49318C1.90566 10.2941 2.16983 + 11.0587 2.84039 11.6053C3.45058 12.1026 3.98165 12.353 4.49574 12.3784C5.01375 12.404 + 5.41804 12.1942 5.73429 12.0076C5.80382 11.9666 5.86891 11.927 5.93113 11.8892C6.17332 + 11.7421 6.37205 11.6214 6.62049 11.5426C6.90191 11.4534 7.2582 11.4205 7.77579 + 11.5787C7.96661 11.637 8.08161 11.7235 8.16212 11.8229C8.24792 11.9289 8.31662 12.0774 + 8.36788 12.2886C8.41955 12.5016 8.44767 12.7527 8.46868 13.0491C8.47651 13.1594 8.48379 + 13.2855 8.49142 13.4176C8.50252 13.6098 8.51437 13.8149 8.52974 14.0037C8.58435 14.6744 + 8.69971 15.4401 9.10362 16.1357C9.51764 16.8488 10.2047 17.439 11.307 17.8158C12.9093 + 18.3636 14.3731 17.9191 15.5126 17.0169C16.6391 16.125 17.4691 14.7761 17.8842 + 13.4272C19.1991 9.15377 17.6728 5.03394 13.6972 2.99169ZM6.29249 4.01905C8.35686 2.75426 + 10.7844 2.61959 13.2403 3.88119C16.7473 5.68275 18.1135 9.28161 16.9284 13.1332C16.5624 + 14.3227 15.8338 15.4871 14.8919 16.2329C13.963 16.9684 12.8486 17.286 11.6305 + 16.8696C10.7269 16.5607 10.2467 16.1129 9.96842 15.6336C9.68001 15.1369 9.57799 14.5556 + 9.52644 13.9225C9.51101 13.733 9.50132 13.5621 9.49147 13.3884C9.48399 13.2564 9.47642 + 13.1229 9.46618 12.9783C9.44424 12.669 9.41175 12.3499 9.33968 12.0529C9.26719 11.7541 + 9.14902 11.4527 8.93935 11.1937C8.72439 10.9282 8.43532 10.7346 8.06801 10.6223C7.36648 + 10.408 6.80266 10.4359 6.31839 10.5893C5.94331 10.7082 5.62016 10.9061 5.37179 + 11.0582C5.31992 11.0899 5.2713 11.1197 5.22616 11.1463C4.94094 11.3146 4.75357 11.39 + 4.54514 11.3796C4.33279 11.3691 4.00262 11.2625 3.47218 10.8301C3.0866 10.5158 2.90473 + 10.0668 2.90237 9.48908C2.89995 8.89865 3.08843 8.20165 3.42324 7.47971C4.09686 6.0272 + 5.28471 4.63649 6.29249 4.01905Z + + + + M14.95 5C14.7184 3.85888 13.7095 3 12.5 3C11.2905 3 10.2816 3.85888 10.05 5H2.5C2.22386 + 5 2 5.22386 2 5.5C2 5.77614 2.22386 6 2.5 6H10.05C10.2816 7.14112 11.2905 8 12.5 8C13.7297 + 8 14.752 7.11217 14.961 5.94254C14.9575 5.96177 14.9539 5.98093 14.95 6H17.5C17.7761 6 18 + 5.77614 18 5.5C18 5.22386 17.7761 5 17.5 5H14.95ZM12.5 7C11.6716 7 11 6.32843 11 5.5C11 + 4.67157 11.6716 4 12.5 4C13.3284 4 14 4.67157 14 5.5C14 6.32843 13.3284 7 12.5 7ZM9.94999 + 14C9.71836 12.8589 8.70948 12 7.5 12C6.29052 12 5.28164 12.8589 5.05001 14H2.5C2.22386 + 14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H5.05001C5.28164 16.1411 6.29052 17 7.5 + 17C8.70948 17 9.71836 16.1411 9.94999 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239 + 17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5 + 13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z + - + - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml index 186b6de9bc..a5a94a2322 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml @@ -3,42 +3,48 @@ xmlns:converters="using:Avalonia.Controls.Converters"> - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - + - - - + + + + + + + + + + + From 952b80df2ae7ef2d3086d293a61516d9a0f3eaaa Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 25 Jul 2022 00:52:54 +0300 Subject: [PATCH 21/97] Use codegen to populate OpenGL functions --- .../ControlCatalog/Pages/OpenGlPage.xaml.cs | 44 +- .../Angle/AngleWin32EglDisplay.cs | 4 +- src/Avalonia.OpenGL/Avalonia.OpenGL.csproj | 1 + .../Controls/OpenGlControlBase.cs | 14 +- src/Avalonia.OpenGL/Egl/EglContext.cs | 2 +- src/Avalonia.OpenGL/Egl/EglDisplay.cs | 4 +- src/Avalonia.OpenGL/Egl/EglInterface.cs | 225 +++----- src/Avalonia.OpenGL/GlBasicInfoInterface.cs | 44 +- src/Avalonia.OpenGL/GlEntryPointAttribute.cs | 103 +--- src/Avalonia.OpenGL/GlInterface.cs | 494 ++++++++---------- src/Avalonia.OpenGL/GlInterfaceBase.cs | 79 --- src/Avalonia.X11/Avalonia.X11.csproj | 2 +- src/Avalonia.X11/Glx/Glx.cs | 111 ++-- .../Output/DrmOutput.cs | 2 +- src/Shared/SourceGeneratorAttributes.cs | 24 + .../Gpu/OpenGl/FboSkiaSurface.cs | 28 +- .../Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs | 2 +- .../Gpu/OpenGl/OpenGlBitmapImpl.cs | 11 +- .../GetProcAddressInitialization.cs | 346 ++++++++++++ src/tools/DevGenerators/Helpers.cs | 31 ++ 20 files changed, 821 insertions(+), 750 deletions(-) delete mode 100644 src/Avalonia.OpenGL/GlInterfaceBase.cs create mode 100644 src/tools/DevGenerators/GetProcAddressInitialization.cs create mode 100644 src/tools/DevGenerators/Helpers.cs diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs index cb79bf219a..f37e0f8701 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs @@ -90,7 +90,6 @@ namespace ControlCatalog.Pages private int _vertexBufferObject; private int _indexBufferObject; private int _vertexArrayObject; - private GlExtrasInterface _glExt; private string GetShader(bool fragment, string shader) { @@ -258,7 +257,6 @@ namespace ControlCatalog.Pages protected unsafe override void OnOpenGlInit(GlInterface GL, int fb) { CheckError(GL); - _glExt = new GlExtrasInterface(GL); Info = $"Renderer: {GL.GetString(GL_RENDERER)} Version: {GL.GetString(GL_VERSION)}"; @@ -298,8 +296,8 @@ namespace ControlCatalog.Pages GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata), GL_STATIC_DRAW); CheckError(GL); - _vertexArrayObject = _glExt.GenVertexArray(); - _glExt.BindVertexArray(_vertexArrayObject); + _vertexArrayObject = GL.GenVertexArray(); + GL.BindVertexArray(_vertexArrayObject); CheckError(GL); GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT, 0, vertexSize, IntPtr.Zero); @@ -316,12 +314,13 @@ namespace ControlCatalog.Pages // Unbind everything GL.BindBuffer(GL_ARRAY_BUFFER, 0); GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - _glExt.BindVertexArray(0); + GL.BindVertexArray(0); GL.UseProgram(0); // Delete all resources. - GL.DeleteBuffers(2, new[] { _vertexBufferObject, _indexBufferObject }); - _glExt.DeleteVertexArrays(1, new[] { _vertexArrayObject }); + GL.DeleteBuffer(_vertexBufferObject); + GL.DeleteBuffer(_indexBufferObject); + GL.DeleteVertexArray(_vertexArrayObject); GL.DeleteProgram(_shaderProgram); GL.DeleteShader(_fragmentShader); GL.DeleteShader(_vertexShader); @@ -338,7 +337,7 @@ namespace ControlCatalog.Pages GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); - _glExt.BindVertexArray(_vertexArrayObject); + GL.BindVertexArray(_vertexArrayObject); GL.UseProgram(_shaderProgram); CheckError(GL); var projection = @@ -369,34 +368,5 @@ namespace ControlCatalog.Pages if (_disco > 0.01) Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); } - - class GlExtrasInterface : GlInterfaceBase - { - public GlExtrasInterface(GlInterface gl) : base(gl.GetProcAddress, gl.ContextInfo) - { - } - - public delegate void GlDeleteVertexArrays(int count, int[] buffers); - [GlMinVersionEntryPoint("glDeleteVertexArrays", 3,0)] - [GlExtensionEntryPoint("glDeleteVertexArraysOES", "GL_OES_vertex_array_object")] - public GlDeleteVertexArrays DeleteVertexArrays { get; } - - public delegate void GlBindVertexArray(int array); - [GlMinVersionEntryPoint("glBindVertexArray", 3,0)] - [GlExtensionEntryPoint("glBindVertexArrayOES", "GL_OES_vertex_array_object")] - public GlBindVertexArray BindVertexArray { get; } - public delegate void GlGenVertexArrays(int n, int[] rv); - - [GlMinVersionEntryPoint("glGenVertexArrays",3,0)] - [GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")] - public GlGenVertexArrays GenVertexArrays { get; } - - public int GenVertexArray() - { - var rv = new int[1]; - GenVertexArrays(1, rv); - return rv[0]; - } - } } } diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs index 94e7728d68..3a8fdb8491 100644 --- a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs +++ b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs @@ -21,7 +21,7 @@ namespace Avalonia.OpenGL.Angle var display = IntPtr.Zero; AngleOptions.PlatformApi angleApi = default; { - if (_egl.GetPlatformDisplayEXT == null) + if (!_egl.IsGetPlatformDisplayExtAvailable) throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll"); var allowedApis = AvaloniaLocator.Current.GetService()?.AllowedPlatformApis @@ -37,7 +37,7 @@ namespace Avalonia.OpenGL.Angle else continue; - display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, + display = _egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE }); if (display != IntPtr.Zero) { diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj index 76924d060f..bacb10c792 100644 --- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj +++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj @@ -11,4 +11,5 @@ + diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index b3469c212b..68442c1fd3 100644 --- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs @@ -66,11 +66,9 @@ namespace Avalonia.OpenGL.Controls return; gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer); - if (_depthBuffer != 0) gl.DeleteRenderbuffers(1, new[] { _depthBuffer }); + if (_depthBuffer != 0) gl.DeleteRenderbuffer(_depthBuffer); - var oneArr = new int[1]; - gl.GenRenderbuffers(1, oneArr); - _depthBuffer = oneArr[0]; + _depthBuffer = gl.GenRenderbuffer(); gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer); gl.RenderbufferStorage(GL_RENDERBUFFER, GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT, @@ -88,9 +86,9 @@ namespace Avalonia.OpenGL.Controls var gl = _context.GlInterface; gl.BindTexture(GL_TEXTURE_2D, 0); gl.BindFramebuffer(GL_FRAMEBUFFER, 0); - gl.DeleteFramebuffers(1, new[] { _fb }); + gl.DeleteFramebuffer(_fb); _fb = 0; - gl.DeleteRenderbuffers(1, new[] { _depthBuffer }); + gl.DeleteRenderbuffer(_depthBuffer); _depthBuffer = 0; _attachment?.Dispose(); _attachment = null; @@ -174,9 +172,7 @@ namespace Avalonia.OpenGL.Controls { _depthBufferSize = GetPixelSize(); var gl = _context.GlInterface; - var oneArr = new int[1]; - gl.GenFramebuffers(1, oneArr); - _fb = oneArr[0]; + _fb = gl.GenFramebuffer(); gl.BindFramebuffer(GL_FRAMEBUFFER, _fb); EnsureDepthBufferAttachment(gl); diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs index bc517c3e27..e7b5dc7c83 100644 --- a/src/Avalonia.OpenGL/Egl/EglContext.cs +++ b/src/Avalonia.OpenGL/Egl/EglContext.cs @@ -24,7 +24,7 @@ namespace Avalonia.OpenGL.Egl SampleCount = sampleCount; StencilSize = stencilSize; using (MakeCurrent()) - GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, b => _egl.GetProcAddress(b)); + GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, _egl.GetProcAddress); } public IntPtr Context { get; } diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs index 623364866b..d3d85e8c83 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs @@ -34,9 +34,9 @@ namespace Avalonia.OpenGL.Egl } else { - if (egl.GetPlatformDisplayEXT == null) + if (!egl.IsGetPlatformDisplayExtAvailable) throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl"); - display = egl.GetPlatformDisplayEXT(platformType, platformDisplay, attrs); + display = egl.GetPlatformDisplayExt(platformType, platformDisplay, attrs); } if (display == IntPtr.Zero) diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs index 6148e58440..4fec8e5356 100644 --- a/src/Avalonia.OpenGL/Egl/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -2,31 +2,26 @@ using System.Runtime.InteropServices; using Avalonia.Platform; using Avalonia.Platform.Interop; +using Avalonia.SourceGenerator; namespace Avalonia.OpenGL.Egl { - public class EglInterface : GlInterfaceBase + public unsafe partial class EglInterface { - public EglInterface() : base(Load()) + public EglInterface(Func getProcAddress) { - - } - - public EglInterface(Func getProcAddress) : base(getProcAddress) - { - + Initialize(getProcAddress); } - public EglInterface(Func getProcAddress) : base(getProcAddress) + public EglInterface(string library) : this(Load(library)) { - } - - public EglInterface(string library) : base(Load(library)) + + public EglInterface() : this(Load()) { + } - static Func Load() { var os = AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem; @@ -46,119 +41,75 @@ namespace Avalonia.OpenGL.Egl } // ReSharper disable UnassignedGetOnlyAutoProperty - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate int EglGetError(); - [GlEntryPoint("eglGetError")] - public EglGetError GetError { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay); - [GlEntryPoint("eglGetDisplay")] - public EglGetDisplay GetDisplay { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr EglGetPlatformDisplayEXT(int platform, IntPtr nativeDisplay, int[] attrs); - [GlEntryPoint("eglGetPlatformDisplayEXT")] - [GlOptionalEntryPoint] - public EglGetPlatformDisplayEXT GetPlatformDisplayEXT { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate bool EglInitialize(IntPtr display, out int major, out int minor); - [GlEntryPoint("eglInitialize")] - public EglInitialize Initialize { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr EglGetProcAddress(Utf8Buffer proc); - [GlEntryPoint("eglGetProcAddress")] - public EglGetProcAddress GetProcAddress { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate bool EglBindApi(int api); - [GlEntryPoint("eglBindAPI")] - public EglBindApi BindApi { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate bool EglChooseConfig(IntPtr display, int[] attribs, - out IntPtr surfaceConfig, int numConfigs, out int choosenConfig); - [GlEntryPoint("eglChooseConfig")] - public EglChooseConfig ChooseConfig { get; } + + [GetProcAddress("eglGetError")] + public partial int GetError(); + + [GetProcAddress("eglGetDisplay")] + public partial IntPtr GetDisplay(IntPtr nativeDisplay); + + [GetProcAddress("eglGetPlatformDisplayEXT", true)] + public partial IntPtr GetPlatformDisplayExt(int platform, IntPtr nativeDisplay, int[] attrs); + + [GetProcAddress("eglInitialize")] + public partial bool Initialize(IntPtr display, out int major, out int minor); + + [GetProcAddress("eglGetProcAddress")] + public partial IntPtr GetProcAddress(IntPtr proc); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr EglCreateContext(IntPtr display, IntPtr config, + [GetProcAddress("eglBindAPI")] + public partial bool BindApi(int api); + + [GetProcAddress("eglChooseConfig")] + public partial bool ChooseConfig(IntPtr display, int[] attribs, + out IntPtr surfaceConfig, int numConfigs, out int choosenConfig); + + [GetProcAddress("eglCreateContext")] + public partial IntPtr CreateContext(IntPtr display, IntPtr config, IntPtr share, int[] attrs); - [GlEntryPoint("eglCreateContext")] - public EglCreateContext CreateContext { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate bool EglDestroyContext(IntPtr display, IntPtr context); - [GlEntryPoint("eglDestroyContext")] - public EglDestroyContext DestroyContext { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr EglCreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs); - [GlEntryPoint("eglCreatePbufferSurface")] - public EglCreatePBufferSurface CreatePBufferSurface { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate bool EglMakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context); - [GlEntryPoint("eglMakeCurrent")] - public EglMakeCurrent MakeCurrent { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr EglGetCurrentContext(); - [GlEntryPoint("eglGetCurrentContext")] - public EglGetCurrentContext GetCurrentContext { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr EglGetCurrentDisplay(); - [GlEntryPoint("eglGetCurrentDisplay")] - public EglGetCurrentContext GetCurrentDisplay { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr EglGetCurrentSurface(int readDraw); - [GlEntryPoint("eglGetCurrentSurface")] - public EglGetCurrentSurface GetCurrentSurface { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void EglDisplaySurfaceVoidDelegate(IntPtr display, IntPtr surface); - [GlEntryPoint("eglDestroySurface")] - public EglDisplaySurfaceVoidDelegate DestroySurface { get; } - - [GlEntryPoint("eglSwapBuffers")] - public EglDisplaySurfaceVoidDelegate SwapBuffers { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr - EglCreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs); - [GlEntryPoint("eglCreateWindowSurface")] - public EglCreateWindowSurface CreateWindowSurface { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate bool EglGetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv); - [GlEntryPoint("eglGetConfigAttrib")] - public EglGetConfigAttrib GetConfigAttrib { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate bool EglWaitGL(); - [GlEntryPoint("eglWaitGL")] - public EglWaitGL WaitGL { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate bool EglWaitClient(); - [GlEntryPoint("eglWaitClient")] - public EglWaitGL WaitClient { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate bool EglWaitNative(int engine); - [GlEntryPoint("eglWaitNative")] - public EglWaitNative WaitNative { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr EglQueryString(IntPtr display, int i); - - [GlEntryPoint("eglQueryString")] - public EglQueryString QueryStringNative { get; } + + [GetProcAddress("eglDestroyContext")] + public partial bool DestroyContext(IntPtr display, IntPtr context); + + [GetProcAddress("eglCreatePbufferSurface")] + public partial IntPtr CreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs); + [GetProcAddress("eglMakeCurrent")] + public partial bool MakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context); + + [GetProcAddress("eglGetCurrentContext")] + public partial IntPtr GetCurrentContext(); + + [GetProcAddress("eglGetCurrentDisplay")] + public partial IntPtr GetCurrentDisplay(); + + [GetProcAddress("eglGetCurrentSurface")] + public partial IntPtr GetCurrentSurface(int readDraw); + + [GetProcAddress("eglDestroySurface")] + public partial void DestroySurface(IntPtr display, IntPtr surface); + + [GetProcAddress("eglSwapBuffers")] + public partial void SwapBuffers(IntPtr display, IntPtr surface); + + [GetProcAddress("eglCreateWindowSurface")] + public partial IntPtr CreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs); + + [GetProcAddress("eglGetConfigAttrib")] + public partial bool GetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv); + + [GetProcAddress("eglWaitGL")] + public partial bool WaitGL(); + + [GetProcAddress("eglWaitClient")] + public partial bool WaitClient(); + + [GetProcAddress("eglWaitNative")] + public partial bool WaitNative(int engine); + + [GetProcAddress("eglQueryString")] + public partial IntPtr QueryStringNative(IntPtr display, int i); + public string QueryString(IntPtr display, int i) { var rv = QueryStringNative(display, i); @@ -166,25 +117,15 @@ namespace Avalonia.OpenGL.Egl return null; return Marshal.PtrToStringAnsi(rv); } + + [GetProcAddress("eglCreatePbufferFromClientBuffer")] + public partial IntPtr CreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list); + + [GetProcAddress("eglQueryDisplayAttribEXT", true)] + public partial bool QueryDisplayAttribExt(IntPtr display, int attr, out IntPtr res); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr EglCreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list); - [GlEntryPoint("eglCreatePbufferFromClientBuffer")] - - public EglCreatePbufferFromClientBuffer CreatePbufferFromClientBuffer { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate bool EglQueryDisplayAttribEXT(IntPtr display, int attr, out IntPtr res); - - [GlEntryPoint("eglQueryDisplayAttribEXT"), GlOptionalEntryPoint] - public EglQueryDisplayAttribEXT QueryDisplayAttribExt { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate bool EglQueryDeviceAttribEXT(IntPtr display, int attr, out IntPtr res); - - [GlEntryPoint("eglQueryDeviceAttribEXT"), GlOptionalEntryPoint] - public EglQueryDisplayAttribEXT QueryDeviceAttribExt { get; } - - // ReSharper restore UnassignedGetOnlyAutoProperty + + [GetProcAddress("eglQueryDeviceAttribEXT", true)] + public partial bool QueryDeviceAttribExt(IntPtr display, int attr, out IntPtr res); } } diff --git a/src/Avalonia.OpenGL/GlBasicInfoInterface.cs b/src/Avalonia.OpenGL/GlBasicInfoInterface.cs index aaba2ec09c..7a7110f15b 100644 --- a/src/Avalonia.OpenGL/GlBasicInfoInterface.cs +++ b/src/Avalonia.OpenGL/GlBasicInfoInterface.cs @@ -3,46 +3,24 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Avalonia.Platform.Interop; +using Avalonia.SourceGenerator; namespace Avalonia.OpenGL { - public class GlBasicInfoInterface : GlBasicInfoInterface + public unsafe partial class GlBasicInfoInterface { - public GlBasicInfoInterface(Func getProcAddress) : base(getProcAddress, null) - { - } - - public GlBasicInfoInterface(Func nativeGetProcAddress) : base(nativeGetProcAddress, null) - { + public GlBasicInfoInterface(Func getProcAddress){ + Initialize(getProcAddress); } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlGetIntegerv(int name, out int rv); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr GlGetString(int v); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr GlGetStringi(int v, int v1); - } - public class GlBasicInfoInterface : GlInterfaceBase - { - public GlBasicInfoInterface(Func getProcAddress, TContextInfo context) : base(getProcAddress, context) - { - } + [GetProcAddress("glGetIntegerv")] + public partial void GetIntegerv(int name, out int rv); - public GlBasicInfoInterface(Func nativeGetProcAddress, TContextInfo context) : base(nativeGetProcAddress, context) - { - } - - [GlEntryPoint("glGetIntegerv")] - public GlBasicInfoInterface.GlGetIntegerv GetIntegerv { get; } - - - [GlEntryPoint("glGetString")] - public GlBasicInfoInterface.GlGetString GetStringNative { get; } - - [GlEntryPoint("glGetStringi")] - public GlBasicInfoInterface.GlGetStringi GetStringiNative { get; } + [GetProcAddress("glGetString")] + public partial IntPtr GetStringNative(int v); + + [GetProcAddress("glGetStringi")] + public partial IntPtr GetStringiNative(int v, int v1); public string GetString(int v) { diff --git a/src/Avalonia.OpenGL/GlEntryPointAttribute.cs b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs index 2ffdaca8fe..3e31de6995 100644 --- a/src/Avalonia.OpenGL/GlEntryPointAttribute.cs +++ b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs @@ -2,117 +2,54 @@ using System; namespace Avalonia.OpenGL { - public interface IGlEntryPointAttribute + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + class GlMinVersionEntryPoint : Attribute { - IntPtr GetProcAddress(Func getProcAddress); - } - - public interface IGlEntryPointAttribute - { - IntPtr GetProcAddress(TContext context, Func getProcAddress); - } - - [AttributeUsage(AttributeTargets.Property)] - public class GlOptionalEntryPoint : Attribute - { - - } - - [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] - public class GlEntryPointAttribute : Attribute, IGlEntryPointAttribute - { - public string[] EntryPoints { get; } - - public GlEntryPointAttribute(string entryPoint) - { - EntryPoints = new []{entryPoint}; - } -/* - public GlEntryPointAttribute(params string[] entryPoints) + public GlMinVersionEntryPoint(string entry, int minVersionMajor, int minVersionMinor) { - EntryPoints = entryPoints; - } -*/ - public IntPtr GetProcAddress(Func getProcAddress) - { - foreach (var name in EntryPoints) - { - var rv = getProcAddress(name); - if (rv != IntPtr.Zero) - return rv; - } - return IntPtr.Zero; - } - } - - [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] - public class GlMinVersionEntryPoint : Attribute, IGlEntryPointAttribute - { - private readonly string _entry; - private readonly GlProfileType? _profile; - private readonly int _minVersionMajor; - private readonly int _minVersionMinor; - - public GlMinVersionEntryPoint(string entry, GlProfileType profile, int minVersionMajor, - int minVersionMinor) - { - _entry = entry; - _profile = profile; - _minVersionMajor = minVersionMajor; - _minVersionMinor = minVersionMinor; } - public GlMinVersionEntryPoint(string entry, int minVersionMajor, - int minVersionMinor) + public GlMinVersionEntryPoint(string entry, int minVersionMajor, int minVersionMinor, GlProfileType profile) { - _entry = entry; - _minVersionMajor = minVersionMajor; - _minVersionMinor = minVersionMinor; } + - public IntPtr GetProcAddress(GlInterface.GlContextInfo context, Func getProcAddress) + public static IntPtr GetProcAddress(Func getProcAddress, GlInterface.GlContextInfo context, + string entry, int minVersionMajor, int minVersionMinor, GlProfileType? profile = null) { - if(_profile.HasValue && context.Version.Type != _profile) + if(profile.HasValue && context.Version.Type != profile) return IntPtr.Zero; - if(context.Version.Major<_minVersionMajor) + if(context.Version.Major + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + class GlExtensionEntryPoint : Attribute { - private readonly string _entry; - private readonly GlProfileType? _profile; - private readonly string _extension; - - public GlExtensionEntryPoint(string entry, GlProfileType profile, string extension) + public GlExtensionEntryPoint(string entry, string extension) { - _entry = entry; - _profile = profile; - _extension = extension; } - public GlExtensionEntryPoint(string entry, string extension) + public GlExtensionEntryPoint(string entry, string extension, GlProfileType profile) { - _entry = entry; - _extension = extension; } - public IntPtr GetProcAddress(GlInterface.GlContextInfo context, Func getProcAddress) + public static IntPtr GetProcAddress(Func getProcAddress, GlInterface.GlContextInfo context, + string entry, string extension, GlProfileType? profile = null) { // Ignore different profile type - if (_profile.HasValue && _profile != context.Version.Type) + if (profile.HasValue && profile != context.Version.Type) return IntPtr.Zero; // Check if extension is supported by the current context - if (!context.Extensions.Contains(_extension)) + if (!context.Extensions.Contains(extension)) return IntPtr.Zero; - return getProcAddress(_entry); + return getProcAddress(entry); } } } diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 18bebe4cb5..d6eff9a35f 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -3,14 +3,14 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using Avalonia.Platform.Interop; +using Avalonia.SourceGenerator; using static Avalonia.OpenGL.GlConsts; namespace Avalonia.OpenGL { - public delegate IntPtr GlGetProcAddressDelegate(string procName); - - public unsafe class GlInterface : GlBasicInfoInterface + public unsafe partial class GlInterface : GlBasicInfoInterface { + private readonly Func _getProcAddress; public string Version { get; } public string Vendor { get; } public string Renderer { get; } @@ -35,12 +35,14 @@ namespace Avalonia.OpenGL } } - private GlInterface(GlContextInfo info, Func getProcAddress) : base(getProcAddress, info) + private GlInterface(GlContextInfo info, Func getProcAddress) : base(getProcAddress) { + _getProcAddress = getProcAddress; ContextInfo = info; Version = GetString(GlConsts.GL_VERSION); Renderer = GetString(GlConsts.GL_RENDERER); - Vendor = GetString(GlConsts.GL_VENDOR); + Vendor = GetString(GlConsts.GL_VENDOR); + Initialize(getProcAddress, ContextInfo); } public GlInterface(GlVersion version, Func getProcAddress) : this( @@ -48,92 +50,58 @@ namespace Avalonia.OpenGL { } - public GlInterface(GlVersion version, Func n) : this(version, ConvertNative(n)) + public IntPtr GetProcAddress(string proc) => _getProcAddress(proc); + + [GetProcAddress("glGetError")] + public partial int GetError(); + + [GetProcAddress("glClearStencil")] + public partial void ClearStencil(int s); + + [GetProcAddress("glClearColor")] + public partial void ClearColor(float r, float g, float b, float a); + + [GetProcAddress("glClear")] + public partial void Clear(int bits); + + [GetProcAddress("glViewport")] + public partial void Viewport(int x, int y, int width, int height); + + [GetProcAddress("glFlush")] + public partial void Flush(); + + [GetProcAddress("glFinish")] + public partial void Finish(); + + [GetProcAddress("glGetIntegerv")] + public partial void GetIntegerv(int name, out int rv); + + [GetProcAddress("glGenFramebuffers")] + public partial void GenFramebuffers(int count, int* res); + + public int GenFramebuffer() { - + int rv = 0; + GenFramebuffers(1, &rv); + return rv; } - public static GlInterface FromNativeUtf8GetProcAddress(GlVersion version, Func getProcAddress) => - new GlInterface(version, getProcAddress); - - - public T GetProcAddress(string proc) => Marshal.GetDelegateForFunctionPointer(GetProcAddress(proc)); - - // ReSharper disable UnassignedGetOnlyAutoProperty - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate int GlGetError(); - [GlEntryPoint("glGetError")] - public GlGetError GetError { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlClearStencil(int s); - [GlEntryPoint("glClearStencil")] - public GlClearStencil ClearStencil { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlClearColor(float r, float g, float b, float a); - [GlEntryPoint("glClearColor")] - public GlClearColor ClearColor { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlClear(int bits); - [GlEntryPoint("glClear")] - public GlClear Clear { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlViewport(int x, int y, int width, int height); - [GlEntryPoint("glViewport")] - public GlViewport Viewport { get; } - - [GlEntryPoint("glFlush")] - public UnmanagedAction Flush { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void UnmanagedAction(); - - [GlEntryPoint("glFinish")] - public UnmanagedAction Finish { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate IntPtr GlGetString(int v); - [GlEntryPoint("glGetString")] - public GlGetString GetStringNative { get; } - - public string GetString(int v) + [GetProcAddress("glDeleteFramebuffers")] + public partial void DeleteFramebuffers(int count, int* framebuffers); + + public void DeleteFramebuffer(int fb) { - var ptr = GetStringNative(v); - if (ptr != IntPtr.Zero) - return Marshal.PtrToStringAnsi(ptr); - return null; + DeleteFramebuffers(1, &fb); } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlGetIntegerv(int name, out int rv); - [GlEntryPoint("glGetIntegerv")] - public GlGetIntegerv GetIntegerv { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlGenFramebuffers(int count, int[] res); - [GlEntryPoint("glGenFramebuffers")] - public GlGenFramebuffers GenFramebuffers { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlDeleteFramebuffers(int count, int[] framebuffers); - [GlEntryPoint("glDeleteFramebuffers")] - public GlDeleteFramebuffers DeleteFramebuffers { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlBindFramebuffer(int target, int fb); - [GlEntryPoint("glBindFramebuffer")] - public GlBindFramebuffer BindFramebuffer { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate int GlCheckFramebufferStatus(int target); - [GlEntryPoint("glCheckFramebufferStatus")] - public GlCheckFramebufferStatus CheckFramebufferStatus { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlBlitFramebuffer(int srcX0, + [GetProcAddress("glBindFramebuffer")] + public partial void BindFramebuffer(int target, int fb); + + [GetProcAddress("glCheckFramebufferStatus")] + public partial int CheckFramebufferStatus(int target); + + [GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0), GetProcAddress(true)] + public partial void BlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, @@ -143,89 +111,78 @@ namespace Avalonia.OpenGL int dstY1, int mask, int filter); - [GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0), GlOptionalEntryPoint] - public GlBlitFramebuffer BlitFramebuffer { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlGenRenderbuffers(int count, int[] res); - [GlEntryPoint("glGenRenderbuffers")] - public GlGenRenderbuffers GenRenderbuffers { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlDeleteRenderbuffers(int count, int[] renderbuffers); - [GlEntryPoint("glDeleteRenderbuffers")] - public GlDeleteTextures DeleteRenderbuffers { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlBindRenderbuffer(int target, int fb); - [GlEntryPoint("glBindRenderbuffer")] - public GlBindRenderbuffer BindRenderbuffer { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlRenderbufferStorage(int target, int internalFormat, int width, int height); - [GlEntryPoint("glRenderbufferStorage")] - public GlRenderbufferStorage RenderbufferStorage { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlFramebufferRenderbuffer(int target, int attachment, + + + [GetProcAddress("glGenRenderbuffers")] + public partial void GenRenderbuffers(int count, int* res); + + public int GenRenderbuffer() + { + int rv = 0; + GenRenderbuffers(1, &rv); + return rv; + } + + [GetProcAddress("glDeleteRenderbuffers")] + public partial void DeleteRenderbuffers(int count, int* renderbuffers); + + public void DeleteRenderbuffer(int renderbuffer) + { + DeleteRenderbuffers(1, &renderbuffer); + } + + [GetProcAddress("glBindRenderbuffer")] + public partial void BindRenderbuffer(int target, int fb); + + [GetProcAddress("glRenderbufferStorage")] + public partial void RenderbufferStorage(int target, int internalFormat, int width, int height); + + [GetProcAddress("glFramebufferRenderbuffer")] + public partial void FramebufferRenderbuffer(int target, int attachment, int renderbufferTarget, int renderbuffer); - [GlEntryPoint("glFramebufferRenderbuffer")] - public GlFramebufferRenderbuffer FramebufferRenderbuffer { get; } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlGenTextures(int count, int[] res); - [GlEntryPoint("glGenTextures")] - public GlGenTextures GenTextures { get; } + [GetProcAddress("glGenTextures")] + public partial void GenTextures(int count, int* res); + + public int GenTexture() + { + int rv = 0; + GenTextures(1, &rv); + return rv; + } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlBindTexture(int target, int fb); - [GlEntryPoint("glBindTexture")] - public GlBindTexture BindTexture { get; } + [GetProcAddress("glBindTexture")] + public partial void BindTexture(int target, int fb); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlActiveTexture(int texture); - [GlEntryPoint("glActiveTexture")] - public GlActiveTexture ActiveTexture { get; } + [GetProcAddress("glActiveTexture")] + public partial void ActiveTexture(int texture); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlDeleteTextures(int count, int[] textures); - [GlEntryPoint("glDeleteTextures")] - public GlDeleteTextures DeleteTextures { get; } + [GetProcAddress("glDeleteTextures")] + public partial void DeleteTextures(int count, int* textures); + public void DeleteTexture(int texture) => DeleteTextures(1, &texture); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlTexImage2D(int target, int level, int internalFormat, int width, int height, int border, + [GetProcAddress("glTexImage2D")] + public partial void TexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntPtr data); - [GlEntryPoint("glTexImage2D")] - public GlTexImage2D TexImage2D { get; } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, + [GetProcAddress("glCopyTexSubImage2D")] + public partial void CopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height); - - [GlEntryPoint("glCopyTexSubImage2D")] - public GlCopyTexSubImage2D CopyTexSubImage2D { get; } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlTexParameteri(int target, int name, int value); - [GlEntryPoint("glTexParameteri")] - public GlTexParameteri TexParameteri { get; } + [GetProcAddress("glTexParameteri")] + public partial void TexParameteri(int target, int name, int value); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlFramebufferTexture2D(int target, int attachment, + + [GetProcAddress("glFramebufferTexture2D")] + public partial void FramebufferTexture2D(int target, int attachment, int texTarget, int texture, int level); - [GlEntryPoint("glFramebufferTexture2D")] - public GlFramebufferTexture2D FramebufferTexture2D { get; } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate int GlCreateShader(int shaderType); - [GlEntryPoint("glCreateShader")] - public GlCreateShader CreateShader { get; } + [GetProcAddress("glCreateShader")] + public partial int CreateShader(int shaderType); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlShaderSource(int shader, int count, IntPtr strings, IntPtr lengths); - [GlEntryPoint("glShaderSource")] - public GlShaderSource ShaderSource { get; } + [GetProcAddress("glShaderSource")] + public partial void ShaderSource(int shader, int count, IntPtr strings, IntPtr lengths); public void ShaderSourceString(int shader, string source) { @@ -237,20 +194,14 @@ namespace Avalonia.OpenGL } } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlCompileShader(int shader); - [GlEntryPoint("glCompileShader")] - public GlCompileShader CompileShader { get; } + [GetProcAddress("glCompileShader")] + public partial void CompileShader(int shader); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlGetShaderiv(int shader, int name, int* parameters); - [GlEntryPoint("glGetShaderiv")] - public GlGetShaderiv GetShaderiv { get; } + [GetProcAddress("glGetShaderiv")] + public partial void GetShaderiv(int shader, int name, int* parameters); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlGetShaderInfoLog(int shader, int maxLength, out int length, void*infoLog); - [GlEntryPoint("glGetShaderInfoLog")] - public GlGetShaderInfoLog GetShaderInfoLog { get; } + [GetProcAddress("glGetShaderInfoLog")] + public partial void GetShaderInfoLog(int shader, int maxLength, out int length, void* infoLog); public unsafe string CompileShaderAndGetError(int shader, string source) { @@ -268,33 +219,24 @@ namespace Avalonia.OpenGL int len; fixed (void* ptr = logData) GetShaderInfoLog(shader, logLength, out len, ptr); - return Encoding.UTF8.GetString(logData,0, len); + return Encoding.UTF8.GetString(logData, 0, len); } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate int GlCreateProgram(); - [GlEntryPoint("glCreateProgram")] - public GlCreateProgram CreateProgram { get; } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlAttachShader(int program, int shader); - [GlEntryPoint("glAttachShader")] - public GlAttachShader AttachShader { get; } + [GetProcAddress("glCreateProgram")] + public partial int CreateProgram(); + + [GetProcAddress("glAttachShader")] + public partial void AttachShader(int program, int shader); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlLinkProgram(int program); - [GlEntryPoint("glLinkProgram")] - public GlLinkProgram LinkProgram { get; } + [GetProcAddress("glLinkProgram")] + public partial void LinkProgram(int program); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlGetProgramiv(int program, int name, int* parameters); - [GlEntryPoint("glGetProgramiv")] - public GlGetProgramiv GetProgramiv { get; } + [GetProcAddress("glGetProgramiv")] + public partial void GetProgramiv(int program, int name, int* parameters); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlGetProgramInfoLog(int program, int maxLength, out int len, void* infoLog); - [GlEntryPoint("glGetProgramInfoLog")] - public GlGetProgramInfoLog GetProgramInfoLog { get; } + [GetProcAddress("glGetProgramInfoLog")] + public partial void GetProgramInfoLog(int program, int maxLength, out int len, void* infoLog); public unsafe string LinkProgramAndGetError(int program) { @@ -309,13 +251,11 @@ namespace Avalonia.OpenGL int len; fixed (void* ptr = logData) GetProgramInfoLog(program, logLength, out len, ptr); - return Encoding.UTF8.GetString(logData,0, len); + return Encoding.UTF8.GetString(logData, 0, len); } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlBindAttribLocation(int program, int index, IntPtr name); - [GlEntryPoint("glBindAttribLocation")] - public GlBindAttribLocation BindAttribLocation { get; } + [GetProcAddress("glBindAttribLocation")] + public partial void BindAttribLocation(int program, int index, IntPtr name); public void BindAttribLocationString(int program, int index, string name) { @@ -323,32 +263,24 @@ namespace Avalonia.OpenGL BindAttribLocation(program, index, b.DangerousGetHandle()); } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlGenBuffers(int len, int[] rv); - [GlEntryPoint("glGenBuffers")] - public GlGenBuffers GenBuffers { get; } + [GetProcAddress("glGenBuffers")] + public partial void GenBuffers(int len, int* rv); public int GenBuffer() { - var rv = new int[1]; - GenBuffers(1, rv); - return rv[0]; + int rv; + GenBuffers(1, &rv); + return rv; } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlBindBuffer(int target, int buffer); - [GlEntryPoint("glBindBuffer")] - public GlBindBuffer BindBuffer { get; } + [GetProcAddress("glBindBuffer")] + public partial void BindBuffer(int target, int buffer); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlBufferData(int target, IntPtr size, IntPtr data, int usage); - [GlEntryPoint("glBufferData")] - public GlBufferData BufferData { get; } + [GetProcAddress("glBufferData")] + public partial void BufferData(int target, IntPtr size, IntPtr data, int usage); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate int GlGetAttribLocation(int program, IntPtr name); - [GlEntryPoint("glGetAttribLocation")] - public GlGetAttribLocation GetAttribLocation { get; } + [GetProcAddress("glGetAttribLocation")] + public partial int GetAttribLocation(int program, IntPtr name); public int GetAttribLocationString(int program, string name) { @@ -356,36 +288,24 @@ namespace Avalonia.OpenGL return GetAttribLocation(program, b.DangerousGetHandle()); } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlVertexAttribPointer(int index, int size, int type, + [GetProcAddress("glVertexAttribPointer")] + public partial void VertexAttribPointer(int index, int size, int type, int normalized, int stride, IntPtr pointer); - [GlEntryPoint("glVertexAttribPointer")] - public GlVertexAttribPointer VertexAttribPointer { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlEnableVertexAttribArray(int index); - [GlEntryPoint("glEnableVertexAttribArray")] - public GlEnableVertexAttribArray EnableVertexAttribArray { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlUseProgram(int program); - [GlEntryPoint("glUseProgram")] - public GlUseProgram UseProgram { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlDrawArrays(int mode, int first, IntPtr count); - [GlEntryPoint("glDrawArrays")] - public GlDrawArrays DrawArrays { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlDrawElements(int mode, int count, int type, IntPtr indices); - [GlEntryPoint("glDrawElements")] - public GlDrawElements DrawElements { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate int GlGetUniformLocation(int program, IntPtr name); - [GlEntryPoint("glGetUniformLocation")] - public GlGetUniformLocation GetUniformLocation { get; } + + [GetProcAddress("glEnableVertexAttribArray")] + public partial void EnableVertexAttribArray(int index); + + [GetProcAddress("glUseProgram")] + public partial void UseProgram(int program); + + [GetProcAddress("glDrawArrays")] + public partial void DrawArrays(int mode, int first, IntPtr count); + + [GetProcAddress("glDrawElements")] + public partial void DrawElements(int mode, int count, int type, IntPtr indices); + + [GetProcAddress("glGetUniformLocation")] + public partial int GetUniformLocation(int program, IntPtr name); public int GetUniformLocationString(int program, string name) { @@ -393,41 +313,65 @@ namespace Avalonia.OpenGL return GetUniformLocation(program, b.DangerousGetHandle()); } - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlUniform1f(int location, float falue); - [GlEntryPoint("glUniform1f")] - public GlUniform1f Uniform1f { get; } - - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlUniformMatrix4fv(int location, int count, bool transpose, void* value); - [GlEntryPoint("glUniformMatrix4fv")] - public GlUniformMatrix4fv UniformMatrix4fv { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlEnable(int what); - [GlEntryPoint("glEnable")] - public GlEnable Enable { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlDeleteBuffers(int count, int[] buffers); - [GlEntryPoint("glDeleteBuffers")] - public GlDeleteBuffers DeleteBuffers { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlDeleteProgram(int program); - [GlEntryPoint("glDeleteProgram")] - public GlDeleteProgram DeleteProgram { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GlDeleteShader(int shader); - [GlEntryPoint("glDeleteShader")] - public GlDeleteShader DeleteShader { get; } - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void GLGetRenderbufferParameteriv(int target, int name, int[] value); - [GlEntryPoint("glGetRenderbufferParameteriv")] - public GLGetRenderbufferParameteriv GetRenderbufferParameteriv { get; } + [GetProcAddress("glUniform1f")] + public partial void Uniform1f(int location, float falue); + + + [GetProcAddress("glUniformMatrix4fv")] + public partial void UniformMatrix4fv(int location, int count, bool transpose, void* value); + + [GetProcAddress("glEnable")] + public partial void Enable(int what); + + [GetProcAddress("glDeleteBuffers")] + public partial void DeleteBuffers(int count, int* buffers); + + public void DeleteBuffer(int buffer) => DeleteBuffers(1, &buffer); + + [GetProcAddress("glDeleteProgram")] + public partial void DeleteProgram(int program); + + [GetProcAddress("glDeleteShader")] + public partial void DeleteShader(int shader); + + [GetProcAddress("glGetRenderbufferParameteriv")] + public partial void GLGetRenderbufferParameteriv(int target, int name, int* value); // ReSharper restore UnassignedGetOnlyAutoProperty + + [GetProcAddress(true)] + [GlMinVersionEntryPoint("glDeleteVertexArrays", 3, 0)] + [GlExtensionEntryPoint("glDeleteVertexArraysOES", "GL_OES_vertex_array_object")] + public partial void DeleteVertexArrays(int count, int* arrays); + + public void DeleteVertexArray(int array) => DeleteVertexArrays(1, &array); + + [GetProcAddress(true)] + [GlMinVersionEntryPoint("glBindVertexArray", 3, 0)] + [GlExtensionEntryPoint("glBindVertexArrayOES", "GL_OES_vertex_array_object")] + public partial void BindVertexArray(int array); + + + [GetProcAddress(true)] + [GlMinVersionEntryPoint("glGenVertexArrays", 3, 0)] + [GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")] + public partial void GenVertexArrays(int n, int* rv); + + public int GenVertexArray() + { + int rv = 0; + GenVertexArrays(1, &rv); + return rv; + } + + public static GlInterface FromNativeUtf8GetProcAddress(GlVersion version, Func getProcAddress) + { + return new GlInterface(version, s => + { + var ptr = Marshal.StringToHGlobalAnsi(s); + var rv = getProcAddress(ptr); + Marshal.FreeHGlobal(ptr); + return rv; + }); + } } -} +} \ No newline at end of file diff --git a/src/Avalonia.OpenGL/GlInterfaceBase.cs b/src/Avalonia.OpenGL/GlInterfaceBase.cs deleted file mode 100644 index e7dd440e36..0000000000 --- a/src/Avalonia.OpenGL/GlInterfaceBase.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using Avalonia.Platform.Interop; - -namespace Avalonia.OpenGL -{ - public class GlInterfaceBase : GlInterfaceBase - { - public GlInterfaceBase(Func getProcAddress) : base(getProcAddress, null) - { - } - - public GlInterfaceBase(Func nativeGetProcAddress) : base(nativeGetProcAddress, null) - { - } - } - - public class GlInterfaceBase - { - private readonly Func _getProcAddress; - public GlInterfaceBase(Func getProcAddress, TContext context) - { - _getProcAddress = getProcAddress; - foreach (var prop in this.GetType().GetProperties()) - { - var attrs = prop.GetCustomAttributes() - .Where(a => - a is IGlEntryPointAttribute || a is IGlEntryPointAttribute) - .ToList(); - if(attrs.Count == 0) - continue; - - var isOptional = prop.GetCustomAttribute() != null; - - var fieldName = $"<{prop.Name}>k__BackingField"; - var field = prop.DeclaringType.GetField(fieldName, - BindingFlags.Instance | BindingFlags.NonPublic); - if (field == null) - throw new InvalidProgramException($"Expected property {prop.Name} to have {fieldName}"); - - - IntPtr proc = IntPtr.Zero; - foreach (var attr in attrs) - { - if (attr is IGlEntryPointAttribute typed) - proc = typed.GetProcAddress(context, getProcAddress); - else if (attr is IGlEntryPointAttribute untyped) - proc = untyped.GetProcAddress(getProcAddress); - if (proc != IntPtr.Zero) - break; - } - - if (proc != IntPtr.Zero) - field.SetValue(this, Marshal.GetDelegateForFunctionPointer(proc, prop.PropertyType)); - else if (!isOptional) - throw new OpenGlException("Unable to find a suitable GL function for " + prop.Name); - } - } - - protected static Func ConvertNative(Func func) => - (proc) => - { - using (var u = new Utf8Buffer(proc)) - { - var rv = func(u); - return rv; - } - }; - - public GlInterfaceBase(Func nativeGetProcAddress, TContext context) : this(ConvertNative(nativeGetProcAddress), context) - { - - } - - public IntPtr GetProcAddress(string proc) => _getProcAddress(proc); - } -} diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj index 45a76bc3d6..621e6fabd7 100644 --- a/src/Avalonia.X11/Avalonia.X11.csproj +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -11,5 +11,5 @@ - + diff --git a/src/Avalonia.X11/Glx/Glx.cs b/src/Avalonia.X11/Glx/Glx.cs index 37ca7f5603..1a26d7d5f5 100644 --- a/src/Avalonia.X11/Glx/Glx.cs +++ b/src/Avalonia.X11/Glx/Glx.cs @@ -4,111 +4,97 @@ using System.Linq; using System.Runtime.InteropServices; using Avalonia.OpenGL; using Avalonia.Platform.Interop; +using Avalonia.SourceGenerator; + // ReSharper disable UnassignedGetOnlyAutoProperty namespace Avalonia.X11.Glx { - unsafe class GlxInterface : GlInterfaceBase + unsafe partial class GlxInterface { private const string libGL = "libGL.so.1"; - [GlEntryPointAttribute("glXMakeContextCurrent")] - public GlxMakeContextCurrent MakeContextCurrent { get; } - public delegate bool GlxMakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context); + [GetProcAddress("glXMakeContextCurrent")] + public partial bool MakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context); - [GlEntryPoint("glXGetCurrentContext")] - public GlxGetCurrentContext GetCurrentContext { get; } - public delegate IntPtr GlxGetCurrentContext(); + [GetProcAddress("glXGetCurrentContext")] + public partial IntPtr GetCurrentContext(); - [GlEntryPoint("glXGetCurrentDisplay")] - public GlxGetCurrentDisplay GetCurrentDisplay { get; } - public delegate IntPtr GlxGetCurrentDisplay(); + [GetProcAddress("glXGetCurrentDisplay")] + public partial IntPtr GetCurrentDisplay(); - [GlEntryPoint("glXGetCurrentDrawable")] - public GlxGetCurrentDrawable GetCurrentDrawable { get; } - public delegate IntPtr GlxGetCurrentDrawable(); + [GetProcAddress("glXGetCurrentDrawable")] + public partial IntPtr GetCurrentDrawable(); - [GlEntryPoint("glXGetCurrentReadDrawable")] - public GlxGetCurrentReadDrawable GetCurrentReadDrawable { get; } - public delegate IntPtr GlxGetCurrentReadDrawable(); + [GetProcAddress("glXGetCurrentReadDrawable")] + public partial IntPtr GetCurrentReadDrawable(); - [GlEntryPoint("glXCreatePbuffer")] - public GlxCreatePbuffer CreatePbuffer { get; } - public delegate IntPtr GlxCreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list); + [GetProcAddress("glXCreatePbuffer")] + public partial IntPtr CreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list); - [GlEntryPoint("glXDestroyPbuffer")] - public GlxDestroyPbuffer DestroyPbuffer { get; } - public delegate IntPtr GlxDestroyPbuffer(IntPtr dpy, IntPtr fb); + [GetProcAddress("glXDestroyPbuffer")] + public partial IntPtr DestroyPbuffer(IntPtr dpy, IntPtr fb); - [GlEntryPointAttribute("glXChooseVisual")] - public GlxChooseVisual ChooseVisual { get; } - public delegate XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList); + [GetProcAddress("glXChooseVisual")] + public partial XVisualInfo* ChooseVisual(IntPtr dpy, int screen, int[] attribList); - [GlEntryPointAttribute("glXCreateContext")] - public GlxCreateContext CreateContext { get; } - public delegate IntPtr GlxCreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct); + [GetProcAddress("glXCreateContext")] + public partial IntPtr CreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct); - [GlEntryPointAttribute("glXCreateContextAttribsARB")] - public GlxCreateContextAttribsARB CreateContextAttribsARB { get; } - public delegate IntPtr GlxCreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList, + [GetProcAddress("glXCreateContextAttribsARB")] + public partial IntPtr CreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList, bool direct, int[] attribs); [DllImport(libGL, EntryPoint = "glXGetProcAddress")] - public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer); + public static extern IntPtr GlxGetProcAddress(string buffer); - [GlEntryPointAttribute("glXDestroyContext")] - public GlxDestroyContext DestroyContext { get; } - public delegate void GlxDestroyContext(IntPtr dpy, IntPtr ctx); + [GetProcAddress("glXDestroyContext")] + public partial void DestroyContext(IntPtr dpy, IntPtr ctx); - [GlEntryPointAttribute("glXChooseFBConfig")] - public GlxChooseFBConfig ChooseFBConfig { get; } - public delegate IntPtr* GlxChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements); + [GetProcAddress("glXChooseFBConfig")] + public partial IntPtr* ChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements); - public IntPtr* GlxChooseFbConfig(IntPtr dpy, int screen, IEnumerable attribs, out int nelements) + public IntPtr* ChooseFbConfig(IntPtr dpy, int screen, IEnumerable attribs, out int nelements) { var arr = attribs.Concat(new[]{0}).ToArray(); return ChooseFBConfig(dpy, screen, arr, out nelements); } - [GlEntryPointAttribute("glXGetVisualFromFBConfig")] - public GlxGetVisualFromFBConfig GetVisualFromFBConfig { get; } - public delegate XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy, IntPtr config); + [GetProcAddress("glXGetVisualFromFBConfig")] + public partial XVisualInfo * GetVisualFromFBConfig(IntPtr dpy, IntPtr config); - [GlEntryPointAttribute("glXGetFBConfigAttrib")] - public GlxGetFBConfigAttrib GetFBConfigAttrib { get; } - public delegate int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value); + [GetProcAddress("glXGetFBConfigAttrib")] + public partial int GetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value); - [GlEntryPointAttribute("glXSwapBuffers")] - public GlxSwapBuffers SwapBuffers { get; } - public delegate void GlxSwapBuffers(IntPtr dpy, IntPtr drawable); + [GetProcAddress("glXSwapBuffers")] + public partial void SwapBuffers(IntPtr dpy, IntPtr drawable); - [GlEntryPointAttribute("glXWaitX")] - public GlxWaitX WaitX { get; } - public delegate void GlxWaitX(); + [GetProcAddress("glXWaitX")] + public partial void WaitX(); - [GlEntryPointAttribute("glXWaitGL")] - public GlxWaitGL WaitGL { get; } - public delegate void GlxWaitGL(); + [GetProcAddress("glXWaitGL")] + public partial void WaitGL(); - public delegate int GlGetError(); - [GlEntryPoint("glGetError")] - public GlGetError GetError { get; } - public delegate IntPtr GlxQueryExtensionsString(IntPtr display, int screen); - [GlEntryPoint("glXQueryExtensionsString")] - public GlxQueryExtensionsString QueryExtensionsString { get; } + [GetProcAddress("glGetError")] + public partial int GlGetError(); + + + [GetProcAddress("glXQueryExtensionsString")] + public partial IntPtr QueryExtensionsString(IntPtr display, int screen); - public GlxInterface() : base(SafeGetProcAddress) + public GlxInterface() { + Initialize(SafeGetProcAddress); } // Ignores egl functions. @@ -122,10 +108,9 @@ namespace Avalonia.X11.Glx return IntPtr.Zero; } - return GlxConverted(proc); + return GlxGetProcAddress(proc); } - private static readonly Func GlxConverted = ConvertNative(GlxGetProcAddress); public string[] GetExtensions(IntPtr display) { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 46a985c0e8..99c4b62716 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -50,7 +50,7 @@ namespace Avalonia.LinuxFramebuffer.Output } [DllImport("libEGL.so.1")] - static extern IntPtr eglGetProcAddress(Utf8Buffer proc); + static extern IntPtr eglGetProcAddress(string proc); private GbmBoUserDataDestroyCallbackDelegate FbDestroyDelegate; private drmModeModeInfo _mode; diff --git a/src/Shared/SourceGeneratorAttributes.cs b/src/Shared/SourceGeneratorAttributes.cs index fdb5977d23..ac7c82c469 100644 --- a/src/Shared/SourceGeneratorAttributes.cs +++ b/src/Shared/SourceGeneratorAttributes.cs @@ -14,4 +14,28 @@ namespace Avalonia.SourceGenerator public string Namespace { get; } public Type BaseType { get; } } + + + internal class GetProcAddressAttribute : Attribute + { + public GetProcAddressAttribute(string proc) + { + + } + + public GetProcAddressAttribute(string proc, bool optional = false) + { + + } + + public GetProcAddressAttribute(bool optional) + { + + } + + public GetProcAddressAttribute() + { + + } + } } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs index 8ab275df63..984c1785b7 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs @@ -27,16 +27,13 @@ namespace Avalonia.Skia gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderbuffer); gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture); - var arr = new int[2]; - + // Generate FBO - gl.GenFramebuffers(1, arr); - _fbo = arr[0]; + _fbo = gl.GenFramebuffer(); gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo); // Create a texture to render into - gl.GenTextures(1, arr); - _texture = arr[0]; + _texture = gl.GenTexture(); gl.BindTexture(GL_TEXTURE_2D, _texture); gl.TexImage2D(GL_TEXTURE_2D, 0, InternalFormat, pixelSize.Width, pixelSize.Height, @@ -48,8 +45,7 @@ namespace Avalonia.Skia var success = false; foreach (var useStencil8 in TrueFalse) { - gl.GenRenderbuffers(1, arr); - _depthStencil = arr[0]; + _depthStencil = gl.GenRenderbuffer(); gl.BindRenderbuffer(GL_RENDERBUFFER, _depthStencil); if (useStencil8) @@ -73,7 +69,7 @@ namespace Avalonia.Skia else { gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderbuffer); - gl.DeleteRenderbuffers(1, arr); + gl.DeleteRenderbuffer(_depthStencil); } } @@ -83,10 +79,8 @@ namespace Avalonia.Skia if (!success) { - arr[0] = _fbo; - gl.DeleteFramebuffers(1, arr); - arr[0] = _texture; - gl.DeleteTextures(1, arr); + gl.DeleteFramebuffer(_fbo); + gl.DeleteTexture(_texture); throw new OpenGlException("Unable to create FBO with stencil"); } @@ -94,7 +88,7 @@ namespace Avalonia.Skia new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat())); Surface = SKSurface.Create(_grContext, target, surfaceOrigin, SKColorType.Rgba8888); - CanBlit = gl.BlitFramebuffer != null; + CanBlit = gl.IsBlitFramebufferAvailable; } public void Dispose() @@ -106,9 +100,9 @@ namespace Avalonia.Skia var gl = _glContext.GlInterface; if (_fbo != 0) { - gl.DeleteFramebuffers(1, new[] { _fbo }); - gl.DeleteTextures(1, new[] { _texture }); - gl.DeleteRenderbuffers(1, new[] { _depthStencil }); + gl.DeleteFramebuffer(_fbo); + gl.DeleteTexture(_texture); + gl.DeleteRenderbuffer(_depthStencil); _fbo = _texture = _depthStencil = 0; } } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index ec8a8436e1..08e0fbd808 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -54,7 +54,7 @@ namespace Avalonia.Skia return null; // Blit feature requires glBlitFramebuffer - if (_glContext.GlInterface.BlitFramebuffer == null) + if (!_glContext.GlInterface.IsBlitFramebufferAvailable) return null; size = new PixelSize(Math.Max(size.Width, 1), Math.Max(size.Height, 1)); diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs index 2ebf7c680b..a4617bb4d5 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs @@ -101,7 +101,7 @@ namespace Avalonia.Skia private bool _disposed; private readonly DisposableLock _lock = new DisposableLock(); - public SharedOpenGlBitmapAttachment(GlOpenGlBitmapImpl bitmap, IGlContext context, Action presentCallback) + public unsafe SharedOpenGlBitmapAttachment(GlOpenGlBitmapImpl bitmap, IGlContext context, Action presentCallback) { _bitmap = bitmap; _context = context; @@ -119,7 +119,8 @@ namespace Avalonia.Skia var gl = _context.GlInterface; var textures = new int[2]; - gl.GenTextures(2, textures); + fixed (int* ptex = textures) + gl.GenTextures(2, ptex); _texture = textures[0]; _frontBuffer = textures[1]; @@ -178,7 +179,7 @@ namespace Avalonia.Skia _presentCallback(); } - public void Dispose() + public unsafe void Dispose() { var gl = _context.GlInterface; _bitmap.Present(null); @@ -191,7 +192,9 @@ namespace Avalonia.Skia if(_disposed) return; _disposed = true; - gl.DeleteTextures(2, new[] { _texture, _frontBuffer }); + var tex = new[] { _texture, _frontBuffer }; + fixed (int* ptex = tex) + gl.DeleteTextures(2, ptex); } } diff --git a/src/tools/DevGenerators/GetProcAddressInitialization.cs b/src/tools/DevGenerators/GetProcAddressInitialization.cs new file mode 100644 index 0000000000..3b3871370c --- /dev/null +++ b/src/tools/DevGenerators/GetProcAddressInitialization.cs @@ -0,0 +1,346 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Generator; + +[Generator(LanguageNames.CSharp)] +public class GetProcAddressInitializationGenerator : IIncrementalGenerator +{ + const string GetProcAddressFullName = "global::Avalonia.SourceGenerator.GetProcAddressAttribute"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + /* + Console.WriteLine("PID: " + Process.GetCurrentProcess().Id); + + File.WriteAllText("/tmp/pid", "PID: " + Process.GetCurrentProcess().Id); + while (!Debugger.IsAttached) + { + Thread.Sleep(1000); + }*/ + var allMethodsWithAttributes = context.SyntaxProvider + .CreateSyntaxProvider( + static (s, _) => s is MethodDeclarationSyntax + { + AttributeLists.Count: > 0, + } md && md.Modifiers.Any(m=>m.IsKind(SyntaxKind.PartialKeyword)), + static (context, _) => + (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!); + + var fieldsWithAttribute = allMethodsWithAttributes + .Where(s => s.HasAttributeWithFullyQualifiedName(GetProcAddressFullName)); + + var all = fieldsWithAttribute.Collect(); + context.RegisterSourceOutput(all, static (context, methods) => + { + foreach (var typeGroup in methods.GroupBy(f => f.ContainingType)) + { + var nextContext = 0; + var contexts = new Dictionary(); + + string GetContextNameFromIndex(int c) => "context" + (c == 0 ? "" : c); + string GetContextName(string type) + { + if (contexts.TryGetValue(type, out var idx)) + return GetContextNameFromIndex(idx); + if (nextContext != 0) + idx += nextContext; + nextContext++; + return GetContextNameFromIndex(contexts[type] = idx); + } + + var classBuilder = new StringBuilder(); + if (typeGroup.Key.ContainingNamespace != null) + classBuilder + .AppendLine("using System;") + .Append("namespace ") + .Append(typeGroup.Key.ContainingNamespace) + .AppendLine(";"); + classBuilder + .Append("unsafe partial class ") + .AppendLine(typeGroup.Key.Name) + .AppendLine("{"); + var initializeBody = new StringBuilder() + .Pad(2) + .AppendLine("var addr = IntPtr.Zero;"); + + foreach (var method in typeGroup) + { + var isOptional = false; + var first = true; + var fieldName = "_addr_" + method.Name; + var delegateType = BuildDelegateType(method); + + void AppendNextAddr() + { + if (first) + { + first = false; + initializeBody.Pad(2); + } + else + initializeBody + .Pad(2) + .Append("if(addr == IntPtr.Zero) "); + } + + initializeBody + .Pad(2).Append("// Initializing ").AppendLine(method.Name) + .Pad(2) + .AppendLine("addr = IntPtr.Zero;"); + foreach (var attr in method.GetAttributes()) + { + if (attr.AttributeClass?.HasFullyQualifiedName(GetProcAddressFullName) == true) + { + string? primaryName = null; + foreach (var arg in attr.ConstructorArguments) + { + if (arg.Value is string name) + primaryName = name; + if (arg.Value is bool opt) + isOptional = opt; + } + + if (primaryName != null) + { + AppendNextAddr(); + initializeBody + .Append("addr = getProcAddress(\"") + .Append(primaryName) + .AppendLine("\");"); + } + } + else + { + if (attr.AttributeClass != null + && attr.AttributeClass.MemberNames.Contains("GetProcAddress")) + { + var getProcMethod = attr.AttributeClass.GetMembers() + .FirstOrDefault(m => m.Name == "GetProcAddress") as IMethodSymbol; + if (getProcMethod == null || !getProcMethod.IsStatic || getProcMethod.Parameters.Length < 2) + continue; + var contextName = + GetContextName(getProcMethod + .Parameters[1].Type + .ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + AppendNextAddr(); + initializeBody + .Append("addr = ") + .Append(attr.AttributeClass.GetFullyQualifiedName()) + .Append(".GetProcAddress(") + .Append("getProcAddress, ") + .Append(contextName); + + var syntaxNode = (AttributeSyntax)attr.ApplicationSyntaxReference.GetSyntax(); + foreach (var arg in syntaxNode.ArgumentList.Arguments) + initializeBody.Append(", ").Append(arg.GetText()); + initializeBody.AppendLine(");"); + } + } + } + + if (!isOptional) + { + initializeBody + .Pad(2) + .Append("if (addr == IntPtr.Zero) throw new System.EntryPointNotFoundException(\"") + .Append(fieldName).AppendLine("\");"); + } + + initializeBody + .Pad(2) + .Append(fieldName) + .Append(" = (") + .Append(delegateType) + .AppendLine(")addr;"); + + classBuilder + .Pad(1) + .Append(delegateType); + classBuilder + .Append(fieldName) + .AppendLine(";"); + + classBuilder + .Pad(1) + .Append("public partial ") + .Append(method.ReturnType.GetFullyQualifiedName()) + .Append(" ") + .Append(method.Name) + .Append("("); + var firstArg = true; + foreach (var p in method.Parameters) + { + if (firstArg) + firstArg = false; + else + classBuilder.Append(", "); + AppendRefKind(classBuilder, p.RefKind); + classBuilder + .Append(p.Type.GetFullyQualifiedName()) + .Append(" @") + .Append(p.Name); + } + classBuilder + .AppendLine(")") + .Pad(1) + .AppendLine("{"); + if (isOptional) + classBuilder + .Pad(2) + .Append("if (") + .Append(fieldName) + .Append(" == null) throw new System.EntryPointNotFoundException(\"") + .Append(method.Name) + .AppendLine("\");"); + + foreach(var p in method.Parameters) + if (NeedsPin(p.Type)) + classBuilder.Pad(2) + .Append("fixed(") + .Append(MapToNative(p.Type)) + .Append(" @__p_") + .Append(p.Name) + .Append(" = ") + .Append(p.Name) + .AppendLine(")"); + + classBuilder.Pad(2); + if (!method.ReturnsVoid) + classBuilder.Append("return "); + + var invokeBuilder = new StringBuilder(); + + invokeBuilder + .Append(fieldName) + .Append("("); + firstArg = true; + foreach (var p in method.Parameters) + { + if (firstArg) + firstArg = false; + else + invokeBuilder.Append(", "); + AppendRefKind(invokeBuilder, p.RefKind); + invokeBuilder + .Append("@") + .Append(ConvertToNative(p.Name, p.Type)); + } + + invokeBuilder.Append(")"); + classBuilder.Append(ConvertToManaged(method.ReturnType, invokeBuilder.ToString())); + + classBuilder.AppendLine(";").Pad(1).AppendLine("}"); + if (isOptional) + classBuilder + .Pad(1) + .Append("public bool Is") + .Append(method.Name) + .Append("Available => ") + .Append(fieldName) + .AppendLine(" != null;"); + } + + classBuilder + .Pad(1) + .Append("void Initialize(Func getProcAddress"); + foreach (var kv in contexts.OrderBy(x => x.Value)) + { + classBuilder + .Append(", ") + .Append(kv.Key) + .Append(" ") + .Append(GetContextNameFromIndex(kv.Value)); + } + + classBuilder.AppendLine(")").Pad(1).AppendLine("{"); + classBuilder.Append(initializeBody.ToString()); + classBuilder.Append("}\n}"); + + + context.AddSource(typeGroup.Key.GetFullyQualifiedName().Replace(":", ""), classBuilder.ToString()); + } + }); + + + } + + static StringBuilder AppendRefKind(StringBuilder sb, RefKind kind) + { + if (kind == RefKind.Ref) + sb.Append("ref "); + if (kind == RefKind.Out) + sb.Append("out "); + return sb; + } + + static bool NeedsPin(ITypeSymbol type) + { + if (type.TypeKind == TypeKind.Array) + return true; + return false; + } + + static string ConvertToNative(string name, ITypeSymbol type) + { + if (NeedsPin(type)) + return "__p_" + name; + if (IsBool(type)) + return $"{name} ? 1 : 0"; + return name; + } + + static string ConvertToManaged(ITypeSymbol type, string expr) + { + if (IsBool(type)) + return expr + " != 0"; + return expr; + } + + static bool IsBool(ITypeSymbol type) => type.GetFullyQualifiedName() == "global::System.Boolean" || + type.GetFullyQualifiedName() == "bool"; + + static string MapToNative(ITypeSymbol type) + { + if (type.TypeKind == TypeKind.Array) + return ((IArrayTypeSymbol)type).ElementType.GetFullyQualifiedName() + "*"; + if (IsBool(type)) + return "int"; + return type.GetFullyQualifiedName(); + } + + static string BuildDelegateType(IMethodSymbol method) + { + StringBuilder name = new("delegate* unmanaged[Stdcall]<"); + var firstArg = true; + + void AppendArg(string a, RefKind kind) + { + if (firstArg) + firstArg = false; + else + name.Append(","); + AppendRefKind(name, kind); + name.Append(a); + } + + foreach (var p in method.Parameters) + { + AppendArg(MapToNative(p.Type), p.RefKind); + } + + AppendArg(MapToNative(method.ReturnType), RefKind.None); + name.Append(">"); + return name.ToString(); + } + +} \ No newline at end of file diff --git a/src/tools/DevGenerators/Helpers.cs b/src/tools/DevGenerators/Helpers.cs new file mode 100644 index 0000000000..3da89d2d0e --- /dev/null +++ b/src/tools/DevGenerators/Helpers.cs @@ -0,0 +1,31 @@ +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace Generator; + +static class Helpers +{ + public static StringBuilder Pad(this StringBuilder sb, int count) => sb.Append(' ', count * 4); + + public static string GetFullyQualifiedName(this ISymbol symbol) + { + return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } + + public static bool HasFullyQualifiedName(this ISymbol symbol, string name) + { + return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == name; + } + + public static bool HasAttributeWithFullyQualifiedName(this ISymbol symbol, string name) + { + ImmutableArray attributes = symbol.GetAttributes(); + + foreach (AttributeData attribute in attributes) + if (attribute.AttributeClass?.HasFullyQualifiedName(name) == true) + return true; + + return false; + } +} \ No newline at end of file From fd42381afd04e82aa436ceb81e03cd546ded3535 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 25 Jul 2022 01:30:01 +0300 Subject: [PATCH 22/97] Updated LayerFbo --- src/Avalonia.OpenGL/GlInterface.cs | 2 +- src/iOS/Avalonia.iOS/LayerFbo.cs | 52 +++++++++++++----------------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index d6eff9a35f..a9dd882e3b 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -335,7 +335,7 @@ namespace Avalonia.OpenGL public partial void DeleteShader(int shader); [GetProcAddress("glGetRenderbufferParameteriv")] - public partial void GLGetRenderbufferParameteriv(int target, int name, int* value); + public partial void GetRenderbufferParameteriv(int target, int name, out int value); // ReSharper restore UnassignedGetOnlyAutoProperty [GetProcAddress(true)] diff --git a/src/iOS/Avalonia.iOS/LayerFbo.cs b/src/iOS/Avalonia.iOS/LayerFbo.cs index 955aaef59f..9a93cdfb22 100644 --- a/src/iOS/Avalonia.iOS/LayerFbo.cs +++ b/src/iOS/Avalonia.iOS/LayerFbo.cs @@ -10,12 +10,12 @@ namespace Avalonia.iOS private readonly EAGLContext _context; private readonly GlInterface _gl; private readonly CAEAGLLayer _layer; - private int[] _framebuffer; - private int[] _renderbuffer; - private int[] _depthBuffer; + private int _framebuffer; + private int _renderbuffer; + private int _depthBuffer; private bool _disposed; - private LayerFbo(EAGLContext context, GlInterface gl, CAEAGLLayer layer, int[] framebuffer, int[] renderbuffer, int[] depthBuffer) + private LayerFbo(EAGLContext context, GlInterface gl, CAEAGLLayer layer, int framebuffer, int renderbuffer, int depthBuffer) { _context = context; _gl = gl; @@ -30,42 +30,36 @@ namespace Avalonia.iOS if (context != EAGLContext.CurrentContext) return null; - var fb = new int[2]; - var rb = new int[2]; - var db = new int[2]; - - gl.GenRenderbuffers(1, rb); - gl.BindRenderbuffer(GlConsts.GL_RENDERBUFFER, rb[0]); + var rb = gl.GenRenderbuffer(); + gl.BindRenderbuffer(GlConsts.GL_RENDERBUFFER, rb); context.RenderBufferStorage(GlConsts.GL_RENDERBUFFER, layer); - gl.GenFramebuffers(1, fb); - gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, fb[0]); - gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_COLOR_ATTACHMENT0, GlConsts.GL_RENDERBUFFER, rb[0]); - - int[] w = new int[1]; - int[] h = new int[1]; - gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_WIDTH, w); - gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_HEIGHT, h); + var fb = gl.GenFramebuffer(); + gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, fb); + gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_COLOR_ATTACHMENT0, GlConsts.GL_RENDERBUFFER, rb); - gl.GenRenderbuffers(1, db); + gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_WIDTH, out var w); + gl.GetRenderbufferParameteriv(GlConsts.GL_RENDERBUFFER, GlConsts.GL_RENDERBUFFER_HEIGHT, out var h); + + var db = gl.GenRenderbuffer(); //GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, depthBuffer); //GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferInternalFormat.DepthComponent16, w, h); - gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_DEPTH_ATTACHMENT, GlConsts.GL_RENDERBUFFER, db[0]); + gl.FramebufferRenderbuffer(GlConsts.GL_FRAMEBUFFER, GlConsts.GL_DEPTH_ATTACHMENT, GlConsts.GL_RENDERBUFFER, db); var frameBufferError = gl.CheckFramebufferStatus(GlConsts.GL_FRAMEBUFFER); if(frameBufferError != GlConsts.GL_FRAMEBUFFER_COMPLETE) { - gl.DeleteFramebuffers(1, fb); - gl.DeleteRenderbuffers(1, db); - gl.DeleteRenderbuffers(1, rb); + gl.DeleteFramebuffer(fb); + gl.DeleteRenderbuffer(db); + gl.DeleteRenderbuffer(rb); return null; } return new LayerFbo(context, gl, layer, fb, rb, db) { - Width = w[0], - Height = h[0] + Width = w, + Height = h }; } @@ -74,7 +68,7 @@ namespace Avalonia.iOS public void Bind() { - _gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, _framebuffer[0]); + _gl.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, _framebuffer); } public void Present() @@ -88,9 +82,9 @@ namespace Avalonia.iOS if(_disposed) return; _disposed = true; - _gl.DeleteFramebuffers(1, _framebuffer); - _gl.DeleteRenderbuffers(1, _depthBuffer); - _gl.DeleteRenderbuffers(1, _renderbuffer); + _gl.DeleteFramebuffer(_framebuffer); + _gl.DeleteRenderbuffer(_depthBuffer); + _gl.DeleteRenderbuffer(_renderbuffer); if (_context != EAGLContext.CurrentContext) throw new InvalidOperationException("Associated EAGLContext is not current"); } From fe4a5e7a5f15552f336ad1dfb2ab1c0b73577e6e Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 24 Jul 2022 19:08:17 -0400 Subject: [PATCH 23/97] Make android emulator work again on control catalog --- samples/ControlCatalog.Android/ControlCatalog.Android.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj index 54acdd9114..a43ea4539a 100644 --- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -9,7 +9,6 @@ 1.0 apk true - android-arm64;android-x64 From cdb9cc22ebfa3472260e972226f8f282b7ea9167 Mon Sep 17 00:00:00 2001 From: Markus Wenzl Date: Mon, 25 Jul 2022 08:42:51 +0200 Subject: [PATCH 24/97] Fix DrmCard creation for passed path --- src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs index 64fafc65f3..de281245f1 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs @@ -164,7 +164,7 @@ namespace Avalonia.LinuxFramebuffer.Output else { Fd = open(path, 2, 0); - if(Fd != -1) throw new Win32Exception($"Couldn't open {path}"); + if(Fd == -1) throw new Win32Exception($"Couldn't open {path}"); } } From 9e6deb238645cbf93b15adf619a90d19c1cdbf54 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 13 Jul 2022 09:22:34 +0200 Subject: [PATCH 25/97] fix for ProgressBar not listening to size changes of parent If the track for the progressbar changes its size the indicator needs to update as well. --- src/Avalonia.Controls/ProgressBar.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index d0e8e8f1a0..56b9caf085 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -100,6 +100,7 @@ namespace Avalonia.Controls private double _indeterminateStartingOffset; private double _indeterminateEndingOffset; private Border? _indicator; + private IDisposable? _trackSizeChangedListener; public static readonly StyledProperty IsIndeterminateProperty = AvaloniaProperty.Register(nameof(IsIndeterminate)); @@ -195,8 +196,9 @@ namespace Avalonia.Controls /// protected override Size ArrangeOverride(Size finalSize) { + var result = base.ArrangeOverride(finalSize); UpdateIndicator(); - return base.ArrangeOverride(finalSize); + return result; } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) @@ -216,8 +218,15 @@ namespace Avalonia.Controls /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { + // dispose any previous track size listener + _trackSizeChangedListener?.Dispose(); + _indicator = e.NameScope.Get("PART_Indicator"); + // listen to size changes of the indicators track (parent) and update the indicator there. + _trackSizeChangedListener = _indicator.Parent?.GetPropertyChangedObservable(BoundsProperty) + .Subscribe(_ => UpdateIndicator()); + UpdateIndicator(); } From 42d4d71aaf28f063209301b86636504fc64a671b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 25 Jul 2022 12:08:17 +0300 Subject: [PATCH 26/97] Removed debug code Co-authored-by: Max Katz --- src/tools/DevGenerators/GetProcAddressInitialization.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/tools/DevGenerators/GetProcAddressInitialization.cs b/src/tools/DevGenerators/GetProcAddressInitialization.cs index 3b3871370c..64698d1f2e 100644 --- a/src/tools/DevGenerators/GetProcAddressInitialization.cs +++ b/src/tools/DevGenerators/GetProcAddressInitialization.cs @@ -19,14 +19,6 @@ public class GetProcAddressInitializationGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { - /* - Console.WriteLine("PID: " + Process.GetCurrentProcess().Id); - - File.WriteAllText("/tmp/pid", "PID: " + Process.GetCurrentProcess().Id); - while (!Debugger.IsAttached) - { - Thread.Sleep(1000); - }*/ var allMethodsWithAttributes = context.SyntaxProvider .CreateSyntaxProvider( static (s, _) => s is MethodDeclarationSyntax From 4ff9225f002fa518ef16f0ab16567cf3c82b03ab Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 25 Jul 2022 11:08:57 +0200 Subject: [PATCH 27/97] Actually add Environment.NewLine to the TextLayout for LineBreak runs --- src/Avalonia.Controls/Documents/LineBreak.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Documents/LineBreak.cs b/src/Avalonia.Controls/Documents/LineBreak.cs index ef8bd35556..108a38d86b 100644 --- a/src/Avalonia.Controls/Documents/LineBreak.cs +++ b/src/Avalonia.Controls/Documents/LineBreak.cs @@ -21,7 +21,13 @@ namespace Avalonia.Controls.Documents internal override void BuildTextRun(IList textRuns) { - textRuns.Add(new TextEndOfLine(Environment.NewLine.Length)); + var text = Environment.NewLine.AsMemory(); + + var textRunProperties = CreateTextRunProperties(); + + var textCharacters = new TextCharacters(text, textRunProperties); + + textRuns.Add(textCharacters); } internal override void AppendText(StringBuilder stringBuilder) From 127e77a5433f92683c9697d73511b2ba94b226ff Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 25 Jul 2022 12:31:24 +0200 Subject: [PATCH 28/97] Update RichTextBlock ControlTheme --- .../Controls/FluentControls.xaml | 1 + src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index bc2352d5d0..0499495239 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -68,6 +68,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml index d7bf6e5cf9..75af2efcb1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml @@ -1,10 +1,14 @@ - - - + + + + From a4679604981a1840b37e99578405ecfe776caf0f Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 25 Jul 2022 12:32:51 +0200 Subject: [PATCH 29/97] Revert change --- samples/Sandbox/App.axaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/Sandbox/App.axaml b/samples/Sandbox/App.axaml index 1c74c07b1d..f601f9f78f 100644 --- a/samples/Sandbox/App.axaml +++ b/samples/Sandbox/App.axaml @@ -3,6 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Sandbox.App"> - + From fdd8e5bdb92e213d1295e95229ac81d3e779e3b8 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 25 Jul 2022 12:33:48 +0200 Subject: [PATCH 30/97] Revert change --- .../Avalonia.Themes.Default.csproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index ae0df89c60..40ed4a0f87 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -9,14 +9,6 @@ - - - - - - MSBuild:Compile - - From 3e39ace557fd448814e508660afc301b90a15aa3 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 25 Jul 2022 12:34:12 +0200 Subject: [PATCH 31/97] Revert change --- src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj index 8acf12a0ff..35603fe216 100644 --- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj +++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj @@ -10,14 +10,6 @@ - - - - - - MSBuild:Compile - - From 821e1b6e5ece05e66e53757e9fa6d222ab7c335e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 26 Jul 2022 13:01:02 +0300 Subject: [PATCH 32/97] Set WM_CLASS property according to ICCCM spec --- src/Avalonia.X11/X11Platform.cs | 3 ++- src/Avalonia.X11/X11Window.cs | 23 +++++++++++++++++------ src/Avalonia.X11/XLib.cs | 14 ++++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 7043c60ae7..cc9737668d 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -277,7 +277,8 @@ namespace Avalonia // and sometimes attempts to use GLX might cause a segfault "llvmpipe" }; - public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication"; + + public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name; /// /// Enables multitouch support. The default value is true. diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 009ccb6159..7b34705b3b 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -168,8 +168,7 @@ namespace Avalonia.X11 XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, new[] {_x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL}, 1); - if (platform.Options.WmClass != null) - SetWmClass(platform.Options.WmClass); + SetWmClass(_platform.Options.WmClass); var surfaces = new List { @@ -1082,12 +1081,24 @@ namespace Avalonia.X11 public void SetWmClass(string wmClass) { - var data = Encoding.ASCII.GetBytes(wmClass); - fixed (void* pdata = data) + // See https://tronche.com/gui/x/icccm/sec-4.html#WM_CLASS + // We don't actually parse the application's command line, so we only use RESOURCE_NAME and argv[0] + var appId = Environment.GetEnvironmentVariable("RESOURCE_NAME") + ?? Process.GetCurrentProcess().ProcessName; + + var encodedAppId = Encoding.ASCII.GetBytes(appId); + var encodedWmClass = Encoding.ASCII.GetBytes(wmClass ?? appId); + + var hint = XAllocClassHint(); + fixed(byte* pAppId = encodedAppId) + fixed (byte* pWmClass = encodedWmClass) { - XChangeProperty(_x11.Display, _handle, _x11.Atoms.XA_WM_CLASS, _x11.Atoms.XA_STRING, 8, - PropertyMode.Replace, pdata, data.Length); + hint->res_name = pAppId; + hint->res_class = pWmClass; + XSetClassHint(_x11.Display, _handle, hint); } + + XFree(hint); } public void SetMinMaxSize(Size minSize, Size maxSize) diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 464ec4f1c8..753d5f530c 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -109,6 +109,9 @@ namespace Avalonia.X11 [DllImport(libX11)] public static extern int XFree(IntPtr data); + + [DllImport(libX11)] + public static extern int XFree(void* data); [DllImport(libX11)] public static extern int XRaiseWindow(IntPtr display, IntPtr window); @@ -628,6 +631,12 @@ namespace Avalonia.X11 return XISelectEvents(display, window, emasks, devices.Count); } + + [DllImport(libX11)] + public static extern XClassHint* XAllocClassHint(); + + [DllImport(libX11)] + public static extern int XSetClassHint(IntPtr display, IntPtr window, XClassHint* class_hints); public struct XGeometry { @@ -639,6 +648,11 @@ namespace Avalonia.X11 public int bw; public int d; } + public struct XClassHint + { + public byte* res_name; + public byte* res_class; + } public struct XSyncValue { public int Hi; From e7e2516c07f46cd17e4d2299b4f7e78cd92d9267 Mon Sep 17 00:00:00 2001 From: Evgeny Gorbovoy Date: Tue, 26 Jul 2022 13:43:39 +0200 Subject: [PATCH 33/97] Scene hittest now uses exclusive `Rect.Contains`. Fixes https://github.com/AvaloniaUI/Avalonia/issues/3079 --- src/Avalonia.Base/Rendering/SceneGraph/Scene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs index 20c23d7bee..c436299018 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs @@ -300,7 +300,7 @@ namespace Avalonia.Rendering.SceneGraph if (node.ClipToBounds) { clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds); - clipped = !clip.Value.Contains(_point); + clipped = !clip.Value.ContainsExclusive(_point); } if (node.GeometryClip != null) From fd28b7b5728f9e1899e244eb98e5e3ef00979717 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 26 Jul 2022 15:17:10 +0200 Subject: [PATCH 34/97] Work around mono/SkiaSharp/#1551 Use the workaround from https://github.com/mono/SkiaSharp/issues/1551#issuecomment-756685252 to load bitmaps. Fixes #7773 --- src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 3 ++- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 5628ae177c..a80f406989 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -57,7 +57,8 @@ namespace Avalonia.Skia public ImmutableBitmap(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode) { using (var skStream = new SKManagedStream(stream)) - using (var codec = SKCodec.Create(skStream)) + using (var skData = SKData.Create(skStream)) + using (var codec = SKCodec.Create(skData)) { var info = codec.Info; diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 506edf0627..80bfcc5973 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -4,7 +4,6 @@ using System.Threading; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Skia.Helpers; -using Avalonia.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia @@ -25,8 +24,9 @@ namespace Avalonia.Skia public WriteableBitmapImpl(Stream stream) { using (var skiaStream = new SKManagedStream(stream)) + using (var skData = SKData.Create(skiaStream)) { - _bitmap = SKBitmap.Decode(skiaStream); + _bitmap = SKBitmap.Decode(skData); if (_bitmap == null) { @@ -41,7 +41,8 @@ namespace Avalonia.Skia public WriteableBitmapImpl(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode) { using (var skStream = new SKManagedStream(stream)) - using (var codec = SKCodec.Create(skStream)) + using (var skData = SKData.Create(skStream)) + using (var codec = SKCodec.Create(skData)) { var info = codec.Info; From 6b1de285ba35d967191f38063b5d632ad86488e6 Mon Sep 17 00:00:00 2001 From: Markus Wenzl Date: Tue, 26 Jul 2022 18:08:48 +0200 Subject: [PATCH 35/97] Added drmModeGetConnectorCurrent, added new StartLinuxDrm with LinuxDrmOptions --- .../LinuxDrmOptions.cs | 46 +++++++++++++++++++ .../LinuxFramebufferPlatform.cs | 8 ++++ .../Avalonia.LinuxFramebuffer/Output/Drm.cs | 2 + .../Output/DrmBindings.cs | 6 +-- .../Output/DrmOutput.cs | 45 +++++++++++++----- 5 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 src/Linux/Avalonia.LinuxFramebuffer/LinuxDrmOptions.cs diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxDrmOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxDrmOptions.cs new file mode 100644 index 0000000000..a67d6f7d8b --- /dev/null +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxDrmOptions.cs @@ -0,0 +1,46 @@ +using Avalonia.LinuxFramebuffer.Output; +using Avalonia.Media; +using JetBrains.Annotations; + +namespace Avalonia.LinuxFramebuffer +{ + public class LinuxDrmOptions + { + /// + /// Path for DrmCard to use, if no is passed. + /// Default: null + /// + [CanBeNull] public string Card { get; set; } + + /// + /// True to call drmModeGetConnector for all available connectors, otherwise drmModeGetConnectorCurrent is called to get the kernel-cached connected connector. + /// Info: since some hardware might have incorrect connector information on startup for some reason, you may need to set this parameter to true. + /// Default: False + /// + public bool DrmConnectorsForceProbe { get; set; } = false; + + /// + /// Scaling factor. + /// Default: 1.0 + /// + public double Scaling { get; set; } = 1.0; + + /// + /// If true an two cycle buffer swapping is processed at init. + /// Default: True + /// + public bool EnableInitialBufferSwapping { get; set; } = true; + + /// + /// Color for + /// Default: R0 G0 B0 A0 + /// + public Color InitialBufferSwappingColor { get; set; } = new Color(0, 0, 0, 0); + + /// + /// IOutputBackend. If Null, a new DrmOutput for with will be created. + /// Default: null + /// + [CanBeNull] public IOutputBackend OutputBackend { get; set; } + } +} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 4add4c423b..bf29aed1a4 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -140,6 +140,14 @@ public static class LinuxFramebufferPlatformExtensions public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) {Scaling = scaling}); + + public static int StartLinuxDrm(this T builder, string[] args, [CanBeNull] LinuxDrmOptions drmOptions) + where T : AppBuilderBase, new() + { + drmOptions ??= new LinuxDrmOptions(); + drmOptions.OutputBackend ??= new DrmOutput(drmOptions) {Scaling = drmOptions.Scaling}; + return StartLinuxDirect(builder, args, drmOptions.OutputBackend); + } public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend backend) where T : AppBuilderBase, new() diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs index 9c476b1b63..04bc2c1f67 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs @@ -162,6 +162,8 @@ namespace Avalonia.LinuxFramebuffer.Output [DllImport(libdrm, SetLastError = true)] public static extern drmModeConnector* drmModeGetConnector(int fd, uint connector); [DllImport(libdrm, SetLastError = true)] + public static extern drmModeConnector* drmModeGetConnectorCurrent(int fd, uint connector); + [DllImport(libdrm, SetLastError = true)] public static extern void drmModeFreeConnector(drmModeConnector* res); [DllImport(libdrm, SetLastError = true)] diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs index de281245f1..070e1a95bc 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs @@ -84,7 +84,7 @@ namespace Avalonia.LinuxFramebuffer.Output { public List Connectors { get; }= new List(); internal Dictionary Encoders { get; } = new Dictionary(); - public DrmResources(int fd) + public DrmResources(int fd, bool connectorsForceProbe = false) { var res = drmModeGetResources(fd); if (res == null) @@ -107,7 +107,7 @@ namespace Avalonia.LinuxFramebuffer.Output for (var c = 0; c < res->count_connectors; c++) { - var conn = drmModeGetConnector(fd, res->connectors[c]); + var conn = connectorsForceProbe ? drmModeGetConnector(fd, res->connectors[c]) : drmModeGetConnectorCurrent(fd, res->connectors[c]); Connectors.Add(new DrmConnector(conn)); drmModeFreeConnector(conn); } @@ -168,7 +168,7 @@ namespace Avalonia.LinuxFramebuffer.Output } } - public DrmResources GetResources() => new DrmResources(Fd); + public DrmResources GetResources(bool connectorsForceProbe = false) => new DrmResources(Fd, connectorsForceProbe); public void Dispose() { close(Fd); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 99c4b62716..85cf1fba76 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -15,20 +15,34 @@ namespace Avalonia.LinuxFramebuffer.Output { public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface { + private LinuxDrmOptions _options; private DrmCard _card; public PixelSize PixelSize => _mode.Resolution; - public double Scaling { get; set; } + + public double Scaling + { + get => _options.Scaling; + set => _options.Scaling = value; + } public IGlContext PrimaryContext => _deferredContext; private EglPlatformOpenGlInterface _platformGl; public IPlatformOpenGlInterface PlatformOpenGlInterface => _platformGl; - public DrmOutput(string path = null) + public DrmOutput(LinuxDrmOptions drmOptions) { - var card = new DrmCard(path); + _options = drmOptions; + CreateDrmOutput(_options.Card, _options.DrmConnectorsForceProbe); + } + public DrmOutput(string path = null) : this(new LinuxDrmOptions() { Card = path }) + { + } - var resources = card.GetResources(); + private void CreateDrmOutput(string path = null, bool connectorsForceProbe = false) + { + var card = new DrmCard(path); + var resources = card.GetResources(connectorsForceProbe); var connector = resources.Connectors.FirstOrDefault(x => x.Connection == DrmModeConnection.DRM_MODE_CONNECTED); @@ -144,7 +158,9 @@ namespace Avalonia.LinuxFramebuffer.Output using (_deferredContext.MakeCurrent(_eglSurface)) { - _deferredContext.GlInterface.ClearColor(0, 0, 0, 0); + _deferredContext.GlInterface.ClearColor(_options.InitialBufferSwappingColor.R, + _options.InitialBufferSwappingColor.G, _options.InitialBufferSwappingColor.B, + _options.InitialBufferSwappingColor.A); _deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT); _eglSurface.SwapBuffers(); } @@ -162,13 +178,18 @@ namespace Avalonia.LinuxFramebuffer.Output _mode = mode; _currentBo = bo; - // Go trough two cycles of buffer swapping (there are render artifacts otherwise) - for(var c=0;c<2;c++) - using (CreateGlRenderTarget().BeginDraw()) - { - _deferredContext.GlInterface.ClearColor(0, 0, 0, 0); - _deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT); - } + if (_options.EnableInitialBufferSwapping) + { + //Go trough two cycles of buffer swapping (there are render artifacts otherwise) + for(var c=0;c<2;c++) + using (CreateGlRenderTarget().BeginDraw()) + { + _deferredContext.GlInterface.ClearColor(_options.InitialBufferSwappingColor.R, + _options.InitialBufferSwappingColor.G, _options.InitialBufferSwappingColor.B, + _options.InitialBufferSwappingColor.A); + _deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT); + } + } } From 0d232a96c767b883b2fd443559f03275fb9e26e5 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 26 Jul 2022 20:05:11 +0300 Subject: [PATCH 36/97] Don't throw exceptions for missing windowing platform since it's actually not mandatory for TrayIcon --- src/Avalonia.Controls/Platform/PlatformManager.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs index 92f6f1cb52..494c847012 100644 --- a/src/Avalonia.Controls/Platform/PlatformManager.cs +++ b/src/Avalonia.Controls/Platform/PlatformManager.cs @@ -25,9 +25,7 @@ namespace Avalonia.Controls.Platform var platform = AvaloniaLocator.Current.GetService(); if (platform == null) - { - throw new Exception("Could not CreateTrayIcon(): IWindowingPlatform is not registered."); - } + return null; return s_designerMode ? null : platform.CreateTrayIcon(); } From 1aff4967380179cbd0348887ffbaae7c625459a7 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 26 Jul 2022 20:38:42 +0300 Subject: [PATCH 37/97] More one liners for the god of one liners --- src/Avalonia.Controls/Platform/PlatformManager.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs index 494c847012..de7708e869 100644 --- a/src/Avalonia.Controls/Platform/PlatformManager.cs +++ b/src/Avalonia.Controls/Platform/PlatformManager.cs @@ -20,15 +20,8 @@ namespace Avalonia.Controls.Platform { } - public static ITrayIconImpl? CreateTrayIcon() - { - var platform = AvaloniaLocator.Current.GetService(); - - if (platform == null) - return null; - - return s_designerMode ? null : platform.CreateTrayIcon(); - } + public static ITrayIconImpl? CreateTrayIcon() => + s_designerMode ? null : AvaloniaLocator.Current.GetService()?.CreateTrayIcon(); public static IWindowImpl CreateWindow() From 5d491894aa69f7938124f5079442f655a0519abe Mon Sep 17 00:00:00 2001 From: Markus Wenzl Date: Tue, 26 Jul 2022 20:00:28 +0200 Subject: [PATCH 38/97] Fixes single responsibility principle --- .../DrmOutputOptions.cs | 27 +++++++++++ .../LinuxDrmOptions.cs | 46 ------------------- .../LinuxFramebufferPlatform.cs | 10 +--- .../Output/DrmOutput.cs | 34 ++++++++------ 4 files changed, 49 insertions(+), 68 deletions(-) create mode 100644 src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs delete mode 100644 src/Linux/Avalonia.LinuxFramebuffer/LinuxDrmOptions.cs diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs new file mode 100644 index 0000000000..e92ad02c7a --- /dev/null +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs @@ -0,0 +1,27 @@ +using Avalonia.LinuxFramebuffer.Output; +using Avalonia.Media; +using JetBrains.Annotations; + +namespace Avalonia.LinuxFramebuffer +{ + public class DrmOutputOptions + { + /// + /// Scaling factor. + /// Default: 1.0 + /// + public double Scaling { get; set; } = 1.0; + + /// + /// If true an two cycle buffer swapping is processed at init. + /// Default: True + /// + public bool EnableInitialBufferSwapping { get; set; } = true; + + /// + /// Color for + /// Default: R0 G0 B0 A0 + /// + public Color InitialBufferSwappingColor { get; set; } = new Color(0, 0, 0, 0); + } +} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxDrmOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxDrmOptions.cs deleted file mode 100644 index a67d6f7d8b..0000000000 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxDrmOptions.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Avalonia.LinuxFramebuffer.Output; -using Avalonia.Media; -using JetBrains.Annotations; - -namespace Avalonia.LinuxFramebuffer -{ - public class LinuxDrmOptions - { - /// - /// Path for DrmCard to use, if no is passed. - /// Default: null - /// - [CanBeNull] public string Card { get; set; } - - /// - /// True to call drmModeGetConnector for all available connectors, otherwise drmModeGetConnectorCurrent is called to get the kernel-cached connected connector. - /// Info: since some hardware might have incorrect connector information on startup for some reason, you may need to set this parameter to true. - /// Default: False - /// - public bool DrmConnectorsForceProbe { get; set; } = false; - - /// - /// Scaling factor. - /// Default: 1.0 - /// - public double Scaling { get; set; } = 1.0; - - /// - /// If true an two cycle buffer swapping is processed at init. - /// Default: True - /// - public bool EnableInitialBufferSwapping { get; set; } = true; - - /// - /// Color for - /// Default: R0 G0 B0 A0 - /// - public Color InitialBufferSwappingColor { get; set; } = new Color(0, 0, 0, 0); - - /// - /// IOutputBackend. If Null, a new DrmOutput for with will be created. - /// Default: null - /// - [CanBeNull] public IOutputBackend OutputBackend { get; set; } - } -} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index bf29aed1a4..a642766809 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -140,14 +140,8 @@ public static class LinuxFramebufferPlatformExtensions public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) {Scaling = scaling}); - - public static int StartLinuxDrm(this T builder, string[] args, [CanBeNull] LinuxDrmOptions drmOptions) - where T : AppBuilderBase, new() - { - drmOptions ??= new LinuxDrmOptions(); - drmOptions.OutputBackend ??= new DrmOutput(drmOptions) {Scaling = drmOptions.Scaling}; - return StartLinuxDirect(builder, args, drmOptions.OutputBackend); - } + public static int StartLinuxDrm(this T builder, string[] args, string card = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null) + where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options)); public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend backend) where T : AppBuilderBase, new() diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 85cf1fba76..509b0cee99 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -7,6 +7,7 @@ using Avalonia.OpenGL; using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; using Avalonia.Platform.Interop; +using JetBrains.Annotations; using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods; using static Avalonia.LinuxFramebuffer.Output.LibDrm; using static Avalonia.LinuxFramebuffer.Output.LibDrm.GbmColorFormats; @@ -15,27 +16,32 @@ namespace Avalonia.LinuxFramebuffer.Output { public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface { - private LinuxDrmOptions _options; + private DrmOutputOptions _outputOptions = new(); private DrmCard _card; public PixelSize PixelSize => _mode.Resolution; public double Scaling { - get => _options.Scaling; - set => _options.Scaling = value; + get => _outputOptions.Scaling; + set => _outputOptions.Scaling = value; } public IGlContext PrimaryContext => _deferredContext; private EglPlatformOpenGlInterface _platformGl; public IPlatformOpenGlInterface PlatformOpenGlInterface => _platformGl; - public DrmOutput(LinuxDrmOptions drmOptions) + public DrmOutput(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo, + DrmOutputOptions? options = null) { - _options = drmOptions; - CreateDrmOutput(_options.Card, _options.DrmConnectorsForceProbe); + if(options != null) + _outputOptions = options; + Init(card, resources, connector, modeInfo); } - public DrmOutput(string path = null) : this(new LinuxDrmOptions() { Card = path }) + public DrmOutput(string path = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null) { + if(options != null) + _outputOptions = options; + CreateDrmOutput(path, connectorsForceProbe); } private void CreateDrmOutput(string path = null, bool connectorsForceProbe = false) @@ -158,9 +164,9 @@ namespace Avalonia.LinuxFramebuffer.Output using (_deferredContext.MakeCurrent(_eglSurface)) { - _deferredContext.GlInterface.ClearColor(_options.InitialBufferSwappingColor.R, - _options.InitialBufferSwappingColor.G, _options.InitialBufferSwappingColor.B, - _options.InitialBufferSwappingColor.A); + _deferredContext.GlInterface.ClearColor(_outputOptions.InitialBufferSwappingColor.R, + _outputOptions.InitialBufferSwappingColor.G, _outputOptions.InitialBufferSwappingColor.B, + _outputOptions.InitialBufferSwappingColor.A); _deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT); _eglSurface.SwapBuffers(); } @@ -178,15 +184,15 @@ namespace Avalonia.LinuxFramebuffer.Output _mode = mode; _currentBo = bo; - if (_options.EnableInitialBufferSwapping) + if (_outputOptions.EnableInitialBufferSwapping) { //Go trough two cycles of buffer swapping (there are render artifacts otherwise) for(var c=0;c<2;c++) using (CreateGlRenderTarget().BeginDraw()) { - _deferredContext.GlInterface.ClearColor(_options.InitialBufferSwappingColor.R, - _options.InitialBufferSwappingColor.G, _options.InitialBufferSwappingColor.B, - _options.InitialBufferSwappingColor.A); + _deferredContext.GlInterface.ClearColor(_outputOptions.InitialBufferSwappingColor.R, + _outputOptions.InitialBufferSwappingColor.G, _outputOptions.InitialBufferSwappingColor.B, + _outputOptions.InitialBufferSwappingColor.A); _deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT); } } From 30f14e45c0678498294010b1f4b59837a6c21250 Mon Sep 17 00:00:00 2001 From: Markus Wenzl Date: Wed, 27 Jul 2022 08:00:38 +0200 Subject: [PATCH 39/97] Fix InitialBufferSwappingColor convertion to float --- .../Avalonia.LinuxFramebuffer/Output/DrmOutput.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 509b0cee99..6c1030925a 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -162,11 +162,14 @@ namespace Avalonia.LinuxFramebuffer.Output _deferredContext = _platformGl.PrimaryEglContext; + var initialBufferSwappingColorR = _outputOptions.InitialBufferSwappingColor.R / 255.0f; + var initialBufferSwappingColorG = _outputOptions.InitialBufferSwappingColor.G / 255.0f; + var initialBufferSwappingColorB = _outputOptions.InitialBufferSwappingColor.B / 255.0f; + var initialBufferSwappingColorA = _outputOptions.InitialBufferSwappingColor.A / 255.0f; using (_deferredContext.MakeCurrent(_eglSurface)) { - _deferredContext.GlInterface.ClearColor(_outputOptions.InitialBufferSwappingColor.R, - _outputOptions.InitialBufferSwappingColor.G, _outputOptions.InitialBufferSwappingColor.B, - _outputOptions.InitialBufferSwappingColor.A); + _deferredContext.GlInterface.ClearColor(initialBufferSwappingColorR, initialBufferSwappingColorG, + initialBufferSwappingColorB, initialBufferSwappingColorA); _deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT); _eglSurface.SwapBuffers(); } @@ -190,9 +193,8 @@ namespace Avalonia.LinuxFramebuffer.Output for(var c=0;c<2;c++) using (CreateGlRenderTarget().BeginDraw()) { - _deferredContext.GlInterface.ClearColor(_outputOptions.InitialBufferSwappingColor.R, - _outputOptions.InitialBufferSwappingColor.G, _outputOptions.InitialBufferSwappingColor.B, - _outputOptions.InitialBufferSwappingColor.A); + _deferredContext.GlInterface.ClearColor(initialBufferSwappingColorR, initialBufferSwappingColorG, + initialBufferSwappingColorB, initialBufferSwappingColorA); _deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT); } } From 406f9ced66f2355d6b67965baef1448d5132c9be Mon Sep 17 00:00:00 2001 From: Markus Wenzl Date: Wed, 27 Jul 2022 08:00:48 +0200 Subject: [PATCH 40/97] Clean up --- src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 6c1030925a..ce210019c0 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -41,11 +41,7 @@ namespace Avalonia.LinuxFramebuffer.Output { if(options != null) _outputOptions = options; - CreateDrmOutput(path, connectorsForceProbe); - } - - private void CreateDrmOutput(string path = null, bool connectorsForceProbe = false) - { + var card = new DrmCard(path); var resources = card.GetResources(connectorsForceProbe); From fcef0659085ab7527b514b91bd7f4079912e30a5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 27 Jul 2022 14:51:16 +0200 Subject: [PATCH 41/97] Fix flaky menu integration tests. Move the mouse pointer out of the way of the menu when running integration tests on menu keys. --- .../MenuTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index d1d231466f..3f1fe7de12 100644 --- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs @@ -60,6 +60,8 @@ namespace Avalonia.IntegrationTests.Appium [PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Alt_Arrow_Keys() { + MovePointerOutOfTheWay(); + new Actions(_session) .KeyDown(Keys.Alt).KeyUp(Keys.Alt) .SendKeys(Keys.Down + Keys.Enter) @@ -72,6 +74,8 @@ namespace Avalonia.IntegrationTests.Appium [PlatformFact(TestPlatforms.Windows)] public void Select_Grandchild_With_Alt_Arrow_Keys() { + MovePointerOutOfTheWay(); + new Actions(_session) .KeyDown(Keys.Alt).KeyUp(Keys.Alt) .SendKeys(Keys.Down + Keys.Down + Keys.Right + Keys.Enter) @@ -84,6 +88,8 @@ namespace Avalonia.IntegrationTests.Appium [PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Alt_Access_Keys() { + MovePointerOutOfTheWay(); + new Actions(_session) .KeyDown(Keys.Alt).KeyUp(Keys.Alt) .SendKeys("rc") @@ -96,6 +102,8 @@ namespace Avalonia.IntegrationTests.Appium [PlatformFact(TestPlatforms.Windows)] public void Select_Grandchild_With_Alt_Access_Keys() { + MovePointerOutOfTheWay(); + new Actions(_session) .KeyDown(Keys.Alt).KeyUp(Keys.Alt) .SendKeys("rhg") @@ -111,6 +119,8 @@ namespace Avalonia.IntegrationTests.Appium var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem"); rootMenuItem.SendClick(); + MovePointerOutOfTheWay(); + new Actions(_session) .SendKeys(Keys.Down + Keys.Enter) .Perform(); @@ -125,6 +135,8 @@ namespace Avalonia.IntegrationTests.Appium var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem"); rootMenuItem.SendClick(); + MovePointerOutOfTheWay(); + new Actions(_session) .SendKeys(Keys.Down + Keys.Down + Keys.Right + Keys.Enter) .Perform(); @@ -159,5 +171,15 @@ namespace Avalonia.IntegrationTests.Appium Assert.True(textBox.GetIsFocused()); } + + private void MovePointerOutOfTheWay() + { + // Move the pointer to the menu tab item so that it's not over the menu in preparation + // for key press tests. This prevents the mouse accidentially selecting the wrong item + // by hovering. + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("Menu"); + tab.MovePointerOver(); + } } } From 276e4c596bb97eeecdc3920d8e0ac47b899b1876 Mon Sep 17 00:00:00 2001 From: Mario Uhlmann Date: Wed, 27 Jul 2022 20:34:42 +0200 Subject: [PATCH 42/97] unused Stopwatch removed --- .../Composition/WinUICompositorConnection.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs index 5334b90d62..84c3cc5c51 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs @@ -120,16 +120,14 @@ namespace Avalonia.Win32.WinRT.Composition private void RunLoop() { + using (var act = _compositor5.RequestCommitAsync()) + act.SetCompleted(new RunLoopHandler(this)); + + while (true) { - var st = Stopwatch.StartNew(); - using (var act = _compositor5.RequestCommitAsync()) - act.SetCompleted(new RunLoopHandler(this)); - while (true) - { - UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0); - lock (_pumpLock) - UnmanagedMethods.DispatchMessage(ref msg); - } + UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0); + lock (_pumpLock) + UnmanagedMethods.DispatchMessage(ref msg); } } From 01d040bf6ca6043fb42fcc6cecb04ea176df3819 Mon Sep 17 00:00:00 2001 From: Evgeny Gorbovoy Date: Wed, 27 Jul 2022 23:25:43 +0200 Subject: [PATCH 43/97] + hit test attempt --- .../DeferredRendererTests_HitTesting.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs index 33c7b3e0f8..e1b36c9d72 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs @@ -537,6 +537,37 @@ namespace Avalonia.Base.UnitTests.Rendering } } + [Fact] + public void HitTest_Should_Not_Hit_Controls_Next_Pixel() + { + using (TestApplication()) + { + Button targetButton; + + var root = new TestRoot + { + Width = 300, + Height = 200, + Child = new StackPanel + { + Orientation = Orientation.Vertical, + Children = + { + new Button { Width = 10, Height = 10 }, + { targetButton = new Button { Width = 10, Height = 10 } } + } + } + }; + + root.Renderer = new DeferredRenderer(root, null); + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var result = root.Renderer.HitTest(new Point(5, 10), root, null); + Assert.Equal(new[] { targetButton }, result); + } + } + private IDisposable TestApplication() { return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); From d560fd28473df11cfe0824419cbf32bfb6f3ee88 Mon Sep 17 00:00:00 2001 From: Evgeny Gorbovoy Date: Wed, 27 Jul 2022 23:28:34 +0200 Subject: [PATCH 44/97] + CompositorHitTesting test --- .../Rendering/CompositorHitTestingTests.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs index 705c8fad9c..6543649eb2 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs @@ -408,10 +408,33 @@ public class CompositorHitTestingTests : CompositorTestsBase s.AssertHitTest(175, 100, null); } } + + [Fact] + public void HitTest_Should_Not_Hit_Controls_Next_Pixel() + { + using (var s = new CompositorServices(new Size(200, 200))) + { + Button targetButton; + + var stackPanel = new StackPanel + { + Orientation = Orientation.Vertical, + Children = + { + new Button { Width = 10, Height = 10 }, + { targetButton = new Button { Width = 10, Height = 10 } } + } + }; + + s.TopLevel.Content = stackPanel; + + s.AssertHitTest(new Point(5, 10), null, targetButton); + } + } private IDisposable TestApplication() { return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); } -} \ No newline at end of file +} From bd343a6290860e2ec546852f06d1b87105152d90 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 28 Jul 2022 10:41:44 +0100 Subject: [PATCH 45/97] update license for composition. --- .../Composition/Animations/AnimationInstanceBase.cs | 3 ++- .../Composition/Animations/CompositionAnimation.cs | 2 ++ .../Composition/Animations/CompositionAnimationGroup.cs | 2 ++ .../Composition/Animations/ExpressionAnimation.cs | 2 ++ .../Composition/Animations/ExpressionAnimationInstance.cs | 2 ++ .../Rendering/Composition/Animations/IAnimationInstance.cs | 2 ++ .../Composition/Animations/ICompositionAnimationBase.cs | 2 ++ .../Composition/Animations/ImplicitAnimationCollection.cs | 2 ++ .../Rendering/Composition/Animations/Interpolators.cs | 2 ++ .../Rendering/Composition/Animations/KeyFrameAnimation.cs | 2 ++ .../Composition/Animations/KeyFrameAnimationInstance.cs | 2 ++ .../Rendering/Composition/Animations/KeyFrames.cs | 2 ++ .../Composition/Animations/PropertySetSnapshot.cs | 2 ++ .../Rendering/Composition/CompositingRenderer.cs | 2 ++ .../Rendering/Composition/CompositionDrawListVisual.cs | 2 ++ .../Rendering/Composition/CompositionObject.cs | 2 ++ .../Rendering/Composition/CompositionPropertySet.cs | 2 ++ .../Rendering/Composition/CompositionTarget.cs | 2 ++ src/Avalonia.Base/Rendering/Composition/Compositor.cs | 2 ++ src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs | 2 ++ .../Rendering/Composition/Drawing/CompositionDrawList.cs | 2 ++ .../Composition/Drawing/CompositionDrawingContext.cs | 3 +++ .../Rendering/Composition/ElementCompositionPreview.cs | 2 ++ src/Avalonia.Base/Rendering/Composition/Enums.cs | 2 ++ .../Composition/Expressions/BuiltInExpressionFfi.cs | 2 ++ .../Composition/Expressions/DelegateExpressionFfi.cs | 2 ++ .../Rendering/Composition/Expressions/Expression.cs | 2 ++ .../Composition/Expressions/ExpressionEvaluationContext.cs | 2 ++ .../Composition/Expressions/ExpressionParseException.cs | 2 ++ .../Rendering/Composition/Expressions/ExpressionParser.cs | 2 ++ .../Composition/Expressions/ExpressionTrackedValues.cs | 2 ++ .../Rendering/Composition/Expressions/ExpressionVariant.cs | 2 ++ .../Rendering/Composition/Expressions/TokenParser.cs | 2 ++ .../Rendering/Composition/ICompositionTargetDebugEvents.cs | 2 ++ src/Avalonia.Base/Rendering/Composition/License.md | 7 +++++++ src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs | 2 ++ .../Rendering/Composition/Server/CompositionProperty.cs | 2 ++ .../Rendering/Composition/Server/DrawingContextProxy.cs | 2 ++ .../Rendering/Composition/Server/FpsCounter.cs | 2 ++ .../Rendering/Composition/Server/ReadbackIndices.cs | 2 ++ .../Composition/Server/ServerCompositionContainerVisual.cs | 2 ++ .../Composition/Server/ServerCompositionDrawListVisual.cs | 2 ++ .../Composition/Server/ServerCompositionSurface.cs | 2 ++ .../Composition/Server/ServerCompositionTarget.cs | 2 ++ .../Server/ServerCompositionVisual.DirtyProperties.cs | 2 ++ .../Composition/Server/ServerCompositionVisual.cs | 2 ++ .../Rendering/Composition/Server/ServerCompositor.cs | 2 ++ .../Rendering/Composition/Server/ServerList.cs | 2 ++ .../Rendering/Composition/Server/ServerObject.cs | 2 ++ src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs | 2 ++ .../Rendering/Composition/Transport/BatchStream.cs | 2 ++ .../Composition/Transport/BatchStreamArrayPool.cs | 2 ++ .../Composition/Transport/BatchStreamDebugMarker.cs | 2 ++ .../Composition/Transport/ServerListProxyHelper.cs | 2 ++ src/Avalonia.Base/Rendering/Composition/Visual.cs | 2 ++ .../Rendering/Composition/VisualCollection.cs | 2 ++ .../CompositionGenerator/Generator.KeyFrameAnimation.cs | 2 ++ 57 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Base/Rendering/Composition/License.md diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs index 80e64118ee..b624f89a2d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs @@ -3,8 +3,9 @@ using System.Collections.Generic; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; -namespace Avalonia.Rendering.Composition.Animations; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +namespace Avalonia.Rendering.Composition.Animations; /// /// The base class for both key-frame and expression animation instances diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs index c5102a2d7d..cb5615a737 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs @@ -8,6 +8,8 @@ using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Transport; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs index 89f8ba411d..e362b63887 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using Avalonia.Rendering.Composition.Transport; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { public class CompositionAnimationGroup : CompositionObject, ICompositionAnimationBase diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs index 163f4e99ba..c9b409fce4 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs @@ -3,6 +3,8 @@ using System; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs index 764bac9931..cd912df81a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs index 4e1972f2c6..bc9bfccc6b 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs @@ -2,6 +2,8 @@ using System; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { internal interface IAnimationInstance diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs index 87e5ad757a..51d23c296a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs @@ -2,6 +2,8 @@ using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs index f4bcc6ff38..2e39bdc46e 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Avalonia.Rendering.Composition.Transport; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs index a4eeacef32..960a76b1f8 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs @@ -1,6 +1,8 @@ using System; using System.Numerics; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs index 49b3ab753a..13f02ade8c 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs @@ -2,6 +2,8 @@ using System; using Avalonia.Animation; using Avalonia.Animation.Easings; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs index 0c0fcfaf2b..b0293b7996 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs @@ -4,6 +4,8 @@ using Avalonia.Animation; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs index 369cc80b95..1a0d967380 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using Avalonia.Animation.Easings; using Avalonia.Rendering.Composition.Expressions; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs b/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs index fc6cfc9f3d..f009d8dd11 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using Avalonia.Rendering.Composition.Expressions; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Animations { /// diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index a571a0518b..64cb43646c 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -11,6 +11,8 @@ using Avalonia.Rendering.Composition.Server; using Avalonia.Threading; using Avalonia.VisualTree; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition; /// diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs index 49aea1c3dc..b64a5afc97 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs @@ -5,6 +5,8 @@ using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Transport; using Avalonia.VisualTree; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition; diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs index f529ee9cff..9e42e4d182 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs @@ -5,6 +5,8 @@ using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Transport; using Avalonia.Utilities; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition { /// diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs index ee4552d154..a61ab809de 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs @@ -5,6 +5,8 @@ using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Transport; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition { /// diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs index 01b2d0d5d9..bb502e4d38 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs @@ -4,6 +4,8 @@ using System.Numerics; using Avalonia.Collections.Pooled; using Avalonia.VisualTree; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index 1bdae44cb9..f3258bbdb0 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -12,6 +12,8 @@ using Avalonia.Rendering.Composition.Transport; using Avalonia.Threading; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition { /// diff --git a/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs index caf074dd6b..a4fadd3134 100644 --- a/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs @@ -1,5 +1,7 @@ using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs index 432a0832f2..02ab269d29 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs @@ -4,6 +4,8 @@ using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Drawing; /// diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs index d7c1ef125d..b8922244ae 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs @@ -8,6 +8,9 @@ using Avalonia.Rendering.Composition.Drawing; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; using Avalonia.VisualTree; + +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition; /// diff --git a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs index 1397a20fb6..dd77423eca 100644 --- a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs +++ b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs @@ -1,3 +1,5 @@ +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition; /// diff --git a/src/Avalonia.Base/Rendering/Composition/Enums.cs b/src/Avalonia.Base/Rendering/Composition/Enums.cs index e349845cbf..0986e0b233 100644 --- a/src/Avalonia.Base/Rendering/Composition/Enums.cs +++ b/src/Avalonia.Base/Rendering/Composition/Enums.cs @@ -1,5 +1,7 @@ using System; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition { public enum CompositionBlendMode diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs index 44347d2c7a..e166279316 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs @@ -4,6 +4,8 @@ using System.Numerics; using Avalonia.Rendering.Composition.Animations; using Avalonia.Utilities; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Expressions { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs index 85c6141409..1e3ac77035 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs @@ -5,6 +5,8 @@ using System.Linq; using System.Numerics; using Avalonia.Media; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Expressions { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs index 5abba00365..cfe1260b22 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs @@ -4,6 +4,8 @@ using System.Globalization; using System.Reflection; using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Expressions { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs index 9d23551e43..3e4fba24bd 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Expressions { internal struct ExpressionEvaluationContext diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs index 6a207a3bf7..33b94eca76 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs @@ -1,5 +1,7 @@ using System; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Expressions { internal class ExpressionParseException : Exception diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs index 5924bb8f1b..901013a9ff 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs @@ -5,6 +5,8 @@ using System.Linq; // ReSharper disable StringLiteralTypo +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Expressions { internal class ExpressionParser diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs index 334f975aa0..b3c2dfcee9 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs @@ -2,6 +2,8 @@ using System.Collections; using System.Collections.Generic; using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Expressions; internal class ExpressionTrackedObjects : IEnumerable diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs index 7b900534d8..563c09c964 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs @@ -4,6 +4,8 @@ using System.Numerics; using System.Runtime.InteropServices; using Avalonia.Media; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Expressions { internal enum VariantType diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs index 27782c8c2c..cc20b3c870 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs @@ -1,6 +1,8 @@ using System; using System.Globalization; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Expressions { /// diff --git a/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs b/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs index 045a4f8cc6..45cc886cd0 100644 --- a/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs +++ b/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs @@ -1,3 +1,5 @@ +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition; internal interface ICompositionTargetDebugEvents diff --git a/src/Avalonia.Base/Rendering/Composition/License.md b/src/Avalonia.Base/Rendering/Composition/License.md new file mode 100644 index 0000000000..d1b709859e --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/License.md @@ -0,0 +1,7 @@ +Please note: Any code in this directory is excluded from the normal MIT license. + +This code is owned and copyright to Avalonia OU. + +This code may be used free of charge by any application that consumes Avalonia binary packages as a direct or indirect dependency. + +Explicit permission is required for any other use outside of Avalonia applications. \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs b/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs index 2cb500cae4..66dc6e7987 100644 --- a/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs +++ b/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs @@ -1,5 +1,7 @@ using System.Numerics; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition { static class MatrixUtils diff --git a/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs b/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs index 282c0e113d..f0aaa1d574 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Threading; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server; internal class CompositionProperty diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index e261507f60..6522af3706 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -6,6 +6,8 @@ using Avalonia.Rendering.Composition.Drawing; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server; /// diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs index 7585710540..d4fa02deef 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs @@ -6,6 +6,8 @@ using Avalonia.Media.TextFormatting; using Avalonia.Platform; using Avalonia.Utilities; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server; /// diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs b/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs index c9592b70ab..fbe9b66bb2 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs @@ -1,3 +1,5 @@ +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs index f7152293cc..1515a21fb1 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs @@ -1,6 +1,8 @@ using System.Numerics; using Avalonia.Platform; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index 93a5226f83..e1265f9eed 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -7,6 +7,8 @@ using Avalonia.Rendering.Composition.Transport; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server; /// diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs index 462a193a86..a03dd0ef77 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs @@ -1,3 +1,5 @@ +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server { internal abstract class ServerCompositionSurface : ServerObject diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 882b66bf70..0760972534 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -9,6 +9,8 @@ using Avalonia.Platform; using Avalonia.Rendering.Composition.Transport; using Avalonia.Utilities; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs index c5af74e2dd..082650cdb8 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs @@ -1,3 +1,5 @@ +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server; partial class ServerCompositionVisual diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index c0e487f209..49a54abf22 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -6,6 +6,8 @@ using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Transport; using Avalonia.Utilities; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs index 564f792ebe..ff30e45c31 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs @@ -6,6 +6,8 @@ using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Transport; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs index 39d6a8dc70..1f8bf0bf53 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using Avalonia.Rendering.Composition.Transport; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs index 93ea8e8dee..6bd6915bd7 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs @@ -7,6 +7,8 @@ using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Transport; using Avalonia.Utilities; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Server { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs index e69768d3bf..c0f4b94bd3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Transport { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs index 6db480a966..cb9804cdc3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs @@ -5,6 +5,8 @@ using System.Runtime.CompilerServices; using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Transport; /// diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs index b0a89c6f92..20a25fa769 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs @@ -5,6 +5,8 @@ using System.Runtime.InteropServices; using Avalonia.Platform; using Avalonia.Threading; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Transport; /// diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs index 7d21b03f24..2f7c4559aa 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs @@ -1,5 +1,7 @@ using System; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Transport; internal class BatchStreamDebugMarkers diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs b/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs index e295c3c2c8..8872d0c96f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs @@ -2,6 +2,8 @@ using System.Collections; using System.Collections.Generic; using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition.Transport { /// diff --git a/src/Avalonia.Base/Rendering/Composition/Visual.cs b/src/Avalonia.Base/Rendering/Composition/Visual.cs index 7ebbb0aa96..ebffa3396e 100644 --- a/src/Avalonia.Base/Rendering/Composition/Visual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Visual.cs @@ -3,6 +3,8 @@ using System.Numerics; using Avalonia.Media; using Avalonia.VisualTree; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition { /// diff --git a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs index 60ebd9271c..7db02148bf 100644 --- a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs +++ b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs @@ -1,6 +1,8 @@ using System; using Avalonia.Rendering.Composition.Server; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition { /// diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs index 7ad40f68e4..464c5e4a23 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs @@ -8,6 +8,8 @@ namespace Avalonia.SourceGenerator.CompositionGenerator using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Expressions; +// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md + namespace Avalonia.Rendering.Composition {{ "; From f6f8e71f3fde734dd5a1e14d3431b601e99d8d29 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 28 Jul 2022 10:47:38 +0100 Subject: [PATCH 46/97] make license linkable --- .../Rendering/Composition/Animations/AnimationInstanceBase.cs | 4 ++-- .../Rendering/Composition/Animations/CompositionAnimation.cs | 4 ++-- .../Composition/Animations/CompositionAnimationGroup.cs | 4 ++-- .../Rendering/Composition/Animations/ExpressionAnimation.cs | 4 ++-- .../Composition/Animations/ExpressionAnimationInstance.cs | 4 ++-- .../Rendering/Composition/Animations/IAnimationInstance.cs | 4 ++-- .../Composition/Animations/ICompositionAnimationBase.cs | 4 ++-- .../Composition/Animations/ImplicitAnimationCollection.cs | 4 ++-- .../Rendering/Composition/Animations/Interpolators.cs | 4 ++-- .../Rendering/Composition/Animations/KeyFrameAnimation.cs | 4 ++-- .../Composition/Animations/KeyFrameAnimationInstance.cs | 4 ++-- .../Rendering/Composition/Animations/KeyFrames.cs | 4 ++-- .../Rendering/Composition/Animations/PropertySetSnapshot.cs | 4 ++-- .../Rendering/Composition/CompositingRenderer.cs | 2 +- .../Rendering/Composition/CompositionDrawListVisual.cs | 4 ++-- src/Avalonia.Base/Rendering/Composition/CompositionObject.cs | 4 ++-- .../Rendering/Composition/CompositionPropertySet.cs | 4 ++-- src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs | 4 ++-- src/Avalonia.Base/Rendering/Composition/Compositor.cs | 4 ++-- src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs | 4 ++-- .../Rendering/Composition/Drawing/CompositionDrawList.cs | 4 ++-- .../Composition/Drawing/CompositionDrawingContext.cs | 4 ++-- .../Rendering/Composition/ElementCompositionPreview.cs | 4 ++-- src/Avalonia.Base/Rendering/Composition/Enums.cs | 4 ++-- .../Rendering/Composition/Expressions/BuiltInExpressionFfi.cs | 4 ++-- .../Composition/Expressions/DelegateExpressionFfi.cs | 4 ++-- .../Rendering/Composition/Expressions/Expression.cs | 4 ++-- .../Composition/Expressions/ExpressionEvaluationContext.cs | 4 ++-- .../Composition/Expressions/ExpressionParseException.cs | 4 ++-- .../Rendering/Composition/Expressions/ExpressionParser.cs | 4 ++-- .../Composition/Expressions/ExpressionTrackedValues.cs | 4 ++-- .../Rendering/Composition/Expressions/ExpressionVariant.cs | 4 ++-- .../Rendering/Composition/Expressions/TokenParser.cs | 4 ++-- .../Rendering/Composition/ICompositionTargetDebugEvents.cs | 4 ++-- src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs | 4 ++-- .../Rendering/Composition/Server/CompositionProperty.cs | 4 ++-- .../Rendering/Composition/Server/DrawingContextProxy.cs | 4 ++-- src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs | 4 ++-- .../Rendering/Composition/Server/ReadbackIndices.cs | 4 ++-- .../Composition/Server/ServerCompositionContainerVisual.cs | 4 ++-- .../Composition/Server/ServerCompositionDrawListVisual.cs | 4 ++-- .../Rendering/Composition/Server/ServerCompositionSurface.cs | 4 ++-- .../Rendering/Composition/Server/ServerCompositionTarget.cs | 4 ++-- .../Server/ServerCompositionVisual.DirtyProperties.cs | 4 ++-- .../Rendering/Composition/Server/ServerCompositionVisual.cs | 4 ++-- .../Rendering/Composition/Server/ServerCompositor.cs | 4 ++-- src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs | 4 ++-- .../Rendering/Composition/Server/ServerObject.cs | 4 ++-- src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs | 4 ++-- .../Rendering/Composition/Transport/BatchStream.cs | 2 +- .../Rendering/Composition/Transport/BatchStreamArrayPool.cs | 4 ++-- .../Rendering/Composition/Transport/BatchStreamDebugMarker.cs | 4 ++-- .../Rendering/Composition/Transport/ServerListProxyHelper.cs | 4 ++-- src/Avalonia.Base/Rendering/Composition/Visual.cs | 4 ++-- src/Avalonia.Base/Rendering/Composition/VisualCollection.cs | 4 ++-- .../CompositionGenerator/Generator.KeyFrameAnimation.cs | 4 ++-- 56 files changed, 110 insertions(+), 110 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs index b624f89a2d..889c1d34c8 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations; @@ -80,4 +80,4 @@ internal abstract class AnimationInstanceBase : IAnimationInstance _invalidated = true; TargetObject.NotifyAnimatedValueChanged(Property); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs index cb5615a737..19d316eb85 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs @@ -8,7 +8,7 @@ using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Transport; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -74,4 +74,4 @@ namespace Avalonia.Rendering.Composition.Animations } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs index e362b63887..bad3991f43 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Avalonia.Rendering.Composition.Transport; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -23,4 +23,4 @@ namespace Avalonia.Rendering.Composition.Animations { } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs index c9b409fce4..577910d975 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs @@ -3,7 +3,7 @@ using System; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -52,4 +52,4 @@ namespace Avalonia.Rendering.Composition.Animations => new ExpressionAnimationInstance(ParsedExpression, targetObject, finalValue, CreateSnapshot()); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs index cd912df81a..6c7c556456 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -48,4 +48,4 @@ namespace Avalonia.Rendering.Composition.Animations _finalValue = finalValue; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs index bc9bfccc6b..8ec4ec19bc 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs @@ -2,7 +2,7 @@ using System; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -15,4 +15,4 @@ namespace Avalonia.Rendering.Composition.Animations void Deactivate(); void Invalidate(); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs index 51d23c296a..91b37a81af 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs @@ -2,7 +2,7 @@ using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -14,4 +14,4 @@ namespace Avalonia.Rendering.Composition.Animations internal void InternalOnly(); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs index 2e39bdc46e..72be4edd07 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Avalonia.Rendering.Composition.Transport; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -81,4 +81,4 @@ namespace Avalonia.Rendering.Composition.Animations return rv; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs index 960a76b1f8..e19c0ff098 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs @@ -1,7 +1,7 @@ using System; using System.Numerics; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -75,4 +75,4 @@ namespace Avalonia.Rendering.Composition.Animations public static BooleanInterpolator Instance { get; } = new BooleanInterpolator(); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs index 13f02ade8c..4692fde5e3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs @@ -2,7 +2,7 @@ using System; using Avalonia.Animation; using Avalonia.Animation.Easings; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -133,4 +133,4 @@ namespace Avalonia.Rendering.Composition.Animations /// SetToFinalValue } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs index b0293b7996..e20a4a9ad8 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs @@ -4,7 +4,7 @@ using Avalonia.Animation; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -177,4 +177,4 @@ namespace Avalonia.Rendering.Composition.Animations base.Deactivate(); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs index 1a0d967380..79045ccff8 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Avalonia.Animation.Easings; using Avalonia.Rendering.Composition.Expressions; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -88,4 +88,4 @@ namespace Avalonia.Rendering.Composition.Animations { public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, IEasing easingFunction); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs b/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs index f009d8dd11..af3c344999 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Avalonia.Rendering.Composition.Expressions; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Animations { @@ -48,4 +48,4 @@ namespace Avalonia.Rendering.Composition.Animations public ExpressionVariant GetProperty(string name) => GetParameter(name); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 64cb43646c..db773bc43c 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -11,7 +11,7 @@ using Avalonia.Rendering.Composition.Server; using Avalonia.Threading; using Avalonia.VisualTree; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition; diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs index b64a5afc97..77b392eee5 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs @@ -5,7 +5,7 @@ using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Transport; using Avalonia.VisualTree; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition; @@ -74,4 +74,4 @@ internal class CompositionDrawListVisual : CompositionContainerVisual return true; return false; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs index 9e42e4d182..50332926ad 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs @@ -5,7 +5,7 @@ using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Transport; using Avalonia.Utilities; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition { @@ -140,4 +140,4 @@ namespace Avalonia.Rendering.Composition writer.Write((byte)(IsDisposed ? 1 : 0)); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs index a61ab809de..7d794af9a2 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs @@ -5,7 +5,7 @@ using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Transport; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition { @@ -146,4 +146,4 @@ namespace Avalonia.Rendering.Composition TypeMismatch, NotFound } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs index bb502e4d38..4e53e163ec 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs @@ -4,7 +4,7 @@ using System.Numerics; using Avalonia.Collections.Pooled; using Avalonia.VisualTree; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition { @@ -129,4 +129,4 @@ namespace Avalonia.Rendering.Composition Compositor.Server.Render(); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index f3258bbdb0..45212d0f36 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -12,7 +12,7 @@ using Avalonia.Rendering.Composition.Transport; using Avalonia.Threading; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition { @@ -140,4 +140,4 @@ namespace Avalonia.Rendering.Composition _invokeOnNextCommit.Add(action); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs index a4fadd3134..9b9c44b95f 100644 --- a/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs @@ -1,6 +1,6 @@ using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition { @@ -23,4 +23,4 @@ namespace Avalonia.Rendering.Composition base.OnRootChangedCore(); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs index 02ab269d29..10a7c3e360 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs @@ -4,7 +4,7 @@ using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Drawing; @@ -101,4 +101,4 @@ internal class CompositionDrawListBuilder if (count < Count) _operations!.RemoveRange(count, _operations.Count - count); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs index b8922244ae..01216d19ed 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs @@ -9,7 +9,7 @@ using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; using Avalonia.VisualTree; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition; @@ -391,4 +391,4 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW } return null; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs index dd77423eca..5bd8e4a4d3 100644 --- a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs +++ b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs @@ -1,4 +1,4 @@ -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition; @@ -13,4 +13,4 @@ public static class ElementComposition /// /// public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual; -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Enums.cs b/src/Avalonia.Base/Rendering/Composition/Enums.cs index 0986e0b233..cc37b25701 100644 --- a/src/Avalonia.Base/Rendering/Composition/Enums.cs +++ b/src/Avalonia.Base/Rendering/Composition/Enums.cs @@ -1,6 +1,6 @@ using System; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition { @@ -119,4 +119,4 @@ namespace Avalonia.Rendering.Composition Fill = 1, //TODO: Uniform, UniformToFill } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs index e166279316..896ae4fc3a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs @@ -4,7 +4,7 @@ using System.Numerics; using Avalonia.Rendering.Composition.Animations; using Avalonia.Utilities; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Expressions { @@ -236,4 +236,4 @@ namespace Avalonia.Rendering.Composition.Expressions public static BuiltInExpressionFfi Instance { get; } = new BuiltInExpressionFfi(); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs index 1e3ac77035..c15487065c 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Numerics; using Avalonia.Media; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Expressions { @@ -183,4 +183,4 @@ namespace Avalonia.Rendering.Composition.Expressions ); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs index cfe1260b22..ff2069e71e 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs @@ -4,7 +4,7 @@ using System.Globalization; using System.Reflection; using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Expressions { @@ -376,4 +376,4 @@ namespace Avalonia.Rendering.Composition.Expressions -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs index 3e4fba24bd..9086c59aad 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Expressions { @@ -31,4 +31,4 @@ namespace Avalonia.Rendering.Composition.Expressions { bool Call(string name, IReadOnlyList arguments, out ExpressionVariant result); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs index 33b94eca76..c32a7832a6 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs @@ -1,6 +1,6 @@ using System; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Expressions { @@ -13,4 +13,4 @@ namespace Avalonia.Rendering.Composition.Expressions Position = position; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs index 901013a9ff..3937d48187 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs @@ -5,7 +5,7 @@ using System.Linq; // ReSharper disable StringLiteralTypo -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Expressions { @@ -297,4 +297,4 @@ namespace Avalonia.Rendering.Composition.Expressions } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs index b3c2dfcee9..e386e058df 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs @@ -2,7 +2,7 @@ using System.Collections; using System.Collections.Generic; using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Expressions; @@ -56,4 +56,4 @@ internal class ExpressionTrackedObjects : IEnumerable _stack.Push(obj); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs index 563c09c964..6e53a138cd 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs @@ -4,7 +4,7 @@ using System.Numerics; using System.Runtime.InteropServices; using Avalonia.Media; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Expressions { @@ -729,4 +729,4 @@ namespace Avalonia.Rendering.Composition.Expressions } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs index cc20b3c870..bb7372c375 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs @@ -1,7 +1,7 @@ using System; using System.Globalization; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Expressions { @@ -258,4 +258,4 @@ namespace Avalonia.Rendering.Composition.Expressions public override string ToString() => _s.ToString(); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs b/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs index 45cc886cd0..131a51b5e6 100644 --- a/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs +++ b/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs @@ -1,8 +1,8 @@ -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition; internal interface ICompositionTargetDebugEvents { void RectInvalidated(Rect rc); -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs b/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs index 66dc6e7987..3d8d5bae78 100644 --- a/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs +++ b/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs @@ -1,6 +1,6 @@ using System.Numerics; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition { @@ -65,4 +65,4 @@ namespace Avalonia.Rendering.Composition matrix44.M42, matrix44.M44); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs b/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs index f0aaa1d574..5d63c8a4b3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Threading; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server; @@ -14,4 +14,4 @@ internal class CompositionProperty { Id = Interlocked.Increment(ref s_NextId) }; -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 6522af3706..7eb35a68ed 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -6,7 +6,7 @@ using Avalonia.Rendering.Composition.Drawing; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server; @@ -178,4 +178,4 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont if (_impl is IDrawingContextWithAcrylicLikeSupport acrylic) acrylic.DrawRectangle(material, rect); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs index d4fa02deef..b0b3982ed5 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs @@ -6,7 +6,7 @@ using Avalonia.Media.TextFormatting; using Avalonia.Platform; using Avalonia.Utilities; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server; @@ -75,4 +75,4 @@ internal class FpsCounter offset += run.Size.Width; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs b/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs index fbe9b66bb2..d9df176e66 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs @@ -1,4 +1,4 @@ -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server { @@ -45,4 +45,4 @@ namespace Avalonia.Rendering.Composition.Server } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs index 1515a21fb1..19349a5196 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs @@ -1,7 +1,7 @@ using System.Numerics; using Avalonia.Platform; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server { @@ -43,4 +43,4 @@ namespace Avalonia.Rendering.Composition.Server Children = new ServerCompositionVisualCollection(Compositor); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index e1265f9eed..6cbd2797f9 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -7,7 +7,7 @@ using Avalonia.Rendering.Composition.Transport; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server; @@ -74,4 +74,4 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua return UiVisual.GetType().ToString(); } #endif -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs index a03dd0ef77..32a99fa187 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs @@ -1,4 +1,4 @@ -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server { @@ -8,4 +8,4 @@ namespace Avalonia.Rendering.Composition.Server { } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 0760972534..c20594aaca 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -9,7 +9,7 @@ using Avalonia.Platform; using Avalonia.Rendering.Composition.Transport; using Avalonia.Utilities; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server { @@ -220,4 +220,4 @@ namespace Avalonia.Rendering.Composition.Server public void EnqueueAdornerUpdate(ServerCompositionVisual visual) => _adornerUpdateQueue.Enqueue(visual); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs index 082650cdb8..405c7879b7 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs @@ -1,4 +1,4 @@ -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server; @@ -75,4 +75,4 @@ partial class ServerCompositionVisual || offset == s_IdOfSizeProperty) _clipSizeDirty = true; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 49a54abf22..b742f1d44a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -6,7 +6,7 @@ using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Transport; using Avalonia.Utilities; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server { @@ -236,4 +236,4 @@ namespace Avalonia.Rendering.Composition.Server } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs index ff30e45c31..621bc84f4a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs @@ -6,7 +6,7 @@ using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Transport; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server { @@ -139,4 +139,4 @@ namespace Avalonia.Rendering.Composition.Server public void RemoveFromClock(IAnimationInstance animationInstance) => _activeAnimations.Remove(animationInstance); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs index 1f8bf0bf53..08a3fdce22 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using Avalonia.Rendering.Composition.Transport; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server { @@ -43,4 +43,4 @@ namespace Avalonia.Rendering.Composition.Server { } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs index 6bd6915bd7..257d3b29a2 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs @@ -7,7 +7,7 @@ using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Transport; using Avalonia.Utilities; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Server { @@ -179,4 +179,4 @@ namespace Avalonia.Rendering.Composition.Server ItselfLastChangedBy = batch.SequenceId; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs index c0f4b94bd3..0cf1650ccf 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Transport { @@ -38,4 +38,4 @@ namespace Avalonia.Rendering.Composition.Transport public Task Completed => _tcs.Task; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs index cb9804cdc3..8b68900994 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Transport; diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs index 20a25fa769..f24f449551 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; using Avalonia.Platform; using Avalonia.Threading; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Transport; @@ -155,4 +155,4 @@ internal sealed class BatchStreamMemoryPool : BatchStreamPoolBase protected override IntPtr CreateItem() => Marshal.AllocHGlobal(BufferSize); protected override void DestroyItem(IntPtr item) => Marshal.FreeHGlobal(item); -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs index 2f7c4559aa..fed0f4d4aa 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs @@ -1,6 +1,6 @@ using System; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Transport; @@ -8,4 +8,4 @@ internal class BatchStreamDebugMarkers { public static object ObjectEndMarker = new object(); public static Guid ObjectEndMagic = Guid.NewGuid(); -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs b/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs index 8872d0c96f..603c7582a1 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs @@ -2,7 +2,7 @@ using System.Collections; using System.Collections.Generic; using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition.Transport { @@ -97,4 +97,4 @@ namespace Avalonia.Rendering.Composition.Transport _changed = false; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Visual.cs b/src/Avalonia.Base/Rendering/Composition/Visual.cs index ebffa3396e..7356b7b9e8 100644 --- a/src/Avalonia.Base/Rendering/Composition/Visual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Visual.cs @@ -3,7 +3,7 @@ using System.Numerics; using Avalonia.Media; using Avalonia.VisualTree; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition { @@ -55,4 +55,4 @@ namespace Avalonia.Rendering.Composition internal virtual bool HitTest(Point point, Func? filter) => true; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs index 7db02148bf..6981dc39e3 100644 --- a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs +++ b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs @@ -1,7 +1,7 @@ using System; using Avalonia.Rendering.Composition.Server; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition { @@ -72,4 +72,4 @@ namespace Avalonia.Rendering.Composition item.Parent = item; } } -} \ No newline at end of file +} diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs index 464c5e4a23..00728d4579 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs @@ -8,7 +8,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Expressions; -// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition {{ @@ -58,4 +58,4 @@ namespace Avalonia.Rendering.Composition _output.AddSource("CompositionAnimations.cs", code); } } -} \ No newline at end of file +} From fa54d7ecde0af4119dc3d0785c3c11218752716d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 28 Jul 2022 10:50:18 +0100 Subject: [PATCH 47/97] add a readme for the compostiion directory. --- src/Avalonia.Base/Rendering/Composition/readme.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/Avalonia.Base/Rendering/Composition/readme.md diff --git a/src/Avalonia.Base/Rendering/Composition/readme.md b/src/Avalonia.Base/Rendering/Composition/readme.md new file mode 100644 index 0000000000..27a23bb029 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/readme.md @@ -0,0 +1,3 @@ +Please note the compostiong renderer is not subject to the usual MIT license. + +Please contact us for more details and see [License.md](https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md) From ea58d04e60d06e9a51c8293bca0b34ef433279e7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 28 Jul 2022 11:23:17 +0100 Subject: [PATCH 48/97] typo --- src/Avalonia.Base/Rendering/Composition/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Rendering/Composition/readme.md b/src/Avalonia.Base/Rendering/Composition/readme.md index 27a23bb029..6c238db191 100644 --- a/src/Avalonia.Base/Rendering/Composition/readme.md +++ b/src/Avalonia.Base/Rendering/Composition/readme.md @@ -1,3 +1,3 @@ -Please note the compostiong renderer is not subject to the usual MIT license. +Please note the composition renderer is not subject to the usual MIT license. Please contact us for more details and see [License.md](https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md) From c226432e9b6e3392e93c38d1708ed6dc61cc291b Mon Sep 17 00:00:00 2001 From: Evgeny Gorbovoy Date: Thu, 28 Jul 2022 14:40:38 +0200 Subject: [PATCH 49/97] Tests fixing --- .../Rendering/CompositorHitTestingTests.cs | 9 +++++---- .../Rendering/DeferredRendererTests_HitTesting.cs | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs index 6543649eb2..a584a859ec 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs @@ -414,21 +414,22 @@ public class CompositorHitTestingTests : CompositorTestsBase { using (var s = new CompositorServices(new Size(200, 200))) { - Button targetButton; + Rectangle targetRectangle; var stackPanel = new StackPanel { Orientation = Orientation.Vertical, + HorizontalAlignment = HorizontalAlignment.Left, Children = { - new Button { Width = 10, Height = 10 }, - { targetButton = new Button { Width = 10, Height = 10 } } + new Rectangle { Width = 10, Height = 10, Fill= Brushes.Red}, + { targetRectangle = new Rectangle { Width = 10, Height = 10, Fill = Brushes.Green} } } }; s.TopLevel.Content = stackPanel; - s.AssertHitTest(new Point(5, 10), null, targetButton); + s.AssertHitTest(new Point(5, 10), null, targetRectangle); } } diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs index e1b36c9d72..7800ddfc8a 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs @@ -542,7 +542,7 @@ namespace Avalonia.Base.UnitTests.Rendering { using (TestApplication()) { - Button targetButton; + Rectangle targetRectangle; var root = new TestRoot { @@ -551,10 +551,11 @@ namespace Avalonia.Base.UnitTests.Rendering Child = new StackPanel { Orientation = Orientation.Vertical, + HorizontalAlignment = HorizontalAlignment.Left, Children = { - new Button { Width = 10, Height = 10 }, - { targetButton = new Button { Width = 10, Height = 10 } } + new Rectangle { Width = 10, Height = 10, Fill = Brushes.Red}, + { targetRectangle = new Rectangle { Width = 10, Height = 10, Fill = Brushes.Green} } } } }; @@ -564,7 +565,7 @@ namespace Avalonia.Base.UnitTests.Rendering root.Arrange(new Rect(root.DesiredSize)); var result = root.Renderer.HitTest(new Point(5, 10), root, null); - Assert.Equal(new[] { targetButton }, result); + Assert.Equal(new[] { targetRectangle }, result); } } From 45d5a753edac3d87dcca6088435f87d723419f3c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Jul 2022 16:39:16 +0200 Subject: [PATCH 50/97] Fix broken build. --- .../CompositionGenerator/Generator.KeyFrameAnimation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs index 00728d4579..9455f5fac2 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs @@ -8,7 +8,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Expressions; -// Special license applies License.md +// Special license applies License.md namespace Avalonia.Rendering.Composition {{ From 23b08b1f34734af2c8942f75ef0cf88181943fd9 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 28 Jul 2022 17:11:39 +0200 Subject: [PATCH 51/97] Attempt to fix consistent hit testing results GetCharacterHitFromDistance and GetDistanceFromCharacterHit should match all the time --- samples/Sandbox/MainWindow.axaml | 10 + samples/Sandbox/MainWindow.axaml.cs | 75 +++- src/Avalonia.Base/Media/GlyphRun.cs | 2 +- .../TextFormatting/FormattedTextSource.cs | 24 ++ .../Media/TextFormatting/TextLineImpl.cs | 339 ++++++++++++++---- .../Presenters/TextPresenter.cs | 5 +- src/Avalonia.Controls/TextBox.cs | 10 +- .../Media/TextFormatting/TextLayoutTests.cs | 68 +++- 8 files changed, 453 insertions(+), 80 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index 6929f192c7..0fc78795a6 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,4 +1,14 @@ + + + + + + + + + + diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs index 3d54036d29..7a8b56bbb6 100644 --- a/samples/Sandbox/MainWindow.axaml.cs +++ b/samples/Sandbox/MainWindow.axaml.cs @@ -1,7 +1,11 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.CompilerServices; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Markup.Xaml; -using Avalonia.Win32.WinRT.Composition; +using Avalonia.VisualTree; namespace Sandbox { @@ -11,6 +15,19 @@ namespace Sandbox { this.InitializeComponent(); this.AttachDevTools(); + + var textBox = this.FindControl("txtBox"); + + textBox.TemplateApplied += TextBox_TemplateApplied; + } + + private void TextBox_TemplateApplied(object sender, Avalonia.Controls.Primitives.TemplateAppliedEventArgs e) + { + var textBox = sender as TextBox; + + var textPresenter = e.NameScope.Find("PART_TextPresenter") as TextPresenter; + + DataContext = new TestViewModel(textPresenter); } private void InitializeComponent() @@ -18,4 +35,60 @@ namespace Sandbox AvaloniaXamlLoader.Load(this); } } + + public class TestViewModel : ViewModelBase + { + private readonly TextPresenter _textPresenter; + private double _distance = 45; + + public TestViewModel(TextPresenter textPresenter) + { + _textPresenter = textPresenter; + } + + public double Distance + { + get => _distance; + set + { + OnDistanceChanged(value); + RaisePropertyChanged(); + } + } + + private void OnDistanceChanged(double distance) + { + if(distance < 0) + { + distance = 0; + } + + if(distance > _textPresenter.TextLayout.Bounds.Width) + { + distance = _textPresenter.TextLayout.Bounds.Width; + } + + var height = _textPresenter.TextLayout.Bounds.Height; + + var distanceY = height / 2; + + _textPresenter.MoveCaretToPoint(new Point(distance, distanceY)); + + var caretIndex = _textPresenter.CaretIndex; + + Debug.WriteLine(caretIndex); + + _distance = distance; + } + } + + public class ViewModelBase : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected void RaisePropertyChanged([CallerMemberName]string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } } diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 2a7f3360ad..cae7a8fe75 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -265,7 +265,7 @@ namespace Avalonia.Media //RightToLeft var glyphIndex = FindGlyphIndex(characterIndex); - if (GlyphClusters != null) + if (GlyphClusters != null && GlyphClusters.Count > 0) { if (characterIndex > GlyphClusters[0]) { diff --git a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs index 97df87d3d9..7ab67ea34d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs +++ b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting @@ -116,7 +117,30 @@ namespace Avalonia.Media.TextFormatting length = text.Length; } + length = CoerceLength(text, length); + return new ValueSpan(firstTextSourceIndex, length, currentProperties); } + + private static int CoerceLength(ReadOnlySlice text, int length) + { + var finalLength = 0; + + var graphemeEnumerator = new GraphemeEnumerator(text); + + while (graphemeEnumerator.MoveNext()) + { + var grapheme = graphemeEnumerator.Current; + + finalLength += grapheme.Text.Length; + + if (finalLength >= length) + { + return finalLength; + } + } + + return length; + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index f3c62f4994..0ee791d935 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -183,8 +183,47 @@ namespace Avalonia.Media.TextFormatting var currentPosition = FirstTextSourceIndex; var currentDistance = 0.0; - foreach (var currentRun in _textRuns) + for (var i = 0; i < _textRuns.Count; i++) { + var currentRun = _textRuns[i]; + + if(currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) + { + var rightToLeftIndex = i; + var rightToLeftDistance = shapedRun.Size.Width; + + while (rightToLeftIndex + 1 <= _textRuns.Count - 1) + { + var nextShaped = _textRuns[rightToLeftIndex + 1] as ShapedTextCharacters; + + if (nextShaped == null || nextShaped.ShapedBuffer.IsLeftToRight) + { + break; + } + + rightToLeftIndex++; + + rightToLeftDistance += nextShaped.Size.Width; + } + + for (var j = rightToLeftIndex; rightToLeftIndex >= 0; j--) + { + currentRun = _textRuns[j]; + + if(distance <= currentDistance + rightToLeftDistance - currentRun.Size.Width) + { + currentPosition += currentRun.TextSourceLength; + rightToLeftDistance -= currentRun.Size.Width; + + continue; + } + + characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance); + + break; + } + } + if (currentDistance + currentRun.Size.Width < distance) { currentDistance += currentRun.Size.Width; @@ -255,6 +294,37 @@ namespace Avalonia.Media.TextFormatting { var currentRun = _textRuns[index]; + if (currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) + { + var i = index; + + while (i + 1 <= _textRuns.Count - 1) + { + var nextRun = _textRuns[i + 1]; + + if (nextRun is ShapedTextCharacters nextShapedRun) + { + if (nextShapedRun.ShapedBuffer.IsLeftToRight) + { + break; + } + } + + i++; + } + + while (i > index) + { + var rightToLeftRun = _textRuns[i]; + + currentPosition += rightToLeftRun.TextSourceLength; + + remainingLength -= rightToLeftRun.TextSourceLength; + + i--; + } + } + if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _)) { @@ -442,92 +512,130 @@ namespace Avalonia.Media.TextFormatting continue; } - if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex) - { - startX += currentRun.Size.Width; - - currentPosition += currentRun.TextSourceLength; - - continue; - } - var characterLength = 0; var endX = startX; + var runWidth = 0.0; + TextRunBounds? currentRunBounds = null; - if (currentRun is ShapedTextCharacters currentShapedRun) + var currentShapedRun = currentRun as ShapedTextCharacters; + + if (currentShapedRun != null && !currentShapedRun.ShapedBuffer.IsLeftToRight) { - var offset = Math.Max(0, firstTextSourceIndex - currentPosition); + var rightToLeftIndex = index; + startX += currentShapedRun.Size.Width; - currentPosition += offset; + while (rightToLeftIndex + 1 <= _textRuns.Count - 1) + { + var nextShapedRun = _textRuns[rightToLeftIndex + 1] as ShapedTextCharacters; - var startIndex = currentRun.Text.Start + offset; + if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight) + { + break; + } - double startOffset; - double endOffset; + startX += nextShapedRun.Size.Width; - if (currentShapedRun.ShapedBuffer.IsLeftToRight) + rightToLeftIndex++; + } + + if (TryGetTextRunBoundsRightToLeft(startX, firstTextSourceIndex, characterIndex, rightToLeftIndex, ref currentPosition, ref remainingLength, out currentRunBounds)) { - startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + startX = currentRunBounds!.Rectangle.Left; + endX = currentRunBounds.Rectangle.Right; - endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + runWidth = currentRunBounds.Rectangle.Width; } - else + + currentDirection = FlowDirection.RightToLeft; + } + else + { + if (currentShapedRun != null) { - endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex) + { + startX += currentRun.Size.Width; - if (currentPosition < startIndex) + currentPosition += currentRun.TextSourceLength; + + continue; + } + + var offset = Math.Max(0, firstTextSourceIndex - currentPosition); + + currentPosition += offset; + + var startIndex = currentRun.Text.Start + offset; + + double startOffset; + double endOffset; + + if (currentShapedRun.ShapedBuffer.IsLeftToRight) { - startOffset = endOffset; + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); } else { - startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + + if (currentPosition < startIndex) + { + startOffset = endOffset; + } + else + { + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + } } - } - startX += startOffset; + startX += startOffset; - endX += endOffset; + endX += endOffset; - var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); - var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); + var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); + var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); - characterLength = Math.Abs(endHit.FirstCharacterIndex + endHit.TrailingLength - startHit.FirstCharacterIndex - startHit.TrailingLength); + characterLength = Math.Abs(endHit.FirstCharacterIndex + endHit.TrailingLength - startHit.FirstCharacterIndex - startHit.TrailingLength); - currentDirection = currentShapedRun.ShapedBuffer.IsLeftToRight ? - FlowDirection.LeftToRight : - FlowDirection.RightToLeft; - } - else - { - if (currentPosition < firstTextSourceIndex) + currentDirection = FlowDirection.LeftToRight; + } + else { - startX += currentRun.Size.Width; + if (currentPosition < firstTextSourceIndex) + { + startX += currentRun.Size.Width; + } + + if (currentPosition + currentRun.TextSourceLength <= characterIndex) + { + endX += currentRun.Size.Width; + + characterLength = currentRun.TextSourceLength; + } } - if (currentPosition + currentRun.TextSourceLength <= characterIndex) + if (endX < startX) { - endX += currentRun.Size.Width; + (endX, startX) = (startX, endX); + } - characterLength = currentRun.TextSourceLength; + //Lines that only contain a linebreak need to be covered here + if (characterLength == 0) + { + characterLength = NewLineLength; } - } - if (endX < startX) - { - (endX, startX) = (startX, endX); - } + runWidth = endX - startX; + currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); - //Lines that only contain a linebreak need to be covered here - if (characterLength == 0) - { - characterLength = NewLineLength; - } + currentPosition += characterLength; - var runWidth = endX - startX; - var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); + remainingLength -= characterLength; + } - if (!MathUtilities.IsZero(runWidth) || NewLineLength > 0) + if (currentRunBounds != null && !MathUtilities.IsZero(runWidth) || NewLineLength > 0) { if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) { @@ -537,32 +645,26 @@ namespace Avalonia.Media.TextFormatting textBounds.Rectangle = currentRect; - textBounds.TextRunBounds.Add(currentRunBounds); + textBounds.TextRunBounds.Add(currentRunBounds!); } else { - currentRect = currentRunBounds.Rectangle; + currentRect = currentRunBounds!.Rectangle; result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds })); } } currentWidth += runWidth; - currentPosition += characterLength; + - if (currentPosition > characterIndex) + if (remainingLength <= 0 || currentPosition >= characterIndex) { break; } startX = endX; lastDirection = currentDirection; - remainingLength -= characterLength; - - if (remainingLength <= 0) - { - break; - } } return result; @@ -674,7 +776,7 @@ namespace Avalonia.Media.TextFormatting var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); - if(!MathUtilities.IsZero(runWidth) || NewLineLength > 0) + if (!MathUtilities.IsZero(runWidth) || NewLineLength > 0) { if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX)) { @@ -692,7 +794,7 @@ namespace Avalonia.Media.TextFormatting result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds })); } - } + } currentWidth += runWidth; currentPosition += characterLength; @@ -716,6 +818,107 @@ namespace Avalonia.Media.TextFormatting return result; } + private bool TryGetTextRunBoundsRightToLeft(double startX, int firstTextSourceIndex, int characterIndex, int runIndex, ref int currentPosition, ref int remainingLength, out TextRunBounds? textRunBounds) + { + textRunBounds = null; + + for (var index = runIndex; index >= 0; index--) + { + if (TextRuns[index] is not DrawableTextRun currentRun) + { + continue; + } + + if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex) + { + startX -= currentRun.Size.Width; + + currentPosition += currentRun.TextSourceLength; + + continue; + } + + var characterLength = 0; + var endX = startX; + + if (currentRun is ShapedTextCharacters currentShapedRun) + { + var offset = Math.Max(0, firstTextSourceIndex - currentPosition); + + currentPosition += offset; + + var startIndex = currentRun.Text.Start + offset; + double startOffset; + double endOffset; + + if (currentShapedRun.ShapedBuffer.IsLeftToRight) + { + if (currentPosition < startIndex) + { + startOffset = endOffset = 0; + } + else + { + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + } + } + else + { + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + } + + startX -= currentRun.Size.Width - startOffset; + endX -= currentRun.Size.Width - endOffset; + + var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); + var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); + + characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength); + } + else + { + if (currentPosition + currentRun.TextSourceLength <= characterIndex) + { + endX -= currentRun.Size.Width; + } + + if (currentPosition < firstTextSourceIndex) + { + startX -= currentRun.Size.Width; + + characterLength = currentRun.TextSourceLength; + } + } + + if (endX < startX) + { + (endX, startX) = (startX, endX); + } + + //Lines that only contain a linebreak need to be covered here + if (characterLength == 0) + { + characterLength = NewLineLength; + } + + var runWidth = endX - startX; + + remainingLength -= characterLength; + + currentPosition += characterLength; + + textRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); + + return true; + } + + return false; + } + public override IReadOnlyList GetTextBounds(int firstTextSourceIndex, int textLength) { if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight) @@ -1319,12 +1522,12 @@ namespace Avalonia.Media.TextFormatting case TextAlignment.Center: var start = (_paragraphWidth - width) / 2; - if(paragraphFlowDirection == FlowDirection.RightToLeft) + if (paragraphFlowDirection == FlowDirection.RightToLeft) { start -= (widthIncludingTrailingWhitespace - width); } - return Math.Max(0, start); + return Math.Max(0, start); case TextAlignment.Right: return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace); diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index e463bc5731..4f9be86641 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -9,6 +9,7 @@ using Avalonia.VisualTree; using Avalonia.Layout; using Avalonia.Media.Immutable; using Avalonia.Controls.Documents; +using Avalonia.Media.TextFormatting.Unicode; namespace Avalonia.Controls.Presenters { @@ -496,14 +497,14 @@ namespace Avalonia.Controls.Presenters var length = Math.Max(selectionStart, selectionEnd) - start; IReadOnlyList>? textStyleOverrides = null; - + if (length > 0) { textStyleOverrides = new[] { new ValueSpan(start, length, new GenericTextRunProperties(typeface, FontSize, - foregroundBrush: SelectionForegroundBrush ?? Brushes.White)) + foregroundBrush: SelectionForegroundBrush ?? Brushes.Red)) }; } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 1b268db2f7..8490bfd3a0 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -17,6 +17,7 @@ using Avalonia.Controls.Metadata; using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Automation.Peers; +using System.Diagnostics; namespace Avalonia.Controls { @@ -1240,9 +1241,12 @@ namespace Avalonia.Controls MathUtilities.Clamp(point.X, 0, Math.Max(_presenter.Bounds.Width - 1, 0)), MathUtilities.Clamp(point.Y, 0, Math.Max(_presenter.Bounds.Height - 1, 0))); - _presenter.MoveCaretToPoint(point); + var hit = _presenter.TextLayout.HitTestPoint(point); + + var caretIndex = hit.TextPosition; + + Debug.WriteLine($"TextPos: {caretIndex}, X: {point.X}"); - var caretIndex = _presenter.CaretIndex; var text = Text; if (text != null && _wordSelectionStart >= 0) @@ -1266,7 +1270,7 @@ namespace Avalonia.Controls } else { - SelectionEnd = _presenter.CaretIndex; + SelectionEnd = caretIndex; } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 7d33f094fa..6d057d900e 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using Avalonia.Media; @@ -914,14 +915,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting public void Should_Get_CharacterHit_From_Distance_RTL() { using (Start()) - { + { var text = "أَبْجَدِيَّة عَرَبِيَّة"; var layout = new TextLayout( - text, - Typeface.Default, - 12, - Brushes.Black); + text, + Typeface.Default, + 12, + Brushes.Black); var textLine = layout.TextLines[0]; @@ -952,6 +953,63 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting rect = layout.HitTestTextPosition(23); Assert.Equal(0, rect.Left, 5); + + } + } + + [Fact] + public void Should_Get_CharacterHit_From_Distance_RTL_With_TextStyles() + { + using (Start()) + { + var text = "أَبْجَدِيَّة عَرَبِيَّة"; + + var i = 0; + + var graphemeEnumerator = new GraphemeEnumerator(text.AsMemory()); + + while (graphemeEnumerator.MoveNext()) + { + var grapheme = graphemeEnumerator.Current; + + var textStyleOverrides = new[] { new ValueSpan(i, grapheme.Text.Length, new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Red)) }; + + i += grapheme.Text.Length; + + var layout = new TextLayout( + text, + Typeface.Default, + 12, + Brushes.Black, + textStyleOverrides: textStyleOverrides); + + var textLine = layout.TextLines[0]; + + var shapedRuns = textLine.TextRuns.Cast().ToList(); + + var clusters = shapedRuns.SelectMany(x => x.ShapedBuffer.GlyphClusters).ToList(); + + var glyphAdvances = shapedRuns.SelectMany(x => x.ShapedBuffer.GlyphAdvances).ToList(); + + var currentX = 0.0; + + for (int j = 0; j < clusters.Count; j++) + { + var cluster = clusters[j]; + + var characterHit = textLine.GetCharacterHitFromDistance(currentX); + + Assert.Equal(cluster, characterHit.FirstCharacterIndex + characterHit.TrailingLength); + + var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster)); + + Assert.Equal(currentX, distance); + + var glyphAdvance = glyphAdvances[j]; + + currentX += glyphAdvance; + } + } } } From 1c1499c39961786f5b5046fc2fbd4c641f570566 Mon Sep 17 00:00:00 2001 From: Evgeny Gorbovoy Date: Thu, 28 Jul 2022 19:00:19 +0200 Subject: [PATCH 52/97] More `ContainsExclusive` --- .../Rendering/SceneGraph/RectangleNode.cs | 4 ++-- .../Rendering/CompositorHitTestingTests.cs | 6 +++--- .../Rendering/DeferredRendererTests_HitTesting.cs | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs index 7b79c446f9..a9d1bf96e5 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs @@ -103,13 +103,13 @@ namespace Avalonia.Rendering.SceneGraph if (Brush != null) { var rect = Rect.Rect.Inflate((Pen?.Thickness / 2) ?? 0); - return rect.Contains(p); + return rect.ContainsExclusive(p); } else { var borderRect = Rect.Rect.Inflate((Pen?.Thickness / 2) ?? 0); var emptyRect = Rect.Rect.Deflate((Pen?.Thickness / 2) ?? 0); - return borderRect.Contains(p) && !emptyRect.Contains(p); + return borderRect.ContainsExclusive(p) && !emptyRect.ContainsExclusive(p); } } diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs index a584a859ec..02012bf62b 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs @@ -414,7 +414,7 @@ public class CompositorHitTestingTests : CompositorTestsBase { using (var s = new CompositorServices(new Size(200, 200))) { - Rectangle targetRectangle; + Border targetRectangle; var stackPanel = new StackPanel { @@ -422,8 +422,8 @@ public class CompositorHitTestingTests : CompositorTestsBase HorizontalAlignment = HorizontalAlignment.Left, Children = { - new Rectangle { Width = 10, Height = 10, Fill= Brushes.Red}, - { targetRectangle = new Rectangle { Width = 10, Height = 10, Fill = Brushes.Green} } + new Border { Width = 10, Height = 10, Background= Brushes.Red}, + { targetRectangle = new Border { Width = 10, Height = 10, Background = Brushes.Green} } } }; diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs index 7800ddfc8a..d3fdbb63db 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs @@ -542,11 +542,11 @@ namespace Avalonia.Base.UnitTests.Rendering { using (TestApplication()) { - Rectangle targetRectangle; + Border targetRectangle; var root = new TestRoot { - Width = 300, + Width = 50, Height = 200, Child = new StackPanel { @@ -554,8 +554,8 @@ namespace Avalonia.Base.UnitTests.Rendering HorizontalAlignment = HorizontalAlignment.Left, Children = { - new Rectangle { Width = 10, Height = 10, Fill = Brushes.Red}, - { targetRectangle = new Rectangle { Width = 10, Height = 10, Fill = Brushes.Green} } + new Border { Width = 50, Height = 50, Background = Brushes.Red}, + { targetRectangle = new Border { Width = 50, Height = 50, Background = Brushes.Green} } } } }; @@ -564,7 +564,7 @@ namespace Avalonia.Base.UnitTests.Rendering root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); - var result = root.Renderer.HitTest(new Point(5, 10), root, null); + var result = root.Renderer.HitTest(new Point(25, 50), root, null); Assert.Equal(new[] { targetRectangle }, result); } } From b5e4a94c4f45c9d6ae7bea85aa6f75212b537e81 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 29 Jul 2022 02:46:41 -0400 Subject: [PATCH 53/97] Minor fluent theme fixes --- .../Controls/ContextMenu.xaml | 4 +- .../Controls/DataValidationErrors.xaml | 84 +++++++++++-------- .../Controls/FluentControls.xaml | 1 - src/Avalonia.Themes.Fluent/Controls/Menu.xaml | 6 +- .../Controls/MenuFlyoutPresenter.xaml | 1 + 5 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml b/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml index 8365ee61d3..fdd79ed736 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml @@ -29,7 +29,7 @@ - + 0,4,0,4 @@ -54,7 +54,7 @@ MinHeight="{TemplateBinding MinHeight}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" CornerRadius="{TemplateBinding CornerRadius}"> - + @@ -26,10 +25,33 @@ - - - - + + + + + + + + + + + + - + - - - - - - - - - + diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 0499495239..577539b26b 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -75,5 +75,4 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/Menu.xaml b/src/Avalonia.Themes.Fluent/Controls/Menu.xaml index 12d18ae644..f5fd69a67f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Menu.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Menu.xaml @@ -5,7 +5,7 @@ - + @@ -46,7 +46,7 @@ MinHeight="{DynamicResource MenuFlyoutThemeMinHeight}" HorizontalAlignment="Stretch" CornerRadius="{DynamicResource OverlayCornerRadius}"> - + - + Date: Fri, 29 Jul 2022 02:58:21 -0400 Subject: [PATCH 54/97] Port default theme to use ControlThemes + some formatting --- .../Controls/AutoCompleteBox.xaml | 36 +- .../Controls/Button.xaml | 54 +- .../Controls/ButtonSpinner.xaml | 188 +++---- .../Controls/Calendar.xaml | 24 +- .../Controls/CalendarButton.xaml | 90 ++-- .../Controls/CalendarDatePicker.xaml | 136 ++--- .../Controls/CalendarDayButton.xaml | 149 +++--- .../Controls/CalendarItem.xaml | 139 +++-- .../Controls/CaptionButtons.xaml | 153 +++--- .../Controls/Carousel.xaml | 38 +- .../Controls/CheckBox.xaml | 95 ++-- .../Controls/ComboBox.xaml | 81 ++- .../Controls/ComboBoxItem.xaml | 63 ++- .../Controls/ContentControl.xaml | 36 +- .../Controls/ContextMenu.xaml | 46 +- .../Controls/DataValidationErrors.xaml | 91 ++-- .../Controls/DatePicker.xaml | 496 +++++++++--------- .../Controls/DateTimePickerShared.xaml | 139 +++++ .../Controls/DropDownButton.xaml | 70 +-- .../Controls/EmbeddableControlRoot.xaml | 43 +- .../Controls/Expander.xaml | 382 ++++++++------ .../Controls/FlyoutPresenter.xaml | 20 +- .../Controls/FocusAdorner.xaml | 9 +- .../Controls/GridSplitter.xaml | 21 +- .../Controls/ItemsControl.xaml | 36 +- .../Controls/Label.xaml | 40 +- .../Controls/ListBox.xaml | 71 +-- .../Controls/ListBoxItem.xaml | 66 +-- .../Controls/ManagedFileChooser.xaml | 319 ++++++----- .../Controls/Menu.xaml | 100 +++- .../Controls/MenuFlyoutPresenter.xaml | 21 +- .../Controls/MenuItem.xaml | 163 +++--- .../Controls/NativeMenuBar.xaml | 60 +-- .../Controls/NotificationCard.xaml | 171 +++--- .../Controls/NumericUpDown.xaml | 49 +- .../Controls/OverlayPopupHost.xaml | 48 +- .../Controls/PathIcon.xaml | 21 +- .../Controls/PopupRoot.xaml | 57 +- .../Controls/ProgressBar.xaml | 155 +++--- .../Controls/RadioButton.xaml | 87 +-- .../Controls/RepeatButton.xaml | 71 ++- .../Controls/RichTextBlock.xaml | 18 +- .../Controls/ScrollBar.xaml | 283 +++++----- .../Controls/ScrollViewer.xaml | 133 +++-- .../Controls/Separator.xaml | 28 +- .../Controls/Slider.xaml | 213 ++++---- .../Controls/SplitButton.xaml | 444 +++++++--------- .../Controls/SplitView.xaml | 451 ++++++++-------- .../Controls/TabControl.xaml | 101 ++-- .../Controls/TabItem.xaml | 91 ++-- .../Controls/TabStrip.xaml | 17 +- .../Controls/TabStripItem.xaml | 33 +- .../Controls/TextBox.xaml | 338 ++++++------ .../Controls/TimePicker.xaml | 467 +++++++++-------- .../Controls/TitleBar.xaml | 80 +-- .../Controls/ToggleButton.xaml | 62 +-- .../Controls/ToggleSwitch.xaml | 483 ++++++++--------- .../Controls/ToolTip.xaml | 40 +- .../Controls/TransitioningContentControl.xaml | 23 +- .../Controls/TreeView.xaml | 58 +- .../Controls/TreeViewItem.xaml | 159 +++--- .../Controls/UserControl.xaml | 39 +- .../Controls/Window.xaml | 59 ++- .../Controls/WindowNotificationManager.xaml | 81 +-- src/Avalonia.Themes.Default/DefaultTheme.xaml | 143 ++--- 65 files changed, 4201 insertions(+), 3777 deletions(-) create mode 100644 src/Avalonia.Themes.Default/Controls/DateTimePickerShared.xaml diff --git a/src/Avalonia.Themes.Default/Controls/AutoCompleteBox.xaml b/src/Avalonia.Themes.Default/Controls/AutoCompleteBox.xaml index fac8ca51f8..65dfaf982b 100644 --- a/src/Avalonia.Themes.Default/Controls/AutoCompleteBox.xaml +++ b/src/Avalonia.Themes.Default/Controls/AutoCompleteBox.xaml @@ -1,32 +1,34 @@ - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/Button.xaml b/src/Avalonia.Themes.Default/Controls/Button.xaml index a2971c3ff6..9018677ab6 100644 --- a/src/Avalonia.Themes.Default/Controls/Button.xaml +++ b/src/Avalonia.Themes.Default/Controls/Button.xaml @@ -1,36 +1,38 @@ - - - - - - + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Default/Controls/ButtonSpinner.xaml index 561ca8bedd..4585fc8e56 100644 --- a/src/Avalonia.Themes.Default/Controls/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Default/Controls/ButtonSpinner.xaml @@ -1,107 +1,89 @@ - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + - - - - - + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/Calendar.xaml b/src/Avalonia.Themes.Default/Controls/Calendar.xaml index 4b67aa232b..6f7267581b 100644 --- a/src/Avalonia.Themes.Default/Controls/Calendar.xaml +++ b/src/Avalonia.Themes.Default/Controls/Calendar.xaml @@ -1,12 +1,14 @@ - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/CalendarButton.xaml b/src/Avalonia.Themes.Default/Controls/CalendarButton.xaml index 3314534b3b..59fb24663f 100644 --- a/src/Avalonia.Themes.Default/Controls/CalendarButton.xaml +++ b/src/Avalonia.Themes.Default/Controls/CalendarButton.xaml @@ -1,81 +1,73 @@ - - - - - - - - + + - - + + - - - + + + diff --git a/src/Avalonia.Themes.Default/Controls/CalendarDatePicker.xaml b/src/Avalonia.Themes.Default/Controls/CalendarDatePicker.xaml index 3e2e497d2c..43fc8c7a34 100644 --- a/src/Avalonia.Themes.Default/Controls/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Default/Controls/CalendarDatePicker.xaml @@ -1,20 +1,21 @@ - - - + - + + UseFloatingWatermark="{TemplateBinding UseFloatingWatermark}" + Watermark="{TemplateBinding Watermark}" /> - + + Margin="6,-1,6,6" + IsVisible="False"> @@ -132,8 +133,8 @@ + Margin="6,-3,7,6" + IsVisible="False"> @@ -146,39 +147,33 @@ - - + Stretch="Fill" /> - - - - - - - + + + + diff --git a/src/Avalonia.Themes.Default/Controls/CaptionButtons.xaml b/src/Avalonia.Themes.Default/Controls/CaptionButtons.xaml index be6642467f..adc02b7c3c 100644 --- a/src/Avalonia.Themes.Default/Controls/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Default/Controls/CaptionButtons.xaml @@ -1,73 +1,106 @@ - - + + + + + - - - - - - - - - - + + + + + - - - - - - - - + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/Carousel.xaml b/src/Avalonia.Themes.Default/Controls/Carousel.xaml index baba0649aa..e79e3942c9 100644 --- a/src/Avalonia.Themes.Default/Controls/Carousel.xaml +++ b/src/Avalonia.Themes.Default/Controls/Carousel.xaml @@ -1,17 +1,21 @@ - + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/CheckBox.xaml b/src/Avalonia.Themes.Default/Controls/CheckBox.xaml index 170ecdd5b8..10e0b883d5 100644 --- a/src/Avalonia.Themes.Default/Controls/CheckBox.xaml +++ b/src/Avalonia.Themes.Default/Controls/CheckBox.xaml @@ -1,72 +1,75 @@ - - - - - - - - - + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ComboBox.xaml b/src/Avalonia.Themes.Default/Controls/ComboBox.xaml index 262f63e2d2..d7dabae516 100644 --- a/src/Avalonia.Themes.Default/Controls/ComboBox.xaml +++ b/src/Avalonia.Themes.Default/Controls/ComboBox.xaml @@ -1,23 +1,7 @@ - - - - - - Item 1 - Item 2 - - - Item 1 - Item 2 - - - - - - - - + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ComboBoxItem.xaml b/src/Avalonia.Themes.Default/Controls/ComboBoxItem.xaml index a694143bec..c3d8b6f900 100644 --- a/src/Avalonia.Themes.Default/Controls/ComboBoxItem.xaml +++ b/src/Avalonia.Themes.Default/Controls/ComboBoxItem.xaml @@ -1,42 +1,39 @@ - - - - - - - - - - - - - + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ContentControl.xaml b/src/Avalonia.Themes.Default/Controls/ContentControl.xaml index d32bc399b6..415b1dc721 100644 --- a/src/Avalonia.Themes.Default/Controls/ContentControl.xaml +++ b/src/Avalonia.Themes.Default/Controls/ContentControl.xaml @@ -1,16 +1,20 @@ - + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ContextMenu.xaml b/src/Avalonia.Themes.Default/Controls/ContextMenu.xaml index 987b72aaa2..baad686bd9 100644 --- a/src/Avalonia.Themes.Default/Controls/ContextMenu.xaml +++ b/src/Avalonia.Themes.Default/Controls/ContextMenu.xaml @@ -1,25 +1,29 @@ - + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/DataValidationErrors.xaml b/src/Avalonia.Themes.Default/Controls/DataValidationErrors.xaml index d7bf4bbbf1..da8ec03cd4 100644 --- a/src/Avalonia.Themes.Default/Controls/DataValidationErrors.xaml +++ b/src/Avalonia.Themes.Default/Controls/DataValidationErrors.xaml @@ -1,43 +1,48 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/DatePicker.xaml b/src/Avalonia.Themes.Default/Controls/DatePicker.xaml index c1ee941416..8198158d5e 100644 --- a/src/Avalonia.Themes.Default/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Default/Controls/DatePicker.xaml @@ -1,239 +1,186 @@ - - - 0,0,0,4 - 40 - 40 - 41 - 296 - 456 - 0,3,0,6 - 9,3,0,6 - 0,3,0,6 - 9,3,0,6 - 1 - + + + + + + + + + + + + Error + + + + + + + - - - - - - + 0,0,0,4 + 40 + 40 + 41 + 296 + 456 + 0,3,0,6 + 9,3,0,6 + 0,3,0,6 + 9,3,0,6 + 1 - - - - - - + + - - + + - - - - - - + - + + + - - - - - + + + ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" + PanelType="Month" + ShouldLoop="True" /> + Theme="{StaticResource DefaultDateTimePickerUpButton}" /> + Theme="{StaticResource DefaultDateTimePickerDownButton}" /> + ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" + PanelType="Day" + ShouldLoop="True" /> + Theme="{StaticResource DefaultDateTimePickerUpButton}" /> + Theme="{StaticResource DefaultDateTimePickerDownButton}" /> + ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" + PanelType="Year" + ShouldLoop="False" /> + Theme="{StaticResource DefaultDateTimePickerUpButton}" /> + Theme="{StaticResource DefaultDateTimePickerDownButton}" /> - + + + + + - + + HorizontalAlignment="Center" + Fill="{DynamicResource ThemeControlMidHighBrush}" /> - - - - - - + + + + - - - + diff --git a/src/Avalonia.Themes.Default/Controls/DateTimePickerShared.xaml b/src/Avalonia.Themes.Default/Controls/DateTimePickerShared.xaml new file mode 100644 index 0000000000..1e4b50ca40 --- /dev/null +++ b/src/Avalonia.Themes.Default/Controls/DateTimePickerShared.xaml @@ -0,0 +1,139 @@ + + + + + + + Standard Item + Month Item + Button + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/DropDownButton.xaml b/src/Avalonia.Themes.Default/Controls/DropDownButton.xaml index 662c6379fb..6a50cd6e36 100644 --- a/src/Avalonia.Themes.Default/Controls/DropDownButton.xaml +++ b/src/Avalonia.Themes.Default/Controls/DropDownButton.xaml @@ -1,12 +1,14 @@ - - - + - + - + - - + + + diff --git a/src/Avalonia.Themes.Default/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Default/Controls/EmbeddableControlRoot.xaml index 9ffe51fae8..79d6c6d917 100644 --- a/src/Avalonia.Themes.Default/Controls/EmbeddableControlRoot.xaml +++ b/src/Avalonia.Themes.Default/Controls/EmbeddableControlRoot.xaml @@ -1,19 +1,24 @@ - + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/Expander.xaml b/src/Avalonia.Themes.Default/Controls/Expander.xaml index 2f18faf84a..bfa5fb3130 100644 --- a/src/Avalonia.Themes.Default/Controls/Expander.xaml +++ b/src/Avalonia.Themes.Default/Controls/Expander.xaml @@ -1,196 +1,256 @@ - + - - + + - - + + - Expanded content + - + - Expanded content + - + - Expanded content + - + - Expanded content + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/FlyoutPresenter.xaml b/src/Avalonia.Themes.Default/Controls/FlyoutPresenter.xaml index 3bc5e085ff..08959cfbbe 100644 --- a/src/Avalonia.Themes.Default/Controls/FlyoutPresenter.xaml +++ b/src/Avalonia.Themes.Default/Controls/FlyoutPresenter.xaml @@ -1,8 +1,10 @@ - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/FocusAdorner.xaml b/src/Avalonia.Themes.Default/Controls/FocusAdorner.xaml index 2d5e369573..f1d5f5f2ac 100644 --- a/src/Avalonia.Themes.Default/Controls/FocusAdorner.xaml +++ b/src/Avalonia.Themes.Default/Controls/FocusAdorner.xaml @@ -1,10 +1,11 @@ - diff --git a/src/Avalonia.Themes.Default/Controls/GridSplitter.xaml b/src/Avalonia.Themes.Default/Controls/GridSplitter.xaml index 490ae01318..f2a3f01397 100644 --- a/src/Avalonia.Themes.Default/Controls/GridSplitter.xaml +++ b/src/Avalonia.Themes.Default/Controls/GridSplitter.xaml @@ -1,6 +1,7 @@ - - - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/ItemsControl.xaml b/src/Avalonia.Themes.Default/Controls/ItemsControl.xaml index 19d13b6399..d8cdf96abd 100644 --- a/src/Avalonia.Themes.Default/Controls/ItemsControl.xaml +++ b/src/Avalonia.Themes.Default/Controls/ItemsControl.xaml @@ -1,16 +1,20 @@ - + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/Label.xaml b/src/Avalonia.Themes.Default/Controls/Label.xaml index 1e04d05497..0faa4852f2 100644 --- a/src/Avalonia.Themes.Default/Controls/Label.xaml +++ b/src/Avalonia.Themes.Default/Controls/Label.xaml @@ -1,18 +1,22 @@ - + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ListBox.xaml b/src/Avalonia.Themes.Default/Controls/ListBox.xaml index b1fcb830b3..7308738681 100644 --- a/src/Avalonia.Themes.Default/Controls/ListBox.xaml +++ b/src/Avalonia.Themes.Default/Controls/ListBox.xaml @@ -1,35 +1,38 @@ - - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/ListBoxItem.xaml b/src/Avalonia.Themes.Default/Controls/ListBoxItem.xaml index c1360c5d98..4d918326c8 100644 --- a/src/Avalonia.Themes.Default/Controls/ListBoxItem.xaml +++ b/src/Avalonia.Themes.Default/Controls/ListBoxItem.xaml @@ -1,42 +1,44 @@ - - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Default/Controls/ManagedFileChooser.xaml index f9f77bf455..52d9000396 100644 --- a/src/Avalonia.Themes.Default/Controls/ManagedFileChooser.xaml +++ b/src/Avalonia.Themes.Default/Controls/ManagedFileChooser.xaml @@ -1,153 +1,196 @@ - - - - - - + + + + Show hidden files + + + + + + + + + - + SelectedItem="{Binding SelectedFilter}" /> - + - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - \ No newline at end of file + + diff --git a/src/Avalonia.Themes.Default/Controls/Menu.xaml b/src/Avalonia.Themes.Default/Controls/Menu.xaml index 583add1017..c698343d83 100644 --- a/src/Avalonia.Themes.Default/Controls/Menu.xaml +++ b/src/Avalonia.Themes.Default/Controls/Menu.xaml @@ -1,17 +1,83 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/MenuFlyoutPresenter.xaml b/src/Avalonia.Themes.Default/Controls/MenuFlyoutPresenter.xaml index d56b46b17c..78c3d767a3 100644 --- a/src/Avalonia.Themes.Default/Controls/MenuFlyoutPresenter.xaml +++ b/src/Avalonia.Themes.Default/Controls/MenuFlyoutPresenter.xaml @@ -1,6 +1,8 @@ - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/MenuItem.xaml b/src/Avalonia.Themes.Default/Controls/MenuItem.xaml index 05c286c1f5..b2d0f3535a 100644 --- a/src/Avalonia.Themes.Default/Controls/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/Controls/MenuItem.xaml @@ -1,14 +1,14 @@ - - - - - - - - - - + - + - + - - + + + diff --git a/src/Avalonia.Themes.Default/Controls/NativeMenuBar.xaml b/src/Avalonia.Themes.Default/Controls/NativeMenuBar.xaml index 3b0019eea1..3c74e2743a 100644 --- a/src/Avalonia.Themes.Default/Controls/NativeMenuBar.xaml +++ b/src/Avalonia.Themes.Default/Controls/NativeMenuBar.xaml @@ -1,30 +1,30 @@ - - - - - - + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/NotificationCard.xaml b/src/Avalonia.Themes.Default/Controls/NotificationCard.xaml index c411fede11..524f964afc 100644 --- a/src/Avalonia.Themes.Default/Controls/NotificationCard.xaml +++ b/src/Avalonia.Themes.Default/Controls/NotificationCard.xaml @@ -1,93 +1,98 @@ - - - - - - - - - - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/NumericUpDown.xaml b/src/Avalonia.Themes.Default/Controls/NumericUpDown.xaml index 6740be69bb..d3adfb31e0 100644 --- a/src/Avalonia.Themes.Default/Controls/NumericUpDown.xaml +++ b/src/Avalonia.Themes.Default/Controls/NumericUpDown.xaml @@ -1,40 +1,39 @@ - - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/OverlayPopupHost.xaml b/src/Avalonia.Themes.Default/Controls/OverlayPopupHost.xaml index 07d905ea1d..d1f5585c02 100644 --- a/src/Avalonia.Themes.Default/Controls/OverlayPopupHost.xaml +++ b/src/Avalonia.Themes.Default/Controls/OverlayPopupHost.xaml @@ -1,23 +1,25 @@ - + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/PathIcon.xaml b/src/Avalonia.Themes.Default/Controls/PathIcon.xaml index 039f0ef10c..4116b5784e 100644 --- a/src/Avalonia.Themes.Default/Controls/PathIcon.xaml +++ b/src/Avalonia.Themes.Default/Controls/PathIcon.xaml @@ -1,21 +1,22 @@ - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/PopupRoot.xaml b/src/Avalonia.Themes.Default/Controls/PopupRoot.xaml index 5e8f3337ee..a5593e3a68 100644 --- a/src/Avalonia.Themes.Default/Controls/PopupRoot.xaml +++ b/src/Avalonia.Themes.Default/Controls/PopupRoot.xaml @@ -1,27 +1,30 @@ - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Default/Controls/ProgressBar.xaml index 3f684f3936..5adb5bde7e 100644 --- a/src/Avalonia.Themes.Default/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Default/Controls/ProgressBar.xaml @@ -1,6 +1,6 @@ - + @@ -12,30 +12,42 @@ - - - - - - - - - + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/RadioButton.xaml b/src/Avalonia.Themes.Default/Controls/RadioButton.xaml index ae33946009..c837b44402 100644 --- a/src/Avalonia.Themes.Default/Controls/RadioButton.xaml +++ b/src/Avalonia.Themes.Default/Controls/RadioButton.xaml @@ -1,61 +1,64 @@ - - - - - - - - - - + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/RepeatButton.xaml b/src/Avalonia.Themes.Default/Controls/RepeatButton.xaml index 47398966f7..73cfd4d810 100644 --- a/src/Avalonia.Themes.Default/Controls/RepeatButton.xaml +++ b/src/Avalonia.Themes.Default/Controls/RepeatButton.xaml @@ -1,41 +1,34 @@ - - - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml index d7bf6e5cf9..c0570282cb 100644 --- a/src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml +++ b/src/Avalonia.Themes.Default/Controls/RichTextBlock.xaml @@ -1,10 +1,14 @@ - - - - + + + + - - + + diff --git a/src/Avalonia.Themes.Default/Controls/ScrollBar.xaml b/src/Avalonia.Themes.Default/Controls/ScrollBar.xaml index 0f8fa4986d..d28ba66f6d 100644 --- a/src/Avalonia.Themes.Default/Controls/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/Controls/ScrollBar.xaml @@ -1,142 +1,151 @@ - - - - + + + + + + + + - - - - - - - + + - + - - + + + diff --git a/src/Avalonia.Themes.Default/Controls/ScrollViewer.xaml b/src/Avalonia.Themes.Default/Controls/ScrollViewer.xaml index aab1b76259..83b3330fcf 100644 --- a/src/Avalonia.Themes.Default/Controls/ScrollViewer.xaml +++ b/src/Avalonia.Themes.Default/Controls/ScrollViewer.xaml @@ -1,106 +1,125 @@ - - + - - + - - + + + diff --git a/src/Avalonia.Themes.Default/Controls/Separator.xaml b/src/Avalonia.Themes.Default/Controls/Separator.xaml index 6a318d2e85..e142cf29ad 100644 --- a/src/Avalonia.Themes.Default/Controls/Separator.xaml +++ b/src/Avalonia.Themes.Default/Controls/Separator.xaml @@ -1,21 +1,19 @@ - - - - - + - + diff --git a/src/Avalonia.Themes.Default/Controls/Slider.xaml b/src/Avalonia.Themes.Default/Controls/Slider.xaml index bcb330f188..851ee1b78a 100644 --- a/src/Avalonia.Themes.Default/Controls/Slider.xaml +++ b/src/Avalonia.Themes.Default/Controls/Slider.xaml @@ -1,97 +1,128 @@ - - - - - - - - - + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/SplitButton.xaml b/src/Avalonia.Themes.Default/Controls/SplitButton.xaml index 0c46ce3724..aa71d0e7c7 100644 --- a/src/Avalonia.Themes.Default/Controls/SplitButton.xaml +++ b/src/Avalonia.Themes.Default/Controls/SplitButton.xaml @@ -1,9 +1,9 @@ - + - @@ -12,50 +12,127 @@ - - 24 - 24 - 1 - 24 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 24 + 24 + 1 + 24 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + @@ -65,211 +142,82 @@ - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + - - - - + + + diff --git a/src/Avalonia.Themes.Default/Controls/SplitView.xaml b/src/Avalonia.Themes.Default/Controls/SplitView.xaml index 10eda2d9cd..b0e4d0b096 100644 --- a/src/Avalonia.Themes.Default/Controls/SplitView.xaml +++ b/src/Avalonia.Themes.Default/Controls/SplitView.xaml @@ -1,6 +1,6 @@ - + - - 320 - 48 - - 00:00:00.2 - 00:00:00.1 - 0.1,0.9,0.2,1.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/TabControl.xaml b/src/Avalonia.Themes.Default/Controls/TabControl.xaml index afb5010baa..4d1fcc0336 100644 --- a/src/Avalonia.Themes.Default/Controls/TabControl.xaml +++ b/src/Avalonia.Themes.Default/Controls/TabControl.xaml @@ -1,62 +1,61 @@ - - - - - - - - - - - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/TabItem.xaml b/src/Avalonia.Themes.Default/Controls/TabItem.xaml index c7748299a0..7b6a1d1116 100644 --- a/src/Avalonia.Themes.Default/Controls/TabItem.xaml +++ b/src/Avalonia.Themes.Default/Controls/TabItem.xaml @@ -1,45 +1,46 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/TabStrip.xaml b/src/Avalonia.Themes.Default/Controls/TabStrip.xaml index 29ca1e7c94..3960c1d275 100644 --- a/src/Avalonia.Themes.Default/Controls/TabStrip.xaml +++ b/src/Avalonia.Themes.Default/Controls/TabStrip.xaml @@ -1,11 +1,13 @@ - - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/TabStripItem.xaml b/src/Avalonia.Themes.Default/Controls/TabStripItem.xaml index 61eecc0395..b4d7218ec3 100644 --- a/src/Avalonia.Themes.Default/Controls/TabStripItem.xaml +++ b/src/Avalonia.Themes.Default/Controls/TabStripItem.xaml @@ -1,24 +1,27 @@ - - - - + + + diff --git a/src/Avalonia.Themes.Default/Controls/TextBox.xaml b/src/Avalonia.Themes.Default/Controls/TextBox.xaml index 1885d3b65b..3cc8bc5421 100644 --- a/src/Avalonia.Themes.Default/Controls/TextBox.xaml +++ b/src/Avalonia.Themes.Default/Controls/TextBox.xaml @@ -1,29 +1,101 @@ - - - M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z - m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z - m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z + + M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z + m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z + m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z - - - - - + + + + + - - - - - - - + + + + - - - - - - + + + + + + @@ -38,17 +110,17 @@ VerticalAlignment="{TemplateBinding VerticalContentAlignment}"> + Foreground="{DynamicResource ThemeAccentBrush}" + Text="{TemplateBinding Watermark}"> - - + + RelativeSource="{RelativeSource TemplatedParent}" /> @@ -56,148 +128,94 @@ - - + + VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"> + IsVisible="{TemplateBinding Text, + Converter={x:Static StringConverters.IsNullOrEmpty}}" + Opacity="0.5" + Text="{TemplateBinding Watermark}" + TextAlignment="{TemplateBinding TextAlignment}" + TextWrapping="{TemplateBinding TextWrapping}" /> + CaretBrush="{TemplateBinding CaretBrush}" + CaretIndex="{TemplateBinding CaretIndex}" + LineHeight="{TemplateBinding LineHeight}" + PasswordChar="{TemplateBinding PasswordChar}" + RevealPassword="{TemplateBinding RevealPassword}" + SelectionBrush="{TemplateBinding SelectionBrush}" + SelectionEnd="{TemplateBinding SelectionEnd}" + SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" + SelectionStart="{TemplateBinding SelectionStart}" + Text="{TemplateBinding Text, + Mode=TwoWay}" + TextAlignment="{TemplateBinding TextAlignment}" + TextWrapping="{TemplateBinding TextWrapping}" /> - + - - - - - - + + + + - - - - - - - - - - - - - - - - - - - + + + - - + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/TimePicker.xaml b/src/Avalonia.Themes.Default/Controls/TimePicker.xaml index 0a5147e335..b0ffc97e68 100644 --- a/src/Avalonia.Themes.Default/Controls/TimePicker.xaml +++ b/src/Avalonia.Themes.Default/Controls/TimePicker.xaml @@ -1,175 +1,204 @@ - - - 40 - 1 - 1 - 0,0,0,4 - 40 - 41 - 242 - 456 - 0,3,0,6 - 0,3,0,6 - + + + + + + + + + + + + Error + + + + + + + - - - + 40 + 1 + 1 + 0,0,0,4 + 40 + 41 + 242 + 456 + 0,3,0,6 + 0,3,0,6 - + + + + + + + - - - + + + - - + + + - + + + + + - - - + + + - + + - - - - - + - + - + + - - - - - - - - + + + + diff --git a/src/Avalonia.Themes.Default/Controls/TitleBar.xaml b/src/Avalonia.Themes.Default/Controls/TitleBar.xaml index 7f8ed24076..a06dfbb834 100644 --- a/src/Avalonia.Themes.Default/Controls/TitleBar.xaml +++ b/src/Avalonia.Themes.Default/Controls/TitleBar.xaml @@ -1,53 +1,65 @@ - + - + - + - + - + - + - - - + - - + + + diff --git a/src/Avalonia.Themes.Default/Controls/ToggleButton.xaml b/src/Avalonia.Themes.Default/Controls/ToggleButton.xaml index 17fb2af16c..2cfbcf6b7a 100644 --- a/src/Avalonia.Themes.Default/Controls/ToggleButton.xaml +++ b/src/Avalonia.Themes.Default/Controls/ToggleButton.xaml @@ -1,40 +1,42 @@ - - - - - - - + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml index 2c831cf360..d00358951a 100644 --- a/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml @@ -1,69 +1,28 @@ - - - 0,0,0,6 - 6 - 6 - 0 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + Classes="h1" /> + TextWrapping="Wrap" /> + Content="Enable automatic Updates?" + OffContent="Uit" + OnContent="Aan" + VerticalAlignment="Bottom" /> + Classes="h1" /> + TextWrapping="Wrap" /> @@ -71,29 +30,71 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ToolTip.xaml b/src/Avalonia.Themes.Default/Controls/ToolTip.xaml index 35c1dceb8d..c4fddd8c4c 100644 --- a/src/Avalonia.Themes.Default/Controls/ToolTip.xaml +++ b/src/Avalonia.Themes.Default/Controls/ToolTip.xaml @@ -1,18 +1,22 @@ - + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/TransitioningContentControl.xaml b/src/Avalonia.Themes.Default/Controls/TransitioningContentControl.xaml index 6a4d56ccb7..63185e11e6 100644 --- a/src/Avalonia.Themes.Default/Controls/TransitioningContentControl.xaml +++ b/src/Avalonia.Themes.Default/Controls/TransitioningContentControl.xaml @@ -1,20 +1,21 @@ - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/TreeView.xaml b/src/Avalonia.Themes.Default/Controls/TreeView.xaml index 67a683dad9..2bc38a0673 100644 --- a/src/Avalonia.Themes.Default/Controls/TreeView.xaml +++ b/src/Avalonia.Themes.Default/Controls/TreeView.xaml @@ -1,27 +1,31 @@ - + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/TreeViewItem.xaml b/src/Avalonia.Themes.Default/Controls/TreeViewItem.xaml index 1996756001..7baf0ef2a6 100644 --- a/src/Avalonia.Themes.Default/Controls/TreeViewItem.xaml +++ b/src/Avalonia.Themes.Default/Controls/TreeViewItem.xaml @@ -1,93 +1,100 @@ - - + + - + + + + + + + + - + + + + + + + + + + + + + + + + + + - - - - - - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/UserControl.xaml b/src/Avalonia.Themes.Default/Controls/UserControl.xaml index d6028daff8..56248f8c5f 100644 --- a/src/Avalonia.Themes.Default/Controls/UserControl.xaml +++ b/src/Avalonia.Themes.Default/Controls/UserControl.xaml @@ -1,16 +1,25 @@ - diff --git a/src/Avalonia.Themes.Default/Controls/Window.xaml b/src/Avalonia.Themes.Default/Controls/Window.xaml index 9c515ebe30..00b108343e 100644 --- a/src/Avalonia.Themes.Default/Controls/Window.xaml +++ b/src/Avalonia.Themes.Default/Controls/Window.xaml @@ -1,26 +1,33 @@ - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/WindowNotificationManager.xaml b/src/Avalonia.Themes.Default/Controls/WindowNotificationManager.xaml index 7c1efa2e82..6dacf63be5 100644 --- a/src/Avalonia.Themes.Default/Controls/WindowNotificationManager.xaml +++ b/src/Avalonia.Themes.Default/Controls/WindowNotificationManager.xaml @@ -1,45 +1,52 @@ - - + + + + + + + + + + + + + + + + + + + + - - - - - + + diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index f266402aef..8f5bea557c 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -1,70 +1,75 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5b8cb5284bf81e1a846ea84a64828c6e7284732b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 29 Jul 2022 02:59:43 -0400 Subject: [PATCH 55/97] Port default theme DataGrid to control themes --- .../Themes/Default.xaml | 652 ++++++++++-------- .../Themes/Fluent.xaml | 3 +- .../Diagnostics/Views/MainWindow.xaml | 5 +- 3 files changed, 364 insertions(+), 296 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml index 0d1fe43eb6..83d9332613 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml @@ -1,305 +1,377 @@ - - 4 - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index a80cc2173c..5ae83427b5 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -57,8 +57,7 @@ diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index c32638f6ca..680424122b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -11,10 +11,7 @@ - - + From ceb59fc475cd8774f03ef472c5becfa81ecfab90 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 29 Jul 2022 03:09:22 -0400 Subject: [PATCH 56/97] Workaround dev tools issue --- samples/ControlCatalog/App.xaml.cs | 9 ++++++--- .../Diagnostics/Views/MainWindow.xaml | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 7ebb87094a..a5535ea52d 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -78,9 +78,12 @@ namespace ControlCatalog public override void Initialize() { - Styles.Insert(0, Fluent); - Styles.Insert(1, ColorPickerFluent); - Styles.Insert(2, DataGridFluent); + Styles.Insert(0, DefaultLight); + Styles.Insert(1, ColorPickerDefault); + Styles.Insert(2, DataGridDefault); + //Styles.Insert(0, Fluent); + //Styles.Insert(1, ColorPickerFluent); + //Styles.Insert(2, DataGridFluent); AvaloniaXamlLoader.Load(this); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index 680424122b..004518598c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -4,7 +4,8 @@ xmlns:diag="clr-namespace:Avalonia.Diagnostics" xmlns:default="using:Avalonia.Themes.Default" Title="Avalonia DevTools" - x:Class="Avalonia.Diagnostics.Views.MainWindow"> + x:Class="Avalonia.Diagnostics.Views.MainWindow" + Theme="{StaticResource {x:Type Window}}"> From 44b8df516c461dbbc38f825c5dab9aa01ee94dd2 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 29 Jul 2022 11:26:55 +0200 Subject: [PATCH 57/97] Set OverlayDismissEventPassThrough to false, add OverlayInputPassThroughElement property --- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 50 ++++++++++----------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index a0f3407b7a..bf938abc79 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -16,8 +16,8 @@ namespace Avalonia.Controls.Primitives /// Defines the property /// public static readonly DirectProperty IsOpenProperty = - AvaloniaProperty.RegisterDirect(nameof(IsOpen), - x => x.IsOpen); + AvaloniaProperty.RegisterDirect(nameof(IsOpen), + x => x.IsOpen); /// /// Defines the property @@ -39,16 +39,18 @@ namespace Avalonia.Controls.Primitives x => x.ShowMode, (x, v) => x.ShowMode = v); /// - /// Defines the AttachedFlyout property + /// Defines the property /// - public static readonly AttachedProperty AttachedFlyoutProperty = - AvaloniaProperty.RegisterAttached("AttachedFlyout", null); + public static readonly DirectProperty OverlayInputPassThroughElementProperty = + Popup.OverlayInputPassThroughElementProperty.AddOwner( + o => o._overlayInputPassThroughElement, + (o, v) => o._overlayInputPassThroughElement = v); /// - /// Defines the OverlayDismissEventPassThrough property + /// Defines the AttachedFlyout property /// - public static readonly StyledProperty OverlayDismissEventPassThroughProperty = - Popup.OverlayDismissEventPassThroughProperty.AddOwner(); + public static readonly AttachedProperty AttachedFlyoutProperty = + AvaloniaProperty.RegisterAttached("AttachedFlyout", null); private readonly Lazy _popupLazy; private bool _isOpen; @@ -58,10 +60,10 @@ namespace Avalonia.Controls.Primitives private PixelRect? _enlargePopupRectScreenPixelRect; private IDisposable? _transientDisposable; private Action? _popupHostChangedHandler; + private IInputElement? _overlayInputPassThroughElement; static FlyoutBase() { - OverlayDismissEventPassThroughProperty.OverrideDefaultValue(true); Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged); } @@ -109,25 +111,20 @@ namespace Avalonia.Controls.Primitives } /// - /// Gets or sets a value indicating whether the event that closes the flyout is passed - /// through to the parent window. + /// Gets or sets an element that should receive pointer input events even when underneath + /// the flyout's overlay. /// - /// - /// Clicks outside the the flyout cause the flyout to close. When is set to - /// false, these clicks will be handled by the flyout and not be registered by the parent - /// window. When set to true, the events will be passed through to the parent window. - /// - public bool OverlayDismissEventPassThrough + public IInputElement? OverlayInputPassThroughElement { - get => GetValue(OverlayDismissEventPassThroughProperty); - set => SetValue(OverlayDismissEventPassThroughProperty, value); + get => _overlayInputPassThroughElement; + set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value); } IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host; - event Action? IPopupHostProvider.PopupHostChanged - { - add => _popupHostChangedHandler += value; + event Action? IPopupHostProvider.PopupHostChanged + { + add => _popupHostChangedHandler += value; remove => _popupHostChangedHandler -= value; } @@ -200,7 +197,7 @@ namespace Avalonia.Controls.Primitives Popup.OverlayInputPassThroughElement = null; ((ISetLogicalParent)Popup).SetParent(null); - + // Ensure this isn't active _transientDisposable?.Dispose(); _transientDisposable = null; @@ -255,8 +252,7 @@ namespace Avalonia.Controls.Primitives Popup.Child = CreatePresenter(); } - Popup.OverlayInputPassThroughElement = placementTarget; - Popup.OverlayDismissEventPassThrough = OverlayDismissEventPassThrough; + Popup.OverlayInputPassThroughElement = OverlayInputPassThroughElement; if (CancelOpening()) { @@ -386,7 +382,9 @@ namespace Avalonia.Controls.Primitives var popup = new Popup { WindowManagerAddShadowHint = false, - IsLightDismissEnabled = true + IsLightDismissEnabled = true, + //Note: This is required to prevent Button.Flyout from opening the flyout again after dismiss. + OverlayDismissEventPassThrough = false }; popup.Opened += OnPopupOpened; From 29d957282ece12d6f497027ed4fa9d28cfce33fd Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 29 Jul 2022 11:28:19 +0200 Subject: [PATCH 58/97] Remove unsetting --- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index bf938abc79..00ebcab70e 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -194,7 +194,6 @@ namespace Avalonia.Controls.Primitives IsOpen = false; Popup.IsOpen = false; - Popup.OverlayInputPassThroughElement = null; ((ISetLogicalParent)Popup).SetParent(null); From 81b0dce302f3e588ce2e6e5e16a9d1eb0b5d69ea Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 11:55:52 +0200 Subject: [PATCH 59/97] Fix RichTextBlock Inlines update handling --- samples/Sandbox/MainWindow.axaml | 15 ++++++ samples/Sandbox/MainWindow.axaml.cs | 46 ++++++++++++++++++- .../Media/TextFormatting/TextLayout.cs | 7 ++- .../Media/TextFormatting/TextLine.cs | 2 +- .../Media/TextFormatting/TextLineImpl.cs | 7 ++- src/Avalonia.Controls/RichTextBlock.cs | 22 ++++----- .../RichTextBlockTests.cs | 44 ++++++++++++++++++ .../Media/TextFormatting/TextLayoutTests.cs | 12 +++-- 8 files changed, 135 insertions(+), 20 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index 0fc78795a6..cf2ce63c1f 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -9,6 +9,21 @@ + + + + + + diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs index 7a8b56bbb6..3040385708 100644 --- a/samples/Sandbox/MainWindow.axaml.cs +++ b/samples/Sandbox/MainWindow.axaml.cs @@ -3,7 +3,9 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Documents; using Avalonia.Controls.Presenters; +using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.VisualTree; @@ -11,6 +13,8 @@ namespace Sandbox { public class MainWindow : Window { + private TestViewModel _dc; + public MainWindow() { this.InitializeComponent(); @@ -27,13 +31,30 @@ namespace Sandbox var textPresenter = e.NameScope.Find("PART_TextPresenter") as TextPresenter; - DataContext = new TestViewModel(textPresenter); + _dc = new TestViewModel(textPresenter); + + DataContext = _dc; } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } + + private void Button_OnClick(object? sender, RoutedEventArgs e) + { + _dc.InlineCollection = new InlineCollection + { + new Run(""), + new Run("test3") {FontWeight = Avalonia.Media.FontWeight.Bold}, + }; + // _dc.Text = "nununu"; + } + + private void TextButton_OnClick(object? sender, RoutedEventArgs e) + { + _dc.Text = "nununu"; + } } public class TestViewModel : ViewModelBase @@ -46,6 +67,29 @@ namespace Sandbox _textPresenter = textPresenter; } + private InlineCollection _inlineCollection; + private string _text; + + public string Text + { + get => _text; + set + { + _text = value; + RaisePropertyChanged(); + } + } + + public InlineCollection InlineCollection + { + get => _inlineCollection; + set + { + _inlineCollection = value; + RaisePropertyChanged(); + } + } + public double Distance { get => _distance; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index f3e8b5969c..0828b6518a 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -537,8 +537,13 @@ namespace Avalonia.Media.TextFormatting /// /// The collapsing width. /// The . - private TextCollapsingProperties GetCollapsingProperties(double width) + private TextCollapsingProperties? GetCollapsingProperties(double width) { + if(_textTrimming == TextTrimming.None) + { + return null; + } + return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties)); } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs index c8a23097db..61b24dc8c5 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs @@ -153,7 +153,7 @@ namespace Avalonia.Media.TextFormatting /// /// A value that represents a collapsed line that can be displayed. /// - public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList); + public abstract TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList); /// /// Create a justified line based on justification text properties. diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 0ee791d935..a0aae76fad 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -119,7 +119,7 @@ namespace Avalonia.Media.TextFormatting } /// - public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList) + public override TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList) { if (collapsingPropertiesList.Length == 0) { @@ -128,6 +128,11 @@ namespace Avalonia.Media.TextFormatting var collapsingProperties = collapsingPropertiesList[0]; + if(collapsingProperties is null) + { + return this; + } + var collapsedRuns = collapsingProperties.Collapse(this); if (collapsedRuns is null) diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 0c8b1d125d..2f8bed3be7 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -44,8 +44,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty InlinesProperty = - AvaloniaProperty.Register( + public static readonly StyledProperty InlinesProperty = + AvaloniaProperty.Register( nameof(Inlines)); public static readonly DirectProperty CanCopyProperty = @@ -138,7 +138,7 @@ namespace Avalonia.Controls /// Gets or sets the inlines. /// [Content] - public InlineCollection Inlines + public InlineCollection? Inlines { get => GetValue(InlinesProperty); set => SetValue(InlinesProperty, value); @@ -159,7 +159,7 @@ namespace Avalonia.Controls remove => RemoveHandler(CopyingToClipboardEvent, value); } - internal bool HasComplexContent => Inlines.Count > 0; + internal bool HasComplexContent => Inlines != null && Inlines.Count > 0; /// /// Copies the current selection to the Clipboard. @@ -260,18 +260,18 @@ namespace Avalonia.Controls { if (!string.IsNullOrEmpty(_text)) { - Inlines.Add(_text); + Inlines?.Add(_text); _text = null; } - Inlines.Add(text); + Inlines?.Add(text); } } protected override string? GetText() { - return _text ?? Inlines.Text; + return _text ?? Inlines?.Text; } protected override void SetText(string? text) @@ -301,10 +301,10 @@ namespace Avalonia.Controls ITextSource textSource; - var inlines = Inlines; - if (HasComplexContent) { + var inlines = Inlines!; + var textRuns = new List(); foreach (var inline in inlines) @@ -537,7 +537,7 @@ namespace Avalonia.Controls switch (change.Property.Name) { - case nameof(InlinesProperty): + case nameof(Inlines): { OnInlinesChanged(change.OldValue as InlineCollection, change.NewValue as InlineCollection); InvalidateTextLayout(); @@ -553,7 +553,7 @@ namespace Avalonia.Controls return ""; } - var text = Inlines.Text ?? Text; + var text = GetText(); if (string.IsNullOrEmpty(text)) { diff --git a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs index eb4b88956d..c74f13b808 100644 --- a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs @@ -48,5 +48,49 @@ namespace Avalonia.Controls.UnitTests Assert.False(target.IsMeasureValid); } } + + [Fact] + public void Changing_Inlines_Should_Invalidate_Measure() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var target = new RichTextBlock(); + + var inlines = new InlineCollection { new Run("Hello") }; + + target.Measure(Size.Infinity); + + Assert.True(target.IsMeasureValid); + + target.Inlines = inlines; + + Assert.False(target.IsMeasureValid); + } + } + + [Fact] + public void Changing_Inlines_Should_Reset_Inlines_Parent() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var target = new RichTextBlock(); + + var run = new Run("Hello"); + + target.Inlines.Add(run); + + target.Measure(Size.Infinity); + + Assert.True(target.IsMeasureValid); + + target.Inlines = null; + + Assert.Null(run.Parent); + + target.Inlines = new InlineCollection { run }; + + Assert.Equal(target, run.Parent); + } + } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 6d057d900e..c457a96299 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -993,9 +993,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var currentX = 0.0; - for (int j = 0; j < clusters.Count; j++) - { - var cluster = clusters[j]; + var cluster = text.Length; + + for (int j = 0; j < clusters.Count - 1; j++) + { + var glyphAdvance = glyphAdvances[j]; var characterHit = textLine.GetCharacterHitFromDistance(currentX); @@ -1005,9 +1007,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(currentX, distance); - var glyphAdvance = glyphAdvances[j]; - currentX += glyphAdvance; + + cluster = clusters[j]; } } } From e7be7cb88de6cbea64f8492b6899d53b4878030d Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 16:29:36 +0200 Subject: [PATCH 60/97] Fix that thing --- .../Media/TextFormatting/TextLineImpl.cs | 89 +++++++++++++------ 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index a0aae76fad..94095cf82b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -176,7 +176,7 @@ namespace Avalonia.Media.TextFormatting return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0); } - if (distance > WidthIncludingTrailingWhitespace) + if (distance >= WidthIncludingTrailingWhitespace) { var lastRun = _textRuns[_textRuns.Count - 1]; @@ -195,7 +195,7 @@ namespace Avalonia.Media.TextFormatting if(currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) { var rightToLeftIndex = i; - var rightToLeftDistance = shapedRun.Size.Width; + currentPosition += currentRun.TextSourceLength; while (rightToLeftIndex + 1 <= _textRuns.Count - 1) { @@ -206,19 +206,24 @@ namespace Avalonia.Media.TextFormatting break; } - rightToLeftIndex++; + currentPosition += nextShaped.TextSourceLength; - rightToLeftDistance += nextShaped.Size.Width; + rightToLeftIndex++; } - for (var j = rightToLeftIndex; rightToLeftIndex >= 0; j--) + for (var j = i; i <= rightToLeftIndex; j++) { + if(j > _textRuns.Count - 1) + { + break; + } + currentRun = _textRuns[j]; - if(distance <= currentDistance + rightToLeftDistance - currentRun.Size.Width) + if(currentDistance + currentRun.Size.Width <= distance) { - currentPosition += currentRun.TextSourceLength; - rightToLeftDistance -= currentRun.Size.Width; + currentDistance += currentRun.Size.Width; + currentPosition -= currentRun.TextSourceLength; continue; } @@ -255,14 +260,18 @@ namespace Avalonia.Media.TextFormatting { characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); - var offset = Math.Max(0, currentPosition - shapedRun.Text.Start); + //var offset = 0; - if (!shapedRun.GlyphRun.IsLeftToRight) - { - offset = Math.Max(0, offset - shapedRun.Text.End); - } + //if (shapedRun.GlyphRun.IsLeftToRight) + //{ + // offset = Math.Max(0, currentPosition - shapedRun.Text.Start); + //} + //else + //{ + // offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length); + //} - characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); + //characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); break; } @@ -303,35 +312,50 @@ namespace Avalonia.Media.TextFormatting { var i = index; + var rightToLeftWidth = currentRun.Size.Width; + while (i + 1 <= _textRuns.Count - 1) { var nextRun = _textRuns[i + 1]; - if (nextRun is ShapedTextCharacters nextShapedRun) + if (nextRun is ShapedTextCharacters nextShapedRun && !nextShapedRun.ShapedBuffer.IsLeftToRight) { - if (nextShapedRun.ShapedBuffer.IsLeftToRight) - { - break; - } - } + i++; + + rightToLeftWidth += nextRun.Size.Width; - i++; + continue; + } + + break; } - while (i > index) + if(i > index) { - var rightToLeftRun = _textRuns[i]; + while (i >= index) + { + currentRun = _textRuns[i]; + + rightToLeftWidth -= currentRun.Size.Width; - currentPosition += rightToLeftRun.TextSourceLength; + if (currentPosition + currentRun.TextSourceLength >= characterIndex) + { + break; + } - remainingLength -= rightToLeftRun.TextSourceLength; + currentPosition += currentRun.TextSourceLength; - i--; + remainingLength -= currentRun.TextSourceLength; + + i--; + } + + currentDistance += rightToLeftWidth; } } - if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, - flowDirection, out var distance, out _)) + if (currentPosition + currentRun.TextSourceLength >= characterIndex && + TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _)) { return currentDistance + distance; } @@ -608,6 +632,15 @@ namespace Avalonia.Media.TextFormatting } else { + if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex) + { + startX += currentRun.Size.Width; + + currentPosition += currentRun.TextSourceLength; + + continue; + } + if (currentPosition < firstTextSourceIndex) { startX += currentRun.Size.Width; From d524dc9cdbc498ff2c8a468fe7cdcdc125ce37c9 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 16:56:43 +0200 Subject: [PATCH 61/97] Only apply the font fallback to regions of missing glyphs --- samples/Sandbox/MainWindow.axaml | 26 +--- samples/Sandbox/MainWindow.axaml.cs | 115 +----------------- .../Media/TextFormatting/TextCharacters.cs | 26 ++-- .../Presenters/TextPresenter.cs | 4 +- src/Avalonia.Controls/TextBox.cs | 2 - 5 files changed, 18 insertions(+), 155 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index cf2ce63c1f..d429777eeb 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,29 +1,5 @@ - - - - - - - - - - - - - - - - + diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs index 3040385708..2d36ed6d28 100644 --- a/samples/Sandbox/MainWindow.axaml.cs +++ b/samples/Sandbox/MainWindow.axaml.cs @@ -13,126 +13,15 @@ namespace Sandbox { public class MainWindow : Window { - private TestViewModel _dc; - public MainWindow() { this.InitializeComponent(); - this.AttachDevTools(); - - var textBox = this.FindControl("txtBox"); - - textBox.TemplateApplied += TextBox_TemplateApplied; - } - - private void TextBox_TemplateApplied(object sender, Avalonia.Controls.Primitives.TemplateAppliedEventArgs e) - { - var textBox = sender as TextBox; - - var textPresenter = e.NameScope.Find("PART_TextPresenter") as TextPresenter; - - _dc = new TestViewModel(textPresenter); - - DataContext = _dc; + this.AttachDevTools(); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } - - private void Button_OnClick(object? sender, RoutedEventArgs e) - { - _dc.InlineCollection = new InlineCollection - { - new Run(""), - new Run("test3") {FontWeight = Avalonia.Media.FontWeight.Bold}, - }; - // _dc.Text = "nununu"; - } - - private void TextButton_OnClick(object? sender, RoutedEventArgs e) - { - _dc.Text = "nununu"; - } - } - - public class TestViewModel : ViewModelBase - { - private readonly TextPresenter _textPresenter; - private double _distance = 45; - - public TestViewModel(TextPresenter textPresenter) - { - _textPresenter = textPresenter; - } - - private InlineCollection _inlineCollection; - private string _text; - - public string Text - { - get => _text; - set - { - _text = value; - RaisePropertyChanged(); - } - } - - public InlineCollection InlineCollection - { - get => _inlineCollection; - set - { - _inlineCollection = value; - RaisePropertyChanged(); - } - } - - public double Distance - { - get => _distance; - set - { - OnDistanceChanged(value); - RaisePropertyChanged(); - } - } - - private void OnDistanceChanged(double distance) - { - if(distance < 0) - { - distance = 0; - } - - if(distance > _textPresenter.TextLayout.Bounds.Width) - { - distance = _textPresenter.TextLayout.Bounds.Width; - } - - var height = _textPresenter.TextLayout.Bounds.Height; - - var distanceY = height / 2; - - _textPresenter.MoveCaretToPoint(new Point(distance, distanceY)); - - var caretIndex = _textPresenter.CaretIndex; - - Debug.WriteLine(caretIndex); - - _distance = distance; - } - } - - public class ViewModelBase : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; - - protected void RaisePropertyChanged([CallerMemberName]string propertyName = "") - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - } + } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index ab72601c3e..6611357ee7 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -38,7 +38,7 @@ namespace Avalonia.Media.TextFormatting /// Gets a list of . /// /// The shapeable text characters. - internal IReadOnlyList GetShapeableCharacters(ReadOnlySlice runText, sbyte biDiLevel, + internal IReadOnlyList GetShapeableCharacters(ReadOnlySlice runText, sbyte biDiLevel, ref TextRunProperties? previousProperties) { var shapeableCharacters = new List(2); @@ -65,7 +65,7 @@ namespace Avalonia.Media.TextFormatting /// The bidi level of the run. /// /// A list of shapeable text runs. - private static ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice text, + private static ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice text, TextRunProperties defaultProperties, sbyte biDiLevel, ref TextRunProperties? previousProperties) { var defaultTypeface = defaultProperties.Typeface; @@ -76,7 +76,7 @@ namespace Avalonia.Media.TextFormatting { if (script == Script.Common && previousTypeface is not null) { - if(TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out var fallbackCount, out _)) + if (TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out var fallbackCount, out _)) { return new ShapeableTextCharacters(text.Take(fallbackCount), defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); @@ -86,10 +86,10 @@ namespace Avalonia.Media.TextFormatting return new ShapeableTextCharacters(text.Take(count), defaultProperties.WithTypeface(currentTypeface), biDiLevel); } - + if (previousTypeface is not null) { - if(TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out count, out _)) + if (TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out count, out _)) { return new ShapeableTextCharacters(text.Take(count), defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); @@ -106,12 +106,12 @@ namespace Avalonia.Media.TextFormatting { continue; } - + codepoint = codepointEnumerator.Current; - + break; } - + //ToDo: Fix FontFamily fallback var matchFound = FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, @@ -157,14 +157,14 @@ namespace Avalonia.Media.TextFormatting /// /// protected static bool TryGetShapeableLength( - ReadOnlySlice text, - Typeface typeface, + ReadOnlySlice text, + Typeface typeface, Typeface? defaultTypeface, out int length, out Script script) { length = 0; - script = Script.Unknown; + script = Script.Unknown; if (text.Length == 0) { @@ -182,7 +182,7 @@ namespace Avalonia.Media.TextFormatting var currentScript = currentGrapheme.FirstCodepoint.Script; - if (currentScript != Script.Common && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) + if (defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) { break; } @@ -192,7 +192,7 @@ namespace Avalonia.Media.TextFormatting { break; } - + if (currentScript != script) { if (script is Script.Unknown || currentScript != Script.Common && diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 4f9be86641..e540f58195 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -498,13 +498,13 @@ namespace Avalonia.Controls.Presenters IReadOnlyList>? textStyleOverrides = null; - if (length > 0) + if (length > 0 && SelectionForegroundBrush != null) { textStyleOverrides = new[] { new ValueSpan(start, length, new GenericTextRunProperties(typeface, FontSize, - foregroundBrush: SelectionForegroundBrush ?? Brushes.Red)) + foregroundBrush: SelectionForegroundBrush)) }; } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 8490bfd3a0..1773163acc 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1245,8 +1245,6 @@ namespace Avalonia.Controls var caretIndex = hit.TextPosition; - Debug.WriteLine($"TextPos: {caretIndex}, X: {point.X}"); - var text = Text; if (text != null && _wordSelectionStart >= 0) From ca84dda76b168c0181819df735df3bfa1512b924 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 16:59:46 +0200 Subject: [PATCH 62/97] Revert changes --- samples/Sandbox/MainWindow.axaml | 1 - samples/Sandbox/MainWindow.axaml.cs | 12 +++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index d429777eeb..6929f192c7 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,5 +1,4 @@ - diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs index 2d36ed6d28..3d54036d29 100644 --- a/samples/Sandbox/MainWindow.axaml.cs +++ b/samples/Sandbox/MainWindow.axaml.cs @@ -1,13 +1,7 @@ -using System.ComponentModel; -using System.Diagnostics; -using System.Runtime.CompilerServices; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Documents; -using Avalonia.Controls.Presenters; -using Avalonia.Interactivity; using Avalonia.Markup.Xaml; -using Avalonia.VisualTree; +using Avalonia.Win32.WinRT.Composition; namespace Sandbox { @@ -16,12 +10,12 @@ namespace Sandbox public MainWindow() { this.InitializeComponent(); - this.AttachDevTools(); + this.AttachDevTools(); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } - } + } } From 6fefe5c8031a2aa24050c477535dff5297ca2f38 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 17:27:39 +0200 Subject: [PATCH 63/97] Align justified text to the natural start --- .../TextFormatting/InterWordJustification.cs | 17 ++++++++++++----- .../Media/TextFormatting/TextLineImpl.cs | 5 +++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs index df83ada34a..a49e4ef13b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs +++ b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs @@ -15,6 +15,13 @@ namespace Avalonia.Media.TextFormatting public override void Justify(TextLine textLine) { + var lineImpl = textLine as TextLineImpl; + + if(lineImpl is null) + { + return; + } + var paragraphWidth = Width; if (double.IsInfinity(paragraphWidth)) @@ -22,12 +29,12 @@ namespace Avalonia.Media.TextFormatting return; } - if (textLine.NewLineLength > 0) + if (lineImpl.NewLineLength > 0) { return; } - var textLineBreak = textLine.TextLineBreak; + var textLineBreak = lineImpl.TextLineBreak; if (textLineBreak is not null && textLineBreak.TextEndOfLine is not null) { @@ -39,7 +46,7 @@ namespace Avalonia.Media.TextFormatting var breakOportunities = new Queue(); - foreach (var textRun in textLine.TextRuns) + foreach (var textRun in lineImpl.TextRuns) { var text = textRun.Text; @@ -68,10 +75,10 @@ namespace Avalonia.Media.TextFormatting return; } - var remainingSpace = Math.Max(0, paragraphWidth - textLine.WidthIncludingTrailingWhitespace); + var remainingSpace = Math.Max(0, paragraphWidth - lineImpl.WidthIncludingTrailingWhitespace); var spacing = remainingSpace / breakOportunities.Count; - foreach (var textRun in textLine.TextRuns) + foreach (var textRun in lineImpl.TextRuns) { var text = textRun.Text; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 94095cf82b..2c4e553355 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1536,6 +1536,11 @@ namespace Avalonia.Media.TextFormatting var textAlignment = _paragraphProperties.TextAlignment; var paragraphFlowDirection = _paragraphProperties.FlowDirection; + if(textAlignment == TextAlignment.Justify) + { + textAlignment = TextAlignment.Start; + } + switch (textAlignment) { case TextAlignment.Start: From 2cf660b601c69ad28656dfb36de0be96c5a4783b Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 19:53:44 +0200 Subject: [PATCH 64/97] Second attempt to fix the font fallback --- src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index 6611357ee7..42a9e61c36 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -182,7 +182,7 @@ namespace Avalonia.Media.TextFormatting var currentScript = currentGrapheme.FirstCodepoint.Script; - if (defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) + if (!currentGrapheme.FirstCodepoint.IsWhiteSpace && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) { break; } From d5084eaf7582b83630c7a7b1c90b88f17503ce8e Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 20:14:48 +0200 Subject: [PATCH 65/97] Fix GetCharacterHitFromDistance --- .../Media/TextFormatting/TextLineImpl.cs | 24 +++++++++---------- src/Avalonia.Controls/RichTextBlock.cs | 2 +- src/Avalonia.Controls/TextBox.cs | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 2c4e553355..b9147c0f65 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -260,18 +260,18 @@ namespace Avalonia.Media.TextFormatting { characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); - //var offset = 0; - - //if (shapedRun.GlyphRun.IsLeftToRight) - //{ - // offset = Math.Max(0, currentPosition - shapedRun.Text.Start); - //} - //else - //{ - // offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length); - //} - - //characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); + var offset = 0; + + if (shapedRun.GlyphRun.IsLeftToRight) + { + offset = Math.Max(0, currentPosition - shapedRun.Text.Start); + } + else + { + offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length); + } + + characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); break; } diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 2f8bed3be7..1f8abbc30d 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -276,7 +276,7 @@ namespace Avalonia.Controls protected override void SetText(string? text) { - var oldValue = _text ?? Inlines?.Text; + var oldValue = GetText(); AddText(text); diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 1773163acc..4c9e9327d4 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1241,9 +1241,9 @@ namespace Avalonia.Controls MathUtilities.Clamp(point.X, 0, Math.Max(_presenter.Bounds.Width - 1, 0)), MathUtilities.Clamp(point.Y, 0, Math.Max(_presenter.Bounds.Height - 1, 0))); - var hit = _presenter.TextLayout.HitTestPoint(point); + _presenter.MoveCaretToPoint(point); - var caretIndex = hit.TextPosition; + var caretIndex = _presenter.CaretIndex; var text = Text; From adc81678ca6049def8124965642b3ad90b34e462 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 21:56:27 +0200 Subject: [PATCH 66/97] Only add offsets for LeftToRight runs --- src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index b9147c0f65..6c6939d2a0 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -266,10 +266,10 @@ namespace Avalonia.Media.TextFormatting { offset = Math.Max(0, currentPosition - shapedRun.Text.Start); } - else - { - offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length); - } + //else + //{ + // offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length); + //} characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); From c94120c065a69838399f87814b8bbcc0340e5789 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Sat, 30 Jul 2022 09:53:07 +0200 Subject: [PATCH 67/97] Try to fix tests on mac --- .../Media/TextFormatting/TextLayoutTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index c457a96299..43948e9229 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -1005,7 +1005,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster)); - Assert.Equal(currentX, distance); + Assert.Equal(currentX, distance, 5); currentX += glyphAdvance; From 2a5760921761d819264d190a381a2314b09a0516 Mon Sep 17 00:00:00 2001 From: Evgeny Gorbovoy Date: Sat, 30 Jul 2022 17:16:16 +0200 Subject: [PATCH 68/97] + more `ContainsExclusive` for nodes --- .../Rendering/SceneGraph/ExperimentalAcrylicNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs index 59ebcf5109..12b67105e9 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs @@ -83,7 +83,7 @@ namespace Avalonia.Rendering.SceneGraph if (Material != null) { var rect = Rect.Rect; - return rect.Contains(p); + return rect.ContainsExclusive(p); } } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs index 9199611ed6..1f58111ecf 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs @@ -73,6 +73,6 @@ namespace Avalonia.Rendering.SceneGraph } /// - public override bool HitTest(Point p) => Bounds.Contains(p); + public override bool HitTest(Point p) => Bounds.ContainsExclusive(p); } } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs index 23267166a5..339881e675 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs @@ -109,7 +109,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public override bool HitTest(Point p) => Bounds.Contains(p); + public override bool HitTest(Point p) => Bounds.ContainsExclusive(p); public override void Dispose() { From 86265f49aa8c9b13d2e420f505a65ad9df16f5b2 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 30 Jul 2022 16:03:48 -0400 Subject: [PATCH 69/97] Update samples/ControlCatalog/App.xaml.cs --- samples/ControlCatalog/App.xaml.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index a5535ea52d..7ebb87094a 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -78,12 +78,9 @@ namespace ControlCatalog public override void Initialize() { - Styles.Insert(0, DefaultLight); - Styles.Insert(1, ColorPickerDefault); - Styles.Insert(2, DataGridDefault); - //Styles.Insert(0, Fluent); - //Styles.Insert(1, ColorPickerFluent); - //Styles.Insert(2, DataGridFluent); + Styles.Insert(0, Fluent); + Styles.Insert(1, ColorPickerFluent); + Styles.Insert(2, DataGridFluent); AvaloniaXamlLoader.Load(this); } From 33931e99bb9222339b50994b514f960d4b3c34b2 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 30 Jul 2022 23:57:00 -0400 Subject: [PATCH 70/97] Fix unit tests --- tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 0bb6c01041..29148e6f2e 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -466,7 +466,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var xaml = @" - + "; var styles = AvaloniaRuntimeXamlLoader.Parse(xaml); From 195ef79e0a3151007eaa3d3b64a8a79d269ee915 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 31 Jul 2022 00:19:34 -0400 Subject: [PATCH 71/97] Convert ColorPicker default styles to ControlThemes --- .../Themes/Default/ColorPreviewer.xaml | 23 +- .../Themes/Default/ColorSlider.xaml | 346 +++++++++--------- .../Themes/Default/ColorSpectrum.xaml | 94 ++--- .../Themes/Default/Default.xaml | 76 ++-- 4 files changed, 274 insertions(+), 265 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml index c3bc7df4a4..e067fe4ab5 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml @@ -1,15 +1,14 @@ - + - - - 80 - 40 - + + 80 + 40 - + - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml index 35cd7a9faa..9aa2dcd9f9 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml @@ -1,188 +1,190 @@ - + - + - + - + - - + + + + + + - - - - + + - - + - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml index 0e57f6b483..0e137c89c6 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml @@ -1,9 +1,10 @@ - + - - - - + + + - - - + + + - - - - - + + + + + - + - - - + + + - + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml index db1fa3ee4e..b452e34394 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml @@ -3,42 +3,48 @@ xmlns:converters="using:Avalonia.Controls.Converters"> - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - + - - - + + + + + + + + + + + From bdd451cdf96179dc9c28659ee21db3cec8b8ee0b Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 31 Jul 2022 00:39:45 -0400 Subject: [PATCH 72/97] Adjust ColorPicker tab background corner radius --- .../Themes/Fluent/ColorPicker.xaml | 15 ++++----------- .../Themes/Fluent/ColorView.xaml | 8 ++++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index 907b00dfff..50c5c06479 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -3,9 +3,6 @@ xmlns:controls="using:Avalonia.Controls" x:CompileBindings="True"> - - 5,5,0,0 - @@ -25,7 +22,7 @@ Padding="0,0,10,0" UseLayoutRounding="False"> - @@ -45,8 +42,10 @@ - + - - - - diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 993745b1e5..153b4422ea 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -77,6 +77,8 @@ 17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5 13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z + + 5,5,0,0 @@ -644,6 +646,12 @@ + + + + From b3bd2e54ecb5fe274164f9ddd96be7482b4a1173 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 1 Aug 2022 16:19:41 +0300 Subject: [PATCH 73/97] Use correct ToggleModifier in ListBox multiselection on MacOS. --- src/Avalonia.Controls/ListBox.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 79285bb86b..80b5259a53 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Selection; using Avalonia.Controls.Templates; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.VisualTree; namespace Avalonia.Controls @@ -157,7 +158,7 @@ namespace Avalonia.Controls e.Source, true, e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), - e.KeyModifiers.HasAllFlags(KeyModifiers.Control), + e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService().CommandModifiers), point.Properties.IsRightButtonPressed); } } From 9f031603424cbcd7b1f4f5bcc5ce38c0c2b93152 Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:27:15 +0300 Subject: [PATCH 74/97] Update MacOS vmImage because current one is deprecated https://github.com/actions/virtual-environments/issues/5583 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index edf3c3d819..dde1da1446 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -59,7 +59,7 @@ jobs: variables: SolutionDir: '$(Build.SourcesDirectory)' pool: - vmImage: 'macOS-10.15' + vmImage: 'macos-12' steps: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 3.1.418' From 8d3984b929e21e44cda15c420d5818ff925dbd4e Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:31:34 +0300 Subject: [PATCH 75/97] macos-12 is in beta,use macos-11 instead --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dde1da1446..78012b9041 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -59,7 +59,7 @@ jobs: variables: SolutionDir: '$(Build.SourcesDirectory)' pool: - vmImage: 'macos-12' + vmImage: 'macos-11' steps: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 3.1.418' From 326fffc9fe14aafb571abc794331fe357423df7b Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:45:11 +0300 Subject: [PATCH 76/97] WIP --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 78012b9041..1d2fe88341 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -59,7 +59,7 @@ jobs: variables: SolutionDir: '$(Build.SourcesDirectory)' pool: - vmImage: 'macos-11' + vmImage: 'macos-12' steps: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 3.1.418' @@ -91,7 +91,7 @@ jobs: inputs: actions: 'build' scheme: '' - sdk: 'macosx11.1' + sdk: 'macosx13.1' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' xcodeVersion: '12' # Options: 8, 9, default, specifyPath From a62c7936690d28cb8a42ac2304b5a30de50b1afc Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:52:59 +0300 Subject: [PATCH 77/97] WIP --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1d2fe88341..2a6e1b5475 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -59,7 +59,7 @@ jobs: variables: SolutionDir: '$(Build.SourcesDirectory)' pool: - vmImage: 'macos-12' + vmImage: 'macos-11' steps: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 3.1.418' @@ -91,7 +91,7 @@ jobs: inputs: actions: 'build' scheme: '' - sdk: 'macosx13.1' + sdk: 'macosx12.2' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' xcodeVersion: '12' # Options: 8, 9, default, specifyPath From ebb8ffed1969c5d9156b6affa381a0ebf8c55e4f Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 19:10:25 +0300 Subject: [PATCH 78/97] WIP --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2a6e1b5475..7e4ba64fb9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -59,7 +59,7 @@ jobs: variables: SolutionDir: '$(Build.SourcesDirectory)' pool: - vmImage: 'macos-11' + vmImage: 'macos-12' steps: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 3.1.418' @@ -91,10 +91,10 @@ jobs: inputs: actions: 'build' scheme: '' - sdk: 'macosx12.2' + sdk: 'macosx12.3' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' - xcodeVersion: '12' # Options: 8, 9, default, specifyPath + xcodeVersion: '13.3' # Options: 8, 9, default, specifyPath args: '-derivedDataPath ./' - task: CmdLine@2 From 3d845206cd8d6c7a4395000b0892015354e0bc99 Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 19:31:40 +0300 Subject: [PATCH 79/97] WIP --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7e4ba64fb9..52fc8db53c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -94,7 +94,7 @@ jobs: sdk: 'macosx12.3' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' - xcodeVersion: '13.3' # Options: 8, 9, default, specifyPath + xcodeVersion: '13' # Options: 8, 9, default, specifyPath args: '-derivedDataPath ./' - task: CmdLine@2 From f87052311bd6e3448a198000fd1fe1915c5ab7c6 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 1 Aug 2022 22:01:38 -0400 Subject: [PATCH 80/97] Use resources to adjust tab background corner radius in ColorPicker --- .../Themes/Fluent/ColorPicker.xaml | 8 ++++++-- .../Themes/Fluent/ColorView.xaml | 12 +++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index 50c5c06479..797d6c90d7 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -45,7 +45,6 @@ + SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"> + + + 5,5,0,0 + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 153b4422ea..59cc48975f 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -77,8 +77,8 @@ 17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5 13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z - - 5,5,0,0 + + 3 @@ -99,7 +99,7 @@ HorizontalAlignment="Stretch" VerticalAlignment="Top" Background="{DynamicResource SystemControlBackgroundBaseLowBrush}" - CornerRadius="{TemplateBinding CornerRadius}" /> + CornerRadius="{DynamicResource ColorViewTabBackgroundCornerRadius}" /> - - - - From 53e7a741c9a476e83a4a0c89956715bac8cf42cc Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 1 Aug 2022 22:01:55 -0400 Subject: [PATCH 81/97] Switch back to default flyout placement in ColorPicker --- .../Themes/Fluent/ColorPicker.xaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index 797d6c90d7..74a1df4991 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -42,8 +42,7 @@ - + Date: Tue, 2 Aug 2022 11:48:20 +0200 Subject: [PATCH 82/97] fix: XML Comment --- src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs | 2 +- .../Rendering/Composition/Animations/CompositionAnimation.cs | 4 ++-- .../Rendering/Composition/Animations/ExpressionAnimation.cs | 2 +- .../Rendering/Composition/Animations/KeyFrameAnimation.cs | 4 ++-- src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs | 2 +- src/Avalonia.Base/Utilities/MathUtilities.cs | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs index 56a90f31ea..ab17263806 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs @@ -224,7 +224,7 @@ namespace Avalonia.Media.TextFormatting.Unicode } /// - /// Returns if is between + /// Returns if is between /// and , inclusive. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs index 19d316eb85..23f2aeaf90 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs @@ -16,10 +16,10 @@ namespace Avalonia.Rendering.Composition.Animations /// This is the base class for ExpressionAnimation and KeyFrameAnimation. /// /// - /// Use the method to start the animation. + /// Use the method to start the animation. /// Value parameters (as opposed to reference parameters which are set using ) /// are copied and "embedded" into an expression at the time CompositionObject.StartAnimation is called. - /// Changing the value of the variable after is called will not affect + /// Changing the value of the variable after is called will not affect /// the value of the ExpressionAnimation. /// See the remarks section of ExpressionAnimation for additional information. /// diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs index 577910d975..ec2972044e 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs @@ -16,7 +16,7 @@ namespace Avalonia.Rendering.Composition.Animations /// This contrasts s, which use an interpolator to define how the animating /// property changes over time. The mathematical equation can be defined using references to properties /// of Composition objects, mathematical functions and operators and Input. - /// Use the method to start the animation. + /// Use the method to start the animation. /// public class ExpressionAnimation : CompositionAnimation { diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs index 4692fde5e3..d21a4d06e3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs @@ -24,9 +24,9 @@ namespace Avalonia.Rendering.Composition.Animations /// The delay behavior of the key frame animation. /// public AnimationDelayBehavior DelayBehavior { get; set; } - + /// - /// Delay before the animation starts after is called. + /// Delay before the animation starts after is called. /// public System.TimeSpan DelayTime { get; set; } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs index 4b43f93aee..e1d43b4268 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The fill brush. /// The stroke pen. /// The geometry. - /// Child scenes for drawing visual brushes. + /// Auxiliary data required to draw the brush. public GeometryNode(Matrix transform, IBrush? brush, IPen? pen, diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs index 1f58111ecf..b23b0684d3 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The transform. /// The foreground brush. /// The glyph run to draw. - /// Child scenes for drawing visual brushes. + /// Auxiliary data required to draw the brush. public GlyphRunNode( Matrix transform, IBrush foreground, diff --git a/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs index ee5ec0a5fc..b027e43378 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The stroke pen. /// The start point of the line. /// The end point of the line. - /// Child scenes for drawing visual brushes. + /// Auxiliary data required to draw the brush. public LineNode( Matrix transform, IPen pen, diff --git a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs index 549c1fd7de..5fd200ddff 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs @@ -17,7 +17,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The opacity mask to push. /// The bounds of the mask. - /// Child scenes for drawing visual brushes. + /// Auxiliary data required to draw the brush. public OpacityMaskNode(IBrush mask, Rect bounds, IDisposable? aux = null) : base(Rect.Empty, Matrix.Identity, aux) { diff --git a/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs index a9d1bf96e5..f2ffd7411c 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs @@ -20,7 +20,7 @@ namespace Avalonia.Rendering.SceneGraph /// The stroke pen. /// The rectangle to draw. /// The box shadow parameters - /// Child scenes for drawing visual brushes. + /// Auxiliary data required to draw the brush. public RectangleNode( Matrix transform, IBrush? brush, diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 3d5be806e1..d381979c1e 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -255,7 +255,7 @@ namespace Avalonia.Utilities /// /// Clamps a value between a minimum and maximum value. /// - /// The value. + /// The value. /// The minimum value. /// The maximum value. /// The clamped value. From 89c0ef56371bddf68590f8f21efe61672d1084fc Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 2 Aug 2022 12:02:04 +0200 Subject: [PATCH 83/97] refactoring: removed some unused namespace --- .../Rendering/Composition/Animations/CompositionAnimation.cs | 3 --- src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs | 2 -- src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs | 4 ---- src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs | 2 -- src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 3 --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 2 -- 6 files changed, 16 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs index 19d316eb85..5499e1119a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs @@ -1,12 +1,9 @@ // ReSharper disable InconsistentNaming // ReSharper disable CheckNamespace -using System; -using System.Collections.Generic; using System.Numerics; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; -using Avalonia.Rendering.Composition.Transport; // Special license applies License.md diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs index 4b43f93aee..747e96f130 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs index 1f58111ecf..ed45e54f57 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; - using Avalonia.Media; -using Avalonia.Media.Immutable; using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { diff --git a/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs index ee5ec0a5fc..973b28dead 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index a80f406989..6400d67fde 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -1,11 +1,8 @@ using System; using System.IO; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Skia.Helpers; -using Avalonia.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 7f1af46e97..81fa8c4bce 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using Avalonia.Controls; @@ -9,7 +8,6 @@ using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Platform; -using Avalonia.Media.Imaging; using SharpDX.DirectWrite; using GlyphRun = Avalonia.Media.GlyphRun; using TextAlignment = Avalonia.Media.TextAlignment; From bf9a0d8656d787fc67a7ba8aea3f9c6097624f0c Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 2 Aug 2022 13:17:01 +0200 Subject: [PATCH 84/97] Use a different arabic font for tests --- .../Assets/NotoKufiArabic-Regular.ttf | Bin 122736 -> 0 bytes .../Assets/NotoSansArabic-Regular.ttf | Bin 0 -> 177004 bytes .../Media/CustomFontManagerImpl.cs | 8 +++++++- .../Media/TextFormatting/TextLayoutTests.cs | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) delete mode 100644 tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf create mode 100644 tests/Avalonia.RenderTests/Assets/NotoSansArabic-Regular.ttf diff --git a/tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf b/tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf deleted file mode 100644 index 6d2ad86f947a530de5dec09b9a230948d0f512c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122736 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 V75=m6xC;lbHm=3_}(L8AakEU|`p@iY%Zk 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 z�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 literal 0 HcmV?d00001 diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index 13cc14b03e..7d0cf05b0b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -16,7 +16,7 @@ namespace Avalonia.Skia.UnitTests.Media private readonly Typeface _defaultTypeface = new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono"); private readonly Typeface _arabicTypeface = - new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Kufi Arabic"); + new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans Arabic"); private readonly Typeface _italicTypeface = new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans", FontStyle.Italic); private readonly Typeface _emojiTypeface = @@ -82,6 +82,12 @@ namespace Avalonia.Skia.UnitTests.Media skTypeface = typefaceCollection.Get(typeface); break; } + case "Noto Sans Arabic": + { + var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_arabicTypeface.FontFamily); + skTypeface = typefaceCollection.Get(typeface); + break; + } case FontFamily.DefaultFontFamilyName: case "Noto Mono": { diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 43948e9229..c457a96299 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -1005,7 +1005,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster)); - Assert.Equal(currentX, distance, 5); + Assert.Equal(currentX, distance); currentX += glyphAdvance; From 4f79ac8a1c1cdecc71e89e4d94d98082ec54c8ea Mon Sep 17 00:00:00 2001 From: Benedikt Date: Tue, 2 Aug 2022 13:25:57 +0200 Subject: [PATCH 85/97] Fix precession errors --- src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs | 6 +++--- .../Media/TextFormatting/TextLayoutTests.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 6c6939d2a0..fa1ab6fd29 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -357,7 +357,7 @@ namespace Avalonia.Media.TextFormatting if (currentPosition + currentRun.TextSourceLength >= characterIndex && TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _)) { - return currentDistance + distance; + return Math.Max(0, currentDistance + distance); } //No hit hit found so we add the full width @@ -382,7 +382,7 @@ namespace Avalonia.Media.TextFormatting distance = currentGlyphRun.Size.Width - distance; } - return currentDistance - distance; + return Math.Max(0, currentDistance - distance); } //No hit hit found so we add the full width @@ -392,7 +392,7 @@ namespace Avalonia.Media.TextFormatting } } - return currentDistance; + return Math.Max(0, currentDistance); } private static bool TryGetDistanceFromCharacterHit( diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index c457a96299..43948e9229 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -1005,7 +1005,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster)); - Assert.Equal(currentX, distance); + Assert.Equal(currentX, distance, 5); currentX += glyphAdvance; From fe79cee6726a76e875a3184218106d7b3b6dc6de Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Tue, 2 Aug 2022 16:11:08 +0300 Subject: [PATCH 86/97] Fix Tests. --- .../ListBoxTests_Single.cs | 133 +++-- .../Primitives/SelectingItemsControlTests.cs | 89 +-- .../SelectingItemsControlTests_Multiple.cs | 542 ++++++++++-------- 3 files changed, 422 insertions(+), 342 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 8f795104bf..bf516748cc 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -5,10 +5,12 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -60,104 +62,123 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - }; - - ApplyTemplate(target); - _mouse.Click(target.Presenter.Panel.Children[0]); - - Assert.Equal(0, target.SelectedIndex); + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + _mouse.Click(target.Presenter.Panel.Children[0]); + + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - }; - - ApplyTemplate(target); - target.SelectedIndex = 0; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 0; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Item_Should_Select_It_When_SelectionMode_Toggle() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Single | SelectionMode.Toggle, - }; - - ApplyTemplate(target); + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Single | SelectionMode.Toggle, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Selected_Item_Should_Deselect_It_When_SelectionMode_Toggle() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Toggle, - }; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Toggle, + }; - ApplyTemplate(target); - target.SelectedIndex = 0; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 0; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(-1, target.SelectedIndex); + Assert.Equal(-1, target.SelectedIndex); + } } [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It_When_SelectionMode_ToggleAlwaysSelected() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected, - }; - - ApplyTemplate(target); - target.SelectedIndex = 0; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 0; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Another_Item_Should_Select_It_When_SelectionMode_Toggle() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Single | SelectionMode.Toggle, - }; - - ApplyTemplate(target); - target.SelectedIndex = 1; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Single | SelectionMode.Toggle, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 1; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 4b6b6a1182..330cbfd7b9 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -14,6 +14,7 @@ using Avalonia.Controls.Selection; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Markup.Data; using Avalonia.Platform; @@ -1115,42 +1116,48 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz " }, - }; - - Prepare(target); - _helper.Down((Interactive)target.Presenter.Panel.Children[1]); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); + _helper.Down((Interactive)target.Presenter.Panel.Children[1]); - var panel = target.Presenter.Panel; + var panel = target.Presenter.Panel; - Assert.Equal( - KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel), - panel.Children[1]); + Assert.Equal( + KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel), + panel.Children[1]); + } } [Fact] public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement() { - var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); - - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = items, - }; + var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); - Prepare(target); + var target = new ListBox + { + Template = Template(), + Items = items, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); - _helper.Down(target.Presenter.Panel.Children[1]); + _helper.Down(target.Presenter.Panel.Children[1]); - items.RemoveAt(1); + items.RemoveAt(1); - var panel = target.Presenter.Panel; + var panel = target.Presenter.Panel; - Assert.Null(KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel)); + Assert.Null(KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel)); + } } [Fact] @@ -1230,31 +1237,37 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - }; - - Prepare(target); - _helper.Down((Interactive)target.Presenter.Panel.Children[3]); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); + _helper.Down((Interactive)target.Presenter.Panel.Children[3]); - Assert.Equal(3, target.SelectedIndex); + Assert.Equal(3, target.SelectedIndex); + } } [Fact] public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - }; - - Prepare(target); - _helper.Down((Interactive)target.Presenter.Panel.Children[3]); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); + _helper.Down((Interactive)target.Presenter.Panel.Children[3]); - Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes); + Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes); + } } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index 6b26d76371..5d2f4e2a64 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -10,8 +10,10 @@ using Avalonia.Controls.Selection; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests.Primitives @@ -701,261 +703,290 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_From_No_Selection_Selects_From_Start() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); - Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); + Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target)); + } } [Fact] public void Ctrl_Selecting_Raises_SelectionChanged_Events() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Qux" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); - SelectionChangedEventArgs receivedArgs = null; + SelectionChangedEventArgs receivedArgs = null; - target.SelectionChanged += (_, args) => receivedArgs = args; + target.SelectionChanged += (_, args) => receivedArgs = args; - void VerifyAdded(string selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(new[] { selection }, receivedArgs.AddedItems); - Assert.Empty(receivedArgs.RemovedItems); - } + void VerifyAdded(string selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(new[] { selection }, receivedArgs.AddedItems); + Assert.Empty(receivedArgs.RemovedItems); + } - void VerifyRemoved(string selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); - Assert.Empty(receivedArgs.AddedItems); - } + void VerifyRemoved(string selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); + Assert.Empty(receivedArgs.AddedItems); + } - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - VerifyAdded("Bar"); + VerifyAdded("Bar"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - VerifyAdded("Baz"); + VerifyAdded("Baz"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); - VerifyAdded("Qux"); + VerifyAdded("Qux"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); - VerifyRemoved("Bar"); + VerifyRemoved("Bar"); + } } [Fact] public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Qux" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); - - Assert.Equal(1, target.SelectedIndex); - Assert.Equal("Bar", target.SelectedItem); - Assert.Equal(new[] { "Bar", "Baz", "Qux" }, target.SelectedItems); - - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); - - Assert.Equal(2, target.SelectedIndex); - Assert.Equal("Baz", target.SelectedItem); - Assert.Equal(new[] { "Baz", "Qux" }, target.SelectedItems); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); + + Assert.Equal(1, target.SelectedIndex); + Assert.Equal("Bar", target.SelectedItem); + Assert.Equal(new[] { "Bar", "Baz", "Qux" }, target.SelectedItems); + + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); + + Assert.Equal(2, target.SelectedIndex); + Assert.Equal("Baz", target.SelectedItem); + Assert.Equal(new[] { "Baz", "Qux" }, target.SelectedItems); + } } [Fact] public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - Assert.Equal(1, target.SelectedIndex); - Assert.Equal("Bar", target.SelectedItem); + Assert.Equal(1, target.SelectedIndex); + Assert.Equal("Bar", target.SelectedItem); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - Assert.Equal(1, target.SelectedIndex); - Assert.Equal("Bar", target.SelectedItem); + Assert.Equal(1, target.SelectedIndex); + Assert.Equal("Bar", target.SelectedItem); + } } [Fact] public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[3]); - _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); - Assert.Equal(new[] { 3, 4 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[3]); + _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { 3, 4 }, SelectedContainers(target)); + } } [Fact] public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[3]); - _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); - Assert.Equal(new[] { 3, 4, 5 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[3]); + _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); + Assert.Equal(new[] { 3, 4, 5 }, SelectedContainers(target)); + } } [Fact] public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, target.SelectedItems); - Assert.Equal(new[] { 0, 1, 2, 3, 4, 5 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, target.SelectedItems); + Assert.Equal(new[] { 0, 1, 2, 3, 4, 5 }, SelectedContainers(target)); + } } [Fact] public void Shift_Selecting_Raises_SelectionChanged_Events() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Qux" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); - SelectionChangedEventArgs receivedArgs = null; + SelectionChangedEventArgs receivedArgs = null; - target.SelectionChanged += (_, args) => receivedArgs = args; + target.SelectionChanged += (_, args) => receivedArgs = args; - void VerifyAdded(params string[] selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(selection, receivedArgs.AddedItems); - Assert.Empty(receivedArgs.RemovedItems); - } + void VerifyAdded(params string[] selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(selection, receivedArgs.AddedItems); + Assert.Empty(receivedArgs.RemovedItems); + } - void VerifyRemoved(string selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); - Assert.Empty(receivedArgs.AddedItems); - } + void VerifyRemoved(string selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); + Assert.Empty(receivedArgs.AddedItems); + } - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - VerifyAdded("Bar"); + VerifyAdded("Bar"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Shift); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Shift); - VerifyAdded("Baz" ,"Qux"); + VerifyAdded("Baz", "Qux"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); - VerifyRemoved("Qux"); + VerifyRemoved("Qux"); + } } [Fact] public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - Assert.Equal(new[] { "Foo" }, target.SelectedItems); + Assert.Equal(new[] { "Foo" }, target.SelectedItems); - _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); - Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); - Assert.Equal(new[] { "Foo", "Bar", "Foo" }, target.SelectedItems); + Assert.Equal(new[] { "Foo", "Bar", "Foo" }, target.SelectedItems); - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); - Assert.Equal(new[] { "Foo", "Bar", "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { "Foo", "Bar", "Foo", "Bar" }, target.SelectedItems); + } } [Fact] @@ -1158,70 +1189,79 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - target.SelectAll(); - - Assert.Equal(3, target.SelectedItems.Count); - - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - - Assert.Equal(1, target.SelectedItems.Count); - Assert.Equal(new[] { "Foo", }, target.SelectedItems); - Assert.Equal(new[] { 0 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectAll(); + + Assert.Equal(3, target.SelectedItems.Count); + + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + + Assert.Equal(1, target.SelectedItems.Count); + Assert.Equal(new[] { "Foo", }, target.SelectedItems); + Assert.Equal(new[] { 0 }, SelectedContainers(target)); + } } [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - target.SelectAll(); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectAll(); - Assert.Equal(3, target.SelectedItems.Count); + Assert.Equal(3, target.SelectedItems.Count); - _helper.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); + _helper.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); - Assert.Equal(3, target.SelectedItems.Count); + Assert.Equal(3, target.SelectedItems.Count); + } } [Fact] public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift); - - Assert.Equal(2, target.SelectedItems.Count); - - _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right); - - Assert.Equal(1, target.SelectedItems.Count); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift); + + Assert.Equal(2, target.SelectedItems.Count); + + _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right); + + Assert.Equal(1, target.SelectedItems.Count); + } } [Fact] @@ -1253,41 +1293,47 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Right_Click_Should_Not_Select_Multiple() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Shift); - - Assert.Equal(1, target.SelectedItems.Count); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Shift); + + Assert.Equal(1, target.SelectedItems.Count); + } } [Fact] public void Ctrl_Right_Click_Should_Not_Select_Multiple() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Control); - - Assert.Equal(1, target.SelectedItems.Count); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Control); + + Assert.Equal(1, target.SelectedItems.Count); + } } [Fact] From 1731f88822a6d0a4c2e5a58b69f866212e7ff7e4 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 2 Aug 2022 20:37:55 -0400 Subject: [PATCH 87/97] Rename Default to Simple --- Avalonia.sln | 2 +- samples/BindingDemo/App.xaml | 2 +- samples/ControlCatalog/App.xaml.cs | 2 +- samples/ControlCatalog/ControlCatalog.csproj | 2 +- samples/ControlCatalog/MainView.xaml.cs | 4 +- samples/PlatformSanityChecks/App.xaml | 3 +- .../PlatformSanityChecks.csproj | 2 +- samples/Previewer/App.xaml | 5 +- samples/Previewer/Previewer.csproj | 2 +- samples/VirtualizationDemo/App.xaml | 4 +- .../VirtualizationDemo.csproj | 2 +- .../interop/Direct3DInteropSample/App.paml | 5 +- .../Direct3DInteropSample.csproj | 2 +- src/Avalonia.Controls/ProgressBar.cs | 4 - .../Avalonia.Diagnostics.csproj | 2 +- .../Diagnostics/Views/MainWindow.xaml | 2 +- .../Diagnostics/Views/MainWindow.xaml.cs | 2 +- src/Avalonia.Themes.Default/DefaultTheme.xaml | 75 ------------------- .../DefaultTheme.xaml.cs | 11 --- .../Accents/Base.xaml | 0 .../Accents/BaseDark.xaml | 0 .../Accents/BaseLight.xaml | 0 .../ApiCompatBaseline.txt | 0 .../Avalonia.Themes.Simple.csproj} | 0 .../Controls/AutoCompleteBox.xaml | 0 .../Controls/Button.xaml | 0 .../Controls/ButtonSpinner.xaml | 6 +- .../Controls/Calendar.xaml | 0 .../Controls/CalendarButton.xaml | 0 .../Controls/CalendarDatePicker.xaml | 0 .../Controls/CalendarDayButton.xaml | 0 .../Controls/CalendarItem.xaml | 0 .../Controls/CaptionButtons.xaml | 10 +-- .../Controls/Carousel.xaml | 0 .../Controls/CheckBox.xaml | 0 .../Controls/ComboBox.xaml | 0 .../Controls/ComboBoxItem.xaml | 0 .../Controls/ContentControl.xaml | 0 .../Controls/ContextMenu.xaml | 2 +- .../Controls/DataValidationErrors.xaml | 0 .../Controls/DatePicker.xaml | 22 +++--- .../Controls/DateTimePickerShared.xaml | 22 +++--- .../Controls/DropDownButton.xaml | 0 .../Controls/EmbeddableControlRoot.xaml | 0 .../Controls/Expander.xaml | 34 ++++----- .../Controls/FlyoutPresenter.xaml | 0 .../Controls/FocusAdorner.xaml | 0 .../Controls/GridSplitter.xaml | 0 .../Controls/ItemsControl.xaml | 0 .../Controls/Label.xaml | 0 .../Controls/ListBox.xaml | 0 .../Controls/ListBoxItem.xaml | 0 .../Controls/ManagedFileChooser.xaml | 0 .../Controls/Menu.xaml | 6 +- .../Controls/MenuFlyoutPresenter.xaml | 2 +- .../Controls/MenuItem.xaml | 2 +- .../Controls/NativeMenuBar.xaml | 10 +-- .../Controls/NotificationCard.xaml | 0 .../Controls/NumericUpDown.xaml | 0 .../Controls/OverlayPopupHost.xaml | 0 .../Controls/PathIcon.xaml | 0 .../Controls/PopupRoot.xaml | 0 .../Controls/ProgressBar.xaml | 0 .../Controls/RadioButton.xaml | 0 .../Controls/RepeatButton.xaml | 0 .../Controls/RichTextBlock.xaml | 0 .../Controls/ScrollBar.xaml | 0 .../Controls/ScrollViewer.xaml | 2 +- .../Controls/Separator.xaml | 0 .../Controls/SimpleControls.xaml | 74 ++++++++++++++++++ .../Controls/Slider.xaml | 0 .../Controls/SplitButton.xaml | 8 +- .../Controls/SplitView.xaml | 0 .../Controls/TabControl.xaml | 0 .../Controls/TabItem.xaml | 0 .../Controls/TabStrip.xaml | 0 .../Controls/TabStripItem.xaml | 0 .../Controls/TextBox.xaml | 14 ++-- .../Controls/TimePicker.xaml | 20 ++--- .../Controls/TitleBar.xaml | 0 .../Controls/ToggleButton.xaml | 0 .../Controls/ToggleSwitch.xaml | 0 .../Controls/ToolTip.xaml | 0 .../Controls/TransitioningContentControl.xaml | 0 .../Controls/TreeView.xaml | 0 .../Controls/TreeViewItem.xaml | 4 +- .../Controls/UserControl.xaml | 4 +- .../Controls/Window.xaml | 0 .../Controls/WindowNotificationManager.xaml | 0 .../IBitmapToImageConverter.cs | 2 +- .../InverseBooleanValueConverter.cs | 2 +- .../Properties/AssemblyInfo.cs | 3 + .../SimpleTheme.cs | 14 ++-- .../SimpleThemeMode.cs | 2 +- .../Avalonia.Designer.HostApp.csproj | 2 +- .../Utilities/UriExtensionsTests.cs | 6 +- .../Avalonia.Benchmarks.csproj | 2 +- .../Themes/ThemeBenchmark.cs | 6 +- .../Avalonia.DesignerSupport.TestApp/App.xaml | 6 +- .../Avalonia.DesignerSupport.TestApp.csproj | 2 +- .../Avalonia.Direct2D1.RenderTests.csproj | 2 +- .../Avalonia.LeakTests.csproj | 2 +- .../Avalonia.Markup.Xaml.UnitTests.csproj | 2 +- .../Xaml/BasicTests.cs | 2 +- .../Avalonia.Skia.RenderTests.csproj | 2 +- .../Avalonia.UnitTests.csproj | 2 +- tests/Avalonia.UnitTests/TestServices.cs | 19 ++--- .../Avalonia.UnitTests/UnitTestApplication.cs | 8 +- 108 files changed, 220 insertions(+), 241 deletions(-) delete mode 100644 src/Avalonia.Themes.Default/DefaultTheme.xaml delete mode 100644 src/Avalonia.Themes.Default/DefaultTheme.xaml.cs rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Accents/Base.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Accents/BaseDark.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Accents/BaseLight.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/ApiCompatBaseline.txt (100%) rename src/{Avalonia.Themes.Default/Avalonia.Themes.Default.csproj => Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj} (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/AutoCompleteBox.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Button.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ButtonSpinner.xaml (94%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Calendar.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CalendarButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CalendarDatePicker.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CalendarDayButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CalendarItem.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CaptionButtons.xaml (93%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Carousel.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CheckBox.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ComboBox.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ComboBoxItem.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ContentControl.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ContextMenu.xaml (95%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/DataValidationErrors.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/DatePicker.xaml (94%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/DateTimePickerShared.xaml (87%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/DropDownButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/EmbeddableControlRoot.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Expander.xaml (88%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/FlyoutPresenter.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/FocusAdorner.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/GridSplitter.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ItemsControl.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Label.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ListBox.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ListBoxItem.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ManagedFileChooser.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Menu.xaml (95%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/MenuFlyoutPresenter.xaml (96%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/MenuItem.xaml (98%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/NativeMenuBar.xaml (76%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/NotificationCard.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/NumericUpDown.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/OverlayPopupHost.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/PathIcon.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/PopupRoot.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ProgressBar.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/RadioButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/RepeatButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/RichTextBlock.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ScrollBar.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ScrollViewer.xaml (99%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Separator.xaml (100%) create mode 100644 src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Slider.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/SplitButton.xaml (98%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/SplitView.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TabControl.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TabItem.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TabStrip.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TabStripItem.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TextBox.xaml (96%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TimePicker.xaml (94%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TitleBar.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ToggleButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ToggleSwitch.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ToolTip.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TransitioningContentControl.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TreeView.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TreeViewItem.xaml (96%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/UserControl.xaml (90%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Window.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/WindowNotificationManager.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/IBitmapToImageConverter.cs (95%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/InverseBooleanValueConverter.cs (94%) create mode 100644 src/Avalonia.Themes.Simple/Properties/AssemblyInfo.cs rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/SimpleTheme.cs (89%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/SimpleThemeMode.cs (67%) diff --git a/Avalonia.sln b/Avalonia.sln index 4999719676..35b6b2108a 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -13,7 +13,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Direct2D1", "src\W EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls", "src\Avalonia.Controls\Avalonia.Controls.csproj", "{D2221C82-4A25-4583-9B43-D791E3F6820C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Default", "src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj", "{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Simple", "src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj", "{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Diagnostics", "src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj", "{7062AE20-5DCC-4442-9645-8195BDECE63E}" EndProject diff --git a/samples/BindingDemo/App.xaml b/samples/BindingDemo/App.xaml index 3e312c8685..175e838616 100644 --- a/samples/BindingDemo/App.xaml +++ b/samples/BindingDemo/App.xaml @@ -4,6 +4,6 @@ x:Class="BindingDemo.App"> - + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 7ebb87094a..eb8ecc3af0 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -5,7 +5,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml.Styling; using Avalonia.Styling; -using Avalonia.Themes.Default; +using Avalonia.Themes.Simple; using Avalonia.Themes.Fluent; using ControlCatalog.ViewModels; diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 8358fb3cd4..2654574a3e 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -29,7 +29,7 @@ - + diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 47d11738bc..d8d58edaf6 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -60,14 +60,14 @@ namespace ControlCatalog } else if (theme == CatalogTheme.DefaultLight) { - App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Light; + App.Default.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Light; Application.Current.Styles[0] = App.DefaultLight; Application.Current.Styles[1] = App.ColorPickerDefault; Application.Current.Styles[2] = App.DataGridDefault; } else if (theme == CatalogTheme.DefaultDark) { - App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Dark; + App.Default.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Dark; Application.Current.Styles[0] = App.DefaultDark; Application.Current.Styles[1] = App.ColorPickerDefault; Application.Current.Styles[2] = App.DataGridDefault; diff --git a/samples/PlatformSanityChecks/App.xaml b/samples/PlatformSanityChecks/App.xaml index 25bab6ae35..1b9d64fca6 100644 --- a/samples/PlatformSanityChecks/App.xaml +++ b/samples/PlatformSanityChecks/App.xaml @@ -1,6 +1,5 @@ - - + diff --git a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj index 4f7f06b529..5c743aabdb 100644 --- a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj +++ b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj @@ -7,7 +7,7 @@ - + diff --git a/samples/Previewer/App.xaml b/samples/Previewer/App.xaml index 6bae1955af..1b9d64fca6 100644 --- a/samples/Previewer/App.xaml +++ b/samples/Previewer/App.xaml @@ -1,6 +1,5 @@ - - + - \ No newline at end of file + diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj index 98560e9ab0..2cc84168dc 100644 --- a/samples/Previewer/Previewer.csproj +++ b/samples/Previewer/Previewer.csproj @@ -10,7 +10,7 @@ - + diff --git a/samples/VirtualizationDemo/App.xaml b/samples/VirtualizationDemo/App.xaml index 3ad1dce794..0b64d0c09b 100644 --- a/samples/VirtualizationDemo/App.xaml +++ b/samples/VirtualizationDemo/App.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="VirtualizationDemo.App"> - - + + \ No newline at end of file diff --git a/samples/VirtualizationDemo/VirtualizationDemo.csproj b/samples/VirtualizationDemo/VirtualizationDemo.csproj index bd6054327f..b27cfe77e8 100644 --- a/samples/VirtualizationDemo/VirtualizationDemo.csproj +++ b/samples/VirtualizationDemo/VirtualizationDemo.csproj @@ -5,7 +5,7 @@ - + diff --git a/samples/interop/Direct3DInteropSample/App.paml b/samples/interop/Direct3DInteropSample/App.paml index d9630eef58..e6d77dfaf4 100644 --- a/samples/interop/Direct3DInteropSample/App.paml +++ b/samples/interop/Direct3DInteropSample/App.paml @@ -1,6 +1,5 @@ - - + - \ No newline at end of file + diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj index 81b94b4d09..f9ef4693d5 100644 --- a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj +++ b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 56b9caf085..91114628ee 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -119,14 +119,12 @@ namespace Avalonia.Controls nameof(Percentage), o => o.Percentage); - [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] public static readonly DirectProperty IndeterminateStartingOffsetProperty = AvaloniaProperty.RegisterDirect( nameof(IndeterminateStartingOffset), p => p.IndeterminateStartingOffset, (p, o) => p.IndeterminateStartingOffset = o); - [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] public static readonly DirectProperty IndeterminateEndingOffsetProperty = AvaloniaProperty.RegisterDirect( nameof(IndeterminateEndingOffset), @@ -139,14 +137,12 @@ namespace Avalonia.Controls private set { SetAndRaise(PercentageProperty, ref _percentage, value); } } - [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] public double IndeterminateStartingOffset { get => _indeterminateStartingOffset; set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value); } - [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] public double IndeterminateEndingOffset { get => _indeterminateEndingOffset; diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index adddf3f57b..d719135a7f 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index 004518598c..63073952ac 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:views="clr-namespace:Avalonia.Diagnostics.Views" xmlns:diag="clr-namespace:Avalonia.Diagnostics" - xmlns:default="using:Avalonia.Themes.Default" + xmlns:default="using:Avalonia.Themes.Simple" Title="Avalonia DevTools" x:Class="Avalonia.Diagnostics.Views.MainWindow" Theme="{StaticResource {x:Type Window}}"> diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 96dc929434..c81997f2cb 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -11,7 +11,7 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Markup.Xaml; using Avalonia.Styling; -using Avalonia.Themes.Default; +using Avalonia.Themes.Simple; using Avalonia.VisualTree; namespace Avalonia.Diagnostics.Views diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml deleted file mode 100644 index 8f5bea557c..0000000000 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml.cs b/src/Avalonia.Themes.Default/DefaultTheme.xaml.cs deleted file mode 100644 index 598b418977..0000000000 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Avalonia.Styling; - -namespace Avalonia.Themes.Default -{ - /// - /// The default Avalonia theme. - /// - public class DefaultTheme : Styles - { - } -} diff --git a/src/Avalonia.Themes.Default/Accents/Base.xaml b/src/Avalonia.Themes.Simple/Accents/Base.xaml similarity index 100% rename from src/Avalonia.Themes.Default/Accents/Base.xaml rename to src/Avalonia.Themes.Simple/Accents/Base.xaml diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml similarity index 100% rename from src/Avalonia.Themes.Default/Accents/BaseDark.xaml rename to src/Avalonia.Themes.Simple/Accents/BaseDark.xaml diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml similarity index 100% rename from src/Avalonia.Themes.Default/Accents/BaseLight.xaml rename to src/Avalonia.Themes.Simple/Accents/BaseLight.xaml diff --git a/src/Avalonia.Themes.Default/ApiCompatBaseline.txt b/src/Avalonia.Themes.Simple/ApiCompatBaseline.txt similarity index 100% rename from src/Avalonia.Themes.Default/ApiCompatBaseline.txt rename to src/Avalonia.Themes.Simple/ApiCompatBaseline.txt diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj similarity index 100% rename from src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj rename to src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj diff --git a/src/Avalonia.Themes.Default/Controls/AutoCompleteBox.xaml b/src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml similarity index 100% rename from src/Avalonia.Themes.Default/Controls/AutoCompleteBox.xaml rename to src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml diff --git a/src/Avalonia.Themes.Default/Controls/Button.xaml b/src/Avalonia.Themes.Simple/Controls/Button.xaml similarity index 100% rename from src/Avalonia.Themes.Default/Controls/Button.xaml rename to src/Avalonia.Themes.Simple/Controls/Button.xaml diff --git a/src/Avalonia.Themes.Default/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Simple/Controls/ButtonSpinner.xaml similarity index 94% rename from src/Avalonia.Themes.Default/Controls/ButtonSpinner.xaml rename to src/Avalonia.Themes.Simple/Controls/ButtonSpinner.xaml index 4585fc8e56..9798e5290b 100644 --- a/src/Avalonia.Themes.Default/Controls/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ButtonSpinner.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:CompileBindings="True"> - @@ -38,7 +38,7 @@ IsVisible="{TemplateBinding ShowButtonSpinner}" Rows="2"> + Theme="{StaticResource SimpleButtonSpinnerRepeatButton}"> + Theme="{StaticResource SimpleButtonSpinnerRepeatButton}"> - @@ -44,7 +44,7 @@ TextElement.FontSize="10"> /// The base URL for the XAML context. - public SimpleTheme(Uri baseUri) + public SimpleTheme(Uri? baseUri = null) { - _baseUri = baseUri; + _baseUri = baseUri ?? new Uri("avares://Avalonia.Themes.Simple/"); InitStyles(_baseUri); } @@ -138,18 +138,18 @@ namespace Avalonia.Themes.Default { new StyleInclude(baseUri) { - Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") + Source = new Uri("avares://Avalonia.Themes.Simple/Controls/SimpleControls.xaml") }, new StyleInclude(baseUri) { - Source = new Uri("avares://Avalonia.Themes.Default/Accents/Base.xaml") + Source = new Uri("avares://Avalonia.Themes.Simple/Accents/Base.xaml") } }; _simpleLight = new Styles { new StyleInclude(baseUri) { - Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml") + Source = new Uri("avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml") } }; @@ -157,7 +157,7 @@ namespace Avalonia.Themes.Default { new StyleInclude(baseUri) { - Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseDark.xaml") + Source = new Uri("avares://Avalonia.Themes.Simple/Accents/BaseDark.xaml") } }; } diff --git a/src/Avalonia.Themes.Default/SimpleThemeMode.cs b/src/Avalonia.Themes.Simple/SimpleThemeMode.cs similarity index 67% rename from src/Avalonia.Themes.Default/SimpleThemeMode.cs rename to src/Avalonia.Themes.Simple/SimpleThemeMode.cs index be33466327..683c751f10 100644 --- a/src/Avalonia.Themes.Default/SimpleThemeMode.cs +++ b/src/Avalonia.Themes.Simple/SimpleThemeMode.cs @@ -1,4 +1,4 @@ -namespace Avalonia.Themes.Default +namespace Avalonia.Themes.Simple { public enum SimpleThemeMode { diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 1cf68c1605..f509bb21ba 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs index 5c3ac6adeb..4a879c8ced 100644 --- a/tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs +++ b/tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs @@ -10,9 +10,9 @@ public class UriExtensionsTests public void Assembly_Name_From_Query_Parsed() { const string key = "assembly"; - const string value = "Avalonia.Themes.Default"; + const string value = "Avalonia.Themes.Simple"; - var uri = new Uri($"resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?{key}={value}"); + var uri = new Uri($"resm:Avalonia.Themes.Simple.Accents.BaseLight.xaml?{key}={value}"); var name = uri.GetAssemblyNameFromQuery(); Assert.Equal(value, name); @@ -21,7 +21,7 @@ public class UriExtensionsTests [Fact] public void Assembly_Name_From_Empty_Query_Not_Parsed() { - var uri = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml"); + var uri = new Uri("resm:Avalonia.Themes.Simple.Accents.BaseLight.xaml"); var name = uri.GetAssemblyNameFromQuery(); Assert.Equal(string.Empty, name); diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj index 5d17808e0c..3f4978f544 100644 --- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj +++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs index 9a5b49790d..c64931a6ef 100644 --- a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs @@ -37,8 +37,8 @@ namespace Avalonia.Benchmarks.Themes } [Benchmark] - [Arguments("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml")] - [Arguments("avares://Avalonia.Themes.Default/Accents/BaseDark.xaml")] + [Arguments("avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml")] + [Arguments("avares://Avalonia.Themes.Simple/Accents/BaseDark.xaml")] public bool InitDefaultTheme(string themeUri) { UnitTestApplication.Current.Styles[0] = new Styles @@ -49,7 +49,7 @@ namespace Avalonia.Benchmarks.Themes }, new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) { - Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") + Source = new Uri("avares://Avalonia.Themes.Simple/DefaultTheme.xaml") } }; return ((IResourceHost)UnitTestApplication.Current).TryGetResource("ThemeAccentColor", out _); diff --git a/tests/Avalonia.DesignerSupport.TestApp/App.xaml b/tests/Avalonia.DesignerSupport.TestApp/App.xaml index 5a33ffff80..803cae09c8 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml @@ -3,8 +3,8 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - - - + + + diff --git a/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj b/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj index f66b2b0457..278b0e087e 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj +++ b/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj @@ -21,7 +21,7 @@ - + diff --git a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj index 52acc78db1..4a90da77e7 100644 --- a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj +++ b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj index a00b24bdd7..4572f7ae7c 100644 --- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj +++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index a0efa7bdeb..f562529cb8 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 29148e6f2e..af2435a52f 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -466,7 +466,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var xaml = @" - + "; var styles = AvaloniaRuntimeXamlLoader.Parse(xaml); diff --git a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj index d3f2b44968..5e481f21c1 100644 --- a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj +++ b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj @@ -14,7 +14,7 @@ - + diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index 52ef23c966..cb6884cad8 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index c1be745aca..6a211d238e 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -6,7 +6,7 @@ using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Styling; -using Avalonia.Themes.Default; +using Avalonia.Themes.Simple; using Avalonia.Rendering; using System.Reactive.Concurrency; using System.Collections.Generic; @@ -81,7 +81,7 @@ namespace Avalonia.UnitTests IScheduler scheduler = null, ICursorFactory standardCursorFactory = null, IStyler styler = null, - Func theme = null, + Func theme = null, IPlatformThreadingInterface threadingInterface = null, IFontManagerImpl fontManagerImpl = null, ITextShaperImpl textShaperImpl = null, @@ -122,7 +122,7 @@ namespace Avalonia.UnitTests public IScheduler Scheduler { get; } public ICursorFactory StandardCursorFactory { get; } public IStyler Styler { get; } - public Func Theme { get; } + public Func Theme { get; } public IPlatformThreadingInterface ThreadingInterface { get; } public IWindowImpl WindowImpl { get; } public IWindowingPlatform WindowingPlatform { get; } @@ -169,18 +169,9 @@ namespace Avalonia.UnitTests windowImpl: windowImpl ?? WindowImpl); } - private static Styles CreateDefaultTheme() + private static IStyle CreateDefaultTheme() { - var result = new Styles - { - new DefaultTheme(), - }; - - var baseLight = (IStyle)AvaloniaXamlLoader.Load( - new Uri("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml")); - result.Add(baseLight); - - return result; + return new SimpleTheme { Mode = SimpleThemeMode.Light }; } private static IPlatformRenderInterface CreateRenderInterfaceMock() diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 63c2832b92..260771c9ab 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -71,12 +71,16 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.Styler) .Bind().ToConstant(Services.WindowingPlatform) .Bind().ToSingleton(); - var styles = Services.Theme?.Invoke(); + var theme = Services.Theme?.Invoke(); - if (styles != null) + if (theme is Styles styles) { Styles.AddRange(styles); } + else if (theme is not null) + { + Styles.Add(theme); + } } } } From 5ee439b5e400db88f1a620ebc60bc9222e96552e Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 2 Aug 2022 20:56:45 -0400 Subject: [PATCH 88/97] Rename remainings --- samples/ControlCatalog/App.xaml.cs | 24 +++++++++---------- samples/ControlCatalog/MainView.xaml | 4 ++-- samples/ControlCatalog/MainView.xaml.cs | 20 ++++++++-------- samples/ControlCatalog/Models/CatalogTheme.cs | 10 +++----- .../{Default => Simple}/ColorPreviewer.xaml | 0 .../{Default => Simple}/ColorSlider.xaml | 0 .../{Default => Simple}/ColorSpectrum.xaml | 0 .../Default.xaml => Simple/Simple.xaml} | 6 ++--- .../Themes/{Default.xaml => Simple.xaml} | 4 ++-- .../Diagnostics/Views/MainWindow.xaml | 5 ++-- 10 files changed, 34 insertions(+), 39 deletions(-) rename src/Avalonia.Controls.ColorPicker/Themes/{Default => Simple}/ColorPreviewer.xaml (100%) rename src/Avalonia.Controls.ColorPicker/Themes/{Default => Simple}/ColorSlider.xaml (100%) rename src/Avalonia.Controls.ColorPicker/Themes/{Default => Simple}/ColorSpectrum.xaml (100%) rename src/Avalonia.Controls.ColorPicker/Themes/{Default/Default.xaml => Simple/Simple.xaml} (94%) rename src/Avalonia.Controls.DataGrid/Themes/{Default.xaml => Simple.xaml} (99%) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index eb8ecc3af0..e936359fe4 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -23,9 +23,9 @@ namespace ControlCatalog Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml") }; - public static readonly StyleInclude ColorPickerDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) + public static readonly StyleInclude ColorPickerSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { - Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml") + Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml") }; public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) @@ -33,16 +33,16 @@ namespace ControlCatalog Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml") }; - public static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) + public static readonly StyleInclude DataGridSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { - Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Default.xaml") + Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml") }; public static FluentTheme Fluent = new FluentTheme(new Uri("avares://ControlCatalog/Styles")); - public static SimpleTheme Default = new SimpleTheme(new Uri("avares://ControlCatalog/Styles")); + public static SimpleTheme Simple = new SimpleTheme(new Uri("avares://ControlCatalog/Styles")); - public static Styles DefaultLight = new Styles + public static Styles SimpleLight = new Styles { new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { @@ -56,10 +56,10 @@ namespace ControlCatalog { Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml") }, - Default + Simple }; - public static Styles DefaultDark = new Styles + public static Styles SimpleDark = new Styles { new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { @@ -73,14 +73,14 @@ namespace ControlCatalog { Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml") }, - Default + Simple }; public override void Initialize() { - Styles.Insert(0, Fluent); - Styles.Insert(1, ColorPickerFluent); - Styles.Insert(2, DataGridFluent); + Styles.Insert(0, Simple); + Styles.Insert(1, ColorPickerSimple); + Styles.Insert(2, DataGridSimple); AvaloniaXamlLoader.Load(this); } diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 7461e78c33..7f5a191519 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -187,8 +187,8 @@ FluentLight FluentDark - DefaultLight - DefaultDark + SimpleLight + SimpleDark - - - + + + diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml similarity index 99% rename from src/Avalonia.Controls.DataGrid/Themes/Default.xaml rename to src/Avalonia.Controls.DataGrid/Themes/Simple.xaml index 83d9332613..6a748f399e 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml @@ -223,7 +223,7 @@ - @@ -270,7 +270,7 @@ BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}" Foreground="{TemplateBinding Foreground}" - Theme="{StaticResource DefaultDataGridRowGroupExpanderButtonTheme}" /> + Theme="{StaticResource SimpleDataGridRowGroupExpanderButtonTheme}" /> @@ -11,8 +10,8 @@ - - + + From fcdb22983b873969f6aaff84c62a4959356b9bb1 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 2 Aug 2022 21:03:17 -0400 Subject: [PATCH 89/97] Revert debug changes --- samples/ControlCatalog/App.xaml.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index e936359fe4..750c1082a6 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -78,9 +78,9 @@ namespace ControlCatalog public override void Initialize() { - Styles.Insert(0, Simple); - Styles.Insert(1, ColorPickerSimple); - Styles.Insert(2, DataGridSimple); + Styles.Insert(0, Fluent); + Styles.Insert(1, ColorPickerFluent); + Styles.Insert(2, DataGridFluent); AvaloniaXamlLoader.Load(this); } From a9910501ec5e67e06309847bfaf321bcf62fea93 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 2 Aug 2022 21:32:04 -0400 Subject: [PATCH 90/97] Fix tests --- samples/VirtualizationDemo/App.xaml | 16 +++++----- .../Themes/ThemeBenchmark.cs | 29 ++++++++----------- .../Avalonia.DesignerSupport.TestApp/App.xaml | 15 ++++------ 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/samples/VirtualizationDemo/App.xaml b/samples/VirtualizationDemo/App.xaml index 0b64d0c09b..eb5f0e4dca 100644 --- a/samples/VirtualizationDemo/App.xaml +++ b/samples/VirtualizationDemo/App.xaml @@ -1,9 +1,7 @@ - - - - - - \ No newline at end of file + + + + + diff --git a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs index c64931a6ef..aed8a38d08 100644 --- a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs @@ -4,6 +4,8 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml.Styling; using Avalonia.Platform; using Avalonia.Styling; +using Avalonia.Themes.Fluent; +using Avalonia.Themes.Simple; using Avalonia.UnitTests; using BenchmarkDotNet.Attributes; @@ -25,32 +27,25 @@ namespace Avalonia.Benchmarks.Themes } [Benchmark] - [Arguments("avares://Avalonia.Themes.Fluent/FluentDark.xaml")] - [Arguments("avares://Avalonia.Themes.Fluent/FluentLight.xaml")] - public bool InitFluentTheme(string themeUri) + [Arguments(FluentThemeMode.Dark)] + [Arguments(FluentThemeMode.Light)] + public bool InitFluentTheme(FluentThemeMode mode) { - UnitTestApplication.Current.Styles[0] = new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) + UnitTestApplication.Current.Styles[0] = new FluentTheme(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) { - Source = new Uri(themeUri) + Mode = mode }; return ((IResourceHost)UnitTestApplication.Current).TryGetResource("SystemAccentColor", out _); } [Benchmark] - [Arguments("avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml")] - [Arguments("avares://Avalonia.Themes.Simple/Accents/BaseDark.xaml")] - public bool InitDefaultTheme(string themeUri) + [Arguments(SimpleThemeMode.Dark)] + [Arguments(SimpleThemeMode.Light)] + public bool InitDefaultTheme(SimpleThemeMode mode) { - UnitTestApplication.Current.Styles[0] = new Styles + UnitTestApplication.Current.Styles[0] = new SimpleTheme(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) { - new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) - { - Source = new Uri(themeUri) - }, - new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) - { - Source = new Uri("avares://Avalonia.Themes.Simple/DefaultTheme.xaml") - } + Mode = mode }; return ((IResourceHost)UnitTestApplication.Current).TryGetResource("ThemeAccentColor", out _); } diff --git a/tests/Avalonia.DesignerSupport.TestApp/App.xaml b/tests/Avalonia.DesignerSupport.TestApp/App.xaml index 803cae09c8..ad32431b37 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml @@ -1,10 +1,7 @@ - - - - - - + + + + From e94eb0cbfd6bc32e588413c132fc7f54e641089c Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 2 Aug 2022 23:49:17 -0400 Subject: [PATCH 91/97] More simple theme renaming --- src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs | 4 ++-- src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml | 2 +- src/Avalonia.ReactiveUI/TransitioningContentControl.cs | 2 +- tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs | 2 +- tests/Avalonia.LeakTests/ControlTests.cs | 2 +- tests/Avalonia.UnitTests/TestServices.cs | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 39a83c00c4..4b41ecf328 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -326,8 +326,8 @@ namespace Avalonia.Build.Tasks var op = i[c].Operand as MethodReference; // TODO: Throw an error - // This usually happens when same XAML resource was added twice for some weird reason - // We currently support it for dual-named default theme resource + // This usually happens when the same XAML resource was added twice for some weird reason + // We currently support it for dual-named simple theme resources if (op != null && op.Name == TrampolineName) { diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml index 697a94421d..1e507a91fe 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml @@ -41,7 +41,7 @@ - + diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs index d26e90b2da..2bbf87590e 100644 --- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs +++ b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs @@ -57,7 +57,7 @@ namespace Avalonia.ReactiveUI /// /// TransitioningContentControl uses the default ContentControl - /// template from Avalonia default theme. + /// template from Avalonia simple theme. /// Type IStyleable.StyleKey => typeof(ContentControl); diff --git a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs index aed8a38d08..86ba4c6005 100644 --- a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs @@ -41,7 +41,7 @@ namespace Avalonia.Benchmarks.Themes [Benchmark] [Arguments(SimpleThemeMode.Dark)] [Arguments(SimpleThemeMode.Light)] - public bool InitDefaultTheme(SimpleThemeMode mode) + public bool InitSimpleTheme(SimpleThemeMode mode) { UnitTestApplication.Current.Styles[0] = new SimpleTheme(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) { diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index a7474dc92e..2a77bd55fc 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -313,7 +313,7 @@ namespace Avalonia.LeakTests var border = textBox.GetTemplateChildren().FirstOrDefault(x => x.Name == "border"); // The TextBox should have subscriptions to its Classes collection from the - // default theme. + // simple theme. Assert.NotEqual(0, textBox.Classes.ListenerCount); // Clear the content and ensure the TextBox is removed. diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 6a211d238e..49da2794c1 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -24,7 +24,7 @@ namespace Avalonia.UnitTests renderInterface: new MockPlatformRenderInterface(), standardCursorFactory: Mock.Of(), styler: new Styler(), - theme: () => CreateDefaultTheme(), + theme: () => CreateSimpleTheme(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), fontManagerImpl: new MockFontManagerImpl(), textShaperImpl: new MockTextShaperImpl(), @@ -169,7 +169,7 @@ namespace Avalonia.UnitTests windowImpl: windowImpl ?? WindowImpl); } - private static IStyle CreateDefaultTheme() + private static IStyle CreateSimpleTheme() { return new SimpleTheme { Mode = SimpleThemeMode.Light }; } From 687b5f9ea0a8cdba122e7e983f379cfad713366c Mon Sep 17 00:00:00 2001 From: robloo Date: Wed, 3 Aug 2022 00:28:53 -0400 Subject: [PATCH 92/97] Fix comment for TransitioningContentControl which uses whatever default theme is loaded Co-authored-by: Max Katz --- src/Avalonia.ReactiveUI/TransitioningContentControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs index 2bbf87590e..d26e90b2da 100644 --- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs +++ b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs @@ -57,7 +57,7 @@ namespace Avalonia.ReactiveUI /// /// TransitioningContentControl uses the default ContentControl - /// template from Avalonia simple theme. + /// template from Avalonia default theme. /// Type IStyleable.StyleKey => typeof(ContentControl); From ce966f72e3fcd5474db30f2a91ba7660c5f65896 Mon Sep 17 00:00:00 2001 From: robloo Date: Wed, 3 Aug 2022 00:29:37 -0400 Subject: [PATCH 93/97] Fix comment which is correct as "default theme" Co-authored-by: Max Katz --- tests/Avalonia.LeakTests/ControlTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 2a77bd55fc..a7474dc92e 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -313,7 +313,7 @@ namespace Avalonia.LeakTests var border = textBox.GetTemplateChildren().FirstOrDefault(x => x.Name == "border"); // The TextBox should have subscriptions to its Classes collection from the - // simple theme. + // default theme. Assert.NotEqual(0, textBox.Classes.ListenerCount); // Clear the content and ensure the TextBox is removed. From 7274083cd1343db196fb5d1d1e9a294c0b66cc5d Mon Sep 17 00:00:00 2001 From: robloo Date: Wed, 3 Aug 2022 00:30:39 -0400 Subject: [PATCH 94/97] Fix comment which is correct as "default theme" Co-authored-by: Max Katz --- src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 4b41ecf328..5915388822 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -327,7 +327,7 @@ namespace Avalonia.Build.Tasks // TODO: Throw an error // This usually happens when the same XAML resource was added twice for some weird reason - // We currently support it for dual-named simple theme resources + // We currently support it for dual-named default theme resources if (op != null && op.Name == TrampolineName) { From 3fd8763e807f195cee6c4313cf6e0ecf44e74aa5 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 3 Aug 2022 13:22:00 +0300 Subject: [PATCH 95/97] Use correct ToggleModifier in TreeView multiselection on MacOS. --- src/Avalonia.Controls/TreeView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index b2a188a2ea..7359f3cade 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -529,7 +529,7 @@ namespace Avalonia.Controls e.Source, true, e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), - e.KeyModifiers.HasAllFlags(KeyModifiers.Control), + e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService().CommandModifiers), point.Properties.IsRightButtonPressed); } } From a30d73eb5ed9433cff2d212b21fb9eae93e7b54b Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 3 Aug 2022 14:38:45 +0300 Subject: [PATCH 96/97] Fix tests. --- .../TreeViewTests.cs | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index d784caf2db..9cf21423a3 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -18,6 +18,7 @@ using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; using JetBrains.Annotations; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -885,28 +886,31 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - var tree = CreateTestTreeData(); - var target = new TreeView + using (UnitTestApplication.Start()) { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + var visualRoot = new TestRoot(); + visualRoot.Child = target; - CreateNodeDataTemplate(target); - ApplyTemplates(target); - target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); - target.SelectAll(); + CreateNodeDataTemplate(target); + ApplyTemplates(target); + target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); + target.SelectAll(); - AssertChildrenSelected(target, tree[0]); - Assert.Equal(5, target.SelectedItems.Count); + AssertChildrenSelected(target, tree[0]); + Assert.Equal(5, target.SelectedItems.Count); - _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); + _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); - Assert.Equal(5, target.SelectedItems.Count); + Assert.Equal(5, target.SelectedItems.Count); + } } [Fact] From fffdcfcc2a8c49f16d9f1c363c774e56e20d685b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 3 Aug 2022 14:05:55 +0200 Subject: [PATCH 97/97] Fix fluent ButtonSpinner theme. The button was greying out its borders on pointer-over. --- .../Controls/ButtonSpinner.xaml | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml index 855dc5363e..aa55065f6d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml @@ -41,13 +41,31 @@ M0,9 L10,0 20,9 19,10 10,2 1,10 z M0,1 L10,10 20,1 19,0 10,8 1,0 z - - + - + + + + + + + + + @@ -83,7 +101,6 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}" - CornerRadius="0" VerticalAlignment="Stretch" VerticalContentAlignment="Center" Foreground="{TemplateBinding Foreground}" @@ -99,7 +116,6 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}" - CornerRadius="0" VerticalAlignment="Stretch" VerticalContentAlignment="Center" Foreground="{TemplateBinding Foreground}"