Skip to content

Commit 0ff0813

Browse files
committed
Add filling effect for ripple
It works with a little hack. Since previewmouseleftbuttonup don't want to firing so i had to add an IsActive property. It property is set to true on IsPressed trigger.
1 parent d828115 commit 0ff0813

File tree

3 files changed

+220
-58
lines changed

3 files changed

+220
-58
lines changed

MaterialDesignThemes.Wpf/Ripple.cs

Lines changed: 182 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,38 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Linq;
45
using System.Text;
56
using System.Threading.Tasks;
67
using System.Windows;
78
using System.Windows.Controls;
89
using System.Windows.Data;
910
using System.Windows.Documents;
11+
using System.Windows.Documents.DocumentStructures;
1012
using System.Windows.Input;
1113
using System.Windows.Media;
14+
using System.Windows.Media.Animation;
1215
using System.Windows.Media.Imaging;
1316
using System.Windows.Navigation;
1417
using System.Windows.Shapes;
1518

1619
namespace MaterialDesignThemes.Wpf
17-
{
20+
{
21+
[TemplatePart(Name = PartBubbleEllipse, Type = typeof(Ellipse))]
1822
public class Ripple : ContentControl
1923
{
24+
public const string PartBubbleEllipse = "PART_BubbleEllipse";
25+
26+
private BubbleStoryboardController _bubbleStoryboardController;
27+
2028
static Ripple()
2129
{
2230
DefaultStyleKeyProperty.OverrideMetadata(typeof(Ripple), new FrameworkPropertyMetadata(typeof(Ripple)));
2331
}
2432

2533
public Ripple()
26-
{
27-
MouseMove += OnMouseMove;
34+
{
35+
MouseMove += OnMouseMove;
2836
}
2937

3038
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
@@ -33,6 +41,8 @@ protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
3341
MouseLeftButtonDownX = position.X;
3442
MouseLeftButtonDownY = position.Y;
3543

44+
this.ReleaseMouseCapture();
45+
3646
base.OnPreviewMouseLeftButtonDown(e);
3747
}
3848

@@ -43,70 +53,229 @@ private void OnMouseMove(object sender, MouseEventArgs mouseEventArgs)
4353
MouseY = position.Y;
4454
}
4555

56+
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(
57+
"IsActive", typeof(bool), typeof(Ripple), new FrameworkPropertyMetadata(false, IsActivePropertyChangedCallback));
58+
59+
public bool IsActive
60+
{
61+
get { return (bool)GetValue(IsActiveProperty); }
62+
set { SetValue(IsActiveProperty, value); }
63+
}
64+
65+
public override void OnApplyTemplate()
66+
{
67+
base.OnApplyTemplate();
68+
69+
_bubbleStoryboardController = new BubbleStoryboardController(this);
70+
}
71+
72+
private void StartBubbleAnimation()
73+
{
74+
_bubbleStoryboardController.Add();
75+
}
76+
77+
private void StopBubbleAnimation()
78+
{
79+
_bubbleStoryboardController.Remove();
80+
}
81+
82+
private static void IsActivePropertyChangedCallback(
83+
DependencyObject dependencyObject,
84+
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
85+
{
86+
var box = dependencyObject as Ripple;
87+
if (box == null) return;
88+
89+
bool isActive = (bool)dependencyPropertyChangedEventArgs.NewValue;
90+
91+
if (isActive)
92+
{
93+
box.StartBubbleAnimation();
94+
}
95+
else
96+
{
97+
box.StopBubbleAnimation();
98+
}
99+
}
100+
46101
public static readonly DependencyProperty FeedbackProperty = DependencyProperty.Register(
47-
"Feedback", typeof (Brush), typeof (Ripple), new PropertyMetadata(default(Brush)));
102+
"Feedback", typeof(Brush), typeof(Ripple), new PropertyMetadata(default(Brush)));
48103

49104
public Brush Feedback
50105
{
51-
get { return (Brush) GetValue(FeedbackProperty); }
106+
get { return (Brush)GetValue(FeedbackProperty); }
52107
set { SetValue(FeedbackProperty, value); }
53108
}
54109

55110
private static readonly DependencyPropertyKey MouseXPropertyKey =
56111
DependencyProperty.RegisterReadOnly(
57-
"MouseX", typeof (double), typeof (Ripple),
112+
"MouseX", typeof(double), typeof(Ripple),
58113
new PropertyMetadata(default(double)));
59114

60115
public static readonly DependencyProperty MouseXProperty =
61116
MouseXPropertyKey.DependencyProperty;
62117

63118
public double MouseX
64119
{
65-
get { return (double) GetValue(MouseXProperty); }
120+
get { return (double)GetValue(MouseXProperty); }
66121
private set { SetValue(MouseXPropertyKey, value); }
67122
}
68123

69124
private static readonly DependencyPropertyKey MouseYPropertyKey =
70125
DependencyProperty.RegisterReadOnly(
71-
"MouseY", typeof (double), typeof (Ripple),
126+
"MouseY", typeof(double), typeof(Ripple),
72127
new PropertyMetadata(default(double)));
73128

74129
public static readonly DependencyProperty MouseYProperty =
75130
MouseYPropertyKey.DependencyProperty;
76131

77132
public double MouseY
78133
{
79-
get { return (double) GetValue(MouseYProperty); }
134+
get { return (double)GetValue(MouseYProperty); }
80135
private set { SetValue(MouseYPropertyKey, value); }
81136
}
82137

83138
private static readonly DependencyPropertyKey MouseLeftButtonDownXPropertyKey =
84139
DependencyProperty.RegisterReadOnly(
85-
"MouseLeftButtonDownX", typeof (double), typeof (Ripple),
140+
"MouseLeftButtonDownX", typeof(double), typeof(Ripple),
86141
new PropertyMetadata(default(double)));
87142

88143
public static readonly DependencyProperty MouseLeftButtonDownXProperty =
89144
MouseLeftButtonDownXPropertyKey.DependencyProperty;
90145

91146
public double MouseLeftButtonDownX
92147
{
93-
get { return (double) GetValue(MouseLeftButtonDownXProperty); }
148+
get { return (double)GetValue(MouseLeftButtonDownXProperty); }
94149
private set { SetValue(MouseLeftButtonDownXPropertyKey, value); }
95150
}
96151

97152
private static readonly DependencyPropertyKey MouseLeftButtonDownYPropertyKey =
98153
DependencyProperty.RegisterReadOnly(
99-
"MouseLeftButtonDownY", typeof (double), typeof (Ripple),
154+
"MouseLeftButtonDownY", typeof(double), typeof(Ripple),
100155
new PropertyMetadata(default(double)));
101156

102157
public static readonly DependencyProperty MouseLeftButtonDownYProperty =
103158
MouseLeftButtonDownYPropertyKey.DependencyProperty;
104159

105160
public double MouseLeftButtonDownY
106161
{
107-
get { return (double) GetValue(MouseLeftButtonDownYProperty); }
162+
get { return (double)GetValue(MouseLeftButtonDownYProperty); }
108163
private set { SetValue(MouseLeftButtonDownYPropertyKey, value); }
109164
}
110165

166+
167+
private sealed class BubbleStoryboardController
168+
{
169+
private readonly Ripple _ripple;
170+
private readonly Ellipse _ellipse;
171+
private readonly TimeSpan _fadeOutDuration = TimeSpan.FromMilliseconds(200);
172+
private readonly TimeSpan _bubbleDuration = TimeSpan.FromMilliseconds(400);
173+
174+
private volatile Storyboard _currentBubbleStoryboard;
175+
private volatile Storyboard _currentFadeOutStoryboard;
176+
177+
public BubbleStoryboardController(Ripple ripple)
178+
{
179+
_ripple = ripple;
180+
_ellipse = _ripple.Template.FindName(PartBubbleEllipse, _ripple) as Ellipse;
181+
182+
if (_ellipse == null) throw new InvalidOperationException();
183+
}
184+
public void Add()
185+
{
186+
_currentFadeOutStoryboard?.Remove();
187+
_currentBubbleStoryboard?.Remove();
188+
189+
_currentBubbleStoryboard = CreateBubbleStoryboard();
190+
_currentBubbleStoryboard.Begin();
191+
}
192+
193+
public void Remove()
194+
{
195+
_currentFadeOutStoryboard?.Remove();
196+
197+
var fadeOutStoryboard = CreateFadeOutStoryboard();
198+
var bubbleStoryboardLocalCopy = _currentBubbleStoryboard;
199+
200+
fadeOutStoryboard.Completed += delegate
201+
{
202+
fadeOutStoryboard.Remove();
203+
bubbleStoryboardLocalCopy?.Remove();
204+
};
205+
206+
_currentFadeOutStoryboard = fadeOutStoryboard;
207+
_currentFadeOutStoryboard.Begin();
208+
}
209+
210+
private Storyboard CreateFadeOutStoryboard()
211+
{
212+
Storyboard resultStorybaord = new Storyboard();
213+
214+
DoubleAnimation opacityAnimation = new DoubleAnimation
215+
{
216+
To = 0
217+
};
218+
SetupAnimation(opacityAnimation, _fadeOutDuration, "(UIElement.Opacity)");
219+
220+
resultStorybaord.Children.Add(opacityAnimation);
221+
222+
return resultStorybaord;
223+
}
224+
225+
private Storyboard CreateBubbleStoryboard()
226+
{
227+
Storyboard resultStorybaord = new Storyboard();
228+
229+
DoubleAnimation opacityAnimation = new DoubleAnimation
230+
{
231+
To = 0.26,
232+
};
233+
SetupAnimation(opacityAnimation, _bubbleDuration, "(UIElement.Opacity)");
234+
235+
double h = Math.Max(_ripple.MouseLeftButtonDownX, _ripple.ActualWidth - _ripple.MouseLeftButtonDownX);
236+
double w = Math.Max(_ripple.MouseLeftButtonDownY, _ripple.ActualHeight - _ripple.MouseLeftButtonDownY);
237+
double diameter = 2 * Math.Sqrt(h * h + w * w);
238+
239+
DoubleAnimation widthAnimation = new DoubleAnimation
240+
{
241+
To = diameter
242+
};
243+
SetupAnimation(widthAnimation, _bubbleDuration, "(FrameworkElement.Width)");
244+
245+
DoubleAnimation heightAnimation = new DoubleAnimation
246+
{
247+
To = diameter
248+
};
249+
SetupAnimation(heightAnimation, _bubbleDuration, "(FrameworkElement.Height)");
250+
251+
DoubleAnimation translateXAnimation = new DoubleAnimation
252+
{
253+
To = -diameter / 2
254+
};
255+
SetupAnimation(translateXAnimation, _bubbleDuration, "(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)");
256+
257+
DoubleAnimation translateYAnimation = new DoubleAnimation
258+
{
259+
To = -diameter / 2
260+
};
261+
SetupAnimation(translateYAnimation, _bubbleDuration, "(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)");
262+
263+
resultStorybaord.Children.Add(opacityAnimation);
264+
resultStorybaord.Children.Add(widthAnimation);
265+
resultStorybaord.Children.Add(heightAnimation);
266+
resultStorybaord.Children.Add(translateXAnimation);
267+
resultStorybaord.Children.Add(translateYAnimation);
268+
269+
return resultStorybaord;
270+
}
271+
272+
private void SetupAnimation(AnimationTimeline animation, TimeSpan duration, string propertyPath)
273+
{
274+
animation.AccelerationRatio = 0.25;
275+
animation.Duration = new Duration(duration);
276+
Storyboard.SetTarget(animation, _ellipse);
277+
Storyboard.SetTargetProperty(animation, new PropertyPath(propertyPath));
278+
}
279+
}
111280
}
112281
}

