committed by
GitHub
108 changed files with 2229 additions and 801 deletions
@ -0,0 +1,27 @@ |
|||||
|
using System.Collections.ObjectModel; |
||||
|
using System.Linq; |
||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace ControlCatalog.ViewModels |
||||
|
{ |
||||
|
public class ItemsRepeaterPageViewModel : ReactiveObject |
||||
|
{ |
||||
|
private int newItemIndex = 1; |
||||
|
|
||||
|
public ItemsRepeaterPageViewModel() |
||||
|
{ |
||||
|
Items = new ObservableCollection<string>( |
||||
|
Enumerable.Range(1, 100000).Select(i => $"Item {i}")); |
||||
|
} |
||||
|
|
||||
|
public ObservableCollection<string> Items { get; } |
||||
|
|
||||
|
public string SelectedItem { get; set; } |
||||
|
|
||||
|
public void AddItem() |
||||
|
{ |
||||
|
var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1; |
||||
|
Items.Insert(index + 1, $"New Item {newItemIndex++}"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,112 +0,0 @@ |
|||||
using System; |
|
||||
using Android.Content; |
|
||||
using Android.Graphics; |
|
||||
using Android.Runtime; |
|
||||
using Android.Views; |
|
||||
using Avalonia.Controls; |
|
||||
using Avalonia.Platform; |
|
||||
|
|
||||
namespace Avalonia.Android.Platform.SkiaPlatform |
|
||||
{ |
|
||||
class PopupImpl : TopLevelImpl, IPopupImpl |
|
||||
{ |
|
||||
private PixelPoint _position; |
|
||||
private bool _isAdded; |
|
||||
Action IWindowBaseImpl.Activated { get; set; } |
|
||||
public Action<PixelPoint> PositionChanged { get; set; } |
|
||||
public Action Deactivated { get; set; } |
|
||||
|
|
||||
public PopupImpl() : base(ActivityTracker.Current, true) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
private Size _clientSize = new Size(1, 1); |
|
||||
|
|
||||
public void Resize(Size value) |
|
||||
{ |
|
||||
if (View == null) |
|
||||
return; |
|
||||
_clientSize = value; |
|
||||
UpdateParams(); |
|
||||
} |
|
||||
|
|
||||
public void SetMinMaxSize(Size minSize, Size maxSize) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public IScreenImpl Screen { get; } |
|
||||
|
|
||||
public PixelPoint Position |
|
||||
{ |
|
||||
get { return _position; } |
|
||||
set |
|
||||
{ |
|
||||
_position = value; |
|
||||
PositionChanged?.Invoke(_position); |
|
||||
UpdateParams(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
WindowManagerLayoutParams CreateParams() => new WindowManagerLayoutParams(0, |
|
||||
WindowManagerFlags.NotTouchModal, Format.Translucent) |
|
||||
{ |
|
||||
Gravity = GravityFlags.Left | GravityFlags.Top, |
|
||||
WindowAnimations = 0, |
|
||||
X = (int) _position.X, |
|
||||
Y = (int) _position.Y, |
|
||||
Width = Math.Max(1, (int) _clientSize.Width), |
|
||||
Height = Math.Max(1, (int) _clientSize.Height) |
|
||||
}; |
|
||||
|
|
||||
void UpdateParams() |
|
||||
{ |
|
||||
if (_isAdded) |
|
||||
ActivityTracker.Current?.WindowManager?.UpdateViewLayout(View, CreateParams()); |
|
||||
} |
|
||||
|
|
||||
public override void Show() |
|
||||
{ |
|
||||
if (_isAdded) |
|
||||
return; |
|
||||
ActivityTracker.Current.WindowManager.AddView(View, CreateParams()); |
|
||||
_isAdded = true; |
|
||||
} |
|
||||
|
|
||||
public override void Hide() |
|
||||
{ |
|
||||
if (_isAdded) |
|
||||
{ |
|
||||
var wm = View.Context.ApplicationContext.GetSystemService(Context.WindowService) |
|
||||
.JavaCast<IWindowManager>(); |
|
||||
wm.RemoveView(View); |
|
||||
_isAdded = false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public override void Dispose() |
|
||||
{ |
|
||||
Hide(); |
|
||||
base.Dispose(); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
public void Activate() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public void BeginMoveDrag() |
|
||||
{ |
|
||||
//Not supported
|
|
||||
} |
|
||||
|
|
||||
public void BeginResizeDrag(WindowEdge edge) |
|
||||
{ |
|
||||
//Not supported
|
|
||||
} |
|
||||
|
|
||||
public void SetTopmost(bool value) |
|
||||
{ |
|
||||
//Not supported
|
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,42 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using Avalonia.LogicalTree; |
|
||||
|
|
||||
namespace Avalonia.Controls.Primitives |
|
||||
{ |
|
||||
public class AdornerDecorator : Decorator |
|
||||
{ |
|
||||
public AdornerDecorator() |
|
||||
{ |
|
||||
AdornerLayer = new AdornerLayer(); |
|
||||
((ISetLogicalParent)AdornerLayer).SetParent(this); |
|
||||
AdornerLayer.ZIndex = int.MaxValue; |
|
||||
VisualChildren.Add(AdornerLayer); |
|
||||
} |
|
||||
|
|
||||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) |
|
||||
{ |
|
||||
base.OnAttachedToLogicalTree(e); |
|
||||
|
|
||||
((ILogical)AdornerLayer).NotifyAttachedToLogicalTree(e); |
|
||||
} |
|
||||
|
|
||||
public AdornerLayer AdornerLayer |
|
||||
{ |
|
||||
get; |
|
||||
} |
|
||||
|
|
||||
protected override Size MeasureOverride(Size availableSize) |
|
||||
{ |
|
||||
AdornerLayer.Measure(availableSize); |
|
||||
return base.MeasureOverride(availableSize); |
|
||||
} |
|
||||
|
|
||||
protected override Size ArrangeOverride(Size finalSize) |
|
||||
{ |
|
||||
AdornerLayer.Arrange(new Rect(finalSize)); |
|
||||
return base.ArrangeOverride(finalSize); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,26 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls.Presenters; |
||||
|
using Avalonia.Controls.Primitives.PopupPositioning; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
public interface IPopupHost : IDisposable |
||||
|
{ |
||||
|
void SetChild(IControl control); |
||||
|
IContentPresenter Presenter { get; } |
||||
|
IVisual HostedVisualTreeRoot { get; } |
||||
|
|
||||
|
event EventHandler<TemplateAppliedEventArgs> TemplateApplied; |
||||
|
|
||||
|
void ConfigurePosition(IVisual target, PlacementMode placement, Point offset, |
||||
|
PopupPositioningEdge anchor = PopupPositioningEdge.None, |
||||
|
PopupPositioningEdge gravity = PopupPositioningEdge.None); |
||||
|
void Show(); |
||||
|
void Hide(); |
||||
|
IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty, |
||||
|
StyledProperty<double> minWidthProperty, StyledProperty<double> maxWidthProperty, |
||||
|
StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty, |
||||
|
StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using System.Linq; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
public class OverlayLayer : Canvas, ICustomSimpleHitTest |
||||
|
{ |
||||
|
public Size AvailableSize { get; private set; } |
||||
|
public static OverlayLayer GetOverlayLayer(IVisual visual) |
||||
|
{ |
||||
|
foreach(var v in visual.GetVisualAncestors()) |
||||
|
if(v is VisualLayerManager vlm) |
||||
|
if (vlm.OverlayLayer != null) |
||||
|
return vlm.OverlayLayer; |
||||
|
if (visual is TopLevel tl) |
||||
|
{ |
||||
|
var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault(); |
||||
|
return layers?.OverlayLayer; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public bool HitTest(Point point) |
||||
|
{ |
||||
|
return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true); |
||||
|
} |
||||
|
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
// We are saving it here since child controls might need to know the entire size of the overlay
|
||||
|
// and Bounds won't be updated in time
|
||||
|
AvailableSize = finalSize; |
||||
|
return base.ArrangeOverride(finalSize); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,149 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Reactive.Disposables; |
||||
|
using Avalonia.Controls.Primitives.PopupPositioning; |
||||
|
using Avalonia.Interactivity; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Threading; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
public class OverlayPopupHost : ContentControl, IPopupHost, IInteractive, IManagedPopupPositionerPopup |
||||
|
{ |
||||
|
private readonly OverlayLayer _overlayLayer; |
||||
|
private PopupPositionerParameters _positionerParameters = new PopupPositionerParameters(); |
||||
|
private ManagedPopupPositioner _positioner; |
||||
|
private Point _lastRequestedPosition; |
||||
|
private bool _shown; |
||||
|
|
||||
|
public OverlayPopupHost(OverlayLayer overlayLayer) |
||||
|
{ |
||||
|
_overlayLayer = overlayLayer; |
||||
|
_positioner = new ManagedPopupPositioner(this); |
||||
|
} |
||||
|
|
||||
|
public void SetChild(IControl control) |
||||
|
{ |
||||
|
Content = control; |
||||
|
} |
||||
|
|
||||
|
public IVisual HostedVisualTreeRoot => null; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
IInteractive IInteractive.InteractiveParent => Parent; |
||||
|
|
||||
|
public void Dispose() => Hide(); |
||||
|
|
||||
|
|
||||
|
public void Show() |
||||
|
{ |
||||
|
_overlayLayer.Children.Add(this); |
||||
|
_shown = true; |
||||
|
} |
||||
|
|
||||
|
public void Hide() |
||||
|
{ |
||||
|
_overlayLayer.Children.Remove(this); |
||||
|
_shown = false; |
||||
|
} |
||||
|
|
||||
|
public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty, StyledProperty<double> minWidthProperty, |
||||
|
StyledProperty<double> maxWidthProperty, StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty, |
||||
|
StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty) |
||||
|
{ |
||||
|
// Topmost property is not supported
|
||||
|
var bindings = new List<IDisposable>(); |
||||
|
|
||||
|
void Bind(AvaloniaProperty what, AvaloniaProperty to) => bindings.Add(this.Bind(what, popup[~to])); |
||||
|
Bind(WidthProperty, widthProperty); |
||||
|
Bind(MinWidthProperty, minWidthProperty); |
||||
|
Bind(MaxWidthProperty, maxWidthProperty); |
||||
|
Bind(HeightProperty, heightProperty); |
||||
|
Bind(MinHeightProperty, minHeightProperty); |
||||
|
Bind(MaxHeightProperty, maxHeightProperty); |
||||
|
|
||||
|
return Disposable.Create(() => |
||||
|
{ |
||||
|
foreach (var x in bindings) |
||||
|
x.Dispose(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset, |
||||
|
PopupPositioningEdge anchor = PopupPositioningEdge.None, PopupPositioningEdge gravity = PopupPositioningEdge.None) |
||||
|
{ |
||||
|
_positionerParameters.ConfigurePosition((TopLevel)_overlayLayer.GetVisualRoot(), target, placement, offset, anchor, |
||||
|
gravity); |
||||
|
UpdatePosition(); |
||||
|
} |
||||
|
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
if (_positionerParameters.Size != finalSize) |
||||
|
{ |
||||
|
_positionerParameters.Size = finalSize; |
||||
|
UpdatePosition(); |
||||
|
} |
||||
|
return base.ArrangeOverride(finalSize); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private void UpdatePosition() |
||||
|
{ |
||||
|
// Don't bother the positioner with layout system artifacts
|
||||
|
if (_positionerParameters.Size.Width == 0 || _positionerParameters.Size.Height == 0) |
||||
|
return; |
||||
|
if (_shown) |
||||
|
{ |
||||
|
_positioner.Update(_positionerParameters); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
IReadOnlyList<ManagedPopupPositionerScreenInfo> IManagedPopupPositionerPopup.Screens |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
var rc = new Rect(default, _overlayLayer.AvailableSize); |
||||
|
return new[] {new ManagedPopupPositionerScreenInfo(rc, rc)}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Rect IManagedPopupPositionerPopup.ParentClientAreaScreenGeometry => |
||||
|
new Rect(default, _overlayLayer.Bounds.Size); |
||||
|
|
||||
|
void IManagedPopupPositionerPopup.MoveAndResize(Point devicePoint, Size virtualSize) |
||||
|
{ |
||||
|
_lastRequestedPosition = devicePoint; |
||||
|
Dispatcher.UIThread.Post(() => |
||||
|
{ |
||||
|
OverlayLayer.SetLeft(this, _lastRequestedPosition.X); |
||||
|
OverlayLayer.SetTop(this, _lastRequestedPosition.Y); |
||||
|
}, DispatcherPriority.Layout); |
||||
|
} |
||||
|
|
||||
|
Point IManagedPopupPositionerPopup.TranslatePoint(Point pt) => pt; |
||||
|
|
||||
|
Size IManagedPopupPositionerPopup.TranslateSize(Size size) => size; |
||||
|
|
||||
|
public static IPopupHost CreatePopupHost(IVisual target, IAvaloniaDependencyResolver dependencyResolver) |
||||
|
{ |
||||
|
var platform = (target.GetVisualRoot() as TopLevel)?.PlatformImpl?.CreatePopup(); |
||||
|
if (platform != null) |
||||
|
return new PopupRoot((TopLevel)target.GetVisualRoot(), platform, dependencyResolver); |
||||
|
|
||||
|
var overlayLayer = OverlayLayer.GetOverlayLayer(target); |
||||
|
if (overlayLayer == null) |
||||
|
throw new InvalidOperationException( |
||||
|
"Unable to create IPopupImpl and no overlay layer is found for the target control"); |
||||
|
|
||||
|
|
||||
|
return new OverlayPopupHost(overlayLayer); |
||||
|
} |
||||
|
|
||||
|
public override void Render(DrawingContext context) |
||||
|
{ |
||||
|
context.FillRectangle(Brushes.White, new Rect(default, Bounds.Size)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,358 @@ |
|||||
|
// The documentation and flag names in this file are initially taken from
|
||||
|
// xdg_shell wayland protocol this API is designed after
|
||||
|
// therefore, I'm including the license from wayland-protocols repo
|
||||
|
|
||||
|
/* |
||||
|
Copyright © 2008-2013 Kristian Høgsberg |
||||
|
Copyright © 2010-2013 Intel Corporation |
||||
|
Copyright © 2013 Rafael Antognolli |
||||
|
Copyright © 2013 Jasper St. Pierre |
||||
|
Copyright © 2014 Jonas Ådahl |
||||
|
Copyright © 2014 Jason Ekstrand |
||||
|
Copyright © 2014-2015 Collabora, Ltd. |
||||
|
Copyright © 2015 Red Hat Inc. |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining a |
||||
|
copy of this software and associated documentation files (the "Software"), |
||||
|
to deal in the Software without restriction, including without limitation |
||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||||
|
and/or sell copies of the Software, and to permit persons to whom the |
||||
|
Software is furnished to do so, subject to the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice (including the next |
||||
|
paragraph) shall be included in all copies or substantial portions of the |
||||
|
Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
|
DEALINGS IN THE SOFTWARE. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
The above is the version of the MIT "Expat" License used by X.org: |
||||
|
|
||||
|
http://cgit.freedesktop.org/xorg/xserver/tree/COPYING
|
||||
|
|
||||
|
|
||||
|
Adjustments for Avalonia needs: |
||||
|
Copyright © 2019 Nikita Tsukanov |
||||
|
|
||||
|
|
||||
|
*/ |
||||
|
|
||||
|
using System; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives.PopupPositioning |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
///
|
||||
|
/// The IPopupPositioner provides a collection of rules for the placement of a
|
||||
|
/// a popup relative to its parent. Rules can be defined to ensure
|
||||
|
/// the popup remains within the visible area's borders, and to
|
||||
|
/// specify how the popup changes its position, such as sliding along
|
||||
|
/// an axis, or flipping around a rectangle. These positioner-created rules are
|
||||
|
/// constrained by the requirement that a popup must intersect with or
|
||||
|
/// be at least partially adjacent to its parent surface.
|
||||
|
/// </summary>
|
||||
|
public struct PopupPositionerParameters |
||||
|
{ |
||||
|
private PopupPositioningEdge _gravity; |
||||
|
private PopupPositioningEdge _anchor; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Set the size of the popup that is to be positioned with the positioner
|
||||
|
/// object. The size is in scaled coordinates.
|
||||
|
/// </summary>
|
||||
|
public Size Size { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specify the anchor rectangle within the parent that the popup
|
||||
|
/// will be placed relative to. The rectangle is relative to the
|
||||
|
/// parent geometry
|
||||
|
///
|
||||
|
/// The anchor rectangle may not extend outside the window geometry of the
|
||||
|
/// popup's parent. The anchor rectangle is in scaled coordinates
|
||||
|
/// </summary>
|
||||
|
public Rect AnchorRectangle { get; set; } |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the anchor point for the anchor rectangle. The specified anchor
|
||||
|
/// is used derive an anchor point that the popup will be
|
||||
|
/// positioned relative to. If a corner anchor is set (e.g. 'TopLeft' or
|
||||
|
/// 'BottomRight'), the anchor point will be at the specified corner;
|
||||
|
/// otherwise, the derived anchor point will be centered on the specified
|
||||
|
/// edge, or in the center of the anchor rectangle if no edge is specified.
|
||||
|
/// </summary>
|
||||
|
public PopupPositioningEdge Anchor |
||||
|
{ |
||||
|
get => _anchor; |
||||
|
set |
||||
|
{ |
||||
|
PopupPositioningEdgeHelper.ValidateEdge(value); |
||||
|
_anchor = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines in what direction a popup should be positioned, relative to
|
||||
|
/// the anchor point of the parent. If a corner gravity is
|
||||
|
/// specified (e.g. 'BottomRight' or 'TopLeft'), then the popup
|
||||
|
/// will be placed towards the specified gravity; otherwise, the popup
|
||||
|
/// will be centered over the anchor point on any axis that had no
|
||||
|
/// gravity specified.
|
||||
|
/// </summary>
|
||||
|
public PopupPositioningEdge Gravity |
||||
|
{ |
||||
|
get => _gravity; |
||||
|
set |
||||
|
{ |
||||
|
PopupPositioningEdgeHelper.ValidateEdge(value); |
||||
|
_gravity = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specify how the popup should be positioned if the originally intended
|
||||
|
/// position caused the popup to be constrained, meaning at least
|
||||
|
/// partially outside positioning boundaries set by the positioner. The
|
||||
|
/// adjustment is set by constructing a bitmask describing the adjustment to
|
||||
|
/// be made when the popup is constrained on that axis.
|
||||
|
///
|
||||
|
/// If no bit for one axis is set, the positioner will assume that the child
|
||||
|
/// surface should not change its position on that axis when constrained.
|
||||
|
///
|
||||
|
/// If more than one bit for one axis is set, the order of how adjustments
|
||||
|
/// are applied is specified in the corresponding adjustment descriptions.
|
||||
|
///
|
||||
|
/// The default adjustment is none.
|
||||
|
/// </summary>
|
||||
|
public PopupPositionerConstraintAdjustment ConstraintAdjustment { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specify the popup position offset relative to the position of the
|
||||
|
/// anchor on the anchor rectangle and the anchor on the popup. For
|
||||
|
/// example if the anchor of the anchor rectangle is at (x, y), the popup
|
||||
|
/// has the gravity bottom|right, and the offset is (ox, oy), the calculated
|
||||
|
/// surface position will be (x + ox, y + oy). The offset position of the
|
||||
|
/// surface is the one used for constraint testing. See
|
||||
|
/// set_constraint_adjustment.
|
||||
|
///
|
||||
|
/// An example use case is placing a popup menu on top of a user interface
|
||||
|
/// element, while aligning the user interface element of the parent surface
|
||||
|
/// with some user interface element placed somewhere in the popup.
|
||||
|
/// </summary>
|
||||
|
public Point Offset { get; set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The constraint adjustment value define ways how popup position will
|
||||
|
/// be adjusted if the unadjusted position would result in the popup
|
||||
|
/// being partly constrained.
|
||||
|
///
|
||||
|
/// Whether a popup is considered 'constrained' is left to the positioner
|
||||
|
/// to determine. For example, the popup may be partly outside the
|
||||
|
/// target platform defined 'work area', thus necessitating the popup's
|
||||
|
/// position be adjusted until it is entirely inside the work area.
|
||||
|
/// </summary>
|
||||
|
[Flags] |
||||
|
public enum PopupPositionerConstraintAdjustment |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Don't alter the surface position even if it is constrained on some
|
||||
|
/// axis, for example partially outside the edge of an output.
|
||||
|
/// </summary>
|
||||
|
None = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Slide the surface along the x axis until it is no longer constrained.
|
||||
|
/// First try to slide towards the direction of the gravity on the x axis
|
||||
|
/// until either the edge in the opposite direction of the gravity is
|
||||
|
/// unconstrained or the edge in the direction of the gravity is
|
||||
|
/// constrained.
|
||||
|
///
|
||||
|
/// Then try to slide towards the opposite direction of the gravity on the
|
||||
|
/// x axis until either the edge in the direction of the gravity is
|
||||
|
/// unconstrained or the edge in the opposite direction of the gravity is
|
||||
|
/// constrained.
|
||||
|
/// </summary>
|
||||
|
SlideX = 1, |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Slide the surface along the y axis until it is no longer constrained.
|
||||
|
///
|
||||
|
/// First try to slide towards the direction of the gravity on the y axis
|
||||
|
/// until either the edge in the opposite direction of the gravity is
|
||||
|
/// unconstrained or the edge in the direction of the gravity is
|
||||
|
/// constrained.
|
||||
|
///
|
||||
|
/// Then try to slide towards the opposite direction of the gravity on the
|
||||
|
/// y axis until either the edge in the direction of the gravity is
|
||||
|
/// unconstrained or the edge in the opposite direction of the gravity is
|
||||
|
/// constrained.
|
||||
|
/// */
|
||||
|
/// </summary>
|
||||
|
SlideY = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invert the anchor and gravity on the x axis if the surface is
|
||||
|
/// constrained on the x axis. For example, if the left edge of the
|
||||
|
/// surface is constrained, the gravity is 'left' and the anchor is
|
||||
|
/// 'left', change the gravity to 'right' and the anchor to 'right'.
|
||||
|
///
|
||||
|
/// If the adjusted position also ends up being constrained, the resulting
|
||||
|
/// position of the flip_x adjustment will be the one before the
|
||||
|
/// adjustment.
|
||||
|
/// </summary>
|
||||
|
FlipX = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invert the anchor and gravity on the y axis if the surface is
|
||||
|
/// constrained on the y axis. For example, if the bottom edge of the
|
||||
|
/// surface is constrained, the gravity is 'bottom' and the anchor is
|
||||
|
/// 'bottom', change the gravity to 'top' and the anchor to 'top'.
|
||||
|
///
|
||||
|
/// The adjusted position is calculated given the original anchor
|
||||
|
/// rectangle and offset, but with the new flipped anchor and gravity
|
||||
|
/// values.
|
||||
|
///
|
||||
|
/// If the adjusted position also ends up being constrained, the resulting
|
||||
|
/// position of the flip_y adjustment will be the one before the
|
||||
|
/// adjustment.
|
||||
|
/// </summary>
|
||||
|
FlipY = 8, |
||||
|
All = SlideX|SlideY|FlipX|FlipY |
||||
|
} |
||||
|
|
||||
|
static class PopupPositioningEdgeHelper |
||||
|
{ |
||||
|
public static void ValidateEdge(this PopupPositioningEdge edge) |
||||
|
{ |
||||
|
if (((edge & PopupPositioningEdge.Left) != 0 && (edge & PopupPositioningEdge.Right) != 0) |
||||
|
|| |
||||
|
((edge & PopupPositioningEdge.Top) != 0 && (edge & PopupPositioningEdge.Bottom) != 0)) |
||||
|
throw new ArgumentException("Opposite edges specified"); |
||||
|
} |
||||
|
|
||||
|
public static PopupPositioningEdge Flip(this PopupPositioningEdge edge) |
||||
|
{ |
||||
|
var hmask = PopupPositioningEdge.Left | PopupPositioningEdge.Right; |
||||
|
var vmask = PopupPositioningEdge.Top | PopupPositioningEdge.Bottom; |
||||
|
if ((edge & hmask) != 0) |
||||
|
edge ^= hmask; |
||||
|
if ((edge & vmask) != 0) |
||||
|
edge ^= vmask; |
||||
|
return edge; |
||||
|
} |
||||
|
|
||||
|
public static PopupPositioningEdge FlipX(this PopupPositioningEdge edge) |
||||
|
{ |
||||
|
if ((edge & PopupPositioningEdge.HorizontalMask) != 0) |
||||
|
edge ^= PopupPositioningEdge.HorizontalMask; |
||||
|
return edge; |
||||
|
} |
||||
|
|
||||
|
public static PopupPositioningEdge FlipY(this PopupPositioningEdge edge) |
||||
|
{ |
||||
|
if ((edge & PopupPositioningEdge.VerticalMask) != 0) |
||||
|
edge ^= PopupPositioningEdge.VerticalMask; |
||||
|
return edge; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
[Flags] |
||||
|
public enum PopupPositioningEdge |
||||
|
{ |
||||
|
None, |
||||
|
Top = 1, |
||||
|
Bottom = 2, |
||||
|
Left = 4, |
||||
|
Right = 8, |
||||
|
TopLeft = Top | Left, |
||||
|
TopRight = Top | Right, |
||||
|
BottomLeft = Bottom | Left, |
||||
|
BottomRight = Bottom | Right, |
||||
|
|
||||
|
|
||||
|
VerticalMask = Top | Bottom, |
||||
|
HorizontalMask = Left | Right, |
||||
|
AllMask = VerticalMask|HorizontalMask |
||||
|
} |
||||
|
|
||||
|
public interface IPopupPositioner |
||||
|
{ |
||||
|
void Update(PopupPositionerParameters parameters); |
||||
|
} |
||||
|
|
||||
|
static class PopupPositionerExtensions |
||||
|
{ |
||||
|
public static void ConfigurePosition(ref this PopupPositionerParameters positionerParameters, |
||||
|
TopLevel topLevel, |
||||
|
IVisual target, PlacementMode placement, Point offset, |
||||
|
PopupPositioningEdge anchor, PopupPositioningEdge gravity) |
||||
|
{ |
||||
|
// We need a better way for tracking the last pointer position
|
||||
|
var pointer = topLevel.PointToClient(topLevel.PlatformImpl.MouseDevice.Position); |
||||
|
|
||||
|
positionerParameters.Offset = offset; |
||||
|
positionerParameters.ConstraintAdjustment = PopupPositionerConstraintAdjustment.All; |
||||
|
if (placement == PlacementMode.Pointer) |
||||
|
{ |
||||
|
positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1)); |
||||
|
positionerParameters.Anchor = PopupPositioningEdge.BottomRight; |
||||
|
positionerParameters.Gravity = PopupPositioningEdge.BottomRight; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (target == null) |
||||
|
throw new InvalidOperationException("Placement mode is not Pointer and PlacementTarget is null"); |
||||
|
var matrix = target.TransformToVisual(topLevel); |
||||
|
if (matrix == null) |
||||
|
{ |
||||
|
if (target.GetVisualRoot() == null) |
||||
|
throw new InvalidCastException("Target control is not attached to the visual tree"); |
||||
|
throw new InvalidCastException("Target control is not in the same tree as the popup parent"); |
||||
|
} |
||||
|
|
||||
|
positionerParameters.AnchorRectangle = new Rect(default, target.Bounds.Size) |
||||
|
.TransformToAABB(matrix.Value); |
||||
|
|
||||
|
if (placement == PlacementMode.Right) |
||||
|
{ |
||||
|
positionerParameters.Anchor = PopupPositioningEdge.TopRight; |
||||
|
positionerParameters.Gravity = PopupPositioningEdge.BottomRight; |
||||
|
} |
||||
|
else if (placement == PlacementMode.Bottom) |
||||
|
{ |
||||
|
positionerParameters.Anchor = PopupPositioningEdge.BottomLeft; |
||||
|
positionerParameters.Gravity = PopupPositioningEdge.BottomRight; |
||||
|
} |
||||
|
else if (placement == PlacementMode.Left) |
||||
|
{ |
||||
|
positionerParameters.Anchor = PopupPositioningEdge.TopLeft; |
||||
|
positionerParameters.Gravity = PopupPositioningEdge.BottomLeft; |
||||
|
} |
||||
|
else if (placement == PlacementMode.Top) |
||||
|
{ |
||||
|
positionerParameters.Anchor = PopupPositioningEdge.TopLeft; |
||||
|
positionerParameters.Gravity = PopupPositioningEdge.TopRight; |
||||
|
} |
||||
|
else if (placement == PlacementMode.AnchorAndGravity) |
||||
|
{ |
||||
|
positionerParameters.Anchor = anchor; |
||||
|
positionerParameters.Gravity = gravity; |
||||
|
} |
||||
|
else |
||||
|
throw new InvalidOperationException("Invalid value for Popup.PlacementMode"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,175 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives.PopupPositioning |
||||
|
{ |
||||
|
public interface IManagedPopupPositionerPopup |
||||
|
{ |
||||
|
IReadOnlyList<ManagedPopupPositionerScreenInfo> Screens { get; } |
||||
|
Rect ParentClientAreaScreenGeometry { get; } |
||||
|
void MoveAndResize(Point devicePoint, Size virtualSize); |
||||
|
Point TranslatePoint(Point pt); |
||||
|
Size TranslateSize(Size size); |
||||
|
} |
||||
|
|
||||
|
public class ManagedPopupPositionerScreenInfo |
||||
|
{ |
||||
|
public Rect Bounds { get; } |
||||
|
public Rect WorkingArea { get; } |
||||
|
|
||||
|
public ManagedPopupPositionerScreenInfo(Rect bounds, Rect workingArea) |
||||
|
{ |
||||
|
Bounds = bounds; |
||||
|
WorkingArea = workingArea; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class ManagedPopupPositioner : IPopupPositioner |
||||
|
{ |
||||
|
private readonly IManagedPopupPositionerPopup _popup; |
||||
|
|
||||
|
public ManagedPopupPositioner(IManagedPopupPositionerPopup popup) |
||||
|
{ |
||||
|
_popup = popup; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private static Point GetAnchorPoint(Rect anchorRect, PopupPositioningEdge edge) |
||||
|
{ |
||||
|
double x, y; |
||||
|
if ((edge & PopupPositioningEdge.Left) != 0) |
||||
|
x = anchorRect.X; |
||||
|
else if ((edge & PopupPositioningEdge.Right) != 0) |
||||
|
x = anchorRect.Right; |
||||
|
else |
||||
|
x = anchorRect.X + anchorRect.Width / 2; |
||||
|
|
||||
|
if ((edge & PopupPositioningEdge.Top) != 0) |
||||
|
y = anchorRect.Y; |
||||
|
else if ((edge & PopupPositioningEdge.Bottom) != 0) |
||||
|
y = anchorRect.Bottom; |
||||
|
else |
||||
|
y = anchorRect.Y + anchorRect.Height / 2; |
||||
|
return new Point(x, y); |
||||
|
} |
||||
|
|
||||
|
private static Point Gravitate(Point anchorPoint, Size size, PopupPositioningEdge gravity) |
||||
|
{ |
||||
|
double x, y; |
||||
|
if ((gravity & PopupPositioningEdge.Left) != 0) |
||||
|
x = -size.Width; |
||||
|
else if ((gravity & PopupPositioningEdge.Right) != 0) |
||||
|
x = 0; |
||||
|
else |
||||
|
x = -size.Width / 2; |
||||
|
|
||||
|
if ((gravity & PopupPositioningEdge.Top) != 0) |
||||
|
y = -size.Height; |
||||
|
else if ((gravity & PopupPositioningEdge.Bottom) != 0) |
||||
|
y = 0; |
||||
|
else |
||||
|
y = -size.Height / 2; |
||||
|
return anchorPoint + new Point(x, y); |
||||
|
} |
||||
|
|
||||
|
public void Update(PopupPositionerParameters parameters) |
||||
|
{ |
||||
|
|
||||
|
Update(_popup.TranslateSize(parameters.Size), parameters.Size, |
||||
|
new Rect(_popup.TranslatePoint(parameters.AnchorRectangle.TopLeft), |
||||
|
_popup.TranslateSize(parameters.AnchorRectangle.Size)), |
||||
|
parameters.Anchor, parameters.Gravity, parameters.ConstraintAdjustment, |
||||
|
_popup.TranslatePoint(parameters.Offset)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private void Update(Size translatedSize, Size originalSize, |
||||
|
Rect anchorRect, PopupPositioningEdge anchor, PopupPositioningEdge gravity, |
||||
|
PopupPositionerConstraintAdjustment constraintAdjustment, Point offset) |
||||
|
{ |
||||
|
var parentGeometry = _popup.ParentClientAreaScreenGeometry; |
||||
|
anchorRect = anchorRect.Translate(parentGeometry.TopLeft); |
||||
|
|
||||
|
Rect GetBounds() |
||||
|
{ |
||||
|
var screens = _popup.Screens; |
||||
|
|
||||
|
var targetScreen = screens.FirstOrDefault(s => s.Bounds.Contains(anchorRect.TopLeft)) |
||||
|
?? screens.FirstOrDefault(s => s.Bounds.Intersects(anchorRect)) |
||||
|
?? screens.FirstOrDefault(s => s.Bounds.Contains(parentGeometry.TopLeft)) |
||||
|
?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry)) |
||||
|
?? screens.FirstOrDefault(); |
||||
|
return targetScreen?.WorkingArea |
||||
|
?? new Rect(0, 0, double.MaxValue, double.MaxValue); |
||||
|
} |
||||
|
|
||||
|
var bounds = GetBounds(); |
||||
|
|
||||
|
bool FitsInBounds(Rect rc, PopupPositioningEdge edge = PopupPositioningEdge.AllMask) |
||||
|
{ |
||||
|
if ((edge & PopupPositioningEdge.Left) != 0 |
||||
|
&& rc.X < bounds.X) |
||||
|
return false; |
||||
|
|
||||
|
if ((edge & PopupPositioningEdge.Top) != 0 |
||||
|
&& rc.Y < bounds.Y) |
||||
|
return false; |
||||
|
|
||||
|
if ((edge & PopupPositioningEdge.Right) != 0 |
||||
|
&& rc.Right > bounds.Right) |
||||
|
return false; |
||||
|
|
||||
|
if ((edge & PopupPositioningEdge.Bottom) != 0 |
||||
|
&& rc.Bottom > bounds.Bottom) |
||||
|
return false; |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
Rect GetUnconstrained(PopupPositioningEdge a, PopupPositioningEdge g) => |
||||
|
new Rect(Gravitate(GetAnchorPoint(anchorRect, a), translatedSize, g) + offset, translatedSize); |
||||
|
|
||||
|
|
||||
|
var geo = GetUnconstrained(anchor, gravity); |
||||
|
|
||||
|
// If flipping geometry and anchor is allowed and helps, use the flipped one,
|
||||
|
// otherwise leave it as is
|
||||
|
if (!FitsInBounds(geo, PopupPositioningEdge.HorizontalMask) |
||||
|
&& (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipX) != 0) |
||||
|
{ |
||||
|
var flipped = GetUnconstrained(anchor.FlipX(), gravity.FlipX()); |
||||
|
if (FitsInBounds(flipped, PopupPositioningEdge.HorizontalMask)) |
||||
|
geo = geo.WithX(flipped.X); |
||||
|
} |
||||
|
|
||||
|
// If sliding is allowed, try moving the rect into the bounds
|
||||
|
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideX) != 0) |
||||
|
{ |
||||
|
geo = geo.WithX(Math.Max(geo.X, bounds.X)); |
||||
|
if (geo.Right > bounds.Right) |
||||
|
geo = geo.WithX(bounds.Right - geo.Width); |
||||
|
} |
||||
|
|
||||
|
// If flipping geometry and anchor is allowed and helps, use the flipped one,
|
||||
|
// otherwise leave it as is
|
||||
|
if (!FitsInBounds(geo, PopupPositioningEdge.VerticalMask) |
||||
|
&& (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipY) != 0) |
||||
|
{ |
||||
|
var flipped = GetUnconstrained(anchor.FlipY(), gravity.FlipY()); |
||||
|
if (FitsInBounds(flipped, PopupPositioningEdge.VerticalMask)) |
||||
|
geo = geo.WithY(flipped.Y); |
||||
|
} |
||||
|
|
||||
|
// If sliding is allowed, try moving the rect into the bounds
|
||||
|
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideY) != 0) |
||||
|
{ |
||||
|
geo = geo.WithY(Math.Max(geo.Y, bounds.Y)); |
||||
|
if (geo.Bottom > bounds.Bottom) |
||||
|
geo = geo.WithY(bounds.Bottom - geo.Height); |
||||
|
} |
||||
|
|
||||
|
_popup.MoveAndResize(geo.TopLeft, originalSize); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives.PopupPositioning |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// This class is used to simplify integration of IPopupImpl implementations with popup positioner
|
||||
|
/// </summary>
|
||||
|
public class ManagedPopupPositionerPopupImplHelper : IManagedPopupPositionerPopup |
||||
|
{ |
||||
|
private readonly IWindowBaseImpl _parent; |
||||
|
|
||||
|
public delegate void MoveResizeDelegate(PixelPoint position, Size size, double scaling); |
||||
|
private readonly MoveResizeDelegate _moveResize; |
||||
|
|
||||
|
public ManagedPopupPositionerPopupImplHelper(IWindowBaseImpl parent, MoveResizeDelegate moveResize) |
||||
|
{ |
||||
|
_parent = parent; |
||||
|
_moveResize = moveResize; |
||||
|
} |
||||
|
|
||||
|
public IReadOnlyList<ManagedPopupPositionerScreenInfo> Screens => |
||||
|
|
||||
|
_parent.Screen.AllScreens.Select(s => new ManagedPopupPositionerScreenInfo( |
||||
|
s.Bounds.ToRect(1), s.WorkingArea.ToRect(1))).ToList(); |
||||
|
|
||||
|
public Rect ParentClientAreaScreenGeometry |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
// Popup positioner operates with abstract coordinates, but in our case they are pixel ones
|
||||
|
var point = _parent.PointToScreen(default); |
||||
|
var size = PixelSize.FromSize(_parent.ClientSize, _parent.Scaling); |
||||
|
return new Rect(point.X, point.Y, size.Width, size.Height); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void MoveAndResize(Point devicePoint, Size virtualSize) |
||||
|
{ |
||||
|
_moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling); |
||||
|
} |
||||
|
|
||||
|
public Point TranslatePoint(Point pt) => pt * _parent.Scaling; |
||||
|
|
||||
|
public Size TranslateSize(Size size) => size * _parent.Scaling; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.LogicalTree; |
||||
|
using Avalonia.Styling; |
||||
|
|
||||
|
namespace Avalonia.Controls.Primitives |
||||
|
{ |
||||
|
public class VisualLayerManager : Decorator |
||||
|
{ |
||||
|
private const int AdornerZIndex = int.MaxValue - 100; |
||||
|
private const int OverlayZIndex = int.MaxValue - 99; |
||||
|
private IStyleHost _styleRoot; |
||||
|
private readonly List<Control> _layers = new List<Control>(); |
||||
|
|
||||
|
|
||||
|
public bool IsPopup { get; set; } |
||||
|
|
||||
|
public AdornerLayer AdornerLayer |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
var rv = FindLayer<AdornerLayer>(); |
||||
|
if (rv == null) |
||||
|
AddLayer(rv = new AdornerLayer(), AdornerZIndex); |
||||
|
return rv; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public OverlayLayer OverlayLayer |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (IsPopup) |
||||
|
return null; |
||||
|
var rv = FindLayer<OverlayLayer>(); |
||||
|
if(rv == null) |
||||
|
AddLayer(rv = new OverlayLayer(), OverlayZIndex); |
||||
|
return rv; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
T FindLayer<T>() where T : class |
||||
|
{ |
||||
|
foreach (var layer in _layers) |
||||
|
if (layer is T match) |
||||
|
return match; |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
void AddLayer(Control layer, int zindex) |
||||
|
{ |
||||
|
_layers.Add(layer); |
||||
|
((ISetLogicalParent)layer).SetParent(this); |
||||
|
layer.ZIndex = zindex; |
||||
|
VisualChildren.Add(layer); |
||||
|
if (((ILogical)this).IsAttachedToLogicalTree) |
||||
|
((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleRoot)); |
||||
|
InvalidateArrange(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) |
||||
|
{ |
||||
|
base.OnAttachedToLogicalTree(e); |
||||
|
_styleRoot = e.Root; |
||||
|
|
||||
|
foreach (var l in _layers) |
||||
|
((ILogical)l).NotifyAttachedToLogicalTree(e); |
||||
|
} |
||||
|
|
||||
|
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) |
||||
|
{ |
||||
|
_styleRoot = null; |
||||
|
base.OnDetachedFromLogicalTree(e); |
||||
|
foreach (var l in _layers) |
||||
|
((ILogical)l).NotifyDetachedFromLogicalTree(e); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
protected override Size MeasureOverride(Size availableSize) |
||||
|
{ |
||||
|
foreach (var l in _layers) |
||||
|
l.Measure(availableSize); |
||||
|
return base.MeasureOverride(availableSize); |
||||
|
} |
||||
|
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
foreach (var l in _layers) |
||||
|
l.Arrange(new Rect(finalSize)); |
||||
|
return base.ArrangeOverride(finalSize); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
<Style xmlns="https://github.com/avaloniaui" Selector="OverlayPopupHost"> |
||||
|
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<VisualLayerManager IsPopup="True" Margin="-1 -1 0 0"> |
||||
|
<ContentPresenter Name="PART_ContentPresenter" |
||||
|
Background="{TemplateBinding Background}" |
||||
|
ContentTemplate="{TemplateBinding ContentTemplate}" |
||||
|
Content="{TemplateBinding Content}" |
||||
|
Padding="{TemplateBinding Padding}"/> |
||||
|
</VisualLayerManager> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
</Style> |
||||
@ -0,0 +1,203 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Animation.Animators; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Avalonia |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines a vector.
|
||||
|
/// </summary>
|
||||
|
public readonly struct PixelVector |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The X vector.
|
||||
|
/// </summary>
|
||||
|
private readonly int _x; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Y vector.
|
||||
|
/// </summary>
|
||||
|
private readonly int _y; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PixelVector"/> structure.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The X vector.</param>
|
||||
|
/// <param name="y">The Y vector.</param>
|
||||
|
public PixelVector(int x, int y) |
||||
|
{ |
||||
|
_x = x; |
||||
|
_y = y; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the X vector.
|
||||
|
/// </summary>
|
||||
|
public int X => _x; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the Y vector.
|
||||
|
/// </summary>
|
||||
|
public int Y => _y; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts the <see cref="PixelVector"/> to a <see cref="PixelPoint"/>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="a">The vector.</param>
|
||||
|
public static explicit operator PixelPoint(PixelVector a) |
||||
|
{ |
||||
|
return new PixelPoint(a._x, a._y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates the dot product of two vectors
|
||||
|
/// </summary>
|
||||
|
/// <param name="a">First vector</param>
|
||||
|
/// <param name="b">Second vector</param>
|
||||
|
/// <returns>The dot product</returns>
|
||||
|
public static int operator *(PixelVector a, PixelVector b) |
||||
|
{ |
||||
|
return a.X * b.X + a.Y * b.Y; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Scales a vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="vector">The vector</param>
|
||||
|
/// <param name="scale">The scaling factor.</param>
|
||||
|
/// <returns>The scaled vector.</returns>
|
||||
|
public static PixelVector operator *(PixelVector vector, int scale) |
||||
|
{ |
||||
|
return new PixelVector(vector._x * scale, vector._y * scale); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Scales a vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="vector">The vector</param>
|
||||
|
/// <param name="scale">The divisor.</param>
|
||||
|
/// <returns>The scaled vector.</returns>
|
||||
|
public static PixelVector operator /(PixelVector vector, int scale) |
||||
|
{ |
||||
|
return new PixelVector(vector._x / scale, vector._y / scale); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Length of the vector
|
||||
|
/// </summary>
|
||||
|
public double Length => Math.Sqrt(X * X + Y * Y); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Negates a vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="a">The vector.</param>
|
||||
|
/// <returns>The negated vector.</returns>
|
||||
|
public static PixelVector operator -(PixelVector a) |
||||
|
{ |
||||
|
return new PixelVector(-a._x, -a._y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds two vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="a">The first vector.</param>
|
||||
|
/// <param name="b">The second vector.</param>
|
||||
|
/// <returns>A vector that is the result of the addition.</returns>
|
||||
|
public static PixelVector operator +(PixelVector a, PixelVector b) |
||||
|
{ |
||||
|
return new PixelVector(a._x + b._x, a._y + b._y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Subtracts two vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="a">The first vector.</param>
|
||||
|
/// <param name="b">The second vector.</param>
|
||||
|
/// <returns>A vector that is the result of the subtraction.</returns>
|
||||
|
public static PixelVector operator -(PixelVector a, PixelVector b) |
||||
|
{ |
||||
|
return new PixelVector(a._x - b._x, a._y - b._y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Check if two vectors are equal (bitwise).
|
||||
|
/// </summary>
|
||||
|
/// <param name="other"></param>
|
||||
|
/// <returns></returns>
|
||||
|
public bool Equals(PixelVector other) |
||||
|
{ |
||||
|
return _x == other._x && _y == other._y; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Check if two vectors are nearly equal (numerically).
|
||||
|
/// </summary>
|
||||
|
/// <param name="other">The other vector.</param>
|
||||
|
/// <returns>True if vectors are nearly equal.</returns>
|
||||
|
[Pure] |
||||
|
public bool NearlyEquals(PixelVector other) |
||||
|
{ |
||||
|
const float tolerance = float.Epsilon; |
||||
|
|
||||
|
return Math.Abs(_x - other._x) < tolerance && Math.Abs(_y - other._y) < tolerance; |
||||
|
} |
||||
|
|
||||
|
public override bool Equals(object obj) |
||||
|
{ |
||||
|
if (ReferenceEquals(null, obj)) return false; |
||||
|
|
||||
|
return obj is PixelVector vector && Equals(vector); |
||||
|
} |
||||
|
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
unchecked |
||||
|
{ |
||||
|
return (_x.GetHashCode() * 397) ^ _y.GetHashCode(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static bool operator ==(PixelVector left, PixelVector right) |
||||
|
{ |
||||
|
return left.Equals(right); |
||||
|
} |
||||
|
|
||||
|
public static bool operator !=(PixelVector left, PixelVector right) |
||||
|
{ |
||||
|
return !left.Equals(right); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the string representation of the point.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The string representation of the point.</returns>
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", _x, _y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a new vector with the specified X coordinate.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The X coordinate.</param>
|
||||
|
/// <returns>The new vector.</returns>
|
||||
|
public PixelVector WithX(int x) |
||||
|
{ |
||||
|
return new PixelVector(x, _y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a new vector with the specified Y coordinate.
|
||||
|
/// </summary>
|
||||
|
/// <param name="y">The Y coordinate.</param>
|
||||
|
/// <returns>The new vector.</returns>
|
||||
|
public PixelVector WithY(int y) |
||||
|
{ |
||||
|
return new PixelVector(_x, y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue