Skip to content

Commit ab1c0e5

Browse files
committed
looking GOOD now! :D
1 parent ca046a5 commit ab1c0e5

File tree

6 files changed

+263
-25
lines changed

6 files changed

+263
-25
lines changed

MainDemo.Wpf/Home.xaml

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
<ResourceDictionary>
1111
<ResourceDictionary.MergedDictionaries>
1212
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
13+
<!-- throw in some extra colour for out floating action button -->
14+
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/MaterialDesignColor.Green.Named.Primary.xaml" />
1315
</ResourceDictionary.MergedDictionaries>
1416
</ResourceDictionary>
1517
</UserControl.Resources>
@@ -132,15 +134,44 @@
132134
</Grid>
133135
</materialDesign:Card>
134136
</StackPanel>
135-
<materialDesign:PopupBox Style="{StaticResource MaterialDesignMultiFloatingActionPopupBox}"
136-
HorizontalAlignment="Right" VerticalAlignment="Bottom"
137-
Margin="32">
138-
<ItemsControl>
139-
<Button ToolTip="Mail">@</Button>
140-
<Button ToolTip="Hashtag">#</Button>
141-
</ItemsControl>
142-
</materialDesign:PopupBox>
143137
</Grid>
144138
</ScrollViewer>
139+
<materialDesign:PopupBox Style="{StaticResource MaterialDesignMultiFloatingActionPopupBox}"
140+
HorizontalAlignment="Right" VerticalAlignment="Bottom"
141+
Margin="32" Grid.Row="1">
142+
<StackPanel>
143+
<Button ToolTip="GitHub" Click="GitHubButton_OnClick">
144+
<Viewbox Width="24" Height="24">
145+
<Canvas Width="24" Height="24">
146+
<Path Data="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z"
147+
Fill="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Button}, Path=Foreground}" />
148+
</Canvas>
149+
</Viewbox>
150+
</Button>
151+
<Button ToolTip="Twitter" Click="TwitterButton_OnClick"
152+
Background="{DynamicResource PrimaryHueMidBrush}"
153+
Foreground="{DynamicResource PrimaryHueMidForegroundBrush}"
154+
>#</Button>
155+
<Button ToolTip="Chat" Click="ChatButton_OnClick">
156+
<!-- mix up the colours by brinking in a named palette (see merged dictionaries at top) -->
157+
<Button.Background>
158+
<SolidColorBrush Color="{StaticResource GreenPrimary500}"></SolidColorBrush>
159+
</Button.Background>
160+
<Button.Foreground>
161+
<SolidColorBrush Color="{StaticResource GreenPrimary500Foreground}"></SolidColorBrush>
162+
</Button.Foreground>
163+
<Viewbox Width="16" Height="16">
164+
<Canvas Width="24" Height="24">
165+
<Path Data="M7.5,2A2,2 0 0,1 9.5,4A2,2 0 0,1 7.5,6A2,2 0 0,1 5.5,4A2,2 0 0,1 7.5,2M6,7H9A2,2 0 0,1 11,9V14.5H9.5V22H5.5V14.5H4V9A2,2 0 0,1 6,7M16.5,2A2,2 0 0,1 18.5,4A2,2 0 0,1 16.5,6A2,2 0 0,1 14.5,4A2,2 0 0,1 16.5,2M15,22V16H12L14.59,8.41C14.84,7.59 15.6,7 16.5,7C17.4,7 18.16,7.59 18.41,8.41L21,16H18V22H15Z"
166+
Fill="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Button}, Path=Foreground}" />
167+
</Canvas>
168+
</Viewbox>
169+
</Button>
170+
<Button ToolTip="Email" Click="EmailButton_OnClick"
171+
Background="{DynamicResource SecondaryAccentBrush}"
172+
Foreground="{DynamicResource SecondaryAccentForegroundBrush}"
173+
>@</Button>
174+
</StackPanel>
175+
</materialDesign:PopupBox>
145176
</Grid>
146177
</UserControl>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Windows;
5+
using System.Windows.Media;
6+
7+
namespace MaterialDesignThemes.Wpf
8+
{
9+
internal static class Extensions
10+
{
11+
public static IEnumerable<DependencyObject> VisualDepthFirstTraversal(this DependencyObject node)
12+
{
13+
if (node == null) throw new ArgumentNullException(nameof(node));
14+
15+
yield return node;
16+
17+
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(node); i++)
18+
{
19+
var child = VisualTreeHelper.GetChild(node, i);
20+
foreach (var descendant in child.VisualDepthFirstTraversal())
21+
{
22+
yield return descendant;
23+
}
24+
}
25+
}
26+
27+
public static bool IsDescendant(this DependencyObject parent, DependencyObject node)
28+
{
29+
return node != null && parent.VisualDepthFirstTraversal().Contains(node);
30+
}
31+
}
32+
}

