Skip to content

Commit

Permalink
Merge branch 'master' into fixes/textAlignmentJustify
Browse files Browse the repository at this point in the history
  • Loading branch information
Gillibald authored Jan 30, 2025
2 parents 304a688 + 3e3f11d commit 3b0de4c
Show file tree
Hide file tree
Showing 37 changed files with 1,112 additions and 75 deletions.
12 changes: 12 additions & 0 deletions api/Avalonia.nupkg.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left>
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.Storage.IStorageFolder.GetFileAsync(System.String)</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.Storage.IStorageFolder.GetFolderAsync(System.String)</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Controls.Notifications.IManagedNotificationManager.Close(System.Object)</Target>
Expand Down
2 changes: 1 addition & 1 deletion packages/Avalonia/Avalonia.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Avalonia.BuildServices" Version="0.0.29" />
<PackageReference Include="Avalonia.BuildServices" Version="0.0.31" />
<ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj"
PrivateAssets="all" />
Expand Down
1 change: 1 addition & 0 deletions samples/ControlCatalog/Pages/TextBoxPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
UseFloatingWatermark="True"
PasswordChar="*"
Text="Password" />
<TextBox Width="200" Watermark="Suggestions are hidden" TextInputOptions.ShowSuggestions="False" />
<TextBox Width="200" Text="Left aligned text" TextAlignment="Left" AcceptsTab="True" />
<TextBox Width="200" Text="Center aligned text" TextAlignment="Center" />
<TextBox Width="200" Text="Right aligned text" TextAlignment="Right" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ public void SetOptions(TextInputOptions options)
if (options.Multiline)
outAttrs.InputType |= InputTypes.TextFlagMultiLine;

if (outAttrs.InputType is InputTypes.ClassText && options.ShowSuggestions == false)
outAttrs.InputType |= InputTypes.TextVariationPassword | InputTypes.TextFlagNoSuggestions;

outAttrs.ImeOptions = options.ReturnKeyType switch
{
TextInputReturnKeyType.Return => ImeFlags.NoEnterAction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,79 @@ public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
return destination;
}
}

private async Task<IStorageItem?> GetItemAsync(string name, bool isDirectory)
{
if (!await EnsureExternalFilesPermission(false))
{
return null;
}

var contentResolver = Activity.ContentResolver;
if (contentResolver == null)
{
return null;
}

var root = PermissionRoot ?? Uri;
var folderId = root != Uri ? DocumentsContract.GetDocumentId(Uri) : DocumentsContract.GetTreeDocumentId(Uri);
var childrenUri = DocumentsContract.BuildChildDocumentsUriUsingTree(root, folderId);

var projection = new[]
{
DocumentsContract.Document.ColumnDocumentId,
DocumentsContract.Document.ColumnMimeType,
DocumentsContract.Document.ColumnDisplayName
};

if (childrenUri != null)
{
using var cursor = contentResolver.Query(childrenUri, projection, null, null, null);
if (cursor != null)
{
while (cursor.MoveToNext())
{
var id = cursor.GetString(0);
var mime = cursor.GetString(1);

var fileName = cursor.GetString(2);
if (fileName != name)
{
continue;
}

bool mineDirectory = mime == DocumentsContract.Document.MimeTypeDir;
if (isDirectory != mineDirectory)
{
return null;
}

var uri = DocumentsContract.BuildDocumentUriUsingTree(root, id);
if (uri == null)
{
return null;
}

return isDirectory ? new AndroidStorageFolder(Activity, uri, false, this, root) :
new AndroidStorageFile(Activity, uri, this, root);
}
}
}

return null;
}

public async Task<IStorageFolder?> GetFolderAsync(string name)
{
var folder = await GetItemAsync(name, true);
return (IStorageFolder?)folder;
}

public async Task<IStorageFile?> GetFileAsync(string name)
{
var file = await GetItemAsync(name, false);
return (IStorageFile?)file;
}
}

internal sealed class WellKnownAndroidStorageFolder : AndroidStorageFolder
Expand Down
15 changes: 15 additions & 0 deletions src/Avalonia.Base/Collections/AvaloniaDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ public AvaloniaDictionary(int capacity)
_inner = new Dictionary<TKey, TValue>(capacity);
}

/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaDictionary{TKey, TValue}"/> class using an IDictionary.
/// </summary>
public AvaloniaDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey>? comparer = null)
{
if (dictionary != null)
{
_inner = new Dictionary<TKey, TValue>(dictionary, comparer ?? EqualityComparer<TKey>.Default);
}
else
{
throw new ArgumentNullException(nameof(dictionary));
}
}

/// <summary>
/// Occurs when the collection changes.
/// </summary>
Expand Down
36 changes: 35 additions & 1 deletion src/Avalonia.Base/Input/TextInput/TextInputOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public static TextInputOptions FromStyledElement(StyledElement avaloniaObject)
AutoCapitalization = GetAutoCapitalization(avaloniaObject),
IsSensitive = GetIsSensitive(avaloniaObject),
Lowercase = GetLowercase(avaloniaObject),
Uppercase = GetUppercase(avaloniaObject)
Uppercase = GetUppercase(avaloniaObject),
ShowSuggestions = GetShowSuggestions(avaloniaObject),
};

