#region Imports using DPumpHydr.WinFrmUI.RLT.Extension; using DPumpHydr.WinFrmUI.RLT.Manager; using DPumpHydr.WinFrmUI.RLT.Util; using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Text; using System.Windows.Forms; using static DPumpHydr.WinFrmUI.RLT.Helper.MaterialDrawHelper; using static DPumpHydr.WinFrmUI.RLT.Util.MaterialAnimations; #endregion namespace DPumpHydr.WinFrmUI.RLT.Controls { #region MaterialSwitch public class MaterialSwitch : System.Windows.Forms.CheckBox, MaterialControlI { [Browsable(false)] public int Depth { get; set; } [Browsable(false)] public MaterialSkinManager SkinManager => MaterialSkinManager.Instance; [Browsable(false)] public MaterialMouseState MouseState { get; set; } [Browsable(false)] public Point MouseLocation { get; set; } private bool useAccentColor; [Category("Material")] public bool UseAccentColor { get => useAccentColor; set { useAccentColor = value; Invalidate(); } } private bool _ripple; [Category("Appearance")] public bool Ripple { get => _ripple; set { _ripple = value; AutoSize = AutoSize; //Make AutoSize directly set the bounds. if (value) { Margin = new Padding(0); } Invalidate(); } } [Category("Appearance")] [Browsable(true), DefaultValue(false), EditorBrowsable(EditorBrowsableState.Always)] public bool ReadOnly { get; set; } private readonly AnimationManager _checkAM; private readonly AnimationManager _hoverAM; private readonly AnimationManager _rippleAM; private const int THUMB_SIZE = 22; private const int THUMB_SIZE_HALF = THUMB_SIZE / 2; private const int TRACK_SIZE_HEIGHT = (int)14; private const int TRACK_SIZE_WIDTH = (int)36; private const int TRACK_RADIUS = (int)(TRACK_SIZE_HEIGHT / 2); private int TRACK_CENTER_Y; private int TRACK_CENTER_X_BEGIN; private int TRACK_CENTER_X_END; private int TRACK_CENTER_X_DELTA; private const int RIPPLE_DIAMETER = 37; private int _trackOffsetY; public MaterialSwitch() { _checkAM = new AnimationManager { AnimationType = AnimationType.EaseInOut, Increment = 0.05 }; _hoverAM = new AnimationManager(true) { AnimationType = AnimationType.Linear, Increment = 0.10 }; _rippleAM = new AnimationManager(false) { AnimationType = AnimationType.Linear, Increment = 0.10, SecondaryIncrement = 0.08 }; _checkAM.OnAnimationProgress += sender => Invalidate(); _rippleAM.OnAnimationProgress += sender => Invalidate(); _hoverAM.OnAnimationProgress += sender => Invalidate(); CheckedChanged += (sender, args) => { if (Ripple) { _checkAM.StartNewAnimation(Checked ? AnimationDirection.In : AnimationDirection.Out); } }; Ripple = true; MouseLocation = new Point(-1, -1); ReadOnly = false; } protected override void OnClick(EventArgs e) { if (!ReadOnly) { base.OnClick(e); } } protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); _trackOffsetY = (Height / 2) - THUMB_SIZE_HALF; TRACK_CENTER_Y = _trackOffsetY + THUMB_SIZE_HALF - 1; TRACK_CENTER_X_BEGIN = TRACK_CENTER_Y; TRACK_CENTER_X_END = TRACK_CENTER_X_BEGIN + TRACK_SIZE_WIDTH - (TRACK_RADIUS * 2); TRACK_CENTER_X_DELTA = TRACK_CENTER_X_END - TRACK_CENTER_X_BEGIN; } public override Size GetPreferredSize(Size proposedSize) { Size strSize; using (MaterialNativeTextRenderer NativeText = new(CreateGraphics())) { strSize = NativeText.MeasureLogString(Text, SkinManager.GetLogFontByType(MaterialSkinManager.FontType.Body1)); } int w = TRACK_SIZE_WIDTH + THUMB_SIZE + strSize.Width; return Ripple ? new Size(w, RIPPLE_DIAMETER) : new Size(w, THUMB_SIZE); } private static readonly Point[] CheckmarkLine = { new(3, 8), new(7, 12), new(14, 5) }; private const int TEXT_OFFSET = THUMB_SIZE; protected override void OnPaint(PaintEventArgs pevent) { Graphics g = pevent.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; g.Clear(Parent.BackColor == Color.Transparent ? ((Parent.Parent == null || (Parent.Parent != null && Parent.Parent.BackColor == Color.Transparent)) ? SkinManager.BackgroundColor : Parent.Parent.BackColor) : Parent.BackColor); double animationProgress = _checkAM.GetProgress(); // Draw Track Color thumbColor = BlendColor( Enabled ? SkinManager.SwitchOffThumbColor : SkinManager.SwitchOffDisabledThumbColor, // Off color Enabled ? UseAccentColor ? SkinManager.ColorScheme.AccentColor : SkinManager.ColorScheme.PrimaryColor : BlendColor(UseAccentColor ? SkinManager.ColorScheme.AccentColor : SkinManager.ColorScheme.PrimaryColor, SkinManager.SwitchOffDisabledThumbColor, 197), // On color animationProgress * 255); // Blend amount using (GraphicsPath path = CreateRoundRect(new Rectangle(TRACK_CENTER_X_BEGIN - TRACK_RADIUS, TRACK_CENTER_Y - (TRACK_SIZE_HEIGHT / 2), TRACK_SIZE_WIDTH, TRACK_SIZE_HEIGHT), TRACK_RADIUS)) { using SolidBrush trackBrush = new( Color.FromArgb(Enabled ? SkinManager.SwitchOffTrackColor.A : SkinManager.BackgroundDisabledColor.A, // Track alpha BlendColor( // animate color Enabled ? SkinManager.SwitchOffTrackColor : SkinManager.BackgroundDisabledColor, // Off color UseAccentColor ? SkinManager.ColorScheme.AccentColor : SkinManager.ColorScheme.PrimaryColor, // On color animationProgress * 255) // Blend amount .RemoveAlpha())); g.FillPath(trackBrush, path); } // Calculate animation movement X position int OffsetX = (int)(TRACK_CENTER_X_DELTA * animationProgress); // Ripple int rippleSize = (Height % 2 == 0) ? Height - 2 : Height - 3; Color rippleColor = Color.FromArgb(40, // color alpha Checked ? UseAccentColor ? SkinManager.ColorScheme.AccentColor : SkinManager.ColorScheme.PrimaryColor : // On color (SkinManager.Theme == MaterialSkinManager.Themes.LIGHT ? Color.Black : Color.White)); // Off color if (Ripple && _rippleAM.IsAnimating()) { for (int i = 0; i < _rippleAM.GetAnimationCount(); i++) { double rippleAnimProgress = _rippleAM.GetProgress(i); int rippleAnimatedDiameter = (_rippleAM.GetDirection(i) == AnimationDirection.InOutIn) ? (int)(rippleSize * (0.7 + (0.3 * rippleAnimProgress))) : rippleSize; using SolidBrush rippleBrush = new(Color.FromArgb((int)(40 * rippleAnimProgress), rippleColor.RemoveAlpha())); g.FillEllipse(rippleBrush, new Rectangle(TRACK_CENTER_X_BEGIN + OffsetX - (rippleAnimatedDiameter / 2), TRACK_CENTER_Y - (rippleAnimatedDiameter / 2), rippleAnimatedDiameter, rippleAnimatedDiameter)); } } // Hover if (Ripple) { double rippleAnimProgress = _hoverAM.GetProgress(); int rippleAnimatedDiameter = (int)(rippleSize * (0.7 + (0.3 * rippleAnimProgress))); using SolidBrush rippleBrush = new(Color.FromArgb((int)(40 * rippleAnimProgress), rippleColor.RemoveAlpha())); g.FillEllipse(rippleBrush, new Rectangle(TRACK_CENTER_X_BEGIN + OffsetX - (rippleAnimatedDiameter / 2), TRACK_CENTER_Y - (rippleAnimatedDiameter / 2), rippleAnimatedDiameter, rippleAnimatedDiameter)); } // draw Thumb Shadow RectangleF thumbBounds = new(TRACK_CENTER_X_BEGIN + OffsetX - THUMB_SIZE_HALF, TRACK_CENTER_Y - THUMB_SIZE_HALF, THUMB_SIZE, THUMB_SIZE); using (SolidBrush shadowBrush = new(Color.FromArgb(12, 0, 0, 0))) { g.FillEllipse(shadowBrush, new RectangleF(thumbBounds.X - 2, thumbBounds.Y - 1, thumbBounds.Width + 4, thumbBounds.Height + 6)); g.FillEllipse(shadowBrush, new RectangleF(thumbBounds.X - 1, thumbBounds.Y - 1, thumbBounds.Width + 2, thumbBounds.Height + 4)); g.FillEllipse(shadowBrush, new RectangleF(thumbBounds.X - 0, thumbBounds.Y - 0, thumbBounds.Width + 0, thumbBounds.Height + 2)); g.FillEllipse(shadowBrush, new RectangleF(thumbBounds.X - 0, thumbBounds.Y + 2, thumbBounds.Width + 0, thumbBounds.Height + 0)); g.FillEllipse(shadowBrush, new RectangleF(thumbBounds.X - 0, thumbBounds.Y + 1, thumbBounds.Width + 0, thumbBounds.Height + 0)); } // draw Thumb using (SolidBrush thumbBrush = new(thumbColor)) { g.FillEllipse(thumbBrush, thumbBounds); } // draw text using MaterialNativeTextRenderer NativeText = new(g); Rectangle textLocation = new(TEXT_OFFSET + TRACK_SIZE_WIDTH, 0, Width - (TEXT_OFFSET + TRACK_SIZE_WIDTH), Height); NativeText.DrawTransparentText( Text, SkinManager.GetLogFontByType(MaterialSkinManager.FontType.Body1), Enabled ? SkinManager.TextHighEmphasisColor : SkinManager.TextDisabledOrHintColor, textLocation.Location, textLocation.Size, MaterialNativeTextRenderer.TextAlignFlags.Left | MaterialNativeTextRenderer.TextAlignFlags.Middle); } private Bitmap DrawCheckMarkBitmap() { Bitmap checkMark = new(THUMB_SIZE, THUMB_SIZE); Graphics g = Graphics.FromImage(checkMark); // clear everything, transparent g.Clear(Color.Transparent); // draw the checkmark lines using (Pen pen = new(Parent.BackColor, 2)) { g.DrawLines(pen, CheckmarkLine); } return checkMark; } public override bool AutoSize { get => base.AutoSize; set { base.AutoSize = value; if (value) { Size = new Size(10, 10); } } } private bool IsMouseInCheckArea() { return ClientRectangle.Contains(MouseLocation); } private bool hovered = false; protected override void OnCreateControl() { base.OnCreateControl(); if (DesignMode) { return; } MouseState = MaterialMouseState.OUT; GotFocus += (sender, AddingNewEventArgs) => { if (Ripple && !hovered) { _hoverAM.StartNewAnimation(AnimationDirection.In, new object[] { Checked }); hovered = true; } }; LostFocus += (sender, args) => { if (Ripple && hovered) { _hoverAM.StartNewAnimation(AnimationDirection.Out, new object[] { Checked }); hovered = false; } }; MouseEnter += (sender, args) => { MouseState = MaterialMouseState.HOVER; //if (Ripple && !hovered) //{ // _hoverAM.StartNewAnimation(AnimationDirection.In, new object[] { Checked }); // hovered = true; //} }; MouseLeave += (sender, args) => { MouseLocation = new Point(-1, -1); MouseState = MaterialMouseState.OUT; //if (Ripple && hovered) //{ // _hoverAM.StartNewAnimation(AnimationDirection.Out, new object[] { Checked }); // hovered = false; //} }; MouseDown += (sender, args) => { MouseState = MaterialMouseState.DOWN; if (Ripple) { _rippleAM.SecondaryIncrement = 0; _rippleAM.StartNewAnimation(AnimationDirection.InOutIn, new object[] { Checked }); } }; KeyDown += (sender, args) => { if (Ripple && (args.KeyCode == Keys.Space) && _rippleAM.GetAnimationCount() == 0) { _rippleAM.SecondaryIncrement = 0; _rippleAM.StartNewAnimation(AnimationDirection.InOutIn, new object[] { Checked }); } }; MouseUp += (sender, args) => { if (Ripple) { MouseState = MaterialMouseState.HOVER; _rippleAM.SecondaryIncrement = 0.08; _hoverAM.StartNewAnimation(AnimationDirection.Out, new object[] { Checked }); hovered = false; } }; KeyUp += (sender, args) => { if (Ripple && (args.KeyCode == Keys.Space)) { MouseState = MaterialMouseState.HOVER; _rippleAM.SecondaryIncrement = 0.08; } }; MouseMove += (sender, args) => { MouseLocation = args.Location; Cursor = IsMouseInCheckArea() ? Cursors.Hand : Cursors.Default; }; } } #endregion }