Browse Source

Merge branch 'master' into remove_header

pull/9167/head
Dong Bin 4 years ago
committed by GitHub
parent
commit
00a2105223
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .editorconfig
  2. 4
      NOTICE.md
  3. 12
      samples/ControlCatalog.Android/MainActivity.cs
  4. 13
      samples/ControlCatalog.Android/SplashActivity.cs
  5. 4
      samples/MobileSandbox.Android/MainActivity.cs
  6. 4
      samples/MobileSandbox.Android/SplashActivity.cs
  7. 105
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  8. 72
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  9. 34
      src/Android/Avalonia.Android/AvaloniaSplashActivity.cs
  10. 11
      src/Android/Avalonia.Android/AvaloniaViewModel.cs
  11. 6
      src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
  12. 11
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  13. 4
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
  14. 26
      src/Android/Avalonia.Android/SingleViewLifetime.cs
  15. 2
      src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs
  16. 5
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  17. 2
      src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
  18. 91
      src/Avalonia.Controls/TextBox.cs
  19. 2
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  20. 20
      src/Avalonia.Controls/TextChangedEventArgs.cs
  21. 20
      src/Avalonia.Controls/TextChangingEventArgs.cs
  22. 2
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs
  23. 6
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  24. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf
  25. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf
  26. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf
  27. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf
  28. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf
  29. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf
  30. BIN
      src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf
  31. 2
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  32. 4
      src/Windows/Avalonia.Win32/Win32Platform.cs
  33. 2
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  34. 2
      tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs

5
.editorconfig

@ -137,6 +137,11 @@ space_within_single_line_array_initializer_braces = true
#Net Analyzer #Net Analyzer
dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed. dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed.
# CA1802: Use literals where appropriate
dotnet_diagnostic.CA1802.severity = warning
# CA1825: Avoid zero-length array allocations
dotnet_diagnostic.CA1825.severity = warning
# Wrapping preferences # Wrapping preferences
csharp_wrap_before_ternary_opsigns = false csharp_wrap_before_ternary_opsigns = false

4
NOTICE.md

@ -111,7 +111,7 @@ DEALINGS IN THE SOFTWARE.
# Metsys.Bson # Metsys.Bson
Copyright (c) 2010, Karl Seguin - http://www.openmymind.net/ Copyright (c) 2010, Karl Seguin - https://www.openmymind.net/
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@ -302,4 +302,4 @@ https://github.com/chromium/chromium
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

12
samples/ControlCatalog.Android/MainActivity.cs

@ -5,16 +5,8 @@ using Avalonia.Android;
namespace ControlCatalog.Android namespace ControlCatalog.Android
{ {
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
public class MainActivity : AvaloniaActivity<App> public class MainActivity : AvaloniaMainActivity
{ {
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
});
}
} }
} }

13
samples/ControlCatalog.Android/SplashActivity.cs

