forked from MaterialDesignInXAML/MaterialDesignInXamlToolkit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSnackbar.cs
More file actions
201 lines (163 loc) · 8.24 KB
/
Snackbar.cs
File metadata and controls
201 lines (163 loc) · 8.24 KB
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
using System.ComponentModel;
using System.Windows.Media.Animation;
using System.Windows.Threading;
namespace MaterialDesignThemes.Wpf;
public enum SnackbarActionButtonPlacementMode
{
Auto,
Inline,
SeparateLine
}
/// <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;
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 => (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),
MessageQueueValidateValueCallback);
private static void MessageQueuePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var snackbar = (Snackbar)dependencyObject;
snackbar._messageQueueRegistrationCleanUp?.Invoke();
var messageQueue = dependencyPropertyChangedEventArgs.NewValue as SnackbarMessageQueue;
snackbar._messageQueueRegistrationCleanUp = messageQueue?.Pair(snackbar);
}
private static bool MessageQueueValidateValueCallback(object value)
{
if (value is null || ((SnackbarMessageQueue)value).Dispatcher == Dispatcher.CurrentDispatcher)
return true;
throw new ArgumentException("SnackbarMessageQueue must be created by the same thread.", nameof(value));
}
public SnackbarMessageQueue? MessageQueue
{
get => (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 => (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 => (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 does not 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 is 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)
{
if (sender is DispatcherTimer dispatcherTimer)
{
dispatcherTimer.Stop();
dispatcherTimer.Tick -= DeactivateStoryboardDispatcherTimerOnTick;
var source = (Tuple<Snackbar, SnackbarMessage>)dispatcherTimer.Tag;
OnDeactivateStoryboardCompleted(source.Item1, source.Item2);
}
}
public static readonly DependencyProperty ActionButtonPlacementProperty = DependencyProperty.Register(
nameof(ActionButtonPlacement), typeof(SnackbarActionButtonPlacementMode), typeof(Snackbar), new PropertyMetadata(SnackbarActionButtonPlacementMode.Auto));
public SnackbarActionButtonPlacementMode ActionButtonPlacement
{
get => (SnackbarActionButtonPlacementMode)GetValue(ActionButtonPlacementProperty);
set => SetValue(ActionButtonPlacementProperty, value);
}
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register(
nameof(CornerRadius), typeof(Thickness), typeof(Snackbar), new PropertyMetadata(new Thickness(0)));
[Bindable(true)]
[Category("Appearance")]
public Thickness CornerRadius
{
get => (Thickness)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
}