return result;
Expand Down Expand Up @@ -253,4 +254,37 @@ public static bool GetIsSensitive(StyledElement avaloniaObject)
/// Text contains sensitive data like card numbers and should not be stored
/// </summary>
public bool IsSensitive { get; set; }

/// <summary>
/// Defines the <see cref="ShowSuggestions"/> property.
/// </summary>
public static readonly AttachedProperty<bool?> ShowSuggestionsProperty =
AvaloniaProperty.RegisterAttached<TextInputOptions, StyledElement, bool?>(
"ShowSuggestions",
inherits: true);

/// <summary>
/// Sets the value of the attached <see cref="ShowSuggestionsProperty"/> on a control.
/// </summary>
/// <param name="avaloniaObject">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetShowSuggestions(StyledElement avaloniaObject, bool? value)
{
avaloniaObject.SetValue(ShowSuggestionsProperty, value);
}

/// <summary>
/// Gets the value of the attached <see cref="ShowSuggestionsProperty"/>.
/// </summary>
/// <param name="avaloniaObject">The target.</param>
/// <returns>true if ShowSuggestions</returns>
public static bool? GetShowSuggestions(StyledElement avaloniaObject)
{
return avaloniaObject.GetValue(ShowSuggestionsProperty);
}

/// <summary>
/// Show virtual keyboard suggestions
/// </summary>
public bool? ShowSuggestions { get; set; }
}
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Media/PathGeometry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static PathGeometry()
/// </summary>
public PathGeometry()
{
_figures = new PathFigures();
Figures = new PathFigures();
}

/// <summary>
Expand Down
27 changes: 3 additions & 24 deletions src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -951,35 +951,14 @@ private static void ResetTrailingWhitespaceBidiLevels(RentedList<TextRun> lineTe
return;
}

var textSpan = shapedText.Text.Span;
var trailingWhitespaceLength = shapedText.GlyphRun.Metrics.TrailingWhitespaceLength;

if (textSpan.IsEmpty)
if (trailingWhitespaceLength == 0)
{
return;
}

var whitespaceCharactersCount = 0;

for (var i = textSpan.Length - 1; i >= 0; i--)
{
var isWhitespace = Codepoint.ReadAt(textSpan, i, out _).IsWhiteSpace;

if (isWhitespace)
{
whitespaceCharactersCount++;
}
else
{
break;
}
}

if (whitespaceCharactersCount == 0)
{
return;
}

var splitIndex = shapedText.Length - whitespaceCharactersCount;
var splitIndex = shapedText.Length - trailingWhitespaceLength;

var (textRuns, trailingWhitespaceRuns) = SplitTextRuns([shapedText], splitIndex, objectPool);

