-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
Snackbar.cs
177 lines (146 loc) · 7.68 KB
/
Snackbar.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media.Animation;
using System.Windows.Threading;
namespace MaterialDesignThemes.Wpf
{
/// <summary>
/// Implements a <see cref="Snackbar"/> inspired by the Material Design specs (https://material.google.com/components/snackbars-toasts.html).
/// </summary>
[ContentProperty(nameof(Message))]
public class Snackbar : Control
{
private const string ActivateStoryboardName = "ActivateStoryboard";
private const string DeactivateStoryboardName = "DeactivateStoryboard";
private Action _messageQueueRegistrationCleanUp = null;
static Snackbar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Snackbar), new FrameworkPropertyMetadata(typeof(Snackbar)));
}
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
nameof(Message), typeof(SnackbarMessage), typeof(Snackbar), new PropertyMetadata(default(SnackbarMessage)));
public SnackbarMessage Message
{
get { return (SnackbarMessage) GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageQueueProperty = DependencyProperty.Register(
nameof(MessageQueue), typeof(SnackbarMessageQueue), typeof(Snackbar), new PropertyMetadata(default(SnackbarMessageQueue), MessageQueuePropertyChangedCallback));
private static void MessageQueuePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var snackbar = (Snackbar) dependencyObject;
(snackbar._messageQueueRegistrationCleanUp ?? (() => { }))();
var messageQueue = dependencyPropertyChangedEventArgs.NewValue as SnackbarMessageQueue;
snackbar._messageQueueRegistrationCleanUp = messageQueue?.Pair(snackbar);
}
public SnackbarMessageQueue MessageQueue
{
get { return (SnackbarMessageQueue) GetValue(MessageQueueProperty); }
set { SetValue(MessageQueueProperty, value); }
}
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(
nameof(IsActive), typeof(bool), typeof(Snackbar), new PropertyMetadata(default(bool), IsActivePropertyChangedCallback));
public bool IsActive
{
get { return (bool) GetValue(IsActiveProperty); }
set { SetValue(IsActiveProperty, value); }
}
public event RoutedPropertyChangedEventHandler<bool> IsActiveChanged
{
add { AddHandler(IsActiveChangedEvent, value); }
remove { RemoveHandler(IsActiveChangedEvent, value); }
}
public static readonly RoutedEvent IsActiveChangedEvent =
EventManager.RegisterRoutedEvent(
nameof(IsActiveChanged),
RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<bool>),
typeof(Snackbar));
private static void OnIsActiveChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as Snackbar;
var args = new RoutedPropertyChangedEventArgs<bool>(
(bool) e.OldValue,
(bool) e.NewValue) {RoutedEvent = IsActiveChangedEvent };
instance?.RaiseEvent(args);
}
public static readonly RoutedEvent DeactivateStoryboardCompletedEvent =
EventManager.RegisterRoutedEvent(
nameof(DeactivateStoryboardCompleted),
RoutingStrategy.Bubble,
typeof(SnackbarMessageEventArgs),
typeof(Snackbar));
public event RoutedPropertyChangedEventHandler<SnackbarMessage> DeactivateStoryboardCompleted
{
add { AddHandler(DeactivateStoryboardCompletedEvent, value); }
remove { RemoveHandler(DeactivateStoryboardCompletedEvent, value); }
}
private static void OnDeactivateStoryboardCompleted(
IInputElement snackbar, SnackbarMessage message)
{
var args = new SnackbarMessageEventArgs(DeactivateStoryboardCompletedEvent, message);
snackbar.RaiseEvent(args);
}
public TimeSpan ActivateStoryboardDuration { get; private set; }
public TimeSpan DeactivateStoryboardDuration { get; private set; }
public static readonly DependencyProperty ActionButtonStyleProperty = DependencyProperty.Register(
nameof(ActionButtonStyle), typeof(Style), typeof(Snackbar), new PropertyMetadata(default(Style)));
public Style ActionButtonStyle
{
get { return (Style) GetValue(ActionButtonStyleProperty); }
set { SetValue(ActionButtonStyleProperty, value); }
}
public override void OnApplyTemplate()
{
//we regards to notification of deactivate storyboard finishing,
//we either build a storyboard in code and subscribe to completed event,
//or take the not 100% proof of the storyboard duration from the storyboard itself
//...HOWEVER...we can both methods result can work under the same public API so
//we can flip the implementation if this version doesnt pan out
//(currently we have no even on the activate animation; don't
// need it just now, but it would mirror the deactivate)
ActivateStoryboardDuration = GetStoryboardResourceDuration(ActivateStoryboardName);
DeactivateStoryboardDuration = GetStoryboardResourceDuration(DeactivateStoryboardName);
base.OnApplyTemplate();
}
private TimeSpan GetStoryboardResourceDuration(string resourceName)
{
var storyboard = Template.Resources.Contains(resourceName)
? (Storyboard)Template.Resources[resourceName]
: null;
return storyboard != null && storyboard.Duration.HasTimeSpan
? storyboard.Duration.TimeSpan
: new Func<TimeSpan>(() =>
{
System.Diagnostics.Debug.WriteLine(
$"Warning, no Duration was specified at root of storyboard '{resourceName}'.");
return TimeSpan.Zero;
})();
}
private static void IsActivePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
OnIsActiveChanged(dependencyObject, dependencyPropertyChangedEventArgs);
if ((bool)dependencyPropertyChangedEventArgs.NewValue) return;
var snackbar = (Snackbar)dependencyObject;
if (snackbar.Message == null) return;
var dispatcherTimer = new DispatcherTimer
{
Tag = new Tuple<Snackbar, SnackbarMessage>(snackbar, snackbar.Message),
Interval = snackbar.DeactivateStoryboardDuration
};
dispatcherTimer.Tick += DeactivateStoryboardDispatcherTimerOnTick;
dispatcherTimer.Start();
}
private static void DeactivateStoryboardDispatcherTimerOnTick(object sender, EventArgs eventArgs)
{
var dispatcherTimer = (DispatcherTimer)sender;
dispatcherTimer.Stop();
dispatcherTimer.Tick -= DeactivateStoryboardDispatcherTimerOnTick;
var source = (Tuple<Snackbar, SnackbarMessage>)dispatcherTimer.Tag;
OnDeactivateStoryboardCompleted(source.Item1, source.Item2);
}
}
}