Skip to content
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
78 changes: 46 additions & 32 deletions src/Wpf.Ui/Controls/ComboBox/ComboBox.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,10 @@
<Border
x:Name="ContentBorder"
Grid.Row="0"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
Padding="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Expand Down Expand Up @@ -262,36 +262,50 @@
Placement="{TemplateBinding Popup.Placement}"
PopupAnimation="{TemplateBinding Popup.PopupAnimation}"
VerticalOffset="1">
<Border
x:Name="DropDownBorder"
MinWidth="{TemplateBinding ActualWidth}"
Margin="0"
Padding="0,4,0,6"
Background="{DynamicResource ComboBoxDropDownBackground}"
BorderBrush="{DynamicResource ComboBoxDropDownBorderBrush}"
BorderThickness="1"
CornerRadius="{DynamicResource PopupCornerRadius}"
SnapsToDevicePixels="True">
<Border.RenderTransform>
<TranslateTransform />
</Border.RenderTransform>
<Grid>
<controls:DynamicScrollViewer
MaxHeight="{TemplateBinding MaxDropDownHeight}"
Margin="0"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
SnapsToDevicePixels="True"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}"
TextElement.Foreground="{TemplateBinding Foreground}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<StackPanel
IsItemsHost="True"
KeyboardNavigation.DirectionalNavigation="Contained"
TextElement.FontSize="{TemplateBinding FontSize}" />
</controls:DynamicScrollViewer>
</Grid>
</Border>
<controls:EffectThicknessDecorator
AnimationDelay="00:00:00.167"
AnimationElement="{Binding ElementName=DropDownBorder}"
Thickness="30,0,30,30">
<Border
x:Name="DropDownBorder"
MinWidth="{TemplateBinding ActualWidth}"
Margin="0"
Padding="0,4,0,6"
Background="{DynamicResource ComboBoxDropDownBackground}"
BorderBrush="{DynamicResource ComboBoxDropDownBorderBrush}"
BorderThickness="1"
CornerRadius="{DynamicResource PopupCornerRadius}"
SnapsToDevicePixels="True">
<Border.RenderTransform>
<TranslateTransform />
</Border.RenderTransform>
<Border.Effect>
<DropShadowEffect
BlurRadius="20"
Direction="270"
Opacity="0.135"
ShadowDepth="10"
Color="#202020" />
</Border.Effect>

<Grid>
<controls:DynamicScrollViewer
MaxHeight="{TemplateBinding MaxDropDownHeight}"
Margin="0"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
SnapsToDevicePixels="True"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}"
TextElement.Foreground="{TemplateBinding Foreground}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<StackPanel
IsItemsHost="True"
KeyboardNavigation.DirectionalNavigation="Contained"
TextElement.FontSize="{TemplateBinding FontSize}" />
</controls:DynamicScrollViewer>
</Grid>
</Border>
</controls:EffectThicknessDecorator>
</Popup>
</Grid>
</Border>
Expand Down
47 changes: 30 additions & 17 deletions src/Wpf.Ui/Controls/ContextMenu/ContextMenu.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
All Rights Reserved.
-->

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Wpf.Ui.Controls">

<Style x:Key="UiContextMenu" TargetType="{x:Type ContextMenu}">
<Setter Property="TextElement.Foreground" Value="{DynamicResource ContextMenuForeground}" />
Expand All @@ -23,22 +26,32 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContextMenu}">
<Border
x:Name="Border"
Padding="0,3,0,3"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1"
CornerRadius="8">
<Border.RenderTransform>
<TranslateTransform />
</Border.RenderTransform>
<StackPanel
ClipToBounds="True"
IsItemsHost="True"
KeyboardNavigation.DirectionalNavigation="Cycle"
Orientation="Vertical" />
</Border>
<controls:EffectThicknessDecorator Thickness="30">
<Border
x:Name="Border"
Padding="0,3,0,3"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1"
CornerRadius="8">
<Border.Effect>
<DropShadowEffect
BlurRadius="20"
Direction="270"
Opacity="0.135"
ShadowDepth="10"
Color="#202020" />
</Border.Effect>
<Border.RenderTransform>
<TranslateTransform />
</Border.RenderTransform>
<StackPanel
ClipToBounds="True"
IsItemsHost="True"
KeyboardNavigation.DirectionalNavigation="Cycle"
Orientation="Vertical" />
</Border>
</controls:EffectThicknessDecorator>
<ControlTemplate.Triggers>
<Trigger Property="IsOpen" Value="True">
<Trigger.EnterActions>
Expand Down
191 changes: 191 additions & 0 deletions src/Wpf.Ui/Controls/EffectThicknessDecorator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
// All Rights Reserved.

