//+------------------------------------------------------------------+ //| 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