Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom popup placement callback #15667

Merged
merged 15 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions api/Avalonia.nupkg.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.IPopupHost.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect})</Target>
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left>
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Screens.#ctor(Avalonia.Platform.IScreenImpl)</Target>
Expand All @@ -43,6 +49,12 @@
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left>
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.IPopupHost.ConfigurePosition(Avalonia.Controls.Primitives.PopupPositioning.PopupPositionRequest)</Target>
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left>
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Controls.Screens</Target>
Expand Down
11 changes: 9 additions & 2 deletions samples/ControlCatalog/Pages/FlyoutsPage.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,15 @@
</Flyout>
</Button.Flyout>
</Button>

<Button Content="Placement=Custom">
<Button.Flyout>
<Flyout Placement="Custom" CustomPopupPlacementCallback="CustomPlacementCallback">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
</UniformGrid>
</Border>
</StackPanel>
Expand Down Expand Up @@ -267,7 +275,6 @@
</Flyout>
</Button.Flyout>
</Button>

</WrapPanel>
</Border>
</StackPanel>
Expand Down
23 changes: 23 additions & 0 deletions samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Interactivity;

namespace ControlCatalog.Pages
Expand Down Expand Up @@ -71,5 +74,25 @@ private void SetXamlTexts()
"Then attach the flyout where you want it:\n" +
"<Button Content=\"Launch Flyout here\" Flyout=\"{StaticResource SharedFlyout}\" />";
}

public void CustomPlacementCallback(CustomPopupPlacement placement)
{
var r = new Random().Next();
placement.Anchor = (r % 4) switch
{
1 => PopupAnchor.Top,
2 => PopupAnchor.Left,
3 => PopupAnchor.Right,
_ => PopupAnchor.Bottom,
};
placement.Gravity = (r % 4) switch
{
1 => PopupGravity.Top,
2 => PopupGravity.Left,
3 => PopupGravity.Right,
_ => PopupGravity.Bottom,
};
placement.Offset = new Point(r % 20, r % 20);
}
}
}
55 changes: 25 additions & 30 deletions samples/ControlCatalog/Pages/ToolTipPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,24 @@
Spacing="4">
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock>

<Grid RowDefinitions="Auto,Auto,Auto,Auto"
ColumnDefinitions="Auto,Auto"
Margin="0,16,0,0"
HorizontalAlignment="Center">
<UniformGrid Columns="2"
Margin="0,16,0,0"
HorizontalAlignment="Center">
<ToggleSwitch Margin="5"
HorizontalAlignment="Center"
IsChecked="{Binding Path=(ToolTip.ServiceEnabled), RelativeSource={RelativeSource AncestorType=UserControl}}"
Content="Enable ToolTip service" />
<Border Grid.Column="0"
Grid.Row="1"
Background="{DynamicResource SystemAccentColor}"
HorizontalAlignment="Center"
IsChecked="{Binding Path=(ToolTip.ServiceEnabled), RelativeSource={RelativeSource AncestorType=UserControl}}"
Content="Enable ToolTip service" />
<ToggleSwitch Margin="5"
IsChecked="{Binding ElementName=Border, Path=(ToolTip.IsOpen)}"
HorizontalAlignment="Center"
Content="ToolTip Open" />
<Border Background="{DynamicResource SystemAccentColor}"
Margin="5"
Padding="50"
ToolTip.Tip="This is a ToolTip">
<TextBlock>Hover Here</TextBlock>
</Border>
<ToggleSwitch Grid.Column="1"
Margin="5"
Grid.Row="0"
IsChecked="{Binding ElementName=Border, Path=(ToolTip.IsOpen)}"
HorizontalAlignment="Center"
Content="ToolTip Open" />
<Border Name="Border"
Grid.Column="1"
Grid.Row="1"
Background="{DynamicResource SystemAccentColor}"
Margin="5"
Padding="50"
Expand All @@ -42,8 +35,15 @@
</ToolTip.Tip>
<TextBlock>ToolTip bottom placement</TextBlock>
</Border>
<Border Grid.Row="2"
Background="{DynamicResource SystemAccentColor}"
<Border Background="{DynamicResource SystemAccentColor}"
Margin="5"
Padding="50"
ToolTip.Placement="Custom"
ToolTip.CustomPopupPlacementCallback="CustomPlacementCallback"
ToolTip.Tip="Custom positioned tooltip">
<TextBlock>ToolTip custom placement</TextBlock>
</Border>
<Border Background="{DynamicResource SystemAccentColor}"
Margin="5"
Padding="50"
ToolTip.Tip="Hello"
Expand All @@ -67,33 +67,28 @@
<TextBlock>Moving offset</TextBlock>
</Border>

