diff --git a/src/OpenSilver.ControlsKit.Controls/ExtendedCheckBox.cs b/src/OpenSilver.ControlsKit.Controls/ExtendedCheckBox.cs new file mode 100644 index 0000000..53f15a8 --- /dev/null +++ b/src/OpenSilver.ControlsKit.Controls/ExtendedCheckBox.cs @@ -0,0 +1,1227 @@ +ο»Ώ/*=================================================================================== +* +* Copyright (c) Userware (OpenSilver.net) +* +* This file is part of the OpenSilver.ControlsKit (https://opensilver.net), which +* is licensed under the MIT license (https://opensource.org/licenses/MIT). +* +* As stated in the MIT license, "the above copyright notice and this permission +* notice shall be included in all copies or substantial portions of the Software." +* +*====================================================================================*/ + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Effects; +using System.Windows.Shapes; +using System.Windows.Threading; + +namespace OpenSilver.ControlsKit.Controls +{ + /// + /// Specifies the size of the checkbox control. + /// + public enum CheckBoxSize + { + /// + /// Small checkbox size. + /// + Small, + /// + /// Medium checkbox size. + /// + Medium, + /// + /// Large checkbox size. + /// + Large, + /// + /// Custom checkbox size. + /// + Custom + } + + /// + /// Specifies the style of the check mark. + /// + public enum CheckMarkStyle + { + /// + /// Check mark is visible using the current CheckMarkGeometry. + /// + Show, + /// + /// No check mark displayed. + /// + None + } + + /// + /// Specifies the animation style for the checkbox transition. + /// + public enum CheckBoxAnimationStyle + { + /// + /// Smooth linear animation. + /// + Linear, + /// + /// Ease-in-out animation. + /// + EaseInOut, + /// + /// Bounce animation. + /// + Bounce, + /// + /// No animation. + /// + None + } + + /// + /// Specifies the position of the text relative to the checkbox. + /// + public enum TextPosition + { + /// + /// Text is positioned to the left of the checkbox. + /// + Left, + /// + /// Text is positioned above the checkbox. + /// + Top, + /// + /// Text is positioned to the right of the checkbox. + /// + Right, + /// + /// Text is positioned below the checkbox. + /// + Bottom + } + + /// + /// A customizable checkbox control with smooth animations, hover effects, and flexible styling options. + /// Provides advanced visual capabilities including shadows, custom colors for different states, and smooth transitions. + /// + public class ExtendedCheckBox : CheckBox + { + #region Private Fields + private Border _checkBoxBorder; + private Path _checkMarkPath; + private Grid _contentGrid; + private TextBlock _textBlock; + private DropShadowEffect _shadowEffect; + private bool _isHovered; + private bool _templateApplied; + private DispatcherTimer _animationTimer; + private DateTime _animationStartTime; + private IEasingFunction _currentEasingFunction; + private double _animationStartOpacity; + private double _animationTargetOpacity; + private TransformGroup _checkMarkTransformGroup; + private ScaleTransform _checkMarkScaleTransform; + #endregion + + #region Dependency Properties + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CheckBoxSizeProperty = + DependencyProperty.Register(nameof(CheckBoxSize), typeof(CheckBoxSize), typeof(ExtendedCheckBox), + new PropertyMetadata(CheckBoxSize.Medium, OnCheckBoxSizeChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BoxWidthProperty = + DependencyProperty.Register(nameof(BoxWidth), typeof(double), typeof(ExtendedCheckBox), + new PropertyMetadata(20.0, OnLayoutPropertyChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BoxHeightProperty = + DependencyProperty.Register(nameof(BoxHeight), typeof(double), typeof(ExtendedCheckBox), + new PropertyMetadata(20.0, OnLayoutPropertyChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CornerRadiusProperty = + DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(ExtendedCheckBox), + new PropertyMetadata(new CornerRadius(3), OnCornerRadiusChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CheckMarkStyleProperty = + DependencyProperty.Register(nameof(CheckMarkStyle), typeof(CheckMarkStyle), typeof(ExtendedCheckBox), + new PropertyMetadata(CheckMarkStyle.Show, OnCheckMarkStyleChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CheckMarkGeometryProperty = + DependencyProperty.Register(nameof(CheckMarkGeometry), typeof(Geometry), typeof(ExtendedCheckBox), + new PropertyMetadata(GetDefaultCheckMarkGeometry(), OnCheckMarkGeometryChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CheckMarkBrushProperty = + DependencyProperty.Register(nameof(CheckMarkBrush), typeof(Brush), typeof(ExtendedCheckBox), + new PropertyMetadata(new SolidColorBrush(Colors.White), OnCheckMarkBrushChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CheckMarkSizeProperty = + DependencyProperty.Register(nameof(CheckMarkSize), typeof(double), typeof(ExtendedCheckBox), + new PropertyMetadata(12.0, OnCheckMarkSizeChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BoxBrushProperty = + DependencyProperty.Register(nameof(BoxBrush), typeof(Brush), typeof(ExtendedCheckBox), + new PropertyMetadata(new SolidColorBrush(Colors.White), OnBoxBrushChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CheckedBoxBrushProperty = + DependencyProperty.Register(nameof(CheckedBoxBrush), typeof(Brush), typeof(ExtendedCheckBox), + new PropertyMetadata(new SolidColorBrush(Colors.DodgerBlue), OnBoxBrushChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty HoverBoxBrushProperty = + DependencyProperty.Register(nameof(HoverBoxBrush), typeof(Brush), typeof(ExtendedCheckBox), + new PropertyMetadata(null)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty HoverCheckedBoxBrushProperty = + DependencyProperty.Register(nameof(HoverCheckedBoxBrush), typeof(Brush), typeof(ExtendedCheckBox), + new PropertyMetadata(null)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BoxBorderBrushProperty = + DependencyProperty.Register(nameof(BoxBorderBrush), typeof(Brush), typeof(ExtendedCheckBox), + new PropertyMetadata(new SolidColorBrush(Colors.Gray), OnBoxBorderBrushChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CheckedBoxBorderBrushProperty = + DependencyProperty.Register(nameof(CheckedBoxBorderBrush), typeof(Brush), typeof(ExtendedCheckBox), + new PropertyMetadata(null, OnBoxBorderBrushChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BoxBorderThicknessProperty = + DependencyProperty.Register(nameof(BoxBorderThickness), typeof(Thickness), typeof(ExtendedCheckBox), + new PropertyMetadata(new Thickness(1), OnBoxBorderThicknessChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty HasShadowProperty = + DependencyProperty.Register(nameof(HasShadow), typeof(bool), typeof(ExtendedCheckBox), + new PropertyMetadata(false, OnShadowChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AnimationDurationProperty = + DependencyProperty.Register(nameof(AnimationDuration), typeof(TimeSpan), typeof(ExtendedCheckBox), + new PropertyMetadata(TimeSpan.FromMilliseconds(200))); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AnimationStyleProperty = + DependencyProperty.Register(nameof(AnimationStyle), typeof(CheckBoxAnimationStyle), typeof(ExtendedCheckBox), + new PropertyMetadata(CheckBoxAnimationStyle.EaseInOut)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(string), typeof(ExtendedCheckBox), + new PropertyMetadata("", OnTextChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TextPositionProperty = + DependencyProperty.Register(nameof(TextPosition), typeof(TextPosition), typeof(ExtendedCheckBox), + new PropertyMetadata(TextPosition.Right, OnLayoutPropertyChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TextMarginProperty = + DependencyProperty.Register(nameof(TextMargin), typeof(Thickness), typeof(ExtendedCheckBox), + new PropertyMetadata(new Thickness(8, 0, 0, 0), OnLayoutPropertyChanged)); + + #endregion + + #region Properties + + /// + /// Gets or sets the predefined size of the checkbox. + /// + /// The checkbox size. Default is Medium. + public CheckBoxSize CheckBoxSize + { + get => (CheckBoxSize)GetValue(CheckBoxSizeProperty); + set => SetValue(CheckBoxSizeProperty, value); + } + + /// + /// Gets or sets the width of the checkbox. + /// + /// The box width in device-independent pixels. Default is 20. + public double BoxWidth + { + get => (double)GetValue(BoxWidthProperty); + set => SetValue(BoxWidthProperty, value); + } + + /// + /// Gets or sets the height of the checkbox. + /// + /// The box height in device-independent pixels. Default is 20. + public double BoxHeight + { + get => (double)GetValue(BoxHeightProperty); + set => SetValue(BoxHeightProperty, value); + } + + /// + /// Gets or sets the corner radius of the checkbox. + /// + /// The corner radius. Default is 3. Set to half of box size for circular appearance. + public CornerRadius CornerRadius + { + get => (CornerRadius)GetValue(CornerRadiusProperty); + set => SetValue(CornerRadiusProperty, value); + } + + /// + /// Gets or sets the style of the check mark. + /// + /// The check mark style. Default is Show. + public CheckMarkStyle CheckMarkStyle + { + get => (CheckMarkStyle)GetValue(CheckMarkStyleProperty); + set => SetValue(CheckMarkStyleProperty, value); + } + + /// + /// Gets or sets the geometry used for the check mark. + /// + /// The check mark geometry. Default is Material Design check mark. + public Geometry CheckMarkGeometry + { + get => (Geometry)GetValue(CheckMarkGeometryProperty); + set => SetValue(CheckMarkGeometryProperty, value); + } + + /// + /// Gets or sets the brush used for the check mark. + /// + /// The check mark brush. Default is white. + public Brush CheckMarkBrush + { + get => (Brush)GetValue(CheckMarkBrushProperty); + set => SetValue(CheckMarkBrushProperty, value); + } + + /// + /// Gets or sets the size of the check mark. + /// + /// The check mark size in device-independent pixels. Default is 12. + public double CheckMarkSize + { + get => (double)GetValue(CheckMarkSizeProperty); + set => SetValue(CheckMarkSizeProperty, value); + } + + /// + /// Gets or sets the brush used for the checkbox background when unchecked. + /// + /// The box brush. Default is white. + public Brush BoxBrush + { + get => (Brush)GetValue(BoxBrushProperty); + set => SetValue(BoxBrushProperty, value); + } + + /// + /// Gets or sets the brush used for the checkbox background when checked. + /// + /// The checked box brush. Default is dodger blue. + public Brush CheckedBoxBrush + { + get => (Brush)GetValue(CheckedBoxBrushProperty); + set => SetValue(CheckedBoxBrushProperty, value); + } + + /// + /// Gets or sets the brush used for the checkbox when hovered and unchecked. + /// + /// The hover box brush. If null, a calculated lighter color is used. + public Brush HoverBoxBrush + { + get => (Brush)GetValue(HoverBoxBrushProperty); + set => SetValue(HoverBoxBrushProperty, value); + } + + /// + /// Gets or sets the brush used for the checkbox when hovered and checked. + /// + /// The hover checked box brush. If null, a calculated lighter color is used. + public Brush HoverCheckedBoxBrush + { + get => (Brush)GetValue(HoverCheckedBoxBrushProperty); + set => SetValue(HoverCheckedBoxBrushProperty, value); + } + + /// + /// Gets or sets the border brush of the checkbox when unchecked. + /// + /// The box border brush. Default is gray. + public Brush BoxBorderBrush + { + get => (Brush)GetValue(BoxBorderBrushProperty); + set => SetValue(BoxBorderBrushProperty, value); + } + + /// + /// Gets or sets the border brush of the checkbox when checked. + /// + /// The checked box border brush. If null, uses the same as CheckedBoxBrush. + public Brush CheckedBoxBorderBrush + { + get => (Brush)GetValue(CheckedBoxBorderBrushProperty); + set => SetValue(CheckedBoxBorderBrushProperty, value); + } + + /// + /// Gets or sets the border thickness of the checkbox. + /// + /// The box border thickness. Default is 1. + public Thickness BoxBorderThickness + { + get => (Thickness)GetValue(BoxBorderThicknessProperty); + set => SetValue(BoxBorderThicknessProperty, value); + } + + /// + /// Gets or sets a value indicating whether the checkbox has a drop shadow effect. + /// + /// true if the checkbox has a shadow; otherwise, false. Default is false. + public bool HasShadow + { + get => (bool)GetValue(HasShadowProperty); + set => SetValue(HasShadowProperty, value); + } + + /// + /// Gets or sets the duration of the check animation. + /// + /// The animation duration. Default is 200 milliseconds. + public TimeSpan AnimationDuration + { + get => (TimeSpan)GetValue(AnimationDurationProperty); + set => SetValue(AnimationDurationProperty, value); + } + + /// + /// Gets or sets the animation style for the check transition. + /// + /// The animation style. Default is EaseInOut. + public CheckBoxAnimationStyle AnimationStyle + { + get => (CheckBoxAnimationStyle)GetValue(AnimationStyleProperty); + set => SetValue(AnimationStyleProperty, value); + } + + /// + /// Gets or sets the text content of the checkbox. + /// + /// The text content. Takes precedence over the Content property for text display. + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + /// + /// Gets or sets the position of the text relative to the checkbox. + /// + /// The text position. Default is Right. + public TextPosition TextPosition + { + get => (TextPosition)GetValue(TextPositionProperty); + set => SetValue(TextPositionProperty, value); + } + + /// + /// Gets or sets the margin around the text. + /// + /// The text margin. Default is 8,0,0,0 (8px right margin). + public Thickness TextMargin + { + get => (Thickness)GetValue(TextMarginProperty); + set => SetValue(TextMarginProperty, value); + } + + #endregion + + #region Constructor + + /// + /// Static constructor to override metadata for IsChecked property. + /// + static ExtendedCheckBox() + { + // Override IsChecked property to handle programmatic changes + IsCheckedProperty.OverrideMetadata(typeof(ExtendedCheckBox), + new FrameworkPropertyMetadata(OnIsCheckedChanged)); + } + + /// + /// Initializes a new instance of the class. + /// + public ExtendedCheckBox() + { + DefaultStyleKey = typeof(ExtendedCheckBox); + Loaded += ExtendedCheckBox_Loaded; + Cursor = Cursors.Hand; + FontSize = 13; + SetupEventHandlers(); + } + + #endregion + + #region Private Methods + + /// + /// Sets up event handlers for checkbox interactions. + /// + private void SetupEventHandlers() + { + MouseEnter += OnMouseEnter; + MouseLeave += OnMouseLeave; + } + + /// + /// Handles the Loaded event to create the checkbox template if not already applied. + /// + /// The event sender. + /// The event arguments. + private void ExtendedCheckBox_Loaded(object sender, RoutedEventArgs e) + { + if (!_templateApplied) + { + CreateTemplate(); + } + } + + /// + /// Creates the visual template for the checkbox programmatically. + /// + private void CreateTemplate() + { + ApplySizePreset(); + + var initialBackground = IsChecked == true ? CheckedBoxBrush : BoxBrush; + var initialBorderBrush = IsChecked == true + ? (CheckedBoxBorderBrush ?? CheckedBoxBrush) + : BoxBorderBrush; + + _checkBoxBorder = new Border + { + Width = BoxWidth, + Height = BoxHeight, + CornerRadius = CornerRadius, + Background = initialBackground, + BorderBrush = initialBorderBrush, + BorderThickness = BoxBorderThickness, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }; + + _checkMarkScaleTransform = new ScaleTransform { ScaleX = 0, ScaleY = 0 }; + _checkMarkTransformGroup = new TransformGroup(); + _checkMarkTransformGroup.Children.Add(_checkMarkScaleTransform); + + _checkMarkPath = new Path + { + Data = CheckMarkGeometry, + Fill = CheckMarkBrush, + Stretch = Stretch.Uniform, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + RenderTransform = _checkMarkTransformGroup, + RenderTransformOrigin = new Point(0.5, 0.5), + Opacity = 0 + }; + + var checkMarkViewBox = new Viewbox + { + Width = CheckMarkSize, + Height = CheckMarkSize, + Stretch = Stretch.Uniform, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Child = _checkMarkPath + }; + + var boxGrid = new Grid + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }; + boxGrid.Children.Add(checkMarkViewBox); + _checkBoxBorder.Child = boxGrid; + + _textBlock = new TextBlock + { + Text = !string.IsNullOrEmpty(Text) ? Text : (Content?.ToString() ?? ""), + Foreground = Foreground ?? new SolidColorBrush(Colors.Black), + FontFamily = FontFamily, + FontSize = FontSize, + FontWeight = FontWeight, + Margin = TextMargin, + VerticalAlignment = VerticalAlignment.Center + }; + + _shadowEffect = new DropShadowEffect + { + BlurRadius = 6, + Direction = 270, + ShadowDepth = 2, + Opacity = 0.25, + Color = Colors.Black + }; + + _contentGrid = new Grid(); + SetupLayout(); + + Content = _contentGrid; + + UpdateCheckMarkVisibility(false); + UpdateShadow(); + _templateApplied = true; + } + + + + /// + /// Gets the default check mark geometry. + /// + /// The default check mark path geometry. + private static Geometry GetDefaultCheckMarkGeometry() + { + // Material Design check mark + return Geometry.Parse("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"); + } + + /// + /// Applies predefined size settings based on CheckBoxSize property. + /// + private void ApplySizePreset() + { + switch (CheckBoxSize) + { + case CheckBoxSize.Small: + SetValue(BoxWidthProperty, 16.0); + SetValue(BoxHeightProperty, 16.0); + SetValue(CheckMarkSizeProperty, 10.0); + break; + case CheckBoxSize.Medium: + SetValue(BoxWidthProperty, 20.0); + SetValue(BoxHeightProperty, 20.0); + SetValue(CheckMarkSizeProperty, 12.0); + break; + case CheckBoxSize.Large: + SetValue(BoxWidthProperty, 24.0); + SetValue(BoxHeightProperty, 24.0); + SetValue(CheckMarkSizeProperty, 16.0); + break; + case CheckBoxSize.Custom: + // Keep current values + break; + } + } + + /// + /// Sets up the layout of the checkbox and text based on TextPosition. + /// + private void SetupLayout() + { + if (_contentGrid == null) return; + + // Clear previous layout + _contentGrid.Children.Clear(); + _contentGrid.RowDefinitions.Clear(); + _contentGrid.ColumnDefinitions.Clear(); + + bool hasText = !string.IsNullOrEmpty(Text) || (Content != null && !string.IsNullOrEmpty(Content.ToString())); + + if (!hasText) + { + // Only checkbox + _contentGrid.Children.Add(_checkBoxBorder); + } + else + { + // Checkbox with text + switch (TextPosition) + { + case TextPosition.Left: + SetupHorizontalLayout(textFirst: true); + break; + case TextPosition.Right: + SetupHorizontalLayout(textFirst: false); + break; + case TextPosition.Top: + SetupVerticalLayout(textFirst: true); + break; + case TextPosition.Bottom: + SetupVerticalLayout(textFirst: false); + break; + } + + _contentGrid.Children.Add(_checkBoxBorder); + _contentGrid.Children.Add(_textBlock); + } + } + + /// + /// Sets up horizontal layout (Left/Right text positions). + /// + /// Whether the text should be placed first. + private void SetupHorizontalLayout(bool textFirst) + { + _contentGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + _contentGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + + if (textFirst) + { + Grid.SetColumn(_textBlock, 0); + Grid.SetColumn(_checkBoxBorder, 1); + _textBlock.Margin = new Thickness(TextMargin.Left, TextMargin.Top, TextMargin.Right, TextMargin.Bottom); + _checkBoxBorder.Margin = new Thickness(8, 0, 0, 0); + } + else + { + Grid.SetColumn(_checkBoxBorder, 0); + Grid.SetColumn(_textBlock, 1); + _checkBoxBorder.Margin = new Thickness(0); + _textBlock.Margin = TextMargin; + } + } + + /// + /// Sets up vertical layout (Top/Bottom text positions). + /// + /// Whether the text should be placed first. + private void SetupVerticalLayout(bool textFirst) + { + _contentGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + _contentGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + + if (textFirst) + { + Grid.SetRow(_textBlock, 0); + Grid.SetRow(_checkBoxBorder, 1); + _textBlock.Margin = new Thickness(TextMargin.Left, TextMargin.Top, TextMargin.Right, TextMargin.Bottom); + _checkBoxBorder.Margin = new Thickness(0, 8, 0, 0); + } + else + { + Grid.SetRow(_checkBoxBorder, 0); + Grid.SetRow(_textBlock, 1); + _checkBoxBorder.Margin = new Thickness(0); + _textBlock.Margin = TextMargin; + } + + _textBlock.HorizontalAlignment = HorizontalAlignment.Center; + _checkBoxBorder.HorizontalAlignment = HorizontalAlignment.Center; + } + + /// + /// Updates the check mark geometry based on current settings. + /// + private void UpdateCheckMarkGeometry() + { + if (_checkMarkPath == null) return; + + // Simple logic: Show uses CheckMarkGeometry, None hides it + Geometry geometry = CheckMarkStyle == CheckMarkStyle.Show ? CheckMarkGeometry : null; + + _checkMarkPath.Data = geometry; + _checkMarkPath.Visibility = geometry != null ? Visibility.Visible : Visibility.Collapsed; + } + + /// + /// Updates the visibility and animation of the check mark. + /// + /// Whether to animate the transition. + private void UpdateCheckMarkVisibility(bool animate) + { + if (_checkMarkPath == null) return; + + bool shouldShow = IsChecked == true && CheckMarkStyle == CheckMarkStyle.Show; + + if (animate && AnimationStyle != CheckBoxAnimationStyle.None) + { + AnimateCheckMark(shouldShow); + } + else + { + _checkMarkScaleTransform.ScaleX = shouldShow ? 1 : 0; + _checkMarkScaleTransform.ScaleY = shouldShow ? 1 : 0; + _checkMarkPath.Opacity = shouldShow ? 1 : 0; + } + } + + /// + /// Animates the check mark appearance/disappearance. + /// + /// Whether to show or hide the check mark. + private void AnimateCheckMark(bool show) + { + // Stop any existing animation + if (_animationTimer != null) + { + _animationTimer.Stop(); + _animationTimer = null; + } + + if (AnimationStyle == CheckBoxAnimationStyle.None) + { + _checkMarkScaleTransform.ScaleX = show ? 1 : 0; + _checkMarkScaleTransform.ScaleY = show ? 1 : 0; + _checkMarkPath.Opacity = show ? 1 : 0; + return; + } + + // Setup animation parameters + _animationStartOpacity = _checkMarkPath.Opacity; + _animationTargetOpacity = show ? 1 : 0; + _animationStartTime = DateTime.Now; + + // Set easing function + switch (AnimationStyle) + { + case CheckBoxAnimationStyle.EaseInOut: + _currentEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut }; + break; + case CheckBoxAnimationStyle.Bounce: + _currentEasingFunction = new BounceEase { EasingMode = EasingMode.EaseOut, Bounces = 2, Bounciness = 2 }; + break; + case CheckBoxAnimationStyle.Linear: + default: + _currentEasingFunction = null; + break; + } + + // Create and start timer + _animationTimer = new DispatcherTimer(); + _animationTimer.Interval = TimeSpan.FromMilliseconds(16); // ~60fps + _animationTimer.Tick += OnCheckMarkAnimationTick; + _animationTimer.Start(); + } + + /// + /// Handles animation timer tick for check mark animation. + /// + /// The event sender. + /// The event arguments. + private void OnCheckMarkAnimationTick(object sender, EventArgs e) + { + if (_animationTimer == null) return; + + var elapsed = DateTime.Now - _animationStartTime; + var progress = Math.Min(1.0, elapsed.TotalMilliseconds / AnimationDuration.TotalMilliseconds); + + // Apply easing function if available + var easedProgress = progress; + if (_currentEasingFunction != null) + { + easedProgress = _currentEasingFunction.Ease(progress); + } + + // Update check mark scale and opacity + _checkMarkScaleTransform.ScaleX = easedProgress * (_animationTargetOpacity > 0 ? 1 : 0) + + (1 - easedProgress) * (_animationTargetOpacity > 0 ? 0 : 1); + _checkMarkScaleTransform.ScaleY = _checkMarkScaleTransform.ScaleX; + + var opacityDistance = _animationTargetOpacity - _animationStartOpacity; + _checkMarkPath.Opacity = _animationStartOpacity + (opacityDistance * easedProgress); + + // Check if animation is complete + if (progress >= 1.0) + { + _animationTimer.Stop(); + _animationTimer = null; + + // Ensure final values are set + _checkMarkScaleTransform.ScaleX = _animationTargetOpacity > 0 ? 1 : 0; + _checkMarkScaleTransform.ScaleY = _animationTargetOpacity > 0 ? 1 : 0; + _checkMarkPath.Opacity = _animationTargetOpacity; + } + } + + /// + /// Updates the shadow effect based on the HasShadow property. + /// + private void UpdateShadow() + { + if (_checkBoxBorder == null) return; + _checkBoxBorder.Effect = HasShadow ? _shadowEffect : null; + } + + /// + /// Updates the visual state of the checkbox. + /// + private void UpdateVisualState() + { + if (_checkBoxBorder == null) return; + + // Update background color + Brush backgroundBrush; + Brush borderBrush; + + if (_isHovered) + { + backgroundBrush = IsChecked == true + ? (HoverCheckedBoxBrush ?? GetCalculatedHoverColor(CheckedBoxBrush)) + : (HoverBoxBrush ?? GetCalculatedHoverColor(BoxBrush)); + } + else + { + backgroundBrush = IsChecked == true ? CheckedBoxBrush : BoxBrush; + } + + borderBrush = IsChecked == true + ? (CheckedBoxBorderBrush ?? CheckedBoxBrush) + : BoxBorderBrush; + + _checkBoxBorder.Background = backgroundBrush; + _checkBoxBorder.BorderBrush = borderBrush; + } + + /// + /// Calculates a lighter color variant for hover states. + /// + /// The original brush. + /// A brush with a lighter color, or the original brush if not a solid color. + private Brush GetCalculatedHoverColor(Brush originalBrush) + { + if (originalBrush is SolidColorBrush solidBrush) + { + var color = solidBrush.Color; + return new SolidColorBrush(Color.FromArgb( + color.A, + (byte)Math.Min(255, color.R + 20), + (byte)Math.Min(255, color.G + 20), + (byte)Math.Min(255, color.B + 20) + )); + } + return originalBrush; + } + + #endregion + + #region Event Handlers + + /// + /// Handles the mouse enter event to trigger hover state. + /// + /// The event sender. + /// The mouse event arguments. + private void OnMouseEnter(object sender, MouseEventArgs e) + { + if (IsEnabled) + { + _isHovered = true; + UpdateVisualState(); + } + } + + /// + /// Handles the mouse leave event to return to normal state. + /// + /// The event sender. + /// The mouse event arguments. + private void OnMouseLeave(object sender, MouseEventArgs e) + { + if (IsEnabled) + { + _isHovered = false; + UpdateVisualState(); + } + } + + #endregion + + #region Property Change Handlers + + /// + /// Handles changes to the IsChecked property (including programmatic changes). + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnIsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox) + { + if (checkBox._templateApplied) + { + checkBox.UpdateCheckMarkVisibility(true); + checkBox.UpdateVisualState(); + } + // If template is not applied yet, the CreateTemplate() method will handle the initial state + } + } + + /// + /// Handles changes to the CheckBoxSize property. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnCheckBoxSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox) + { + checkBox.ApplySizePreset(); + if (checkBox._templateApplied) + { + checkBox.UpdateLayout(); + } + } + } + + /// + /// Handles changes to layout-affecting properties. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnLayoutPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox && checkBox._templateApplied) + { + checkBox.UpdateLayout(); + } + } + + /// + /// Updates layout when properties change. + /// + private void UpdateLayout() + { + if (_checkBoxBorder == null) return; + + _checkBoxBorder.Width = BoxWidth; + _checkBoxBorder.Height = BoxHeight; + _checkBoxBorder.CornerRadius = CornerRadius; + + if (_checkMarkPath != null) + { + if (_checkMarkPath.Parent is Viewbox vb) + { + vb.Width = CheckMarkSize; + vb.Height = CheckMarkSize; + } + _checkMarkPath.Stretch = Stretch.Uniform; + UpdateCheckMarkGeometry(); + } + + if (_textBlock != null) + { + _textBlock.Text = !string.IsNullOrEmpty(Text) ? Text : (Content?.ToString() ?? ""); + } + + SetupLayout(); + UpdateShadow(); + } + + + /// + /// Handles changes to the CornerRadius property. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnCornerRadiusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox && checkBox._checkBoxBorder != null) + { + checkBox._checkBoxBorder.CornerRadius = (CornerRadius)e.NewValue; + } + } + + /// + /// Handles changes to the CheckMarkStyle property. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnCheckMarkStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox) + { + checkBox.UpdateCheckMarkGeometry(); + checkBox.UpdateCheckMarkVisibility(false); + } + } + + /// + /// Handles changes to the CheckMarkGeometry property. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnCheckMarkGeometryChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox && checkBox._checkMarkPath != null) + { + // Directly update the Path.Data when geometry changes + checkBox._checkMarkPath.Data = (Geometry)e.NewValue; + checkBox.UpdateCheckMarkGeometry(); + } + } + + /// + /// Handles changes to the CheckMarkBrush property. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnCheckMarkBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox && checkBox._checkMarkPath != null) + { + checkBox._checkMarkPath.Fill = (Brush)e.NewValue; + } + } + + /// + /// Handles changes to the CheckMarkSize property. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnCheckMarkSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox && checkBox._checkMarkPath != null) + { + var size = (double)e.NewValue; + checkBox._checkMarkPath.Width = size; + checkBox._checkMarkPath.Height = size; + } + } + + /// + /// Handles changes to box brush properties. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnBoxBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox) + { + checkBox.UpdateVisualState(); + } + } + + /// + /// Handles changes to box border brush properties. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnBoxBorderBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox) + { + checkBox.UpdateVisualState(); + } + } + + /// + /// Handles changes to the BoxBorderThickness property. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnBoxBorderThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox && checkBox._checkBoxBorder != null) + { + checkBox._checkBoxBorder.BorderThickness = (Thickness)e.NewValue; + } + } + + /// + /// Handles changes to the HasShadow property. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnShadowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox) + { + checkBox.UpdateShadow(); + } + } + + /// + /// Handles changes to the Text property. + /// + /// The dependency object. + /// The dependency property changed event arguments. + private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ExtendedCheckBox checkBox && checkBox._templateApplied) + { + checkBox.UpdateLayout(); + } + } + + #endregion + + #region Overrides + + /// + /// Called when the content property changes. + /// + /// The old content. + /// The new content. + protected override void OnContentChanged(object oldContent, object newContent) + { + // Don't call base to prevent default content handling + // We handle content ourselves in the template + + if (_templateApplied) + { + UpdateLayout(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/OpenSilver.ControlsKit.Controls/ExtendedSlider.cs b/src/OpenSilver.ControlsKit.Controls/ExtendedSlider.cs new file mode 100644 index 0000000..fd95588 --- /dev/null +++ b/src/OpenSilver.ControlsKit.Controls/ExtendedSlider.cs @@ -0,0 +1,744 @@ +ο»Ώ/*=================================================================================== +* +* Copyright (c) Userware (OpenSilver.net) +* +* This file is part of the OpenSilver.ControlsKit (https://opensilver.net), which +* is licensed under the MIT license (https://opensource.org/licenses/MIT). +* +* As stated in the MIT license, "the above copyright notice and this permission +* notice shall be included in all copies or substantial portions of the Software." +* +*====================================================================================*/ + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Effects; + +namespace OpenSilver.ControlsKit.Controls +{ + /// + /// Specifies the size of the slider control. + /// + public enum SliderSize + { + /// + /// Small slider size. + /// + Small, + /// + /// Medium slider size. + /// + Medium, + /// + /// Large slider size. + /// + Large, + /// + /// Custom slider size. + /// + Custom + } + + /// + /// A customizable slider control with smooth animations, hover effects, and flexible styling options. + /// Provides advanced visual capabilities including shadows, custom colors for different states, and smooth transitions. + /// + public class ExtendedSlider : ContentControl + { + #region Events + + /// + /// Occurs when the Value property changes. + /// + public event RoutedPropertyChangedEventHandler ValueChanged; + + #endregion + + #region Dependency Properties + + /// + /// Identifies the Value dependency property. + /// + public static readonly DependencyProperty ValueProperty = + DependencyProperty.Register(nameof(Value), typeof(double), typeof(ExtendedSlider), + new PropertyMetadata(0.0, OnValueChanged, CoerceValue)); + + /// + /// Identifies the Minimum dependency property. + /// + public static readonly DependencyProperty MinimumProperty = + DependencyProperty.Register(nameof(Minimum), typeof(double), typeof(ExtendedSlider), + new PropertyMetadata(0.0, OnRangeChanged)); + + /// + /// Identifies the Maximum dependency property. + /// + public static readonly DependencyProperty MaximumProperty = + DependencyProperty.Register(nameof(Maximum), typeof(double), typeof(ExtendedSlider), + new PropertyMetadata(100.0, OnRangeChanged)); + + /// + /// Identifies the SliderSize dependency property. + /// + public static readonly DependencyProperty SliderSizeProperty = + DependencyProperty.Register(nameof(SliderSize), typeof(SliderSize), typeof(ExtendedSlider), + new PropertyMetadata(SliderSize.Medium, OnSliderSizeChanged)); + + /// + /// Identifies the TrackWidth dependency property. + /// + public static readonly DependencyProperty TrackWidthProperty = + DependencyProperty.Register(nameof(TrackWidth), typeof(double), typeof(ExtendedSlider), + new PropertyMetadata(200.0, OnLayoutChanged)); + + /// + /// Identifies the TrackHeight dependency property. + /// + public static readonly DependencyProperty TrackHeightProperty = + DependencyProperty.Register(nameof(TrackHeight), typeof(double), typeof(ExtendedSlider), + new PropertyMetadata(6.0, OnLayoutChanged)); + + /// + /// Identifies the ThumbSize dependency property. + /// + public static readonly DependencyProperty ThumbSizeProperty = + DependencyProperty.Register(nameof(ThumbSize), typeof(double), typeof(ExtendedSlider), + new PropertyMetadata(20.0, OnLayoutChanged)); + + /// + /// Identifies the CornerRadius dependency property. + /// + public static readonly DependencyProperty CornerRadiusProperty = + DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(ExtendedSlider), + new PropertyMetadata(new CornerRadius(3), OnStyleChanged)); + + /// + /// Identifies the TrackBrush dependency property. + /// + public static readonly DependencyProperty TrackBrushProperty = + DependencyProperty.Register(nameof(TrackBrush), typeof(Brush), typeof(ExtendedSlider), + new PropertyMetadata(new SolidColorBrush(Colors.LightGray), OnStyleChanged)); + + /// + /// Identifies the TrackBorderBrush dependency property. + /// + public static readonly DependencyProperty TrackBorderBrushProperty = + DependencyProperty.Register(nameof(TrackBorderBrush), typeof(Brush), typeof(ExtendedSlider), + new PropertyMetadata(null, OnStyleChanged)); + + /// + /// Identifies the TrackBorderThickness dependency property. + /// + public static readonly DependencyProperty TrackBorderThicknessProperty = + DependencyProperty.Register(nameof(TrackBorderThickness), typeof(Thickness), typeof(ExtendedSlider), + new PropertyMetadata(new Thickness(0), OnStyleChanged)); + + /// + /// Identifies the FillBrush dependency property. + /// + public static readonly DependencyProperty FillBrushProperty = + DependencyProperty.Register(nameof(FillBrush), typeof(Brush), typeof(ExtendedSlider), + new PropertyMetadata(new SolidColorBrush(Colors.DodgerBlue), OnStyleChanged)); + + /// + /// Identifies the ThumbBrush dependency property. + /// + public static readonly DependencyProperty ThumbBrushProperty = + DependencyProperty.Register(nameof(ThumbBrush), typeof(Brush), typeof(ExtendedSlider), + new PropertyMetadata(new SolidColorBrush(Colors.White), OnStyleChanged)); + + /// + /// Identifies the ThumbBorderBrush dependency property. + /// + public static readonly DependencyProperty ThumbBorderBrushProperty = + DependencyProperty.Register(nameof(ThumbBorderBrush), typeof(Brush), typeof(ExtendedSlider), + new PropertyMetadata(new SolidColorBrush(Colors.Gray), OnStyleChanged)); + + /// + /// Identifies the ThumbBorderThickness dependency property. + /// + public static readonly DependencyProperty ThumbBorderThicknessProperty = + DependencyProperty.Register(nameof(ThumbBorderThickness), typeof(Thickness), typeof(ExtendedSlider), + new PropertyMetadata(new Thickness(1), OnStyleChanged)); + + /// + /// Identifies the HasShadow dependency property. + /// + public static readonly DependencyProperty HasShadowProperty = + DependencyProperty.Register(nameof(HasShadow), typeof(bool), typeof(ExtendedSlider), + new PropertyMetadata(true, OnStyleChanged)); + + /// + /// Identifies the IsAnimated dependency property. + /// + public static readonly DependencyProperty IsAnimatedProperty = + DependencyProperty.Register(nameof(IsAnimated), typeof(bool), typeof(ExtendedSlider), + new PropertyMetadata(true)); + + #endregion + + #region Properties + + /// + /// Gets or sets the current value of the slider. + /// + public double Value + { + get => (double)GetValue(ValueProperty); + set => SetValue(ValueProperty, value); + } + + /// + /// Gets or sets the minimum value of the slider. + /// + public double Minimum + { + get => (double)GetValue(MinimumProperty); + set => SetValue(MinimumProperty, value); + } + + /// + /// Gets or sets the maximum value of the slider. + /// + public double Maximum + { + get => (double)GetValue(MaximumProperty); + set => SetValue(MaximumProperty, value); + } + + /// + /// Gets or sets the predefined size of the slider. + /// + public SliderSize SliderSize + { + get => (SliderSize)GetValue(SliderSizeProperty); + set => SetValue(SliderSizeProperty, value); + } + + /// + /// Gets or sets the width of the track. + /// + public double TrackWidth + { + get => (double)GetValue(TrackWidthProperty); + set => SetValue(TrackWidthProperty, value); + } + + /// + /// Gets or sets the height of the track. + /// + public double TrackHeight + { + get => (double)GetValue(TrackHeightProperty); + set => SetValue(TrackHeightProperty, value); + } + + /// + /// Gets or sets the size of the thumb. + /// + public double ThumbSize + { + get => (double)GetValue(ThumbSizeProperty); + set => SetValue(ThumbSizeProperty, value); + } + + /// + /// Gets or sets the corner radius of the slider track. + /// + public CornerRadius CornerRadius + { + get => (CornerRadius)GetValue(CornerRadiusProperty); + set => SetValue(CornerRadiusProperty, value); + } + + /// + /// Gets or sets the brush used for the track background. + /// + public Brush TrackBrush + { + get => (Brush)GetValue(TrackBrushProperty); + set => SetValue(TrackBrushProperty, value); + } + + /// + /// Gets or sets the border brush of the track. + /// + public Brush TrackBorderBrush + { + get => (Brush)GetValue(TrackBorderBrushProperty); + set => SetValue(TrackBorderBrushProperty, value); + } + + /// + /// Gets or sets the border thickness of the track. + /// + public Thickness TrackBorderThickness + { + get => (Thickness)GetValue(TrackBorderThicknessProperty); + set => SetValue(TrackBorderThicknessProperty, value); + } + + /// + /// Gets or sets the brush used for the filled portion of the track. + /// + public Brush FillBrush + { + get => (Brush)GetValue(FillBrushProperty); + set => SetValue(FillBrushProperty, value); + } + + /// + /// Gets or sets the brush used for the thumb. + /// + public Brush ThumbBrush + { + get => (Brush)GetValue(ThumbBrushProperty); + set => SetValue(ThumbBrushProperty, value); + } + + /// + /// Gets or sets the border brush of the thumb. + /// + public Brush ThumbBorderBrush + { + get => (Brush)GetValue(ThumbBorderBrushProperty); + set => SetValue(ThumbBorderBrushProperty, value); + } + + /// + /// Gets or sets the border thickness of the thumb. + /// + public Thickness ThumbBorderThickness + { + get => (Thickness)GetValue(ThumbBorderThicknessProperty); + set => SetValue(ThumbBorderThicknessProperty, value); + } + + /// + /// Gets or sets a value indicating whether the slider has a drop shadow effect. + /// + public bool HasShadow + { + get => (bool)GetValue(HasShadowProperty); + set => SetValue(HasShadowProperty, value); + } + + /// + /// Gets or sets a value indicating whether value changes are animated. + /// + public bool IsAnimated + { + get => (bool)GetValue(IsAnimatedProperty); + set => SetValue(IsAnimatedProperty, value); + } + + #endregion + + #region Private Fields + + private Canvas _rootCanvas; + private Border _trackBorder; + private Border _fillBorder; + private Border _thumbBorder; + private bool _isDragging; + private bool _isHovered; + private Point _lastMousePosition; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the ExtendedSlider class. + /// + public ExtendedSlider() + { + InitializeComponent(); + Loaded += OnLoaded; + } + + #endregion + + #region Initialization + + private void InitializeComponent() + { + _rootCanvas = new Canvas + { + Background = new SolidColorBrush(Colors.Transparent) + }; + + _trackBorder = new Border + { + Background = TrackBrush, + BorderBrush = TrackBorderBrush, + BorderThickness = TrackBorderThickness, + CornerRadius = CornerRadius + }; + + _fillBorder = new Border + { + Background = FillBrush, + CornerRadius = CornerRadius + }; + + _thumbBorder = new Border + { + Background = ThumbBrush, + BorderBrush = ThumbBorderBrush, + BorderThickness = ThumbBorderThickness, + CornerRadius = new CornerRadius(10), + Cursor = Cursors.Hand + }; + + _rootCanvas.Children.Add(_trackBorder); + _rootCanvas.Children.Add(_fillBorder); + _rootCanvas.Children.Add(_thumbBorder); + + Content = _rootCanvas; + + SetupEventHandlers(); + } + + private void SetupEventHandlers() + { + _rootCanvas.MouseLeftButtonDown += OnCanvasMouseDown; + _rootCanvas.MouseMove += OnCanvasMouseMove; + _rootCanvas.MouseLeftButtonUp += OnCanvasMouseUp; + + _thumbBorder.MouseLeftButtonDown += OnThumbMouseDown; + _thumbBorder.MouseEnter += OnThumbMouseEnter; + _thumbBorder.MouseLeave += OnThumbMouseLeave; + + KeyDown += OnKeyDown; + Focusable = true; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + ApplySizePreset(); + UpdateLayout(); + UpdateVisualElements(); + UpdateValueDisplay(); + } + + #endregion + + #region Layout and Visual Updates + + private void ApplySizePreset() + { + switch (SliderSize) + { + case SliderSize.Small: + TrackWidth = 150; + TrackHeight = 4; + ThumbSize = 16; + break; + case SliderSize.Medium: + TrackWidth = 200; + TrackHeight = 6; + ThumbSize = 20; + break; + case SliderSize.Large: + TrackWidth = 250; + TrackHeight = 8; + ThumbSize = 24; + break; + case SliderSize.Custom: + break; + } + } + + private void UpdateLayout() + { + if (_rootCanvas == null) return; + + var canvasHeight = Math.Max(TrackHeight, ThumbSize) + 4; + _rootCanvas.Width = TrackWidth; + _rootCanvas.Height = canvasHeight; + + _trackBorder.Width = TrackWidth; + _trackBorder.Height = TrackHeight; + Canvas.SetLeft(_trackBorder, 0); + Canvas.SetTop(_trackBorder, (canvasHeight - TrackHeight) / 2); + + UpdateValueDisplay(); + + _thumbBorder.Width = ThumbSize; + _thumbBorder.Height = ThumbSize; + _thumbBorder.CornerRadius = new CornerRadius(ThumbSize / 2); + + UpdateShadowEffect(); + } + + private void UpdateValueDisplay() + { + if (_fillBorder == null || _thumbBorder == null) return; + + var range = Maximum - Minimum; + if (range <= 0) return; + + var normalizedValue = Math.Max(0, Math.Min(1, (Value - Minimum) / range)); + var canvasHeight = _rootCanvas.Height; + + var fillWidth = normalizedValue * TrackWidth; + _fillBorder.Width = fillWidth; + _fillBorder.Height = TrackHeight; + Canvas.SetLeft(_fillBorder, 0); + Canvas.SetTop(_fillBorder, (canvasHeight - TrackHeight) / 2); + + var thumbPosition = normalizedValue * (TrackWidth - ThumbSize); + + if (IsAnimated && !_isDragging) + { + AnimateThumbToPosition(thumbPosition); + } + else + { + Canvas.SetLeft(_thumbBorder, thumbPosition); + } + + Canvas.SetTop(_thumbBorder, (canvasHeight - ThumbSize) / 2); + } + + private void AnimateThumbToPosition(double targetPosition) + { + var currentPosition = Canvas.GetLeft(_thumbBorder); + + if (double.IsNaN(currentPosition)) + { + currentPosition = 0; + } + + var animation = new DoubleAnimation + { + From = currentPosition, + To = targetPosition, + Duration = TimeSpan.FromMilliseconds(150), + EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut } + }; + + var storyboard = new Storyboard(); + storyboard.Children.Add(animation); + Storyboard.SetTarget(animation, _thumbBorder); + Storyboard.SetTargetProperty(animation, new PropertyPath("(Canvas.Left)")); + storyboard.Begin(); + } + + private void UpdateVisualElements() + { + if (_trackBorder == null) return; + + _trackBorder.Background = TrackBrush; + _trackBorder.BorderBrush = TrackBorderBrush; + _trackBorder.BorderThickness = TrackBorderThickness; + _trackBorder.CornerRadius = CornerRadius; + + _fillBorder.Background = FillBrush; + _fillBorder.CornerRadius = CornerRadius; + + _thumbBorder.Background = ThumbBrush; + _thumbBorder.BorderBrush = ThumbBorderBrush; + _thumbBorder.BorderThickness = ThumbBorderThickness; + + ApplyHoverEffect(); + } + + private void ApplyHoverEffect() + { + if (_thumbBorder == null) return; + + if (_isHovered) + { + var scaleTransform = new ScaleTransform { ScaleX = 1.1, ScaleY = 1.1 }; + _thumbBorder.RenderTransform = scaleTransform; + _thumbBorder.RenderTransformOrigin = new Point(0.5, 0.5); + } + else + { + _thumbBorder.RenderTransform = null; + } + } + + private void UpdateShadowEffect() + { + if (_thumbBorder == null) return; + + if (HasShadow) + { + _thumbBorder.Effect = new DropShadowEffect + { + BlurRadius = 6, + Direction = 270, + ShadowDepth = 2, + Opacity = 0.25, + Color = Colors.Black + }; + } + else + { + _thumbBorder.Effect = null; + } + } + + #endregion + + #region Event Handlers + + private void OnCanvasMouseDown(object sender, MouseButtonEventArgs e) + { + if (!IsEnabled) return; + + var position = e.GetPosition(_rootCanvas); + UpdateValueFromPosition(position.X); + + _isDragging = true; + _rootCanvas.CaptureMouse(); + Focus(); + + e.Handled = true; + } + + private void OnCanvasMouseMove(object sender, MouseEventArgs e) + { + if (!IsEnabled || !_isDragging) return; + + var position = e.GetPosition(_rootCanvas); + UpdateValueFromPosition(position.X); + + e.Handled = true; + } + + private void OnCanvasMouseUp(object sender, MouseButtonEventArgs e) + { + if (_isDragging) + { + _isDragging = false; + _rootCanvas.ReleaseMouseCapture(); + e.Handled = true; + } + } + + private void OnThumbMouseDown(object sender, MouseButtonEventArgs e) + { + if (!IsEnabled) return; + + _isDragging = true; + _rootCanvas.CaptureMouse(); + Focus(); + + e.Handled = true; + } + + private void OnThumbMouseEnter(object sender, MouseEventArgs e) + { + _isHovered = true; + ApplyHoverEffect(); + } + + private void OnThumbMouseLeave(object sender, MouseEventArgs e) + { + _isHovered = false; + ApplyHoverEffect(); + } + + private void OnKeyDown(object sender, KeyEventArgs e) + { + if (!IsEnabled) return; + + var step = (Maximum - Minimum) / 100; + var newValue = Value; + + switch (e.Key) + { + case Key.Left: + case Key.Down: + newValue = Math.Max(Minimum, Value - step); + break; + case Key.Right: + case Key.Up: + newValue = Math.Min(Maximum, Value + step); + break; + case Key.Home: + newValue = Minimum; + break; + case Key.End: + newValue = Maximum; + break; + case Key.PageDown: + newValue = Math.Max(Minimum, Value - step * 10); + break; + case Key.PageUp: + newValue = Math.Min(Maximum, Value + step * 10); + break; + default: + return; + } + + Value = newValue; + e.Handled = true; + } + + private void UpdateValueFromPosition(double mouseX) + { + var normalizedPosition = Math.Max(0, Math.Min(1, mouseX / TrackWidth)); + var newValue = Minimum + normalizedPosition * (Maximum - Minimum); + Value = newValue; + } + + #endregion + + #region Property Change Handlers + + private static object CoerceValue(DependencyObject d, object value) + { + var slider = (ExtendedSlider)d; + var doubleValue = (double)value; + return Math.Max(slider.Minimum, Math.Min(slider.Maximum, doubleValue)); + } + + private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var slider = (ExtendedSlider)d; + + slider.ValueChanged?.Invoke(slider, new RoutedPropertyChangedEventArgs( + (double)e.OldValue, (double)e.NewValue)); + + slider.UpdateValueDisplay(); + } + + private static void OnRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var slider = (ExtendedSlider)d; + + slider.CoerceValue(ValueProperty); + slider.UpdateValueDisplay(); + } + + private static void OnSliderSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var slider = (ExtendedSlider)d; + slider.ApplySizePreset(); + slider.UpdateLayout(); + } + + private static void OnLayoutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var slider = (ExtendedSlider)d; + slider.UpdateLayout(); + } + + private static void OnStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var slider = (ExtendedSlider)d; + slider.UpdateVisualElements(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/TestApp/TestApp/Pages/TestExtendedCheckBox.xaml b/src/TestApp/TestApp/Pages/TestExtendedCheckBox.xaml new file mode 100644 index 0000000..6ed0c39 --- /dev/null +++ b/src/TestApp/TestApp/Pages/TestExtendedCheckBox.xaml @@ -0,0 +1,540 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/TestApp/TestApp/Pages/TestExtendedCheckBox.xaml.cs b/src/TestApp/TestApp/Pages/TestExtendedCheckBox.xaml.cs new file mode 100644 index 0000000..8278b96 --- /dev/null +++ b/src/TestApp/TestApp/Pages/TestExtendedCheckBox.xaml.cs @@ -0,0 +1,27 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Navigation; + +namespace TestApp.Pages +{ + public partial class TestExtendedCheckBox : Page + { + public TestExtendedCheckBox() + { + this.InitializeComponent(); + } + + // Executes when the user navigates to this page. + protected override void OnNavigatedTo(NavigationEventArgs e) + { + } + } +} diff --git a/src/TestApp/TestApp/Pages/TestExtendedSlider.xaml b/src/TestApp/TestApp/Pages/TestExtendedSlider.xaml new file mode 100644 index 0000000..e837166 --- /dev/null +++ b/src/TestApp/TestApp/Pages/TestExtendedSlider.xaml @@ -0,0 +1,617 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/TestApp/TestApp/Pages/TestExtendedSlider.xaml.cs b/src/TestApp/TestApp/Pages/TestExtendedSlider.xaml.cs new file mode 100644 index 0000000..0a59ff1 --- /dev/null +++ b/src/TestApp/TestApp/Pages/TestExtendedSlider.xaml.cs @@ -0,0 +1,27 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Navigation; + +namespace TestApp.Pages +{ + public partial class TestExtendedSlider : Page + { + public TestExtendedSlider() + { + this.InitializeComponent(); + } + + // Executes when the user navigates to this page. + protected override void OnNavigatedTo(NavigationEventArgs e) + { + } + } +} diff --git a/src/TestApp/TestApp/Registry/TestRegistry.cs b/src/TestApp/TestApp/Registry/TestRegistry.cs index c5694d9..b4c67bd 100644 --- a/src/TestApp/TestApp/Registry/TestRegistry.cs +++ b/src/TestApp/TestApp/Registry/TestRegistry.cs @@ -17,6 +17,8 @@ static TestRegistry() new TreeItem("FlexPanel", "FlexPanel"), new TreeItem("ExtendedButton", "TestExtendedButtonControl"), new TreeItem("ExtendedSwitch", "TestExtendedSwitch"), + new TreeItem("ExtendedSlider", "TestExtendedSlider"), + new TreeItem("ExtendedCheckBox", "TestExtendedCheckBox"), }; } } diff --git a/src/TestApp/TestApp/TestApp.csproj b/src/TestApp/TestApp/TestApp.csproj index 53f1c9e..02cc35d 100644 --- a/src/TestApp/TestApp/TestApp.csproj +++ b/src/TestApp/TestApp/TestApp.csproj @@ -32,6 +32,12 @@ MSBuild:Compile + + MSBuild:Compile + + + MSBuild:Compile + MSBuild:Compile @@ -48,6 +54,8 @@ + +