Article-22495-Dispatch-Driv.../AI Canvas Scrollbar.mqh

255 lines
No EOL
10 KiB
MQL5

//+------------------------------------------------------------------+
//| AI Canvas Scrollbar.mqh |
//| 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"
//--- Include guard
#ifndef AI_CANVAS_SCROLLBAR_MQH
#define AI_CANVAS_SCROLLBAR_MQH
//--- Include required modules
#include "AI Canvas Theme.mqh"
#include "AI Canvas Primitives.mqh"
//+------------------------------------------------------------------+
//| Scrollbar State Structure |
//+------------------------------------------------------------------+
struct AiScrollState
{
int trackL; // Track left edge
int trackT; // Track top edge
int trackR; // Track right edge
int trackB; // Track bottom edge
int scrollPx; // Current scroll offset in pixels
int viewportH; // Visible viewport height
int totalH; // Total scrollable content height
bool hoveredArea; // Cursor is inside the scrollbar area
bool hoveredThumb; // Cursor is over the scrollbar thumb
bool dragging; // Thumb is being dragged
bool hover; // Generic hover flag
int dragOriginPx; // Scroll position at drag start
int dragOriginY; // Mouse Y at drag start
};
//+------------------------------------------------------------------+
//| Reset scroll state to defaults |
//+------------------------------------------------------------------+
void AiScrollInit(AiScrollState &s)
{
//--- Zero track and scroll metrics
s.trackL = 0; s.trackT = 0; s.trackR = 0; s.trackB = 0;
s.scrollPx = 0;
s.viewportH = 0;
s.totalH = 0;
//--- Clear hover and drag flags
s.hoveredArea = false;
s.hoveredThumb = false;
s.hover = false;
s.dragging = false;
s.dragOriginPx = 0;
s.dragOriginY = 0;
}
//+------------------------------------------------------------------+
//| Compute maximum allowed scroll offset |
//+------------------------------------------------------------------+
int AiScrollMax(const AiScrollState &s)
{
//--- Cap minimum at zero when content fits in viewport
int m = s.totalH - s.viewportH;
return (m < 0) ? 0 : m;
}
//+------------------------------------------------------------------+
//| Test if scrollbar should be visible |
//+------------------------------------------------------------------+
bool AiScrollVisible(const AiScrollState &s)
{
//--- Only visible when content overflows viewport
return AiScrollMax(s) > 0;
}
//+------------------------------------------------------------------+
//| Clamp scroll offset within valid range |
//+------------------------------------------------------------------+
void AiScrollClamp(AiScrollState &s)
{
//--- Clamp scroll position between 0 and max
int m = AiScrollMax(s);
if(s.scrollPx < 0) s.scrollPx = 0;
if(s.scrollPx > m) s.scrollPx = m;
}
//+------------------------------------------------------------------+
//| Compute thumb top and bottom Y coordinates |
//+------------------------------------------------------------------+
bool AiScrollThumbRect(const AiScrollState &s, int &thT, int &thB)
{
//--- Bail when scrollbar is not visible
if(!AiScrollVisible(s)) { thT = 0; thB = 0; return false; }
//--- Bail when track has no height
int trackH = s.trackB - s.trackT;
if(trackH <= 0) { thT = 0; thB = 0; return false; }
//--- Size thumb proportional to viewport-to-content ratio
int thumbH = (int)((double)trackH * (double)s.viewportH / (double)s.totalH);
if(thumbH < 20) thumbH = 20;
if(thumbH > trackH) thumbH = trackH;
//--- Position thumb based on scroll ratio
int maxScroll = AiScrollMax(s);
int travel = trackH - thumbH;
double ratio = (maxScroll == 0) ? 0.0 : (double)s.scrollPx / (double)maxScroll;
thT = s.trackT + (int)((double)travel * ratio);
thB = thT + thumbH;
return true;
}
//+------------------------------------------------------------------+
//| Test if point is over the scrollbar thumb |
//+------------------------------------------------------------------+
bool AiScrollHitTestThumb(const AiScrollState &s, int x, int y)
{
//--- Get thumb rect and test point
int thT, thB;
if(!AiScrollThumbRect(s, thT, thB)) return false;
return AiPointInRect(x, y, s.trackL, thT, s.trackR - s.trackL, thB - thT);
}
//+------------------------------------------------------------------+
//| Test if point is over the scrollbar area (track + padding) |
//+------------------------------------------------------------------+
bool AiScrollHitTestArea(const AiScrollState &s, int x, int y)
{
//--- Bail when scrollbar is not visible
if(!AiScrollVisible(s)) return false;
//--- Test against padded track for easier hover targeting
const int pad = 6;
return AiPointInRect(x, y, s.trackL - pad, s.trackT,
(s.trackR - s.trackL) + 2 * pad, s.trackB - s.trackT);
}
//+------------------------------------------------------------------+
//| Draw scrollbar thumb pill onto canvas |
//+------------------------------------------------------------------+
void AiScrollDraw(CCanvas &canvas, AiScrollState &s, CAiCanvasPrimitives &prim)
{
//--- Skip drawing when not hovered and not dragging
if(!s.hoveredArea && !s.dragging) return;
//--- Get thumb rect and bail if invalid
int thT, thB;
if(!AiScrollThumbRect(s, thT, thB)) return;
const int sbW = s.trackR - s.trackL;
const int thumbH = thB - thT;
if(sbW <= 0 || thumbH <= 0) return;
//--- Pick pill color and alpha by interaction state
color pillCol;
uchar pillA;
if(s.dragging) { pillCol = g_ai_scrollSliderDrag; pillA = 255; }
else if(s.hoveredThumb) { pillCol = g_ai_scrollSliderHover; pillA = 255; }
else { pillCol = g_ai_scrollSlider; pillA = 180; }
const uint pillArgb = ColorToARGB(pillCol, pillA);
//--- Setup high-res super-sampling factor
const int factor = 4;
const int pws = sbW * factor;
const int phs = thumbH * factor;
//--- Reuse cached HR fill canvas from primitives helper
if(!prim.EnsureHrFill(pws, phs)) return;
prim.m_hrFill.FillRectangle(0, 0, pws - 1, phs - 1, 0);
prim.FillRoundRectHR(prim.m_hrFill, 0, 0, pws, phs, MathMax(1, pws / 2), pillArgb);
//--- Downsample HR pill back onto canvas with alpha blending
const int ss2 = factor * factor;
for(int py = 0; py < thumbH; py++)
for(int px = 0; px < sbW; px++)
{
//--- Sum HR sub-pixels for this output pixel
double sumA = 0, sumR = 0, sumG = 0, sumB = 0, wc = 0;
for(int dy = 0; dy < factor; dy++)
for(int dx = 0; dx < factor; dx++)
{
const int sx = px * factor + dx, sy = py * factor + dy;
if(sx >= pws || sy >= phs) continue;
const uint p = prim.m_hrFill.PixelGet(sx, sy);
const uchar pa = (uchar)((p >> 24) & 0xFF);
sumA += pa;
if(pa > 0) { sumR += (p >> 16) & 0xFF; sumG += (p >> 8) & 0xFF; sumB += p & 0xFF; wc += 1.0; }
}
//--- Compute averaged final pixel and blend onto canvas
const uchar fa = (uchar)(sumA / ss2);
if(fa > 0 && wc > 0)
prim.BlendPixelSet(canvas, s.trackL + px, thT + py,
((uint)fa << 24) | ((uint)(uchar)(sumR / wc) << 16) |
((uint)(uchar)(sumG / wc) << 8) | (uint)(uchar)(sumB / wc));
}
}
//+------------------------------------------------------------------+
//| Apply mouse-wheel scroll delta |
//+------------------------------------------------------------------+
void AiScrollByWheel(AiScrollState &s, int delta, int step = 30)
{
//--- Negative delta scrolls up, positive scrolls down
if(delta > 0) s.scrollPx -= step;
else s.scrollPx += step;
AiScrollClamp(s);
}
//+------------------------------------------------------------------+
//| Begin scrollbar thumb drag |
//+------------------------------------------------------------------+
void AiScrollBeginDrag(AiScrollState &s, int mouseY)
{
//--- Snapshot scroll position and mouse Y at drag start
s.dragging = true;
s.dragOriginPx = s.scrollPx;
s.dragOriginY = mouseY;
}
//+------------------------------------------------------------------+
//| Update scroll position during thumb drag |
//+------------------------------------------------------------------+
void AiScrollUpdateDrag(AiScrollState &s, int mouseY)
{
//--- Bail when not actively dragging
if(!s.dragging) return;
//--- Get thumb rect and bail when invalid
int trackH = s.trackB - s.trackT;
int thT, thB;
if(!AiScrollThumbRect(s, thT, thB)) { s.dragging = false; return; }
//--- Bail when no travel room
int thumbH = thB - thT;
int travel = trackH - thumbH;
if(travel <= 0) return;
//--- Compute new scroll position from mouse delta ratio
int dy = mouseY - s.dragOriginY;
double ratio = (double)dy / (double)travel;
int maxScroll = AiScrollMax(s);
s.scrollPx = s.dragOriginPx + (int)(ratio * maxScroll);
AiScrollClamp(s);
}
//+------------------------------------------------------------------+
//| End scrollbar thumb drag |
//+------------------------------------------------------------------+
void AiScrollEndDrag(AiScrollState &s)
{
//--- Clear drag flag
s.dragging = false;
}
#endif // AI_CANVAS_SCROLLBAR_MQH