<Button Grid.Row="2" Grid.Column="1"
IsEnabled="False"
<Button IsEnabled="False"
ToolTip.ShowOnDisabled="True"
ToolTip.Tip="This control is disabled"
Margin="5"
Padding="50">
<TextBlock>ToolTip on a disabled control</TextBlock>
</Button>

<Border Grid.Row="3"
Grid.Column="0"
Background="{DynamicResource SystemAccentColor}"
<Border Background="{DynamicResource SystemAccentColor}"
Margin="5"
Padding="50"
ToolTip.Tip="Outer tooltip">
<TextBlock Background="{StaticResource SystemAccentColorDark1}" Padding="10" ToolTip.Tip="Inner tooltip" VerticalAlignment="Center">Nested ToolTips</TextBlock>
</Border>

<Border Grid.Row="3"
Grid.Column="1"
Background="{DynamicResource SystemAccentColor}"

<Border Background="{DynamicResource SystemAccentColor}"
Margin="5"
Padding="50"
ToolTip.ToolTipOpening="ToolTipOpening"
ToolTip.Tip="Should never be visible">
<TextBlock VerticalAlignment="Center">ToolTip replaced on the fly</TextBlock>
</Border>
</Grid>
</UniformGrid>
</StackPanel>
</UserControl>
26 changes: 25 additions & 1 deletion samples/ControlCatalog/Pages/ToolTipPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Markup.Xaml;

namespace ControlCatalog.Pages
Expand All @@ -19,6 +22,27 @@ private void InitializeComponent()
private void ToolTipOpening(object? sender, CancelRoutedEventArgs args)
{
((Control)args.Source!).SetValue(ToolTip.TipProperty, "New tip set from ToolTipOpening.");
}
}

public void CustomPlacementCallback(CustomPopupPlacement placement)
{
var r = new Random().Next();

placement.Anchor = (r % 4) switch
{
1 => PopupAnchor.Top,
2 => PopupAnchor.Left,
3 => PopupAnchor.Right,
_ => PopupAnchor.Bottom,
};
placement.Gravity = (r % 4) switch
{
1 => PopupGravity.Top,
2 => PopupGravity.Left,
3 => PopupGravity.Right,
_ => PopupGravity.Bottom,
};
placement.Offset = new Point(r % 20, r % 20);
}
}
}
12 changes: 12 additions & 0 deletions src/Avalonia.Controls/ContextMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public class ContextMenu : MenuBase, ISetterValue, IPopupHostProvider
public static readonly StyledProperty<Control?> PlacementTargetProperty =
Popup.PlacementTargetProperty.AddOwner<ContextMenu>();

/// <inheritdoc cref="Popup.CustomPopupPlacementCallbackProperty"/>
public static readonly StyledProperty<CustomPopupPlacementCallback?> CustomPopupPlacementCallbackProperty =
Popup.CustomPopupPlacementCallbackProperty.AddOwner<ContextMenu>();

private Popup? _popup;
private List<Control>? _attachedControls;
private IInputElement? _previousFocus;
Expand Down Expand Up @@ -185,6 +189,13 @@ public Control? PlacementTarget
set => SetValue(PlacementTargetProperty, value);
}

/// <inheritdoc cref="Popup.CustomPopupPlacementCallback"/>
public CustomPopupPlacementCallback? CustomPopupPlacementCallback
{
get => GetValue(CustomPopupPlacementCallbackProperty);
set => SetValue(CustomPopupPlacementCallbackProperty, value);
}

/// <summary>
/// Occurs when the value of the
/// <see cref="P:Avalonia.Controls.ContextMenu.IsOpen" />
Expand Down Expand Up @@ -340,6 +351,7 @@ private void Open(Control control, Control placementTarget, PlacementMode placem
_popup.PlacementConstraintAdjustment = PlacementConstraintAdjustment;
_popup.PlacementGravity = PlacementGravity;
_popup.PlacementRect = PlacementRect;
_popup.CustomPopupPlacementCallback = CustomPopupPlacementCallback;
_popup.WindowManagerAddShadowHint = WindowManagerAddShadowHint;
IsOpen = true;
_popup.IsOpen = true;
Expand Down
7 changes: 7 additions & 0 deletions src/Avalonia.Controls/ContextRequestedEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ public ContextRequestedEventArgs(PointerEventArgs pointerEventArgs)
_pointerEventArgs = pointerEventArgs;
}