MaterialDesignThemes.Wpf/MaterialDesignThemes.Wpf.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@
244244
<Compile Include="DialogSession.cs" />
245245
<Compile Include="DialogHost.cs" />
246246
<Compile Include="DrawerHost.cs" />
247+
<Compile Include="Extensions.cs" />
247248
<Compile Include="ListSortDirectionIndicator.cs" />
248249
<Compile Include="MaterialDataGridComboBoxColumn.cs" />
249250
<Compile Include="MaterialDataGridTextColumn.cs" />

MaterialDesignThemes.Wpf/PopupBox.cs

Lines changed: 136 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Windows.Input;
1111
using System.Windows.Markup;
1212
using System.Windows.Media;
13+
using System.Windows.Media.Animation;
1314
using Controlz;
1415

1516
namespace MaterialDesignThemes.Wpf
@@ -45,23 +46,43 @@ public enum PopupBoxPlacementMode
4546
TopAndAlignCentres
4647
}
4748

49+
/// <summary>
50+
/// Defines what causes the <see cref="PopupBox"/> to open it's popup.
51+
/// </summary>
52+
public enum PopupBoxPopupMode
53+
{
54+
/// <summary>
55+
/// Open when the toggle button is clicked.
56+
/// </summary>
57+
Click,
58+
/// <summary>
59+
/// Open when the mouse goes over the toggle button.
60+
/// </summary>
61+
MouseOver,
62+
/// <summary>
63+
/// Open when the mouse goes over the toggle button, or the space in which the popup box would occupy should it be open.
64+
/// </summary>
65+
MouseOverEager
66+
}
67+
4868
/// <summary>
4969
/// Popup box, similar to a <see cref="ComboBox"/>, but allows more customizable content.
5070
/// </summary>
5171
[TemplatePart(Name = PopupPartName, Type = typeof(Popup))]
52-
[TemplatePart(Name = TogglePartName, Type = typeof(ToggleButton))]
72+
[TemplatePart(Name = PopupContentControlPartName, Type = typeof(ContentControl))]
5373
[TemplateVisualState(GroupName = "PopupStates", Name = PopupIsOpenStateName)]
5474
[TemplateVisualState(GroupName = "PopupStates", Name = PopupIsClosedStateName)]
5575
[ContentProperty("PopupContent")]
5676
public class PopupBox : ContentControl
5777
{
5878
public const string PopupPartName = "PART_Popup";
5979
public const string TogglePartName = "PART_Toggle";
80+
public const string PopupContentControlPartName = "PART_PopupContentControl";
6081
public const string PopupIsOpenStateName = "IsOpen";
6182
public const string PopupIsClosedStateName = "IsClosed";
6283
private PopupEx _popup;
63-
private ToggleButton _toggleButton;
64-
84+
private ContentControl _popupContentControl;
85+
6586
static PopupBox()
6687
{
6788
DefaultStyleKeyProperty.OverrideMetadata(typeof(PopupBox), new FrameworkPropertyMetadata(typeof(PopupBox)));
@@ -137,13 +158,21 @@ private static void IsPopupOpenPropertyChangedCallback(DependencyObject dependen
137158
{
138159
var popupBox = (PopupBox) dependencyObject;
139160
var newValue = (bool)dependencyPropertyChangedEventArgs.NewValue;
161+
if (popupBox.PopupMode == PopupBoxPopupMode.Click)
162+
{
163+
if (newValue)
164+
Mouse.Capture(popupBox, CaptureMode.SubTree);
165+
else
166+
Mouse.Capture(null);
167+
}
168+
140169
if (newValue)
141-
Mouse.Capture(popupBox, CaptureMode.SubTree);
142-
else
143-
Mouse.Capture(null);
170+
{
171+
popupBox.AnimateChildren();
172+
}
144173

145174
VisualStateManager.GoToState(popupBox, newValue ? PopupIsOpenStateName : PopupIsClosedStateName, true);
146-
}
175+
}
147176

148177
/// <summary>
149178
/// Gets or sets whether the popup is currently open.
@@ -169,23 +198,44 @@ public bool StaysOpen
169198
public static readonly DependencyProperty PropertyTypeProperty = DependencyProperty.Register(
170199
"PlacementMode", typeof (PopupBoxPlacementMode), typeof (PopupBox), new PropertyMetadata(default(PopupBoxPlacementMode)));
171200

201+
/// <summary>
202+
/// Gets or sets how the popup is aligned in relation to the toggle.
203+
/// </summary>
172204
public PopupBoxPlacementMode PlacementMode
173205
{
174206
get { return (PopupBoxPlacementMode) GetValue(PropertyTypeProperty); }
175207
set { SetValue(PropertyTypeProperty, value); }
176208
}
177209

210+
public static readonly DependencyProperty PopupModeProperty = DependencyProperty.Register(
211+
"PopupMode", typeof (PopupBoxPopupMode), typeof (PopupBox), new PropertyMetadata(default(PopupBoxPopupMode)));
212+
213+
/// <summary>
214+
/// Gets or sets what trigger causes the popup to open.
215+
/// </summary>
216+
public PopupBoxPopupMode PopupMode
217+
{
218+
get { return (PopupBoxPopupMode) GetValue(PopupModeProperty); }
219+
set { SetValue(PopupModeProperty, value); }
220+
}
221+
178222
/// <summary>
179223
/// Framework use. Provides the method used to position the popup.
180224
/// </summary>
181225
public CustomPopupPlacementCallback PopupPlacementMethod => GetPopupPlacement;
182226

183227
public override void OnApplyTemplate()
184228
{
229+
if (_popup != null)
230+
_popup.Loaded -= PopupOnLoaded;
231+
185232
base.OnApplyTemplate();
186233

187234
_popup = GetTemplateChild(PopupPartName) as PopupEx;
188-
_toggleButton = GetTemplateChild(TogglePartName) as ToggleButton;
235+
_popupContentControl = GetTemplateChild(PopupContentControlPartName) as ContentControl;
236+
237+
if (_popup != null)
238+
_popup.Loaded += PopupOnLoaded;
189239

190240
VisualStateManager.GoToState(this, IsPopupOpen ? PopupIsOpenStateName : PopupIsClosedStateName, false);
191241
}
@@ -200,7 +250,27 @@ protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChanged
200250
}
201251
}
202252

