Article-22193-Tools-Palette.../Tools Palette Part 2.mq5

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()
{
//---
}
//+------------------------------------------------------------------+