/// <inheritdoc cref="ContextRequestedEventArgs()" />
public ContextRequestedEventArgs(ContextRequestedEventArgs contextRequestedEventArgs)
: this()
{
_pointerEventArgs = contextRequestedEventArgs._pointerEventArgs;
}

/// <summary>
/// Gets the x- and y-coordinates of the pointer position, optionally evaluated against a coordinate origin of a supplied <see cref="Control"/>.
/// </summary>
Expand Down
14 changes: 13 additions & 1 deletion src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider
/// <inheritdoc cref="Popup.PlacementAnchorProperty"/>
public static readonly StyledProperty<PopupAnchor> PlacementAnchorProperty =
Popup.PlacementAnchorProperty.AddOwner<PopupFlyoutBase>();

/// <inheritdoc cref="Popup.PlacementAnchorProperty"/>
public static readonly StyledProperty<PopupGravity> PlacementGravityProperty =
Popup.PlacementGravityProperty.AddOwner<PopupFlyoutBase>();

/// <inheritdoc cref="Popup.CustomPopupPlacementCallbackProperty"/>
public static readonly StyledProperty<CustomPopupPlacementCallback?> CustomPopupPlacementCallbackProperty =
Popup.CustomPopupPlacementCallbackProperty.AddOwner<PopupFlyoutBase>();

/// <summary>
/// Defines the <see cref="ShowMode"/> property
/// </summary>
Expand Down Expand Up @@ -112,6 +116,13 @@ public double VerticalOffset
set => SetValue(VerticalOffsetProperty, value);
}

/// <inheritdoc cref="Popup.CustomPopupPlacementCallback"/>
public CustomPopupPlacementCallback? CustomPopupPlacementCallback
{
get => GetValue(CustomPopupPlacementCallbackProperty);
set => SetValue(CustomPopupPlacementCallbackProperty, value);
}

/// <summary>
/// Gets or sets the desired ShowMode
/// </summary>
Expand Down Expand Up @@ -445,6 +456,7 @@ private void PositionPopup(bool showAtPointer)
Popup.HorizontalOffset = HorizontalOffset;
Popup.PlacementAnchor = PlacementAnchor;
Popup.PlacementGravity = PlacementGravity;
Popup.CustomPopupPlacementCallback = CustomPopupPlacementCallback;
if (showAtPointer)
{
Popup.Placement = PlacementMode.Pointer;
Expand Down
7 changes: 6 additions & 1 deletion src/Avalonia.Controls/PlacementMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ public enum PlacementMode
/// <summary>
/// Preferred location is to the right of the target element, with the bottom edge of popup aligned with bottom edge of the target element.
/// </summary>
RightEdgeAlignedBottom
RightEdgeAlignedBottom,

/// <summary>
/// A position and repositioning behavior that is defined by the <see cref="Popup.CustomPopupPlacementCallback"/> property.
/// </summary>
Custom
}
}
17 changes: 3 additions & 14 deletions src/Avalonia.Controls/Primitives/IPopupHost.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Diagnostics;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Metadata;
Expand All @@ -17,6 +18,7 @@ namespace Avalonia.Controls.Primitives
/// on an <see cref="OverlayLayer"/>.
/// </remarks>
[NotClientImplementable]
[Unstable(ObsoletionMessages.MayBeRemovedInAvalonia12)]
public interface IPopupHost : IDisposable, IFocusScope
{
/// <summary>
Expand Down Expand Up @@ -79,20 +81,7 @@ public interface IPopupHost : IDisposable, IFocusScope
/// Configures the position of the popup according to a target control and a set of
/// placement parameters.
/// </summary>
/// <param name="target">The placement target.</param>
/// <param name="placement">The placement mode.</param>
/// <param name="offset">The offset, in device-independent pixels.</param>
/// <param name="anchor">The anchor point.</param>
/// <param name="gravity">The popup gravity.</param>
/// <param name="constraintAdjustment">Defines how a popup position will be adjusted if the unadjusted position would result in the popup being partly constrained.</param>
/// <param name="rect">
/// The anchor rect. If null, the bounds of <paramref name="target"/> will be used.
/// </param>
void ConfigurePosition(Visual target, PlacementMode placement, Point offset,
PopupAnchor anchor = PopupAnchor.None,
PopupGravity gravity = PopupGravity.None,
PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All,
Rect? rect = null);
void ConfigurePosition(PopupPositionRequest positionRequest);

/// <summary>
/// Sets the control to display in the popup.
Expand Down
Loading
Loading