203-
private void Close()
253+
protected override void OnMouseEnter(MouseEventArgs e)
254+
{
255+
if (PopupMode == PopupBoxPopupMode.MouseOverEager
256+
|| PopupMode == PopupBoxPopupMode.MouseOver)
257+
258+
SetCurrentValue(IsPopupOpenProperty, true);
259+
260+
base.OnMouseEnter(e);
261+
}
262+
263+
protected override void OnMouseLeave(MouseEventArgs e)
264+
{
265+
if (PopupMode == PopupBoxPopupMode.MouseOverEager
266+
|| PopupMode == PopupBoxPopupMode.MouseOver)
267+
268+
Close();
269+
270+
base.OnMouseEnter(e);
271+
}
272+
273+
protected void Close()
204274
{
205275
if (IsPopupOpen)
206276
SetCurrentValue(IsPopupOpenProperty, false);
@@ -243,6 +313,57 @@ private CustomPopupPlacement[] GetPopupPlacement(Size popupSize, Size targetSize
243313
return new[] {new CustomPopupPlacement(point, PopupPrimaryAxis.Horizontal)};
244314
}
245315

316+
private void AnimateChildren()
317+
{
318+
if (_popupContentControl == null) return;
319+
if (VisualTreeHelper.GetChildrenCount(_popupContentControl) != 1) return;
320+
var contentPresenter = VisualTreeHelper.GetChild(_popupContentControl, 0) as ContentPresenter;
321+
322+
var controls = contentPresenter.VisualDepthFirstTraversal().OfType<ButtonBase>();
323+
double translateYFrom;
324+
if (PlacementMode == PopupBoxPlacementMode.TopAndAlignCentres
325+
|| PlacementMode == PopupBoxPlacementMode.TopAndAlignLeftEdges
326+
|| PlacementMode == PopupBoxPlacementMode.TopAndAlignRightEdges)
327+
{
328+
controls = controls.Reverse();
329+
translateYFrom = 40;
330+
}
331+
else
332+
translateYFrom = -40;
333+
334+
var i = 0;
335+
foreach (var uiElement in controls)
336+
{
337+
var transformGroup = new TransformGroup
338+
{
339+
Children = new TransformCollection(new Transform[]
340+
{
341+
new ScaleTransform(.5, .5),
342+
new TranslateTransform(0, translateYFrom)
343+
})
344+
};
345+
uiElement.SetCurrentValue(RenderTransformOriginProperty, new Point(.5, .5));
346+
uiElement.RenderTransform = transformGroup;
347+
348+
var scaleXAnimation = new DoubleAnimation(.5, 1, new Duration(TimeSpan.FromMilliseconds(100)));
349+
Storyboard.SetTargetProperty(scaleXAnimation, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"));
350+
Storyboard.SetTarget(scaleXAnimation, uiElement);
351+
var scaleYAnimation = new DoubleAnimation(.5, 1, new Duration(TimeSpan.FromMilliseconds(100)));
352+
Storyboard.SetTargetProperty(scaleYAnimation, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"));
353+
Storyboard.SetTarget(scaleYAnimation, uiElement);
354+
var translateYAnimation = new DoubleAnimation(translateYFrom, 0, new Duration(TimeSpan.FromMilliseconds(100)));
355+
Storyboard.SetTargetProperty(translateYAnimation, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform.Y)"));
356+
Storyboard.SetTarget(translateYAnimation, uiElement);
357+
var storyboard = new Storyboard();
358+
storyboard.Children.Add(scaleXAnimation);
359+
storyboard.Children.Add(scaleYAnimation);
360+
storyboard.Children.Add(translateYAnimation);
361+
storyboard.BeginTime = TimeSpan.FromMilliseconds(i++ * 20);
362+
363+
storyboard.Begin();
364+
}
365+
}
366+
246367
#region Capture
247368

248369
[DllImport("user32.dll", CharSet = CharSet.Auto)]
@@ -310,6 +431,12 @@ protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
310431

311432
#endregion
312433

434+
private void PopupOnLoaded(object sender, RoutedEventArgs routedEventArgs)
435+
{
436+
if (PopupMode == PopupBoxPopupMode.MouseOverEager)
437+
_popup.IsOpen = true;
438+
}
439+
313440
private static object CoerceToolTipIsEnabled(DependencyObject dependencyObject, object value)
314441
{
315442
var popupBox = (PopupBox) dependencyObject;

0 commit comments

Comments
 (0)