Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Refactor PopupBox custom popup placement
Custom placement (and Popup offsets) now adjusts according to the required space for the drop shadow
  • Loading branch information
nicolaihenriksen committed Apr 2, 2023
commit d995ab6cf1283a24c035364754fe7a6ad20d349f
20 changes: 20 additions & 0 deletions MaterialDesignThemes.Wpf/DpiHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ public static double TransformToDeviceY(Visual visual, double y)
return TransformToDeviceY(y);
}

public static double TransformFromDeviceY(Visual visual, double y)
{
var source = PresentationSource.FromVisual(visual);
if (source?.CompositionTarget != null) return y / source.CompositionTarget.TransformToDevice.M22;

return TransformFromDeviceY(y);
}

public static double TransformToDeviceX(Visual visual, double x)
{
var source = PresentationSource.FromVisual(visual);
Expand All @@ -39,8 +47,20 @@ public static double TransformToDeviceX(Visual visual, double x)
return TransformToDeviceX(x);
}

public static double TransformFromDeviceX(Visual visual, double x)
{
var source = PresentationSource.FromVisual(visual);
if (source?.CompositionTarget != null) return x / source.CompositionTarget.TransformToDevice.M11;

return TransformFromDeviceX(x);
}

public static double TransformToDeviceY(double y) => y * DpiY / StandardDpiY;

public static double TransformFromDeviceY(double y) => y / DpiY * StandardDpiY;

public static double TransformToDeviceX(double x) => x * DpiX / StandardDpiX;

public static double TransformFromDeviceX(double x) => x / DpiX * StandardDpiX;
}
}
36 changes: 35 additions & 1 deletion MaterialDesignThemes.Wpf/ElevationAssist.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Windows.Media.Effects;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media.Effects;

namespace MaterialDesignThemes.Wpf;

Expand Down Expand Up @@ -61,3 +63,35 @@ public static class ElevationAssist

public static DropShadowEffect? GetDropShadow(Elevation elevation) => ElevationInfo.GetDropShadow(elevation);
}

public class ElevationMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Elevation elevation && elevation != Elevation.Dp0)
{
return new Thickness(ElevationInfo.GetDropShadow(elevation)!.BlurRadius);
}
return new Thickness(0);
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}

public class ElevationRadiusConverter : IValueConverter
{
public double Multiplier { get; set; } = 1.0;

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Elevation elevation && elevation != Elevation.Dp0)
{
return ElevationInfo.GetDropShadow(elevation)!.BlurRadius * Multiplier;
}
return 0;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}
68 changes: 39 additions & 29 deletions MaterialDesignThemes.Wpf/PopupBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -518,69 +518,79 @@ protected void Close()
SetCurrentValue(IsPopupOpenProperty, false);
}