Expand Down
6 changes: 6 additions & 0 deletions src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ public IAsyncEnumerable<IStorageItem> GetItemsAsync() => GetItemsCore(directoryI

public Task<IStorageFolder?> CreateFolderAsync(string name) => Task.FromResult(
(IStorageFolder?)WrapFileSystemInfo(CreateFolderCore(directoryInfo, name)));

public Task<IStorageFolder?> GetFolderAsync(string name) => Task.FromResult(
(IStorageFolder?)WrapFileSystemInfo(GetFolderCore(directoryInfo, name)));

public Task<IStorageFile?> GetFileAsync(string name) => Task.FromResult(
(IStorageFile?)WrapFileSystemInfo(GetFileCore(directoryInfo, name)));
}
22 changes: 22 additions & 0 deletions src/Avalonia.Base/Platform/Storage/FileIO/BclStorageItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,28 @@ internal static IEnumerable<FileSystemInfo> GetItemsCore(DirectoryInfo directory
.OfType<FileSystemInfo>()
.Concat(directoryInfo.EnumerateFiles());

internal static FileSystemInfo? GetFolderCore(DirectoryInfo directoryInfo, string name)
{
var path = System.IO.Path.Combine(directoryInfo.FullName, name);
if (Directory.Exists(path))
{
return new DirectoryInfo(path);
}

return null;
}

internal static FileSystemInfo? GetFileCore(DirectoryInfo directoryInfo, string name)
{
var path = System.IO.Path.Combine(directoryInfo.FullName, name);
if (File.Exists(path))
{
return new FileInfo(path);
}

return null;
}

internal static FileInfo CreateFileCore(DirectoryInfo directoryInfo, string name)
{
var fileName = System.IO.Path.Combine(directoryInfo.FullName, name);
Expand Down
18 changes: 18 additions & 0 deletions src/Avalonia.Base/Platform/Storage/IStorageFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@ public interface IStorageFolder : IStorageItem
/// </returns>
IAsyncEnumerable<IStorageItem> GetItemsAsync();

/// <summary>
/// Gets the folder with the specified name from the current folder.
/// </summary>
/// <param name="name">The name of the folder to get</param>
/// <returns>
/// When this method completes successfully, it returns the folder with the specified name from the current folder.
/// </returns>
Task<IStorageFolder?> GetFolderAsync(string name);

/// <summary>
/// Gets the file with the specified name from the current folder.
/// </summary>
/// <param name="name">The name of the file to get</param>
/// <returns>
/// When this method completes successfully, it returns the file with the specified name from the current folder.
/// </returns>
Task<IStorageFile?> GetFileAsync(string name);

/// <summary>
/// Creates a file with specified name as a child of the current storage folder
/// </summary>
Expand Down
10 changes: 8 additions & 2 deletions src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,16 @@ void IManagedPopupPositionerPopup.MoveAndResize(Point devicePoint, Size virtualS
// TODO12: mark PrivateAPI or internal.
[Unstable("PopupHost is considered an internal API. Use Popup or any Popup-based controls (Flyout, Tooltip) instead.")]
public static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver)
=> CreatePopupHost(target, dependencyResolver, false);

internal static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver, bool shouldUseOverlayLayer)
{
if (TopLevel.GetTopLevel(target) is { } topLevel && topLevel.PlatformImpl?.CreatePopup() is { } popupImpl)
if (!shouldUseOverlayLayer)
{
return new PopupRoot(topLevel, popupImpl, dependencyResolver);
if (TopLevel.GetTopLevel(target) is { } topLevel && topLevel.PlatformImpl?.CreatePopup() is { } popupImpl)
{
return new PopupRoot(topLevel, popupImpl, dependencyResolver);
}
}

if (OverlayLayer.GetOverlayLayer(target) is { } overlayLayer)
Expand Down
39 changes: 38 additions & 1 deletion src/Avalonia.Controls/Primitives/Popup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,21 @@ public class Popup : Control, IPopupHostProvider
public static readonly AttachedProperty<bool> TakesFocusFromNativeControlProperty =
AvaloniaProperty.RegisterAttached<Popup, Control, bool>(nameof(TakesFocusFromNativeControl), true);

/// <summary>
/// Defines the <see cref="ShouldUseOverlayLayer"/> property.
/// </summary>
public static readonly StyledProperty<bool> ShouldUseOverlayLayerProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(ShouldUseOverlayLayer));

/// <summary>
/// Defines the <see cref="IsUsingOverlayLayer"/> property.
/// </summary>
public static readonly DirectProperty<Popup, bool> IsUsingOverlayLayerProperty = AvaloniaProperty.RegisterDirect<Popup, bool>(
nameof(IsUsingOverlayLayer), o => o.IsUsingOverlayLayer);

private bool _isOpenRequested;
private bool _ignoreIsOpenChanged;
private bool _isUsingOverlayLayer;
private PopupOpenState? _openState;
private Action<IPopupHost?>? _popupHostChangedHandler;

Expand Down Expand Up @@ -386,6 +399,29 @@ public bool TakesFocusFromNativeControl
set => SetValue(TakesFocusFromNativeControlProperty, value);
}

/// <summary>
/// Gets or sets a value that indicates whether the popup should be shown in the overlay layer of the parent window.
/// </summary>
/// <remarks>
/// When <see cref="ShouldUseOverlayLayer"/> is "false" implementation depends on the platform.
/// Use <see cref="IsUsingOverlayLayer"/> to get actual popup behavior.
/// This is an equvalent of `OverlayPopups` property of the platform options, but settable independently per each popup.
/// </remarks>
public bool ShouldUseOverlayLayer
{
get => GetValue(ShouldUseOverlayLayerProperty);
set => SetValue(ShouldUseOverlayLayerProperty, value);
}

/// <summary>
/// Gets a value that indicates whether the popup is shown in the overlay layer of the parent window.
/// </summary>
public bool IsUsingOverlayLayer
{
get => _isUsingOverlayLayer;
private set => SetAndRaise(IsUsingOverlayLayerProperty, ref _isUsingOverlayLayer, value);
}

IPopupHost? IPopupHostProvider.PopupHost => Host;

event Action<IPopupHost?>? IPopupHostProvider.PopupHostChanged
Expand Down Expand Up @@ -423,7 +459,7 @@ public void Open()

_isOpenRequested = false;

var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver, ShouldUseOverlayLayer);
var handlerCleanup = new CompositeDisposable(7);

UpdateHostSizing(popupHost, topLevel, placementTarget);
Expand Down Expand Up @@ -541,6 +577,7 @@ public void Open()
WindowManagerAddShadowHintChanged(popupHost, WindowManagerAddShadowHint);

popupHost.Show();
IsUsingOverlayLayer = popupHost is OverlayPopupHost;

if (TakesFocusFromNativeControl)
popupHost.TakeFocus();
Expand Down
Loading

0 comments on commit 3b0de4c

Please sign in to comment.