MaterialDesignThemes.Wpf/Themes/Generic.xaml

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@
3535
<Setter Property="Template">
3636
<Setter.Value>
3737
<ControlTemplate TargetType="{x:Type local:Ripple}">
38-
<Grid Background="Transparent">
39-
<Canvas IsHitTestVisible="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
40-
<Ellipse x:Name="ClickEllipse" Fill="{TemplateBinding Feedback}" Opacity="0" Width="0" Height="0" Canvas.Left="{TemplateBinding MouseLeftButtonDownX}" Canvas.Top="{TemplateBinding MouseLeftButtonDownY}"
38+
<Grid Background="Transparent">
39+
<Canvas IsHitTestVisible="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
40+
x:Name="EllipseCanvas">
41+
<Ellipse x:Name="PART_BubbleEllipse"
42+
Fill="{TemplateBinding Feedback}"
43+
Opacity="0" Height="0" Width="0"
44+
Canvas.Left="{TemplateBinding MouseLeftButtonDownX}" Canvas.Top="{TemplateBinding MouseLeftButtonDownY}"
4145
RenderTransformOrigin=".5,.5">
4246
<Ellipse.RenderTransform>
4347
<TransformGroup>
@@ -49,43 +53,14 @@
4953
</Ellipse.RenderTransform>
5054
</Ellipse>
5155
</Canvas>
52-
<ContentPresenter Content="{TemplateBinding Content}"
56+
<ContentPresenter Content="{TemplateBinding Content}"
5357
ContentTemplate="{TemplateBinding ContentTemplate}"
5458
RecognizesAccessKey="True"
5559
Cursor="{TemplateBinding Cursor}"
5660
Margin="{TemplateBinding Padding}"
5761
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
5862
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
5963
</Grid>
60-
<ControlTemplate.Triggers>
61-
<EventTrigger RoutedEvent="PreviewMouseLeftButtonDown">
62-
<BeginStoryboard>
63-
<Storyboard TargetName="ClickEllipse">
64-
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Width)">
65-
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
66-
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="120"/>
67-
</DoubleAnimationUsingKeyFrames>
68-
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)">
69-
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
70-
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="120"/>
71-
</DoubleAnimationUsingKeyFrames>
72-
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
73-
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
74-
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="-60"/>
75-
</DoubleAnimationUsingKeyFrames>
76-
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
77-
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
78-
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="-60"/>
79-
</DoubleAnimationUsingKeyFrames>
80-
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)">
81-
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
82-
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value=".26"/>
83-
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0"/>
84-
</DoubleAnimationUsingKeyFrames>
85-
</Storyboard>
86-
</BeginStoryboard>
87-
</EventTrigger>
88-
</ControlTemplate.Triggers>
8964
</ControlTemplate>
9065
</Setter.Value>
9166
</Setter>

0 commit comments

Comments
 (0)