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

Popup placement properties API spec #4905

Merged
merged 26 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
564a099
Updating CommandBarFlyout's CommandBar to take monitor information in…
llongley Mar 13, 2021
0efa0e0
Adding spec information for the new API.
llongley Mar 13, 2021
b00bf0e
Fixing typo.
llongley Mar 13, 2021
880334e
Changed the design to add additional APIs to Popup instead.
llongley Apr 20, 2021
832a2b7
Updating some wording.
llongley Apr 20, 2021
9eb5871
Updating wording.
llongley Apr 20, 2021
0e1b81d
More wording updates.
llongley Apr 20, 2021
7e31ace
Update Popup-AdditionalLayoutProperties-Spec.md
llongley Apr 20, 2021
a99f757
Update Popup-AdditionalLayoutProperties-Spec.md
llongley Apr 20, 2021
fe6003a
Update Popup-AdditionalLayoutProperties-Spec.md
llongley Apr 20, 2021
1ab598f
Update Popup-AdditionalLayoutProperties-Spec.md
llongley Apr 20, 2021
864cc2a
Update Popup-AdditionalLayoutProperties-Spec.md
llongley Apr 23, 2021
614c625
Updates
MikeHillberg Apr 26, 2021
9513b70
Adding a spec for new Popup placement property APIs.
llongley Apr 26, 2021
a4a69b5
Merge remote-tracking branch 'origin/user/llongley/AddCommandBarFlyou…
llongley Apr 26, 2021
aa6eb16
Update Popup-AdditionalLayoutProperties-Spec.md
llongley Apr 26, 2021
6ab9640
Revert "Updating CommandBarFlyout's CommandBar to take monitor inform…
llongley Apr 26, 2021
a03baeb
Merge branch 'user/llongley/PopupPlacementAPISpec' of https://github.…
llongley Apr 26, 2021
5994951
Super minor cleanup
MikeHillberg Apr 27, 2021
dde1b34
Update Popup-AdditionalLayoutProperties-Spec.md
llongley Apr 27, 2021
b4a7664
Update Popup-AdditionalLayoutProperties-Spec.md
llongley Apr 27, 2021
8ce00ee
Update specs/Popup-AdditionalLayoutProperties-Spec.md
llongley Apr 28, 2021
66b27a7
Update Popup-AdditionalLayoutProperties-Spec.md
llongley Apr 28, 2021
eb51143
Update Popup-AdditionalLayoutProperties-Spec.md
llongley Apr 28, 2021
7cde2b1
Update Popup-AdditionalLayoutProperties-Spec.md
llongley May 1, 2021
3a65a85
Update Popup-AdditionalLayoutProperties-Spec.md
llongley May 11, 2021
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
226 changes: 226 additions & 0 deletions specs/Popup-AdditionalLayoutProperties-Spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
Popup additional layout properties
===

# Background

