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 MaterialRadioButton : RadioButton, 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(); } } // animation managers private readonly AnimationManager _animationManager; private readonly AnimationManager _rippleAnimationManager; // size related variables which should be recalculated onsizechanged private Rectangle _radioButtonBounds; private int _boxOffset; // size constants private const int RADIOBUTTON_SIZE = 19; private const int RADIOBUTTON_SIZE_HALF = RADIOBUTTON_SIZE / 2; private const int RADIOBUTTON_OUTER_CIRCLE_WIDTH = 2; private const int RADIOBUTTON_INNER_CIRCLE_SIZE = RADIOBUTTON_SIZE - (2 * RADIOBUTTON_OUTER_CIRCLE_WIDTH); public MaterialRadioButton() { SetStyle(ControlStyles.DoubleBuffer | ControlStyles.OptimizedDoubleBuffer, true); _animationManager = new AnimationManager { AnimationType = AnimationType.EaseInOut, Increment = 0.06 }; _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); SizeChanged += OnSizeChanged; Ripple = true; MouseLocation = new Point(-1, -1); } private void OnSizeChanged(object sender, EventArgs eventArgs) { _boxOffset = Height / 2 - (int)Math.Ceiling(RADIOBUTTON_SIZE / 2d); _radioButtonBounds = new Rectangle(_boxOffset, _boxOffset, RADIOBUTTON_SIZE, RADIOBUTTON_SIZE); } public override Size GetPreferredSize(Size proposedSize) { var width = _boxOffset + 20 + (int)CreateGraphics().MeasureString(Text, SkinManager.ROBOTO_MEDIUM_10).Width; return Ripple ? new Size(width, 30) : new Size(width, 20); } 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 RADIOBUTTON_CENTER = _boxOffset + RADIOBUTTON_SIZE_HALF; var animationProgress = _animationManager.GetProgress(); int colorAlpha = Enabled ? (int)(animationProgress * 255.0) : SkinManager.GetCheckBoxOffDisabledColor().A; int backgroundAlpha = Enabled ? (int)(SkinManager.GetCheckboxOffColor().A * (1.0 - animationProgress)) : SkinManager.GetCheckBoxOffDisabledColor().A; float animationSize = (float)(animationProgress * 8f); float animationSizeHalf = animationSize / 2; animationSize = (float)(animationProgress * 9f); var brush = new SolidBrush(Color.FromArgb(colorAlpha, 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(RADIOBUTTON_CENTER, RADIOBUTTON_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(); } } // draw radiobutton circle Color uncheckedColor = DrawHelper.BlendColor(Parent.BackColor, Enabled ? SkinManager.GetCheckboxOffColor() : SkinManager.GetCheckBoxOffDisabledColor(), backgroundAlpha); using (var path = DrawHelper.CreateRoundRect(_boxOffset, _boxOffset, RADIOBUTTON_SIZE, RADIOBUTTON_SIZE, 9f)) { g.FillPath(new SolidBrush(uncheckedColor), path); if (Enabled) { g.FillPath(brush, path); } } g.FillEllipse( new SolidBrush(Parent.BackColor), RADIOBUTTON_OUTER_CIRCLE_WIDTH + _boxOffset, RADIOBUTTON_OUTER_CIRCLE_WIDTH + _boxOffset, RADIOBUTTON_INNER_CIRCLE_SIZE, RADIOBUTTON_INNER_CIRCLE_SIZE); if (Checked) { using (var path = DrawHelper.CreateRoundRect(RADIOBUTTON_CENTER - animationSizeHalf, RADIOBUTTON_CENTER - animationSizeHalf, animationSize, animationSize, 4f)) { g.FillPath(brush, path); } } SizeF stringSize = g.MeasureString(Text, SkinManager.ROBOTO_MEDIUM_10); g.DrawString(Text, SkinManager.ROBOTO_MEDIUM_10, Enabled ? SkinManager.GetPrimaryTextBrush() : SkinManager.GetDisabledOrHintBrush(), _boxOffset + 22, Height / 2 - stringSize.Height / 2); brush.Dispose(); pen.Dispose(); } private bool IsMouseInCheckArea() { return _radioButtonBounds.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; }; } } }