using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Windows.Forms;
namespace TradeIdeas.TIProGUI.Charting.Controls.ColorHexagonPicker
{
[DefaultProperty("Color")]
[DefaultEvent("ColorChanged")]
public partial class ColorWheel: Control
{
private const int PADDING = 10;
private const int INNER_RADIUS = 200;
private const int OUTER_RADIUS = INNER_RADIUS + 50;
#region Fields
private Brush _brush;
private PointF _centerPoint;
private Color _color;
private int _colorStep;
private bool _dragStartedWithinWheel;
private HslColor _hslColor;
private int _largeChange;
private float _radius;
private int _selectionSize;
private int _smallChange;
private int _updateCount;
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
public ColorWheel()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.Selectable | ControlStyles.StandardClick | ControlStyles.StandardDoubleClick, true);
this.Color = Color.Black;
this.ColorStep = 4;
this.SelectionSize = 10;
this.SmallChange = 1;
this.LargeChange = 5;
this.SelectionGlyph = this.CreateSelectionGlyph();
}
#endregion
#region Events
///
/// Occurs when the Color property value changes
///
[Category("Property Changed")]
public event EventHandler ColorChanged;
///
/// Occurs when the ColorStep property value changes
///
[Category("Property Changed")]
public event EventHandler ColorStepChanged;
///
/// Occurs when the HslColor property value changes
///
[Category("Property Changed")]
public event EventHandler HslColorChanged;
///
/// Occurs when the LargeChange property value changes
///
[Category("Property Changed")]
public event EventHandler LargeChangeChanged;
///
/// Occurs when the SelectionSize property value changes
///
[Category("Property Changed")]
public event EventHandler SelectionSizeChanged;
///
/// Occurs when the SmallChange property value changes
///
[Category("Property Changed")]
public event EventHandler SmallChangeChanged;
#endregion
#region Overridden Methods
///
/// Releases the unmanaged resources used by the and its child controls and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_brush != null)
{
_brush.Dispose();
}
if (this.SelectionGlyph != null)
{
this.SelectionGlyph.Dispose();
}
}
base.Dispose(disposing);
}
///
/// Determines whether the specified key is a regular input key or a special key that requires preprocessing.
///
/// One of the values.
/// true if the specified key is a regular input key; otherwise, false.
protected override bool IsInputKey(Keys keyData)
{
bool result;
if ((keyData & Keys.Left) == Keys.Left || (keyData & Keys.Up) == Keys.Up || (keyData & Keys.Down) == Keys.Down || (keyData & Keys.Right) == Keys.Right || (keyData & Keys.PageUp) == Keys.PageUp || (keyData & Keys.PageDown) == Keys.PageDown)
{
result = true;
}
else
{
result = base.IsInputKey(keyData);
}
return result;
}
///
/// Raises the event.
///
/// An that contains the event data.
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
this.Invalidate();
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnKeyDown(KeyEventArgs e)
{
HslColor color;
double hue;
int step;
color = this.HslColor;
hue = color.H;
step = e.Shift ? this.LargeChange : this.SmallChange;
switch (e.KeyCode)
{
case Keys.Right:
case Keys.Up:
hue += step;
break;
case Keys.Left:
case Keys.Down:
hue -= step;
break;
case Keys.PageUp:
hue += this.LargeChange;
break;
case Keys.PageDown:
hue -= this.LargeChange;
break;
}
if (hue >= 360)
{
hue = 0;
}
if (hue < 0)
{
hue = 359;
}
if (hue != color.H)
{
color.H = hue;
// As the Color and HslColor properties update each other, need to temporarily disable this and manually set both
// otherwise the wheel "sticks" due to imprecise conversion from RGB to HSL
this.LockUpdates = true;
this.Color = color.ToRgbColor();
this.HslColor = color;
this.LockUpdates = false;
e.Handled = true;
}
base.OnKeyDown(e);
}
///
/// Raises the event.
///
/// An that contains the event data.
protected override void OnLostFocus(EventArgs e)
{
base.OnLostFocus(e);
this.Invalidate();
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (!this.Focused && this.TabStop)
{
this.Focus();
}
if (e.Button == MouseButtons.Left && this.IsPointInWheel(e.Location))
{
_dragStartedWithinWheel = true;
this.SetColor(e.Location);
}
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left && _dragStartedWithinWheel)
{
this.SetColor(e.Location);
}
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
_dragStartedWithinWheel = false;
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnPaddingChanged(EventArgs e)
{
base.OnPaddingChanged(e);
this.RefreshWheel();
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (this.AllowPainting)
{
base.OnPaintBackground(e);
if (this.BackgroundImage == null && this.Parent != null && (this.BackColor == this.Parent.BackColor || this.Parent.BackColor.A != 255))
{
ButtonRenderer.DrawParentBackground(e.Graphics, this.DisplayRectangle, this);
}
if (_brush != null)
{
e.Graphics.FillPie(_brush, this.ClientRectangle, 0, 360);
}
// smooth out the edge of the wheel
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (Pen pen = new Pen(this.BackColor, 2))
{
e.Graphics.DrawEllipse(pen, new RectangleF(_centerPoint.X - _radius, _centerPoint.Y - _radius, _radius * 2, _radius * 2));
}
if (!this.Color.IsEmpty)
{
this.PaintCurrentColor(e);
}
}
}
///
/// Raises the event.
///
/// An that contains the event data.
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
this.RefreshWheel();
}
#endregion
#region Public Properties
///
/// Gets or sets the component color.
///
/// The component color.
[Category("Appearance")]
[DefaultValue(typeof(Color), "Black")]
public virtual Color Color
{
get { return _color; }
set
{
if (this.Color != value)
{
_color = value;
this.OnColorChanged(EventArgs.Empty);
}
}
}
///
/// Gets or sets the increment for rendering the color wheel.
///
/// The color step.
/// Value must be between 1 and 359
[Category("Appearance")]
[DefaultValue(4)]
public virtual int ColorStep
{
get { return _colorStep; }
set
{
if (value < 1 || value > 359)
{
throw new ArgumentOutOfRangeException("value", value, "Value must be between 1 and 359");
}
if (this.ColorStep != value)
{
_colorStep = value;
this.OnColorStepChanged(EventArgs.Empty);
}
}
}
///
/// Gets or sets the component color.
///
/// The component color.
[Category("Appearance")]
[DefaultValue(typeof(HslColor), "0, 0, 0")]
[Browsable(false) /* disable editing until I write a proper type convertor */]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual HslColor HslColor
{
get { return _hslColor; }
set
{
if (this.HslColor != value)
{
_hslColor = value;
this.OnHslColorChanged(EventArgs.Empty);
}
}
}
///
/// Gets or sets a value to be added to or subtracted from the property when the wheel selection is moved a large distance.
///
/// A numeric value. The default value is 5.
[Category("Behavior")]
[DefaultValue(5)]
public virtual int LargeChange
{
get { return _largeChange; }
set
{
if (this.LargeChange != value)
{
_largeChange = value;
this.OnLargeChangeChanged(EventArgs.Empty);
}
}
}
///
/// Gets or sets the size of the selection handle.
///
/// The size of the selection handle.
[Category("Appearance")]
[DefaultValue(10)]
public virtual int SelectionSize
{
get { return _selectionSize; }
set
{
if (this.SelectionSize != value)
{
_selectionSize = value;
this.OnSelectionSizeChanged(EventArgs.Empty);
}
}
}
///
/// Gets or sets a value to be added to or subtracted from the property when the wheel selection is moved a small distance.
///
/// A numeric value. The default value is 1.
[Category("Behavior")]
[DefaultValue(1)]
public virtual int SmallChange
{
get { return _smallChange; }
set
{
if (this.SmallChange != value)
{
_smallChange = value;
this.OnSmallChangeChanged(EventArgs.Empty);
}
}
}
#endregion
#region Protected Properties
///
/// Gets a value indicating whether painting of the control is allowed.
///
///
/// true if painting of the control is allowed; otherwise, false.
///
protected virtual bool AllowPainting
{
get { return _updateCount == 0; }
}
protected Color[] Colors { get; set; }
protected bool LockUpdates { get; set; }
protected PointF[] Points { get; set; }
protected Image SelectionGlyph { get; set; }
#endregion
#region Public Members
///
/// Disables any redrawing of the image box
///
public virtual void BeginUpdate()
{
_updateCount++;
}
///
/// Enables the redrawing of the image box
///
public virtual void EndUpdate()
{
if (_updateCount > 0)
{
_updateCount--;
}
if (this.AllowPainting)
{
this.Invalidate();
}
}
#endregion
#region Protected Members
///
/// Calculates wheel attributes.
///
protected virtual void CalculateWheel()
{
List points;
List colors;
points = new List();
colors = new List();
// Only define the points if the control is above a minimum size, otherwise if it's too small,
// you get an "out of memory" exceptions (of all things) when creating the brush
if (this.ClientSize.Width > 16 && this.ClientSize.Height > 16)
{
int w;
int h;
w = this.ClientSize.Width;
h = this.ClientSize.Height;
_centerPoint = new PointF(w / 2.0F, h / 2.0F);
_radius = this.GetRadius(_centerPoint);
for (double angle = 0; angle < 360; angle += this.ColorStep)
{
double angleR;
PointF location;
angleR = angle * (Math.PI / 180);
location = this.GetColorLocation(angleR, _radius);
points.Add(location);
colors.Add(new HslColor(angle, 1.0, 0.5).ToRgbColor());
}
}
this.Points = points.ToArray();
this.Colors = colors.ToArray();
}
///
/// Creates the gradient brush used to paint the wheel.
///
protected virtual Brush CreateGradientBrush()
{
Brush result;
if (this.Points.Length != 0 && this.Points.Length == this.Colors.Length)
{
result = new PathGradientBrush(this.Points, WrapMode.Clamp)
{
CenterPoint = _centerPoint,
CenterColor = Color.White,
SurroundColors = this.Colors
};
}
else
{
result = null;
}
return result;
}
///
/// Creates the selection glyph.
///
protected virtual Image CreateSelectionGlyph()
{
Image image;
int halfSize;
halfSize = this.SelectionSize / 2;
image = new Bitmap(this.SelectionSize + 1, this.SelectionSize + 1);
using (Graphics g = Graphics.FromImage(image))
{
Point[] diamondOuter;
diamondOuter = new[]
{
new Point(halfSize, 0), new Point(this.SelectionSize, halfSize), new Point(halfSize, this.SelectionSize), new Point(0, halfSize)
};
g.FillPolygon(SystemBrushes.Control, diamondOuter);
g.DrawPolygon(SystemPens.ControlDark, diamondOuter);
using (Pen pen = new Pen(Color.FromArgb(128, SystemColors.ControlDark)))
{
g.DrawLine(pen, halfSize, 1, this.SelectionSize - 1, halfSize);
g.DrawLine(pen, halfSize, 2, this.SelectionSize - 2, halfSize);
g.DrawLine(pen, halfSize, this.SelectionSize - 1, this.SelectionSize - 2, halfSize + 1);
g.DrawLine(pen, halfSize, this.SelectionSize - 2, this.SelectionSize - 3, halfSize + 1);
}
using (Pen pen = new Pen(Color.FromArgb(196, SystemColors.ControlLightLight)))
{
g.DrawLine(pen, halfSize, this.SelectionSize - 1, 1, halfSize);
}
g.DrawLine(SystemPens.ControlLightLight, 1, halfSize, halfSize, 1);
}
return image;
}
///
/// Gets the point within the wheel representing the source color.
///
/// The color.
protected PointF GetColorLocation(Color color)
{
return this.GetColorLocation(new HslColor(color));
}
///
/// Gets the point within the wheel representing the source color.
///
/// The color.
protected virtual PointF GetColorLocation(HslColor color)
{
double angle;
double radius;
angle = color.H * Math.PI / 180;
radius = _radius * color.S;
return this.GetColorLocation(angle, radius);
}
protected PointF GetColorLocation(double angleR, double radius)
{
double x;
double y;
x = this.Padding.Left + _centerPoint.X + Math.Cos(angleR) * radius;
y = this.Padding.Top + _centerPoint.Y - Math.Sin(angleR) * radius;
return new PointF((float)x, (float)y);
}
protected float GetRadius(PointF centerPoint)
{
return Math.Min(centerPoint.X, centerPoint.Y) - (Math.Max(this.Padding.Horizontal, this.Padding.Vertical) + (this.SelectionSize / 2));
}
///
/// Determines whether the specified point is within the bounds of the color wheel.
///
/// The point.
/// true if the specified point is within the bounds of the color wheel; otherwise, false.
protected bool IsPointInWheel(Point point)
{
PointF normalized;
// http://my.safaribooksonline.com/book/programming/csharp/9780672331985/graphics-with-windows-forms-and-gdiplus/ch17lev1sec21
normalized = new PointF(point.X - _centerPoint.X, point.Y - _centerPoint.Y);
return (normalized.X * normalized.X + normalized.Y * normalized.Y) <= (_radius * _radius);
}
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void OnColorChanged(EventArgs e)
{
EventHandler handler;
if (!this.LockUpdates)
{
this.HslColor = new HslColor(this.Color);
}
this.Refresh();
handler = this.ColorChanged;
if (handler != null)
{
handler(this, e);
}
}
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void OnColorStepChanged(EventArgs e)
{
EventHandler handler;
this.RefreshWheel();
handler = this.ColorStepChanged;
if (handler != null)
{
handler(this, e);
}
}
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void OnHslColorChanged(EventArgs e)
{
EventHandler handler;
if (!this.LockUpdates)
{
this.Color = this.HslColor.ToRgbColor();
}
this.Invalidate();
handler = this.HslColorChanged;
if (handler != null)
{
handler(this, e);
}
}
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void OnLargeChangeChanged(EventArgs e)
{
EventHandler handler;
handler = this.LargeChangeChanged;
if (handler != null)
{
handler(this, e);
}
}
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void OnSelectionSizeChanged(EventArgs e)
{
EventHandler handler;
if (this.SelectionGlyph != null)
{
this.SelectionGlyph.Dispose();
}
this.SelectionGlyph = this.CreateSelectionGlyph();
this.RefreshWheel();
handler = this.SelectionSizeChanged;
if (handler != null)
{
handler(this, e);
}
}
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void OnSmallChangeChanged(EventArgs e)
{
EventHandler handler;
handler = this.SmallChangeChanged;
if (handler != null)
{
handler(this, e);
}
}
protected void PaintColor(PaintEventArgs e, HslColor color)
{
this.PaintColor(e, color, false);
}
protected virtual void PaintColor(PaintEventArgs e, HslColor color, bool includeFocus)
{
PointF location;
location = this.GetColorLocation(color);
if (!float.IsNaN(location.X) && !float.IsNaN(location.Y))
{
int x;
int y;
x = (int)location.X - (this.SelectionSize / 2);
y = (int)location.Y - (this.SelectionSize / 2);
if (this.SelectionGlyph == null)
{
e.Graphics.DrawRectangle(Pens.Black, x, y, this.SelectionSize, this.SelectionSize);
}
else
{
e.Graphics.DrawImage(this.SelectionGlyph, x, y);
}
if (this.Focused && includeFocus)
{
ControlPaint.DrawFocusRectangle(e.Graphics, new Rectangle(x - 1, y - 1, this.SelectionSize + 2, this.SelectionSize + 2));
}
}
}
protected virtual void PaintCurrentColor(PaintEventArgs e)
{
this.PaintColor(e, this.HslColor, true);
}
protected virtual void SetColor(Point point)
{
double dx;
double dy;
double angle;
double distance;
double saturation;
dx = Math.Abs(point.X - _centerPoint.X - this.Padding.Left);
dy = Math.Abs(point.Y - _centerPoint.Y - this.Padding.Top);
angle = Math.Atan(dy / dx) / Math.PI * 180;
distance = Math.Pow((Math.Pow(dx, 2) + (Math.Pow(dy, 2))), 0.5);
saturation = distance / _radius;
if (distance < 6)
{
saturation = 0; // snap to center
}
if (point.X < _centerPoint.X)
{
angle = 180 - angle;
}
if (point.Y > _centerPoint.Y)
{
angle = 360 - angle;
}
this.LockUpdates = true;
this.HslColor = new HslColor(angle, saturation, 0.5);
this.Color = this.HslColor.ToRgbColor();
this.LockUpdates = false;
}
#endregion
#region Private Members
///
/// Refreshes the wheel attributes and then repaints the control
///
private void RefreshWheel()
{
if (_brush != null)
{
_brush.Dispose();
}
this.CalculateWheel();
_brush = this.CreateGradientBrush();
this.Invalidate();
}
#endregion
}
}