diff --git a/KeyboardAudioVisualizer/Attached/SliderValue.cs b/KeyboardAudioVisualizer/Attached/SliderValue.cs new file mode 100644 index 0000000..3de7ffd --- /dev/null +++ b/KeyboardAudioVisualizer/Attached/SliderValue.cs @@ -0,0 +1,111 @@ +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; + +namespace KeyboardAudioVisualizer.Attached +{ + public static class SliderValue + { + #region Properties & Fields + // ReSharper disable InconsistentNaming + + public static readonly DependencyProperty UnitProperty = DependencyProperty.RegisterAttached( + "Unit", typeof(string), typeof(SliderValue), new PropertyMetadata(default(string))); + + public static void SetUnit(DependencyObject element, string value) => element.SetValue(UnitProperty, value); + public static string GetUnit(DependencyObject element) => (string)element.GetValue(UnitProperty); + + public static readonly DependencyProperty IsShownProperty = DependencyProperty.RegisterAttached( + "IsShown", typeof(bool), typeof(SliderValue), new PropertyMetadata(default(bool), IsShownChanged)); + + public static void SetIsShown(DependencyObject element, bool value) => element.SetValue(IsShownProperty, value); + public static bool GetIsShown(DependencyObject element) => (bool)element.GetValue(IsShownProperty); + + public static readonly DependencyProperty BorderBrushProperty = DependencyProperty.RegisterAttached( + "BorderBrush", typeof(Brush), typeof(SliderValue), new PropertyMetadata(default(Brush))); + + public static void SetBorderBrush(DependencyObject element, Brush value) => element.SetValue(BorderBrushProperty, value); + public static Brush GetBorderBrush(DependencyObject element) => (Brush)element.GetValue(BorderBrushProperty); + + public static readonly DependencyProperty BackgroundProperty = DependencyProperty.RegisterAttached( + "Background", typeof(Brush), typeof(SliderValue), new PropertyMetadata(default(Brush))); + + public static void SetBackground(DependencyObject element, Brush value) => element.SetValue(BackgroundProperty, value); + public static Brush GetBackground(DependencyObject element) => (Brush)element.GetValue(BackgroundProperty); + + public static readonly DependencyProperty ForegroundProperty = DependencyProperty.RegisterAttached( + "Foreground", typeof(Brush), typeof(SliderValue), new PropertyMetadata(default(Brush))); + + public static void SetForeground(DependencyObject element, Brush value) => element.SetValue(ForegroundProperty, value); + public static Brush GetForeground(DependencyObject element) => (Brush)element.GetValue(ForegroundProperty); + + public static readonly DependencyProperty FontProperty = DependencyProperty.RegisterAttached( + "Font", typeof(FontFamily), typeof(SliderValue), new PropertyMetadata(default(FontFamily))); + + public static void SetFont(DependencyObject element, FontFamily value) => element.SetValue(FontProperty, value); + public static FontFamily GetFont(DependencyObject element) => (FontFamily)element.GetValue(FontProperty); + + public static readonly DependencyProperty FontSizeProperty = DependencyProperty.RegisterAttached( + "FontSize", typeof(double), typeof(SliderValue), new PropertyMetadata(default(double))); + + public static void SetFontSize(DependencyObject element, double value) => element.SetValue(FontSizeProperty, value); + public static double GetFontSize(DependencyObject element) => (double)element.GetValue(FontSizeProperty); + + // ReSharper enable InconsistentNaming + #endregion + + #region Methods + + private static void IsShownChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + if (!(dependencyObject is Slider slider)) return; + + if (dependencyPropertyChangedEventArgs.NewValue as bool? == true) + { + slider.MouseEnter += SliderOnMouseEnter; + slider.MouseLeave += SliderOnMouseLeave; + } + else + { + slider.MouseEnter -= SliderOnMouseEnter; + slider.MouseLeave -= SliderOnMouseLeave; + RemoveAdorner(slider); + } + } + + private static void SliderOnMouseEnter(object sender, MouseEventArgs mouseEventArgs) + { + if (!(sender is Slider slider)) return; + AdornerLayer.GetAdornerLayer(slider)?.Add(new SliderValueAdorner(slider, GetUnit(slider)) + { + BorderBrush = GetBorderBrush(slider), + Background = GetBackground(slider), + Foreground = GetForeground(slider), + Font = GetFont(slider), + FontSize = GetFontSize(slider) + }); + } + + private static void SliderOnMouseLeave(object sender, MouseEventArgs mouseEventArgs) + { + if (!(sender is Slider slider)) return; + RemoveAdorner(slider); + } + + private static void RemoveAdorner(Slider slider) + { + AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(slider); + Adorner adorner = adornerLayer?.GetAdorners(slider)?.FirstOrDefault(x => x is SliderValueAdorner); + if (adorner != null) + { + adornerLayer.Remove(adorner); + (adorner as SliderValueAdorner)?.Cleanup(); + } + } + + #endregion + } +} diff --git a/KeyboardAudioVisualizer/Attached/SliderValueAdorner.cs b/KeyboardAudioVisualizer/Attached/SliderValueAdorner.cs new file mode 100644 index 0000000..9f9a235 --- /dev/null +++ b/KeyboardAudioVisualizer/Attached/SliderValueAdorner.cs @@ -0,0 +1,99 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Media; +using Point = System.Windows.Point; + +namespace KeyboardAudioVisualizer.Attached +{ + public class SliderValueAdorner : System.Windows.Documents.Adorner + { + #region Properties & Fields + + private readonly string _unit; + private readonly Slider _slider; + private readonly Thumb _thumb; + private readonly RepeatButton _decreaseRepeatButton; + + public Brush BorderBrush { get; set; } = Brushes.Black; + public Brush Background { get; set; } = Brushes.Black; + public Brush Foreground { get; set; } = Brushes.White; + public FontFamily Font { get; set; } = new FontFamily("Verdana"); + public double FontSize { get; set; } = 14; + + #endregion + + #region Constructors + + public SliderValueAdorner(UIElement adornedElement, string unit) + : base(adornedElement) + { + this._unit = unit; + + _slider = (Slider)adornedElement; + Track track = (Track)_slider.Template.FindName("PART_Track", _slider); + + _thumb = track.Thumb; + _decreaseRepeatButton = track.DecreaseRepeatButton; + _decreaseRepeatButton.SizeChanged += OnButtonSizeChanged; + } + + #endregion + + #region Methods + + public void Cleanup() + { + _decreaseRepeatButton.SizeChanged -= OnButtonSizeChanged; + } + + private void OnButtonSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs) => InvalidateVisual(); + + protected override void OnRender(DrawingContext drawingContext) + { + double offset = _decreaseRepeatButton.ActualWidth + (_thumb.ActualWidth / 2.0); + + FormattedText text = new FormattedText(GetText(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface(Font, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal), FontSize, Foreground); + Geometry border = CreateBorder(offset, text.Width, text.Height); + + drawingContext.DrawGeometry(Background, new Pen(BorderBrush, 1), border); + drawingContext.DrawText(text, new Point(offset - (text.Width / 2.0), -26)); + } + + private string GetText() + { + string valueText = _slider.Value.ToString(); + if (!string.IsNullOrWhiteSpace(_unit)) + valueText += " " + _unit; + + return valueText; + } + + private Geometry CreateBorder(double offset, double width, double height) + { + double halfWidth = width / 2.0; + + PathGeometry borderGeometry = new PathGeometry(); + PathFigure border = new PathFigure + { + StartPoint = new Point(offset, 0), + IsClosed = true, + IsFilled = true + }; + + border.Segments.Add(new LineSegment(new Point(offset + 4, -6), true)); + border.Segments.Add(new LineSegment(new Point(offset + 4 + halfWidth, -6), true)); + border.Segments.Add(new LineSegment(new Point(offset + 4 + halfWidth, -10 - height), true)); + border.Segments.Add(new LineSegment(new Point(offset - 4 - halfWidth, -10 - height), true)); + border.Segments.Add(new LineSegment(new Point(offset - 4 - halfWidth, -6), true)); + border.Segments.Add(new LineSegment(new Point(offset - 4, -6), true)); + + borderGeometry.Figures.Add(border); + + return borderGeometry; + } + + #endregion + } +} diff --git a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj index b36f3ff..fdaf5c8 100644 --- a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj +++ b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj @@ -108,6 +108,8 @@ MSBuild:Compile Designer + + App.xaml Code diff --git a/KeyboardAudioVisualizer/Styles/Slider.xaml b/KeyboardAudioVisualizer/Styles/Slider.xaml index d686eb8..d6028df 100644 --- a/KeyboardAudioVisualizer/Styles/Slider.xaml +++ b/KeyboardAudioVisualizer/Styles/Slider.xaml @@ -1,15 +1,24 @@  + xmlns:styles="clr-namespace:KeyboardAudioVisualizer.Styles" + xmlns:attached="clr-namespace:KeyboardAudioVisualizer.Attached"> -