diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index 7e2bbc13bc..097815cc69 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -138,9 +138,19 @@ partial class Build : NukeBuild
.SetWorkingDirectory(webappDir)
.SetCommand("dist"));
});
-
- Target Compile => _ => _
+
+ Target CompileNative => _ => _
.DependsOn(Clean)
+ .OnlyWhenStatic(() => EnvironmentInfo.IsOsx)
+ .Executes(() =>
+ {
+ var project = $"{RootDirectory}/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/";
+ var args = $"-project {project} -configuration {Parameters.Configuration} CONFIGURATION_BUILD_DIR={RootDirectory}/Build/Products/Release";
+ ProcessTasks.StartProcess("xcodebuild", args).AssertZeroExitCode();
+ });
+
+ Target Compile => _ => _
+ .DependsOn(Clean, CompileNative)
.DependsOn(CompileHtmlPreviewer)
.Executes(async () =>
{
diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
index f90a0c4658..a49616e543 100644
--- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
@@ -51,6 +51,11 @@
Width="200"
Margin="0,0,0,8"
FilterMode="None"/>
+
+
diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
index f9d6a72a3a..574cc79a7d 100644
--- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
@@ -92,13 +92,28 @@ namespace ControlCatalog.Pages
}
public StateData[] States { get; private set; }
+ private LinkedList[] BuildAllSentences()
+ {
+ return new string[]
+ {
+ "Hello world",
+ "No this is Patrick",
+ "Never gonna give you up",
+ "How does one patch KDE2 under FreeBSD"
+ }
+ .Select(x => new LinkedList(x.Split(' ')))
+ .ToArray();
+ }
+ public LinkedList[] Sentences { get; private set; }
+
public AutoCompleteBoxPage()
{
this.InitializeComponent();
States = BuildAllStates();
+ Sentences = BuildAllSentences();
- foreach (AutoCompleteBox box in GetAllAutoCompleteBox())
+ foreach (AutoCompleteBox box in GetAllAutoCompleteBox().Where(x => x.Name != "CustomAutocompleteBox"))
{
box.Items = States;
}
@@ -116,6 +131,11 @@ namespace ControlCatalog.Pages
var asyncBox = this.FindControl("AsyncBox");
asyncBox.AsyncPopulator = PopulateAsync;
+
+ var customAutocompleteBox = this.FindControl("CustomAutocompleteBox");
+ customAutocompleteBox.Items = Sentences.SelectMany(x => x);
+ customAutocompleteBox.TextFilter = LastWordContains;
+ customAutocompleteBox.TextSelector = AppendWord;
}
private IEnumerable GetAllAutoCompleteBox()
{
@@ -137,6 +157,42 @@ namespace ControlCatalog.Pages
.ToList();
}
+ private bool LastWordContains(string searchText, string item)
+ {
+ var words = searchText.Split(' ');
+ var options = Sentences.Select(x => x.First).ToArray();
+ for (var i = 0; i < words.Length; ++i)
+ {
+ var word = words[i];
+ for (var j = 0; j < options.Length; ++j)
+ {
+ var option = options[j];
+ if (option == null)
+ continue;
+
+ if (i == words.Length - 1)
+ {
+ options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null;
+ }
+ else
+ {
+ options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null;
+ }
+ }
+ }
+
+ return options.Any(x => x != null && x.Value == item);
+ }
+ private string AppendWord(string text, string item)
+ {
+ string[] parts = text.Split(' ');
+ if (parts.Length == 0)
+ return item;
+
+ parts[parts.Length - 1] = item;
+ return string.Join(" ", parts);
+ }
+
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
index 6c13a5ac22..cb79bf219a 100644
--- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
@@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.OpenGL;
+using Avalonia.OpenGL.Controls;
using Avalonia.Platform.Interop;
using Avalonia.Threading;
using static Avalonia.OpenGL.GlConsts;
diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml
index 770960d7c4..93fbe5e412 100644
--- a/samples/RenderDemo/MainWindow.xaml
+++ b/samples/RenderDemo/MainWindow.xaml
@@ -3,8 +3,8 @@
x:Class="RenderDemo.MainWindow"
Title="AvaloniaUI Rendering Test"
xmlns:pages="clr-namespace:RenderDemo.Pages"
- Width="800"
- Height="600">
+ Width="{Binding Width, Mode=TwoWay}"
+ Height="{Binding Height, Mode=TwoWay}">
diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
index 7f15845596..ddee880288 100644
--- a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
+++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
@@ -61,7 +61,6 @@ namespace RenderDemo.Pages
{
Foreground = Brushes.Black,
GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _glyphIndices),
- BaselineOrigin = new Point(0, -_glyphTypeface.Ascent * scale)
};
drawingGroup.Children.Add(glyphRunDrawing);
@@ -69,7 +68,7 @@ namespace RenderDemo.Pages
var geometryDrawing = new GeometryDrawing
{
Pen = new Pen(Brushes.Black),
- Geometry = new RectangleGeometry { Rect = glyphRunDrawing.GlyphRun.Bounds }
+ Geometry = new RectangleGeometry { Rect = new Rect(glyphRunDrawing.GlyphRun.Size) }
};
drawingGroup.Children.Add(geometryDrawing);
diff --git a/samples/RenderDemo/ViewModels/MainWindowViewModel.cs b/samples/RenderDemo/ViewModels/MainWindowViewModel.cs
index d2d789a687..eda5e80530 100644
--- a/samples/RenderDemo/ViewModels/MainWindowViewModel.cs
+++ b/samples/RenderDemo/ViewModels/MainWindowViewModel.cs
@@ -1,5 +1,6 @@
-using System;
-using System.Reactive;
+using System.Reactive;
+using System.Threading.Tasks;
+
using ReactiveUI;
namespace RenderDemo.ViewModels
@@ -8,26 +9,61 @@ namespace RenderDemo.ViewModels
{
private bool drawDirtyRects = false;
private bool drawFps = true;
+ private double width = 800;
+ private double height = 600;
public MainWindowViewModel()
{
ToggleDrawDirtyRects = ReactiveCommand.Create(() => DrawDirtyRects = !DrawDirtyRects);
ToggleDrawFps = ReactiveCommand.Create(() => DrawFps = !DrawFps);
+ ResizeWindow = ReactiveCommand.CreateFromTask(ResizeWindowAsync);
}
public bool DrawDirtyRects
{
- get { return drawDirtyRects; }
- set { this.RaiseAndSetIfChanged(ref drawDirtyRects, value); }
+ get => drawDirtyRects;
+ set => this.RaiseAndSetIfChanged(ref drawDirtyRects, value);
}
public bool DrawFps
{
- get { return drawFps; }
- set { this.RaiseAndSetIfChanged(ref drawFps, value); }
+ get => drawFps;
+ set => this.RaiseAndSetIfChanged(ref drawFps, value);
+ }
+
+ public double Width
+ {
+ get => width;
+ set => this.RaiseAndSetIfChanged(ref width, value);
+ }
+
+ public double Height
+ {
+ get => height;
+ set => this.RaiseAndSetIfChanged(ref height, value);
}
public ReactiveCommand ToggleDrawDirtyRects { get; }
public ReactiveCommand ToggleDrawFps { get; }
+ public ReactiveCommand ResizeWindow { get; }
+
+ private async Task ResizeWindowAsync()
+ {
+ for (int i = 0; i < 30; i++)
+ {
+ Width += 10;
+ Height += 5;
+ await Task.Delay(10);
+ }
+
+ await Task.Delay(10);
+
+ for (int i = 0; i < 30; i++)
+ {
+ Width -= 10;
+ Height -= 5;
+ await Task.Delay(10);
+ }
+ }
}
}
diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt
new file mode 100644
index 0000000000..4668a572c5
--- /dev/null
+++ b/src/Avalonia.Base/ApiCompatBaseline.txt
@@ -0,0 +1,3 @@
+Compat issues with assembly Avalonia.Base:
+CannotAddAbstractMembers : Member 'protected System.IObservable Avalonia.AvaloniaProperty.GetChanged()' is abstract in the implementation but is missing in the contract.
+Total Issues: 1
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 39391490b0..3ae0445e9b 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Utilities;
@@ -18,7 +17,6 @@ namespace Avalonia
public static readonly object UnsetValue = new UnsetValueType();
private static int s_nextId;
- private readonly Subject _changed;
private readonly PropertyMetadata _defaultMetadata;
private readonly Dictionary _metadata;
private readonly Dictionary _metadataCache = new Dictionary();
@@ -50,7 +48,6 @@ namespace Avalonia
throw new ArgumentException("'name' may not contain periods.");
}
- _changed = new Subject();
_metadata = new Dictionary();
Name = name;
@@ -77,7 +74,6 @@ namespace Avalonia
Contract.Requires(source != null);
Contract.Requires(ownerType != null);
- _changed = source._changed;
_metadata = new Dictionary();
Name = source.Name;
@@ -139,7 +135,7 @@ namespace Avalonia
/// An observable that is fired when this property changes on any
/// instance.
///
- public IObservable Changed => _changed;
+ public IObservable Changed => GetChanged();
///
/// Gets a method that gets called before and after the property starts being notified on an
@@ -474,15 +470,6 @@ namespace Avalonia
public abstract void Accept(IAvaloniaPropertyVisitor vistor, ref TData data)
where TData : struct;
- ///
- /// Notifies the observable.
- ///
- /// The observable arguments.
- internal void NotifyChanged(AvaloniaPropertyChangedEventArgs e)
- {
- _changed.OnNext(e);
- }
-
///
/// Routes an untyped ClearValue call to a typed call.
///
@@ -553,6 +540,8 @@ namespace Avalonia
_hasMetadataOverrides = true;
}
+ protected abstract IObservable GetChanged();
+
private PropertyMetadata GetMetadataWithOverrides(Type type)
{
if (type is null)
diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs
index 2f26d855f2..d5549e979b 100644
--- a/src/Avalonia.Base/AvaloniaProperty`1.cs
+++ b/src/Avalonia.Base/AvaloniaProperty`1.cs
@@ -1,4 +1,5 @@
using System;
+using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Utilities;
@@ -10,6 +11,8 @@ namespace Avalonia
/// The value type of the property.
public abstract class AvaloniaProperty : AvaloniaProperty
{
+ private readonly Subject> _changed;
+
///
/// Initializes a new instance of the class.
///
@@ -24,22 +27,61 @@ namespace Avalonia
Action notifying = null)
: base(name, typeof(TValue), ownerType, metadata, notifying)
{
+ _changed = new Subject>();
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The property to copy.
/// The new owner type.
/// Optional overridden metadata.
+ [Obsolete("Use constructor with AvaloniaProperty instead.", true)]
protected AvaloniaProperty(
- AvaloniaProperty source,
- Type ownerType,
+ AvaloniaProperty source,
+ Type ownerType,
+ PropertyMetadata metadata)
+ : this(source as AvaloniaProperty ?? throw new InvalidOperationException(), ownerType, metadata)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The property to copy.
+ /// The new owner type.
+ /// Optional overridden metadata.
+ protected AvaloniaProperty(
+ AvaloniaProperty source,
+ Type ownerType,
PropertyMetadata metadata)
: base(source, ownerType, metadata)
{
+ _changed = source._changed;
+ }
+
+ ///
+ /// Gets an observable that is fired when this property changes on any
+ /// instance.
+ ///
+ ///
+ /// An observable that is fired when this property changes on any
+ /// instance.
+ ///
+
+ public new IObservable> Changed => _changed;
+
+ ///
+ /// Notifies the observable.
+ ///
+ /// The observable arguments.
+ internal void NotifyChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ _changed.OnNext(e);
}
+ protected override IObservable GetChanged() => Changed;
+
protected BindingValue
+ [PseudoClasses(":dropdownopen")]
public class PopulatedEventArgs : EventArgs
{
///
@@ -225,6 +227,27 @@ namespace Avalonia.Controls
Custom = 13,
}
+ ///
+ /// Represents the selector used by the
+ /// control to
+ /// determine how the specified text should be modified with an item.
+ ///
+ ///
+ /// Modified text that will be used by the
+ /// .
+ ///
+ /// The string used as the basis for filtering.
+ ///
+ /// The selected item that should be combined with the
+ /// parameter.
+ ///
+ ///
+ /// The type used for filtering the
+ /// .
+ /// This type can be either a string or an object.
+ ///
+ public delegate string AutoCompleteSelector(string search, T item);
+
///
/// Represents a control that provides a text box for user input and a
/// drop-down that contains possible matches based on the input in the text
@@ -362,6 +385,9 @@ namespace Avalonia.Controls
private AutoCompleteFilterPredicate