using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Text; using System.Windows.Forms; namespace JYControl { public partial class PulseButton : Button { #region -- Members -- private readonly Timer pulseTimer; private RectangleF[] pulses; private RectangleF centerRect; private Color[] pulseColors; private int pulseWidth; private bool mouseOver; private bool pressed; private float pulseSpeed; public enum Shape { Round, Rectangle } #endregion #region -- Properties -- /// /// Gets or sets the top button color. /// /// The top button color. [Browsable(true), DefaultValue(typeof(Color), "CornflowerBlue")] [Category("Appearance")] public Color ButtonColorTop { get; set; } /// /// Gets or sets the bottom button color. /// /// The bottom button color. [Browsable(true), DefaultValue(typeof(Color), "DodgerBlue")] [Category("Appearance")] public Color ButtonColorBottom { get; set; } /// /// Gets or sets the color of the pulse. /// /// The color of the pulse. [Browsable(true), DefaultValue(typeof(Color), "Black")] [Category("Appearance")] public Color PulseColor { get; set; } /// /// Gets or sets the type of the shape. /// /// The type of the shape. [Browsable(true), DefaultValue(typeof(Shape), "Round")] [Category("Appearance")] public Shape ShapeType { get; set; } /// /// Gets or sets the corner radius. /// /// The corner radius. [Browsable(true), DefaultValue(10)] [Category("Appearance")] public int CornerRadius { get; set; } /// /// Gets or sets the color of the focus. /// /// The color of the focus. [Browsable(true), DefaultValue(typeof(Color), "Orange")] [Category("Appearance")] public Color FocusColor { get; set; } /// /// Gets or sets the foreground color of the control. /// /// /// /// The foreground of the control. The default is the value of the property. /// /// /// /// [Browsable(true), DefaultValue(typeof(Color), "White")] [Category("Appearance")] public new Color ForeColor { get { return base.ForeColor; } set { base.ForeColor = value; } } /// /// Gets or sets the number of pulses. /// /// The number of pulses. [Browsable(true), DefaultValue(3)] [Category("Appearance")] public int NumberOfPulses { get { return pulses.Length; } set { if (value <= 0) return; pulses = new RectangleF[value]; pulseColors = new Color[value]; ArrangePulses(); } } /// /// Gets or sets the width of the pulse. /// /// The width of the pulse. [Browsable(true), DefaultValue(10)] [Category("Appearance")] public int PulseWidth { get { return pulseWidth; } set { pulseWidth = value; ArrangePulses(); } } /// /// Gets or sets the wave speed. /// /// The speed of the pulses. [Browsable(true), DefaultValue(typeof(float), "0.3f")] [Category("Appearance")] public float PulseSpeed { get { return pulseSpeed; } set { if (value <= 0) return; pulseSpeed = value; } } /// /// Gets or sets the interval. /// /// The interval. [Browsable(false), DefaultValue(50)] public int Interval { get { return pulseTimer.Interval; } set { pulseTimer.Interval = value; } } #endregion #region -- Constructor -- /// /// Initializes a new instance of the class. /// public PulseButton() { // Control styles SetStyle(ControlStyles.SupportsTransparentBackColor, true); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.ResizeRedraw, true); SetStyle(ControlStyles.UserPaint, true); InitializeComponent(); // Layout & initialization SuspendLayout(); pulseWidth = 10; PulseSpeed = .3f; ButtonColorTop = Color.CornflowerBlue; ButtonColorBottom = Color.DodgerBlue; FocusColor = Color.Orange; PulseColor = Color.Black; ShapeType = Shape.Round; CornerRadius = 10; Image = null; base.TextAlign = ContentAlignment.MiddleCenter; Size = new Size(40, 40); // Timer pulseTimer = new Timer { Interval = 50 }; pulseTimer.Tick += PulseTimerTick; pulses = new RectangleF[3]; pulseColors = new Color[3]; ArrangePulses(); pulseTimer.Enabled = true; ResumeLayout(true); } #endregion #region -- EventHandlers -- /// /// Handles the pulse timer tick. /// /// The sender. /// The instance containing the event data. private void PulseTimerTick(object sender, EventArgs e) { pulseTimer.Enabled = false; InflatePulses(); Invalidate(); pulseTimer.Enabled = true; } #endregion #region -- Protected overrides -- #region - Mouse - /// /// Raises the event. /// /// A that contains the event data. protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); if (e.Button != MouseButtons.Left) return; pressed = false; } /// /// Raises the event. /// /// A that contains the event data. protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (e.Button != MouseButtons.Left) return; pressed = true; } /// /// Raises the event. /// /// A that contains the event data. protected override void OnMouseMove(MouseEventArgs mevent) { base.OnMouseMove(mevent); mouseOver = centerRect.Contains(mevent.Location); } /// /// Raises the event. /// /// A that contains the event data. protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); mouseOver = false; pressed = false; } #endregion /// /// Raises the event. /// /// An that contains the event data. protected override void OnEnabledChanged(EventArgs e) { base.OnEnabledChanged(e); pulseTimer.Enabled = Enabled; } /// /// Raises the event. /// /// An that contains the event data. protected override void OnResize(EventArgs e) { base.OnResize(e); if (pulses == null || pulses.Length == 0) return; ArrangePulses(); } /// /// Raises the event. /// /// A that contains the event data. protected override void OnPaint(PaintEventArgs e) { //base.OnPaint(e); base.OnPaintBackground(e); // Set Graphics interpolation and smoothing Graphics g = e.Graphics; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.AntiAlias; // Draw pulses DrawPulses(g); if (centerRect.IsEmpty) return; // Draw center DrawCenter(g); // Draw border DrawBorder(g); // Image if (Image != null) g.DrawImage(Image, centerRect); // Draw highlight if (mouseOver) DrawHighLight(g); // Reflex if (!pressed) DrawReflex(g); // Text DrawText(g); } #endregion #region -- Protected virtual methods -- /// /// Draws the border. /// /// The graphics object protected virtual void DrawBorder(Graphics g) { using (var pen = new Pen(!Focused ? Color.FromArgb(60, Color.Black) : FocusColor, 2)) PaintShape(g, pen, centerRect); } /// /// Draws the center. /// /// The graphics object protected virtual void DrawCenter(Graphics g) { if (Enabled) { using (var lgb = new LinearGradientBrush(centerRect, ButtonColorTop, ButtonColorBottom, LinearGradientMode.Vertical)) { PaintShape(g, lgb, centerRect); } } else { using (var lgb = new SolidBrush(Color.Gray)) PaintShape(g, lgb, centerRect); } } /// /// Draws the pulses. /// /// The graphics object protected virtual void DrawPulses(Graphics g) { if (!Enabled) return; for (var i = 0; i < pulses.Length; i++) { using (var sb = new SolidBrush(pulseColors[i])) { PaintShape(g, sb, pulses[i]); } } } /// /// Draws the text. /// /// The graphics object protected virtual void DrawText(Graphics g) { var format = new StringFormat(StringFormat.GenericDefault) { Trimming = StringTrimming.EllipsisCharacter }; format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces; format.FormatFlags ^= StringFormatFlags.LineLimit; format.HotkeyPrefix = HotkeyPrefix.Show; SizeF size = g.MeasureString(Text, Font, new SizeF(centerRect.Width, centerRect.Height), format); RectangleF textRect = GetAlignPlacement(TextAlign, centerRect, size); using (var sb = new SolidBrush(ForeColor)) g.DrawString(Text, Font, sb, textRect, format); } /// /// Draws the reflex. /// /// The graphics object protected virtual void DrawReflex(Graphics g) { using (var path = new GraphicsPath()) { RectangleF rect = centerRect; rect.Height = rect.Height / 2; if (ShapeType == Shape.Round) { path.AddArc(centerRect, -180, 180); RectangleF reflexRectangle = rect; reflexRectangle.Offset(0, rect.Height); reflexRectangle.Height /= 6; path.AddArc(reflexRectangle, 0, 180); path.CloseFigure(); } else { rect.Height += rect.Height / 6; path.AddRectangle(rect); } RectangleF area = path.GetBounds(); using (var lgb = new LinearGradientBrush(area, Color.FromArgb(30, Color.White), Color.FromArgb(60, Color.White), -90)) { g.FillPath(lgb, path); } } } /// /// Draws the high light. /// /// The graphics object protected virtual void DrawHighLight(Graphics g) { RectangleF highlightRect = centerRect; highlightRect.Inflate(-2, -2); using (var pen = new Pen(Color.FromArgb(60, Color.White), 4)) { if (ShapeType == Shape.Round) g.DrawEllipse(pen, highlightRect); else g.DrawPath(pen, GetRoundRect(g, highlightRect, CornerRadius)); } } /// /// Paints the shape. /// /// The graphics object /// The pen /// The rectangle. protected virtual void PaintShape(Graphics g, Pen p, RectangleF rectangle) { if (ShapeType == Shape.Round) g.DrawEllipse(p, rectangle); else using (var path = GetRoundRect(g, rectangle, CornerRadius)) g.DrawPath(p, path); } /// /// Paints the shape. /// /// The graphics object /// The brush /// The rectangle. protected virtual void PaintShape(Graphics g, Brush b, RectangleF rectangle) { if (ShapeType == Shape.Round) g.FillEllipse(b, rectangle); else using (var path = GetRoundRect(g, rectangle, CornerRadius)) g.FillPath(b, path); } #endregion #region -- Public static methods -- /// /// Gets a path of a rectangle with round corners. /// /// The graphics object /// The rectangle /// The corner radius /// public static GraphicsPath GetRoundRect(Graphics g, RectangleF rect, float radius) { var gp = new GraphicsPath(); var diameter = radius * 2; gp.AddArc(rect.X + rect.Width - diameter, rect.Y, diameter, diameter, 270, 90); gp.AddArc(rect.X + rect.Width - diameter, rect.Y + rect.Height - diameter, diameter, diameter, 0, 90); gp.AddArc(rect.X, rect.Y + rect.Height - diameter, diameter, diameter, 90, 90); gp.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90); gp.CloseFigure(); return gp; } /// /// Gets the placement. /// /// The alignment of the element /// A retangle /// The element to be placed /// public static RectangleF GetAlignPlacement(ContentAlignment align, RectangleF rect, SizeF element) { // Left & Top (default) float lft = rect.Left; float top = rect.Y; // Right if ((align & (ContentAlignment.BottomRight | ContentAlignment.MiddleRight | ContentAlignment.TopRight)) != 0) lft = rect.Right - element.Width; // Center else if ((align & (ContentAlignment.BottomCenter | ContentAlignment.MiddleCenter | ContentAlignment.TopCenter)) != 0) lft = (rect.Width / 2) - (element.Width / 2) + rect.Left; // Bottom if ((align & (ContentAlignment.BottomCenter | ContentAlignment.BottomLeft | ContentAlignment.BottomRight)) != 0) top = rect.Bottom - element.Height; // Middle else if ((align & (ContentAlignment.MiddleCenter | ContentAlignment.MiddleLeft | ContentAlignment.MiddleRight)) != 0) top = (rect.Height / 2) - (element.Height / 2) + rect.Y; return new RectangleF(lft, top, element.Width, element.Height); } #endregion #region -- Private methods -- /// /// Arranges the pulses. /// private void ArrangePulses() { centerRect = new RectangleF(pulseWidth, pulseWidth, Width - 2 * pulseWidth, Height - 2 * pulseWidth); for (var i = 1; i <= pulses.Length; i++) { pulses[i - 1] = new RectangleF( pulseWidth * i / (float)pulses.Length, pulseWidth * i / (float)pulses.Length, Width - 2 * pulseWidth * i / pulses.Length, Height - 2 * pulseWidth * i / pulses.Length ); pulseColors[i - 1] = Color.FromArgb((int)(Math.Min(pulses[i - 1].X * 255 / pulseWidth, 255)), Color.White); } } /// /// Inflates the pulses. /// private void InflatePulses() { for (var i = 0; i < pulses.Length; i++) { pulses[i].Inflate(PulseSpeed, PulseSpeed); if (pulses[i].Width > Width || pulses[i].Height > Height || pulses[i].X < 0 || pulses[i].Y < 0) pulses[i] = new RectangleF(pulseWidth, pulseWidth, Width - 2 * pulseWidth, Height - 2 * pulseWidth); pulseColors[i] = Color.FromArgb((int)(Math.Min(pulses[i].X * 255 / pulseWidth, 255)), PulseColor); } } #endregion } }