@ -1,12 +1,23 @@
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Content.PM;
using Android.OS; using Android.OS;
using Avalonia.Android;
namespace ControlCatalog.Android namespace ControlCatalog.Android
{ {
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : Activity public class SplashActivity : AvaloniaSplashActivity<App>
{ {
protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
});
}
protected override void OnCreate(Bundle? savedInstanceState) protected override void OnCreate(Bundle? savedInstanceState)
{ {
base.OnCreate(savedInstanceState); base.OnCreate(savedInstanceState);

4
samples/MobileSandbox.Android/MainActivity.cs

@ -5,8 +5,8 @@ using Avalonia.Android;
namespace MobileSandbox.Android namespace MobileSandbox.Android
{ {
[Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
public class MainActivity : AvaloniaActivity<App> public class MainActivity : AvaloniaMainActivity
{ {
} }
} }

4
samples/MobileSandbox.Android/SplashActivity.cs

@ -1,11 +1,11 @@
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS; using Avalonia.Android;
namespace MobileSandbox.Android namespace MobileSandbox.Android
{ {
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : Activity public class SplashActivity : AvaloniaSplashActivity<App>
{ {
protected override void OnResume() protected override void OnResume()
{ {

105
src/Android/Avalonia.Android/AvaloniaActivity.cs

@ -1,105 +0,0 @@
using Android.OS;
using AndroidX.AppCompat.App;
using Android.Content.Res;
using AndroidX.Lifecycle;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls;
using Android.Runtime;
using Android.App;
using Android.Content;
using System;
namespace Avalonia.Android
{
public abstract class AvaloniaActivity : AppCompatActivity
{
internal class SingleViewLifetime : ISingleViewApplicationLifetime
{
public AvaloniaView View { get; internal set; }
public Control MainView
{
get => (Control)View.Content;
set => View.Content = value;
}
}
internal Action<int, Result, Intent> ActivityResult;
internal AvaloniaView View;
internal AvaloniaViewModel _viewModel;
protected abstract AppBuilder CreateAppBuilder();
protected override void OnCreate(Bundle savedInstanceState)
{
var builder = CreateAppBuilder();
var lifetime = new SingleViewLifetime();
builder.AfterSetup(x =>
{
_viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel;
View = new AvaloniaView(this);
if (_viewModel.Content != null)
{
View.Content = _viewModel.Content;
}
SetContentView(View);
lifetime.View = View;
View.Prepare();
});
builder.SetupWithLifetime(lifetime);
base.OnCreate(savedInstanceState);
}
public object Content
{
get
{
return _viewModel.Content;
}
set
{
_viewModel.Content = value;
if (View != null)
View.Content = value;
}
}
public override void OnConfigurationChanged(Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
}
protected override void OnDestroy()
{
View.Content = null;
base.OnDestroy();
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
ActivityResult?.Invoke(requestCode, resultCode, data);
}
}
public abstract class AvaloniaActivity<TApp> : AvaloniaActivity where TApp : Application, new()
{
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
protected override AppBuilder CreateAppBuilder()
{
var builder = AppBuilder.Configure<TApp>();
return CustomizeAppBuilder(builder);
}
}
}

72
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -0,0 +1,72 @@
using System;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using AndroidX.AppCompat.App;
using AndroidX.Lifecycle;
namespace Avalonia.Android
{
public abstract class AvaloniaMainActivity : AppCompatActivity
{
internal static object ViewContent;
internal Action<int, Result, Intent> ActivityResult;
internal AvaloniaView View;
protected override void OnCreate(Bundle savedInstanceState)
{
View = new AvaloniaView(this);
if (ViewContent != null)
{
View.Content = ViewContent;
}
View.Prepare();
if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime)
{
lifetime.View = View;
}
base.OnCreate(savedInstanceState);
SetContentView(View);
}
public object Content
{
get
{
return ViewContent;
}
set
{
ViewContent = value;
if (View != null)
View.Content = value;
}
}
public override void OnConfigurationChanged(Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
}
protected override void OnDestroy()
{
View.Content = null;
base.OnDestroy();
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
ActivityResult?.Invoke(requestCode, resultCode, data);
}
}
}

34
src/Android/Avalonia.Android/AvaloniaSplashActivity.cs

@ -0,0 +1,34 @@
using Android.OS;
using AndroidX.AppCompat.App;
using AndroidX.Lifecycle;
namespace Avalonia.Android
{
public abstract class AvaloniaSplashActivity : AppCompatActivity
{
protected abstract AppBuilder CreateAppBuilder();
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
var builder = CreateAppBuilder();
var lifetime = new SingleViewLifetime();
builder.SetupWithLifetime(lifetime);
}
}
public abstract class AvaloniaSplashActivity<TApp> : AvaloniaSplashActivity where TApp : Application, new()
{
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
protected override AppBuilder CreateAppBuilder()
{
var builder = AppBuilder.Configure<TApp>();
return CustomizeAppBuilder(builder);
}
}
}

11
src/Android/Avalonia.Android/AvaloniaViewModel.cs

@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Android
{
internal class AvaloniaViewModel : AndroidX.Lifecycle.ViewModel
{
public object Content { get; set; }
}
}

6
src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs

@ -1,4 +1,5 @@
using Avalonia.OpenGL.Egl; using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces; using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL namespace Avalonia.Android.OpenGL
@ -19,7 +20,8 @@ namespace Avalonia.Android.OpenGL
public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info) public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info)
{ {
if (EglPlatformOpenGlInterface.TryCreate() is EglPlatformOpenGlInterface egl) var feature = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
if (feature is EglPlatformOpenGlInterface egl)
{ {
return new GlPlatformSurface(egl, info); return new GlPlatformSurface(egl, info);
} }

11
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -3,20 +3,14 @@ using System.Collections.Generic;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.Media.TV;
using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Text;
using Android.Views; using Android.Views;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using Android.Widget;
using Avalonia.Android.OpenGL; using Avalonia.Android.OpenGL;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Android.Platform.Storage; using Avalonia.Android.Platform.Storage;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input; using Avalonia.Input;
@ -29,7 +23,6 @@ using Avalonia.Platform.Storage;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Java.Lang; using Java.Lang;
using static System.Net.Mime.MediaTypeNames;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
{ {
@ -59,7 +52,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
StorageProvider = new AndroidStorageProvider((AvaloniaActivity)avaloniaView.Context); StorageProvider = new AndroidStorageProvider((AvaloniaMainActivity)avaloniaView.Context);
} }
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@ -301,7 +294,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
IsComposing = true; IsComposing = true;
_inputMethod.Client.SetPreeditText(ComposingText); _inputMethod.Client?.SetPreeditText(ComposingText);
return base.SetComposingText(text, newCursorPosition); return base.SetComposingText(text, newCursorPosition);
} }

4
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs

@ -14,10 +14,10 @@ namespace Avalonia.Android.Platform.Storage;
internal class AndroidStorageProvider : IStorageProvider internal class AndroidStorageProvider : IStorageProvider
{ {
private readonly AvaloniaActivity _activity; private readonly AvaloniaMainActivity _activity;
private int _lastRequestCode = 20000; private int _lastRequestCode = 20000;
public AndroidStorageProvider(AvaloniaActivity activity) public AndroidStorageProvider(AvaloniaMainActivity activity)
{ {
_activity = activity; _activity = activity;
} }

26
src/Android/Avalonia.Android/SingleViewLifetime.cs

@ -0,0 +1,26 @@
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android
{
internal class SingleViewLifetime : ISingleViewApplicationLifetime
{
private AvaloniaView _view;
public AvaloniaView View
{
get => _view; internal set
{
if (_view != null)
{
_view.Content = null;
_view.Dispose();
}
_view = value;
_view.Content = MainView;
}
}
public Control MainView { get; set; }
}
}

2
src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs

@ -2,6 +2,6 @@ namespace Avalonia.Media.TextFormatting.Unicode
{ {
internal static class GraphemeBreak internal static class GraphemeBreak
{ {
public static byte[] Data => new byte[0]; public static byte[] Data => System.Array.Empty<byte>();
} }
} }

5
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -9,6 +9,11 @@
<NoWarn>NU1605;CS8632</NoWarn> <NoWarn>NU1605;CS8632</NoWarn>
</PropertyGroup> </PropertyGroup>
<!--Disable Net Perf. analyzer for submodule to avoid commit issue -->
<PropertyGroup>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="../Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs"> <Compile Include="../Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs">
<Link>Shared/AvaloniaResourcesIndex.cs</Link> <Link>Shared/AvaloniaResourcesIndex.cs</Link>

2
src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs

@ -49,7 +49,7 @@ namespace Avalonia.Controls.Primitives
{ {
if (width == 0 || height == 0) if (width == 0 || height == 0)
{ {
return new byte[0]; return Array.Empty<byte>();
} }
var bitmap = await Task.Run<byte[]>(() => var bitmap = await Task.Run<byte[]>(() =>

91
src/Avalonia.Controls/TextBox.cs

@ -18,6 +18,7 @@ using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using System.Diagnostics; using System.Diagnostics;
using Avalonia.Threading;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -159,18 +160,41 @@ namespace Avalonia.Controls
(o, v) => o.UndoLimit = v, (o, v) => o.UndoLimit = v,
unsetValue: -1); unsetValue: -1);
/// <summary>
/// Defines the <see cref="CopyingToClipboard"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> CopyingToClipboardEvent = public static readonly RoutedEvent<RoutedEventArgs> CopyingToClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>( RoutedEvent.Register<TextBox, RoutedEventArgs>(
nameof(CopyingToClipboard), RoutingStrategies.Bubble); nameof(CopyingToClipboard), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="CuttingToClipboard"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> CuttingToClipboardEvent = public static readonly RoutedEvent<RoutedEventArgs> CuttingToClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>( RoutedEvent.Register<TextBox, RoutedEventArgs>(
nameof(CuttingToClipboard), RoutingStrategies.Bubble); nameof(CuttingToClipboard), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="PastingFromClipboard"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> PastingFromClipboardEvent = public static readonly RoutedEvent<RoutedEventArgs> PastingFromClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>( RoutedEvent.Register<TextBox, RoutedEventArgs>(
nameof(PastingFromClipboard), RoutingStrategies.Bubble); nameof(PastingFromClipboard), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="TextChanged"/> event.
/// </summary>
public static readonly RoutedEvent<TextChangedEventArgs> TextChangedEvent =
RoutedEvent.Register<TextBox, TextChangedEventArgs>(
nameof(TextChanged), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="TextChanging"/> event.
/// </summary>
public static readonly RoutedEvent<TextChangingEventArgs> TextChangingEvent =
RoutedEvent.Register<TextBox, TextChangingEventArgs>(
nameof(TextChanging), RoutingStrategies.Bubble);
readonly struct UndoRedoState : IEquatable<UndoRedoState> readonly struct UndoRedoState : IEquatable<UndoRedoState>
{ {
public string? Text { get; } public string? Text { get; }
@ -359,8 +383,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public double LineHeight public double LineHeight
{ {
get { return GetValue(LineHeightProperty); } get => GetValue(LineHeightProperty);
set { SetValue(LineHeightProperty, value); } set => SetValue(LineHeightProperty, value);
} }
[Content] [Content]
@ -376,11 +400,19 @@ namespace Avalonia.Controls
CaretIndex = CoerceCaretIndex(caretIndex, value); CaretIndex = CoerceCaretIndex(caretIndex, value);
SelectionStart = CoerceCaretIndex(selectionStart, value); SelectionStart = CoerceCaretIndex(selectionStart, value);
SelectionEnd = CoerceCaretIndex(selectionEnd, value); SelectionEnd = CoerceCaretIndex(selectionEnd, value);
if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing)
var textChanged = SetAndRaise(TextProperty, ref _text, value);
if (textChanged && IsUndoEnabled && !_isUndoingRedoing)
{ {
_undoRedoHelper.Clear(); _undoRedoHelper.Clear();
SnapshotUndoRedo(); // so we always have an initial state SnapshotUndoRedo(); // so we always have an initial state
} }
if (textChanged)
{
RaiseTextChangeEvents();
}
} }
} }
@ -564,6 +596,27 @@ namespace Avalonia.Controls
remove => RemoveHandler(PastingFromClipboardEvent, value); remove => RemoveHandler(PastingFromClipboardEvent, value);
} }
/// <summary>
/// Occurs asynchronously after text changes and the new text is rendered.
/// </summary>
public event EventHandler<TextChangedEventArgs>? TextChanged
{
add => AddHandler(TextChangedEvent, value);
remove => RemoveHandler(TextChangedEvent, value);
}
/// <summary>
/// Occurs synchronously when text starts to change but before it is rendered.
/// </summary>
/// <remarks>
/// This event occurs just after the <see cref="Text"/> property value has been updated.
/// </remarks>
public event EventHandler<TextChangingEventArgs>? TextChanging
{
add => AddHandler(TextChangingEvent, value);
remove => RemoveHandler(TextChangingEvent, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter"); _presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
@ -1252,7 +1305,7 @@ namespace Avalonia.Controls
if (text != null && _wordSelectionStart >= 0) if (text != null && _wordSelectionStart >= 0)
{ {
var distance = caretIndex - _wordSelectionStart; var distance = caretIndex - _wordSelectionStart;
if (distance <= 0) if (distance <= 0)
{ {
@ -1539,11 +1592,39 @@ namespace Avalonia.Controls
return text.Substring(start, end - start); return text.Substring(start, end - start);
} }
/// <summary>
/// Raises both the <see cref="TextChanging"/> and <see cref="TextChanged"/> events.
/// </summary>
/// <remarks>
/// This must be called after the <see cref="Text"/> property is set.
/// </remarks>
private void RaiseTextChangeEvents()
{
// Note the following sequence of these events (following WinUI)
// 1. TextChanging occurs synchronously when text starts to change but before it is rendered.
// This occurs after the Text property is set.
// 2. TextChanged occurs asynchronously after text changes and the new text is rendered.
var textChangingEventArgs = new TextChangingEventArgs(TextChangingEvent);
RaiseEvent(textChangingEventArgs);
Dispatcher.UIThread.Post(() =>
{
var textChangedEventArgs = new TextChangedEventArgs(TextChangedEvent);
RaiseEvent(textChangedEventArgs);
}, DispatcherPriority.Normal);
}
private void SetTextInternal(string value, bool raiseTextChanged = true) private void SetTextInternal(string value, bool raiseTextChanged = true)
{ {
if (raiseTextChanged) if (raiseTextChanged)
{ {
SetAndRaise(TextProperty, ref _text, value); bool textChanged = SetAndRaise(TextProperty, ref _text, value);
if (textChanged)
{
RaiseTextChangeEvents();
}
} }
else else
{ {

2
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -64,7 +64,7 @@ namespace Avalonia.Controls
return new TextInputMethodSurroundingText return new TextInputMethodSurroundingText
{ {
Text = lineText ?? "", Text = lineText ?? "",
AnchorOffset = anchorOffset, AnchorOffset = anchorOffset,
CursorOffset = cursorOffset CursorOffset = cursorOffset
}; };

20
src/Avalonia.Controls/TextChangedEventArgs.cs

@ -0,0 +1,20 @@
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data specific to a TextChanged event.
/// </summary>
public class TextChangedEventArgs : RoutedEventArgs
{
public TextChangedEventArgs(RoutedEvent? routedEvent)
: base (routedEvent)
{
}
public TextChangedEventArgs(RoutedEvent? routedEvent, IInteractive? source)
: base(routedEvent, source)
{
}
}
}

20
src/Avalonia.Controls/TextChangingEventArgs.cs

@ -0,0 +1,20 @@
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data specific to a TextChanging event.
/// </summary>
public class TextChangingEventArgs : RoutedEventArgs
{
public TextChangingEventArgs(RoutedEvent? routedEvent)
: base (routedEvent)
{
}
public TextChangingEventArgs(RoutedEvent? routedEvent, IInteractive? source)
: base(routedEvent, source)
{
}
}
}

2
src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs

@ -140,7 +140,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport
IsWebsocketRequest = true; IsWebsocketRequest = true;
if (headers.TryGetValue("Sec-WebSocket-Protocol", out h)) if (headers.TryGetValue("Sec-WebSocket-Protocol", out h))
WebSocketProtocols = h.Split(',').Select(x => x.Trim()).ToArray(); WebSocketProtocols = h.Split(',').Select(x => x.Trim()).ToArray();
else WebSocketProtocols = new string[0]; else WebSocketProtocols = Array.Empty<string>();
} }
} }

6
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

@ -340,7 +340,7 @@ namespace Avalonia.FreeDesktop
{ {
var id = item == null ? 0 : GetId(item); var id = item == null ? 0 : GetId(item);
var props = GetProperties((item, menu), propertyNames); var props = GetProperties((item, menu), propertyNames);
var children = (depth == 0 || menu == null) ? new object[0] : new object[menu.Items.Count]; var children = (depth == 0 || menu == null) ? Array.Empty<object>() : new object[menu.Items.Count];
if(menu != null) if(menu != null)
for (var c = 0; c < children.Length; c++) for (var c = 0; c < children.Length; c++)
{ {
@ -397,7 +397,7 @@ namespace Avalonia.FreeDesktop
{ {
foreach (var e in Events) foreach (var e in Events)
HandleEvent(e.id, e.eventId, e.data, e.timestamp); HandleEvent(e.id, e.eventId, e.data, e.timestamp);
return Task.FromResult(new int[0]); return Task.FromResult(Array.Empty<int>());
} }
public async Task<bool> AboutToShowAsync(int Id) public async Task<bool> AboutToShowAsync(int Id)
@ -407,7 +407,7 @@ namespace Avalonia.FreeDesktop
public async Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids) public async Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids)
{ {
return (new int[0], new int[0]); return (Array.Empty<int>(), Array.Empty<int>());
} }
#region Events #region Events

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf

Binary file not shown.

BIN
src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf

Binary file not shown.

2
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs

@ -99,7 +99,7 @@ namespace Avalonia.LinuxFramebuffer.Output
// prepare for the new ioctl call // prepare for the new ioctl call
var handles = new uint[] {handle, 0, 0, 0}; var handles = new uint[] {handle, 0, 0, 0};
var pitches = new uint[] {stride, 0, 0, 0}; var pitches = new uint[] {stride, 0, 0, 0};
var offsets = new uint[] {}; var offsets = Array.Empty<uint>();
var ret = drmModeAddFB2(_card.Fd, w, h, format, handles, pitches, var ret = drmModeAddFB2(_card.Fd, w, h, format, handles, pitches,
offsets, out var fbHandle, 0); offsets, out var fbHandle, 0);

4
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -241,8 +241,8 @@ namespace Avalonia.Win32
}); });
} }
private static readonly int SignalW = unchecked((int) 0xdeadbeaf); private const int SignalW = unchecked((int)0xdeadbeaf);
private static readonly int SignalL = unchecked((int)0x12345678); private const int SignalL = unchecked((int)0x12345678);
public void Signal(DispatcherPriority prio) public void Signal(DispatcherPriority prio)
{ {

2
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@ -235,7 +235,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
public bool FillContains(Point point) public bool FillContains(Point point)
{ {
// Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation) // to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++) for (int i = 0; i < points.Count; i++)
{ {

2
tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs

@ -148,7 +148,7 @@ namespace Avalonia.UnitTests
public bool FillContains(Point point) public bool FillContains(Point point)
{ {
// Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation) // to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++) for (int i = 0; i < points.Count; i++)
{ {

Loading…
Cancel
Save