private CustomPopupPlacement[] GetPopupPlacement(Size popupSize, Size targetSize, Point offset)
private CustomPopupPlacement[] GetPopupPlacement(Size popupSize, Size targetSize, Point _)
{
double x, y;
double offsetX = PopupHorizontalOffset;
double offsetY = PopupVerticalOffset;

if (FlowDirection == FlowDirection.RightToLeft)
offset.X += targetSize.Width / 2;
Size popupSizeTransformed = new Size(DpiHelper.TransformFromDeviceX(this, popupSize.Width), DpiHelper.TransformFromDeviceY(this, popupSize.Height));
Size targetSizeTransformed = new Size(DpiHelper.TransformFromDeviceX(this, targetSize.Width), DpiHelper.TransformFromDeviceY(this, targetSize.Height));

switch (PlacementMode)
{
case PopupBoxPlacementMode.BottomAndAlignLeftEdges:
x = 0 - Math.Abs(offset.X * 3);
y = targetSize.Height - Math.Abs(offset.Y);
x = 0 + offsetX - UseOffsetIfRtl(popupSizeTransformed.Width);
y = targetSizeTransformed.Height + offsetY;
break;
case PopupBoxPlacementMode.BottomAndAlignRightEdges:
x = 0 - popupSize.Width + targetSize.Width - offset.X;
y = targetSize.Height - Math.Abs(offset.Y);
x = 0 - popupSizeTransformed.Width + targetSizeTransformed.Width + offsetX + UseOffsetIfRtl(popupSizeTransformed.Width - targetSizeTransformed.Width * 2);
y = targetSizeTransformed.Height + offsetY;
break;
case PopupBoxPlacementMode.BottomAndAlignCentres:
x = targetSize.Width / 2 - popupSize.Width / 2 - Math.Abs(offset.X * 2);
y = targetSize.Height - Math.Abs(offset.Y);
x = (targetSizeTransformed.Width - popupSizeTransformed.Width) / 2 + offsetX - UseOffsetIfRtl(targetSizeTransformed.Width);
y = targetSizeTransformed.Height + offsetY;
break;
case PopupBoxPlacementMode.TopAndAlignLeftEdges:
x = 0 - Math.Abs(offset.X * 3);
y = 0 - popupSize.Height - Math.Abs(offset.Y * 2);
x = 0 + offsetX - UseOffsetIfRtl(popupSizeTransformed.Width);
y = 0 - popupSizeTransformed.Height + offsetY;
break;
case PopupBoxPlacementMode.TopAndAlignRightEdges:
x = 0 - popupSize.Width + targetSize.Width - offset.X;
y = 0 - popupSize.Height - Math.Abs(offset.Y * 2);
x = 0 - popupSizeTransformed.Width + targetSizeTransformed.Width + offsetX + UseOffsetIfRtl(popupSizeTransformed.Width - targetSizeTransformed.Width * 2);
y = 0 - popupSizeTransformed.Height + offsetY;
break;
case PopupBoxPlacementMode.TopAndAlignCentres:
x = targetSize.Width / 2 - popupSize.Width / 2 - Math.Abs(offset.X * 2);
y = 0 - popupSize.Height - Math.Abs(offset.Y * 2);
x = (targetSizeTransformed.Width - popupSizeTransformed.Width) / 2 + offsetX - UseOffsetIfRtl(targetSizeTransformed.Width);
y = 0 - popupSizeTransformed.Height + offsetY;
break;
case PopupBoxPlacementMode.LeftAndAlignTopEdges:
x = 0 - popupSize.Width - Math.Abs(offset.X * 2);
y = 0 - Math.Abs(offset.Y * 3);
x = 0 - popupSizeTransformed.Width + offsetX + UseOffsetIfRtl(popupSizeTransformed.Width);
y = 0 + offsetY;
break;
case PopupBoxPlacementMode.LeftAndAlignBottomEdges:
x = 0 - popupSize.Width - Math.Abs(offset.X * 2);
y = 0 - (popupSize.Height - targetSize.Height);
x = 0 - popupSizeTransformed.Width + offsetX + UseOffsetIfRtl(popupSizeTransformed.Width);
y = 0 - (popupSizeTransformed.Height - targetSizeTransformed.Height) + offsetY;
break;
case PopupBoxPlacementMode.LeftAndAlignMiddles:
x = 0 - popupSize.Width - Math.Abs(offset.X * 2);
y = targetSize.Height / 2 - popupSize.Height / 2 - Math.Abs(offset.Y * 2);
x = 0 - popupSizeTransformed.Width + offsetX + UseOffsetIfRtl(popupSizeTransformed.Width);
y = 0 - (popupSizeTransformed.Height - targetSizeTransformed.Height) / 2 + offsetY;
break;
case PopupBoxPlacementMode.RightAndAlignTopEdges:
x = targetSize.Width;
y = 0 - Math.Abs(offset.X * 3);
x = targetSizeTransformed.Width + offsetX - UseOffsetIfRtl(popupSizeTransformed.Width + targetSizeTransformed.Width * 2);
y = 0 + offsetY;
break;
case PopupBoxPlacementMode.RightAndAlignBottomEdges:
x = targetSize.Width;
y = 0 - (popupSize.Height - targetSize.Height);
x = targetSizeTransformed.Width + offsetX - UseOffsetIfRtl(popupSizeTransformed.Width + targetSizeTransformed.Width * 2);
y = 0 - (popupSizeTransformed.Height - targetSizeTransformed.Height) + offsetY;
break;
case PopupBoxPlacementMode.RightAndAlignMiddles:
x = targetSize.Width;
y = targetSize.Height / 2 - popupSize.Height / 2 - Math.Abs(offset.Y * 2);
x = targetSizeTransformed.Width + offsetX - UseOffsetIfRtl(popupSizeTransformed.Width + targetSizeTransformed.Width * 2);
y = 0 - (popupSizeTransformed.Height - targetSizeTransformed.Height) / 2 + offsetY;
break;
default:
throw new ArgumentOutOfRangeException();
}

_popupPointFromLastRequest = new Point(x, y);
double xTransformed = DpiHelper.TransformToDeviceX(x);
double yTransformed = DpiHelper.TransformToDeviceY(y);

_popupPointFromLastRequest = new Point(xTransformed, yTransformed);
return new[] { new CustomPopupPlacement(_popupPointFromLastRequest, PopupPrimaryAxis.Horizontal) };

double UseOffsetIfRtl(double rtlOffset)
{
return FlowDirection == FlowDirection.LeftToRight ? 0 : rtlOffset;
}
}

