diff --git a/Documentation/build.md b/Documentation/build.md index 56b028206d..8c2ef57b54 100644 --- a/Documentation/build.md +++ b/Documentation/build.md @@ -36,7 +36,7 @@ Avalonia requires [CastXML](https://github.com/CastXML/CastXML) for XML processi On macOS: ``` -brew install castxml +brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/8a004a91a7fcd3f6620d5b01b6541ff0a640ffba/Formula/castxml.rb ``` On Debian based Linux (Debian, Ubuntu, Mint, etc): diff --git a/samples/RenderDemo/Controls/LineBoundsDemoControl.cs b/samples/RenderDemo/Controls/LineBoundsDemoControl.cs new file mode 100644 index 0000000000..cc847a594d --- /dev/null +++ b/samples/RenderDemo/Controls/LineBoundsDemoControl.cs @@ -0,0 +1,53 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Rendering.SceneGraph; +using Avalonia.Threading; + +namespace RenderDemo.Controls +{ + public class LineBoundsDemoControl : Control + { + static LineBoundsDemoControl() + { + AffectsRender(AngleProperty); + } + + public LineBoundsDemoControl() + { + var timer = new DispatcherTimer(); + timer.Interval = TimeSpan.FromSeconds(1 / 60.0); + timer.Tick += (sender, e) => Angle += Math.PI / 360; + timer.Start(); + } + + public static readonly StyledProperty AngleProperty = + AvaloniaProperty.Register(nameof(Angle)); + + public double Angle + { + get => GetValue(AngleProperty); + set => SetValue(AngleProperty, value); + } + + public override void Render(DrawingContext drawingContext) + { + var lineLength = Math.Sqrt((100 * 100) + (100 * 100)); + + var diffX = LineBoundsHelper.CalculateAdjSide(Angle, lineLength); + var diffY = LineBoundsHelper.CalculateOppSide(Angle, lineLength); + + + var p1 = new Point(200, 200); + var p2 = new Point(p1.X + diffX, p1.Y + diffY); + + var pen = new Pen(Brushes.Green, 20, lineCap: PenLineCap.Square); + var boundPen = new Pen(Brushes.Black); + + drawingContext.DrawLine(pen, p1, p2); + + drawingContext.DrawRectangle(boundPen, LineBoundsHelper.CalculateBounds(p1, p2, pen)); + } + } +} diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index b17520a466..c098ef411e 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -44,6 +44,9 @@ + + + diff --git a/samples/RenderDemo/Pages/LineBoundsPage.xaml b/samples/RenderDemo/Pages/LineBoundsPage.xaml new file mode 100644 index 0000000000..07d658630a --- /dev/null +++ b/samples/RenderDemo/Pages/LineBoundsPage.xaml @@ -0,0 +1,9 @@ + + + diff --git a/samples/RenderDemo/Pages/LineBoundsPage.xaml.cs b/samples/RenderDemo/Pages/LineBoundsPage.xaml.cs new file mode 100644 index 0000000000..28ddedd4bc --- /dev/null +++ b/samples/RenderDemo/Pages/LineBoundsPage.xaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace RenderDemo.Pages +{ + public class LineBoundsPage : UserControl + { + public LineBoundsPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/RenderDemo/RenderDemo.csproj b/samples/RenderDemo/RenderDemo.csproj index ce33f42143..0d7d62e177 100644 --- a/samples/RenderDemo/RenderDemo.csproj +++ b/samples/RenderDemo/RenderDemo.csproj @@ -3,6 +3,9 @@ Exe netcoreapp3.1 + + + diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index 3e4f47ec8a..b38cc56a17 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -10,6 +10,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; +using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using Avalonia.Collections; @@ -1100,6 +1101,7 @@ namespace Avalonia.Controls { _textBoxSubscriptions = _textBox.GetObservable(TextBox.TextProperty) + .Skip(1) .Subscribe(_ => OnTextBoxTextChanged()); if (Text != null) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 1735599988..86499530da 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using Avalonia.Controls.Generators; @@ -9,18 +10,19 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.LogicalTree; +using Avalonia.Styling; namespace Avalonia.Controls { /// /// A control context menu. /// - public class ContextMenu : MenuBase + public class ContextMenu : MenuBase, ISetterValue { private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel { Orientation = Orientation.Vertical }); private Popup _popup; - private Control _attachedControl; + private List _attachedControls; private IInputElement _previousFocus; /// @@ -74,13 +76,14 @@ namespace Avalonia.Controls if (e.OldValue is ContextMenu oldMenu) { control.PointerReleased -= ControlPointerReleased; - oldMenu._attachedControl = null; + oldMenu._attachedControls?.Remove(control); ((ISetLogicalParent)oldMenu._popup)?.SetParent(null); } if (e.NewValue is ContextMenu newMenu) { - newMenu._attachedControl = control; + newMenu._attachedControls ??= new List(); + newMenu._attachedControls.Add(control); control.PointerReleased += ControlPointerReleased; } } @@ -96,18 +99,22 @@ namespace Avalonia.Controls /// The control. public void Open(Control control) { - if (control is null && _attachedControl is null) + if (control is null && (_attachedControls is null || _attachedControls.Count == 0)) { throw new ArgumentNullException(nameof(control)); } - if (control is object && _attachedControl is object && control != _attachedControl) + if (control is object && + _attachedControls is object && + !_attachedControls.Contains(control)) { throw new ArgumentException( "Cannot show ContentMenu on a different control to the one it is attached to.", nameof(control)); } + control ??= _attachedControls[0]; + if (IsOpen) { return; @@ -126,7 +133,12 @@ namespace Avalonia.Controls _popup.Closed += PopupClosed; } - ((ISetLogicalParent)_popup).SetParent(control); + if (_popup.Parent != control) + { + ((ISetLogicalParent)_popup).SetParent(null); + ((ISetLogicalParent)_popup).SetParent(control); + } + _popup.Child = this; _popup.IsOpen = true; @@ -155,6 +167,17 @@ namespace Avalonia.Controls } } + void ISetterValue.Initialize(ISetter setter) + { + // ContextMenu can be assigned to the ContextMenu property in a setter. This overrides + // the behavior defined in Control which requires controls to be wrapped in a