959 lines
No EOL
52 KiB
MQL5
959 lines
No EOL
52 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Tools Palette Part 2.mq5 |
|
|
//| Copyright 2026, Allan Munene Mutiiria. |
|
|
//| https://t.me/Forex_Algo_Trader |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2026, Allan Munene Mutiiria."
|
|
#property link "https://t.me/Forex_Algo_Trader"
|
|
#property version "1.00"
|
|
#property strict
|
|
|
|
//--- Include canvas drawing library
|
|
#include <Canvas/Canvas.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Icon definitions — category sidebar buttons only |
|
|
//+------------------------------------------------------------------+
|
|
struct SIconDefinition { string fontName; uchar charCode; }; // Store icon font and character code
|
|
|
|
//--- Define icon for each tool category using font/char pairs
|
|
SIconDefinition ICON_CATEGORY_CURSORS = { "Wingdings", (uchar)'v' }; // Cursors category icon
|
|
SIconDefinition ICON_CATEGORY_LINES = { "Wingdings 3", (uchar)'&' }; // Lines category icon
|
|
SIconDefinition ICON_CATEGORY_CHANNELS = { "Wingdings 3", (uchar)'2' }; // Channels category icon
|
|
SIconDefinition ICON_CATEGORY_PITCHFORK = { "Wingdings 3", (uchar)'H' }; // Pitchfork category icon
|
|
SIconDefinition ICON_CATEGORY_GANN = { "Wingdings", (uchar)'T' }; // Gann category icon
|
|
SIconDefinition ICON_CATEGORY_FIBONACCI = { "Wingdings", (uchar)'z' }; // Fibonacci category icon
|
|
SIconDefinition ICON_CATEGORY_SHAPES = { "Wingdings", (uchar)'o' }; // Shapes category icon
|
|
SIconDefinition ICON_CATEGORY_ANNOTATIONS = { "Webdings", (uchar)'>' }; // Annotations category icon
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Enumerations |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_SNAP_STATE
|
|
{
|
|
SNAP_LEFT, // Snap sidebar to left edge
|
|
SNAP_RIGHT, // Snap sidebar to right edge
|
|
SNAP_FLOAT // Allow sidebar to float freely
|
|
};
|
|
|
|
enum ENUM_CATEGORY
|
|
{
|
|
CAT_CURSORS = 0, // Cursors category index
|
|
CAT_LINES, // Lines category index
|
|
CAT_CHANNELS, // Channels category index
|
|
CAT_PITCHFORK, // Pitchfork category index
|
|
CAT_GANN, // Gann category index
|
|
CAT_FIBONACCI, // Fibonacci category index
|
|
CAT_SHAPES, // Shapes category index
|
|
CAT_ANNOTATIONS, // Annotations category index
|
|
CAT_COUNT // Total number of categories
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Inputs |
|
|
//+------------------------------------------------------------------+
|
|
input int CanvasY = 50; // Canvas Y Position
|
|
input double BackgroundOpacity = 0.92; // Background Opacity (0.0 - 1.0)
|
|
input bool StartDark = true; // Start In Dark Theme
|
|
input int BorderWidth = 1; // Border Width (px)
|
|
input int CategoryIconSize = 26; // Category Icon Size (pt)
|
|
input int SnapThreshold = 40; // Edge Snap Threshold (px)
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Category definition structure |
|
|
//+------------------------------------------------------------------+
|
|
struct CategoryDefinition
|
|
{
|
|
string categoryLabel; // Display label for the category
|
|
string iconFontName; // Font name used to render the icon
|
|
uchar iconCharCode; // Character code of the icon glyph
|
|
bool hasMultipleTools; // Flag indicating category has sub-tools
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Theme color set structure |
|
|
//+------------------------------------------------------------------+
|
|
struct ThemeColorSet
|
|
{
|
|
color sidebarBackground; // Background fill color of the sidebar panel
|
|
color sidebarBorder; // Outline border color of the sidebar panel
|
|
color buttonIconColor; // Color used to render category icons
|
|
color gripDotsColor; // Color of the drag-grip dot indicators
|
|
color separatorColor; // Color of the horizontal separator lines
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS 1 — Blend and draw low-level canvas primitives |
|
|
//+------------------------------------------------------------------+
|
|
class CCanvasPrimitives
|
|
{
|
|
protected:
|
|
//--- Blend a single pixel onto the canvas using alpha compositing
|
|
void BlendPixelSet(CCanvas &canvas, int x, int y, uint sourceARGB);
|
|
//--- Downsample a high-res canvas into a lower-res destination
|
|
void DownsampleCanvas(CCanvas &dst, CCanvas &src, int factor);
|
|
//--- Fill a single corner quadrant of a rounded rectangle at high res
|
|
void FillCornerQuadrantHR(CCanvas &canvas, int cx, int cy, int radius, uint argb, int signX, int signY);
|
|
//--- Fill a full rounded rectangle at high resolution
|
|
void FillRoundRectHR(CCanvas &canvas, int x, int y, int w, int h, int radius, uint argb);
|
|
//--- Fill a rounded rectangle with per-corner rounding control at high res
|
|
void FillSelectiveRoundRectHR(CCanvas &canvas, int x, int y, int w, int h, int radius, uint argb,
|
|
bool rTL, bool rTR, bool rBL, bool rBR);
|
|
//--- Fill a quadrilateral shape using scanline rasterization
|
|
void FillQuadrilateralBorder(CCanvas &canvas, double &vx[], double &vy[], uint argb);
|
|
//--- Draw a single thick border edge between two points
|
|
void DrawBorderEdge(CCanvas &canvas, double x0, double y0, double x1, double y1, int thickness, uint argb);
|
|
//--- Check whether an angle falls within a given arc range
|
|
bool IsAngleBetween(double angle, double startAngle, double endAngle);
|
|
//--- Draw a corner arc segment with specified thickness and angle range
|
|
void DrawCornerArc(CCanvas &canvas, int cx, int cy, int radius, int thickness, uint argb, double startAngle, double endAngle);
|
|
//--- Draw a rounded rectangle border with per-corner rounding control at high res
|
|
void DrawSelectiveRoundRectBorderHR(CCanvas &canvas, int x, int y, int w, int h, int radius, uint argb, int thickness,
|
|
bool rTL, bool rTR, bool rBL, bool rBR);
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Blend source pixel onto canvas using alpha compositing |
|
|
//+------------------------------------------------------------------+
|
|
void CCanvasPrimitives::BlendPixelSet(CCanvas &canvas, int x, int y, uint src)
|
|
{
|
|
//--- Skip pixels outside canvas bounds
|
|
if (x < 0 || x >= canvas.Width() || y < 0 || y >= canvas.Height()) return;
|
|
//--- Read existing destination pixel
|
|
uint dst = canvas.PixelGet(x, y);
|
|
//--- Unpack source ARGB channels to normalised floats
|
|
double sA = ((src >> 24) & 0xFF) / 255.0, sR = ((src >> 16) & 0xFF) / 255.0;
|
|
double sG = ((src >> 8) & 0xFF) / 255.0, sB = ( src & 0xFF) / 255.0;
|
|
//--- Unpack destination ARGB channels to normalised floats
|
|
double dA = ((dst >> 24) & 0xFF) / 255.0, dR = ((dst >> 16) & 0xFF) / 255.0;
|
|
double dG = ((dst >> 8) & 0xFF) / 255.0, dB = ( dst & 0xFF) / 255.0;
|
|
//--- Compute output alpha using standard over-compositing formula
|
|
double oA = sA + dA * (1.0 - sA);
|
|
//--- Write fully transparent pixel and exit if output alpha is zero
|
|
if (oA == 0.0) { canvas.PixelSet(x, y, 0); return; }
|
|
//--- Write blended ARGB pixel to canvas
|
|
canvas.PixelSet(x, y,
|
|
((uint)(uchar)(oA * 255 + 0.5) << 24) |
|
|
((uint)(uchar)((sR * sA + dR * dA * (1.0 - sA)) / oA * 255 + 0.5) << 16) |
|
|
((uint)(uchar)((sG * sA + dG * dA * (1.0 - sA)) / oA * 255 + 0.5) << 8) |
|
|
(uint)(uchar)((sB * sA + dB * dA * (1.0 - sA)) / oA * 255 + 0.5));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Downsample high-res canvas into destination by averaging pixels |
|
|
//+------------------------------------------------------------------+
|
|
void CCanvasPrimitives::DownsampleCanvas(CCanvas &dst, CCanvas &src, int factor)
|
|
{
|
|
//--- Cache destination dimensions and squared sample count
|
|
int dW = dst.Width(), dH = dst.Height(), ss2 = factor * factor;
|
|
//--- Iterate over every destination pixel
|
|
for (int py = 0; py < dH; py++)
|
|
for (int px = 0; px < dW; px++)
|
|
{
|
|
//--- Accumulate channel sums across the source sample block
|
|
double sA = 0, sR = 0, sG = 0, sB = 0, wc = 0;
|
|
for (int dy = 0; dy < factor; dy++)
|
|
for (int dx = 0; dx < factor; dx++)
|
|
{
|
|
//--- Compute source sample coordinates
|
|
int sx = px * factor + dx, sy = py * factor + dy;
|
|
//--- Skip samples outside source bounds
|
|
if (sx >= src.Width() || sy >= src.Height()) continue;
|
|
//--- Read source pixel and extract alpha
|
|
uint p = src.PixelGet(sx, sy); uchar a = (uchar)((p >> 24) & 0xFF);
|
|
//--- Accumulate alpha unconditionally
|
|
sA += a;
|
|
//--- Accumulate colour channels only for non-transparent samples
|
|
if (a > 0) { sR += (p >> 16) & 0xFF; sG += (p >> 8) & 0xFF; sB += p & 0xFF; wc += 1.0; }
|
|
}
|
|
//--- Compute averaged output alpha
|
|
uchar fa = (uchar)(sA / ss2);
|
|
//--- Write transparent pixel and skip if result is fully transparent
|
|
if (fa == 0 || wc == 0) { dst.PixelSet(px, py, 0); continue; }
|
|
//--- Write averaged ARGB pixel to destination canvas
|
|
dst.PixelSet(px, py, ((uint)fa << 24) | ((uint)(uchar)(sR / wc) << 16) |
|
|
((uint)(uchar)(sG / wc) << 8) | (uint)(uchar)(sB / wc));
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Fill one corner quadrant of a rounded rect at high resolution |
|
|
//+------------------------------------------------------------------+
|
|
void CCanvasPrimitives::FillCornerQuadrantHR(CCanvas &canvas, int cx, int cy, int radius, uint argb, int signX, int signY)
|
|
{
|
|
//--- Cache radius as double and extract alpha and RGB components
|
|
double rd = (double)radius;
|
|
uchar bA = (uchar)((argb >> 24) & 0xFF);
|
|
uint rgb = argb & 0x00FFFFFF;
|
|
//--- Set sub-pixel sample count and derived values
|
|
int sub = 4; double step = 1.0 / sub; int subSq = sub * sub;
|
|
//--- Iterate over pixel neighbourhood around corner centre
|
|
for (int dy = -(radius + 1); dy <= (radius + 1); dy++)
|
|
for (int dx = -(radius + 1); dx <= (radius + 1); dx++)
|
|
{
|
|
//--- Check pixel belongs to the target quadrant
|
|
bool inQ = ((signX > 0) ? (dx >= 0) : (dx <= 0)) && ((signY > 0) ? (dy >= 0) : (dy <= 0));
|
|
if (!inQ) continue;
|
|
//--- Compute distance from corner centre
|
|
double dist = MathSqrt((double)(dx * dx + dy * dy));
|
|
//--- Skip pixels too far outside radius
|
|
if (dist > rd + 1.0) continue;
|
|
//--- Fill pixels fully inside radius without anti-aliasing
|
|
if (dist <= rd - 1.0) { canvas.PixelSet(cx + dx, cy + dy, argb); continue; }
|
|
//--- Count sub-pixel samples falling inside the circle
|
|
int inside = 0;
|
|
for (int sy = 0; sy < sub; sy++)
|
|
for (int sx = 0; sx < sub; sx++)
|
|
{
|
|
//--- Compute sub-pixel offsets from pixel centre
|
|
double sdx = (double)dx - 0.5 + (sx + 0.5) * step;
|
|
double sdy = (double)dy - 0.5 + (sy + 0.5) * step;
|
|
if (sdx * sdx + sdy * sdy <= rd * rd) inside++;
|
|
}
|
|
//--- Skip pixel if no sub-samples are inside
|
|
if (inside == 0) continue;
|
|
//--- Blend anti-aliased pixel using coverage fraction
|
|
BlendPixelSet(canvas, cx + dx, cy + dy, (((uint)(uchar)((int)bA * inside / subSq)) << 24) | rgb);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Fill a fully rounded rectangle at high resolution |
|
|
//+------------------------------------------------------------------+
|
|
void CCanvasPrimitives::FillRoundRectHR(CCanvas &canvas, int x, int y, int w, int h, int radius, uint argb)
|
|
{
|
|
//--- Clamp radius to half the smallest dimension
|
|
radius = MathMin(radius, MathMin(w / 2, h / 2));
|
|
//--- Fall back to plain rectangle when radius is zero or negative
|
|
if (radius <= 0) { canvas.FillRectangle(x, y, x + w - 1, y + h - 1, argb); return; }
|
|
//--- Fill horizontal centre strip
|
|
canvas.FillRectangle(x + radius, y, x + w - radius - 1, y + h - 1, argb);
|
|
//--- Fill left vertical strip
|
|
canvas.FillRectangle(x, y + radius, x + radius - 1, y + h - radius - 1, argb);
|
|
//--- Fill right vertical strip
|
|
canvas.FillRectangle(x + w - radius, y + radius, x + w - 1, y + h - radius - 1, argb);
|
|
//--- Fill top-left corner quadrant
|
|
FillCornerQuadrantHR(canvas, x + radius, y + radius, radius, argb, -1, -1);
|
|
//--- Fill top-right corner quadrant
|
|
FillCornerQuadrantHR(canvas, x + w - radius, y + radius, radius, argb, 1, -1);
|
|
//--- Fill bottom-left corner quadrant
|
|
FillCornerQuadrantHR(canvas, x + radius, y + h - radius, radius, argb, -1, 1);
|
|
//--- Fill bottom-right corner quadrant
|
|
FillCornerQuadrantHR(canvas, x + w - radius, y + h - radius, radius, argb, 1, 1);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Fill rounded rectangle with per-corner rounding at high res |
|
|
//+------------------------------------------------------------------+
|
|
void CCanvasPrimitives::FillSelectiveRoundRectHR(CCanvas &canvas, int x, int y, int w, int h, int radius, uint argb,
|
|
bool rTL, bool rTR, bool rBL, bool rBR)
|
|
{
|
|
//--- Clamp radius to half the smallest dimension
|
|
radius = MathMin(radius, MathMin(w / 2, h / 2));
|
|
//--- Fall back to plain rectangle when radius is zero or negative
|
|
if (radius <= 0) { canvas.FillRectangle(x, y, x + w - 1, y + h - 1, argb); return; }
|
|
//--- Fill horizontal centre strip spanning full width
|
|
canvas.FillRectangle(x + radius, y, x + w - radius - 1, y + h - 1, argb);
|
|
//--- Fill left strip, respecting top-left and bottom-left rounding
|
|
canvas.FillRectangle(x, y + (rTL ? radius : 0), x + radius - 1, y + h - 1 - (rBL ? radius : 0), argb);
|
|
//--- Fill right strip, respecting top-right and bottom-right rounding
|
|
canvas.FillRectangle(x + w - radius, y + (rTR ? radius : 0), x + w - 1, y + h - 1 - (rBR ? radius : 0), argb);
|
|
//--- Fill or square top-left corner based on rounding flag
|
|
if (rTL) FillCornerQuadrantHR(canvas, x + radius, y + radius, radius, argb, -1, -1);
|
|
else canvas.FillRectangle(x, y, x + radius - 1, y + radius - 1, argb);
|
|
//--- Fill or square top-right corner based on rounding flag
|
|
if (rTR) FillCornerQuadrantHR(canvas, x + w - radius, y + radius, radius, argb, 1, -1);
|
|
else canvas.FillRectangle(x + w - radius, y, x + w - 1, y + radius - 1, argb);
|
|
//--- Fill or square bottom-left corner based on rounding flag
|
|
if (rBL) FillCornerQuadrantHR(canvas, x + radius, y + h - radius, radius, argb, -1, 1);
|
|
else canvas.FillRectangle(x, y + h - radius, x + radius - 1, y + h - 1, argb);
|
|
//--- Fill or square bottom-right corner based on rounding flag
|
|
if (rBR) FillCornerQuadrantHR(canvas, x + w - radius, y + h - radius, radius, argb, 1, 1);
|
|
else canvas.FillRectangle(x + w - radius, y + h - radius, x + w - 1, y + h - 1, argb);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Fill a quadrilateral using scanline rasterization |
|
|
//+------------------------------------------------------------------+
|
|
void CCanvasPrimitives::FillQuadrilateralBorder(CCanvas &canvas, double &vx[], double &vy[], uint argb)
|
|
{
|
|
//--- Find vertical bounding extent of the quad
|
|
double minY = vy[0], maxY = vy[0];
|
|
for (int i = 1; i < 4; i++) { if (vy[i] < minY) minY = vy[i]; if (vy[i] > maxY) maxY = vy[i]; }
|
|
//--- Iterate over each horizontal scanline within the bounding box
|
|
for (int scanY = (int)MathCeil(minY); scanY <= (int)MathCeil(maxY) - 1; scanY++)
|
|
{
|
|
//--- Compute scanline centre Y and prepare intersection buffer
|
|
double cy = (double)scanY + 0.5; double xi[8]; int nc = 0;
|
|
//--- Compute X intersections with each edge of the quad
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
int ni = (i + 1) % 4;
|
|
//--- Determine edge vertical extents
|
|
double eMin = (vy[i] < vy[ni]) ? vy[i] : vy[ni], eMax = (vy[i] > vy[ni]) ? vy[i] : vy[ni];
|
|
//--- Skip edges that do not cross the current scanline
|
|
if (cy < eMin || cy > eMax || MathAbs(vy[ni] - vy[i]) < 1e-12) continue;
|
|
//--- Compute intersection parameter along the edge
|
|
double t = (cy - vy[i]) / (vy[ni] - vy[i]);
|
|
if (t < 0.0 || t > 1.0) continue;
|
|
//--- Record intersection X coordinate
|
|
xi[nc++] = vx[i] + t * (vx[ni] - vx[i]);
|
|
}
|
|
//--- Sort intersections left to right
|
|
for (int a = 0; a < nc - 1; a++)
|
|
for (int b = a + 1; b < nc; b++)
|
|
if (xi[a] > xi[b]) { double tmp = xi[a]; xi[a] = xi[b]; xi[b] = tmp; }
|
|
//--- Fill pixels between paired intersection spans
|
|
for (int p = 0; p + 1 < nc; p += 2)
|
|
for (int fx = (int)MathCeil(xi[p]); fx <= (int)MathCeil(xi[p + 1]) - 1; fx++)
|
|
canvas.PixelSet(fx, scanY, argb);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw a thick border edge between two endpoints |
|
|
//+------------------------------------------------------------------+
|
|
void CCanvasPrimitives::DrawBorderEdge(CCanvas &canvas, double x0, double y0, double x1, double y1, int thickness, uint argb)
|
|
{
|
|
//--- Compute edge direction vector and length
|
|
double dx = x1 - x0, dy = y1 - y0, len = MathSqrt(dx * dx + dy * dy);
|
|
//--- Skip degenerate edges with near-zero length
|
|
if (len < 1e-6) return;
|
|
//--- Compute perpendicular and unit direction vectors
|
|
double px = -dy / len, py = dx / len, ex = dx / len, ey = dy / len;
|
|
//--- Compute half-thickness and end cap extension
|
|
double ht = thickness / 2.0, ext = 0.23 * thickness;
|
|
//--- Extend start and end points slightly for mitre cap effect
|
|
double sx = x0 - ex * ext, sy = y0 - ey * ext, ex2 = x1 + ex * ext, ey2 = y1 + ey * ext;
|
|
//--- Build quad vertices offset perpendicular to edge direction
|
|
double tvx[4] = { sx - px*ht, sx + px*ht, ex2 + px*ht, ex2 - px*ht };
|
|
double tvy[4] = { sy - py*ht, sy + py*ht, ey2 + py*ht, ey2 - py*ht };
|
|
//--- Fill the resulting quad as the border edge shape
|
|
FillQuadrilateralBorder(canvas, tvx, tvy, argb);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check whether an angle falls within a start-to-end arc range |
|
|
//+------------------------------------------------------------------+
|
|
bool CCanvasPrimitives::IsAngleBetween(double angle, double start, double end)
|
|
{
|
|
//--- Normalise all angles to the [0, 2π) range
|
|
double tp = 2.0 * M_PI;
|
|
angle = MathMod(angle + tp, tp); start = MathMod(start + tp, tp); end = MathMod(end + tp, tp);
|
|
//--- Return true if angle lies within the arc from start to end
|
|
return MathMod(angle - start + tp, tp) <= MathMod(end - start + tp, tp);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw an anti-aliased corner arc with specified thickness |
|
|
//+------------------------------------------------------------------+
|
|
void CCanvasPrimitives::DrawCornerArc(CCanvas &canvas, int cx, int cy, int radius, int thickness, uint argb, double startAngle, double endAngle)
|
|
{
|
|
//--- Compute outer and inner radii for the arc ring
|
|
double oR = (double)radius, iR = MathMax(0.0, (double)radius - thickness);
|
|
//--- Extract alpha and RGB components from packed colour
|
|
uchar bA = (uchar)((argb >> 24) & 0xFF); uint rgb = argb & 0x00FFFFFF;
|
|
//--- Set sub-pixel sample count and pixel scan radius
|
|
int sub = 4; double step = 1.0 / sub; int subSq = sub * sub, pr = (int)(oR + 2.0);
|
|
//--- Iterate over pixels in the bounding box of the arc
|
|
for (int dy = -pr; dy <= pr; dy++)
|
|
for (int dx = -pr; dx <= pr; dx++)
|
|
{
|
|
//--- Compute distance from arc centre
|
|
double dist = MathSqrt((double)(dx * dx + dy * dy));
|
|
//--- Skip pixels clearly outside the ring or wrong angle
|
|
if (dist > oR + 1.0 || dist < iR - 1.0) continue;
|
|
if (!IsAngleBetween(MathArctan2((double)dy, (double)dx), startAngle, endAngle)) continue;
|
|
//--- Fill pixels fully inside the ring without anti-aliasing
|
|
if (dist <= oR - 1.0 && dist >= iR + 1.0) { canvas.PixelSet(cx + dx, cy + dy, argb); continue; }
|
|
//--- Count sub-pixel samples inside the arc ring and angle range
|
|
int inside = 0;
|
|
for (int sy = 0; sy < sub; sy++)
|
|
for (int sx = 0; sx < sub; sx++)
|
|
{
|
|
double sdx = (double)dx - 0.5 + (sx + 0.5) * step, sdy = (double)dy - 0.5 + (sy + 0.5) * step;
|
|
double sd = MathSqrt(sdx * sdx + sdy * sdy);
|
|
if (sd >= iR && sd <= oR && IsAngleBetween(MathArctan2(sdy, sdx), startAngle, endAngle)) inside++;
|
|
}
|
|
//--- Skip pixel if no sub-samples qualify
|
|
if (inside == 0) continue;
|
|
//--- Write fully opaque pixel if all sub-samples qualify
|
|
if (inside >= subSq) canvas.PixelSet(cx + dx, cy + dy, argb);
|
|
//--- Blend anti-aliased pixel using coverage fraction
|
|
else BlendPixelSet(canvas, cx + dx, cy + dy, (((uint)(uchar)((int)bA * inside / subSq)) << 24) | rgb);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw rounded rect border with per-corner rounding at high res |
|
|
//+------------------------------------------------------------------+
|
|
void CCanvasPrimitives::DrawSelectiveRoundRectBorderHR(CCanvas &canvas, int x, int y, int w, int h, int radius, uint argb, int thickness,
|
|
bool rTL, bool rTR, bool rBL, bool rBR)
|
|
{
|
|
//--- Skip drawing when border width is disabled
|
|
if (BorderWidth <= 0) return;
|
|
//--- Clamp radius to half the smallest dimension
|
|
radius = MathMin(radius, MathMin(w / 2, h / 2));
|
|
//--- Compute per-corner radii based on rounding flags and half-thickness offset
|
|
int tlR = rTL ? radius : 0, trR = rTR ? radius : 0, blR = rBL ? radius : 0, brR = rBR ? radius : 0, h2 = thickness / 2;
|
|
//--- Draw top edge
|
|
DrawBorderEdge(canvas, x + tlR, y + h2, x + w - trR, y + h2, thickness, argb);
|
|
//--- Draw right edge if any right corner is rounded
|
|
if (rTR || rBR) DrawBorderEdge(canvas, x + w - h2, y + trR, x + w - h2, y + h - brR, thickness, argb);
|
|
//--- Draw bottom edge
|
|
DrawBorderEdge(canvas, x + w - brR, y + h - h2, x + blR, y + h - h2, thickness, argb);
|
|
//--- Draw left edge if any left corner is rounded
|
|
if (rTL || rBL) DrawBorderEdge(canvas, x + h2, y + h - blR, x + h2, y + tlR, thickness, argb);
|
|
//--- Draw top-left corner arc if rounded
|
|
if (rTL) DrawCornerArc(canvas, x + radius, y + radius, radius, thickness, argb, M_PI, M_PI * 1.5);
|
|
//--- Draw top-right corner arc if rounded
|
|
if (rTR) DrawCornerArc(canvas, x + w - radius, y + radius, radius, thickness, argb, M_PI * 1.5, M_PI * 2.0);
|
|
//--- Draw bottom-left corner arc if rounded
|
|
if (rBL) DrawCornerArc(canvas, x + radius, y + h - radius, radius, thickness, argb, M_PI * 0.5, M_PI);
|
|
//--- Draw bottom-right corner arc if rounded
|
|
if (rBR) DrawCornerArc(canvas, x + w - radius, y + h - radius, radius, thickness, argb, 0.0, M_PI * 0.5);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS 2 — Manage and apply light and dark theme colour sets |
|
|
//+------------------------------------------------------------------+
|
|
class CThemeManager : public CCanvasPrimitives
|
|
{
|
|
protected:
|
|
bool m_isDarkTheme; // Active theme flag: true = dark, false = light
|
|
ThemeColorSet m_themeColors; // Active colour set for the current theme
|
|
protected:
|
|
//--- Apply colour values matching the current theme state
|
|
void ApplyTheme();
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Apply colour values matching the current theme state |
|
|
//+------------------------------------------------------------------+
|
|
void CThemeManager::ApplyTheme()
|
|
{
|
|
//--- Apply dark theme colour assignments
|
|
if (m_isDarkTheme)
|
|
{
|
|
m_themeColors.sidebarBackground = C'30,34,45'; // Dark navy background
|
|
m_themeColors.sidebarBorder = C'200,210,225'; // Light blue-grey border
|
|
m_themeColors.buttonIconColor = C'220,225,235'; // Near-white icon colour
|
|
m_themeColors.gripDotsColor = C'90,100,120'; // Muted slate grip dots
|
|
m_themeColors.separatorColor = C'44,50,64'; // Dark separator line
|
|
}
|
|
else
|
|
{
|
|
//--- Apply light theme colour assignments
|
|
m_themeColors.sidebarBackground = clrWhite; // White background
|
|
m_themeColors.sidebarBorder = C'30,35,45'; // Dark border
|
|
m_themeColors.buttonIconColor = C'40,45,58'; // Dark icon colour
|
|
m_themeColors.gripDotsColor = C'160,170,185'; // Light grey grip dots
|
|
m_themeColors.separatorColor = C'210,215,225'; // Light separator line
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS 3 — Register and initialise all tool category definitions |
|
|
//+------------------------------------------------------------------+
|
|
class CCategoryRegistry : public CThemeManager
|
|
{
|
|
protected:
|
|
CategoryDefinition m_categories[CAT_COUNT]; // Array of all category definitions
|
|
protected:
|
|
//--- Populate all category definitions with labels, icons, and tool flags
|
|
void InitAllCategories();
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Populate all category definitions with labels, icons, flags |
|
|
//+------------------------------------------------------------------+
|
|
void CCategoryRegistry::InitAllCategories()
|
|
{
|
|
//--- Assign Cursors category definition
|
|
m_categories[CAT_CURSORS].categoryLabel = "Cursors";
|
|
m_categories[CAT_CURSORS].iconFontName = ICON_CATEGORY_CURSORS.fontName;
|
|
m_categories[CAT_CURSORS].iconCharCode = ICON_CATEGORY_CURSORS.charCode;
|
|
m_categories[CAT_CURSORS].hasMultipleTools = false;
|
|
//--- Assign Lines category definition
|
|
m_categories[CAT_LINES].categoryLabel = "Lines";
|
|
m_categories[CAT_LINES].iconFontName = ICON_CATEGORY_LINES.fontName;
|
|
m_categories[CAT_LINES].iconCharCode = ICON_CATEGORY_LINES.charCode;
|
|
m_categories[CAT_LINES].hasMultipleTools = true;
|
|
//--- Assign Channels category definition
|
|
m_categories[CAT_CHANNELS].categoryLabel = "Channels";
|
|
m_categories[CAT_CHANNELS].iconFontName = ICON_CATEGORY_CHANNELS.fontName;
|
|
m_categories[CAT_CHANNELS].iconCharCode = ICON_CATEGORY_CHANNELS.charCode;
|
|
m_categories[CAT_CHANNELS].hasMultipleTools = true;
|
|
//--- Assign Pitchfork category definition
|
|
m_categories[CAT_PITCHFORK].categoryLabel = "Pitchfork";
|
|
m_categories[CAT_PITCHFORK].iconFontName = ICON_CATEGORY_PITCHFORK.fontName;
|
|
m_categories[CAT_PITCHFORK].iconCharCode = ICON_CATEGORY_PITCHFORK.charCode;
|
|
m_categories[CAT_PITCHFORK].hasMultipleTools = true;
|
|
//--- Assign Gann category definition
|
|
m_categories[CAT_GANN].categoryLabel = "Gann";
|
|
m_categories[CAT_GANN].iconFontName = ICON_CATEGORY_GANN.fontName;
|
|
m_categories[CAT_GANN].iconCharCode = ICON_CATEGORY_GANN.charCode;
|
|
m_categories[CAT_GANN].hasMultipleTools = true;
|
|
//--- Assign Fibonacci category definition
|
|
m_categories[CAT_FIBONACCI].categoryLabel = "Fibonacci";
|
|
m_categories[CAT_FIBONACCI].iconFontName = ICON_CATEGORY_FIBONACCI.fontName;
|
|
m_categories[CAT_FIBONACCI].iconCharCode = ICON_CATEGORY_FIBONACCI.charCode;
|
|
m_categories[CAT_FIBONACCI].hasMultipleTools = true;
|
|
//--- Assign Shapes category definition
|
|
m_categories[CAT_SHAPES].categoryLabel = "Shapes";
|
|
m_categories[CAT_SHAPES].iconFontName = ICON_CATEGORY_SHAPES.fontName;
|
|
m_categories[CAT_SHAPES].iconCharCode = ICON_CATEGORY_SHAPES.charCode;
|
|
m_categories[CAT_SHAPES].hasMultipleTools = true;
|
|
//--- Assign Annotations category definition
|
|
m_categories[CAT_ANNOTATIONS].categoryLabel = "Annotate";
|
|
m_categories[CAT_ANNOTATIONS].iconFontName = ICON_CATEGORY_ANNOTATIONS.fontName;
|
|
m_categories[CAT_ANNOTATIONS].iconCharCode = ICON_CATEGORY_ANNOTATIONS.charCode;
|
|
m_categories[CAT_ANNOTATIONS].hasMultipleTools = true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS 4 — Create, destroy, and resize all canvas layers |
|
|
//+------------------------------------------------------------------+
|
|
class CCanvasLayer : public CCategoryRegistry
|
|
{
|
|
protected:
|
|
int m_supersampleFactor; // Supersampling multiplier for high-res rendering
|
|
long m_chartId; // Chart identifier this layer belongs to
|
|
CCanvas m_canvasSidebar; // Final display-resolution sidebar canvas
|
|
CCanvas m_canvasSidebarHighRes; // High-resolution sidebar canvas for supersampling
|
|
string m_nameSidebar; // Object name of the sidebar bitmap label
|
|
|
|
protected:
|
|
//--- Create all canvas objects at the given dimensions
|
|
bool CreateAllCanvases(int w, int h);
|
|
//--- Destroy all canvas objects and remove chart objects
|
|
void DestroyAllCanvases();
|
|
//--- Resize both sidebar canvases to the given dimensions
|
|
void ResizeSidebarCanvases(int w, int h);
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Create all canvas objects at the given dimensions |
|
|
//+------------------------------------------------------------------+
|
|
bool CCanvasLayer::CreateAllCanvases(int w, int h)
|
|
{
|
|
//--- Create the display-resolution sidebar bitmap label canvas
|
|
if (!m_canvasSidebar.CreateBitmapLabel(0, 0, m_nameSidebar, 0, 0, w, h, COLOR_FORMAT_ARGB_NORMALIZE))
|
|
{ Print("CCanvasLayer: Failed to create sidebar canvas"); return false; }
|
|
//--- Create the high-resolution sidebar canvas for supersampled drawing
|
|
if (!m_canvasSidebarHighRes.Create("ToolsPalette_SidebarHR", w * m_supersampleFactor, h * m_supersampleFactor, COLOR_FORMAT_ARGB_NORMALIZE))
|
|
{ Print("CCanvasLayer: Failed to create sidebar HR canvas"); return false; }
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destroy all canvas objects and remove chart objects |
|
|
//+------------------------------------------------------------------+
|
|
void CCanvasLayer::DestroyAllCanvases()
|
|
{
|
|
//--- Destroy display canvas and remove its chart object
|
|
m_canvasSidebar.Destroy();
|
|
ObjectDelete(0, m_nameSidebar);
|
|
//--- Destroy the high-resolution working canvas
|
|
m_canvasSidebarHighRes.Destroy();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Resize both sidebar canvases to the given dimensions |
|
|
//+------------------------------------------------------------------+
|
|
void CCanvasLayer::ResizeSidebarCanvases(int w, int h)
|
|
{
|
|
//--- Resize the display-resolution sidebar canvas
|
|
m_canvasSidebar.Resize(w, h);
|
|
//--- Update the chart object dimensions to match
|
|
ObjectSetInteger(0, m_nameSidebar, OBJPROP_XSIZE, w);
|
|
ObjectSetInteger(0, m_nameSidebar, OBJPROP_YSIZE, h);
|
|
//--- Resize the high-resolution canvas scaled by the supersample factor
|
|
m_canvasSidebarHighRes.Resize(w * m_supersampleFactor, h * m_supersampleFactor);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS 5 — Compute and maintain sidebar layout and geometry |
|
|
//+------------------------------------------------------------------+
|
|
class CSidebarLayout : public CCanvasLayer
|
|
{
|
|
protected:
|
|
int m_panelX; // Horizontal position of the sidebar panel
|
|
int m_panelY; // Vertical position of the sidebar panel
|
|
int m_sidebarWidth; // Width of the sidebar panel in pixels
|
|
int m_sidebarHeight; // Height of the sidebar panel in pixels
|
|
int m_categoryButtonSize; // Size of each category button in pixels
|
|
int m_categoryButtonPadding; // Vertical gap between category buttons
|
|
int m_panelCornerRadius; // Corner rounding radius of the panel
|
|
int m_headerGripHeight; // Height of the top header and grip area
|
|
ENUM_SNAP_STATE m_snapState; // Current snap alignment state
|
|
int m_sidebarMaxVisibleCats; // Maximum number of visible category buttons
|
|
|
|
protected:
|
|
//--- Compute and set the sidebar panel height based on available chart space
|
|
void CalcSidebarHeight();
|
|
//--- Compute the Y pixel position of a category button by index
|
|
int CalcCategoryButtonY(int idx);
|
|
//--- Compute the top clipping boundary for the category button area
|
|
int CalcClipTop();
|
|
//--- Compute the bottom clipping boundary for the category button area
|
|
int CalcClipBottom();
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Compute and set sidebar height based on available chart space |
|
|
//+------------------------------------------------------------------+
|
|
void CSidebarLayout::CalcSidebarHeight()
|
|
{
|
|
//--- Get current chart height in pixels
|
|
int chartH = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
|
|
//--- Define vertical padding constants
|
|
int topPad = 8, botPad = 10;
|
|
//--- Set button gap spacing
|
|
m_categoryButtonPadding = 6;
|
|
//--- Pin panel to fixed Y offset below chart top
|
|
m_panelY = 30;
|
|
ObjectSetInteger(0, m_nameSidebar, OBJPROP_YDISTANCE, m_panelY);
|
|
//--- Compute maximum available height below panel top offset
|
|
int availH = chartH - m_panelY - 8;
|
|
//--- Compute ideal natural height to fit all category buttons
|
|
int naturalH = m_headerGripHeight + topPad + CAT_COUNT * (m_categoryButtonSize + m_categoryButtonPadding) - m_categoryButtonPadding + botPad;
|
|
//--- Compute minimum height to show at least three category buttons
|
|
int minH = m_headerGripHeight + topPad + 3 * (m_categoryButtonSize + m_categoryButtonPadding) - m_categoryButtonPadding + botPad;
|
|
//--- Clamp sidebar height between minimum and available space
|
|
m_sidebarHeight = MathMax(minH, MathMin(naturalH, availH));
|
|
//--- Compute usable height for the button area
|
|
int btnAreaH = m_sidebarHeight - m_headerGripHeight - topPad - botPad;
|
|
//--- Compute total height needed for all buttons at natural spacing
|
|
int fullBtnH = CAT_COUNT * (m_categoryButtonSize + m_categoryButtonPadding) - m_categoryButtonPadding;
|
|
//--- Compute how many buttons fit within the available button area
|
|
m_sidebarMaxVisibleCats = (fullBtnH <= btnAreaH) ? CAT_COUNT
|
|
: MathMax(3, MathMin(CAT_COUNT, btnAreaH / (m_categoryButtonSize + m_categoryButtonPadding)));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Compute Y pixel position of a category button by index |
|
|
//+------------------------------------------------------------------+
|
|
int CSidebarLayout::CalcCategoryButtonY(int idx)
|
|
{
|
|
//--- Return Y offset below the header grip area
|
|
return m_headerGripHeight + 8 + idx * (m_categoryButtonSize + m_categoryButtonPadding);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Compute top clip boundary for the category button area |
|
|
//+------------------------------------------------------------------+
|
|
int CSidebarLayout::CalcClipTop()
|
|
{
|
|
//--- Return Y position just below the header grip bottom edge
|
|
return m_headerGripHeight + 8;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Compute bottom clip boundary for the category button area |
|
|
//+------------------------------------------------------------------+
|
|
int CSidebarLayout::CalcClipBottom()
|
|
{
|
|
//--- Return Y position leaving bottom padding inside the panel
|
|
return m_sidebarHeight - 10;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS 6 — Draw and composite all sidebar visual elements |
|
|
//+------------------------------------------------------------------+
|
|
class CSidebarRenderer : public CSidebarLayout
|
|
{
|
|
protected:
|
|
//--- Draw and composite the full sidebar onto its canvas
|
|
void DrawSidebar();
|
|
//--- Draw the header grip strip at high resolution
|
|
void DrawHeaderStripHR(int canvasW, int canvasH);
|
|
//--- Draw a single category button indicator at high resolution
|
|
void DrawCategoryButtonHR(CCanvas &target, int xHR, int yHR, int sizeHR, bool hasDot);
|
|
//--- Draw icon glyphs and separator lines onto the display canvas
|
|
void DrawSidebarIconLabels();
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw and composite the full sidebar onto its canvas |
|
|
//+------------------------------------------------------------------+
|
|
void CSidebarRenderer::DrawSidebar()
|
|
{
|
|
//--- Compute high-resolution canvas dimensions
|
|
int ws = m_sidebarWidth * m_supersampleFactor, hs = m_sidebarHeight * m_supersampleFactor;
|
|
//--- Resize high-res canvas if dimensions have changed
|
|
if (m_canvasSidebarHighRes.Width() != ws || m_canvasSidebarHighRes.Height() != hs)
|
|
m_canvasSidebarHighRes.Resize(ws, hs);
|
|
//--- Clear the high-res canvas to fully transparent
|
|
m_canvasSidebarHighRes.Erase(0x00000000);
|
|
//--- Compute background alpha from opacity input
|
|
uchar bgA = (uchar)(255 * BackgroundOpacity);
|
|
//--- Determine which corners are rounded based on snap state
|
|
bool rTL = (m_snapState != SNAP_LEFT), rBL = rTL;
|
|
bool rTR = (m_snapState != SNAP_RIGHT), rBR = rTR;
|
|
//--- Fill sidebar background with selective rounded corners
|
|
FillSelectiveRoundRectHR(m_canvasSidebarHighRes, 0, 0, ws, hs,
|
|
m_panelCornerRadius * m_supersampleFactor,
|
|
ColorToARGB(m_themeColors.sidebarBackground, bgA), rTL, rTR, rBL, rBR);
|
|
//--- Draw left snap flush edge using foreground chart colour
|
|
if (m_snapState == SNAP_LEFT)
|
|
m_canvasSidebarHighRes.FillRectangle(0, 0, m_supersampleFactor - 1, hs - 1,
|
|
ColorToARGB((color)ChartGetInteger(0, CHART_COLOR_FOREGROUND), 255));
|
|
//--- Draw right snap flush edge using foreground chart colour
|
|
else if (m_snapState == SNAP_RIGHT)
|
|
m_canvasSidebarHighRes.FillRectangle(ws - m_supersampleFactor, 0, ws - 1, hs - 1,
|
|
ColorToARGB((color)ChartGetInteger(0, CHART_COLOR_FOREGROUND), 255));
|
|
//--- Draw the header grip strip onto the high-res canvas
|
|
DrawHeaderStripHR(ws, hs);
|
|
//--- Draw each category button indicator at high resolution
|
|
for (int c = 0; c < CAT_COUNT; c++)
|
|
DrawCategoryButtonHR(m_canvasSidebarHighRes,
|
|
(m_sidebarWidth - m_categoryButtonSize) / 2 * m_supersampleFactor,
|
|
CalcCategoryButtonY(c) * m_supersampleFactor,
|
|
m_categoryButtonSize * m_supersampleFactor,
|
|
m_categories[c].hasMultipleTools);
|
|
//--- Draw the panel border at high resolution if enabled
|
|
if (BorderWidth > 0)
|
|
DrawSelectiveRoundRectBorderHR(m_canvasSidebarHighRes, 0, 0, ws, hs,
|
|
m_panelCornerRadius * m_supersampleFactor,
|
|
ColorToARGB(m_themeColors.sidebarBorder, 255),
|
|
BorderWidth * m_supersampleFactor, rTL, rTR, rBL, rBR);
|
|
//--- Downsample high-res canvas into the display-resolution canvas
|
|
DownsampleCanvas(m_canvasSidebar, m_canvasSidebarHighRes, m_supersampleFactor);
|
|
//--- Overlay icon glyphs and separator lines onto the display canvas
|
|
DrawSidebarIconLabels();
|
|
//--- Flush the display canvas to the chart
|
|
m_canvasSidebar.Update();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw the header grip strip at high resolution |
|
|
//+------------------------------------------------------------------+
|
|
void CSidebarRenderer::DrawHeaderStripHR(int canvasW, int canvasH)
|
|
{
|
|
//--- Compute scaled header height and border inset
|
|
int headerH = m_headerGripHeight * m_supersampleFactor;
|
|
int brd = BorderWidth * m_supersampleFactor;
|
|
//--- Compute horizontal insets based on snap state
|
|
int inL = (m_snapState == SNAP_LEFT) ? 0 : brd, inR = (m_snapState == SNAP_RIGHT) ? 0 : brd;
|
|
//--- Compute header fill rectangle position and size
|
|
int hx = inL, hy = brd, hw = canvasW - inL - inR, hh = headerH - brd;
|
|
//--- Compute inner corner radius inset from border
|
|
int innerR = MathMax(0, m_panelCornerRadius * m_supersampleFactor - brd);
|
|
//--- Determine top corner rounding based on snap state
|
|
bool rTL = (m_snapState != SNAP_LEFT), rTR = (m_snapState != SNAP_RIGHT);
|
|
//--- Select header fill colour based on active theme
|
|
color hdrFill = m_isDarkTheme ? C'25,29,40' : C'245,247,252';
|
|
//--- Fill the upper portion of the header strip with rounded top corners
|
|
FillSelectiveRoundRectHR(m_canvasSidebarHighRes, hx, hy, hw, hh, innerR, ColorToARGB(hdrFill, 255), rTL, rTR, false, false);
|
|
//--- Fill the lower half of the header to square off the bottom
|
|
m_canvasSidebarHighRes.FillRectangle(hx, hy + hh / 2, hx + hw - 1, headerH - 1, ColorToARGB(hdrFill, 255));
|
|
//--- Compute grip dot row position and height
|
|
int row2Y = m_categoryButtonSize * m_supersampleFactor, row2H = 20 * m_supersampleFactor;
|
|
//--- Compute dot spacing and radius at high resolution
|
|
int gapX = 6 * m_supersampleFactor, dotR = 2 * m_supersampleFactor;
|
|
uint dotColor = ColorToARGB(m_themeColors.gripDotsColor, 255);
|
|
//--- Draw three horizontally centred grip dots
|
|
for (int col = 0; col < 3; col++)
|
|
m_canvasSidebarHighRes.FillCircle(canvasW / 2 + (col - 1) * gapX, row2Y + row2H / 2, dotR, dotColor);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw a single category button dot indicator at high resolution |
|
|
//+------------------------------------------------------------------+
|
|
void CSidebarRenderer::DrawCategoryButtonHR(CCanvas &target, int xHR, int yHR, int sizeHR, bool hasDot)
|
|
{
|
|
//--- Draw the multi-tool indicator dot in the bottom-right corner if applicable
|
|
if (hasDot)
|
|
target.FillCircle(xHR + sizeHR - 6 * m_supersampleFactor, yHR + sizeHR - 6 * m_supersampleFactor,
|
|
2 * m_supersampleFactor, ColorToARGB(m_themeColors.gripDotsColor, 180));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw icon glyphs and separator lines onto the display canvas |
|
|
//+------------------------------------------------------------------+
|
|
void CSidebarRenderer::DrawSidebarIconLabels()
|
|
{
|
|
//--- Iterate over all category slots to draw icons
|
|
for (int c = 0; c < CAT_COUNT; c++)
|
|
{
|
|
//--- Compute button top-left position in display-resolution coordinates
|
|
int btnY = CalcCategoryButtonY(c), btnX = (m_sidebarWidth - m_categoryButtonSize) / 2;
|
|
//--- Set font to the category icon font at configured size
|
|
m_canvasSidebar.FontSet(m_categories[c].iconFontName, CategoryIconSize);
|
|
//--- Build the icon glyph string from its character code
|
|
string sym = CharToString(m_categories[c].iconCharCode);
|
|
//--- Measure icon glyph dimensions for centring
|
|
int iw = m_canvasSidebar.TextWidth(sym), ih = m_canvasSidebar.TextHeight(sym);
|
|
//--- Draw the icon centred within the button area
|
|
m_canvasSidebar.TextOut(btnX + (m_categoryButtonSize - iw) / 2, btnY + (m_categoryButtonSize - ih) / 2, sym, ColorToARGB(m_themeColors.buttonIconColor, 255));
|
|
}
|
|
//--- Compute separator line left and right extents based on snap and border
|
|
int brd = BorderWidth;
|
|
int sepL = (m_snapState == SNAP_LEFT) ? 0 : brd, sepR = m_sidebarWidth - 1 - ((m_snapState == SNAP_RIGHT) ? 0 : brd);
|
|
//--- Pack separator line colours for primary and secondary lines
|
|
uint sepCol = ColorToARGB(m_themeColors.separatorColor, 255);
|
|
uint sepCol2 = ColorToARGB(m_isDarkTheme ? C'45,52,66' : C'195,202,215', 255);
|
|
//--- Draw primary separator below the header strip
|
|
m_canvasSidebar.Line(sepL, m_headerGripHeight - 1, sepR, m_headerGripHeight - 1, sepCol);
|
|
//--- Draw first secondary separator below the header row
|
|
m_canvasSidebar.Line(sepL, m_categoryButtonSize, sepR, m_categoryButtonSize, sepCol2);
|
|
//--- Draw second secondary separator below the theme toggle row
|
|
m_canvasSidebar.Line(sepL, m_categoryButtonSize + 20, sepR, m_categoryButtonSize + 20, sepCol2);
|
|
//--- Set font to Webdings for the close button icon
|
|
m_canvasSidebar.FontSet("Webdings", CategoryIconSize);
|
|
string closeSym = CharToString((uchar)114); // Webdings char 114 = close/X glyph
|
|
//--- Measure close icon dimensions for centring
|
|
int clW = m_canvasSidebar.TextWidth(closeSym), clH = m_canvasSidebar.TextHeight(closeSym);
|
|
//--- Draw the close icon centred in the top header button slot
|
|
m_canvasSidebar.TextOut((m_sidebarWidth - clW) / 2, (m_categoryButtonSize - clH) / 2, closeSym, ColorToARGB(m_themeColors.buttonIconColor, 255));
|
|
//--- Compute row extents for the theme toggle button slot
|
|
int row3Y = m_categoryButtonSize + 20, row3H = m_headerGripHeight - m_categoryButtonSize - 20;
|
|
//--- Set font to Wingdings for the theme toggle icon
|
|
m_canvasSidebar.FontSet("Wingdings", CategoryIconSize);
|
|
string themeSym = CharToString((uchar)91); // Wingdings char 91 = sun/moon glyph
|
|
//--- Measure theme icon dimensions for centring
|
|
int thW = m_canvasSidebar.TextWidth(themeSym), thH = m_canvasSidebar.TextHeight(themeSym);
|
|
//--- Draw the theme toggle icon centred in its row slot
|
|
m_canvasSidebar.TextOut((m_sidebarWidth - thW) / 2, row3Y + (row3H - thH) / 2, themeSym, ColorToARGB(m_themeColors.buttonIconColor, 255));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CLASS 7 — Top-level sidebar shell exposing the public interface |
|
|
//+------------------------------------------------------------------+
|
|
class CToolsSidebar : public CSidebarRenderer
|
|
{
|
|
public:
|
|
CToolsSidebar() { InitDefaults(); } // Construct and apply default state
|
|
~CToolsSidebar() { Destroy(); } // Destruct and clean up all resources
|
|
|
|
//--- Initialise the sidebar and build all canvas objects
|
|
bool Init(long chartId);
|
|
//--- Destroy all canvas objects and release resources
|
|
void Destroy();
|
|
//--- Handle incoming chart events (reserved for future interaction)
|
|
void OnEvent(const int id, const long &lp, const double &dp, const string &sp) {}
|
|
|
|
private:
|
|
//--- Set all member variables to their compile-time default values
|
|
void InitDefaults();
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set all member variables to their compile-time default values |
|
|
//+------------------------------------------------------------------+
|
|
void CToolsSidebar::InitDefaults()
|
|
{
|
|
//--- Reset chart reference to default
|
|
m_chartId = 0;
|
|
//--- Set the sidebar bitmap label object name
|
|
m_nameSidebar = "ToolsPalette_Sidebar";
|
|
//--- Set supersampling factor for high-res rendering
|
|
m_supersampleFactor = 4;
|
|
//--- Set default button size and spacing
|
|
m_categoryButtonSize = 36;
|
|
m_categoryButtonPadding = 6;
|
|
//--- Set panel corner rounding radius
|
|
m_panelCornerRadius = 10;
|
|
//--- Set header and grip strip height
|
|
m_headerGripHeight = 92;
|
|
//--- Set sidebar panel width
|
|
m_sidebarWidth = 48;
|
|
//--- Reset computed height and visible category count
|
|
m_sidebarHeight = 0;
|
|
m_sidebarMaxVisibleCats = 0;
|
|
//--- Reset panel position to origin
|
|
m_panelX = 0;
|
|
m_panelY = CanvasY;
|
|
//--- Default snap state to left edge
|
|
m_snapState = SNAP_LEFT;
|
|
//--- Apply the starting theme from user input
|
|
m_isDarkTheme = StartDark;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialise the sidebar and build all canvas objects |
|
|
//+------------------------------------------------------------------+
|
|
bool CToolsSidebar::Init(long chartId)
|
|
{
|
|
//--- Reset all members to defaults before initialising
|
|
InitDefaults();
|
|
//--- Store the target chart identifier
|
|
m_chartId = chartId;
|
|
//--- Populate all category definitions
|
|
InitAllCategories();
|
|
//--- Apply the active theme colour set
|
|
ApplyTheme();
|
|
//--- Compute and set the sidebar panel height
|
|
CalcSidebarHeight();
|
|
//--- Create all canvas layers; abort on failure
|
|
if (!CreateAllCanvases(m_sidebarWidth, m_sidebarHeight)) return false;
|
|
//--- Position the sidebar panel on the chart
|
|
ObjectSetInteger(0, m_nameSidebar, OBJPROP_XDISTANCE, m_panelX);
|
|
ObjectSetInteger(0, m_nameSidebar, OBJPROP_YDISTANCE, m_panelY);
|
|
//--- Set the sidebar Z-order to render above chart objects
|
|
ObjectSetInteger(0, m_nameSidebar, OBJPROP_ZORDER, 100);
|
|
//--- Draw the initial sidebar frame
|
|
DrawSidebar();
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destroy all canvas objects and release resources |
|
|
//+------------------------------------------------------------------+
|
|
void CToolsSidebar::Destroy()
|
|
{
|
|
//--- Delegate canvas cleanup to the layer base class
|
|
DestroyAllCanvases();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Global variables |
|
|
//+------------------------------------------------------------------+
|
|
CToolsSidebar g_sidebar; // Global sidebar instance
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
//---
|
|
//--- Initialise the sidebar and fail if canvas creation fails
|
|
if (!g_sidebar.Init(ChartID())) return INIT_FAILED;
|
|
//--- Force a chart redraw to display the sidebar immediately
|
|
ChartRedraw();
|
|
//---
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
//---
|
|
//--- Destroy the sidebar and remove all canvas objects
|
|
g_sidebar.Destroy();
|
|
//--- Force a chart redraw to clear the sidebar from view
|
|
ChartRedraw();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert chart event function |
|
|
//+------------------------------------------------------------------+
|
|
void OnChartEvent(const int id, const long &lp, const double &dp, const string &sp)
|
|
{
|
|
//---
|
|
//--- Forward the chart event to the sidebar event handler
|
|
g_sidebar.OnEvent(id, lp, dp, sp);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
//---
|
|
|
|
}
|
|
//+------------------------------------------------------------------+ |