using System; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Media.Animation; using HandyControl.Data; using HandyControl.Interactivity; using HandyControl.Tools; namespace HandyControl.Controls; [ContentProperty("Content")] public class Drawer : FrameworkElement { private Storyboard _storyboard; private AdornerContainer _container; private ContentControl _animationControl; private TranslateTransform _translateTransform; private double _animationLength; private string _animationPropertyName; private FrameworkElement _maskElement; private AdornerLayer _layer; private UIElement _contentElement; private Point _contentRenderTransformOrigin; static Drawer() { DataContextProperty.OverrideMetadata(typeof(Drawer), new FrameworkPropertyMetadata(DataContextPropertyChanged)); } public Drawer() { Loaded += Drawer_Loaded; } private void Drawer_Loaded(object sender, RoutedEventArgs e) { if (IsOpen) { OnIsOpenChanged(true); } Loaded -= Drawer_Loaded; } private static void DataContextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((Drawer) d).OnDataContextPropertyChanged(e); private void OnDataContextPropertyChanged(DependencyPropertyChangedEventArgs e) => UpdateDataContext(_animationControl, e.OldValue, e.NewValue); public static readonly RoutedEvent OpenedEvent = EventManager.RegisterRoutedEvent("Opened", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Drawer)); public event RoutedEventHandler Opened { add => AddHandler(OpenedEvent, value); remove => RemoveHandler(OpenedEvent, value); } public static readonly RoutedEvent ClosedEvent = EventManager.RegisterRoutedEvent("Closed", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Drawer)); public event RoutedEventHandler Closed { add => AddHandler(ClosedEvent, value); remove => RemoveHandler(ClosedEvent, value); } public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register( nameof(IsOpen), typeof(bool), typeof(Drawer), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsOpenChanged)); private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctl = (Drawer) d; ctl.OnIsOpenChanged((bool) e.NewValue); } public bool IsOpen { get => (bool) GetValue(IsOpenProperty); set => SetValue(IsOpenProperty, ValueBoxes.BooleanBox(value)); } public static readonly DependencyProperty MaskCanCloseProperty = DependencyProperty.Register( nameof(MaskCanClose), typeof(bool), typeof(Drawer), new PropertyMetadata(ValueBoxes.TrueBox)); public bool MaskCanClose { get => (bool) GetValue(MaskCanCloseProperty); set => SetValue(MaskCanCloseProperty, ValueBoxes.BooleanBox(value)); } public static readonly DependencyProperty ShowMaskProperty = DependencyProperty.Register( nameof(ShowMask), typeof(bool), typeof(Drawer), new PropertyMetadata(ValueBoxes.TrueBox)); public bool ShowMask { get => (bool) GetValue(ShowMaskProperty); set => SetValue(ShowMaskProperty, ValueBoxes.BooleanBox(value)); } public static readonly DependencyProperty DockProperty = DependencyProperty.Register( nameof(Dock), typeof(Dock), typeof(Drawer), new PropertyMetadata(default(Dock))); public Dock Dock { get => (Dock) GetValue(DockProperty); set => SetValue(DockProperty, value); } public static readonly DependencyProperty ShowModeProperty = DependencyProperty.Register( nameof(ShowMode), typeof(DrawerShowMode), typeof(Drawer), new PropertyMetadata(default(DrawerShowMode))); public DrawerShowMode ShowMode { get => (DrawerShowMode) GetValue(ShowModeProperty); set => SetValue(ShowModeProperty, value); } public static readonly DependencyProperty MaskBrushProperty = DependencyProperty.Register( nameof(MaskBrush), typeof(Brush), typeof(Drawer), new PropertyMetadata(default(Brush))); public Brush MaskBrush { get => (Brush) GetValue(MaskBrushProperty); set => SetValue(MaskBrushProperty, value); } public static readonly DependencyProperty ContentProperty = DependencyProperty.Register( nameof(Content), typeof(object), typeof(Drawer), new PropertyMetadata(default(object))); public object Content { get => GetValue(ContentProperty); set => SetValue(ContentProperty, value); } private void CreateContainer() { _storyboard = new Storyboard(); _storyboard.Completed += Storyboard_Completed; _translateTransform = new TranslateTransform(); _animationControl = new ContentControl { Content = Content, RenderTransform = _translateTransform, DataContext = this }; var panel = new SimplePanel { ClipToBounds = true }; if (ShowMask) { _maskElement = new Border { Background = MaskBrush, Opacity = 0 }; _maskElement.PreviewMouseLeftButtonDown += MaskElement_PreviewMouseLeftButtonDown; panel.Children.Add(_maskElement); } _animationControl.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); var size = _animationControl.DesiredSize; switch (Dock) { case Dock.Left: _animationControl.HorizontalAlignment = HorizontalAlignment.Left; _animationControl.VerticalAlignment = VerticalAlignment.Stretch; _translateTransform.X = -size.Width; _animationLength = -size.Width; _animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.X)"; break; case Dock.Top: _animationControl.HorizontalAlignment = HorizontalAlignment.Stretch; _animationControl.VerticalAlignment = VerticalAlignment.Top; _translateTransform.Y = -size.Height; _animationLength = -size.Height; _animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.Y)"; break; case Dock.Right: _animationControl.HorizontalAlignment = HorizontalAlignment.Right; _animationControl.VerticalAlignment = VerticalAlignment.Stretch; _translateTransform.X = size.Width; _animationLength = size.Width; _animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.X)"; break; case Dock.Bottom: _animationControl.HorizontalAlignment = HorizontalAlignment.Stretch; _animationControl.VerticalAlignment = VerticalAlignment.Bottom; _translateTransform.Y = size.Height; _animationLength = size.Height; _animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.Y)"; break; default: throw new ArgumentOutOfRangeException(); } _animationControl.DataContext = DataContext; _animationControl.CommandBindings.Clear(); _animationControl.CommandBindings.Add(new CommandBinding(ControlCommands.Close, (s, e) => SetCurrentValue(IsOpenProperty, ValueBoxes.FalseBox))); panel.Children.Add(_animationControl); _container = new AdornerContainer(_layer) { Child = panel, ClipToBounds = true }; } private void Storyboard_Completed(object sender, EventArgs e) { if (!IsOpen) { _contentElement.SetCurrentValue(RenderTransformOriginProperty, _contentRenderTransformOrigin); _layer.Remove(_container); RaiseEvent(new RoutedEventArgs(ClosedEvent, this)); } else { RaiseEvent(new RoutedEventArgs(OpenedEvent, this)); } } private void OnIsOpenChanged(bool isOpen) { if (Content == null || DesignerHelper.IsInDesignMode) { return; } AdornerDecorator decorator; var parent = VisualHelper.GetParent(this); if (parent != null) { _contentElement = parent.Child; decorator = parent; } else { var window = WindowHelper.GetActiveWindow(); if (window == null) { return; } decorator = VisualHelper.GetChild(window); _contentElement = window.Content as UIElement; } if (_contentElement == null) { return; } _layer = decorator?.AdornerLayer; if (_layer == null) { return; } _contentRenderTransformOrigin = _contentElement.RenderTransformOrigin; if (_container == null) { CreateContainer(); } switch (ShowMode) { case DrawerShowMode.Push: ShowByPush(isOpen); break; case DrawerShowMode.Press: _contentElement.SetCurrentValue(RenderTransformOriginProperty, new Point(0.5, 0.5)); ShowByPress(isOpen); break; } if (isOpen) { if (_maskElement != null) { var maskAnimation = AnimationHelper.CreateAnimation(1); Storyboard.SetTarget(maskAnimation, _maskElement); Storyboard.SetTargetProperty(maskAnimation, new PropertyPath(OpacityProperty.Name)); _storyboard.Children.Add(maskAnimation); } var drawerAnimation = AnimationHelper.CreateAnimation(0); Storyboard.SetTarget(drawerAnimation, _animationControl); Storyboard.SetTargetProperty(drawerAnimation, new PropertyPath(_animationPropertyName)); _storyboard.Children.Add(drawerAnimation); _layer.Remove(_container); _layer.Add(_container); } else { if (_maskElement != null) { var maskAnimation = AnimationHelper.CreateAnimation(0); Storyboard.SetTarget(maskAnimation, _maskElement); Storyboard.SetTargetProperty(maskAnimation, new PropertyPath(OpacityProperty.Name)); _storyboard.Children.Add(maskAnimation); } var drawerAnimation = AnimationHelper.CreateAnimation(_animationLength); Storyboard.SetTarget(drawerAnimation, _animationControl); Storyboard.SetTargetProperty(drawerAnimation, new PropertyPath(_animationPropertyName)); _storyboard.Children.Add(drawerAnimation); } _storyboard.Begin(); } private void ShowByPush(bool isOpen) { string animationPropertyName; switch (Dock) { case Dock.Left: case Dock.Right: animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.X)"; _contentElement.RenderTransform = new TranslateTransform { X = isOpen ? 0 : -_animationLength }; break; case Dock.Top: case Dock.Bottom: animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.Y)"; _contentElement.RenderTransform = new TranslateTransform { Y = isOpen ? 0 : -_animationLength }; break; default: throw new ArgumentOutOfRangeException(); } var animation = isOpen ? AnimationHelper.CreateAnimation(-_animationLength) : AnimationHelper.CreateAnimation(0); Storyboard.SetTarget(animation, _contentElement); Storyboard.SetTargetProperty(animation, new PropertyPath(animationPropertyName)); _storyboard.Children.Add(animation); } private void ShowByPress(bool isOpen) { _contentElement.RenderTransform = isOpen ? new ScaleTransform() : new ScaleTransform { ScaleX = 0.9, ScaleY = 0.9 }; var animationX = isOpen ? AnimationHelper.CreateAnimation(.9) : AnimationHelper.CreateAnimation(1); Storyboard.SetTarget(animationX, _contentElement); Storyboard.SetTargetProperty(animationX, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)")); _storyboard.Children.Add(animationX); var animationY = isOpen ? AnimationHelper.CreateAnimation(.9) : AnimationHelper.CreateAnimation(1); Storyboard.SetTarget(animationY, _contentElement); Storyboard.SetTargetProperty(animationY, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)")); _storyboard.Children.Add(animationY); } private void MaskElement_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (MaskCanClose) { SetCurrentValue(IsOpenProperty, ValueBoxes.FalseBox); } } private void UpdateDataContext(FrameworkElement target, object oldValue, object newValue) { if (target == null || BindingOperations.GetBindingExpression(target, DataContextProperty) != null) return; if (ReferenceEquals(this, target.DataContext) || Equals(oldValue, target.DataContext)) { target.DataContext = newValue ?? this; } } }