using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace Wpf.Ui.Controls;

public class EffectThicknessDecorator : Decorator
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider giving this class a doc block (explaining the purpose) that effectively repeats this part from the PR:

This is because effects get clipped. [..] The solution is to add a decorator that wraps around it.

{
public static readonly DependencyProperty ThicknessProperty =
DependencyProperty.Register(nameof(Thickness), typeof(Thickness), typeof(EffectThicknessDecorator), new PropertyMetadata(new Thickness(35), OnThicknessChanged));

public static readonly DependencyProperty AnimationDelayProperty =
DependencyProperty.Register(nameof(AnimationDelay), typeof(TimeSpan), typeof(EffectThicknessDecorator), new PropertyMetadata(TimeSpan.Zero));

public static readonly DependencyProperty AnimationElementProperty =
DependencyProperty.Register(nameof(AnimationElement), typeof(UIElement), typeof(EffectThicknessDecorator), new PropertyMetadata(default(UIElement)));

private PopupContainer? _popupContainer;

public EffectThicknessDecorator()
{
SizeChanged += (_, _) => UpdateLayout();
}

public TimeSpan AnimationDelay
{
get { return (TimeSpan)GetValue(AnimationDelayProperty); }
set { SetValue(AnimationDelayProperty, value); }
}

public UIElement? AnimationElement
{
get { return (UIElement?)GetValue(AnimationElementProperty); }
set { SetValue(AnimationElementProperty, value); }
}

/// <summary>
/// Gets or sets the thickness of the effect around the containing element.
/// </summary>
public Thickness Thickness
{
get { return (Thickness)GetValue(ThicknessProperty); }
set { SetValue(ThicknessProperty, value); }
}

/// <inheritdoc />
protected override int VisualChildrenCount => 1;

/// <inheritdoc />
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
base.OnVisualParentChanged(oldParent);

if (IsInitialized)
{
SetPopupContainer();
}
}

/// <inheritdoc />
protected override Visual GetVisualChild(int index)
{
// Only 1 child...
return Child;
}

/// <inheritdoc />
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);

SetPopupContainer();
}

private void SetPopupContainer()
{
PopupContainer? popupContainer = null;

switch (VisualParent)
{
case ContextMenu contextMenu:
popupContainer = new PopupContainer(contextMenu);
break;
case ToolTip toolTip:
popupContainer = new PopupContainer(toolTip);
break;
default:
if (GetParentPopup(this) is { } parentPopup)
{
popupContainer = new PopupContainer(parentPopup);
}

break;
}

if (popupContainer == null || _popupContainer?.FrameworkElement == popupContainer.FrameworkElement)
{
return;
}

popupContainer.Opened += (_, _) =>
{
if (AnimationElement is { Effect: { } effect } animationElement && AnimationDelay.Ticks > 0)
{
animationElement.Effect = null;

Task.Delay(AnimationDelay).ContinueWith(_ => Dispatcher.Invoke(() => animationElement.Effect = effect));
}
};

_popupContainer = popupContainer;
ApplyMargin();
}

private static Popup? GetParentPopup(FrameworkElement element)
{
while (true)
{
switch (element.Parent)
{
case Popup popup:
return popup;
case FrameworkElement frameworkElement:
element = frameworkElement;
continue;
}

if (VisualTreeHelper.GetParent(element) is FrameworkElement parent)
{
element = parent;
continue;
}

return null;
}
}

private static void OnThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is EffectThicknessDecorator decorator)
{
decorator.ApplyMargin();
}
}

private void ApplyMargin()
{
_popupContainer?.SetMargin(Thickness);
}

private class PopupContainer
{
private readonly ContextMenu? _contextMenu;
private readonly Popup? _popup;
private readonly ToolTip? _toolTip;

public PopupContainer(ContextMenu contextMenu)
{
_contextMenu = contextMenu;
contextMenu.Opened += (sender, args) => Opened?.Invoke(sender, args);
}

public PopupContainer(ToolTip toolTip)
{
_toolTip = toolTip;
toolTip.Opened += (sender, args) => Opened?.Invoke(sender, args);
}

public PopupContainer(Popup popup)
{
_popup = popup;
popup.Opened += (sender, args) => Opened?.Invoke(sender, args);
}

public event EventHandler Opened;

public FrameworkElement? FrameworkElement => _contextMenu ?? _toolTip ?? _popup?.Child as FrameworkElement;

public void SetMargin(Thickness margin)
{
if (FrameworkElement is { } frameworkElement)
{
frameworkElement.Margin = margin;
}
}
}
}
Loading