using SmartCoalApplication.Core; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace SmartCoalApplication.PluginAssemblys { public sealed class ColorGradientControl : UserControl { private Point lastTrackingMouseXY = new Point(-1, -1); private int tracking = -1; private int highlight = -1; private const int triangleSize = 7; private const int triangleHalfLength = (triangleSize - 1) / 2; private Orientation orientation = Orientation.Vertical; private Color[] customGradient = null; private bool drawNearNub = true; public bool DrawNearNub { get { return this.drawNearNub; } set { this.drawNearNub = value; Invalidate(); } } private bool drawFarNub = true; public bool DrawFarNub { get { return this.drawFarNub; } set { this.drawFarNub = value; Invalidate(); } } private int[] vals; // value from [0,255] that specifies the hsv "value" component // where we should draw little triangles that show the value public int Value { get { return GetValue(0); } set { SetValue(0, value); } } public Color[] CustomGradient { get { if (this.customGradient == null) { return null; } else { return (Color[])this.customGradient.Clone(); } } set { if (value != this.customGradient) { if (value == null) { this.customGradient = null; } else { this.customGradient = (Color[])value.Clone(); } Invalidate(); } } } public Orientation Orientation { get { return this.orientation; } set { if (value != this.orientation) { this.orientation = value; Invalidate(); } } } public int Count { get { return vals.Length; } set { if (value < 0 || value > 16) { throw new ArgumentOutOfRangeException("value", value, "Count must be between 0 and 16"); } vals = new int[value]; if (value > 1) { for (int i = 0; i < value; i++) { vals[i] = i * 255 / (value - 1); } } else if (value == 1) { vals[0] = 128; } OnValueChanged(0); Invalidate(); } } public int GetValue(int index) { if (index < 0 || index >= vals.Length) { throw new ArgumentOutOfRangeException("index", index, "Index must be within the bounds of the array"); } int val = vals[index]; return val; } public void SetValue(int index, int val) { int min = -1; int max = 256; if (index < 0 || index >= vals.Length) { throw new ArgumentOutOfRangeException("index", index, "Index must be within the bounds of the array"); } if (index - 1 >= 0) { min = vals[index - 1]; } if (index + 1 < vals.Length) { max = vals[index + 1]; } if (vals[index] != val) { int newVal = Utility.Clamp(val, min + 1, max - 1); vals[index] = newVal; OnValueChanged(index); Invalidate(); } Update(); } public event IndexEventHandler ValueChanged; private void OnValueChanged(int index) { if (ValueChanged != null) { ValueChanged(this, new IndexEventArgs(index)); } } [Obsolete("Use MinColor property instead", true)] public Color BottomColor { get { return MinColor; } set { MinColor = value; } } [Obsolete("Use MaxColor property instead", true)] public Color TopColor { get { return MaxColor; } set { MaxColor = value; } } private Color maxColor; public Color MaxColor { get { return maxColor; } set { if (maxColor != value) { maxColor = value; Invalidate(); } } } private Color minColor; public Color MinColor { get { return minColor; } set { if (minColor != value) { minColor = value; Invalidate(); } } } public ColorGradientControl() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); this.DoubleBuffered = true; this.ResizeRedraw = true; this.Count = 1; } private void DrawGradient(Graphics g) { g.PixelOffsetMode = PixelOffsetMode.Half; Rectangle gradientRect; float gradientAngle; switch (this.orientation) { case Orientation.Horizontal: gradientAngle = 180.0f; break; case Orientation.Vertical: gradientAngle = 90.0f; break; default: throw new InvalidEnumArgumentException(); } // draw gradient gradientRect = ClientRectangle; switch (this.orientation) { case Orientation.Horizontal: gradientRect.Inflate(-triangleHalfLength, -triangleSize + 3); break; case Orientation.Vertical: gradientRect.Inflate(-triangleSize + 3, -triangleHalfLength); break; default: throw new InvalidEnumArgumentException(); } if (this.customGradient != null && gradientRect.Width > 1 && gradientRect.Height > 1) { Surface gradientSurface = new Surface(gradientRect.Size); using (RenderArgs ra = new RenderArgs(gradientSurface)) { Utility.DrawColorRectangle(ra.Graphics, ra.Bounds, Color.Transparent, false); if (Orientation == Orientation.Horizontal) { for (int x = 0; x < gradientSurface.Width; ++x) { double index = (double)(x * (this.customGradient.Length - 1)) / (double)(gradientSurface.Width - 1); int indexL = (int)Math.Floor(index); double t = 1.0 - (index - indexL); int indexR = (int)Math.Min(this.customGradient.Length - 1, Math.Ceiling(index)); Color colorL = this.customGradient[indexL]; Color colorR = this.customGradient[indexR]; double a1 = colorL.A / 255.0; double r1 = colorL.R / 255.0; double g1 = colorL.G / 255.0; double b1 = colorL.B / 255.0; double a2 = colorR.A / 255.0; double r2 = colorR.R / 255.0; double g2 = colorR.G / 255.0; double b2 = colorR.B / 255.0; double at = (t * a1) + ((1.0 - t) * a2); double rt; double gt; double bt; if (at == 0) { rt = 0; gt = 0; bt = 0; } else { rt = ((t * a1 * r1) + ((1.0 - t) * a2 * r2)) / at; gt = ((t * a1 * g1) + ((1.0 - t) * a2 * g2)) / at; bt = ((t * a1 * b1) + ((1.0 - t) * a2 * b2)) / at; } int ap = Utility.Clamp((int)Math.Round(at * 255.0), 0, 255); int rp = Utility.Clamp((int)Math.Round(rt * 255.0), 0, 255); int gp = Utility.Clamp((int)Math.Round(gt * 255.0), 0, 255); int bp = Utility.Clamp((int)Math.Round(bt * 255.0), 0, 255); for (int y = 0; y < gradientSurface.Height; ++y) { ColorBgra src = gradientSurface[x, y]; // we are assuming that src.A = 255 int rd = ((rp * ap) + (src.R * (255 - ap))) / 255; int gd = ((gp * ap) + (src.G * (255 - ap))) / 255; int bd = ((bp * ap) + (src.B * (255 - ap))) / 255; gradientSurface[x, y] = ColorBgra.FromBgra((byte)bd, (byte)gd, (byte)rd, 255); } } g.DrawImage(ra.Bitmap, gradientRect, ra.Bounds, GraphicsUnit.Pixel); } else if (Orientation == Orientation.Vertical) { } else { throw new InvalidEnumArgumentException(); } } gradientSurface.Dispose(); } else { using (LinearGradientBrush lgb = new LinearGradientBrush(this.ClientRectangle, maxColor, minColor, gradientAngle, false)) { g.FillRectangle(lgb, gradientRect); } } // fill background using (PdnRegion nonGradientRegion = new PdnRegion()) { nonGradientRegion.MakeInfinite(); nonGradientRegion.Exclude(gradientRect); using (SolidBrush sb = new SolidBrush(this.BackColor)) { g.FillRegion(sb, nonGradientRegion.GetRegionReadOnly()); } } // draw value triangles for (int i = 0; i < this.vals.Length; i++) { int pos = ValueToPosition(vals[i]); Brush brush; Pen pen; if (i == highlight) { brush = Brushes.Blue; pen = (Pen)Pens.White.Clone(); } else { brush = Brushes.Black; pen = (Pen)Pens.Gray.Clone(); } g.SmoothingMode = SmoothingMode.AntiAlias; Point a1; Point b1; Point c1; Point a2; Point b2; Point c2; switch (this.orientation) { case Orientation.Horizontal: a1 = new Point(pos - triangleHalfLength, 0); b1 = new Point(pos, triangleSize - 1); c1 = new Point(pos + triangleHalfLength, 0); a2 = new Point(a1.X, Height - 1 - a1.Y); b2 = new Point(b1.X, Height - 1 - b1.Y); c2 = new Point(c1.X, Height - 1 - c1.Y); break; case Orientation.Vertical: a1 = new Point(0, pos - triangleHalfLength); b1 = new Point(triangleSize - 1, pos); c1 = new Point(0, pos + triangleHalfLength); a2 = new Point(Width - 1 - a1.X, a1.Y); b2 = new Point(Width - 1 - b1.X, b1.Y); c2 = new Point(Width - 1 - c1.X, c1.Y); break; default: throw new InvalidEnumArgumentException(); } if (this.drawNearNub) { g.FillPolygon(brush, new Point[] { a1, b1, c1, a1 }); } if (this.drawFarNub) { g.FillPolygon(brush, new Point[] { a2, b2, c2, a2 }); } if (pen != null) { if (this.drawNearNub) { g.DrawPolygon(pen, new Point[] { a1, b1, c1, a1 }); } if (this.drawFarNub) { g.DrawPolygon(pen, new Point[] { a2, b2, c2, a2 }); } pen.Dispose(); } } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); DrawGradient(e.Graphics); } protected override void OnPaintBackground(PaintEventArgs pevent) { DrawGradient(pevent.Graphics); } /// /// Clean up any resources being used. /// protected override void Dispose(bool disposing) { if (disposing) { } base.Dispose(disposing); } private int PositionToValue(int pos) { int max; switch (this.orientation) { case Orientation.Horizontal: max = Width; break; case Orientation.Vertical: max = Height; break; default: throw new InvalidEnumArgumentException(); } int val = (((max - triangleSize) - (pos - triangleHalfLength)) * 255) / (max - triangleSize); if (this.orientation == Orientation.Horizontal) { val = 255 - val; } return val; } private int ValueToPosition(int val) { int max; if (this.orientation == Orientation.Horizontal) { val = 255 - val; } switch (this.orientation) { case Orientation.Horizontal: max = Width; break; case Orientation.Vertical: max = Height; break; default: throw new InvalidEnumArgumentException(); } int pos = triangleHalfLength + ((max - triangleSize) - (((val * (max - triangleSize)) / 255))); return pos; } private int WhichTriangle(int val) { int bestIndex = -1; int bestDistance = int.MaxValue; int v = PositionToValue(val); for (int i = 0; i < this.vals.Length; i++) { int distance = Math.Abs(this.vals[i] - v); if (distance < bestDistance) { bestDistance = distance; bestIndex = i; } } return bestIndex; } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (e.Button == MouseButtons.Left) { int val = GetOrientedValue(e); tracking = WhichTriangle(val); Invalidate(); OnMouseMove(e); } } protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); if (e.Button == MouseButtons.Left) { OnMouseMove(e); tracking = -1; Invalidate(); } } private int GetOrientedValue(MouseEventArgs me) { return GetOrientedValue(new Point(me.X, me.Y)); } private int GetOrientedValue(Point pt) { int pos; switch (this.orientation) { case Orientation.Horizontal: pos = pt.X; break; case Orientation.Vertical: pos = pt.Y; break; default: throw new InvalidEnumArgumentException(); } return pos; } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); int pos = GetOrientedValue(e); Point newMouseXY = new Point(e.X, e.Y); if (tracking >= 0 && newMouseXY != this.lastTrackingMouseXY) { int val = PositionToValue(pos); this.SetValue(tracking, val); this.lastTrackingMouseXY = newMouseXY; } else { int oldHighlight = highlight; highlight = WhichTriangle(pos); if (highlight != oldHighlight) { this.InvalidateTriangle(oldHighlight); this.InvalidateTriangle(highlight); } } } protected override void OnMouseLeave(EventArgs e) { int oldhighlight = highlight; highlight = -1; this.InvalidateTriangle(oldhighlight); } private void InvalidateTriangle(int index) { if (index < 0 || index >= this.vals.Length) { return; } int value = ValueToPosition(this.vals[index]); Rectangle rect; switch (this.orientation) { case Orientation.Horizontal: rect = new Rectangle(value - triangleHalfLength, 0, triangleSize, this.Height); break; case Orientation.Vertical: rect = new Rectangle(0, value - triangleHalfLength, this.Width, triangleSize); break; default: throw new InvalidEnumArgumentException(); } this.Invalidate(rect, true); } #region Component Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { } #endregion } }