private void AnimateChildrenIn(bool reverse)
Expand Down Expand Up @@ -797,4 +807,4 @@ private static object CoerceToolTipIsEnabled(DependencyObject dependencyObject,
return popupBox.IsPopupOpen ? false : value;
}
}
}
}
22 changes: 15 additions & 7 deletions MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.PopupBox.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ToolTip.xaml" />
</ResourceDictionary.MergedDictionaries>

<wpf:ElevationMarginConverter x:Key="ElevationMarginConverter" />
<wpf:ElevationRadiusConverter x:Key="ElevationRadiusConverter" Multiplier="-1" />
<wpf:ElevationRadiusConverter x:Key="RtlElevationRadiusConverter" Multiplier="1" />

<Style x:Key="MaterialDesignPopupBoxButton" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{DynamicResource MaterialDesignBody}" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
Expand Down Expand Up @@ -84,9 +88,9 @@
<Setter Property="Focusable" Value="True" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Padding" Value="0,8,0,8" />
<Setter Property="PopupHorizontalOffset" Value="5" />
<Setter Property="PopupHorizontalOffset" Value="0" />
<Setter Property="PopupUniformCornerRadius" Value="2" />
<Setter Property="PopupVerticalOffset" Value="5" />
<Setter Property="PopupVerticalOffset" Value="0" />
<Setter Property="ToolTipService.IsEnabled" Value="{Binding IsPopupOpen, RelativeSource={RelativeSource Self}, Converter={StaticResource NotConverter}}"/>
<Setter Property="Template">
<Setter.Value>
Expand Down Expand Up @@ -126,19 +130,20 @@
<wpf:PopupEx x:Name="PART_Popup"
AllowsTransparency="True"
CustomPopupPlacementCallback="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PopupPlacementMethod}"
HorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PopupHorizontalOffset}"
HorizontalOffset="{TemplateBinding wpf:ElevationAssist.Elevation, Converter={StaticResource ElevationRadiusConverter}}"
VerticalOffset="{TemplateBinding wpf:ElevationAssist.Elevation, Converter={StaticResource ElevationRadiusConverter}}"
IsOpen="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsPopupOpen, Mode=TwoWay}"
Placement="Custom"
PlacementTarget="{Binding ElementName=PART_Toggle}"
PopupAnimation="Fade"
VerticalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PopupVerticalOffset}">
<wpf:Card Margin="5"
PopupAnimation="Fade">
<wpf:Card Margin="{TemplateBinding wpf:ElevationAssist.Elevation, Converter={StaticResource ElevationMarginConverter}}"
Padding="{TemplateBinding Padding}"
Content="{TemplateBinding PopupContent}"
ContentTemplate="{TemplateBinding PopupContentTemplate}"
Foreground="{DynamicResource MaterialDesignBody}"
RenderOptions.ClearTypeHint="Enabled"
UniformCornerRadius="{TemplateBinding PopupUniformCornerRadius}">
UniformCornerRadius="{TemplateBinding PopupUniformCornerRadius}"
wpf:ElevationAssist.Elevation="{TemplateBinding wpf:ElevationAssist.Elevation}">
<wpf:Card.Resources>
<Style TargetType="Button" BasedOn="{StaticResource MaterialDesignPopupBoxButton}" />
</wpf:Card.Resources>
Expand All @@ -149,6 +154,9 @@
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.38" />
</Trigger>
<Trigger Property="FlowDirection" Value="RightToLeft">
<Setter TargetName="PART_Popup" Property="HorizontalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(wpf:ElevationAssist.Elevation), Converter={StaticResource RtlElevationRadiusConverter}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
Expand Down