using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Text; using System.Windows.Forms; using MaterialSkin.Animations; namespace MaterialSkin.Controls { public class MaterialCheckBox : CheckBox, IMaterialControl { [Browsable(false)] public int Depth { get; set; } [Browsable(false)] public MaterialSkinManager SkinManager => MaterialSkinManager.Instance; [Browsable(false)] public MouseState MouseState { get; set; } [Browsable(false)] public Point MouseLocation { get; set; } private bool _ripple; [Category("Behavior")] public bool Ripple { get { return _ripple; } set { _ripple = value; AutoSize = AutoSize; //Make AutoSize directly set the bounds. if (value) { Margin = new Padding(0); } Invalidate(); } } private readonly AnimationManager _animationManager; private readonly AnimationManager _rippleAnimationManager; private const int CHECKBOX_SIZE = 18; private const int CHECKBOX_SIZE_HALF = CHECKBOX_SIZE / 2; private const int CHECKBOX_INNER_BOX_SIZE = CHECKBOX_SIZE - 4; private int _boxOffset; private Rectangle _boxRectangle; public MaterialCheckBox() { _animationManager = new AnimationManager { AnimationType = AnimationType.EaseInOut, Increment = 0.05 }; _rippleAnimationManager = new AnimationManager(false) { AnimationType = AnimationType.Linear, Increment = 0.10, SecondaryIncrement = 0.08 }; _animationManager.OnAnimationProgress += sender => Invalidate(); _rippleAnimationManager.OnAnimationProgress += sender => Invalidate(); CheckedChanged += (sender, args) => { _animationManager.StartNewAnimation(Checked ? AnimationDirection.In : AnimationDirection.Out); }; Ripple = true; MouseLocation = new Point(-1, -1); } protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); _boxOffset = Height / 2 - 9; _boxRectangle = new Rectangle(_boxOffset, _boxOffset, CHECKBOX_SIZE - 1, CHECKBOX_SIZE - 1); } public override Size GetPreferredSize(Size proposedSize) { var w = _boxOffset + CHECKBOX_SIZE + 2 + (int)CreateGraphics().MeasureString(Text, SkinManager.ROBOTO_MEDIUM_10).Width; return Ripple ? new Size(w, 30) : new Size(w, 20); } private static readonly Point[] CheckmarkLine = { new Point(3, 8), new Point(7, 12), new Point(14, 5) }; private const int TEXT_OFFSET = 22; protected override void OnPaint(PaintEventArgs pevent) { var g = pevent.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; g.TextRenderingHint = TextRenderingHint.AntiAlias; // clear the control g.Clear(Parent.BackColor); var CHECKBOX_CENTER = _boxOffset + CHECKBOX_SIZE_HALF - 1; var animationProgress = _animationManager.GetProgress(); var colorAlpha = Enabled ? (int)(animationProgress * 255.0) : SkinManager.GetCheckBoxOffDisabledColor().A; var backgroundAlpha = Enabled ? (int)(SkinManager.GetCheckboxOffColor().A * (1.0 - animationProgress)) : SkinManager.GetCheckBoxOffDisabledColor().A; var brush = new SolidBrush(Color.FromArgb(colorAlpha, Enabled ? SkinManager.ColorScheme.AccentColor : SkinManager.GetCheckBoxOffDisabledColor())); var brush3 = new SolidBrush(Enabled ? SkinManager.ColorScheme.AccentColor : SkinManager.GetCheckBoxOffDisabledColor()); var pen = new Pen(brush.Color); // draw ripple animation if (Ripple && _rippleAnimationManager.IsAnimating()) { for (var i = 0; i < _rippleAnimationManager.GetAnimationCount(); i++) { var animationValue = _rippleAnimationManager.GetProgress(i); var animationSource = new Point(CHECKBOX_CENTER, CHECKBOX_CENTER); var rippleBrush = new SolidBrush(Color.FromArgb((int)((animationValue * 40)), ((bool)_rippleAnimationManager.GetData(i)[0]) ? Color.Black : brush.Color)); var rippleHeight = (Height % 2 == 0) ? Height - 3 : Height - 2; var rippleSize = (_rippleAnimationManager.GetDirection(i) == AnimationDirection.InOutIn) ? (int)(rippleHeight * (0.8d + (0.2d * animationValue))) : rippleHeight; using (var path = DrawHelper.CreateRoundRect(animationSource.X - rippleSize / 2, animationSource.Y - rippleSize / 2, rippleSize, rippleSize, rippleSize / 2)) { g.FillPath(rippleBrush, path); } rippleBrush.Dispose(); } } brush3.Dispose(); var checkMarkLineFill = new Rectangle(_boxOffset, _boxOffset, (int)((int)(FontHeight * 1.1) * animationProgress), (int)(FontHeight * 1.1)); using (var checkmarkPath = DrawHelper.CreateRoundRect(_boxOffset, _boxOffset, (int)(FontHeight*1.1), (int)(FontHeight * 1.1), 1f)) { var brush2 = new SolidBrush(DrawHelper.BlendColor(Parent.BackColor, Enabled ? SkinManager.GetCheckboxOffColor() : SkinManager.GetCheckBoxOffDisabledColor(), backgroundAlpha)); var pen2 = new Pen(brush2.Color); g.FillPath(brush2, checkmarkPath); g.DrawPath(pen2, checkmarkPath); g.FillRectangle(new SolidBrush(Parent.BackColor), _boxOffset + 2, _boxOffset + 2, (int)(FontHeight * 1.1) -4, (int)(FontHeight * 1.1) -4); g.DrawRectangle(new Pen(Parent.BackColor), _boxOffset + 2, _boxOffset + 2, (int)(FontHeight * 1.1) -4, (int)(FontHeight * 1.1) - 4); brush2.Dispose(); pen2.Dispose(); if (Enabled) { g.FillPath(brush, checkmarkPath); g.DrawPath(pen, checkmarkPath); } else if (Checked) { g.SmoothingMode = SmoothingMode.None; g.FillRectangle(brush, _boxOffset + 2, _boxOffset + 2, CHECKBOX_INNER_BOX_SIZE, CHECKBOX_INNER_BOX_SIZE); g.SmoothingMode = SmoothingMode.AntiAlias; } g.DrawImageUnscaledAndClipped(DrawCheckMarkBitmap(), checkMarkLineFill); } // draw checkbox text SizeF stringSize = g.MeasureString(Text, Font); g.DrawString( Text, Font, Enabled ? SkinManager.GetPrimaryTextBrush() : SkinManager.GetDisabledOrHintBrush(), _boxOffset + TEXT_OFFSET, Height / 2 - stringSize.Height / 2); // dispose used paint objects pen.Dispose(); brush.Dispose(); } private Bitmap DrawCheckMarkBitmap() { var checkMark = new Bitmap(CHECKBOX_SIZE, CHECKBOX_SIZE); var g = Graphics.FromImage(checkMark); // clear everything, transparent g.Clear(Color.Transparent); // draw the checkmark lines using (var pen = new Pen(Parent.BackColor, 2)) { g.DrawLines(pen, CheckmarkLine); } return checkMark; } public override bool AutoSize { get { return base.AutoSize; } set { base.AutoSize = value; if (value) { Size = new Size(10, 10); } } } private bool IsMouseInCheckArea() { return _boxRectangle.Contains(MouseLocation); } protected override void OnCreateControl() { base.OnCreateControl(); Font = SkinManager.ROBOTO_MEDIUM_10; if (DesignMode) return; MouseState = MouseState.OUT; MouseEnter += (sender, args) => { MouseState = MouseState.HOVER; }; MouseLeave += (sender, args) => { MouseLocation = new Point(-1, -1); MouseState = MouseState.OUT; }; MouseDown += (sender, args) => { MouseState = MouseState.DOWN; if (Ripple && args.Button == MouseButtons.Left && IsMouseInCheckArea()) { _rippleAnimationManager.SecondaryIncrement = 0; _rippleAnimationManager.StartNewAnimation(AnimationDirection.InOutIn, new object[] { Checked }); } }; MouseUp += (sender, args) => { MouseState = MouseState.HOVER; _rippleAnimationManager.SecondaryIncrement = 0.08; }; MouseMove += (sender, args) => { MouseLocation = args.Location; Cursor = IsMouseInCheckArea() ? Cursors.Hand : Cursors.Default; }; } } }