2026-06-05 15:26:54 +03:00
//+------------------------------------------------------------------+
//| Tools Palette Part 4.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
2026-06-05 15:30:58 +03:00
2026-06-05 15:26:54 +03:00
//--- Include canvas drawing library
# include <Canvas/Canvas.mqh>
//+------------------------------------------------------------------+
//| Icon definitions — category and tool buttons |
//+------------------------------------------------------------------+
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
//--- Define icon for each individual drawing tool using font/char pairs
SIconDefinition ICON_TOOL_POINTER = { " Wingdings 3 " , ( uchar ) ' - ' } ; // Pointer tool icon
SIconDefinition ICON_TOOL_CROSSHAIR = { " Wingdings " , ( uchar ) ' W ' } ; // Crosshair tool icon
SIconDefinition ICON_TOOL_TRENDLINE = { " Wingdings 3 " , ( uchar ) ' & ' } ; // Trend line tool icon
SIconDefinition ICON_TOOL_HLINE = { " Wingdings 3 " , ( uchar ) ' " ' } ; // Horizontal line tool icon
SIconDefinition ICON_TOOL_VLINE = { " Wingdings 3 " , ( uchar ) ' # ' } ; // Vertical line tool icon
SIconDefinition ICON_TOOL_RAY = { " Wingdings 3 " , ( uchar ) ' & ' } ; // Ray line tool icon
SIconDefinition ICON_TOOL_EXTENDED_LINE = { " Wingdings 3 " , ( uchar ) ' 1 ' } ; // Extended line tool icon
SIconDefinition ICON_TOOL_INFO_LINE = { " Wingdings 3 " , ( uchar ) ' 2 ' } ; // Info/measure line tool icon
SIconDefinition ICON_TOOL_PARALLEL_CH = { " Wingdings 3 " , ( uchar ) ' H ' } ; // Parallel channel tool icon
SIconDefinition ICON_TOOL_REGRESSION_CH = { " Wingdings 3 " , ( uchar ) ' I ' } ; // Regression channel tool icon
SIconDefinition ICON_TOOL_STDDEV_CH = { " Wingdings 3 " , ( uchar ) ' J ' } ; // Standard deviation channel tool icon
SIconDefinition ICON_TOOL_PITCHFORK = { " Wingdings 3 " , ( uchar ) ' H ' } ; // Andrew's pitchfork tool icon
SIconDefinition ICON_TOOL_SCHIFF = { " Wingdings 3 " , ( uchar ) ' I ' } ; // Schiff pitchfork tool icon
SIconDefinition ICON_TOOL_MOD_SCHIFF = { " Wingdings 3 " , ( uchar ) ' K ' } ; // Modified Schiff pitchfork tool icon
SIconDefinition ICON_TOOL_GANN_LINE = { " Wingdings 3 " , ( uchar ) ' & ' } ; // Gann line tool icon
SIconDefinition ICON_TOOL_GANN_FAN = { " Wingdings 3 " , ( uchar ) ' 0 ' } ; // Gann fan tool icon
SIconDefinition ICON_TOOL_GANN_GRID = { " Wingdings " , ( uchar ) ' i ' } ; // Gann grid tool icon
SIconDefinition ICON_TOOL_FIBO_RET = { " Wingdings " , ( uchar ) ' [ ' } ; // Fibonacci retracement tool icon
SIconDefinition ICON_TOOL_FIBO_EXP = { " Wingdings 3 " , ( uchar ) ' & ' } ; // Fibonacci expansion tool icon
SIconDefinition ICON_TOOL_FIBO_CH = { " Wingdings 3 " , ( uchar ) ' H ' } ; // Fibonacci channel tool icon
SIconDefinition ICON_TOOL_FIBO_TZ = { " Wingdings 3 " , ( uchar ) ' # ' } ; // Fibonacci time zones tool icon
SIconDefinition ICON_TOOL_FIBO_FAN = { " Wingdings 3 " , ( uchar ) ' J ' } ; // Fibonacci fan tool icon
SIconDefinition ICON_TOOL_FIBO_ARCS = { " Wingdings " , ( uchar ) ' l ' } ; // Fibonacci arcs tool icon
SIconDefinition ICON_TOOL_RECTANGLE = { " Wingdings " , ( uchar ) ' o ' } ; // Rectangle tool icon
SIconDefinition ICON_TOOL_TRIANGLE = { " Wingdings 3 " , ( uchar ) ' p ' } ; // Triangle tool icon
SIconDefinition ICON_TOOL_ELLIPSE = { " Wingdings " , ( uchar ) ' l ' } ; // Ellipse tool icon
SIconDefinition ICON_TOOL_TEXT = { " Webdings " , ( uchar ) ' > ' } ; // Text label tool icon
SIconDefinition ICON_TOOL_ARROW_UP = { " Wingdings " , ( uchar ) 225 } ; // Arrow up tool icon
SIconDefinition ICON_TOOL_ARROW_DOWN = { " Wingdings " , ( uchar ) 226 } ; // Arrow down tool icon
SIconDefinition ICON_TOOL_THUMB_UP = { " Wingdings " , ( uchar ) ' C ' } ; // Thumbs up tool icon
SIconDefinition ICON_TOOL_THUMB_DOWN = { " Wingdings " , ( uchar ) ' D ' } ; // Thumbs down tool icon
SIconDefinition ICON_TOOL_PRICE_LABEL = { " Wingdings " , ( uchar ) 234 } ; // Left price label tool icon
SIconDefinition ICON_TOOL_STOP_SIGN = { " Wingdings " , ( uchar ) 251 } ; // Stop sign tool icon
SIconDefinition ICON_TOOL_CHECK_MARK = { " Wingdings " , ( uchar ) 252 } ; // Check mark tool 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 TOOL_TYPE
{
TOOL_NONE = 0 , // No tool active
TOOL_POINTER , // Default pointer cursor
TOOL_CROSSHAIR , // Crosshair / measure cursor
TOOL_TRENDLINE , // Trend line drawing tool
TOOL_HLINE , // Horizontal line drawing tool
TOOL_VLINE , // Vertical line drawing tool
TOOL_RAY , // Ray line drawing tool
TOOL_EXTENDED_LINE , // Extended (infinite) line drawing tool
TOOL_INFO_LINE , // Info / measure line drawing tool
TOOL_PARALLEL_CHANNEL , // Parallel channel drawing tool
TOOL_REGRESSION_CHANNEL , // Regression channel drawing tool
TOOL_STDDEV_CHANNEL , // Standard deviation channel drawing tool
TOOL_PITCHFORK , // Andrew's pitchfork drawing tool
TOOL_SCHIFF_PITCHFORK , // Schiff pitchfork drawing tool
TOOL_MOD_SCHIFF , // Modified Schiff pitchfork drawing tool
TOOL_GANN_LINE , // Gann line drawing tool
TOOL_GANN_FAN , // Gann fan drawing tool
TOOL_GANN_GRID , // Gann grid drawing tool
TOOL_FIBO_RETRACEMENT , // Fibonacci retracement drawing tool
TOOL_FIBO_EXPANSION , // Fibonacci expansion drawing tool
TOOL_FIBO_CHANNEL , // Fibonacci channel drawing tool
TOOL_FIBO_TIMEZONES , // Fibonacci time zones drawing tool
TOOL_FIBO_FAN , // Fibonacci fan drawing tool
TOOL_FIBO_ARCS , // Fibonacci arcs drawing tool
TOOL_RECTANGLE , // Rectangle shape drawing tool
TOOL_TRIANGLE , // Triangle shape drawing tool
TOOL_ELLIPSE , // Ellipse shape drawing tool
TOOL_TEXT , // Text label annotation tool
TOOL_ARROW_UP , // Arrow up annotation tool
TOOL_ARROW_DOWN , // Arrow down annotation tool
TOOL_THUMB_UP , // Thumbs up annotation tool
TOOL_THUMB_DOWN , // Thumbs down annotation tool
TOOL_PRICE_LABEL , // Left price label annotation tool
TOOL_STOP_SIGN , // Stop sign annotation tool
TOOL_CHECK_MARK // Check mark annotation tool
} ;
enum ENUM_CATEGORY
{
CAT_NONE = -1 , // No category selected
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 FlyoutIconSize = 22 ; // Flyout Icon Size (pt)
input int FlyoutLabelSize = 15 ; // Flyout Label Font Size (pt)
input int FlyoutTitleSize = 14 ; // Flyout Title Font Size (pt)
input int MouseScrollSpeed = 8 ; // Mouse Scroll Step (px)
input int SnapThreshold = 40 ; // Edge Snap Threshold (px)
input int ReticleOffset = 30 ; // Crosshair Reticle Offset (px)
input int ReticleTickLen = 14 ; // Crosshair Reticle Tick Length (px)
input int ReticleThickness = 2 ; // Crosshair Reticle Tick Thickness (px)
input int MagDiameter = 180 ; // Magnifier Diameter (px)
input double MagZoom = 3.0 ; // Magnifier Zoom Factor
input int MagOffset = 45 ; // Magnifier Offset From Cursor (px)
input int AxisLabelFontSize = 9 ; // Axis Label Font Size (pt)
input string AxisLabelFont = " Arial " ; // Axis Label Font
//+------------------------------------------------------------------+
//| Tool definition structure |
//+------------------------------------------------------------------+
struct ToolDefinition
{
TOOL_TYPE toolType ; // Unique tool type identifier
string toolLabel ; // Display label shown in the flyout panel
string iconFontName ; // Font name used to render the tool icon
uchar iconCharCode ; // Character code of the tool icon glyph
string tooltipText ; // Tooltip string shown on hover
} ;
//+------------------------------------------------------------------+
//| Category definition structure |
//+------------------------------------------------------------------+
struct CategoryDefinition
{
string categoryLabel ; // Display label for the category
string iconFontName ; // Font name used to render the category icon
uchar iconCharCode ; // Character code of the category icon glyph
ToolDefinition tools [ ] ; // Dynamic array of tools belonging to this category
} ;
//+------------------------------------------------------------------+
//| 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 buttonHoverBackground ; // Background fill when a category button is hovered
color buttonActiveBackground ; // Background fill when a category button is active
color buttonIconColor ; // Default color used to render category icons
color buttonIconActiveColor ; // Icon color when the button is in active state
color flyoutBackground ; // Background fill color of the flyout panel
color flyoutBorder ; // Outline border color of the flyout panel
color flyoutItemHoverBackground ; // Background fill of a hovered flyout item row
color flyoutTextColor ; // Default text color of flyout item labels
color flyoutTextActiveColor ; // Text color of the active flyout item label
color flyoutTitleColor ; // Color of the flyout panel title text
color gripDotsColor ; // Color of the drag-grip dot indicators
color closeButtonHoverColor ; // Background fill of the close button on hover
color themeButtonHoverColor ; // Background fill of the theme button on hover
color separatorColor ; // Color of the horizontal separator lines
color accentBarColor ; // Color of the active tool accent bar indicator
color scrollArrowColor ; // Default color of the scroll thumb pill
color scrollArrowHoverColor ; // Color of the scroll thumb pill on hover
} ;
//+------------------------------------------------------------------+
//| 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 triangle using scanline rasterization at high resolution
void FillTriangleHR ( CCanvas & canvas , int x0 , int y0 , int x1 , int y1 , int x2 , int y2 , uint argb ) ;
//--- 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 ) ;
//--- Draw a line using Bresenham's algorithm with alpha blending
void DrawBresenhamLine ( CCanvas & canvas , int x0 , int y0 , int x1 , int y1 , uint argb ) ;
} ;
//+------------------------------------------------------------------+
//| 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 + + )
{
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 triangle using scanline rasterization at high resolution |
//+------------------------------------------------------------------+
void CCanvasPrimitives : : FillTriangleHR ( CCanvas & canvas , int x0 , int y0 , int x1 , int y1 , int x2 , int y2 , uint argb )
{
//--- Store triangle vertices as floating-point arrays for scanline processing
double vx [ 3 ] = { ( double ) x0 , ( double ) x1 , ( double ) x2 } ;
double vy [ 3 ] = { ( double ) y0 , ( double ) y1 , ( double ) y2 } ;
//--- Find vertical bounding extent of the triangle
double minY = vy [ 0 ] , maxY = vy [ 0 ] ;
for ( int i = 1 ; i < 3 ; 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 ) MathFloor ( maxY ) ; scanY + + )
{
//--- Compute scanline centre Y and prepare intersection buffer
double cy = ( double ) scanY + 0.5 ; double xi [ 6 ] ; int nc = 0 ;
//--- Compute X intersections with each edge of the triangle
for ( int i = 0 ; i < 3 ; i + + )
{
int ni = ( i + 1 ) % 3 ;
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 ;
double t = ( cy - vy [ i ] ) / ( vy [ ni ] - vy [ i ] ) ;
if ( t < 0.0 | | t > 1.0 ) continue ;
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 ) MathFloor ( xi [ p + 1 ] ) ; fx + + )
canvas . PixelSet ( fx , scanY , 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 + + )
{
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 ;
double eMin = ( vy [ i ] < vy [ ni ] ) ? vy [ i ] : vy [ ni ] , eMax = ( vy [ i ] > vy [ ni ] ) ? vy [ i ] : vy [ ni ] ;
if ( cy < eMin | | cy > eMax | | MathAbs ( vy [ ni ] - vy [ i ] ) < 1e-12 ) continue ;
double t = ( cy - vy [ i ] ) / ( vy [ ni ] - vy [ i ] ) ;
if ( t < 0.0 | | t > 1.0 ) continue ;
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 + + )
{
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 + + ;
}
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 ) ;
}
//+------------------------------------------------------------------+
//| Draw a line between two points using Bresenham's algorithm |
//+------------------------------------------------------------------+
void CCanvasPrimitives : : DrawBresenhamLine ( CCanvas & canvas , int x0 , int y0 , int x1 , int y1 , uint argb )
{
//--- Compute absolute deltas and step directions
int dx = MathAbs ( x1 - x0 ) , dy = MathAbs ( y1 - y0 ) ;
int sx = ( x0 < x1 ) ? 1 : -1 , sy = ( y0 < y1 ) ? 1 : -1 , err = dx - dy ;
int w = canvas . Width ( ) , h = canvas . Height ( ) ;
//--- Iterate pixel by pixel until the endpoint is reached
while ( true )
{
//--- Blend the current pixel onto the canvas if within bounds
if ( x0 > = 0 & & x0 < w & & y0 > = 0 & & y0 < h ) BlendPixelSet ( canvas , x0 , y0 , argb ) ;
//--- Stop when the endpoint is reached
if ( x0 = = x1 & & y0 = = y1 ) break ;
int e2 = 2 * err ;
if ( e2 > - dy ) { err - = dy ; x0 + = sx ; }
if ( e2 < dx ) { err + = dx ; y0 + = sy ; }
}
}
//+------------------------------------------------------------------+
//| 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 ( ) ;
//--- Toggle between dark and light theme and reapply colours
void ToggleTheme ( ) ;
} ;
//+------------------------------------------------------------------+
//| 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 . buttonHoverBackground = C ' 30 , 100 , 200 ' ; // Blue hover background
m_themeColors . buttonActiveBackground = C ' 41 , 98 , 255 ' ; // Bright blue active background
m_themeColors . buttonIconColor = C ' 220 , 225 , 235 ' ; // Near-white icon colour
m_themeColors . buttonIconActiveColor = clrWhite ; // Pure white active icon
m_themeColors . flyoutBackground = C ' 36 , 41 , 54 ' ; // Dark flyout background
m_themeColors . flyoutBorder = C ' 200 , 210 , 225 ' ; // Light flyout border
m_themeColors . flyoutItemHoverBackground = C ' 30 , 100 , 200 ' ; // Blue flyout row hover
m_themeColors . flyoutTextColor = C ' 200 , 210 , 225 ' ; // Light flyout text
m_themeColors . flyoutTextActiveColor = clrWhite ; // White active flyout text
m_themeColors . flyoutTitleColor = C ' 90 , 105 , 130 ' ; // Muted blue-grey title
m_themeColors . gripDotsColor = C ' 90 , 100 , 120 ' ; // Muted slate grip dots
m_themeColors . closeButtonHoverColor = C ' 235 , 55 , 55 ' ; // Red close button hover
m_themeColors . themeButtonHoverColor = C ' 255 , 200 , 50 ' ; // Yellow theme button hover
m_themeColors . separatorColor = C ' 44 , 50 , 64 ' ; // Dark separator line
m_themeColors . accentBarColor = C ' 41 , 98 , 255 ' ; // Bright blue accent bar
m_themeColors . scrollArrowColor = C ' 120 , 130 , 150 ' ; // Muted scroll thumb
m_themeColors . scrollArrowHoverColor = clrWhite ; // White scroll thumb hover
}
else
{
//--- Apply light theme colour assignments
m_themeColors . sidebarBackground = clrWhite ; // White background
m_themeColors . sidebarBorder = C ' 30 , 35 , 45 ' ; // Dark border
m_themeColors . buttonHoverBackground = C ' 30 , 100 , 200 ' ; // Blue hover background
m_themeColors . buttonActiveBackground = C ' 41 , 98 , 255 ' ; // Bright blue active background
m_themeColors . buttonIconColor = C ' 40 , 45 , 58 ' ; // Dark icon colour
m_themeColors . buttonIconActiveColor = clrWhite ; // White active icon
m_themeColors . flyoutBackground = clrWhite ; // White flyout background
m_themeColors . flyoutBorder = C ' 30 , 35 , 45 ' ; // Dark flyout border
m_themeColors . flyoutItemHoverBackground = C ' 30 , 100 , 200 ' ; // Blue flyout row hover
m_themeColors . flyoutTextColor = C ' 40 , 45 , 58 ' ; // Dark flyout text
m_themeColors . flyoutTextActiveColor = clrWhite ; // White active flyout text
m_themeColors . flyoutTitleColor = C ' 130 , 140 , 160 ' ; // Muted grey title
m_themeColors . gripDotsColor = C ' 160 , 170 , 185 ' ; // Light grey grip dots
m_themeColors . closeButtonHoverColor = C ' 210 , 35 , 35 ' ; // Red close button hover
m_themeColors . themeButtonHoverColor = C ' 150 , 100 , 0 ' ; // Amber theme button hover
m_themeColors . separatorColor = C ' 210 , 215 , 225 ' ; // Light separator line
m_themeColors . accentBarColor = C ' 41 , 98 , 255 ' ; // Bright blue accent bar
m_themeColors . scrollArrowColor = C ' 120 , 130 , 145 ' ; // Muted scroll thumb
m_themeColors . scrollArrowHoverColor = C ' 40 , 45 , 58 ' ; // Dark scroll thumb hover
}
}
//+------------------------------------------------------------------+
//| Toggle between dark and light theme and reapply colours |
//+------------------------------------------------------------------+
void CThemeManager : : ToggleTheme ( )
{
//--- Flip the active theme flag and reapply colour assignments
m_isDarkTheme = ! m_isDarkTheme ;
ApplyTheme ( ) ;
}
//+------------------------------------------------------------------+
//| CLASS 3 — Register all tool and category definitions |
//+------------------------------------------------------------------+
class CToolRegistry : public CThemeManager
{
protected :
CategoryDefinition m_categories [ CAT_COUNT ] ; // Array of all category definitions
protected :
//--- Populate all categories and their associated tool lists
void InitAllCategoriesAndTools ( ) ;
//--- Append a single tool entry to the given category tool array
void AddTool ( ToolDefinition & arr [ ] , TOOL_TYPE type , string label , string font , uchar code , string tooltip ) ;
//--- Return the category that owns the given active tool type
ENUM_CATEGORY GetCategoryForActiveTool ( TOOL_TYPE activeTool ) ;
//--- Return the number of chart clicks required to place the given tool
int GetRequiredClickCount ( TOOL_TYPE toolType ) ;
//--- Return the display label string for the given tool type
string GetToolLabel ( TOOL_TYPE toolType ) ;
} ;
//+------------------------------------------------------------------+
//| Append a single tool entry to a category tool array |
//+------------------------------------------------------------------+
void CToolRegistry : : AddTool ( ToolDefinition & arr [ ] , TOOL_TYPE type , string label , string font , uchar code , string tooltip )
{
//--- Expand the array by one slot to accommodate the new tool
int sz = ArraySize ( arr ) ;
ArrayResize ( arr , sz + 1 ) ;
//--- Populate all fields of the new tool definition
arr [ sz ] . toolType = type ;
arr [ sz ] . toolLabel = label ;
arr [ sz ] . iconFontName = font ;
arr [ sz ] . iconCharCode = code ;
arr [ sz ] . tooltipText = tooltip ;
}
//+------------------------------------------------------------------+
//| Populate all categories and their associated tool lists |
//+------------------------------------------------------------------+
void CToolRegistry : : InitAllCategoriesAndTools ( )
{
//--- Assign Cursors category definition and reset its tool array
m_categories [ CAT_CURSORS ] . categoryLabel = " Cursors " ;
m_categories [ CAT_CURSORS ] . iconFontName = ICON_CATEGORY_CURSORS . fontName ;
m_categories [ CAT_CURSORS ] . iconCharCode = ICON_CATEGORY_CURSORS . charCode ;
ArrayResize ( m_categories [ CAT_CURSORS ] . tools , 0 ) ;
//--- Add pointer and crosshair tools to Cursors
AddTool ( m_categories [ CAT_CURSORS ] . tools , TOOL_POINTER , " Pointer " , ICON_TOOL_POINTER . fontName , ICON_TOOL_POINTER . charCode , " Default Pointer " ) ;
AddTool ( m_categories [ CAT_CURSORS ] . tools , TOOL_CROSSHAIR , " Crosshair " , ICON_TOOL_CROSSHAIR . fontName , ICON_TOOL_CROSSHAIR . charCode , " Crosshair / Measure " ) ;
//--- Assign Lines category definition and reset its tool array
m_categories [ CAT_LINES ] . categoryLabel = " Lines " ;
m_categories [ CAT_LINES ] . iconFontName = ICON_CATEGORY_LINES . fontName ;
m_categories [ CAT_LINES ] . iconCharCode = ICON_CATEGORY_LINES . charCode ;
ArrayResize ( m_categories [ CAT_LINES ] . tools , 0 ) ;
//--- Add all line drawing tools to Lines
AddTool ( m_categories [ CAT_LINES ] . tools , TOOL_TRENDLINE , " Trend Line " , ICON_TOOL_TRENDLINE . fontName , ICON_TOOL_TRENDLINE . charCode , " Trend Line " ) ;
AddTool ( m_categories [ CAT_LINES ] . tools , TOOL_HLINE , " Horizontal " , ICON_TOOL_HLINE . fontName , ICON_TOOL_HLINE . charCode , " Horizontal Line " ) ;
AddTool ( m_categories [ CAT_LINES ] . tools , TOOL_VLINE , " Vertical " , ICON_TOOL_VLINE . fontName , ICON_TOOL_VLINE . charCode , " Vertical Line " ) ;
AddTool ( m_categories [ CAT_LINES ] . tools , TOOL_RAY , " Ray " , ICON_TOOL_RAY . fontName , ICON_TOOL_RAY . charCode , " Ray Line " ) ;
AddTool ( m_categories [ CAT_LINES ] . tools , TOOL_EXTENDED_LINE , " Extended " , ICON_TOOL_EXTENDED_LINE . fontName , ICON_TOOL_EXTENDED_LINE . charCode , " Extended Line " ) ;
AddTool ( m_categories [ CAT_LINES ] . tools , TOOL_INFO_LINE , " Info Line " , ICON_TOOL_INFO_LINE . fontName , ICON_TOOL_INFO_LINE . charCode , " Info / Measure Line " ) ;
//--- Assign Channels category definition and reset its tool array
m_categories [ CAT_CHANNELS ] . categoryLabel = " Channels " ;
m_categories [ CAT_CHANNELS ] . iconFontName = ICON_CATEGORY_CHANNELS . fontName ;
m_categories [ CAT_CHANNELS ] . iconCharCode = ICON_CATEGORY_CHANNELS . charCode ;
ArrayResize ( m_categories [ CAT_CHANNELS ] . tools , 0 ) ;
//--- Add all channel drawing tools to Channels
AddTool ( m_categories [ CAT_CHANNELS ] . tools , TOOL_PARALLEL_CHANNEL , " Parallel Channel " , ICON_TOOL_PARALLEL_CH . fontName , ICON_TOOL_PARALLEL_CH . charCode , " Parallel Channel " ) ;
AddTool ( m_categories [ CAT_CHANNELS ] . tools , TOOL_REGRESSION_CHANNEL , " Regression " , ICON_TOOL_REGRESSION_CH . fontName , ICON_TOOL_REGRESSION_CH . charCode , " Regression Channel " ) ;
AddTool ( m_categories [ CAT_CHANNELS ] . tools , TOOL_STDDEV_CHANNEL , " Std Deviation " , ICON_TOOL_STDDEV_CH . fontName , ICON_TOOL_STDDEV_CH . charCode , " Standard Deviation Channel " ) ;
//--- Assign Pitchfork category definition and reset its tool array
m_categories [ CAT_PITCHFORK ] . categoryLabel = " Pitchfork " ;
m_categories [ CAT_PITCHFORK ] . iconFontName = ICON_CATEGORY_PITCHFORK . fontName ;
m_categories [ CAT_PITCHFORK ] . iconCharCode = ICON_CATEGORY_PITCHFORK . charCode ;
ArrayResize ( m_categories [ CAT_PITCHFORK ] . tools , 0 ) ;
//--- Add all pitchfork drawing tools to Pitchfork
AddTool ( m_categories [ CAT_PITCHFORK ] . tools , TOOL_PITCHFORK , " Andrew's Fork " , ICON_TOOL_PITCHFORK . fontName , ICON_TOOL_PITCHFORK . charCode , " Andrew's Pitchfork " ) ;
AddTool ( m_categories [ CAT_PITCHFORK ] . tools , TOOL_SCHIFF_PITCHFORK , " Schiff Fork " , ICON_TOOL_SCHIFF . fontName , ICON_TOOL_SCHIFF . charCode , " Schiff Pitchfork " ) ;
AddTool ( m_categories [ CAT_PITCHFORK ] . tools , TOOL_MOD_SCHIFF , " Mod. Schiff " , ICON_TOOL_MOD_SCHIFF . fontName , ICON_TOOL_MOD_SCHIFF . charCode , " Modified Schiff Pitchfork " ) ;
//--- Assign Gann category definition and reset its tool array
m_categories [ CAT_GANN ] . categoryLabel = " Gann " ;
m_categories [ CAT_GANN ] . iconFontName = ICON_CATEGORY_GANN . fontName ;
m_categories [ CAT_GANN ] . iconCharCode = ICON_CATEGORY_GANN . charCode ;
ArrayResize ( m_categories [ CAT_GANN ] . tools , 0 ) ;
//--- Add all Gann drawing tools to Gann
AddTool ( m_categories [ CAT_GANN ] . tools , TOOL_GANN_LINE , " Gann Line " , ICON_TOOL_GANN_LINE . fontName , ICON_TOOL_GANN_LINE . charCode , " Gann Line " ) ;
AddTool ( m_categories [ CAT_GANN ] . tools , TOOL_GANN_FAN , " Gann Fan " , ICON_TOOL_GANN_FAN . fontName , ICON_TOOL_GANN_FAN . charCode , " Gann Fan " ) ;
AddTool ( m_categories [ CAT_GANN ] . tools , TOOL_GANN_GRID , " Gann Grid " , ICON_TOOL_GANN_GRID . fontName , ICON_TOOL_GANN_GRID . charCode , " Gann Grid " ) ;
//--- Assign Fibonacci category definition and reset its tool array
m_categories [ CAT_FIBONACCI ] . categoryLabel = " Fibonacci " ;
m_categories [ CAT_FIBONACCI ] . iconFontName = ICON_CATEGORY_FIBONACCI . fontName ;
m_categories [ CAT_FIBONACCI ] . iconCharCode = ICON_CATEGORY_FIBONACCI . charCode ;
ArrayResize ( m_categories [ CAT_FIBONACCI ] . tools , 0 ) ;
//--- Add all Fibonacci drawing tools to Fibonacci
AddTool ( m_categories [ CAT_FIBONACCI ] . tools , TOOL_FIBO_RETRACEMENT , " Retracement " , ICON_TOOL_FIBO_RET . fontName , ICON_TOOL_FIBO_RET . charCode , " Fibonacci Retracement " ) ;
AddTool ( m_categories [ CAT_FIBONACCI ] . tools , TOOL_FIBO_EXPANSION , " Expansion " , ICON_TOOL_FIBO_EXP . fontName , ICON_TOOL_FIBO_EXP . charCode , " Fibonacci Expansion " ) ;
AddTool ( m_categories [ CAT_FIBONACCI ] . tools , TOOL_FIBO_CHANNEL , " Fib Channel " , ICON_TOOL_FIBO_CH . fontName , ICON_TOOL_FIBO_CH . charCode , " Fibonacci Channel " ) ;
AddTool ( m_categories [ CAT_FIBONACCI ] . tools , TOOL_FIBO_TIMEZONES , " Time Zones " , ICON_TOOL_FIBO_TZ . fontName , ICON_TOOL_FIBO_TZ . charCode , " Fibonacci Time Zones " ) ;
AddTool ( m_categories [ CAT_FIBONACCI ] . tools , TOOL_FIBO_FAN , " Fib Fan " , ICON_TOOL_FIBO_FAN . fontName , ICON_TOOL_FIBO_FAN . charCode , " Fibonacci Fan " ) ;
AddTool ( m_categories [ CAT_FIBONACCI ] . tools , TOOL_FIBO_ARCS , " Fib Arcs " , ICON_TOOL_FIBO_ARCS . fontName , ICON_TOOL_FIBO_ARCS . charCode , " Fibonacci Arcs " ) ;
//--- Assign Shapes category definition and reset its tool array
m_categories [ CAT_SHAPES ] . categoryLabel = " Shapes " ;
m_categories [ CAT_SHAPES ] . iconFontName = ICON_CATEGORY_SHAPES . fontName ;
m_categories [ CAT_SHAPES ] . iconCharCode = ICON_CATEGORY_SHAPES . charCode ;
ArrayResize ( m_categories [ CAT_SHAPES ] . tools , 0 ) ;
//--- Add all shape drawing tools to Shapes
AddTool ( m_categories [ CAT_SHAPES ] . tools , TOOL_RECTANGLE , " Rectangle " , ICON_TOOL_RECTANGLE . fontName , ICON_TOOL_RECTANGLE . charCode , " Rectangle " ) ;
AddTool ( m_categories [ CAT_SHAPES ] . tools , TOOL_TRIANGLE , " Triangle " , ICON_TOOL_TRIANGLE . fontName , ICON_TOOL_TRIANGLE . charCode , " Triangle " ) ;
AddTool ( m_categories [ CAT_SHAPES ] . tools , TOOL_ELLIPSE , " Ellipse " , ICON_TOOL_ELLIPSE . fontName , ICON_TOOL_ELLIPSE . charCode , " Ellipse " ) ;
//--- Assign Annotations category definition and reset its tool array
m_categories [ CAT_ANNOTATIONS ] . categoryLabel = " Annotate " ;
m_categories [ CAT_ANNOTATIONS ] . iconFontName = ICON_CATEGORY_ANNOTATIONS . fontName ;
m_categories [ CAT_ANNOTATIONS ] . iconCharCode = ICON_CATEGORY_ANNOTATIONS . charCode ;
ArrayResize ( m_categories [ CAT_ANNOTATIONS ] . tools , 0 ) ;
//--- Add all annotation tools to Annotations
AddTool ( m_categories [ CAT_ANNOTATIONS ] . tools , TOOL_TEXT , " Text " , ICON_TOOL_TEXT . fontName , ICON_TOOL_TEXT . charCode , " Text Label " ) ;
AddTool ( m_categories [ CAT_ANNOTATIONS ] . tools , TOOL_ARROW_UP , " Arrow Up " , ICON_TOOL_ARROW_UP . fontName , ICON_TOOL_ARROW_UP . charCode , " Arrow Up " ) ;
AddTool ( m_categories [ CAT_ANNOTATIONS ] . tools , TOOL_ARROW_DOWN , " Arrow Down " , ICON_TOOL_ARROW_DOWN . fontName , ICON_TOOL_ARROW_DOWN . charCode , " Arrow Down " ) ;
AddTool ( m_categories [ CAT_ANNOTATIONS ] . tools , TOOL_THUMB_UP , " Thumb Up " , ICON_TOOL_THUMB_UP . fontName , ICON_TOOL_THUMB_UP . charCode , " Thumbs Up " ) ;
AddTool ( m_categories [ CAT_ANNOTATIONS ] . tools , TOOL_THUMB_DOWN , " Thumb Down " , ICON_TOOL_THUMB_DOWN . fontName , ICON_TOOL_THUMB_DOWN . charCode , " Thumbs Down " ) ;
AddTool ( m_categories [ CAT_ANNOTATIONS ] . tools , TOOL_PRICE_LABEL , " Price Label " , ICON_TOOL_PRICE_LABEL . fontName , ICON_TOOL_PRICE_LABEL . charCode , " Left Price Label " ) ;
AddTool ( m_categories [ CAT_ANNOTATIONS ] . tools , TOOL_STOP_SIGN , " Stop Sign " , ICON_TOOL_STOP_SIGN . fontName , ICON_TOOL_STOP_SIGN . charCode , " Stop Sign " ) ;
AddTool ( m_categories [ CAT_ANNOTATIONS ] . tools , TOOL_CHECK_MARK , " Check Mark " , ICON_TOOL_CHECK_MARK . fontName , ICON_TOOL_CHECK_MARK . charCode , " Check Mark " ) ;
}
//+------------------------------------------------------------------+
//| Return the category that owns the given active tool type |
//+------------------------------------------------------------------+
ENUM_CATEGORY CToolRegistry : : GetCategoryForActiveTool ( TOOL_TYPE activeTool )
{
//--- Return no category for inactive or pointer tool states
if ( activeTool = = TOOL_NONE | | activeTool = = TOOL_POINTER ) return CAT_NONE ;
//--- Search all categories and their tool lists for a match
for ( int c = 0 ; c < CAT_COUNT ; c + + )
for ( int t = 0 ; t < ArraySize ( m_categories [ c ] . tools ) ; t + + )
if ( m_categories [ c ] . tools [ t ] . toolType = = activeTool ) return ( ENUM_CATEGORY ) c ;
return CAT_NONE ;
}
//+------------------------------------------------------------------+
//| Return click count required to place the given tool |
//+------------------------------------------------------------------+
int CToolRegistry : : GetRequiredClickCount ( TOOL_TYPE toolType )
{
switch ( toolType )
{
//--- Cursor tools require no chart clicks
case TOOL_POINTER : case TOOL_CROSSHAIR :
return 0 ;
//--- Single-click tools are placed with one chart interaction
case TOOL_HLINE : case TOOL_VLINE : case TOOL_TEXT : case TOOL_ARROW_UP : case TOOL_ARROW_DOWN :
case TOOL_THUMB_UP : case TOOL_THUMB_DOWN : case TOOL_PRICE_LABEL : case TOOL_STOP_SIGN :
case TOOL_CHECK_MARK : case TOOL_FIBO_TIMEZONES :
return 1 ;
//--- Two-click tools require a start and end point
case TOOL_TRENDLINE : case TOOL_RAY : case TOOL_EXTENDED_LINE : case TOOL_INFO_LINE :
case TOOL_RECTANGLE : case TOOL_TRIANGLE : case TOOL_ELLIPSE : case TOOL_FIBO_RETRACEMENT :
case TOOL_FIBO_EXPANSION : case TOOL_FIBO_FAN : case TOOL_FIBO_ARCS : case TOOL_GANN_LINE :
case TOOL_GANN_FAN : case TOOL_GANN_GRID : case TOOL_REGRESSION_CHANNEL : case TOOL_STDDEV_CHANNEL :
return 2 ;
//--- Three-click tools require three anchor points
case TOOL_PARALLEL_CHANNEL : case TOOL_FIBO_CHANNEL : case TOOL_PITCHFORK :
case TOOL_SCHIFF_PITCHFORK : case TOOL_MOD_SCHIFF :
return 3 ;
//--- Default to single click for unrecognised tool types
default : return 1 ;
}
}
//+------------------------------------------------------------------+
//| Return the display label string for the given tool type |
//+------------------------------------------------------------------+
string CToolRegistry : : GetToolLabel ( TOOL_TYPE toolType )
{
//--- Search all categories and tool lists for a label match
for ( int c = 0 ; c < CAT_COUNT ; c + + )
for ( int t = 0 ; t < ArraySize ( m_categories [ c ] . tools ) ; t + + )
if ( m_categories [ c ] . tools [ t ] . toolType = = toolType ) return m_categories [ c ] . tools [ t ] . toolLabel ;
return " None " ;
}
//+------------------------------------------------------------------+
//| CLASS 4 — Create, destroy, and resize all canvas layers |
//+------------------------------------------------------------------+
class CCanvasLayer : public CToolRegistry
{
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
CCanvas m_canvasFlyout ; // Final display-resolution flyout canvas
CCanvas m_canvasFlyoutHighRes ; // High-resolution flyout canvas for supersampling
CCanvas m_canvasReticle ; // Crosshair reticle tick-mark canvas
CCanvas m_canvasMagnifier ; // Circular magnifier lens canvas
CCanvas m_canvasCrossVertical ; // Crosshair vertical line canvas (1 × chartH)
CCanvas m_canvasCrossHorizontal ; // Crosshair horizontal line canvas (chartW × 1)
CCanvas m_canvasCrossPriceLabel ; // Crosshair price axis label canvas
CCanvas m_canvasCrossTimeLabel ; // Crosshair time axis label canvas
CCanvas m_canvasMeasureVertical ; // Measure mode vertical anchor line canvas
CCanvas m_canvasMeasureHorizontal ; // Measure mode horizontal anchor line canvas
CCanvas m_canvasMeasurePriceLabel ; // Measure mode price axis label canvas
CCanvas m_canvasMeasureTimeLabel ; // Measure mode time axis label canvas
CCanvas m_canvasMeasureDiagonalLine ; // Measure mode diagonal line canvas (chartW × chartH)
string m_nameSidebar ; // Object name of the sidebar bitmap label
string m_nameFlyout ; // Object name of the flyout bitmap label
string m_nameReticle ; // Object name of the reticle bitmap label
string m_nameMagnifier ; // Object name of the magnifier bitmap label
string m_nameCrossVertical ; // Object name of the crosshair vertical bitmap label
string m_nameCrossHorizontal ; // Object name of the crosshair horizontal bitmap label
string m_nameCrossPriceLabel ; // Object name of the crosshair price label bitmap
string m_nameCrossTimeLabel ; // Object name of the crosshair time label bitmap
string m_nameMeasureVertical ; // Object name of the measure vertical bitmap label
string m_nameMeasureHorizontal ; // Object name of the measure horizontal bitmap label
string m_nameMeasurePriceLabel ; // Object name of the measure price label bitmap
string m_nameMeasureTimeLabel ; // Object name of the measure time label bitmap
string m_nameMeasureDiagonalLine ; // Object name of the measure diagonal line bitmap
protected :
//--- Create all canvas objects at the given sidebar 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 ) ;
//--- Fill the crosshair vertical line canvas with the foreground colour
void DrawCrossVerticalLinePixels ( int chartH ) ;
//--- Fill the crosshair horizontal line canvas with the foreground colour
void DrawCrossHorizontalLinePixels ( int chartW ) ;
//--- Fill the measure vertical line canvas with the foreground colour at reduced opacity
void DrawMeasureVerticalLinePixels ( int chartH ) ;
//--- Fill the measure horizontal line canvas with the foreground colour at reduced opacity
void DrawMeasureHorizontalLinePixels ( int chartW ) ;
} ;
//+------------------------------------------------------------------+
//| Create all canvas objects at the given sidebar dimensions |
//+------------------------------------------------------------------+
bool CCanvasLayer : : CreateAllCanvases ( int w , int h )
{
//--- Read current chart dimensions for full-width/height canvas sizing
int chartW = ( int ) ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS ) ;
int chartH = ( int ) ChartGetInteger ( 0 , CHART_HEIGHT_IN_PIXELS ) ;
//--- 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 ( " 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 ( " Failed to create sidebar HR canvas " ) ; return false ; }
//--- Create the display-resolution flyout bitmap label canvas
if ( ! m_canvasFlyout . CreateBitmapLabel ( 0 , 0 , m_nameFlyout , 0 , 0 , 200 , 200 , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create flyout canvas " ) ; return false ; }
//--- Create the high-resolution flyout canvas for supersampled drawing
if ( ! m_canvasFlyoutHighRes . Create ( " ToolsPalette_FlyoutHR " , 200 * m_supersampleFactor , 200 * m_supersampleFactor , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create flyout HR canvas " ) ; return false ; }
//--- Create the reticle canvas sized to fit the tick-mark geometry
int reticleSize = 2 * ( ReticleOffset + ReticleTickLen / 2 ) + 6 ;
if ( ! m_canvasReticle . CreateBitmapLabel ( 0 , 0 , m_nameReticle , 0 , 0 , reticleSize , reticleSize , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create reticle canvas " ) ; return false ; }
//--- Hide the reticle until the crosshair tool is active
ObjectSetInteger ( 0 , m_nameReticle , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
ObjectSetInteger ( 0 , m_nameReticle , OBJPROP_ZORDER , 90 ) ;
//--- Create the magnifier lens canvas
if ( ! m_canvasMagnifier . CreateBitmapLabel ( 0 , 0 , m_nameMagnifier , 0 , 0 , MagDiameter , MagDiameter , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create magnifier canvas " ) ; return false ; }
//--- Hide the magnifier until the crosshair tool is active
ObjectSetInteger ( 0 , m_nameMagnifier , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
ObjectSetInteger ( 0 , m_nameMagnifier , OBJPROP_ZORDER , 95 ) ;
//--- Create the crosshair vertical line canvas (1 pixel wide, full chart height)
if ( ! m_canvasCrossVertical . CreateBitmapLabel ( 0 , 0 , m_nameCrossVertical , 0 , 0 , 1 , chartH , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create cross vertical canvas " ) ; return false ; }
ObjectSetInteger ( 0 , m_nameCrossVertical , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
ObjectSetInteger ( 0 , m_nameCrossVertical , OBJPROP_ZORDER , 80 ) ;
//--- Pre-fill the vertical line pixels with the chart foreground colour
DrawCrossVerticalLinePixels ( chartH ) ;
//--- Create the crosshair horizontal line canvas (full chart width, 1 pixel tall)
if ( ! m_canvasCrossHorizontal . CreateBitmapLabel ( 0 , 0 , m_nameCrossHorizontal , 0 , 0 , chartW , 1 , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create cross horizontal canvas " ) ; return false ; }
ObjectSetInteger ( 0 , m_nameCrossHorizontal , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
ObjectSetInteger ( 0 , m_nameCrossHorizontal , OBJPROP_ZORDER , 80 ) ;
//--- Pre-fill the horizontal line pixels with the chart foreground colour
DrawCrossHorizontalLinePixels ( chartW ) ;
//--- Create the crosshair price axis label canvas
if ( ! m_canvasCrossPriceLabel . CreateBitmapLabel ( 0 , 0 , m_nameCrossPriceLabel , 0 , 0 , 80 , 18 , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create cross price label canvas " ) ; return false ; }
ObjectSetInteger ( 0 , m_nameCrossPriceLabel , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
ObjectSetInteger ( 0 , m_nameCrossPriceLabel , OBJPROP_ZORDER , 85 ) ;
//--- Create the crosshair time axis label canvas
if ( ! m_canvasCrossTimeLabel . CreateBitmapLabel ( 0 , 0 , m_nameCrossTimeLabel , 0 , 0 , 140 , 18 , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create cross time label canvas " ) ; return false ; }
ObjectSetInteger ( 0 , m_nameCrossTimeLabel , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
ObjectSetInteger ( 0 , m_nameCrossTimeLabel , OBJPROP_ZORDER , 85 ) ;
//--- Create the measure mode vertical anchor line canvas
if ( ! m_canvasMeasureVertical . CreateBitmapLabel ( 0 , 0 , m_nameMeasureVertical , 0 , 0 , 1 , chartH , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create measure vertical canvas " ) ; return false ; }
ObjectSetInteger ( 0 , m_nameMeasureVertical , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
ObjectSetInteger ( 0 , m_nameMeasureVertical , OBJPROP_ZORDER , 79 ) ;
//--- Pre-fill the measure vertical pixels at reduced opacity
DrawMeasureVerticalLinePixels ( chartH ) ;
//--- Create the measure mode horizontal anchor line canvas
if ( ! m_canvasMeasureHorizontal . CreateBitmapLabel ( 0 , 0 , m_nameMeasureHorizontal , 0 , 0 , chartW , 1 , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create measure horizontal canvas " ) ; return false ; }
ObjectSetInteger ( 0 , m_nameMeasureHorizontal , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
ObjectSetInteger ( 0 , m_nameMeasureHorizontal , OBJPROP_ZORDER , 79 ) ;
//--- Pre-fill the measure horizontal pixels at reduced opacity
DrawMeasureHorizontalLinePixels ( chartW ) ;
//--- Create the measure mode price axis label canvas
if ( ! m_canvasMeasurePriceLabel . CreateBitmapLabel ( 0 , 0 , m_nameMeasurePriceLabel , 0 , 0 , 80 , 18 , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create measure price label canvas " ) ; return false ; }
ObjectSetInteger ( 0 , m_nameMeasurePriceLabel , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
ObjectSetInteger ( 0 , m_nameMeasurePriceLabel , OBJPROP_ZORDER , 84 ) ;
//--- Create the measure mode time axis label canvas
if ( ! m_canvasMeasureTimeLabel . CreateBitmapLabel ( 0 , 0 , m_nameMeasureTimeLabel , 0 , 0 , 140 , 18 , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create measure time label canvas " ) ; return false ; }
ObjectSetInteger ( 0 , m_nameMeasureTimeLabel , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
ObjectSetInteger ( 0 , m_nameMeasureTimeLabel , OBJPROP_ZORDER , 84 ) ;
//--- Create the measure mode diagonal line canvas (full chart size)
if ( ! m_canvasMeasureDiagonalLine . CreateBitmapLabel ( 0 , 0 , m_nameMeasureDiagonalLine , 0 , 0 , chartW , chartH , COLOR_FORMAT_ARGB_NORMALIZE ) )
{ Print ( " Failed to create measure diagonal canvas " ) ; return false ; }
ObjectSetInteger ( 0 , m_nameMeasureDiagonalLine , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
ObjectSetInteger ( 0 , m_nameMeasureDiagonalLine , OBJPROP_ZORDER , 78 ) ;
//--- Clear the diagonal canvas to fully transparent and flush
m_canvasMeasureDiagonalLine . Erase ( 0x00000000 ) ;
m_canvasMeasureDiagonalLine . Update ( ) ;
return true ;
}
//+------------------------------------------------------------------+
//| Destroy all canvas objects and remove chart objects |
//+------------------------------------------------------------------+
void CCanvasLayer : : DestroyAllCanvases ( )
{
//--- ALL canvas objects created with CreateBitmapLabel need explicit ObjectDelete
//--- after Destroy() — without it they stay as ghost objects on the chart and
//--- CreateBitmapLabel fails silently on the next Init() call (parameter change restart)
m_canvasSidebar . Destroy ( ) ; ObjectDelete ( 0 , m_nameSidebar ) ;
m_canvasSidebarHighRes . Destroy ( ) ;
m_canvasFlyout . Destroy ( ) ; ObjectDelete ( 0 , m_nameFlyout ) ;
m_canvasFlyoutHighRes . Destroy ( ) ;
m_canvasReticle . Destroy ( ) ; ObjectDelete ( 0 , m_nameReticle ) ;
m_canvasMagnifier . Destroy ( ) ; ObjectDelete ( 0 , m_nameMagnifier ) ;
m_canvasCrossVertical . Destroy ( ) ; ObjectDelete ( 0 , m_nameCrossVertical ) ;
m_canvasCrossHorizontal . Destroy ( ) ; ObjectDelete ( 0 , m_nameCrossHorizontal ) ;
m_canvasCrossPriceLabel . Destroy ( ) ; ObjectDelete ( 0 , m_nameCrossPriceLabel ) ;
m_canvasCrossTimeLabel . Destroy ( ) ; ObjectDelete ( 0 , m_nameCrossTimeLabel ) ;
m_canvasMeasureVertical . Destroy ( ) ; ObjectDelete ( 0 , m_nameMeasureVertical ) ;
m_canvasMeasureHorizontal . Destroy ( ) ; ObjectDelete ( 0 , m_nameMeasureHorizontal ) ;
m_canvasMeasurePriceLabel . Destroy ( ) ; ObjectDelete ( 0 , m_nameMeasurePriceLabel ) ;
m_canvasMeasureTimeLabel . Destroy ( ) ; ObjectDelete ( 0 , m_nameMeasureTimeLabel ) ;
m_canvasMeasureDiagonalLine . Destroy ( ) ; ObjectDelete ( 0 , m_nameMeasureDiagonalLine ) ;
}
//+------------------------------------------------------------------+
//| 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 ) ;
}
//+------------------------------------------------------------------+
//| Fill the crosshair vertical line canvas with foreground colour |
//+------------------------------------------------------------------+
void CCanvasLayer : : DrawCrossVerticalLinePixels ( int chartH )
{
//--- Clear the canvas to fully transparent
m_canvasCrossVertical . Erase ( 0x00000000 ) ;
//--- Pack the chart foreground colour at full opacity
uint col = ColorToARGB ( ( color ) ChartGetInteger ( 0 , CHART_COLOR_FOREGROUND ) , 255 ) ;
//--- Set every pixel in the single-column canvas to the foreground colour
for ( int y = 0 ; y < chartH ; y + + ) m_canvasCrossVertical . PixelSet ( 0 , y , col ) ;
m_canvasCrossVertical . Update ( ) ;
}
//+------------------------------------------------------------------+
//| Fill the crosshair horizontal line canvas with foreground colour |
//+------------------------------------------------------------------+
void CCanvasLayer : : DrawCrossHorizontalLinePixels ( int chartW )
{
//--- Clear the canvas to fully transparent
m_canvasCrossHorizontal . Erase ( 0x00000000 ) ;
//--- Pack the chart foreground colour at full opacity
uint col = ColorToARGB ( ( color ) ChartGetInteger ( 0 , CHART_COLOR_FOREGROUND ) , 255 ) ;
//--- Set every pixel in the single-row canvas to the foreground colour
for ( int x = 0 ; x < chartW ; x + + ) m_canvasCrossHorizontal . PixelSet ( x , 0 , col ) ;
m_canvasCrossHorizontal . Update ( ) ;
}
//+------------------------------------------------------------------+
//| Fill measure vertical line canvas at reduced opacity |
//+------------------------------------------------------------------+
void CCanvasLayer : : DrawMeasureVerticalLinePixels ( int chartH )
{
//--- Clear the canvas to fully transparent
m_canvasMeasureVertical . Erase ( 0x00000000 ) ;
//--- Pack the chart foreground colour at 200/255 opacity for visual distinction
uint col = ColorToARGB ( ( color ) ChartGetInteger ( 0 , CHART_COLOR_FOREGROUND ) , 200 ) ;
//--- Set every pixel in the single-column canvas
for ( int y = 0 ; y < chartH ; y + + ) m_canvasMeasureVertical . PixelSet ( 0 , y , col ) ;
m_canvasMeasureVertical . Update ( ) ;
}
//+------------------------------------------------------------------+
//| Fill measure horizontal line canvas at reduced opacity |
//+------------------------------------------------------------------+
void CCanvasLayer : : DrawMeasureHorizontalLinePixels ( int chartW )
{
//--- Clear the canvas to fully transparent
m_canvasMeasureHorizontal . Erase ( 0x00000000 ) ;
//--- Pack the chart foreground colour at 200/255 opacity for visual distinction
uint col = ColorToARGB ( ( color ) ChartGetInteger ( 0 , CHART_COLOR_FOREGROUND ) , 200 ) ;
//--- Set every pixel in the single-row canvas
for ( int x = 0 ; x < chartW ; x + + ) m_canvasMeasureHorizontal . PixelSet ( x , 0 , col ) ;
m_canvasMeasureHorizontal . Update ( ) ;
}
//+------------------------------------------------------------------+
//| 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
int m_sidebarScrollPixels ; // Current vertical scroll offset in pixels
int m_sidebarScrollThumbHeight ; // Height of the sidebar scroll thumb pill
int m_sidebarScrollThinWidth ; // Width of the sidebar scroll thumb pill
bool m_isSidebarThumbDragging ; // Flag indicating scroll thumb drag in progress
int m_sidebarThumbDragStartY ; // Mouse Y when sidebar thumb drag started
int m_sidebarThumbDragStartPixels ; // Scroll offset when sidebar thumb drag started
bool m_isHoveredSidebarScrollArea ; // Flag indicating mouse is over sidebar scroll area
bool m_isHoveredSidebarThumb ; // Flag indicating mouse is over sidebar scroll thumb
bool m_isPanelDragging ; // Flag indicating panel drag in progress
int m_dragOffsetX ; // Mouse X offset from panel origin when drag started
int m_dragOffsetY ; // Mouse Y offset from panel origin when drag started
bool m_isResizingBottomEdge ; // Flag indicating bottom resize drag in progress
int m_bottomResizeDragStartY ; // Mouse Y when bottom resize drag started
int m_bottomResizeStartHeight ; // Panel height when bottom resize drag started
int m_snappedSidebarHeight ; // User-set height override while panel is snapped
bool m_isBottomResizeHovered ; // Flag indicating mouse is over the bottom resize grip
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 total pixel height of all category buttons stacked
int CalcSidebarTotalScrollPixels ( ) ;
//--- Compute the visible viewport pixel height for category buttons
int CalcSidebarViewportPixels ( ) ;
//--- Compute the maximum allowable scroll offset in pixels
int CalcSidebarMaxScrollPixels ( ) ;
//--- Check whether the category button at the given index is within the visible clip area
bool IsCategoryButtonVisible ( int idx ) ;
//--- Attempt to snap the panel to a chart edge based on current position
void TrySnapToEdge ( ) ;
//--- Test whether the given screen coordinates hit the sidebar panel
bool HitTestOverSidebar ( int mouseX , int mouseY , int & lx , int & ly ) ;
//--- Return the category under the given local coordinates, or CAT_NONE
ENUM_CATEGORY HitTestCategoryButton ( int lx , int ly ) ;
//--- Test whether the given local coordinates hit the grip drag area
bool HitTestOverGripArea ( int lx , int ly ) ;
//--- Test whether the given local coordinates hit the close button area
bool HitTestOverCloseButton ( int lx , int ly ) ;
//--- Test whether the given local coordinates hit the theme toggle button area
bool HitTestOverThemeButton ( int lx , int ly ) ;
//--- Test whether the given local coordinates hit the bottom resize grip
bool HitTestOverBottomResizeGrip ( int lx , int ly ) ;
} ;
//+------------------------------------------------------------------+
//| 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 ;
//--- Handle snapped panel height computation
if ( m_snapState ! = SNAP_FLOAT )
{
//--- 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 ;
//--- Apply user-set snapped height override if present, otherwise use natural size
if ( m_snappedSidebarHeight > 0 )
m_sidebarHeight = MathMax ( minH , MathMin ( MathMin ( naturalH , availH ) , m_snappedSidebarHeight ) ) ;
else
m_sidebarHeight = MathMax ( minH , MathMin ( naturalH , availH ) ) ;
}
else
{
//--- Compute natural and maximum height bounds for a floating panel
int naturalH = m_headerGripHeight + topPad + CAT_COUNT * ( m_categoryButtonSize + m_categoryButtonPadding ) - m_categoryButtonPadding + botPad ;
int maxH = chartH - m_panelY - 20 ;
int minH = m_headerGripHeight + topPad + 3 * ( m_categoryButtonSize + m_categoryButtonPadding ) - m_categoryButtonPadding + botPad ;
//--- Clamp the floating panel height within valid bounds
if ( m_sidebarHeight < minH | | m_sidebarHeight > MathMin ( naturalH , maxH ) )
m_sidebarHeight = MathMin ( naturalH , maxH ) ;
}
//--- 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 ;
//--- All buttons fit: show all and clear scroll offset
if ( fullBtnH < = btnAreaH )
{
m_sidebarMaxVisibleCats = CAT_COUNT ;
m_sidebarScrollPixels = 0 ;
}
else
{
//--- Compute how many buttons fit and clamp scroll offset within valid range
m_sidebarMaxVisibleCats = MathMax ( 3 , MathMin ( CAT_COUNT , btnAreaH / ( m_categoryButtonSize + m_categoryButtonPadding ) ) ) ;
m_sidebarScrollPixels = MathMax ( 0 , MathMin ( m_sidebarScrollPixels , CalcSidebarMaxScrollPixels ( ) ) ) ;
}
}
//+------------------------------------------------------------------+
//| Compute Y pixel position of a category button by index |
//+------------------------------------------------------------------+
int CSidebarLayout : : CalcCategoryButtonY ( int idx )
{
//--- Return scroll-adjusted Y offset below the header grip area
return m_headerGripHeight + 8 + idx * ( m_categoryButtonSize + m_categoryButtonPadding ) - m_sidebarScrollPixels ;
}
//+------------------------------------------------------------------+
//| 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 ;
}
//+------------------------------------------------------------------+
//| Compute total pixel height of all category buttons stacked |
//+------------------------------------------------------------------+
int CSidebarLayout : : CalcSidebarTotalScrollPixels ( )
{
//--- Return the combined height of all buttons including inter-button gaps
return CAT_COUNT * ( m_categoryButtonSize + m_categoryButtonPadding ) - m_categoryButtonPadding ;
}
//+------------------------------------------------------------------+
//| Compute the visible viewport pixel height for category buttons |
//+------------------------------------------------------------------+
int CSidebarLayout : : CalcSidebarViewportPixels ( )
{
//--- Return the pixel height of the visible button clip region
return CalcClipBottom ( ) - CalcClipTop ( ) ;
}
//+------------------------------------------------------------------+
//| Compute the maximum allowable scroll offset in pixels |
//+------------------------------------------------------------------+
int CSidebarLayout : : CalcSidebarMaxScrollPixels ( )
{
//--- Return zero if all buttons fit; otherwise return the overflow amount
return MathMax ( 0 , CalcSidebarTotalScrollPixels ( ) - CalcSidebarViewportPixels ( ) ) ;
}
//+------------------------------------------------------------------+
//| Check whether a category button is within the visible clip area |
//+------------------------------------------------------------------+
bool CSidebarLayout : : IsCategoryButtonVisible ( int idx )
{
//--- All buttons are visible when scroll is not needed
if ( m_sidebarMaxVisibleCats > = CAT_COUNT ) return true ;
//--- Compute the button's scroll-adjusted Y position
int y = CalcCategoryButtonY ( idx ) ;
//--- Return true if the button overlaps the clip region
return ( y + m_categoryButtonSize > CalcClipTop ( ) & & y < CalcClipBottom ( ) ) ;
}
//+------------------------------------------------------------------+
//| Attempt to snap the panel to a chart edge based on position |
//+------------------------------------------------------------------+
void CSidebarLayout : : TrySnapToEdge ( )
{
//--- Get current chart width for right-edge detection
int chartW = ( int ) ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS ) ;
ENUM_SNAP_STATE prev = m_snapState ;
//--- Snap to left edge if panel is within the snap threshold
if ( m_panelX < = SnapThreshold )
{ m_snapState = SNAP_LEFT ; m_panelX = 0 ; if ( prev = = SNAP_FLOAT ) m_snappedSidebarHeight = 0 ; }
//--- Snap to right edge if panel right boundary is within the snap threshold
else if ( m_panelX + m_sidebarWidth > = chartW - SnapThreshold )
{ m_snapState = SNAP_RIGHT ; m_panelX = chartW - m_sidebarWidth ; if ( prev = = SNAP_FLOAT ) m_snappedSidebarHeight = 0 ; }
else
{
//--- Set floating state and clear snapped height when leaving a snapped edge
m_snapState = SNAP_FLOAT ;
if ( prev ! = SNAP_FLOAT ) { m_snappedSidebarHeight = 0 ; m_categoryButtonPadding = 6 ; }
}
//--- Update the chart object X position to reflect the snapped or free position
ObjectSetInteger ( 0 , m_nameSidebar , OBJPROP_XDISTANCE , m_panelX ) ;
}
//+------------------------------------------------------------------+
//| Test whether screen coordinates hit the sidebar panel |
//+------------------------------------------------------------------+
bool CSidebarLayout : : HitTestOverSidebar ( int mouseX , int mouseY , int & lx , int & ly )
{
//--- Compute local coordinates relative to the panel origin
lx = mouseX - m_panelX ; ly = mouseY - m_panelY ;
//--- Return true if local coordinates fall within the panel bounds
return ( lx > = 0 & & lx < m_sidebarWidth & & ly > = 0 & & ly < m_sidebarHeight ) ;
}
//+------------------------------------------------------------------+
//| Return the category under local coordinates, or CAT_NONE |
//+------------------------------------------------------------------+
ENUM_CATEGORY CSidebarLayout : : HitTestCategoryButton ( int lx , int ly )
{
//--- Reject coordinates outside the category button clip region
if ( ly < CalcClipTop ( ) | | ly > = CalcClipBottom ( ) ) return CAT_NONE ;
//--- Compute horizontal start of the centred button column
int btnX = ( m_sidebarWidth - m_categoryButtonSize ) / 2 ;
//--- Test each visible category button for a hit
for ( int c = 0 ; c < CAT_COUNT ; c + + )
{
if ( ! IsCategoryButtonVisible ( c ) ) continue ;
int btnY = CalcCategoryButtonY ( c ) ;
//--- Return category if local coordinates fall within the button bounds
if ( lx > = btnX & & lx < = btnX + m_categoryButtonSize & &
ly > = btnY & & ly < = btnY + m_categoryButtonSize & & ly < m_sidebarHeight - 8 )
return ( ENUM_CATEGORY ) c ;
}
return CAT_NONE ;
}
//+------------------------------------------------------------------+
//| Test whether local coordinates hit the grip drag area |
//+------------------------------------------------------------------+
bool CSidebarLayout : : HitTestOverGripArea ( int lx , int ly )
{
//--- Return true if coordinates fall within the horizontal grip strip
return ( lx > = 0 & & lx < m_sidebarWidth & & ly > = m_categoryButtonSize & & ly < m_categoryButtonSize + 20 ) ;
}
//+------------------------------------------------------------------+
//| Test whether local coordinates hit the close button area |
//+------------------------------------------------------------------+
bool CSidebarLayout : : HitTestOverCloseButton ( int lx , int ly )
{
//--- Return true if coordinates fall within the top close button slot
return ( lx > = 0 & & lx < m_sidebarWidth & & ly > = 0 & & ly < m_categoryButtonSize ) ;
}
//+------------------------------------------------------------------+
//| Test whether local coordinates hit the theme toggle button area |
//+------------------------------------------------------------------+
bool CSidebarLayout : : HitTestOverThemeButton ( int lx , int ly )
{
//--- Return true if coordinates fall within the theme toggle row
return ( lx > = 0 & & lx < m_sidebarWidth & & ly > = m_categoryButtonSize + 20 & & ly < m_headerGripHeight ) ;
}
//+------------------------------------------------------------------+
//| Test whether local coordinates hit the bottom resize grip |
//+------------------------------------------------------------------+
bool CSidebarLayout : : HitTestOverBottomResizeGrip ( int lx , int ly )
{
//--- Return true if coordinates fall within the bottom resize handle strip
return ( lx > = 0 & & lx < m_sidebarWidth & & ly > = m_sidebarHeight - 8 & & ly < m_sidebarHeight ) ;
}
//+------------------------------------------------------------------+
//| CLASS 6 — Manage the flyout tool selection panel |
//+------------------------------------------------------------------+
class CFlyoutPanel : public CSidebarLayout
{
protected :
int m_flyoutWidth ; // Width of the flyout body (excluding pointer triangle)
int m_flyoutItemHeight ; // Height of each tool item row in the flyout
int m_flyoutPadding ; // Horizontal and vertical padding inside the flyout
int m_flyoutPointerWidth ; // Half-height of the pointer triangle
int m_flyoutPointerHeight ; // Depth (horizontal extent) of the pointer triangle
int m_flyoutPointerLocalY ; // Local Y centre of the pointer tip within the flyout
bool m_flyoutPointerOnLeft ; // Flag indicating the pointer faces left toward the sidebar
bool m_isFlyoutVisible ; // Flag indicating the flyout is currently visible
ENUM_CATEGORY m_flyoutActiveCat ; // Category whose tools are currently shown in the flyout
int m_hoveredFlyoutItem ; // Index of the hovered flyout item row, or -1
int m_flyoutScrollPixels ; // Current vertical scroll offset of the flyout list
int m_flyoutMaxVisibleItems ; // Maximum number of visible tool rows in the flyout
int m_flyoutScrollThumbHeight ; // Height of the flyout scroll thumb pill
bool m_isFlyoutThumbDragging ; // Flag indicating flyout scroll thumb drag in progress
int m_flyoutThumbDragStartY ; // Mouse Y when flyout thumb drag started
int m_flyoutThumbDragStartPixels ; // Scroll offset when flyout thumb drag started
bool m_isHoveredFlyoutScrollArea ; // Flag indicating mouse is over the flyout scroll area
bool m_isHoveredFlyoutThumb ; // Flag indicating mouse is over the flyout scroll thumb
protected :
//--- Show the flyout panel for the given category, highlighting the active tool
void ShowFlyout ( ENUM_CATEGORY cat , TOOL_TYPE activeTool ) ;
//--- Hide the flyout panel and reset its state
void HideFlyout ( ) ;
//--- Draw and composite the full flyout panel for the given category
void DrawFlyoutForCategory ( ENUM_CATEGORY cat , TOOL_TYPE activeTool ) ;
//--- Draw the flyout scroll thumb pill overlay onto the display canvas
void DrawFlyoutScrollPillOverlay ( ENUM_CATEGORY cat ) ;
//--- Draw the flyout body border at high resolution
void DrawFlyoutBodyBorderHR ( int x , int y , int w , int h , int r , int thickness , uint borderColor ) ;
//--- Test whether screen coordinates hit the visible flyout panel
bool HitTestOverFlyout ( int mouseX , int mouseY , int & lx , int & ly ) ;
//--- Return the flyout item index under the given local coordinates, or -1
int HitTestFlyoutItem ( int lx , int ly ) ;
} ;
//+------------------------------------------------------------------+
//| Hide the flyout panel and reset its state |
//+------------------------------------------------------------------+
void CFlyoutPanel : : HideFlyout ( )
{
//--- Reset hover item and scroll offset
m_hoveredFlyoutItem = -1 ;
m_flyoutScrollPixels = 0 ;
//--- Clear scroll hover flags
m_isHoveredFlyoutScrollArea = false ;
m_isHoveredFlyoutThumb = false ;
//--- Hide the flyout chart object from all timeframes
ObjectSetInteger ( 0 , m_nameFlyout , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
//--- Mark the flyout as hidden and clear the active category
m_isFlyoutVisible = false ;
m_flyoutActiveCat = CAT_NONE ;
}
//+------------------------------------------------------------------+
//| Test whether screen coordinates hit the visible flyout panel |
//+------------------------------------------------------------------+
bool CFlyoutPanel : : HitTestOverFlyout ( int mouseX , int mouseY , int & lx , int & ly )
{
//--- Skip test if flyout is not visible
if ( ! m_isFlyoutVisible ) return false ;
//--- Read the flyout chart object position and size
int fx = ( int ) ObjectGetInteger ( 0 , m_nameFlyout , OBJPROP_XDISTANCE ) ;
int fy = ( int ) ObjectGetInteger ( 0 , m_nameFlyout , OBJPROP_YDISTANCE ) ;
int fw = ( int ) ObjectGetInteger ( 0 , m_nameFlyout , OBJPROP_XSIZE ) ;
int fh = ( int ) ObjectGetInteger ( 0 , m_nameFlyout , OBJPROP_YSIZE ) ;
//--- Compute local coordinates relative to the flyout origin
lx = mouseX - fx ; ly = mouseY - fy ;
//--- Return true if the mouse is within the flyout bounds
return ( mouseX > = fx & & mouseX < fx + fw & & mouseY > = fy & & mouseY < fy + fh ) ;
}
//+------------------------------------------------------------------+
//| Return flyout item index under local coordinates, or -1 |
//+------------------------------------------------------------------+
int CFlyoutPanel : : HitTestFlyoutItem ( int lx , int ly )
{
//--- Return no hit if no category is active
if ( m_flyoutActiveCat = = CAT_NONE ) return -1 ;
int nTools = ArraySize ( m_categories [ ( int ) m_flyoutActiveCat ] . tools ) ;
//--- Compute title row height and body left offset for pointer direction
int titleH = 26 , dispBx = m_flyoutPointerOnLeft ? m_flyoutPointerHeight : 0 ;
int visibleTools = MathMin ( nTools , m_flyoutMaxVisibleItems ) ;
//--- Exclude clicks on the scroll thumb column when scrolling is active
if ( nTools > m_flyoutMaxVisibleItems )
{
int tw = m_sidebarScrollThinWidth ;
if ( ! m_flyoutPointerOnLeft ) { if ( lx < = dispBx + tw + 8 ) return -1 ; }
else { if ( lx > = dispBx + m_flyoutWidth - tw - 8 ) return -1 ; }
}
//--- Compute the vertical clip region for item rows
int itemClipTop = titleH + m_flyoutPadding , itemClipBot = titleH + m_flyoutPadding + visibleTools * m_flyoutItemHeight ;
//--- Return no hit if Y is outside the item clip region
if ( ly < itemClipTop | | ly > = itemClipBot ) return -1 ;
//--- Compute item index from Y position accounting for scroll offset
int idx = ( ly - itemClipTop + m_flyoutScrollPixels ) / m_flyoutItemHeight ;
if ( idx < 0 | | idx > = nTools ) return -1 ;
return idx ;
}
//+------------------------------------------------------------------+
//| Show the flyout for the given category with active tool state |
//+------------------------------------------------------------------+
void CFlyoutPanel : : ShowFlyout ( ENUM_CATEGORY cat , TOOL_TYPE activeTool )
{
//--- Hide flyout and exit if the category has no tools
int nTools = ArraySize ( m_categories [ ( int ) cat ] . tools ) ;
if ( nTools = = 0 ) { HideFlyout ( ) ; return ; }
//--- Reset the flyout scroll offset on each new show
m_flyoutScrollPixels = 0 ;
//--- Compute flyout panel height based on visible tool count
int titleH = 26 , visibleTools = MathMin ( nTools , m_flyoutMaxVisibleItems ) ;
int flyH = titleH + m_flyoutPadding + visibleTools * m_flyoutItemHeight + m_flyoutPadding ;
int totalW = m_flyoutWidth + m_flyoutPointerHeight ;
//--- Read chart dimensions for bounds checking
int chartW = ( int ) ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS ) , chartH = ( int ) ChartGetInteger ( 0 , CHART_HEIGHT_IN_PIXELS ) ;
//--- Determine pointer direction and flyout X position based on snap state
bool ptrLeft ; int flyX ;
if ( m_snapState = = SNAP_LEFT )
{ ptrLeft = true ; flyX = m_panelX + m_sidebarWidth ; }
else if ( m_snapState = = SNAP_RIGHT )
{ ptrLeft = false ; flyX = m_panelX - totalW ; }
else
{
//--- For floating panels, prefer opening to the right with fallback to left
int rightX = m_panelX + m_sidebarWidth ;
if ( rightX + totalW < = chartW - 4 ) { ptrLeft = true ; flyX = rightX ; }
else { ptrLeft = false ; flyX = m_panelX - totalW ; if ( flyX < 0 ) { ptrLeft = true ; flyX = rightX ; } }
}
m_flyoutPointerOnLeft = ptrLeft ;
//--- Compute the flyout Y position aligned to the hovered category button centre
int btnCentreY = m_panelY + CalcCategoryButtonY ( ( int ) cat ) + m_categoryButtonSize / 2 ;
int flyY = btnCentreY - ( titleH + m_flyoutPadding + 6 ) ;
//--- Clamp flyout Y within chart bounds
if ( flyY + flyH > chartH - 8 ) flyY = chartH - flyH - 8 ;
if ( flyY < 4 ) flyY = 4 ;
//--- Clamp the pointer local Y to stay within the flyout rounded corners
m_flyoutPointerLocalY = MathMax ( m_panelCornerRadius + m_flyoutPointerWidth + 2 ,
MathMin ( flyH - m_panelCornerRadius - m_flyoutPointerWidth - 2 , btnCentreY - flyY ) ) ;
//--- Position the flyout chart object and mark it visible
ObjectSetInteger ( 0 , m_nameFlyout , OBJPROP_XDISTANCE , flyX ) ;
ObjectSetInteger ( 0 , m_nameFlyout , OBJPROP_YDISTANCE , flyY ) ;
m_isFlyoutVisible = true ;
m_flyoutActiveCat = cat ;
//--- Draw the flyout contents and make the object visible on all timeframes
DrawFlyoutForCategory ( cat , activeTool ) ;
ObjectSetInteger ( 0 , m_nameFlyout , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ;
}
//+------------------------------------------------------------------+
//| Draw the flyout body border at high resolution |
//+------------------------------------------------------------------+
void CFlyoutPanel : : DrawFlyoutBodyBorderHR ( int x , int y , int w , int h , int r , int thickness , uint borderColor )
{
//--- Skip drawing when border width is disabled
if ( BorderWidth < = 0 ) return ;
//--- Clamp corner radius and compute half-thickness offset
r = MathMin ( r , MathMin ( w / 2 , h / 2 ) ) ; int h2 = thickness / 2 ;
//--- Draw all four border edges of the flyout body
DrawBorderEdge ( m_canvasFlyoutHighRes , x + r , y + h2 , x + w - r , y + h2 , thickness , borderColor ) ;
DrawBorderEdge ( m_canvasFlyoutHighRes , x + w - h2 , y + r , x + w - h2 , y + h - r , thickness , borderColor ) ;
DrawBorderEdge ( m_canvasFlyoutHighRes , x + w - r , y + h - h2 , x + r , y + h - h2 , thickness , borderColor ) ;
DrawBorderEdge ( m_canvasFlyoutHighRes , x + h2 , y + h - r , x + h2 , y + r , thickness , borderColor ) ;
//--- Draw all four corner arcs of the flyout body
DrawCornerArc ( m_canvasFlyoutHighRes , x + r , y + r , r , thickness , borderColor , M_PI , M_PI * 1.5 ) ;
DrawCornerArc ( m_canvasFlyoutHighRes , x + w - r , y + r , r , thickness , borderColor , M_PI * 1.5 , M_PI * 2.0 ) ;
DrawCornerArc ( m_canvasFlyoutHighRes , x + r , y + h - r , r , thickness , borderColor , M_PI * 0.5 , M_PI ) ;
DrawCornerArc ( m_canvasFlyoutHighRes , x + w - r , y + h - r , r , thickness , borderColor , 0.0 , M_PI * 0.5 ) ;
}
//+------------------------------------------------------------------+
//| Draw and composite the full flyout panel for the given category |
//+------------------------------------------------------------------+
void CFlyoutPanel : : DrawFlyoutForCategory ( ENUM_CATEGORY cat , TOOL_TYPE activeTool )
{
//--- Exit early if the category has no tools
int nTools = ArraySize ( m_categories [ ( int ) cat ] . tools ) ;
if ( nTools = = 0 ) return ;
//--- Compute layout dimensions
int titleH = 26 , visibleTools = MathMin ( nTools , m_flyoutMaxVisibleItems ) ;
bool needsScroll = ( nTools > m_flyoutMaxVisibleItems ) ;
int flyH = titleH + m_flyoutPadding + visibleTools * m_flyoutItemHeight + m_flyoutPadding ;
int totalW = m_flyoutWidth + m_flyoutPointerHeight ;
//--- Compute high-res canvas dimensions
int ws = totalW * m_supersampleFactor , hs = flyH * m_supersampleFactor ;
bool ptrLeft = m_flyoutPointerOnLeft ;
//--- Compute scroll thumb height if scrolling is needed
if ( needsScroll )
m_flyoutScrollThumbHeight = MathMax ( 20 , ( int ) ( MathMin ( nTools , m_flyoutMaxVisibleItems ) * m_flyoutItemHeight * ( double ) m_flyoutMaxVisibleItems / nTools ) ) ;
//--- Resize display and high-res canvases if flyout dimensions have changed
if ( m_canvasFlyout . Width ( ) ! = totalW | | m_canvasFlyout . Height ( ) ! = flyH )
m_canvasFlyout .Resize ( totalW , flyH ) ;
if ( m_canvasFlyoutHighRes . Width ( ) ! = ws | | m_canvasFlyoutHighRes . Height ( ) ! = hs )
m_canvasFlyoutHighRes .Resize ( ws , hs ) ;
//--- Update the flyout chart object size to match
ObjectSetInteger ( 0 , m_nameFlyout , OBJPROP_XSIZE , totalW ) ;
ObjectSetInteger ( 0 , m_nameFlyout , OBJPROP_YSIZE , flyH ) ;
//--- Clear the high-res canvas to fully transparent
m_canvasFlyoutHighRes . Erase ( 0x00000000 ) ;
//--- Compute horizontal body offset and width at high resolution
int bx = ptrLeft ? m_flyoutPointerHeight * m_supersampleFactor : 0 ;
int bw = m_flyoutWidth * m_supersampleFactor ;
int br = m_panelCornerRadius * m_supersampleFactor ;
//--- Compute pointer tip Y and half-height at high resolution
int ptrCY = MathMax ( br + m_flyoutPointerWidth * m_supersampleFactor + m_supersampleFactor ,
MathMin ( hs - br - m_flyoutPointerWidth * m_supersampleFactor - m_supersampleFactor ,
m_flyoutPointerLocalY * m_supersampleFactor ) ) ;
int ptrHHS = m_flyoutPointerWidth * m_supersampleFactor ;
//--- Compute pointer tip and base X based on pointer direction
int tipX = ptrLeft ? 0 : ws - 1 , baseX = ptrLeft ? bx : bx + bw - 1 ;
//--- Pack background and border colours
uchar flyBgA = ( uchar ) ( 255 * BackgroundOpacity ) ;
uint fillARGB = ColorToARGB ( m_themeColors . flyoutBackground , flyBgA ) ;
uint borderARGB = ColorToARGB ( m_themeColors . flyoutBorder , 255 ) ;
int brdT = BorderWidth * m_supersampleFactor ;
//--- Fill flyout body background with rounded corners
FillRoundRectHR ( m_canvasFlyoutHighRes , bx , 0 , bw , hs , br , fillARGB ) ;
//--- Fill the pointer triangle background
FillTriangleHR ( m_canvasFlyoutHighRes , tipX , ptrCY , baseX , ptrCY - ptrHHS , baseX , ptrCY + ptrHHS , fillARGB ) ;
//--- Draw flyout body border and pointer edges if border is enabled
if ( BorderWidth > 0 )
{
if ( ptrLeft )
{
//--- Draw body border on all four sides
DrawFlyoutBodyBorderHR ( bx , 0 , bw , hs , br , brdT , borderARGB ) ;
//--- Erase the body-left gap where the pointer connects
m_canvasFlyoutHighRes . FillRectangle ( bx , ptrCY - ptrHHS , bx + brdT + m_supersampleFactor , ptrCY + ptrHHS , fillARGB ) ;
//--- Draw pointer triangle border edges
DrawBorderEdge ( m_canvasFlyoutHighRes , ( double ) bx , ( double ) ( ptrCY - ptrHHS ) , ( double ) tipX , ( double ) ptrCY , brdT , borderARGB ) ;
DrawBorderEdge ( m_canvasFlyoutHighRes , ( double ) tipX , ( double ) ptrCY , ( double ) bx , ( double ) ( ptrCY + ptrHHS ) , brdT , borderARGB ) ;
}
else
{
//--- Compute the body right boundary for the right-facing pointer
int bodyRight = bx + bw ;
DrawFlyoutBodyBorderHR ( bx , 0 , bw , hs , br , brdT , borderARGB ) ;
//--- Erase the body-right gap where the pointer connects
m_canvasFlyoutHighRes . FillRectangle ( bodyRight - brdT - m_supersampleFactor , ptrCY - ptrHHS , bodyRight , ptrCY + ptrHHS , fillARGB ) ;
//--- Draw pointer triangle border edges for right-facing pointer
DrawBorderEdge ( m_canvasFlyoutHighRes , ( double ) bodyRight , ( double ) ( ptrCY - ptrHHS ) , ( double ) tipX , ( double ) ptrCY , brdT , borderARGB ) ;
DrawBorderEdge ( m_canvasFlyoutHighRes , ( double ) tipX , ( double ) ptrCY , ( double ) bodyRight , ( double ) ( ptrCY + ptrHHS ) , brdT , borderARGB ) ;
}
}
//--- Fill the flyout title strip background with rounded top corners
color titleFill = m_isDarkTheme ? C ' 25 , 29 , 40 ' : C ' 245 , 247 , 252 ' ;
int tbrd = MathMax ( brdT , m_supersampleFactor ) , innerTR = MathMax ( 0 , br - tbrd ) ;
FillSelectiveRoundRectHR ( m_canvasFlyoutHighRes , bx + tbrd , tbrd , bw - 2 * tbrd , titleH * m_supersampleFactor - tbrd , innerTR , ColorToARGB ( titleFill , 255 ) , true , true , false , false ) ;
//--- Square off the lower half of the title strip
m_canvasFlyoutHighRes . FillRectangle ( bx + tbrd , ( titleH / 2 ) * m_supersampleFactor , bx + bw - tbrd - 1 , titleH * m_supersampleFactor - 1 , ColorToARGB ( titleFill , 255 ) ) ;
//--- Compute item clip boundaries
int itemClipTop = titleH + m_flyoutPadding , itemClipBot = titleH + m_flyoutPadding + visibleTools * m_flyoutItemHeight ;
//--- Draw item highlight backgrounds — use tmpHighRes when scrolling so highlights
//--- never bleed above itemClipTop into the title strip after DownsampleCanvas
if ( needsScroll )
{
//--- Draw all scrolled item backgrounds onto a temporary HR canvas
CCanvas tmpHighRes ;
tmpHighRes . Create ( " FlyoutTmpHR " , ws , hs , COLOR_FORMAT_ARGB_NORMALIZE ) ;
tmpHighRes . Erase ( 0x00000000 ) ;
for ( int t = 0 ; t < nTools ; t + + )
{
//--- Compute scroll-adjusted item Y at high resolution
int itemY = ( titleH + m_flyoutPadding + t * m_flyoutItemHeight - m_flyoutScrollPixels ) * m_supersampleFactor ;
//--- Skip items fully above or below the clip region
if ( itemY + ( m_flyoutItemHeight - 2 ) * m_supersampleFactor < = itemClipTop * m_supersampleFactor ) continue ;
if ( itemY > = itemClipBot * m_supersampleFactor ) continue ;
bool isActive = ( activeTool = = m_categories [ ( int ) cat ] . tools [ t ] . toolType ) ;
bool isHovered = ( m_hoveredFlyoutItem = = t & & m_flyoutActiveCat = = cat ) ;
int itemH = ( m_flyoutItemHeight - 2 ) * m_supersampleFactor , padS = m_flyoutPadding * m_supersampleFactor ;
//--- Fill active item row background onto temp canvas
if ( isActive )
FillRoundRectHR ( tmpHighRes , bx + padS , itemY , bw - 2 * padS , itemH , 5 * m_supersampleFactor , ColorToARGB ( m_themeColors . buttonActiveBackground , 255 ) ) ;
//--- Fill hovered item row background onto temp canvas
else if ( isHovered )
FillRoundRectHR ( tmpHighRes , bx + padS , itemY , bw - 2 * padS , itemH , 5 * m_supersampleFactor , ColorToARGB ( m_themeColors . flyoutItemHoverBackground , 255 ) ) ;
//--- Draw active indicator dot onto temp canvas
if ( isActive )
tmpHighRes . FillCircle ( bx + bw - m_flyoutPadding * m_supersampleFactor - 5 * m_supersampleFactor ,
itemY + itemH / 2 , 3 * m_supersampleFactor , ColorToARGB ( m_themeColors . flyoutTextActiveColor , 255 ) ) ;
}
//--- Blit only the clip region from temp onto the main HR canvas
for ( int y = itemClipTop * m_supersampleFactor ; y < itemClipBot * m_supersampleFactor & & y < hs ; y + + )
for ( int x = 0 ; x < ws ; x + + )
{
uint px = tmpHighRes . PixelGet ( x , y ) ;
if ( ( ( px > > 24 ) & 0xFF ) > 0 ) BlendPixelSet ( m_canvasFlyoutHighRes , x , y , px ) ;
}
tmpHighRes . Destroy ( ) ;
}
else
{
//--- No scrolling — all items fit, draw directly onto the HR canvas
for ( int t = 0 ; t < visibleTools ; t + + )
{
bool isActive = ( activeTool = = m_categories [ ( int ) cat ] . tools [ t ] . toolType ) ;
bool isHovered = ( m_hoveredFlyoutItem = = t & & m_flyoutActiveCat = = cat ) ;
//--- Compute item Y at high resolution (no scroll offset needed)
int itemY = ( titleH + m_flyoutPadding + t * m_flyoutItemHeight ) * m_supersampleFactor ;
int itemH = ( m_flyoutItemHeight - 2 ) * m_supersampleFactor ;
int padS = m_flyoutPadding * m_supersampleFactor ;
//--- Fill active item row background
if ( isActive )
FillRoundRectHR ( m_canvasFlyoutHighRes , bx + padS , itemY , bw - 2 * padS , itemH , 5 * m_supersampleFactor , ColorToARGB ( m_themeColors . buttonActiveBackground , 255 ) ) ;
//--- Fill hovered item row background
else if ( isHovered )
FillRoundRectHR ( m_canvasFlyoutHighRes , bx + padS , itemY , bw - 2 * padS , itemH , 5 * m_supersampleFactor , ColorToARGB ( m_themeColors . flyoutItemHoverBackground , 255 ) ) ;
//--- Draw active indicator dot
if ( isActive )
m_canvasFlyoutHighRes . FillCircle ( bx + bw - m_flyoutPadding * m_supersampleFactor - 5 * m_supersampleFactor ,
itemY + itemH / 2 , 3 * m_supersampleFactor , ColorToARGB ( m_themeColors . flyoutTextActiveColor , 255 ) ) ;
}
}
//--- Downsample high-res canvas into the display-resolution flyout canvas
DownsampleCanvas ( m_canvasFlyout , m_canvasFlyoutHighRes , m_supersampleFactor ) ;
//--- Compute display-resolution body left offset
int dispBx = ptrLeft ? m_flyoutPointerHeight : 0 ;
//--- Draw horizontal separator below the title strip
m_canvasFlyout . Line ( dispBx + BorderWidth , titleH , dispBx + m_flyoutWidth - BorderWidth - 1 , titleH , ColorToARGB ( m_themeColors . flyoutBorder , 255 ) ) ;
//--- Draw the uppercased category title text
string titleStr = m_categories [ ( int ) cat ] . categoryLabel ;
StringToUpper ( titleStr ) ;
m_canvasFlyout . FontSet ( " Arial Bold " , FlyoutTitleSize ) ;
m_canvasFlyout . TextOut ( dispBx + m_flyoutPadding + 4 , 6 , titleStr , ColorToARGB ( m_themeColors . flyoutTitleColor , 255 ) ) ;
//--- Draw the tool count badge if the category has more than one tool
if ( nTools > 1 )
{
string countStr = IntegerToString ( nTools ) ;
m_canvasFlyout . FontSet ( " Arial " , 15 ) ;
int cw = m_canvasFlyout . TextWidth ( countStr ) ;
//--- Right-align the count badge within the title strip
m_canvasFlyout . TextOut ( dispBx + m_flyoutWidth - m_flyoutPadding - cw - 4 , 8 , countStr , ColorToARGB ( m_themeColors . flyoutTitleColor , 200 ) ) ;
}
//--- Create a temporary canvas for the icon and label text pass
//--- Drawing directly onto m_canvasFlyout has no Y clipping; TextOut at scrolled
//--- positions bleeds into the title strip. tmpText is seeded only for the clip
//--- region and blitted back, so glyphs outside [itemClipTop, itemClipBot) are discarded
CCanvas tmpText ;
tmpText . Create ( " FlyoutTmpText " , m_canvasFlyout . Width ( ) , m_canvasFlyout . Height ( ) , COLOR_FORMAT_ARGB_NORMALIZE ) ;
tmpText . Erase ( 0x00000000 ) ;
//--- Seed the clip region with existing flyout pixels as the drawing background
for ( int y = itemClipTop ; y < itemClipBot & & y < m_canvasFlyout . Height ( ) ; y + + )
for ( int x = 0 ; x < m_canvasFlyout . Width ( ) ; x + + )
tmpText . PixelSet ( x , y , m_canvasFlyout . PixelGet ( x , y ) ) ;
//--- Draw icon glyphs and label text for each tool row onto the temp canvas
for ( int t = 0 ; t < nTools ; t + + )
{
//--- Compute scroll-adjusted display-resolution item Y
int itemY = titleH + m_flyoutPadding + t * m_flyoutItemHeight - m_flyoutScrollPixels ;
int itemH = m_flyoutItemHeight - 2 ;
//--- Skip rows fully outside the clip region
if ( itemY + itemH < = itemClipTop | | itemY > = itemClipBot ) continue ;
bool isActive = ( activeTool = = m_categories [ ( int ) cat ] . tools [ t ] . toolType ) ;
bool isHovered = ( m_hoveredFlyoutItem = = t & & m_flyoutActiveCat = = cat ) ;
//--- Select icon and text colours based on state
color iconColor = isActive ? m_themeColors . flyoutTextActiveColor : ( isHovered ? clrWhite : m_themeColors . buttonIconColor ) ;
color textColor = isActive ? m_themeColors . flyoutTextActiveColor : ( isHovered ? clrWhite : m_themeColors . flyoutTextColor ) ;
//--- Set icon font and draw tool icon glyph onto the temp canvas
tmpText . FontSet ( m_categories [ ( int ) cat ] . tools [ t ] . iconFontName , FlyoutIconSize ) ;
string sym = CharToString ( m_categories [ ( int ) cat ] . tools [ t ] . iconCharCode ) ;
int ih = tmpText . TextHeight ( sym ) ;
tmpText . TextOut ( dispBx + m_flyoutPadding + 8 , itemY + ( itemH - ih ) / 2 , sym , ColorToARGB ( iconColor , 255 ) ) ;
//--- Set label font and draw tool label text onto the temp canvas
tmpText . FontSet ( " Arial " , FlyoutLabelSize ) ;
int lh = tmpText . TextHeight ( m_categories [ ( int ) cat ] . tools [ t ] . toolLabel ) ;
tmpText . TextOut ( dispBx + m_flyoutPadding + 34 , itemY + ( itemH - lh ) / 2 ,
m_categories [ ( int ) cat ] . tools [ t ] . toolLabel , ColorToARGB ( textColor , 255 ) ) ;
}
//--- Blit only the clip region back onto the display canvas, discarding any out-of-bounds draws
for ( int y = itemClipTop ; y < itemClipBot & & y < m_canvasFlyout . Height ( ) ; y + + )
for ( int x = 0 ; x < m_canvasFlyout . Width ( ) ; x + + )
m_canvasFlyout . PixelSet ( x , y , tmpText . PixelGet ( x , y ) ) ;
//--- Destroy the temporary canvas
tmpText . Destroy ( ) ;
//--- Overlay the scroll thumb pill if hover or drag is active
DrawFlyoutScrollPillOverlay ( cat ) ;
//--- Flush the display canvas to the chart
m_canvasFlyout . Update ( ) ;
}
//+------------------------------------------------------------------+
//| Draw the flyout scroll thumb pill overlay onto display canvas |
//+------------------------------------------------------------------+
void CFlyoutPanel : : DrawFlyoutScrollPillOverlay ( ENUM_CATEGORY cat )
{
//--- Skip drawing if neither hovered nor dragging
if ( ! m_isHoveredFlyoutScrollArea & & ! m_isFlyoutThumbDragging ) return ;
if ( cat = = CAT_NONE ) return ;
int nTools = ArraySize ( m_categories [ ( int ) cat ] . tools ) ;
//--- Skip if all items are visible and no scroll is needed
if ( nTools < = m_flyoutMaxVisibleItems ) return ;
//--- Compute scroll track geometry
int titleH = 26 , itemsTop = titleH + m_flyoutPadding ;
int trackH = MathMin ( nTools , m_flyoutMaxVisibleItems ) * m_flyoutItemHeight ;
m_flyoutScrollThumbHeight = MathMax ( 20 , ( int ) ( trackH * ( double ) m_flyoutMaxVisibleItems / nTools ) ) ;
//--- Compute the thumb Y position from the current scroll fraction
int maxScrollPx = ( nTools - m_flyoutMaxVisibleItems ) * m_flyoutItemHeight ;
double scrollPos = ( maxScrollPx > 0 ) ? ( double ) m_flyoutScrollPixels / maxScrollPx : 0.0 ;
int thumbY = itemsTop + ( int ) ( scrollPos * ( trackH - m_flyoutScrollThumbHeight ) ) ;
//--- Compute scroll pill X position based on pointer direction
int tw = m_sidebarScrollThinWidth , dispBx = m_flyoutPointerOnLeft ? m_flyoutPointerHeight : 0 ;
int thinX = m_flyoutPointerOnLeft ? ( dispBx + m_flyoutWidth - tw - 2 ) : ( dispBx + 2 ) ;
//--- Select pill colour and opacity based on interaction state
color pillColor ; uchar pillAlpha ;
if ( m_isFlyoutThumbDragging ) { pillColor = m_themeColors . accentBarColor ; pillAlpha = 255 ; }
else if ( m_isHoveredFlyoutThumb ) { pillColor = m_themeColors . scrollArrowHoverColor ; pillAlpha = 255 ; }
else { pillColor = m_themeColors . scrollArrowColor ; pillAlpha = 180 ; }
uint thumbARGB = ColorToARGB ( pillColor , pillAlpha ) ;
//--- Create a temporary high-res canvas for the pill shape
int pws = tw * m_supersampleFactor , phs = m_flyoutScrollThumbHeight * m_supersampleFactor ;
CCanvas pillHR ;
pillHR . Create ( " FlyoutPillHR_tmp " , pws , phs , COLOR_FORMAT_ARGB_NORMALIZE ) ;
pillHR . Erase ( 0x00000000 ) ;
//--- Fill the pill with a fully rounded rect at high resolution
FillRoundRectHR ( pillHR , 0 , 0 , pws , phs , MathMax ( 1 , pws / 2 ) , thumbARGB ) ;
//--- Downsample the pill and blend it onto the flyout display canvas
for ( int py = 0 ; py < m_flyoutScrollThumbHeight ; py + + )
for ( int px = 0 ; px < tw ; px + + )
{
//--- Accumulate channel sums across the high-res sample block
double sumA = 0 , sumR = 0 , sumG = 0 , sumB = 0 , wc = 0 ;
for ( int dy = 0 ; dy < m_supersampleFactor ; dy + + )
for ( int dx = 0 ; dx < m_supersampleFactor ; dx + + )
{
int sx = px * m_supersampleFactor + dx , sy = py * m_supersampleFactor + dy ;
if ( sx > = pws | | sy > = phs ) continue ;
uint p = pillHR . PixelGet ( sx , sy ) ; uchar a = ( uchar ) ( ( p > > 24 ) & 0xFF ) ;
sumA + = a ;
if ( a > 0 ) { sumR + = ( p > > 16 ) & 0xFF ; sumG + = ( p > > 8 ) & 0xFF ; sumB + = p & 0xFF ; wc + = 1.0 ; }
}
//--- Compute averaged output alpha and blend onto the display canvas
int ss2 = m_supersampleFactor * m_supersampleFactor ; uchar fa = ( uchar ) ( sumA / ss2 ) ;
if ( fa > 0 & & wc > 0 )
BlendPixelSet ( m_canvasFlyout , thinX + px , thumbY + py ,
( ( uint ) fa < < 24 ) | ( ( uint ) ( uchar ) ( sumR / wc ) < < 16 ) | ( ( uint ) ( uchar ) ( sumG / wc ) < < 8 ) | ( uint ) ( uchar ) ( sumB / wc ) ) ;
}
//--- Destroy the temporary high-res pill canvas
pillHR . Destroy ( ) ;
}
//+------------------------------------------------------------------+
//| CLASS 7 — Render sidebar buttons, icons, and scroll overlay |
//+------------------------------------------------------------------+
class CSidebarRenderer : public CFlyoutPanel
{
protected :
ENUM_CATEGORY m_hoveredCategory ; // Category button currently under the mouse cursor
bool m_isCloseButtonHovered ; // Flag indicating mouse is over the close button
bool m_isThemeButtonHovered ; // Flag indicating mouse is over the theme toggle button
bool m_isGripAreaHovered ; // Flag indicating mouse is over the drag grip strip
protected :
//--- Render the full sidebar panel for the given active tool
void DrawSidebar ( TOOL_TYPE activeTool ) ;
//--- Render the header strip background and control rows at high resolution
void DrawHeaderStripHR ( int canvasW , int canvasH ) ;
//--- Render a single category button background and accent bar at high resolution
void DrawCategoryButtonHR ( CCanvas & target , int xHR , int yHR , int sizeHR , bool isActive , bool isHovered , bool hasDot ) ;
//--- Render all category icon glyphs and sidebar control labels onto the display canvas
void DrawSidebarIconLabels ( TOOL_TYPE activeTool ) ;
//--- Render the sidebar scroll thumb pill overlay onto the display canvas
void DrawSidebarScrollPillOverlay ( ) ;
} ;
//+------------------------------------------------------------------+
//| Render the full sidebar panel for the given active tool |
//+------------------------------------------------------------------+
void CSidebarRenderer : : DrawSidebar ( TOOL_TYPE activeTool )
{
//--- Compute the scroll thumb height when the sidebar is scrollable
if ( CalcSidebarMaxScrollPixels ( ) > 0 )
{
int trackH = CalcSidebarViewportPixels ( ) ;
m_sidebarScrollThumbHeight = MathMax ( 20 , ( int ) ( trackH * ( double ) trackH / CalcSidebarTotalScrollPixels ( ) ) ) ;
}
//--- Compute high-res canvas dimensions
int ws = m_sidebarWidth * m_supersampleFactor , hs = m_sidebarHeight * m_supersampleFactor ;
//--- Resize the high-res canvas if sidebar 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 ) ;
//--- Pack background alpha from the opacity input
uchar bgA = ( uchar ) ( 255 * BackgroundOpacity ) ;
//--- Determine which corners are rounded based on the snap state
bool rTL = ( m_snapState ! = SNAP_LEFT ) , rBL = rTL ;
bool rTR = ( m_snapState ! = SNAP_RIGHT ) , rBR = rTR ;
//--- Fill the sidebar background with selective corner rounding
FillSelectiveRoundRectHR ( m_canvasSidebarHighRes , 0 , 0 , ws , hs ,
m_panelCornerRadius * m_supersampleFactor ,
ColorToARGB ( m_themeColors . sidebarBackground , bgA ) , rTL , rTR , rBL , rBR ) ;
//--- Draw the snapped edge flush border line on the snap side
if ( m_snapState = = SNAP_LEFT )
m_canvasSidebarHighRes . FillRectangle ( 0 , 0 , m_supersampleFactor - 1 , hs - 1 ,
ColorToARGB ( ( color ) ChartGetInteger ( 0 , CHART_COLOR_FOREGROUND ) , 255 ) ) ;
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 strip background and control rows
DrawHeaderStripHR ( ws , hs ) ;
//--- Determine which category is active for the current tool
ENUM_CATEGORY activeCat = GetCategoryForActiveTool ( activeTool ) ;
//--- When scrolling is active, draw buttons onto a temp canvas and blit only the clip region
if ( m_sidebarMaxVisibleCats < CAT_COUNT )
{
CCanvas tmpHR ;
tmpHR . Create ( " SB_TmpHR " , ws , hs , COLOR_FORMAT_ARGB_NORMALIZE ) ;
tmpHR . Erase ( 0x00000000 ) ;
//--- Draw each visible category button onto the temp canvas
for ( int c = 0 ; c < CAT_COUNT ; c + + )
{
if ( ! IsCategoryButtonVisible ( c ) ) continue ;
DrawCategoryButtonHR ( tmpHR ,
( m_sidebarWidth - m_categoryButtonSize ) / 2 * m_supersampleFactor ,
CalcCategoryButtonY ( c ) * m_supersampleFactor ,
m_categoryButtonSize * m_supersampleFactor ,
activeCat = = ( ENUM_CATEGORY ) c ,
m_hoveredCategory = = ( ENUM_CATEGORY ) c ,
ArraySize ( m_categories [ c ] . tools ) > 1 ) ;
}
//--- Blit only the clip region to prevent buttons bleeding into the header
int clipTop = CalcClipTop ( ) * m_supersampleFactor , clipBot = CalcClipBottom ( ) * m_supersampleFactor ;
for ( int y = clipTop ; y < clipBot & & y < hs ; y + + )
for ( int x = 0 ; x < ws ; x + + )
{
uint px = tmpHR . PixelGet ( x , y ) ;
if ( ( ( px > > 24 ) & 0xFF ) > 0 ) BlendPixelSet ( m_canvasSidebarHighRes , x , y , px ) ;
}
tmpHR . Destroy ( ) ;
}
else
{
//--- All buttons fit: draw directly onto the high-res canvas
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 ,
activeCat = = ( ENUM_CATEGORY ) c ,
m_hoveredCategory = = ( ENUM_CATEGORY ) c ,
ArraySize ( m_categories [ c ] . tools ) > 1 ) ;
}
//--- Draw the panel border with selective corner rounding
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 the high-res canvas into the display-resolution sidebar canvas
DownsampleCanvas ( m_canvasSidebar , m_canvasSidebarHighRes , m_supersampleFactor ) ;
//--- Draw icon glyphs and control labels onto the display canvas
DrawSidebarIconLabels ( activeTool ) ;
//--- Draw the scroll thumb pill overlay if scroll is active
DrawSidebarScrollPillOverlay ( ) ;
//--- Flush the display canvas to the chart
m_canvasSidebar . Update ( ) ;
}
//+------------------------------------------------------------------+
//| Render the header strip background and control rows at high res |
//+------------------------------------------------------------------+
void CSidebarRenderer : : DrawHeaderStripHR ( int canvasW , int canvasH )
{
//--- Compute header and border dimensions at high resolution
int headerH = m_headerGripHeight * m_supersampleFactor ;
int brd = BorderWidth * m_supersampleFactor ;
//--- Compute inner horizontal insets based on snap state
int inL = ( m_snapState = = SNAP_LEFT ) ? 0 : brd ;
int inR = ( m_snapState = = SNAP_RIGHT ) ? 0 : brd ;
//--- Compute header fill area dimensions
int hx = inL , hy = brd , hw = canvasW - inL - inR , hh = headerH - brd ;
int innerR = MathMax ( 0 , m_panelCornerRadius * m_supersampleFactor - brd ) ;
bool rTL = ( m_snapState ! = SNAP_LEFT ) , rTR = ( m_snapState ! = SNAP_RIGHT ) ;
//--- Fill header strip background with rounded top corners
color hdrFill = m_isDarkTheme ? C ' 25 , 29 , 40 ' : C ' 245 , 247 , 252 ' ;
FillSelectiveRoundRectHR ( m_canvasSidebarHighRes , hx , hy , hw , hh , innerR , ColorToARGB ( hdrFill , 255 ) , rTL , rTR , false , false ) ;
//--- Square off the lower half of the header strip
m_canvasSidebarHighRes . FillRectangle ( hx , hy + hh / 2 , hx + hw - 1 , headerH - 1 , ColorToARGB ( hdrFill , 255 ) ) ;
//--- Highlight the close button row if hovered
if ( m_isCloseButtonHovered )
FillSelectiveRoundRectHR ( m_canvasSidebarHighRes , inL , hy , canvasW - inL - inR ,
m_categoryButtonSize * m_supersampleFactor , innerR ,
ColorToARGB ( m_themeColors . closeButtonHoverColor , 255 ) , rTL , rTR , false , false ) ;
//--- Compute grip strip row geometry
int row2Y = m_categoryButtonSize * m_supersampleFactor , row2H = 20 * m_supersampleFactor ;
//--- Highlight the grip row if hovered
if ( m_isGripAreaHovered )
m_canvasSidebarHighRes . FillRectangle ( inL , row2Y , canvasW - inR - 1 , row2Y + row2H - 1 ,
ColorToARGB ( C ' 25 , 130 , 80 ' , 255 ) ) ;
//--- Compute theme button row geometry
int row3Y = ( m_categoryButtonSize + 20 ) * m_supersampleFactor ;
int row3H = ( m_headerGripHeight - m_categoryButtonSize - 20 ) * m_supersampleFactor ;
//--- Highlight the theme toggle row if hovered
if ( m_isThemeButtonHovered )
m_canvasSidebarHighRes . FillRectangle ( inL , row3Y , canvasW - inR - 1 , row3Y + row3H - 1 ,
ColorToARGB ( C ' 110 , 60 , 200 ' , 255 ) ) ;
//--- Select grip dot colour based on hover state
uint dotColor = m_isGripAreaHovered
? ColorToARGB ( clrWhite , 255 )
: ColorToARGB ( m_themeColors . buttonIconColor , 255 ) ;
//--- Draw three evenly-spaced grip dots centred in the grip strip
int gapX = 6 * m_supersampleFactor , dotR = 2 * m_supersampleFactor ;
for ( int col = 0 ; col < 3 ; col + + )
m_canvasSidebarHighRes . FillCircle ( canvasW / 2 + ( col - 1 ) * gapX , row2Y + row2H / 2 , dotR , dotColor ) ;
}
//+------------------------------------------------------------------+
//| Render a single category button background at high resolution |
//+------------------------------------------------------------------+
void CSidebarRenderer : : DrawCategoryButtonHR ( CCanvas & target , int xHR , int yHR , int sizeHR ,
bool isActive , bool isHovered , bool hasDot )
{
//--- Compute the corner radius for category buttons at high resolution
int cornerHR = 6 * m_supersampleFactor ;
if ( isActive )
{
//--- Fill the active button background
FillRoundRectHR ( target , xHR , yHR , sizeHR , sizeHR , cornerHR ,
ColorToARGB ( m_themeColors . buttonActiveBackground , 255 ) ) ;
//--- Compute the accent bar dimensions and X position
int barW = 3 * m_supersampleFactor , barH = sizeHR / 2 ;
int barX = ( m_snapState = = SNAP_RIGHT )
? xHR + sizeHR + m_supersampleFactor
: xHR - barW - m_supersampleFactor ;
//--- Draw the accent bar adjacent to the active button
FillRoundRectHR ( target , barX , yHR + sizeHR / 4 , barW , barH , m_supersampleFactor ,
ColorToARGB ( m_themeColors . accentBarColor , 255 ) ) ;
}
else if ( isHovered )
{
//--- Fill the hovered button background
FillRoundRectHR ( target , xHR , yHR , sizeHR , sizeHR , cornerHR ,
ColorToARGB ( m_themeColors . buttonHoverBackground , 255 ) ) ;
}
//--- Draw the multi-tool indicator dot in the bottom-right corner
if ( hasDot )
target . FillCircle (
xHR + sizeHR - 6 * m_supersampleFactor ,
yHR + sizeHR - 6 * m_supersampleFactor ,
2 * m_supersampleFactor ,
ColorToARGB ( isActive ? m_themeColors . buttonIconActiveColor : m_themeColors . gripDotsColor , 180 ) ) ;
}
//+------------------------------------------------------------------+
//| Render category icons and sidebar control labels onto display |
//+------------------------------------------------------------------+
void CSidebarRenderer : : DrawSidebarIconLabels ( TOOL_TYPE activeTool )
{
//--- Determine which category owns the current active tool
ENUM_CATEGORY activeCat = GetCategoryForActiveTool ( activeTool ) ;
int clipTop = CalcClipTop ( ) , clipBot = CalcClipBottom ( ) ;
//--- Create a temp canvas seeded with the current sidebar pixels to draw icons onto
CCanvas tmpIcons ;
tmpIcons . Create ( " SB_TmpIcons " , m_sidebarWidth , m_sidebarHeight , COLOR_FORMAT_ARGB_NORMALIZE ) ;
tmpIcons . Erase ( 0x00000000 ) ;
//--- Determine the vertical range to seed and blit based on scroll state
int blitY0 = ( m_sidebarMaxVisibleCats < CAT_COUNT ) ? clipTop : 0 ;
int blitY1 = ( m_sidebarMaxVisibleCats < CAT_COUNT ) ? clipBot : m_sidebarHeight ;
//--- Seed the temp canvas with existing sidebar pixels in the blit region
for ( int y = blitY0 ; y < blitY1 & & y < m_sidebarHeight ; y + + )
for ( int x = 0 ; x < m_sidebarWidth ; x + + )
tmpIcons . PixelSet ( x , y , m_canvasSidebar . PixelGet ( x , y ) ) ;
//--- Draw the category icon glyph for each visible button
for ( int c = 0 ; c < CAT_COUNT ; c + + )
{
if ( ! IsCategoryButtonVisible ( c ) ) continue ;
int btnY = CalcCategoryButtonY ( c ) , btnX = ( m_sidebarWidth - m_categoryButtonSize ) / 2 ;
bool isActive = ( activeCat = = ( ENUM_CATEGORY ) c ) ;
color iconColor = isActive ? m_themeColors . buttonIconActiveColor : m_themeColors . buttonIconColor ;
//--- Set the category icon font and measure the glyph dimensions
tmpIcons . FontSet ( m_categories [ c ] . iconFontName , CategoryIconSize ) ;
string sym = CharToString ( m_categories [ c ] . iconCharCode ) ;
int iw = tmpIcons . TextWidth ( sym ) , ih = tmpIcons . TextHeight ( sym ) ;
//--- Draw the icon glyph centred within the button bounds
tmpIcons . TextOut ( btnX + ( m_categoryButtonSize - iw ) / 2 ,
btnY + ( m_categoryButtonSize - ih ) / 2 ,
sym , ColorToARGB ( iconColor , 255 ) ) ;
}
//--- Blit the icon region back onto the display canvas
for ( int y = blitY0 ; y < blitY1 & & y < m_sidebarHeight ; y + + )
for ( int x = 0 ; x < m_sidebarWidth ; x + + )
m_canvasSidebar . PixelSet ( x , y , tmpIcons . PixelGet ( x , y ) ) ;
tmpIcons . Destroy ( ) ;
//--- Compute separator line horizontal extents respecting snap-side insets
int brd = BorderWidth ;
int sepL = ( m_snapState = = SNAP_LEFT ) ? 0 : brd ;
int sepR = m_sidebarWidth - 1 - ( ( m_snapState = = SNAP_RIGHT ) ? 0 : brd ) ;
//--- Pack separator colours
uint sepCol = ColorToARGB ( m_themeColors . separatorColor , 255 ) ;
uint sepCol2 = ColorToARGB ( m_isDarkTheme ? C ' 45 , 52 , 66 ' : C ' 195 , 202 , 215 ' , 255 ) ;
//--- Draw header bottom separator
m_canvasSidebar . Line ( sepL , m_headerGripHeight - 1 , sepR , m_headerGripHeight - 1 , sepCol ) ;
//--- Draw grip strip top and bottom separators
m_canvasSidebar . Line ( sepL , m_categoryButtonSize , sepR , m_categoryButtonSize , sepCol2 ) ;
m_canvasSidebar . Line ( sepL , m_categoryButtonSize + 20 , sepR , m_categoryButtonSize + 20 , sepCol2 ) ;
//--- Draw close button icon centred in the close button row
color closeIconColor = m_isCloseButtonHovered ? clrWhite : m_themeColors . buttonIconColor ;
m_canvasSidebar . FontSet ( " Webdings " , CategoryIconSize ) ;
string closeSym = CharToString ( ( uchar ) 114 ) ;
int clW = m_canvasSidebar . TextWidth ( closeSym ) , clH = m_canvasSidebar . TextHeight ( closeSym ) ;
m_canvasSidebar . TextOut ( ( m_sidebarWidth - clW ) / 2 , ( m_categoryButtonSize - clH ) / 2 ,
closeSym , ColorToARGB ( closeIconColor , 255 ) ) ;
//--- Compute theme button row geometry and draw theme icon
int row3Y = m_categoryButtonSize + 20 , row3H = m_headerGripHeight - m_categoryButtonSize - 20 ;
color themeIconColor = m_isThemeButtonHovered ? clrWhite : m_themeColors . buttonIconColor ;
m_canvasSidebar . FontSet ( " Wingdings " , CategoryIconSize ) ;
string themeSym = CharToString ( ( uchar ) 91 ) ;
int thW = m_canvasSidebar . TextWidth ( themeSym ) , thH = m_canvasSidebar . TextHeight ( themeSym ) ;
m_canvasSidebar . TextOut ( ( m_sidebarWidth - thW ) / 2 , row3Y + ( row3H - thH ) / 2 ,
themeSym , ColorToARGB ( themeIconColor , 255 ) ) ;
//--- Draw the bottom resize grip indicator bar when hovered or dragging
if ( m_isBottomResizeHovered | | m_isResizingBottomEdge )
{
int stripH = 3 , gripY = m_sidebarHeight - stripH - 1 ;
color barC = m_isResizingBottomEdge ? m_themeColors . accentBarColor : m_themeColors . scrollArrowHoverColor ;
m_canvasSidebar . FillRectangle ( 8 , gripY , m_sidebarWidth - 9 , gripY + stripH - 1 ,
ColorToARGB ( barC , 210 ) ) ;
}
}
//+------------------------------------------------------------------+
//| Render the sidebar scroll thumb pill overlay onto display canvas |
//+------------------------------------------------------------------+
void CSidebarRenderer : : DrawSidebarScrollPillOverlay ( )
{
//--- Skip if no scroll is needed or neither hover nor drag is active
if ( CalcSidebarMaxScrollPixels ( ) < = 0 | |
( ! m_isHoveredSidebarScrollArea & & ! m_isSidebarThumbDragging ) ) return ;
//--- Compute the scroll track geometry
int trackY = CalcClipTop ( ) , trackH = CalcSidebarViewportPixels ( ) ;
m_sidebarScrollThumbHeight = MathMax ( 20 , ( int ) ( trackH * ( double ) trackH / CalcSidebarTotalScrollPixels ( ) ) ) ;
int maxPx = CalcSidebarMaxScrollPixels ( ) ;
//--- Compute thumb Y from the current scroll fraction
double pos = ( maxPx > 0 ) ? ( double ) m_sidebarScrollPixels / maxPx : 0.0 ;
int thumbY = trackY + ( int ) ( pos * ( trackH - m_sidebarScrollThumbHeight ) ) ;
//--- Compute thumb X position on the non-snap side of the sidebar
int tw = m_sidebarScrollThinWidth ;
int thinX = ( m_snapState = = SNAP_RIGHT ) ? 2 : m_sidebarWidth - tw - 2 ;
//--- Select pill colour and opacity based on interaction state
color pillColor ; uchar pillAlpha ;
if ( m_isSidebarThumbDragging ) { pillColor = m_themeColors . accentBarColor ; pillAlpha = 255 ; }
else if ( m_isHoveredSidebarThumb ) { pillColor = m_themeColors . scrollArrowHoverColor ; pillAlpha = 255 ; }
else { pillColor = m_themeColors . scrollArrowColor ; pillAlpha = 180 ; }
uint thumbARGB = ColorToARGB ( pillColor , pillAlpha ) ;
//--- Create a temporary high-res canvas for the pill shape
int pws = tw * m_supersampleFactor , phs = m_sidebarScrollThumbHeight * m_supersampleFactor ;
CCanvas pillHR ;
pillHR . Create ( " SB_PillHR_tmp " , pws , phs , COLOR_FORMAT_ARGB_NORMALIZE ) ;
pillHR . Erase ( 0x00000000 ) ;
//--- Fill the pill with a fully rounded rect at high resolution
FillRoundRectHR ( pillHR , 0 , 0 , pws , phs , MathMax ( 1 , pws / 2 ) , thumbARGB ) ;
//--- Downsample the pill and blend onto the sidebar display canvas
for ( int py = 0 ; py < m_sidebarScrollThumbHeight ; py + + )
for ( int px = 0 ; px < tw ; px + + )
{
//--- Accumulate channel sums across the high-res sample block
double sumA = 0 , sumR = 0 , sumG = 0 , sumB = 0 , wc = 0 ;
for ( int dy = 0 ; dy < m_supersampleFactor ; dy + + )
for ( int dx = 0 ; dx < m_supersampleFactor ; dx + + )
{
int sx = px * m_supersampleFactor + dx , sy = py * m_supersampleFactor + dy ;
if ( sx > = pws | | sy > = phs ) continue ;
uint p = pillHR . PixelGet ( sx , sy ) ; uchar a = ( uchar ) ( ( p > > 24 ) & 0xFF ) ;
sumA + = a ;
if ( a > 0 ) { sumR + = ( p > > 16 ) & 0xFF ; sumG + = ( p > > 8 ) & 0xFF ; sumB + = p & 0xFF ; wc + = 1.0 ; }
}
int ss2 = m_supersampleFactor * m_supersampleFactor ; uchar fa = ( uchar ) ( sumA / ss2 ) ;
if ( fa > 0 & & wc > 0 )
BlendPixelSet ( m_canvasSidebar , thinX + px , thumbY + py ,
( ( uint ) fa < < 24 ) | ( ( uint ) ( uchar ) ( sumR / wc ) < < 16 ) |
( ( uint ) ( uchar ) ( sumG / wc ) < < 8 ) | ( uint ) ( uchar ) ( sumB / wc ) ) ;
}
//--- Destroy the temporary high-res pill canvas
pillHR . Destroy ( ) ;
}
//+------------------------------------------------------------------+
//| CLASS 8 — Manage crosshair, reticle, magnifier, and measure mode |
//+------------------------------------------------------------------+
class CCrosshairManager : public CSidebarRenderer
{
protected :
int m_reticleCanvasSize ; // Pixel size of the square reticle canvas
bool m_isReticleVisible ; // Flag indicating the reticle is currently shown
bool m_isMagnifierVisible ; // Flag indicating the magnifier lens is shown
bool m_isCrossVertVisible ; // Flag indicating the vertical crosshair line is shown
bool m_isCrossHorizVisible ; // Flag indicating the horizontal crosshair line is shown
bool m_isCrossPriceLabelVisible ; // Flag indicating the crosshair price label is shown
bool m_isCrossTimeLabelVisible ; // Flag indicating the crosshair time label is shown
bool m_isMeasureVertVisible ; // Flag indicating the measure vertical anchor line is shown
bool m_isMeasureHorizVisible ; // Flag indicating the measure horizontal anchor line is shown
bool m_isMeasurePriceLabelVisible ; // Flag indicating the measure price label is shown
bool m_isMeasureTimeLabelVisible ; // Flag indicating the measure time label is shown
bool m_isMeasureDiagonalVisible ; // Flag indicating the measure diagonal line is shown
bool m_isMeasuringActive ; // Flag indicating measure mode is locked to an anchor point
datetime m_measureAnchorTime ; // Chart time of the measure mode anchor point
double m_measureAnchorPrice ; // Price level of the measure mode anchor point
int m_measureAnchorPixelX ; // Screen X of the measure mode anchor point
int m_measureAnchorPixelY ; // Screen Y of the measure mode anchor point
ulong m_lastClickTimeMicros ; // Microsecond timestamp of the last mouse click
int m_lastMagMouseX ; // Last mouse X used to draw the magnifier lens
int m_lastMagMouseY ; // Last mouse Y used to draw the magnifier lens
protected :
//--- Draw the reticle tick-mark crosses onto the reticle canvas
void DrawReticleTickMarks ( ) ;
//--- Make the reticle canvas visible on the chart
void ShowReticle ( ) ;
//--- Hide the reticle canvas from the chart
void HideReticle ( ) ;
//--- Move the reticle canvas to follow the mouse cursor
void UpdateReticlePosition ( int mouseX , int mouseY ) ;
//--- Make the crosshair vertical line canvas visible
void ShowCrossVertical ( ) ;
//--- Hide the crosshair vertical line canvas
void HideCrossVertical ( ) ;
//--- Move the crosshair vertical line to the given screen X
void UpdateCrossVerticalPosition ( int mouseX ) ;
//--- Make the crosshair horizontal line canvas visible
void ShowCrossHorizontal ( ) ;
//--- Hide the crosshair horizontal line canvas
void HideCrossHorizontal ( ) ;
//--- Move the crosshair horizontal line to the given screen Y
void UpdateCrossHorizontalPosition ( int mouseY ) ;
//--- Make the crosshair price axis label visible
void ShowCrossPriceLabel ( ) ;
//--- Hide the crosshair price axis label
void HideCrossPriceLabel ( ) ;
//--- Make the crosshair time axis label visible
void ShowCrossTimeLabel ( ) ;
//--- Hide the crosshair time axis label
void HideCrossTimeLabel ( ) ;
//--- Draw and position one axis label canvas next to the crosshair
void DrawAndPositionAxisLabel ( CCanvas & labelCanvas , string objectName , string labelText ,
bool isPriceAxis , int crosshairPixelPos , int chartWidth , int chartHeight ) ;
//--- Update both crosshair axis labels to reflect the current mouse position
void UpdateCrosshairAxisLabels ( int mouseX , int mouseY , datetime barTime , double barPrice ) ;
//--- Show all measure mode line and label canvases
void ShowMeasureLines ( ) ;
//--- Hide all measure mode line and label canvases
void HideMeasureLines ( ) ;
//--- Move the measure vertical anchor line to the given screen X
void UpdateMeasureVerticalPosition ( int pixelX ) ;
//--- Move the measure horizontal anchor line to the given screen Y
void UpdateMeasureHorizontalPosition ( int pixelY ) ;
//--- Update the measure anchor axis labels at the anchor chart coordinate
void UpdateMeasureAnchorLabels ( ) ;
//--- Make the magnifier lens canvas visible
void ShowMagnifier ( ) ;
//--- Hide the magnifier lens canvas
void HideMagnifier ( ) ;
//--- Move the magnifier and redraw its lens content if the mouse has moved
void UpdateMagnifierPosition ( int mouseX , int mouseY , datetime barTime , double barPrice ) ;
//--- Render the zoomed candle chart content inside the circular magnifier lens
void DrawMagnifierLensContent ( int mouseX , int mouseY , datetime centerTime , double centerPrice ) ;
//--- Redraw the measure diagonal line from the anchor to the current mouse position
void UpdateMeasureDiagonalLine ( int currentMouseX , int currentMouseY ) ;
//--- Update the floating measure info label near the cursor with bar/pip statistics
void UpdateMeasurementInfoLabel ( int mouseX , int mouseY , datetime barTime , double barPrice ) ;
//--- Hide all crosshair element canvases in one call
void HideAllCrosshairElements ( ) ;
//--- Show all crosshair element canvases in one call
void ShowAllCrosshairElements ( ) ;
//--- Handle a potential double-click to toggle measure mode anchor
void HandleCrosshairDoubleClick ( int mouseX , int mouseY , datetime barTime , double barPrice ) ;
//--- Delete all measure mode chart objects and hide canvases
void DeleteAllMeasureObjects ( ) ;
} ;
//+------------------------------------------------------------------+
//| Draw the reticle tick-mark crosses onto the reticle canvas |
//+------------------------------------------------------------------+
void CCrosshairManager : : DrawReticleTickMarks ( )
{
//--- Clear the reticle canvas to fully transparent
m_canvasReticle . Erase ( 0x00000000 ) ;
//--- Compute the centre of the square reticle canvas
int cx = m_reticleCanvasSize / 2 , cy = m_reticleCanvasSize / 2 ;
//--- Cache tick geometry from inputs
int off = ReticleOffset , tl = ReticleTickLen / 2 , th = ReticleThickness ;
//--- Pack the chart foreground colour at slightly reduced opacity
uint col = ColorToARGB ( ( color ) ChartGetInteger ( 0 , CHART_COLOR_FOREGROUND ) , 230 ) ;
//--- Draw left tick marks above and below the horizontal axis
m_canvasReticle . FillRectangle ( cx - off - tl , cy - th - 1 , cx - off + tl , cy - 2 , col ) ;
m_canvasReticle . FillRectangle ( cx - off - tl , cy + 2 , cx - off + tl , cy + th + 1 , col ) ;
//--- Draw right tick marks above and below the horizontal axis
m_canvasReticle . FillRectangle ( cx + off - tl , cy - th - 1 , cx + off + tl , cy - 2 , col ) ;
m_canvasReticle . FillRectangle ( cx + off - tl , cy + 2 , cx + off + tl , cy + th + 1 , col ) ;
//--- Draw top tick marks left and right of the vertical axis
m_canvasReticle . FillRectangle ( cx - th - 1 , cy - off - tl , cx - 2 , cy - off + tl , col ) ;
m_canvasReticle . FillRectangle ( cx + 2 , cy - off - tl , cx + th + 1 , cy - off + tl , col ) ;
//--- Draw bottom tick marks left and right of the vertical axis
m_canvasReticle . FillRectangle ( cx - th - 1 , cy + off - tl , cx - 2 , cy + off + tl , col ) ;
m_canvasReticle . FillRectangle ( cx + 2 , cy + off - tl , cx + th + 1 , cy + off + tl , col ) ;
m_canvasReticle . Update ( ) ;
}
//+------------------------------------------------------------------+
//| Make the reticle canvas visible on the chart |
//+------------------------------------------------------------------+
void CCrosshairManager : : ShowReticle ( )
{
//--- Skip if already visible
if ( m_isReticleVisible ) return ;
//--- Draw fresh tick marks before making visible
DrawReticleTickMarks ( ) ;
//--- Make the reticle chart object visible on all timeframes
ObjectSetInteger ( 0 , m_nameReticle , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ;
m_isReticleVisible = true ;
}
//+------------------------------------------------------------------+
//| Hide the reticle canvas from the chart |
//+------------------------------------------------------------------+
void CCrosshairManager : : HideReticle ( )
{
//--- Skip if already hidden
if ( ! m_isReticleVisible ) return ;
ObjectSetInteger ( 0 , m_nameReticle , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
m_isReticleVisible = false ;
}
//+------------------------------------------------------------------+
//| Move the reticle canvas to follow the mouse cursor |
//+------------------------------------------------------------------+
void CCrosshairManager : : UpdateReticlePosition ( int mouseX , int mouseY )
{
if ( ! m_isReticleVisible ) return ;
//--- Centre the reticle canvas on the mouse cursor
int half = m_reticleCanvasSize / 2 ;
ObjectSetInteger ( 0 , m_nameReticle , OBJPROP_XDISTANCE , mouseX - half ) ;
ObjectSetInteger ( 0 , m_nameReticle , OBJPROP_YDISTANCE , mouseY - half ) ;
}
//+------------------------------------------------------------------+
//| Make the crosshair vertical line canvas visible |
//+------------------------------------------------------------------+
void CCrosshairManager : : ShowCrossVertical ( )
{
if ( m_isCrossVertVisible ) return ;
ObjectSetInteger ( 0 , m_nameCrossVertical , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ;
m_isCrossVertVisible = true ;
}
//+------------------------------------------------------------------+
//| Hide the crosshair vertical line canvas |
//+------------------------------------------------------------------+
void CCrosshairManager : : HideCrossVertical ( )
{
if ( ! m_isCrossVertVisible ) return ;
ObjectSetInteger ( 0 , m_nameCrossVertical , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
m_isCrossVertVisible = false ;
}
//+------------------------------------------------------------------+
//| Move the crosshair vertical line to the given screen X |
//+------------------------------------------------------------------+
void CCrosshairManager : : UpdateCrossVerticalPosition ( int mouseX )
{
if ( ! m_isCrossVertVisible ) return ;
//--- Position the 1-pixel-wide canvas at the cursor X
ObjectSetInteger ( 0 , m_nameCrossVertical , OBJPROP_XDISTANCE , mouseX ) ;
ObjectSetInteger ( 0 , m_nameCrossVertical , OBJPROP_YDISTANCE , 0 ) ;
}
//+------------------------------------------------------------------+
//| Make the crosshair horizontal line canvas visible |
//+------------------------------------------------------------------+
void CCrosshairManager : : ShowCrossHorizontal ( )
{
if ( m_isCrossHorizVisible ) return ;
ObjectSetInteger ( 0 , m_nameCrossHorizontal , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ;
m_isCrossHorizVisible = true ;
}
//+------------------------------------------------------------------+
//| Hide the crosshair horizontal line canvas |
//+------------------------------------------------------------------+
void CCrosshairManager : : HideCrossHorizontal ( )
{
if ( ! m_isCrossHorizVisible ) return ;
ObjectSetInteger ( 0 , m_nameCrossHorizontal , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
m_isCrossHorizVisible = false ;
}
//+------------------------------------------------------------------+
//| Move the crosshair horizontal line to the given screen Y |
//+------------------------------------------------------------------+
void CCrosshairManager : : UpdateCrossHorizontalPosition ( int mouseY )
{
if ( ! m_isCrossHorizVisible ) return ;
//--- Position the 1-pixel-tall canvas at the cursor Y
ObjectSetInteger ( 0 , m_nameCrossHorizontal , OBJPROP_XDISTANCE , 0 ) ;
ObjectSetInteger ( 0 , m_nameCrossHorizontal , OBJPROP_YDISTANCE , mouseY ) ;
}
//+------------------------------------------------------------------+
//| Make the crosshair price axis label visible |
//+------------------------------------------------------------------+
void CCrosshairManager : : ShowCrossPriceLabel ( )
{
if ( m_isCrossPriceLabelVisible ) return ;
ObjectSetInteger ( 0 , m_nameCrossPriceLabel , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ;
m_isCrossPriceLabelVisible = true ;
}
//+------------------------------------------------------------------+
//| Hide the crosshair price axis label |
//+------------------------------------------------------------------+
void CCrosshairManager : : HideCrossPriceLabel ( )
{
if ( ! m_isCrossPriceLabelVisible ) return ;
ObjectSetInteger ( 0 , m_nameCrossPriceLabel , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
m_isCrossPriceLabelVisible = false ;
}
//+------------------------------------------------------------------+
//| Make the crosshair time axis label visible |
//+------------------------------------------------------------------+
void CCrosshairManager : : ShowCrossTimeLabel ( )
{
if ( m_isCrossTimeLabelVisible ) return ;
ObjectSetInteger ( 0 , m_nameCrossTimeLabel , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ;
m_isCrossTimeLabelVisible = true ;
}
//+------------------------------------------------------------------+
//| Hide the crosshair time axis label |
//+------------------------------------------------------------------+
void CCrosshairManager : : HideCrossTimeLabel ( )
{
if ( ! m_isCrossTimeLabelVisible ) return ;
ObjectSetInteger ( 0 , m_nameCrossTimeLabel , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
m_isCrossTimeLabelVisible = false ;
}
//+------------------------------------------------------------------+
//| Draw and position one axis label canvas next to the crosshair |
//+------------------------------------------------------------------+
void CCrosshairManager : : DrawAndPositionAxisLabel ( CCanvas & labelCanvas , string objectName ,
string labelText , bool isPriceAxis ,
int crosshairPixelPos , int chartWidth , int chartHeight )
{
color fgColor = ( color ) ChartGetInteger ( 0 , CHART_COLOR_FOREGROUND ) ;
color bgColor = ( color ) ChartGetInteger ( 0 , CHART_COLOR_BACKGROUND ) ;
uint fg = ColorToARGB ( fgColor , 255 ) , bg = ColorToARGB ( bgColor , 255 ) ;
//--- Measure text using standalone API
TextSetFont ( AxisLabelFont , - AxisLabelFontSize * 10 ) ;
uint tw = 0 , th = 0 ;
TextGetSize ( labelText , tw , th ) ;
int lw = ( int ) tw + 8 , lh = ( int ) th + 4 ;
if ( labelCanvas . Width ( ) ! = lw | | labelCanvas . Height ( ) ! = lh ) labelCanvas .Resize ( lw , lh ) ;
ObjectSetInteger ( 0 , objectName , OBJPROP_XSIZE , lw ) ;
ObjectSetInteger ( 0 , objectName , OBJPROP_YSIZE , lh ) ;
//--- Render text into raw buffer using XRGB — no alpha, pure colors
uint textBuf [ ] ;
int totalPx = lw * lh ;
ArrayResize ( textBuf , totalPx ) ;
ArrayFill ( textBuf , 0 , totalPx , bg & 0x00FFFFFF ) ;
TextOut ( labelText , 4 , 2 , TA_LEFT | TA_TOP , textBuf , lw , lh , fg & 0x00FFFFFF , COLOR_FORMAT_XRGB_NOALPHA ) ;
//--- Copy clean pixels onto canvas with full alpha
for ( int py = 0 ; py < lh ; py + + )
for ( int px = 0 ; px < lw ; px + + )
labelCanvas . PixelSet ( px , py , textBuf [ py * lw + px ] | 0xFF000000 ) ;
//--- Draw border on top
labelCanvas . Rectangle ( 0 , 0 , lw - 1 , lh - 1 , fg ) ;
labelCanvas . Update ( ) ;
//--- Position the label at the appropriate axis edge
if ( isPriceAxis )
{
ObjectSetInteger ( 0 , objectName , OBJPROP_XDISTANCE , chartWidth - lw + 1 ) ;
ObjectSetInteger ( 0 , objectName , OBJPROP_YDISTANCE , crosshairPixelPos - lh / 2 ) ;
}
else
{
ObjectSetInteger ( 0 , objectName , OBJPROP_XDISTANCE , crosshairPixelPos - lw / 2 ) ;
ObjectSetInteger ( 0 , objectName , OBJPROP_YDISTANCE , chartHeight - lh ) ;
}
}
//+------------------------------------------------------------------+
//| Update both crosshair axis labels for the current mouse position |
//+------------------------------------------------------------------+
void CCrosshairManager : : UpdateCrosshairAxisLabels ( int mouseX , int mouseY , datetime barTime , double barPrice )
{
//--- Read chart dimensions and symbol digit count
int chartW = ( int ) ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS ) ;
int chartH = ( int ) ChartGetInteger ( 0 , CHART_HEIGHT_IN_PIXELS ) ;
int digits = ( int ) SymbolInfoInteger ( _Symbol , SYMBOL_DIGITS ) ;
//--- Draw and position the price label on the right axis at cursor Y
DrawAndPositionAxisLabel ( m_canvasCrossPriceLabel , m_nameCrossPriceLabel ,
DoubleToString ( barPrice , digits ) , true , mouseY , chartW , chartH ) ;
//--- Draw and position the time label on the bottom axis at cursor X
DrawAndPositionAxisLabel ( m_canvasCrossTimeLabel , m_nameCrossTimeLabel ,
TimeToString ( barTime , TIME_DATE | TIME_MINUTES ) , false , mouseX , chartW , chartH ) ;
}
//+------------------------------------------------------------------+
//| Show all measure mode line and label canvases |
//+------------------------------------------------------------------+
void CCrosshairManager : : ShowMeasureLines ( )
{
//--- Show each measure canvas if not already visible
if ( ! m_isMeasureVertVisible )
{ ObjectSetInteger ( 0 , m_nameMeasureVertical , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ; m_isMeasureVertVisible = true ; }
if ( ! m_isMeasureHorizVisible )
{ ObjectSetInteger ( 0 , m_nameMeasureHorizontal , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ; m_isMeasureHorizVisible = true ; }
if ( ! m_isMeasureDiagonalVisible )
{ ObjectSetInteger ( 0 , m_nameMeasureDiagonalLine , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ; m_isMeasureDiagonalVisible = true ; }
if ( ! m_isMeasurePriceLabelVisible )
{ ObjectSetInteger ( 0 , m_nameMeasurePriceLabel , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ; m_isMeasurePriceLabelVisible = true ; }
if ( ! m_isMeasureTimeLabelVisible )
{ ObjectSetInteger ( 0 , m_nameMeasureTimeLabel , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ; m_isMeasureTimeLabelVisible = true ; }
}
//+------------------------------------------------------------------+
//| Hide all measure mode line and label canvases |
//+------------------------------------------------------------------+
void CCrosshairManager : : HideMeasureLines ( )
{
//--- Hide each measure canvas if currently visible
if ( m_isMeasureVertVisible )
{ ObjectSetInteger ( 0 , m_nameMeasureVertical , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ; m_isMeasureVertVisible = false ; }
if ( m_isMeasureHorizVisible )
{ ObjectSetInteger ( 0 , m_nameMeasureHorizontal , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ; m_isMeasureHorizVisible = false ; }
if ( m_isMeasureDiagonalVisible )
{
//--- Clear the diagonal canvas before hiding to avoid stale pixels
ObjectSetInteger ( 0 , m_nameMeasureDiagonalLine , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
m_canvasMeasureDiagonalLine . Erase ( 0x00000000 ) ;
m_canvasMeasureDiagonalLine . Update ( ) ;
m_isMeasureDiagonalVisible = false ;
}
if ( m_isMeasurePriceLabelVisible )
{ ObjectSetInteger ( 0 , m_nameMeasurePriceLabel , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ; m_isMeasurePriceLabelVisible = false ; }
if ( m_isMeasureTimeLabelVisible )
{ ObjectSetInteger ( 0 , m_nameMeasureTimeLabel , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ; m_isMeasureTimeLabelVisible = false ; }
}
//+------------------------------------------------------------------+
//| Move the measure vertical anchor line to the given screen X |
//+------------------------------------------------------------------+
void CCrosshairManager : : UpdateMeasureVerticalPosition ( int pixelX )
{
if ( ! m_isMeasureVertVisible ) return ;
ObjectSetInteger ( 0 , m_nameMeasureVertical , OBJPROP_XDISTANCE , pixelX ) ;
ObjectSetInteger ( 0 , m_nameMeasureVertical , OBJPROP_YDISTANCE , 0 ) ;
}
//+------------------------------------------------------------------+
//| Move the measure horizontal anchor line to the given screen Y |
//+------------------------------------------------------------------+
void CCrosshairManager : : UpdateMeasureHorizontalPosition ( int pixelY )
{
if ( ! m_isMeasureHorizVisible ) return ;
ObjectSetInteger ( 0 , m_nameMeasureHorizontal , OBJPROP_XDISTANCE , 0 ) ;
ObjectSetInteger ( 0 , m_nameMeasureHorizontal , OBJPROP_YDISTANCE , pixelY ) ;
}
//+------------------------------------------------------------------+
//| Update the measure anchor axis labels at the anchor coordinate |
//+------------------------------------------------------------------+
void CCrosshairManager : : UpdateMeasureAnchorLabels ( )
{
//--- Read chart dimensions and symbol digit count
int chartW = ( int ) ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS ) ;
int chartH = ( int ) ChartGetInteger ( 0 , CHART_HEIGHT_IN_PIXELS ) ;
int digits = ( int ) SymbolInfoInteger ( _Symbol , SYMBOL_DIGITS ) ;
//--- Convert the anchor chart coordinate to screen pixel position
int fx = 0 , fy = 0 ;
if ( ! ChartTimePriceToXY ( m_chartId , 0 , m_measureAnchorTime , m_measureAnchorPrice , fx , fy ) ) return ;
//--- Draw and position the anchor price label on the right axis
DrawAndPositionAxisLabel ( m_canvasMeasurePriceLabel , m_nameMeasurePriceLabel ,
DoubleToString ( m_measureAnchorPrice , digits ) , true , fy , chartW , chartH ) ;
//--- Draw and position the anchor time label on the bottom axis
DrawAndPositionAxisLabel ( m_canvasMeasureTimeLabel , m_nameMeasureTimeLabel ,
TimeToString ( m_measureAnchorTime , TIME_DATE | TIME_MINUTES ) , false , fx , chartW , chartH ) ;
}
//+------------------------------------------------------------------+
//| Make the magnifier lens canvas visible |
//+------------------------------------------------------------------+
void CCrosshairManager : : ShowMagnifier ( )
{
if ( m_isMagnifierVisible ) return ;
ObjectSetInteger ( 0 , m_nameMagnifier , OBJPROP_TIMEFRAMES , OBJ_ALL_PERIODS ) ;
m_isMagnifierVisible = true ;
}
//+------------------------------------------------------------------+
//| Hide the magnifier lens canvas |
//+------------------------------------------------------------------+
void CCrosshairManager : : HideMagnifier ( )
{
if ( ! m_isMagnifierVisible ) return ;
ObjectSetInteger ( 0 , m_nameMagnifier , OBJPROP_TIMEFRAMES , OBJ_NO_PERIODS ) ;
m_isMagnifierVisible = false ;
}
//+------------------------------------------------------------------+
//| Move the magnifier and redraw lens content if cursor has moved |
//+------------------------------------------------------------------+
void CCrosshairManager : : UpdateMagnifierPosition ( int mouseX , int mouseY , datetime barTime , double barPrice )
{
if ( ! m_isMagnifierVisible ) return ;
int chartW = ( int ) ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS ) ;
int chartH = ( int ) ChartGetInteger ( 0 , CHART_HEIGHT_IN_PIXELS ) ;
int diam = MagDiameter ;
//--- Prefer placing the magnifier to the upper-right of the cursor
int magX = ( mouseX + MagOffset + diam < chartW ) ? mouseX + MagOffset : mouseX - MagOffset - diam ;
int magY = ( mouseY - MagOffset - diam > 0 ) ? mouseY - MagOffset - diam : mouseY + MagOffset ;
//--- Clamp the magnifier position within chart bounds
magX = MathMax ( 2 , MathMin ( chartW - diam - 2 , magX ) ) ;
magY = MathMax ( 2 , MathMin ( chartH - diam - 2 , magY ) ) ;
ObjectSetInteger ( 0 , m_nameMagnifier , OBJPROP_XDISTANCE , magX ) ;
ObjectSetInteger ( 0 , m_nameMagnifier , OBJPROP_YDISTANCE , magY ) ;
//--- Skip redrawing lens content if cursor has not moved
if ( mouseX = = m_lastMagMouseX & & mouseY = = m_lastMagMouseY ) return ;
m_lastMagMouseX = mouseX ;
m_lastMagMouseY = mouseY ;
//--- Redraw lens content for the new cursor position
DrawMagnifierLensContent ( mouseX , mouseY , barTime , barPrice ) ;
}
//+------------------------------------------------------------------+
//| Render zoomed candle chart content inside the magnifier lens |
//+------------------------------------------------------------------+
void CCrosshairManager : : DrawMagnifierLensContent ( int mouseX , int mouseY , datetime centerTime , double centerPrice )
{
int diam = MagDiameter , radius = diam / 2 ;
double zoom = MagZoom ;
//--- Resize magnifier canvas if diameter has changed
if ( m_canvasMagnifier . Width ( ) ! = diam | | m_canvasMagnifier . Height ( ) ! = diam )
m_canvasMagnifier .Resize ( diam , diam ) ;
m_canvasMagnifier . Erase ( 0x00000000 ) ;
//--- Read chart colour settings for consistent lens rendering
color bgColor = ( color ) ChartGetInteger ( 0 , CHART_COLOR_BACKGROUND ) ;
color fgColor = ( color ) ChartGetInteger ( 0 , CHART_COLOR_FOREGROUND ) ;
color bullBody = ( color ) ChartGetInteger ( 0 , CHART_COLOR_CANDLE_BULL ) ;
color bearBody = ( color ) ChartGetInteger ( 0 , CHART_COLOR_CANDLE_BEAR ) ;
color bullBord = ( color ) ChartGetInteger ( 0 , CHART_COLOR_CHART_UP ) ;
color bearBord = ( color ) ChartGetInteger ( 0 , CHART_COLOR_CHART_DOWN ) ;
color askColor = ( color ) ChartGetInteger ( 0 , CHART_COLOR_ASK ) ;
color bidColor = ( color ) ChartGetInteger ( 0 , CHART_COLOR_BID ) ;
bool showAsk = ( ChartGetInteger ( 0 , CHART_SHOW_ASK_LINE ) ! = 0 ) ;
bool showBid = ( ChartGetInteger ( 0 , CHART_SHOW_BID_LINE ) ! = 0 ) ;
//--- Read chart price range and bar width for coordinate mapping
int chartH = ( int ) ChartGetInteger ( 0 , CHART_HEIGHT_IN_PIXELS ) ;
double chartMax = ChartGetDouble ( 0 , CHART_PRICE_MAX , 0 ) ;
double chartMin = ChartGetDouble ( 0 , CHART_PRICE_MIN , 0 ) ;
double chartRange = MathMax ( chartMax - chartMin , _Point * 100 ) ;
int barWidth = ( int ) MathPow ( 2.0 , ( int ) ChartGetInteger ( 0 , CHART_SCALE ) ) ;
double pricePerPixel = chartRange / chartH ;
double radiusSq = ( double ) ( radius - 3 ) * ( radius - 3 ) ;
//--- Fill the circular lens background
uint bgARGB = ColorToARGB ( bgColor , 255 ) ;
double bgRSq = ( double ) ( radius - 2 ) * ( radius - 2 ) ;
for ( int py = 0 ; py < diam ; py + + )
for ( int px = 0 ; px < diam ; px + + )
{
double ddx = px - radius , ddy = py - radius ;
if ( ddx * ddx + ddy * ddy < = bgRSq ) m_canvasMagnifier . PixelSet ( px , py , bgARGB ) ;
}
//--- Compute bar range to fetch based on zoom and visible bar width
int chartVisibleBars = ( int ) ChartGetInteger ( 0 , CHART_VISIBLE_BARS ) ;
int halfRange = MathMin ( ( int ) ( ( radius * 2.0 / zoom ) / MathMax ( 1 , barWidth ) ) / 2 + 2 , chartVisibleBars / 2 + 2 ) ;
int cursorBar = iBarShift ( _Symbol , _Period , centerTime , false ) ;
if ( cursorBar < 0 ) cursorBar = 0 ;
//--- Fetch OHLC rates for the visible bar range
MqlRates rates [ ] ;
ArraySetAsSeries ( rates , false ) ;
int startBar = MathMax ( 0 , cursorBar - halfRange ) ;
int copied = CopyRates ( _Symbol , _Period , startBar , cursorBar + halfRange + 1 - startBar , rates ) ;
//--- Compute zoomed wick and border thickness
int wickThickness = MathMax ( 1 , ( int ) MathRound ( zoom * 0.55 ) ) ;
int borderThickness = MathMax ( 1 , ( int ) MathRound ( zoom * 0.45 ) ) ;
//--- Draw each candle inside the lens clipped to the circle
if ( copied > 0 )
{
for ( int i = 0 ; i < copied ; i + + )
{
//--- Convert bar time/price to screen pixel position
int barPxX = 0 , barPxY = 0 ;
if ( ! ChartTimePriceToXY ( m_chartId , 0 , rates [ i ] . time , rates [ i ] . close , barPxX , barPxY ) ) continue ;
//--- Map screen pixel to lens X using zoom factor
int lensX = radius + ( int ) ( ( barPxX - mouseX ) * zoom ) ;
//--- Compute zoomed bar body width
int zbw = MathMax ( 3 , ( int ) ( barWidth * zoom * 0.65 ) ) ; if ( zbw % 2 = = 0 ) zbw + + ;
int bh = zbw / 2 ;
if ( lensX + bh < 0 | | lensX - bh > = diam ) continue ;
//--- Determine candle direction and pack colours
bool isBull = ( rates [ i ] . close > = rates [ i ] . open ) ;
uint wickARGB = ColorToARGB ( isBull ? bullBord : bearBord , 255 ) ;
uint bodyARGB = ColorToARGB ( isBull ? bullBody : bearBody , 255 ) ;
uint borderARGB = ColorToARGB ( isBull ? bullBord : bearBord , 255 ) ;
//--- Convert high/low/open/close to lens Y coordinates
int lensHi = radius - ( int ) ( ( rates [ i ] . high - centerPrice ) / pricePerPixel * zoom ) ;
int lensLo = radius - ( int ) ( ( rates [ i ] . low - centerPrice ) / pricePerPixel * zoom ) ;
double bTop = isBull ? rates [ i ] . close : rates [ i ] . open ;
double bBot = isBull ? rates [ i ] . open : rates [ i ] . close ;
int lensBT = radius - ( int ) ( ( bTop - centerPrice ) / pricePerPixel * zoom ) ;
int lensBB = radius - ( int ) ( ( bBot - centerPrice ) / pricePerPixel * zoom ) ;
//--- Ensure minimum body height of 1 pixel
if ( lensBB - lensBT < 1 ) lensBB = lensBT + 1 ;
//--- Draw the wick clipped to the lens circle
int wickHalf = wickThickness / 2 ;
for ( int wy = MathMax ( 0 , lensHi ) ; wy < = MathMin ( diam - 1 , lensLo ) ; wy + + )
for ( int wx = lensX - wickHalf ; wx < = lensX + wickHalf ; wx + + )
{
if ( wx < 0 | | wx > = diam ) continue ;
double ddx = wx - radius , ddy = wy - radius ;
if ( ddx * ddx + ddy * ddy < radiusSq ) m_canvasMagnifier . PixelSet ( wx , wy , wickARGB ) ;
}
//--- Draw the candle body fill clipped to the lens circle
for ( int by = MathMax ( 0 , lensBT ) ; by < = MathMin ( diam - 1 , lensBB ) ; by + + )
for ( int bx = lensX - bh ; bx < = lensX + bh ; bx + + )
{
if ( bx < 0 | | bx > = diam ) continue ;
double ddx = bx - radius , ddy = by - radius ;
if ( ddx * ddx + ddy * ddy < radiusSq ) m_canvasMagnifier . PixelSet ( bx , by , bodyARGB ) ;
}
//--- Draw the candle body border clipped to the lens circle
for ( int bt = 0 ; bt < borderThickness ; bt + + )
{
int topRow = MathMax ( 0 , lensBT + bt ) , botRow = MathMin ( diam - 1 , lensBB - bt ) ;
for ( int bx = lensX - bh ; bx < = lensX + bh ; bx + + )
{
if ( bx < 0 | | bx > = diam ) continue ;
double ddx = bx - radius ;
double ddyT = topRow - radius , ddyB = botRow - radius ;
if ( ddx * ddx + ddyT * ddyT < radiusSq ) m_canvasMagnifier . PixelSet ( bx , topRow , borderARGB ) ;
if ( ddx * ddx + ddyB * ddyB < radiusSq ) m_canvasMagnifier . PixelSet ( bx , botRow , borderARGB ) ;
}
int leftCol = lensX - bh + bt , rightCol = lensX + bh - bt ;
for ( int by = MathMax ( 0 , lensBT ) ; by < = MathMin ( diam - 1 , lensBB ) ; by + + )
{
double ddy = by - radius ;
double ddxL = leftCol - radius , ddxR = rightCol - radius ;
if ( leftCol > = 0 & & leftCol < diam & & ddxL * ddxL + ddy * ddy < radiusSq ) m_canvasMagnifier . PixelSet ( leftCol , by , borderARGB ) ;
if ( rightCol > = 0 & & rightCol < diam & & ddxR * ddxR + ddy * ddy < radiusSq ) m_canvasMagnifier . PixelSet ( rightCol , by , borderARGB ) ;
}
}
}
}
//--- Draw Bid line inside lens if enabled
if ( showBid )
{
double bidPrice = SymbolInfoDouble ( _Symbol , SYMBOL_BID ) ;
int bidY = radius - ( int ) ( ( bidPrice - centerPrice ) / pricePerPixel * zoom ) ;
uint bidARGB = ColorToARGB ( bidColor , 200 ) ;
for ( int gx = 0 ; gx < diam ; gx + + )
{
double ddx = gx - radius , ddy = bidY - radius ;
if ( ddx * ddx + ddy * ddy < radiusSq ) BlendPixelSet ( m_canvasMagnifier , gx , bidY , bidARGB ) ;
}
}
//--- Draw Ask line inside lens if enabled
if ( showAsk )
{
double askPrice = SymbolInfoDouble ( _Symbol , SYMBOL_ASK ) ;
int askY = radius - ( int ) ( ( askPrice - centerPrice ) / pricePerPixel * zoom ) ;
uint askARGB = ColorToARGB ( askColor , 200 ) ;
for ( int gx = 0 ; gx < diam ; gx + + )
{
double ddx = gx - radius , ddy = askY - radius ;
if ( ddx * ddx + ddy * ddy < radiusSq ) BlendPixelSet ( m_canvasMagnifier , gx , askY , askARGB ) ;
}
}
//--- Draw the anti-aliased circular lens ring border
uint ringARGB = ColorToARGB ( m_isDarkTheme ? C ' 140 , 150 , 170 ' : C ' 80 , 90 , 110 ' , 255 ) ;
double outerR = radius - 1.0 , innerR = outerR - 2.5 ;
for ( int py = 0 ; py < diam ; py + + )
for ( int px = 0 ; px < diam ; px + + )
{
double ddx = px - radius + 0.5 , ddy = py - radius + 0.5 ;
double dist = MathSqrt ( ddx * ddx + ddy * ddy ) ;
if ( dist < innerR - 1.0 | | dist > outerR + 1.0 ) continue ;
//--- Compute anti-aliased ring edge coverage
double alpha = MathMin ( MathMin ( 1.0 , dist - ( innerR - 1.0 ) ) , MathMin ( 1.0 , outerR + 1.0 - dist ) ) ;
if ( alpha < = 0.0 ) continue ;
BlendPixelSet ( m_canvasMagnifier , px , py , ( ( uint ) ( uchar ) ( alpha * 255.0 ) < < 24 ) | ( ringARGB & 0x00FFFFFF ) ) ;
}
//--- Draw a faint dashed crosshair at lens centre
uint crossARGB = ColorToARGB ( fgColor , 60 ) ;
for ( int px = 0 ; px < diam ; px + + )
{
if ( px % 4 = = 0 ) continue ;
double ddx = ( double ) ( px - radius ) ;
if ( ddx * ddx > = radiusSq ) continue ;
BlendPixelSet ( m_canvasMagnifier , px , radius , crossARGB ) ;
}
for ( int py = 0 ; py < diam ; py + + )
{
if ( py % 4 = = 0 ) continue ;
double ddy = ( double ) ( py - radius ) ;
if ( ddy * ddy > = radiusSq ) continue ;
BlendPixelSet ( m_canvasMagnifier , radius , py , crossARGB ) ;
}
//--- Draw the current price label inside the lens near the bottom
int digits = ( int ) SymbolInfoInteger ( _Symbol , SYMBOL_DIGITS ) ;
m_canvasMagnifier . FontSet ( " Arial Bold " , 11 ) ;
string priceStr = DoubleToString ( centerPrice , digits ) ;
int tw = m_canvasMagnifier . TextWidth ( priceStr ) , th = m_canvasMagnifier . TextHeight ( priceStr ) ;
int tx = radius - tw / 2 , ty = diam - th - 16 ;
double tdy = ty - radius ;
//--- Only draw the label if it fits within the lens circle
if ( tdy * tdy + 4 < radiusSq )
{
m_canvasMagnifier . FillRectangle ( tx - 4 , ty - 1 , tx + tw + 4 , ty + th + 1 ,
( ringARGB & 0x00FFFFFF ) | 0xFF000000 ) ;
m_canvasMagnifier . TextOut ( tx , ty , priceStr , ColorToARGB ( clrWhite , 255 ) ) ;
}
m_canvasMagnifier . Update ( ) ;
}
//+------------------------------------------------------------------+
//| Redraw the measure diagonal line from anchor to current cursor |
//+------------------------------------------------------------------+
void CCrosshairManager : : UpdateMeasureDiagonalLine ( int currentMouseX , int currentMouseY )
{
if ( ! m_isMeasureDiagonalVisible ) return ;
//--- Clear the full-screen diagonal canvas before redrawing
m_canvasMeasureDiagonalLine . Erase ( 0x00000000 ) ;
//--- Draw an anti-aliased Bresenham line from anchor to current cursor
DrawBresenhamLine ( m_canvasMeasureDiagonalLine ,
m_measureAnchorPixelX , m_measureAnchorPixelY , currentMouseX , currentMouseY ,
ColorToARGB ( ( color ) ChartGetInteger ( 0 , CHART_COLOR_FOREGROUND ) , 220 ) ) ;
m_canvasMeasureDiagonalLine . Update ( ) ;
}
//+------------------------------------------------------------------+
//| Update the floating measure info label near the cursor |
//+------------------------------------------------------------------+
void CCrosshairManager : : UpdateMeasurementInfoLabel ( int mouseX , int mouseY , datetime barTime , double barPrice )
{
string labelName = " ToolsPalette_MeasureInfoLabel " ;
//--- Compute bar count between anchor and cursor using period seconds
long periodSec = PeriodSeconds ( _Period ) ;
int barCount = ( int ) MathAbs ( m_measureAnchorTime / periodSec - barTime / periodSec ) ;
//--- Compute pip distance using correct pip size for the symbol
double pointSize = SymbolInfoDouble ( _Symbol , SYMBOL_POINT ) ;
long digits = SymbolInfoInteger ( _Symbol , SYMBOL_DIGITS ) ;
double pipSize = ( digits = = 3 | | digits = = 5 ) ? pointSize * 10.0 : pointSize ;
double pips = MathAbs ( barPrice - m_measureAnchorPrice ) / pipSize ;
//--- Build the label text string with bar count, pip distance, and raw price difference
string labelText = StringFormat ( " %d bars, %.1f pips, Diff: %s " ,
barCount , pips , DoubleToString ( MathAbs ( barPrice - m_measureAnchorPrice ) , ( int ) digits ) ) ;
//--- Create the OBJ_LABEL chart object if it does not yet exist
if ( ObjectFind ( m_chartId , labelName ) < 0 )
{
ObjectCreate ( m_chartId , labelName , OBJ_LABEL , 0 , 0 , 0 ) ;
ObjectSetInteger ( m_chartId , labelName , OBJPROP_CORNER , CORNER_LEFT_UPPER ) ;
ObjectSetInteger ( m_chartId , labelName , OBJPROP_FONTSIZE , 9 ) ;
ObjectSetString ( m_chartId , labelName , OBJPROP_FONT , " Arial " ) ;
ObjectSetInteger ( m_chartId , labelName , OBJPROP_COLOR ,
( color ) ChartGetInteger ( 0 , CHART_COLOR_FOREGROUND ) ) ;
}
//--- Update the label position to follow the cursor with a small offset
ObjectSetInteger ( m_chartId , labelName , OBJPROP_XDISTANCE , mouseX + 20 ) ;
ObjectSetInteger ( m_chartId , labelName , OBJPROP_YDISTANCE , mouseY + 3 ) ;
ObjectSetString ( m_chartId , labelName , OBJPROP_TEXT , labelText ) ;
}
//+------------------------------------------------------------------+
//| Hide all crosshair element canvases in one call |
//+------------------------------------------------------------------+
void CCrosshairManager : : HideAllCrosshairElements ( )
{
HideReticle ( ) ;
HideMagnifier ( ) ;
HideCrossVertical ( ) ;
HideCrossHorizontal ( ) ;
HideCrossPriceLabel ( ) ;
HideCrossTimeLabel ( ) ;
}
//+------------------------------------------------------------------+
//| Show all crosshair element canvases in one call |
//+------------------------------------------------------------------+
void CCrosshairManager : : ShowAllCrosshairElements ( )
{
ShowReticle ( ) ;
ShowMagnifier ( ) ;
ShowCrossVertical ( ) ;
ShowCrossHorizontal ( ) ;
ShowCrossPriceLabel ( ) ;
ShowCrossTimeLabel ( ) ;
}
//+------------------------------------------------------------------+
//| Delete all measure mode chart objects and hide canvases |
//+------------------------------------------------------------------+
void CCrosshairManager : : DeleteAllMeasureObjects ( )
{
//--- Hide all measure canvases
HideMeasureLines ( ) ;
//--- Remove the floating info label chart object
ObjectDelete ( m_chartId , " ToolsPalette_MeasureInfoLabel " ) ;
}
//+------------------------------------------------------------------+
//| Handle a potential double-click to toggle measure mode anchor |
//+------------------------------------------------------------------+
void CCrosshairManager : : HandleCrosshairDoubleClick ( int mouseX , int mouseY , datetime barTime , double barPrice )
{
ulong nowMicros = GetMicrosecondCount ( ) ;
//--- Detect double-click by checking time since last click
if ( nowMicros - m_lastClickTimeMicros < 500000 )
{
if ( ! m_isMeasuringActive )
{
//--- Lock the measure anchor to the current chart coordinate
m_measureAnchorTime = barTime ;
m_measureAnchorPrice = barPrice ;
m_measureAnchorPixelX = mouseX ;
m_measureAnchorPixelY = mouseY ;
m_isMeasuringActive = true ;
//--- Disable chart scroll while measuring
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , false ) ;
}
else
{
//--- Release the measure anchor and clean up all measure objects
m_isMeasuringActive = false ;
DeleteAllMeasureObjects ( ) ;
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , true ) ;
}
//--- Reset click timer to prevent triple-click retriggering
m_lastClickTimeMicros = 0 ;
}
else
{
//--- Record this click as the first of a potential double-click pair
m_lastClickTimeMicros = nowMicros ;
}
}
//+------------------------------------------------------------------+
//| CLASS 9 — Route chart events to the appropriate handlers |
//+------------------------------------------------------------------+
class CChartEventHandler : public CCrosshairManager
{
protected :
int m_previousMouseButtonState ; // Mouse button state recorded during the last MOUSE_MOVE event
protected :
//--- Dispatch a chart event to the correct handler method
void RouteChartEvent ( const int id , const long & lp , const double & dp , const string & sp , TOOL_TYPE & activeTool ) ;
//--- Handle CHARTEVENT_CHART_CHANGE: reposition and resize all canvases
void OnChartChangeEvent ( TOOL_TYPE activeTool ) ;
//--- Handle CHARTEVENT_MOUSE_WHEEL: scroll sidebar or flyout under the cursor
void OnMouseWheelEvent ( int mouseX , int mouseY , int wheelDelta , TOOL_TYPE activeTool ) ;
//--- Handle CHARTEVENT_MOUSE_MOVE: dispatch all sub-handlers for mouse interaction
void OnMouseMoveEvent ( int mouseX , int mouseY , int mouseButtons , TOOL_TYPE & activeTool ) ;
//--- Move the sidebar panel to follow the mouse during a drag
void HandlePanelDragMove ( int mouseX , int mouseY , TOOL_TYPE activeTool ) ;
//--- Finalise a panel drag, snap the panel, and redraw
void HandlePanelDragRelease ( TOOL_TYPE activeTool ) ;
//--- Resize the sidebar height during a bottom-edge drag
void HandleBottomResizeDrag ( int mouseX , int mouseY , TOOL_TYPE activeTool ) ;
//--- Scroll the sidebar list by dragging the sidebar scroll thumb
void HandleSidebarThumbDrag ( int mouseX , int mouseY , TOOL_TYPE activeTool ) ;
//--- Finalise a sidebar thumb drag and redraw
void HandleSidebarThumbRelease ( TOOL_TYPE activeTool ) ;
//--- Scroll the flyout list by dragging the flyout scroll thumb
void HandleFlyoutThumbDrag ( int mouseX , int mouseY ) ;
//--- Finalise a flyout thumb drag and redraw
void HandleFlyoutThumbRelease ( ) ;
//--- Recompute all hover flags and redraw if any state changed
void UpdateAllHoverStates ( int mouseX , int mouseY , bool overSidebar , bool overFlyout ,
int lx , int ly , int flx , int fly , TOOL_TYPE activeTool ) ;
//--- Handle a fresh left-button press on the sidebar or flyout
void HandleMouseClickDown ( int mouseX , int mouseY , bool overSidebar , bool overFlyout ,
int lx , int ly , int flx , int fly , TOOL_TYPE & activeTool ) ;
} ;
//+------------------------------------------------------------------+
//| Dispatch a chart event to the correct handler method |
//+------------------------------------------------------------------+
void CChartEventHandler : : RouteChartEvent ( const int id , const long & lp , const double & dp , const string & sp , TOOL_TYPE & activeTool )
{
//--- Dispatch chart change events (window resize, scroll, zoom)
if ( id = = CHARTEVENT_CHART_CHANGE ) { OnChartChangeEvent ( activeTool ) ; return ; }
//--- Dispatch mouse wheel scroll events
if ( id = = CHARTEVENT_MOUSE_WHEEL ) { OnMouseWheelEvent ( ( int ) ( short ) lp , ( int ) ( short ) ( lp > > 16 ) , ( int ) dp , activeTool ) ; return ; }
//--- Dispatch mouse move events (includes button state in sp)
if ( id = = CHARTEVENT_MOUSE_MOVE ) OnMouseMoveEvent ( ( int ) lp , ( int ) dp , ( int ) sp , activeTool ) ;
}
//+------------------------------------------------------------------+
//| Handle chart change: reposition and resize all canvases |
//+------------------------------------------------------------------+
void CChartEventHandler : : OnChartChangeEvent ( TOOL_TYPE activeTool )
{
//--- Reset all drag and interaction state on chart resize/redraw
m_previousMouseButtonState = 0 ;
m_isPanelDragging = false ;
m_isResizingBottomEdge = false ;
m_isSidebarThumbDragging = false ;
m_isFlyoutThumbDragging = false ;
m_lastMagMouseX = -9999 ;
m_lastMagMouseY = -9999 ;
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , true ) ;
//--- Reposition and redraw snapped panels to keep them flush with the chart edge
if ( m_snapState ! = SNAP_FLOAT )
{
int chartW = ( int ) ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS ) ;
m_panelX = ( m_snapState = = SNAP_RIGHT ) ? chartW - m_sidebarWidth : 0 ;
ObjectSetInteger ( 0 , m_nameSidebar , OBJPROP_XDISTANCE , m_panelX ) ;
//--- Clamp the snapped height override within the new chart bounds
if ( m_snappedSidebarHeight > 0 )
m_snappedSidebarHeight = MathMin ( m_snappedSidebarHeight ,
( int ) ChartGetInteger ( 0 , CHART_HEIGHT_IN_PIXELS ) - m_panelY - 8 ) ;
CalcSidebarHeight ( ) ;
ResizeSidebarCanvases ( m_sidebarWidth , m_sidebarHeight ) ;
DrawSidebar ( activeTool ) ;
if ( m_isFlyoutVisible ) ShowFlyout ( m_flyoutActiveCat , activeTool ) ;
}
//--- Read the updated chart dimensions
int chartH = ( int ) ChartGetInteger ( 0 , CHART_HEIGHT_IN_PIXELS ) ;
int chartW = ( int ) ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS ) ;
//--- Resize and redraw the full-height crosshair vertical line canvas
m_canvasCrossVertical .Resize ( 1 , chartH ) ;
DrawCrossVerticalLinePixels ( chartH ) ;
ObjectSetInteger ( 0 , m_nameCrossVertical , OBJPROP_YSIZE , chartH ) ;
//--- Resize and redraw the full-width crosshair horizontal line canvas
m_canvasCrossHorizontal .Resize ( chartW , 1 ) ;
DrawCrossHorizontalLinePixels ( chartW ) ;
ObjectSetInteger ( 0 , m_nameCrossHorizontal , OBJPROP_XSIZE , chartW ) ;
//--- Resize and redraw the full-height measure vertical line canvas
m_canvasMeasureVertical .Resize ( 1 , chartH ) ;
DrawMeasureVerticalLinePixels ( chartH ) ;
ObjectSetInteger ( 0 , m_nameMeasureVertical , OBJPROP_YSIZE , chartH ) ;
//--- Resize and redraw the full-width measure horizontal line canvas
m_canvasMeasureHorizontal .Resize ( chartW , 1 ) ;
DrawMeasureHorizontalLinePixels ( chartW ) ;
ObjectSetInteger ( 0 , m_nameMeasureHorizontal , OBJPROP_XSIZE , chartW ) ;
//--- Resize and clear the full-screen measure diagonal line canvas
m_canvasMeasureDiagonalLine .Resize ( chartW , chartH ) ;
m_canvasMeasureDiagonalLine . Erase ( 0x00000000 ) ;
m_canvasMeasureDiagonalLine . Update ( ) ;
ObjectSetInteger ( 0 , m_nameMeasureDiagonalLine , OBJPROP_XSIZE , chartW ) ;
ObjectSetInteger ( 0 , m_nameMeasureDiagonalLine , OBJPROP_YSIZE , chartH ) ;
//--- Update measure anchor screen positions if measuring is active
if ( m_isMeasuringActive )
{
int fx = 0 , fy = 0 ;
if ( ChartTimePriceToXY ( m_chartId , 0 , m_measureAnchorTime , m_measureAnchorPrice , fx , fy ) )
{
m_measureAnchorPixelX = fx ;
m_measureAnchorPixelY = fy ;
UpdateMeasureVerticalPosition ( fx ) ;
UpdateMeasureHorizontalPosition ( fy ) ;
UpdateMeasureAnchorLabels ( ) ;
}
}
ChartRedraw ( ) ;
}
//+------------------------------------------------------------------+
//| Handle mouse wheel: scroll sidebar or flyout under the cursor |
//+------------------------------------------------------------------+
void CChartEventHandler : : OnMouseWheelEvent ( int mouseX , int mouseY , int wheelDelta , TOOL_TYPE activeTool )
{
int lx , ly , flx , fly ;
bool overSidebar = HitTestOverSidebar ( mouseX , mouseY , lx , ly ) ;
bool overFlyout = HitTestOverFlyout ( mouseX , mouseY , flx , fly ) ;
//--- Scroll the sidebar if the wheel is over the sidebar and scrolling is needed
if ( overSidebar & & m_sidebarMaxVisibleCats < CAT_COUNT )
{
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , false ) ;
m_sidebarScrollPixels = MathMax ( 0 , MathMin (
m_sidebarScrollPixels + ( ( wheelDelta < 0 ) ? MathMax ( 1 , MouseScrollSpeed ) : - MathMax ( 1 , MouseScrollSpeed ) ) ,
CalcSidebarMaxScrollPixels ( ) ) ) ;
HideFlyout ( ) ; DrawSidebar ( activeTool ) ; ChartRedraw ( ) ;
return ;
}
//--- Scroll the flyout if the wheel is over the flyout and scrolling is needed
if ( overFlyout & & m_isFlyoutVisible & & m_flyoutActiveCat ! = CAT_NONE )
{
int nTools = ArraySize ( m_categories [ ( int ) m_flyoutActiveCat ] . tools ) ;
if ( nTools > m_flyoutMaxVisibleItems )
{
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , false ) ;
int maxPx = ( nTools - m_flyoutMaxVisibleItems ) * m_flyoutItemHeight ;
m_flyoutScrollPixels = MathMax ( 0 , MathMin (
m_flyoutScrollPixels + ( ( wheelDelta < 0 ) ? MathMax ( 1 , MouseScrollSpeed ) : - MathMax ( 1 , MouseScrollSpeed ) ) ,
maxPx ) ) ;
DrawFlyoutForCategory ( m_flyoutActiveCat , activeTool ) ; ChartRedraw ( ) ;
}
return ;
}
//--- Restore chart scroll when the cursor is not over any panel
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , true ) ;
}
//+------------------------------------------------------------------+
//| Move the sidebar panel during a drag interaction |
//+------------------------------------------------------------------+
void CChartEventHandler : : HandlePanelDragMove ( int mouseX , int mouseY , TOOL_TYPE activeTool )
{
int chartW = ( int ) ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS ) ;
int chartH = ( int ) ChartGetInteger ( 0 , CHART_HEIGHT_IN_PIXELS ) ;
//--- Clamp the panel position within chart bounds
m_panelX = MathMax ( 0 , MathMin ( chartW - m_sidebarWidth , mouseX - m_dragOffsetX ) ) ;
m_panelY = MathMax ( 0 , MathMin ( chartH - m_sidebarHeight , mouseY - m_dragOffsetY ) ) ;
ObjectSetInteger ( 0 , m_nameSidebar , OBJPROP_XDISTANCE , m_panelX ) ;
ObjectSetInteger ( 0 , m_nameSidebar , OBJPROP_YDISTANCE , m_panelY ) ;
//--- Keep the flyout aligned if it is open
if ( m_isFlyoutVisible ) ShowFlyout ( m_flyoutActiveCat , activeTool ) ;
DrawSidebar ( activeTool ) ; ChartRedraw ( ) ;
}
//+------------------------------------------------------------------+
//| Finalise a panel drag, snap the panel, and redraw |
//+------------------------------------------------------------------+
void CChartEventHandler : : HandlePanelDragRelease ( TOOL_TYPE activeTool )
{
m_isPanelDragging = false ;
TrySnapToEdge ( ) ;
CalcSidebarHeight ( ) ;
ResizeSidebarCanvases ( m_sidebarWidth , m_sidebarHeight ) ;
DrawSidebar ( activeTool ) ;
ChartRedraw ( ) ;
}
//+------------------------------------------------------------------+
//| Resize the sidebar height during a bottom-edge drag |
//+------------------------------------------------------------------+
void CChartEventHandler : : HandleBottomResizeDrag ( int mouseX , int mouseY , TOOL_TYPE activeTool )
{
int dy = mouseY - m_bottomResizeDragStartY ;
int chartH = ( int ) ChartGetInteger ( 0 , CHART_HEIGHT_IN_PIXELS ) ;
//--- Compute allowed height range for the resize operation
int naturalH = m_headerGripHeight + 8 + CAT_COUNT * ( m_categoryButtonSize + 6 ) - 6 + 10 ;
int minH = m_headerGripHeight + 8 + 10 + 3 * ( m_categoryButtonSize + 6 ) - 6 ;
int maxH = ( m_snapState ! = SNAP_FLOAT )
? MathMin ( naturalH , chartH - m_panelY - 8 )
: chartH - m_panelY - 8 ;
//--- Compute the new height clamped within bounds
int newH = MathMax ( minH , MathMin ( maxH , m_bottomResizeStartHeight + dy ) ) ;
if ( newH ! = m_sidebarHeight )
{
//--- Apply the new height as a snapped override or a direct float height
if ( m_snapState ! = SNAP_FLOAT ) m_snappedSidebarHeight = newH ; else m_sidebarHeight = newH ;
CalcSidebarHeight ( ) ;
ResizeSidebarCanvases ( m_sidebarWidth , m_sidebarHeight ) ;
DrawSidebar ( activeTool ) ;
ChartRedraw ( ) ;
}
}
//+------------------------------------------------------------------+
//| Scroll the sidebar list by dragging the sidebar scroll thumb |
//+------------------------------------------------------------------+
void CChartEventHandler : : HandleSidebarThumbDrag ( int mouseX , int mouseY , TOOL_TYPE activeTool )
{
int trackH = CalcSidebarViewportPixels ( ) , travel = trackH - m_sidebarScrollThumbHeight ;
if ( travel > 0 )
{
int dy = mouseY - m_sidebarThumbDragStartY ;
int maxPx = CalcSidebarMaxScrollPixels ( ) ;
//--- Map thumb drag displacement to scroll pixel offset
int newPx = MathMax ( 0 , MathMin ( maxPx ,
m_sidebarThumbDragStartPixels + ( int ) MathRound ( ( double ) dy / travel * maxPx ) ) ) ;
if ( newPx ! = m_sidebarScrollPixels )
{ m_sidebarScrollPixels = newPx ; HideFlyout ( ) ; DrawSidebar ( activeTool ) ; ChartRedraw ( ) ; }
}
}
//+------------------------------------------------------------------+
//| Finalise a sidebar thumb drag and redraw |
//+------------------------------------------------------------------+
void CChartEventHandler : : HandleSidebarThumbRelease ( TOOL_TYPE activeTool )
{
m_isSidebarThumbDragging = false ;
DrawSidebar ( activeTool ) ;
ChartRedraw ( ) ;
}
//+------------------------------------------------------------------+
//| Scroll the flyout list by dragging the flyout scroll thumb |
//+------------------------------------------------------------------+
void CChartEventHandler : : HandleFlyoutThumbDrag ( int mouseX , int mouseY )
{
if ( m_flyoutActiveCat = = CAT_NONE ) return ;
int nTools = ArraySize ( m_categories [ ( int ) m_flyoutActiveCat ] . tools ) ;
if ( nTools < = m_flyoutMaxVisibleItems ) return ;
int trackH = MathMin ( nTools , m_flyoutMaxVisibleItems ) * m_flyoutItemHeight ;
int travel = trackH - m_flyoutScrollThumbHeight ;
if ( travel > 0 )
{
int dy = mouseY - m_flyoutThumbDragStartY ;
int maxPx = ( nTools - m_flyoutMaxVisibleItems ) * m_flyoutItemHeight ;
//--- Map thumb drag displacement to flyout scroll pixel offset
int newPx = MathMax ( 0 , MathMin ( maxPx ,
m_flyoutThumbDragStartPixels + ( int ) MathRound ( ( double ) dy / travel * maxPx ) ) ) ;
if ( newPx ! = m_flyoutScrollPixels )
{ m_flyoutScrollPixels = newPx ; DrawFlyoutForCategory ( m_flyoutActiveCat , TOOL_NONE ) ; ChartRedraw ( ) ; }
}
}
//+------------------------------------------------------------------+
//| Finalise a flyout thumb drag and redraw |
//+------------------------------------------------------------------+
void CChartEventHandler : : HandleFlyoutThumbRelease ( )
{
m_isFlyoutThumbDragging = false ;
DrawFlyoutForCategory ( m_flyoutActiveCat , TOOL_NONE ) ;
ChartRedraw ( ) ;
}
//+------------------------------------------------------------------+
//| Recompute all hover flags and redraw if any state changed |
//+------------------------------------------------------------------+
void CChartEventHandler : : UpdateAllHoverStates ( int mouseX , int mouseY , bool overSidebar , bool overFlyout ,
int lx , int ly , int flx , int fly , TOOL_TYPE activeTool )
{
//--- Snapshot previous hover states for change detection
ENUM_CATEGORY prevHovCat = m_hoveredCategory ;
int prevHovItem = m_hoveredFlyoutItem ;
bool prevClose = m_isCloseButtonHovered , prevTheme = m_isThemeButtonHovered ;
bool prevGrip = m_isGripAreaHovered , prevSBA = m_isHoveredSidebarScrollArea ;
bool prevFSA = m_isHoveredFlyoutScrollArea , prevBR = m_isBottomResizeHovered ;
bool prevSbTh = m_isHoveredSidebarThumb , prevFlyTh = m_isHoveredFlyoutThumb ;
//--- Clear all hover flags before recomputing
m_isCloseButtonHovered = m_isThemeButtonHovered = m_isGripAreaHovered = false ;
m_isBottomResizeHovered = m_isHoveredSidebarScrollArea = m_isHoveredSidebarThumb = false ;
m_isHoveredFlyoutScrollArea = m_isHoveredFlyoutThumb = false ;
//--- Recompute sidebar hover flags when cursor is over the sidebar
if ( overSidebar )
{
m_hoveredCategory = HitTestCategoryButton ( lx , ly ) ;
m_isCloseButtonHovered = HitTestOverCloseButton ( lx , ly ) ;
m_isThemeButtonHovered = HitTestOverThemeButton ( lx , ly ) ;
m_isGripAreaHovered = HitTestOverGripArea ( lx , ly ) ;
m_isBottomResizeHovered = HitTestOverBottomResizeGrip ( lx , ly ) ;
//--- Recompute sidebar scroll thumb hover if scroll is active
if ( CalcSidebarMaxScrollPixels ( ) > 0 )
{
int trackY = CalcClipTop ( ) , trackH = CalcSidebarViewportPixels ( ) ;
m_isHoveredSidebarScrollArea = ( ly > = trackY & & ly < = trackY + trackH ) ;
if ( m_isHoveredSidebarScrollArea )
{
int tw = m_sidebarScrollThinWidth ;
int thinX = ( m_snapState = = SNAP_RIGHT ) ? 2 : m_sidebarWidth - tw - 2 ;
if ( lx > = thinX - 4 & & lx < = thinX + tw + 4 )
{
int maxPx = CalcSidebarMaxScrollPixels ( ) ;
int sliderY = trackY + ( int ) ( ( maxPx > 0 ? ( double ) m_sidebarScrollPixels / maxPx : 0.0 ) *
( trackH - m_sidebarScrollThumbHeight ) ) ;
m_isHoveredSidebarThumb = ( ly > = sliderY & & ly < = sliderY + m_sidebarScrollThumbHeight ) ;
}
}
}
}
else if ( ! overFlyout ) m_hoveredCategory = CAT_NONE ;
//--- Recompute flyout hover flags when cursor is over the flyout
if ( overFlyout )
{
m_hoveredFlyoutItem = HitTestFlyoutItem ( flx , fly ) ;
if ( m_hoveredFlyoutItem < 0 ) m_hoveredFlyoutItem = -1 ;
//--- Check if scroll area hover applies
m_isHoveredFlyoutScrollArea = m_isFlyoutVisible & & m_flyoutActiveCat ! = CAT_NONE & &
( ArraySize ( m_categories [ ( int ) m_flyoutActiveCat ] . tools ) > m_flyoutMaxVisibleItems ) ;
if ( m_isHoveredFlyoutScrollArea )
{
int nTools = ArraySize ( m_categories [ ( int ) m_flyoutActiveCat ] . tools ) ;
int titleH = 26 , itemsTop = titleH + m_flyoutPadding ;
int trackH = MathMin ( nTools , m_flyoutMaxVisibleItems ) * m_flyoutItemHeight ;
int tw = m_sidebarScrollThinWidth ;
int dispBx = m_flyoutPointerOnLeft ? m_flyoutPointerHeight : 0 ;
int thinX = m_flyoutPointerOnLeft ? ( dispBx + m_flyoutWidth - tw - 2 ) : ( dispBx + 2 ) ;
if ( flx > = thinX - 6 & & flx < = thinX + tw + 6 & & fly > = itemsTop & & fly < = itemsTop + trackH )
{
int maxPx = ( nTools - m_flyoutMaxVisibleItems ) * m_flyoutItemHeight ;
int sliderY = itemsTop + ( int ) ( ( maxPx > 0 ? ( double ) m_flyoutScrollPixels / maxPx : 0.0 ) *
( trackH - m_flyoutScrollThumbHeight ) ) ;
m_isHoveredFlyoutThumb = ( fly > = sliderY & & fly < = sliderY + m_flyoutScrollThumbHeight ) ;
}
}
}
else if ( ! overSidebar ) { m_hoveredFlyoutItem = -1 ; m_isHoveredFlyoutScrollArea = false ; }
//--- Open the flyout for a hovered category button that is not a header control
if ( overSidebar & & m_hoveredCategory ! = CAT_NONE & &
! m_isCloseButtonHovered & & ! m_isThemeButtonHovered & & ! m_isGripAreaHovered )
{
if ( m_hoveredCategory ! = m_flyoutActiveCat ) ShowFlyout ( m_hoveredCategory , activeTool ) ;
}
else if ( ! overFlyout & & m_isFlyoutVisible )
{
//--- Keep the flyout open when transitioning between the panel and the flyout
bool transitEdge = false ;
if ( overSidebar )
{
int margin = m_sidebarWidth / 4 ;
transitEdge = ( m_snapState = = SNAP_LEFT ) ? ( lx > = m_sidebarWidth - margin ) :
( m_snapState = = SNAP_RIGHT ) ? ( lx < = margin ) :
( m_flyoutPointerOnLeft ? ( lx > = m_sidebarWidth - margin ) : ( lx < = margin ) ) ;
}
if ( ! transitEdge ) { HideFlyout ( ) ; ChartRedraw ( ) ; }
}
//--- Redraw if any hover state changed
bool changed = ( prevHovCat ! = m_hoveredCategory | | prevHovItem ! = m_hoveredFlyoutItem | |
prevClose ! = m_isCloseButtonHovered | | prevTheme ! = m_isThemeButtonHovered | |
prevGrip ! = m_isGripAreaHovered | | prevSBA ! = m_isHoveredSidebarScrollArea | |
prevFSA ! = m_isHoveredFlyoutScrollArea | | prevBR ! = m_isBottomResizeHovered | |
prevSbTh ! = m_isHoveredSidebarThumb | | prevFlyTh ! = m_isHoveredFlyoutThumb ) ;
if ( changed )
{
DrawSidebar ( activeTool ) ;
if ( m_isFlyoutVisible ) DrawFlyoutForCategory ( m_flyoutActiveCat , activeTool ) ;
ChartRedraw ( ) ;
}
}
//+------------------------------------------------------------------+
//| Handle a fresh left-button press on the sidebar or flyout |
//+------------------------------------------------------------------+
void CChartEventHandler : : HandleMouseClickDown ( int mouseX , int mouseY , bool overSidebar , bool overFlyout ,
int lx , int ly , int flx , int fly , TOOL_TYPE & activeTool )
{
//--- Handle clicks on the sidebar scroll track or thumb
if ( overSidebar & & CalcSidebarMaxScrollPixels ( ) > 0 )
{
int trackY = CalcClipTop ( ) , trackH = CalcSidebarViewportPixels ( ) , tw = m_sidebarScrollThinWidth ;
int thinX = ( m_snapState = = SNAP_RIGHT ) ? 2 : m_sidebarWidth - tw - 2 ;
if ( lx > = thinX - 4 & & lx < = thinX + tw + 4 & & ly > = trackY & & ly < = trackY + trackH )
{
int maxPx = CalcSidebarMaxScrollPixels ( ) ;
int sliderY = trackY + ( int ) ( ( maxPx > 0 ? ( double ) m_sidebarScrollPixels / maxPx : 0.0 ) *
( trackH - m_sidebarScrollThumbHeight ) ) ;
if ( ly > = sliderY & & ly < = sliderY + m_sidebarScrollThumbHeight )
{
//--- Begin thumb drag
m_isSidebarThumbDragging = true ;
m_sidebarThumbDragStartY = mouseY ;
m_sidebarThumbDragStartPixels = m_sidebarScrollPixels ;
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , false ) ;
HideFlyout ( ) ; DrawSidebar ( activeTool ) ; ChartRedraw ( ) ;
}
else
{
//--- Page-scroll the sidebar by one button size on track click
int step = m_categoryButtonSize + m_categoryButtonPadding ;
m_sidebarScrollPixels = MathMax ( 0 , MathMin ( maxPx ,
m_sidebarScrollPixels + ( ( ly < sliderY ) ? - step : step ) ) ) ;
HideFlyout ( ) ; DrawSidebar ( activeTool ) ; ChartRedraw ( ) ;
}
return ;
}
}
//--- Handle clicks on the flyout scroll track or thumb
if ( overFlyout & & m_flyoutActiveCat ! = CAT_NONE )
{
int nTools = ArraySize ( m_categories [ ( int ) m_flyoutActiveCat ] . tools ) ;
if ( nTools > m_flyoutMaxVisibleItems )
{
int titleH = 26 , itemsTop = titleH + m_flyoutPadding ;
int trackH = MathMin ( nTools , m_flyoutMaxVisibleItems ) * m_flyoutItemHeight ;
int tw = m_sidebarScrollThinWidth ;
int dispBx = m_flyoutPointerOnLeft ? m_flyoutPointerHeight : 0 ;
int thinX = m_flyoutPointerOnLeft ? ( dispBx + m_flyoutWidth - tw - 2 ) : ( dispBx + 2 ) ;
if ( flx > = thinX - 6 & & flx < = thinX + tw + 6 & & fly > = itemsTop & & fly < = itemsTop + trackH )
{
int maxPx = ( nTools - m_flyoutMaxVisibleItems ) * m_flyoutItemHeight ;
int sliderY = itemsTop + ( int ) ( ( maxPx > 0 ? ( double ) m_flyoutScrollPixels / maxPx : 0.0 ) *
( trackH - m_flyoutScrollThumbHeight ) ) ;
if ( fly > = sliderY & & fly < = sliderY + m_flyoutScrollThumbHeight )
{
//--- Begin flyout thumb drag
m_isFlyoutThumbDragging = true ;
m_flyoutThumbDragStartY = mouseY ;
m_flyoutThumbDragStartPixels = m_flyoutScrollPixels ;
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , false ) ;
DrawFlyoutForCategory ( m_flyoutActiveCat , activeTool ) ; ChartRedraw ( ) ;
}
else
{
//--- Page-scroll the flyout by one item height on track click
m_flyoutScrollPixels = MathMax ( 0 , MathMin ( maxPx ,
m_flyoutScrollPixels + ( ( fly < sliderY ) ? - m_flyoutItemHeight : m_flyoutItemHeight ) ) ) ;
DrawFlyoutForCategory ( m_flyoutActiveCat , activeTool ) ; ChartRedraw ( ) ;
}
return ;
}
}
}
//--- Begin panel drag when clicking the grip area
if ( overSidebar & & HitTestOverGripArea ( lx , ly ) & & ! m_isCloseButtonHovered & & ! m_isThemeButtonHovered )
{
m_isPanelDragging = true ;
m_dragOffsetX = lx ;
m_dragOffsetY = ly ;
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , false ) ;
HideFlyout ( ) ;
return ;
}
//--- Begin bottom resize drag when clicking the resize grip
if ( overSidebar & & HitTestOverBottomResizeGrip ( lx , ly ) )
{
m_isResizingBottomEdge = true ;
m_bottomResizeDragStartY = mouseY ;
m_bottomResizeStartHeight = m_sidebarHeight ;
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , false ) ;
HideFlyout ( ) ;
return ;
}
//--- Remove the indicator when the close button is clicked
if ( overSidebar & & m_isCloseButtonHovered ) { ExpertRemove ( ) ; return ; }
}
//+------------------------------------------------------------------+
//| Handle mouse move: dispatch all sub-handlers for mouse events |
//+------------------------------------------------------------------+
void CChartEventHandler : : OnMouseMoveEvent ( int mouseX , int mouseY , int mouseButtons , TOOL_TYPE & activeTool )
{
int lx , ly , flx , fly ;
bool overSidebar = HitTestOverSidebar ( mouseX , mouseY , lx , ly ) ;
bool overFlyout = ! overSidebar & & HitTestOverFlyout ( mouseX , mouseY , flx , fly ) ;
//--- Delegate to active drag and resize handlers first
if ( m_isPanelDragging & & mouseButtons = = 1 ) { HandlePanelDragMove ( mouseX , mouseY , activeTool ) ; m_previousMouseButtonState = mouseButtons ; return ; }
if ( m_isPanelDragging & & mouseButtons = = 0 ) { HandlePanelDragRelease ( activeTool ) ; m_previousMouseButtonState = mouseButtons ; return ; }
if ( m_isResizingBottomEdge & & mouseButtons = = 1 ) { HandleBottomResizeDrag ( mouseX , mouseY , activeTool ) ; m_previousMouseButtonState = mouseButtons ; return ; }
if ( m_isResizingBottomEdge & & mouseButtons = = 0 ) { m_isResizingBottomEdge = false ; m_previousMouseButtonState = mouseButtons ; return ; }
if ( m_isSidebarThumbDragging & & mouseButtons = = 1 ) { HandleSidebarThumbDrag ( mouseX , mouseY , activeTool ) ; m_previousMouseButtonState = mouseButtons ; return ; }
if ( m_isSidebarThumbDragging & & mouseButtons = = 0 ) { HandleSidebarThumbRelease ( activeTool ) ; m_previousMouseButtonState = mouseButtons ; return ; }
if ( m_isFlyoutThumbDragging & & mouseButtons = = 1 ) { HandleFlyoutThumbDrag ( mouseX , mouseY ) ; m_previousMouseButtonState = mouseButtons ; return ; }
if ( m_isFlyoutThumbDragging & & mouseButtons = = 0 ) { HandleFlyoutThumbRelease ( ) ; m_previousMouseButtonState = mouseButtons ; return ; }
//--- Update hover state for all sidebar and flyout elements
UpdateAllHoverStates ( mouseX , mouseY , overSidebar , overFlyout , lx , ly , flx , fly , activeTool ) ;
//--- Restore or suppress chart scroll based on cursor position
bool overAny = overSidebar | | overFlyout ;
if ( ! m_isSidebarThumbDragging & & ! m_isPanelDragging & & ! m_isResizingBottomEdge & & ! m_isFlyoutThumbDragging )
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , ! overAny ) ;
//--- Handle a new left-button press
if ( mouseButtons = = 1 & & m_previousMouseButtonState = = 0 )
HandleMouseClickDown ( mouseX , mouseY , overSidebar , overFlyout , lx , ly , flx , fly , activeTool ) ;
m_previousMouseButtonState = mouseButtons ;
}
//+------------------------------------------------------------------+
//| CLASS 10 — Handle chart drawing tool click interactions |
//+------------------------------------------------------------------+
class CDrawingEngine : public CChartEventHandler
{
protected :
int m_drawnObjectCounter ; // Counter used to generate unique object name suffixes
int m_toolDrawingClickCount ; // Number of clicks recorded for the current drawing operation
datetime m_drawPoint1Time ; // Chart time of the first click for multi-click tools
datetime m_drawPoint2Time ; // Chart time of the second click for three-click tools
double m_drawPoint1Price ; // Price level of the first click for multi-click tools
double m_drawPoint2Price ; // Price level of the second click for three-click tools
protected :
//--- Generate a unique chart object name for a new drawn object
string MakeUniqueObjectName ( ) ;
//--- Process a chart click for the active drawing tool
void HandleDrawingClick ( int mouseX , int mouseY , TOOL_TYPE & activeTool , string & instruction ) ;
//--- Create a chart object that requires only one click to place
void CreateSingleClickObject ( int sub , datetime t , double p , TOOL_TYPE toolType ) ;
//--- Create a chart object that requires two clicks to place
void CreateTwoClickObject ( int sub , TOOL_TYPE toolType ) ;
//--- Create a chart object that requires three clicks to place
void CreateThreeClickObject ( int sub , datetime t3 , double p3 , TOOL_TYPE toolType ) ;
} ;
//+------------------------------------------------------------------+
//| Generate a unique chart object name for a new drawn object |
//+------------------------------------------------------------------+
string CDrawingEngine : : MakeUniqueObjectName ( )
{
m_drawnObjectCounter + + ;
//--- Combine counter and current time for guaranteed uniqueness
return " ToolsPalette_Drawing_ " + IntegerToString ( m_drawnObjectCounter ) + " _ " + IntegerToString ( ( int ) TimeCurrent ( ) ) ;
}
//+------------------------------------------------------------------+
//| Process a chart click for the active drawing tool |
//+------------------------------------------------------------------+
void CDrawingEngine : : HandleDrawingClick ( int mouseX , int mouseY , TOOL_TYPE & activeTool , string & instruction )
{
datetime barTime ; double barPrice ; int sub ;
//--- Convert screen coordinates to chart time and price
if ( ! ChartXYToTimePrice ( m_chartId , mouseX , mouseY , sub , barTime , barPrice ) ) return ;
int clicksNeeded = GetRequiredClickCount ( activeTool ) ;
if ( clicksNeeded < = 0 ) return ;
m_toolDrawingClickCount + + ;
if ( m_toolDrawingClickCount = = 1 )
{
//--- Record first anchor point
m_drawPoint1Time = barTime ; m_drawPoint1Price = barPrice ;
if ( clicksNeeded = = 1 )
{
//--- Place single-click object immediately
CreateSingleClickObject ( sub , barTime , barPrice , activeTool ) ;
m_toolDrawingClickCount = 0 ; activeTool = TOOL_NONE ; instruction = " " ;
}
else instruction = " Click second point for " + GetToolLabel ( activeTool ) + " . " ;
}
else if ( m_toolDrawingClickCount = = 2 )
{
//--- Record second anchor point
m_drawPoint2Time = barTime ; m_drawPoint2Price = barPrice ;
if ( clicksNeeded = = 2 )
{
//--- Place two-click object
CreateTwoClickObject ( sub , activeTool ) ;
m_toolDrawingClickCount = 0 ; activeTool = TOOL_NONE ; instruction = " " ;
}
else instruction = " Click third point for " + GetToolLabel ( activeTool ) + " . " ;
}
else if ( m_toolDrawingClickCount = = 3 )
{
//--- Place three-click object with all three anchor points
CreateThreeClickObject ( sub , barTime , barPrice , activeTool ) ;
m_toolDrawingClickCount = 0 ; activeTool = TOOL_NONE ; instruction = " " ;
}
}
//+------------------------------------------------------------------+
//| Create a chart object that requires only one click to place |
//+------------------------------------------------------------------+
void CDrawingEngine : : CreateSingleClickObject ( int sub , datetime t , double p , TOOL_TYPE toolType )
{
string name = MakeUniqueObjectName ( ) ; bool ok = false ;
switch ( toolType )
{
//--- Horizontal line at the clicked price level
case TOOL_HLINE :
ok = ObjectCreate ( m_chartId , name , OBJ_HLINE , 0 , 0 , p ) ;
if ( ok ) { ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , clrDodgerBlue ) ; ObjectSetInteger ( m_chartId , name , OBJPROP_STYLE , STYLE_DASH ) ; }
break ;
//--- Vertical line at the clicked bar time
case TOOL_VLINE :
ok = ObjectCreate ( m_chartId , name , OBJ_VLINE , 0 , t , 0 ) ;
if ( ok ) { ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , clrDodgerBlue ) ; ObjectSetInteger ( m_chartId , name , OBJPROP_STYLE , STYLE_DASH ) ; }
break ;
//--- Text label at the clicked coordinate
case TOOL_TEXT :
ok = ObjectCreate ( m_chartId , name , OBJ_TEXT , sub , t , p ) ;
if ( ok ) { ObjectSetString ( m_chartId , name , OBJPROP_TEXT , " Text " ) ; ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , clrWhite ) ; ObjectSetInteger ( m_chartId , name , OBJPROP_FONTSIZE , 10 ) ; }
break ;
//--- Arrow up annotation at the clicked coordinate
case TOOL_ARROW_UP :
ok = ObjectCreate ( m_chartId , name , OBJ_ARROW_UP , sub , t , p ) ;
if ( ok ) ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , clrLime ) ;
break ;
//--- Arrow down annotation at the clicked coordinate
case TOOL_ARROW_DOWN :
ok = ObjectCreate ( m_chartId , name , OBJ_ARROW_DOWN , sub , t , p ) ;
if ( ok ) ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , clrRed ) ;
break ;
//--- Thumbs up annotation at the clicked coordinate
case TOOL_THUMB_UP :
ok = ObjectCreate ( m_chartId , name , OBJ_ARROW_THUMB_UP , sub , t , p ) ;
if ( ok ) ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , clrLime ) ;
break ;
//--- Thumbs down annotation at the clicked coordinate
case TOOL_THUMB_DOWN :
ok = ObjectCreate ( m_chartId , name , OBJ_ARROW_THUMB_DOWN , sub , t , p ) ;
if ( ok ) ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , clrRed ) ;
break ;
//--- Left price label annotation at the clicked coordinate
case TOOL_PRICE_LABEL :
ok = ObjectCreate ( m_chartId , name , OBJ_ARROW_LEFT_PRICE , sub , t , p ) ;
if ( ok ) ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , clrDodgerBlue ) ;
break ;
//--- Stop sign annotation at the clicked coordinate
case TOOL_STOP_SIGN :
ok = ObjectCreate ( m_chartId , name , OBJ_ARROW_STOP , sub , t , p ) ;
if ( ok ) ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , clrRed ) ;
break ;
//--- Check mark annotation at the clicked coordinate
case TOOL_CHECK_MARK :
ok = ObjectCreate ( m_chartId , name , OBJ_ARROW_CHECK , sub , t , p ) ;
if ( ok ) ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , clrLime ) ;
break ;
//--- Fibonacci time zones object placed at the clicked bar
case TOOL_FIBO_TIMEZONES :
ok = ObjectCreate ( m_chartId , name , OBJ_FIBOTIMES , sub , t , p , t , p ) ;
if ( ok ) ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , clrGold ) ;
break ;
default : break ;
}
//--- Select the newly created object so the user can adjust it
if ( ok )
{
ObjectSetInteger ( m_chartId , name , OBJPROP_SELECTABLE , true ) ;
ObjectSetInteger ( m_chartId , name , OBJPROP_SELECTED , true ) ;
ChartRedraw ( m_chartId ) ;
}
}
//+------------------------------------------------------------------+
//| Create a chart object that requires two clicks to place |
//+------------------------------------------------------------------+
void CDrawingEngine : : CreateTwoClickObject ( int sub , TOOL_TYPE toolType )
{
string name = MakeUniqueObjectName ( ) ; bool ok = false ; color objColor = clrDodgerBlue ;
//--- Retrieve stored first and second anchor points
datetime t1 = m_drawPoint1Time , t2 = m_drawPoint2Time ;
double p1 = m_drawPoint1Price , p2 = m_drawPoint2Price ;
switch ( toolType )
{
//--- Standard trend line between two points
case TOOL_TRENDLINE :
ok = ObjectCreate ( m_chartId , name , OBJ_TREND , sub , t1 , p1 , t2 , p2 ) ;
break ;
//--- Ray line extending to the right
case TOOL_RAY :
ok = ObjectCreate ( m_chartId , name , OBJ_TREND , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) ObjectSetInteger ( m_chartId , name , OBJPROP_RAY_RIGHT , true ) ;
break ;
//--- Extended (infinite in both directions) line
case TOOL_EXTENDED_LINE :
ok = ObjectCreate ( m_chartId , name , OBJ_TREND , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) { ObjectSetInteger ( m_chartId , name , OBJPROP_RAY_LEFT , true ) ; ObjectSetInteger ( m_chartId , name , OBJPROP_RAY_RIGHT , true ) ; }
break ;
//--- Info/measure line with pip distance label
case TOOL_INFO_LINE :
ok = ObjectCreate ( m_chartId , name , OBJ_TREND , sub , t1 , p1 , t2 , p2 ) ;
if ( ok )
{
ObjectSetString ( m_chartId , name , OBJPROP_TEXT ,
StringFormat ( " %.0f pips " , MathAbs ( p2 - p1 ) / SymbolInfoDouble ( _Symbol , SYMBOL_POINT ) / 10.0 ) ) ;
objColor = clrMediumSlateBlue ;
}
break ;
//--- Filled rectangle between two diagonal corners
case TOOL_RECTANGLE :
ok = ObjectCreate ( m_chartId , name , OBJ_RECTANGLE , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) ObjectSetInteger ( m_chartId , name , OBJPROP_FILL , true ) ;
break ;
//--- Triangle with auto-computed third vertex
case TOOL_TRIANGLE :
ok = ObjectCreate ( m_chartId , name , OBJ_TRIANGLE , sub , t1 , p1 , t2 , p2 ,
t1 + ( t2 - t1 ) / 2 , p1 - MathAbs ( p2 - p1 ) ) ;
if ( ok ) objColor = clrMediumSlateBlue ;
break ;
//--- Ellipse fitted to two diagonal corners
case TOOL_ELLIPSE :
ok = ObjectCreate ( m_chartId , name , OBJ_ELLIPSE , sub , t1 , p1 , t2 , p2 , t1 , p1 + ( p2 - p1 ) / 2 ) ;
if ( ok ) objColor = clrMediumOrchid ;
break ;
//--- Fibonacci retracement levels
case TOOL_FIBO_RETRACEMENT :
ok = ObjectCreate ( m_chartId , name , OBJ_FIBO , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) objColor = clrGold ;
break ;
//--- Fibonacci expansion levels
case TOOL_FIBO_EXPANSION :
ok = ObjectCreate ( m_chartId , name , OBJ_EXPANSION , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) objColor = clrGold ;
break ;
//--- Fibonacci fan lines
case TOOL_FIBO_FAN :
ok = ObjectCreate ( m_chartId , name , OBJ_FIBOFAN , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) objColor = clrGold ;
break ;
//--- Fibonacci arcs
case TOOL_FIBO_ARCS :
ok = ObjectCreate ( m_chartId , name , OBJ_FIBOARC , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) objColor = clrGold ;
break ;
//--- Gann line
case TOOL_GANN_LINE :
ok = ObjectCreate ( m_chartId , name , OBJ_GANNLINE , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) objColor = clrOrangeRed ;
break ;
//--- Gann fan lines from anchor
case TOOL_GANN_FAN :
ok = ObjectCreate ( m_chartId , name , OBJ_GANNFAN , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) objColor = clrOrangeRed ;
break ;
//--- Gann grid
case TOOL_GANN_GRID :
ok = ObjectCreate ( m_chartId , name , OBJ_GANNGRID , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) objColor = clrOrangeRed ;
break ;
//--- Linear regression channel
case TOOL_REGRESSION_CHANNEL :
ok = ObjectCreate ( m_chartId , name , OBJ_REGRESSION , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) objColor = clrCornflowerBlue ;
break ;
//--- Standard deviation channel
case TOOL_STDDEV_CHANNEL :
ok = ObjectCreate ( m_chartId , name , OBJ_STDDEVCHANNEL , sub , t1 , p1 , t2 , p2 ) ;
if ( ok ) objColor = clrCornflowerBlue ;
break ;
default : break ;
}
//--- Apply common properties and select the new object
if ( ok )
{
ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , objColor ) ;
ObjectSetInteger ( m_chartId , name , OBJPROP_WIDTH , 1 ) ;
ObjectSetInteger ( m_chartId , name , OBJPROP_SELECTABLE , true ) ;
ObjectSetInteger ( m_chartId , name , OBJPROP_SELECTED , true ) ;
ChartRedraw ( m_chartId ) ;
}
}
//+------------------------------------------------------------------+
//| Create a chart object that requires three clicks to place |
//+------------------------------------------------------------------+
void CDrawingEngine : : CreateThreeClickObject ( int sub , datetime t3 , double p3 , TOOL_TYPE toolType )
{
string name = MakeUniqueObjectName ( ) ; bool ok = false ; color objColor = clrDodgerBlue ;
//--- Retrieve stored first and second anchor points
datetime t1 = m_drawPoint1Time , t2 = m_drawPoint2Time ;
double p1 = m_drawPoint1Price , p2 = m_drawPoint2Price ;
switch ( toolType )
{
//--- Parallel channel defined by three anchor points
case TOOL_PARALLEL_CHANNEL :
ok = ObjectCreate ( m_chartId , name , OBJ_CHANNEL , sub , t1 , p1 , t2 , p2 , t3 , p3 ) ;
if ( ok ) objColor = clrCornflowerBlue ;
break ;
//--- Fibonacci channel defined by three anchor points
case TOOL_FIBO_CHANNEL :
ok = ObjectCreate ( m_chartId , name , OBJ_FIBOCHANNEL , sub , t1 , p1 , t2 , p2 , t3 , p3 ) ;
if ( ok ) objColor = clrGold ;
break ;
//--- Andrew's, Schiff, and Modified Schiff pitchforks share the same object type
case TOOL_PITCHFORK :
case TOOL_SCHIFF_PITCHFORK :
case TOOL_MOD_SCHIFF :
ok = ObjectCreate ( m_chartId , name , OBJ_PITCHFORK , sub , t1 , p1 , t2 , p2 , t3 , p3 ) ;
if ( ok ) objColor = clrMediumSeaGreen ;
break ;
default : break ;
}
//--- Apply common properties and select the new object
if ( ok )
{
ObjectSetInteger ( m_chartId , name , OBJPROP_COLOR , objColor ) ;
ObjectSetInteger ( m_chartId , name , OBJPROP_WIDTH , 1 ) ;
ObjectSetInteger ( m_chartId , name , OBJPROP_SELECTABLE , true ) ;
ObjectSetInteger ( m_chartId , name , OBJPROP_SELECTED , true ) ;
ChartRedraw ( m_chartId ) ;
}
}
//+------------------------------------------------------------------+
//| CLASS 11 — Top-level indicator class (entry point) |
//+------------------------------------------------------------------+
class CToolsSidebar : public CDrawingEngine
{
private :
TOOL_TYPE m_currentActiveTool ; // Currently active drawing or cursor tool
string m_currentInstruction ; // On-screen instruction text for multi-click tools
public :
CToolsSidebar ( ) { InitDefaults ( ) ; } // Initialise all members to safe defaults on construction
~ CToolsSidebar ( ) { Destroy ( ) ; } // Clean up all canvases and chart objects on destruction
//--- Initialise all canvases and register chart event hooks
bool Init ( long chartId ) ;
//--- Destroy all canvases, chart objects, and restore chart state
void Destroy ( ) ;
//--- Dispatch all incoming chart events
void OnEvent ( const int id , const long & lp , const double & dp , const string & sp ) ;
private :
//--- Set all member variables to their default starting values
void InitDefaults ( ) ;
//--- Toggle the given tool on or off as the active tool
void ToggleTool ( TOOL_TYPE toolType ) ;
//--- Deactivate the current tool and redraw
void DeactivateCurrentTool ( ) ;
//--- Clean up crosshair and measure mode when switching away from the crosshair tool
void CleanupCrosshairOnToolSwitch ( ) ;
//--- Remove all drawn chart objects created by this indicator
void CleanupAllDrawnObjects ( ) ;
//--- Update all crosshair and measure canvases for the current mouse position
void HandleCrosshairMouseMove ( int mouseX , int mouseY , bool overSidebar , bool overFlyout ) ;
} ;
//+------------------------------------------------------------------+
//| Set all member variables to their default starting values |
//+------------------------------------------------------------------+
void CToolsSidebar : : InitDefaults ( )
{
//--- Chart and canvas identity
m_chartId = 0 ;
m_nameSidebar = " ToolsPalette_Sidebar " ;
m_nameFlyout = " ToolsPalette_Flyout " ;
m_nameReticle = " ToolsPalette_Reticle " ;
m_nameMagnifier = " ToolsPalette_Magnifier " ;
m_nameCrossVertical = " ToolsPalette_CrosshairVertical " ;
m_nameCrossHorizontal = " ToolsPalette_CrosshairHorizontal " ;
m_nameCrossPriceLabel = " ToolsPalette_CrosshairPriceLabel " ;
m_nameCrossTimeLabel = " ToolsPalette_CrosshairTimeLabel " ;
m_nameMeasureVertical = " ToolsPalette_MeasureVertical " ;
m_nameMeasureHorizontal = " ToolsPalette_MeasureHorizontal " ;
m_nameMeasurePriceLabel = " ToolsPalette_MeasurePriceLabel " ;
m_nameMeasureTimeLabel = " ToolsPalette_MeasureTimeLabel " ;
m_nameMeasureDiagonalLine = " ToolsPalette_MeasureDiagonalLine " ;
//--- Rendering parameters
m_supersampleFactor = 4 ; // 4× supersampling multiplier
m_categoryButtonSize = 36 ; // Category button square size in pixels
m_categoryButtonPadding = 6 ; // Gap between category buttons in pixels
m_panelCornerRadius = 10 ; // Sidebar panel corner radius in pixels
m_headerGripHeight = 92 ; // Header strip height including close/grip/theme rows
m_sidebarWidth = 48 ; // Fixed sidebar width in pixels
m_sidebarHeight = 0 ; // Computed on first CalcSidebarHeight call
m_sidebarMaxVisibleCats = 0 ;
m_sidebarScrollPixels = 0 ;
m_sidebarScrollThumbHeight = 30 ;
m_sidebarScrollThinWidth = 3 ;
m_isSidebarThumbDragging = false ;
m_sidebarThumbDragStartY = 0 ;
m_sidebarThumbDragStartPixels = 0 ;
m_isHoveredSidebarScrollArea = false ;
m_isHoveredSidebarThumb = false ;
//--- Panel position and drag state
m_panelX = 0 ;
m_panelY = CanvasY ;
m_snapState = SNAP_LEFT ;
m_isPanelDragging = false ;
m_dragOffsetX = 0 ;
m_dragOffsetY = 0 ;
m_snappedSidebarHeight = 0 ;
m_isResizingBottomEdge = false ;
m_bottomResizeDragStartY = 0 ;
m_bottomResizeStartHeight = 0 ;
m_isBottomResizeHovered = false ;
//--- Sidebar hover state
m_hoveredCategory = CAT_NONE ;
m_isCloseButtonHovered = false ;
m_isThemeButtonHovered = false ;
m_isGripAreaHovered = false ;
//--- Flyout layout and state
m_flyoutWidth = 195 ;
m_flyoutItemHeight = 32 ;
m_flyoutPadding = 7 ;
m_flyoutPointerWidth = 10 ;
m_flyoutPointerHeight = 8 ;
m_flyoutPointerLocalY = 40 ;
m_flyoutPointerOnLeft = true ;
m_isFlyoutVisible = false ;
m_flyoutActiveCat = CAT_NONE ;
m_hoveredFlyoutItem = -1 ;
m_flyoutScrollPixels = 0 ;
m_flyoutMaxVisibleItems = 5 ;
m_flyoutScrollThumbHeight = 30 ;
m_isFlyoutThumbDragging = false ;
m_flyoutThumbDragStartY = 0 ;
m_flyoutThumbDragStartPixels = 0 ;
m_isHoveredFlyoutScrollArea = false ;
m_isHoveredFlyoutThumb = false ;
//--- Theme state
m_isDarkTheme = StartDark ;
//--- Crosshair and reticle state
m_reticleCanvasSize = 2 * ( ReticleOffset + ReticleTickLen / 2 ) + 6 ;
m_isReticleVisible = false ;
m_isMagnifierVisible = false ;
m_isCrossVertVisible = false ;
m_isCrossHorizVisible = false ;
m_isCrossPriceLabelVisible = false ;
m_isCrossTimeLabelVisible = false ;
//--- Measure mode state
m_isMeasureVertVisible = false ;
m_isMeasureHorizVisible = false ;
m_isMeasurePriceLabelVisible = false ;
m_isMeasureTimeLabelVisible = false ;
m_isMeasureDiagonalVisible = false ;
m_isMeasuringActive = false ;
m_measureAnchorTime = 0 ;
m_measureAnchorPrice = 0.0 ;
m_measureAnchorPixelX = 0 ;
m_measureAnchorPixelY = 0 ;
m_lastClickTimeMicros = 0 ;
m_lastMagMouseX = -9999 ;
m_lastMagMouseY = -9999 ;
//--- Mouse and drawing interaction state
m_previousMouseButtonState = 0 ;
m_currentActiveTool = TOOL_NONE ;
m_currentInstruction = " " ;
m_drawnObjectCounter = 0 ;
m_toolDrawingClickCount = 0 ;
m_drawPoint1Time = 0 ;
m_drawPoint2Time = 0 ;
m_drawPoint1Price = 0.0 ;
m_drawPoint2Price = 0.0 ;
}
//+------------------------------------------------------------------+
//| Initialise all canvases and register chart event hooks |
//+------------------------------------------------------------------+
bool CToolsSidebar ::Init ( long chartId )
{
//--- Reset all members and store the chart ID
InitDefaults ( ) ;
m_chartId = chartId ;
//--- Position the panel flush with the snap edge
m_panelX = ( m_snapState = = SNAP_RIGHT )
? ( int ) ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS ) - m_sidebarWidth : 0 ;
//--- Register all categories and tools, apply the starting theme, and compute layout
InitAllCategoriesAndTools ( ) ;
ApplyTheme ( ) ;
CalcSidebarHeight ( ) ;
//--- Create all canvas chart objects; abort init on failure
if ( ! CreateAllCanvases ( m_sidebarWidth , m_sidebarHeight ) ) return false ;
//--- Position and configure the sidebar chart object
ObjectSetInteger ( 0 , m_nameSidebar , OBJPROP_XDISTANCE , m_panelX ) ;
ObjectSetInteger ( 0 , m_nameSidebar , OBJPROP_YDISTANCE , m_panelY ) ;
ObjectSetInteger ( 0 , m_nameSidebar , OBJPROP_ZORDER , 100 ) ;
ObjectSetInteger ( 0 , m_nameFlyout , OBJPROP_ZORDER , 200 ) ;
//--- Perform the initial render pass
HideFlyout ( ) ;
DrawSidebar ( m_currentActiveTool ) ;
//--- Re-confirm position after the first draw (draw may resize internally)
ObjectSetInteger ( 0 , m_nameSidebar , OBJPROP_XDISTANCE , m_panelX ) ;
ObjectSetInteger ( 0 , m_nameSidebar , OBJPROP_YDISTANCE , m_panelY ) ;
//--- Enable mouse move and wheel events for this chart
ChartSetInteger ( 0 , CHART_EVENT_MOUSE_MOVE , true ) ;
ChartSetInteger ( 0 , CHART_EVENT_MOUSE_WHEEL , true ) ;
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , true ) ;
return true ;
}
//+------------------------------------------------------------------+
//| Destroy all canvases, chart objects, and restore chart state |
//+------------------------------------------------------------------+
void CToolsSidebar : : Destroy ( )
{
//--- Clean up crosshair and measure resources before destroying canvases
CleanupCrosshairOnToolSwitch ( ) ;
m_currentActiveTool = TOOL_NONE ;
//--- Destroy all canvas objects and their associated chart labels
DestroyAllCanvases ( ) ;
//--- Remove all drawn chart objects created during the session
CleanupAllDrawnObjects ( ) ;
//--- Restore chart scrolling
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , true ) ;
}
//+------------------------------------------------------------------+
//| Toggle the given tool on or off as the active tool |
//+------------------------------------------------------------------+
void CToolsSidebar : : ToggleTool ( TOOL_TYPE toolType )
{
//--- Clean up crosshair state before switching tools
CleanupCrosshairOnToolSwitch ( ) ;
if ( toolType = = TOOL_POINTER | | m_currentActiveTool = = toolType )
{
//--- Deactivate the current tool when toggling pointer or the same tool
m_currentActiveTool = TOOL_NONE ;
m_toolDrawingClickCount = 0 ;
m_currentInstruction = " " ;
}
else
{
//--- Activate the new tool and set the appropriate instruction text
m_currentActiveTool = toolType ;
m_toolDrawingClickCount = 0 ;
if ( toolType = = TOOL_CROSSHAIR )
{
m_currentInstruction = " Move mouse for crosshair. Double-click to measure. " ;
ShowAllCrosshairElements ( ) ;
}
else
m_currentInstruction = " Click on chart to place " + GetToolLabel ( toolType ) + " . " ;
}
}
//+------------------------------------------------------------------+
//| Deactivate the current tool and redraw |
//+------------------------------------------------------------------+
void CToolsSidebar : : DeactivateCurrentTool ( )
{
CleanupCrosshairOnToolSwitch ( ) ;
m_currentActiveTool = TOOL_NONE ;
m_toolDrawingClickCount = 0 ;
m_currentInstruction = " " ;
DrawSidebar ( m_currentActiveTool ) ;
ChartRedraw ( ) ;
}
//+------------------------------------------------------------------+
//| Clean up crosshair and measure mode when switching tools |
//+------------------------------------------------------------------+
void CToolsSidebar : : CleanupCrosshairOnToolSwitch ( )
{
//--- Only clean up if the crosshair was the active tool or measure mode is locked
if ( m_currentActiveTool = = TOOL_CROSSHAIR | | m_isMeasuringActive )
{
HideAllCrosshairElements ( ) ;
if ( m_isMeasuringActive )
{
//--- Release the measure anchor and restore chart scrolling
m_isMeasuringActive = false ;
DeleteAllMeasureObjects ( ) ;
ChartSetInteger ( 0 , CHART_MOUSE_SCROLL , true ) ;
}
}
}
//+------------------------------------------------------------------+
//| Remove all drawn chart objects created by this indicator |
//+------------------------------------------------------------------+
void CToolsSidebar : : CleanupAllDrawnObjects ( )
{
//--- Iterate in reverse to safely delete while counting
int total = ObjectsTotal ( m_chartId ) ;
for ( int i = total - 1 ; i > = 0 ; i - - )
{
string n = ObjectName ( m_chartId , i ) ;
//--- Delete any object whose name begins with the drawing prefix
if ( StringFind ( n , " ToolsPalette_Drawing_ " ) = = 0 ) ObjectDelete ( m_chartId , n ) ;
}
//--- Remove the floating measure info label
ObjectDelete ( m_chartId , " ToolsPalette_MeasureInfoLabel " ) ;
}
//+------------------------------------------------------------------+
//| Update all crosshair and measure canvases for the cursor pos |
//+------------------------------------------------------------------+
void CToolsSidebar : : HandleCrosshairMouseMove ( int mouseX , int mouseY , bool overSidebar , bool overFlyout )
{
//--- Skip if the crosshair tool is not active
if ( m_currentActiveTool ! = TOOL_CROSSHAIR ) return ;
//--- Hide all crosshair elements when the cursor is over a panel
if ( overSidebar | | overFlyout ) { HideAllCrosshairElements ( ) ; return ; }
//--- Show all crosshair elements when the cursor is on the chart
ShowAllCrosshairElements ( ) ;
datetime barTime ; double barPrice ; int subWindow ;
if ( ChartXYToTimePrice ( m_chartId , mouseX , mouseY , subWindow , barTime , barPrice ) )
{
//--- Update all crosshair line and label positions
UpdateCrossVerticalPosition ( mouseX ) ;
UpdateCrossHorizontalPosition ( mouseY ) ;
UpdateCrosshairAxisLabels ( mouseX , mouseY , barTime , barPrice ) ;
UpdateReticlePosition ( mouseX , mouseY ) ;
UpdateMagnifierPosition ( mouseX , mouseY , barTime , barPrice ) ;
//--- Update measure mode elements if measuring is active
if ( m_isMeasuringActive )
{
int fx = 0 , fy = 0 ;
if ( ChartTimePriceToXY ( m_chartId , 0 , m_measureAnchorTime , m_measureAnchorPrice , fx , fy ) )
{
ShowMeasureLines ( ) ;
UpdateMeasureVerticalPosition ( fx ) ;
UpdateMeasureHorizontalPosition ( fy ) ;
UpdateMeasureAnchorLabels ( ) ;
}
UpdateMeasureDiagonalLine ( mouseX , mouseY ) ;
UpdateMeasurementInfoLabel ( mouseX , mouseY , barTime , barPrice ) ;
}
ChartRedraw ( ) ;
}
}
//+------------------------------------------------------------------+
//| Dispatch all incoming chart events |
//+------------------------------------------------------------------+
void CToolsSidebar : : OnEvent ( const int id , const long & lp , const double & dp , const string & sp )
{
//--- Deactivate the active tool on Escape key press
if ( id = = CHARTEVENT_KEYDOWN & & lp = = 27 ) { DeactivateCurrentTool ( ) ; return ; }
if ( id = = CHARTEVENT_MOUSE_MOVE )
{
int mouseX = ( int ) lp , mouseY = ( int ) dp , mouseButtons = ( int ) sp ;
int lx , ly , flx , fly ;
bool overSidebar = HitTestOverSidebar ( mouseX , mouseY , lx , ly ) ;
bool overFlyout = ! overSidebar & & HitTestOverFlyout ( mouseX , mouseY , flx , fly ) ;
//--- Handle theme toggle button click
if ( mouseButtons = = 1 & & m_previousMouseButtonState = = 0 & &
overSidebar & & m_isThemeButtonHovered )
{
ToggleTheme ( ) ;
DrawSidebar ( m_currentActiveTool ) ;
if ( m_isFlyoutVisible ) DrawFlyoutForCategory ( m_flyoutActiveCat , m_currentActiveTool ) ;
//--- Redraw crosshair canvases to match the new theme foreground colour
if ( m_isCrossVertVisible ) { int chartH = ( int ) ChartGetInteger ( 0 , CHART_HEIGHT_IN_PIXELS ) ; DrawCrossVerticalLinePixels ( chartH ) ; }
if ( m_isCrossHorizVisible ) { int chartW = ( int ) ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS ) ; DrawCrossHorizontalLinePixels ( chartW ) ; }
if ( m_isReticleVisible ) DrawReticleTickMarks ( ) ;
ChartRedraw ( ) ;
m_previousMouseButtonState = mouseButtons ;
return ;
}
//--- Handle flyout item selection click
if ( mouseButtons = = 1 & & m_previousMouseButtonState = = 0 & &
overFlyout & & m_hoveredFlyoutItem > = 0 & & m_flyoutActiveCat ! = CAT_NONE )
{
int nT = ArraySize ( m_categories [ ( int ) m_flyoutActiveCat ] . tools ) ;
if ( m_hoveredFlyoutItem < nT )
{
ToggleTool ( m_categories [ ( int ) m_flyoutActiveCat ] . tools [ m_hoveredFlyoutItem ] . toolType ) ;
HideFlyout ( ) ;
DrawSidebar ( m_currentActiveTool ) ;
ChartRedraw ( ) ;
}
m_previousMouseButtonState = mouseButtons ;
return ;
}
//--- Handle single-tool category button click
if ( mouseButtons = = 1 & & m_previousMouseButtonState = = 0 & &
overSidebar & & m_hoveredCategory ! = CAT_NONE & &
! m_isCloseButtonHovered & & ! m_isThemeButtonHovered & & ! m_isGripAreaHovered & &
ArraySize ( m_categories [ ( int ) m_hoveredCategory ] . tools ) = = 1 )
{
ToggleTool ( m_categories [ ( int ) m_hoveredCategory ] . tools [ 0 ] . toolType ) ;
HideFlyout ( ) ;
DrawSidebar ( m_currentActiveTool ) ;
ChartRedraw ( ) ;
m_previousMouseButtonState = mouseButtons ;
return ;
}
//--- Handle crosshair double-click to toggle measure mode anchor
if ( mouseButtons = = 1 & & m_previousMouseButtonState = = 0 & &
m_currentActiveTool = = TOOL_CROSSHAIR & & ! overSidebar & & ! overFlyout )
{
datetime barTime ; double barPrice ; int subWindow ;
if ( ChartXYToTimePrice ( m_chartId , mouseX , mouseY , subWindow , barTime , barPrice ) )
HandleCrosshairDoubleClick ( mouseX , mouseY , barTime , barPrice ) ;
m_previousMouseButtonState = mouseButtons ;
return ;
}
//--- Handle drawing tool placement click on the chart
if ( mouseButtons = = 1 & & m_previousMouseButtonState = = 0 & &
m_currentActiveTool ! = TOOL_NONE & &
m_currentActiveTool ! = TOOL_CROSSHAIR & &
m_currentActiveTool ! = TOOL_POINTER & &
! overSidebar & & ! overFlyout )
{
HandleDrawingClick ( mouseX , mouseY , m_currentActiveTool , m_currentInstruction ) ;
DrawSidebar ( m_currentActiveTool ) ;
m_previousMouseButtonState = mouseButtons ;
return ;
}
//--- Update crosshair canvas positions for the current cursor location
HandleCrosshairMouseMove ( mouseX , mouseY , overSidebar , overFlyout ) ;
//--- Delegate remaining mouse move processing to the event handler chain
RouteChartEvent ( id , lp , dp , sp , m_currentActiveTool ) ;
//--- Update the sidebar tooltip text based on what the cursor is hovering over
string tip = " " ;
if ( overSidebar & & m_hoveredCategory ! = CAT_NONE )
tip = m_categories [ ( int ) m_hoveredCategory ] . categoryLabel ;
if ( overFlyout & & m_hoveredFlyoutItem > = 0 & & m_flyoutActiveCat ! = CAT_NONE )
{
int nT = ArraySize ( m_categories [ ( int ) m_flyoutActiveCat ] . tools ) ;
if ( m_hoveredFlyoutItem < nT )
tip = m_categories [ ( int ) m_flyoutActiveCat ] . tools [ m_hoveredFlyoutItem ] . tooltipText ;
}
ObjectSetString ( 0 , m_nameSidebar , OBJPROP_TOOLTIP , tip ) ;
return ;
}
//--- Delegate all non-mouse-move chart events to the routing chain
RouteChartEvent ( id , lp , dp , sp , m_currentActiveTool ) ;
}
//+------------------------------------------------------------------+
//| Global indicator instance |
//+------------------------------------------------------------------+
CToolsSidebar g_sidebar ;
//+------------------------------------------------------------------+
//| Expert initialisation function |
//+------------------------------------------------------------------+
int OnInit ( )
{
//--- Initialise the sidebar with the current chart ID
if ( ! g_sidebar .Init ( ChartID ( ) ) ) return INIT_FAILED ;
ChartRedraw ( ) ;
return INIT_SUCCEEDED ;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit ( const int reason )
{
//--- Destroy all canvases and chart objects on removal
g_sidebar . Destroy ( ) ;
ChartRedraw ( ) ;
}
//+------------------------------------------------------------------+
//| Chart event handler — forward all events to the sidebar |
//+------------------------------------------------------------------+
void OnChartEvent ( const int id , const long & lp , const double & dp , const string & sp )
{
g_sidebar . OnEvent ( id , lp , dp , sp ) ;
}
//+------------------------------------------------------------------+
//| Tick handler — no per-tick logic required |
//+------------------------------------------------------------------+
void OnTick ( ) { }
//+------------------------------------------------------------------+