[Popups](https://docs.microsoft.com/uwp/api/Windows.UI.Xaml.Controls.Primitives.Popup)
and
[flyouts](https://docs.microsoft.com/uwp/api/Windows.UI.Xaml.Controls.Primitives.FlyoutBase)
in XAML are given two modes of display:
* They can either appear as part of the rest of XAML,
in which case they're confined to the bounds of the XAML root,
* Or they can appear in their own HWND, which
allows them to escape the bounds of the XAML root. This is common for elements such as context menus.

[CommandBarFlyout](https://docs.microsoft.com/uwp/api/Windows.UI.Xaml.Controls.CommandBarFlyout)
is one such element, but since it's defined in the WinUI 2 controls library
rather than in the OS, it does not
have access to the HWND used to allow it to escape the XAML root's bounds. As such, it has no way to
determine which monitor it's being displayed in, which makes it unable to know whether it has enough visual space
to open the popup for its secondary commands _below_ its primary commands or whether it should open them above instead.

This new API adds three properties and an event to `Popup` which will allow apps to specify where it logically
desires a popup
to be displayed relative to another element, and then respond to where system XAML was able to actually place
the popup. This will allow elements such as `CommandBarFlyout` to be able to rely on system XAML for the placement of their
child popups in a way that will take monitor or app bounds into account without needing to do that accounting manually.

## Visual Examples

**Opening down**

When CommandBarFlyout has enough space below its primary commands, we want it to open down.

Secondary commands flyout closed:

![Shows the CommandBarFlyout not yet open with sufficient monitor space below it](images/CommandBarFlyout-SufficientSpace.png)

Secondary commands flyout open:

![Shows the CommandBarFlyout opened down](images/CommandBarFlyout-SufficientSpace-Open.png)

**Opening up**

When CommandBarFlyout does *not* have enough space below its primary commands, we want it to be able to open up.

Secondary commands flyout closed:

![Shows the CommandBarFlyout not yet open with insufficient monitor space below it](images/CommandBarFlyout-InsufficientSpace.png)

Secondary commands flyout open:

![Shows the CommandBarFlyout opened up](images/CommandBarFlyout-InsufficientSpace-Open.png)

# API Pages

## Popup class

```csharp
class Popup
{
// Existing APIs
// ...

// New APIs
UIElement PlacementTarget { get; set; }
PopupPlacementMode DesiredPlacement { get; set; }
PopupPlacementMode ActualPlacement { get; }

public event System.EventHandler<object> PlacementChanged;
}
```

The example below shows how the `Placement` APIs are used by the
[CommandBarFlyout](https://docs.microsoft.com/uwp/api/Windows.UI.Xaml.Controls.CommandBarFlyout)
to position its
[CommandBarFlyoutCommandBar](https://docs.microsoft.com/uwp/api/Windows.UI.Xaml.Controls.Primitives.CommandBarFlyoutCommandBar)'s
secondary commands Popup, and how to respond to the event raised when XAML places the Popup.

```xml
<!-- Part of the CommandBarFlyoutCommandBar's default template -->
<Popup
x:Name="OverflowPopup"
PlacementTarget="{Binding ElementName=PrimaryItemsRoot}"
DesiredPlacement="Bottom">
</Popup>
```

```csharp
void OnApplyTemplate()
{
m_overflowPopup = GetTemplateChild("OverflowPopup");
m_overflowPopup.PlacementChanged += OnOverflowPopupPlacementChanged;
}

void OnOverflowPopupPlacementChanged(object sender, object args)
{
UpdateVisualState(useTransitions: false);
}

void UpdateVisualState(bool useTransitions)
{
if (m_overflowPopup.ActualPlacement == PopupPlacementMode.Top)
{
VisualStateManager.GoToState(this, "ExpandedUp", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "ExpandedDown", useTransitions);
}
}
```

## Popup.PlacementTarget property

Use this property to describe which element the `Popup` should be positioned relative to.
Defaults to `null`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the addition of this property will require some reworking of the existing Popup documentation that talks about placement.

In fact, we forgot to update the Popup documentation when we added ShouldConstrainToRootBounds. The Popup documentation still says right there in the first sentence, "Displays content on top of existing content, within the bounds of the application window" which is no longer true if ShouldConstrainToRootBounds is false.


If this is `null`, then `DesiredPlacement` is ignored, `ActualPlacement` is always `None`, and
llongley marked this conversation as resolved.
Show resolved Hide resolved
`PlacementChanged` is never raised. If the `Popup` is in the visual tree, `PlacementTarget` will override what its
position would otherwise be set to by layout. Setting `PlacementTarget` to an element under a different XAML root than
`Popup.XamlRoot` is invalid and will throw an `ArgumentException`.

Ignored if `DesiredPlacement` is `None`.

_Spec note: this property is analogous to the
[FlyoutBase.Target](https://docs.microsoft.com/uwp/api/Windows.UI.Xaml.Controls.Primitives.FlyoutBase.Target)
property, but `Popup.Target` looked confusing, so we added the `Placement` prefix._

## Popup.DesiredPlacement property

Use this property to describe how you would ideally like the `Popup`
positioned relative to `PlacementTarget`. Defaults to `None`.

If this is `None`, then `PlacementTarget` is ignored,
`ActualPlacement` is always `None` and `PlacementChanged` is never raised.
llongley marked this conversation as resolved.
Show resolved Hide resolved
If both `DesiredPlacement` and `PlacementTarget` are set and `HorizontalOffset` and/or `VerticalOffset`
are also set, then the latter two properties will offset the `Popup` from where it would have been
placed by `DesiredPlacement` and `PlacementTarget` alone.

Ignored if `PlacementTarget` is null.

## Popup.ActualPlacement property

Use this read-only property to determine where the popup was positioned.
Will always be `None` if either `PlacementTarget` and `DesiredPlacement` are not set.
llongley marked this conversation as resolved.
Show resolved Hide resolved

## Popup.PlacementChanged event
llongley marked this conversation as resolved.
Show resolved Hide resolved

Raised whenever XAML changes the value of `ActualPlacement`,
which allows apps to respond to where a `Popup` was placed.

For example, use this to determine the visual state to go into,
based on whether a `Popup` is appearing above or below `PlacementTarget`.

This event is raised before the screen is refreshed, meaning that any visual changes made
in response to this event can be made before anything is drawn to the screen at the new position.
Will never be raised if either `PlacementTarget` and `DesiredPlacement` are not set.
llongley marked this conversation as resolved.
Show resolved Hide resolved

## PopupPlacementMode enum

_Spec note: This is designed to align with the existing
[FlyoutPlacementMode](https://docs.microsoft.com/uwp/api/Windows.UI.Xaml.Controls.Primitives.FlyoutPlacementMode)
enum, with the exception of the absence of "Full". "Full" is absent since developers should use a Flyout
if they want something full-screen._

```csharp
enum PopupPlacementMode
llongley marked this conversation as resolved.
Show resolved Hide resolved
{
None,
Top,
llongley marked this conversation as resolved.
Show resolved Hide resolved
Bottom,
Left,
Right,
TopEdgeAlignedLeft,
TopEdgeAlignedRight,
BottomEdgeAlignedLeft,
BottomEdgeAlignedRight,
LeftEdgeAlignedTop,
LeftEdgeAlignedBottom,
RightEdgeAlignedTop,
RightEdgeAlignedBottom
}
```

_Spec note: The meaning of "Left" and "Right" are swapped if
[FlowDirection](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.frameworkelement.flowdirection)
is `RightToLeft`._

# API Details

```csharp
namespace Windows.UI.Xaml.Controls.Primitives
{
[webhosthidden]
enum PopupPlacementMode
{
None,
Top,
Bottom,
Left,
Right,
TopEdgeAlignedLeft,
TopEdgeAlignedRight,
BottomEdgeAlignedLeft,
BottomEdgeAlignedRight,
LeftEdgeAlignedTop,
LeftEdgeAlignedBottom,
RightEdgeAlignedTop,
RightEdgeAlignedBottom
};

[webhosthidden]
interface IPopup2
{
Windows.UI.Xaml.UIElement PlacementTarget;
Windows.UI.Xaml.Controls.Primitives.PopupPlacementMode DesiredPlacement;
Windows.UI.Xaml.Controls.Primitives.PopupPlacementMode ActualPlacement { get; };

event Windows.Foundation.EventHandler<Object> PlacementChanged;

static Windows.UI.Xaml.DependencyProperty PlacementTargetProperty{ get; };
static Windows.UI.Xaml.DependencyProperty DesiredPlacementProperty{ get; };
static Windows.UI.Xaml.DependencyProperty ActualPlacementProperty{ get; };
};
}
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added specs/images/CommandBarFlyout-SufficientSpace.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.