﻿//+------------------------------------------------------------------+
//|                                ToolsPalette_Engine_Render.mqh    |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#ifndef TOOLS_PALETTE_ENGINE_RENDER_MQH
#define TOOLS_PALETTE_ENGINE_RENDER_MQH

//--- Include CDrawingEngine class declaration; guard prevents double-inclusion via the main chain
#include "ToolsPalette_Tools.mqh"

//+------------------------------------------------------------------+
//| Render permanent axis label pills for every HLine, VLine, Cross  |
//+------------------------------------------------------------------+
void CDrawingEngine::RedrawPermanentAxisLabels()
  {
   //--- Read canvas dimensions and symbol digit count
   int cW     = m_canvasDrawings.Width();
   int cH     = m_canvasDrawings.Height();
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   //--- Iterate every stored drawn object looking for line-type tools
   int n = ArraySize(m_drawnObjects);
   for(int i = 0; i < n; i++)
     {
      //--- Skip hidden objects
      if(!m_drawnObjects[i].visible) continue;
      TOOL_TYPE tt = m_drawnObjects[i].toolType;
      //--- Only process HLine, VLine, and Cross Line objects
      if(tt != TOOL_HLINE && tt != TOOL_VLINE && tt != TOOL_CROSS_LINE) continue;
      //--- Cross Line renders two pills (price + time); all others render one
      int passCount = (tt == TOOL_CROSS_LINE) ? 2 : 1;
      for(int pass = 0; pass < passCount; pass++)
        {
         //--- Resolve effective tool type: Cross Line pass 0 = price pill, pass 1 = time pill
         TOOL_TYPE effTT = tt;
         if(tt == TOOL_CROSS_LINE) effTT = (pass == 0) ? TOOL_HLINE : TOOL_VLINE;
         //--- Build pill label text: price string for HLine, time string for VLine
         string labelText = (effTT == TOOL_HLINE)
            ? DoubleToString(m_drawnObjects[i].price1, digits)
            : TimeToString(m_drawnObjects[i].time1, TIME_DATE | TIME_MINUTES);
         //--- Skip if no label text was produced
         if(StringLen(labelText) == 0) continue;
         //--- Convert the anchor time/price to screen pixel coordinates
         int hx = 0, hy = 0;
         ChartTimePriceToXY(m_chartId, 0,
                             m_drawnObjects[i].time1,
                             m_drawnObjects[i].price1,
                             hx, hy);
         //--- Use the object's user-chosen color as the pill background
         color pillBgColor = m_drawnObjects[i].objColor;
         //--- Extract RGB components to compute luminance for contrast selection
         uchar lR = (uchar)((pillBgColor)       & 0xFF);
         uchar lG = (uchar)((pillBgColor >> 8)  & 0xFF);
         uchar lB = (uchar)((pillBgColor >> 16) & 0xFF);
         double luminance = 0.299 * lR + 0.587 * lG + 0.114 * lB;
         bool   bgIsDark  = (luminance < 128.0);
         //--- Pick white text on dark backgrounds, black on light backgrounds
         color  textColor    = bgIsDark ? clrWhite : clrBlack;
         uint   pillBgARGB   = ColorToARGB(pillBgColor, 255);
         uint   pillTextARGB = ColorToARGB(textColor,   255);
         //--- Measure the rendered text dimensions at 9 pt Arial
         TextSetFont("Arial", -(9 * 10));
         uint twU = 0, thU = 0;
         TextGetSize(labelText, twU, thU);
         int tw = (int)twU, th = (int)thU;
         //--- Compute pill dimensions with padding, enforcing a minimum size
         int padX = 4, padY = 2;
         int lw = tw + padX * 2;
         int lh = th + padY * 2;
         if(lw < 20) lw = 20;
         if(lh < 12) lh = 12;
         //--- Compute pill top-left canvas position based on line type
         int lx, ly;
         if(effTT == TOOL_HLINE)
           {
            //--- Pin to the right canvas edge, vertically centered on the line's Y
            lx = cW - lw;
            if(lx < 0) lx = 0;
            ly = hy - lh / 2;
            if(ly < 0) ly = 0;
            if(ly + lh > cH) ly = cH - lh;
           }
         else
           {
            //--- Pin to the bottom canvas edge, horizontally centered on the line's X
            lx = hx - lw / 2;
            if(lx < 0) lx = 0;
            if(lx + lw > cW) lx = cW - lw;
            ly = cH - lh;
            if(ly < 0) ly = 0;
           }
         //--- Render label text into an offscreen pixel buffer filled with the pill background
         uint buf[];
         ArrayResize(buf, lw * lh);
         ArrayFill(buf, 0, lw * lh, pillBgARGB);
         TextOut(labelText, padX, padY, TA_LEFT | TA_TOP, buf, lw, lh,
                 pillTextARGB, COLOR_FORMAT_ARGB_NORMALIZE);
         //--- Blit the offscreen buffer onto the drawings canvas at the computed position
         for(int py = 0; py < lh; py++)
           {
            int dy = ly + py;
            //--- Skip rows outside the canvas bounds
            if(dy < 0 || dy >= cH) continue;
            for(int px = 0; px < lw; px++)
              {
               int dx = lx + px;
               //--- Skip columns outside the canvas bounds
               if(dx < 0 || dx >= cW) continue;
               m_canvasDrawings.PixelSet(dx, dy, buf[py * lw + px]);
              }
           }
         //--- Draw a 1-px top and bottom border row on the pill for contrast
         for(int px = 0; px < lw; px++)
           {
            int dx = lx + px;
            if(dx < 0 || dx >= cW) continue;
            if(ly          >= 0 && ly          < cH) m_canvasDrawings.PixelSet(dx, ly,          pillTextARGB);
            if(ly + lh - 1 >= 0 && ly + lh - 1 < cH) m_canvasDrawings.PixelSet(dx, ly + lh - 1, pillTextARGB);
           }
         //--- Draw a 1-px left and right border column on the pill for contrast
         for(int py = 0; py < lh; py++)
           {
            int dy = ly + py;
            if(dy < 0 || dy >= cH) continue;
            if(lx          >= 0 && lx          < cW) m_canvasDrawings.PixelSet(lx,          dy, pillTextARGB);
            if(lx + lw - 1 >= 0 && lx + lw - 1 < cW) m_canvasDrawings.PixelSet(lx + lw - 1, dy, pillTextARGB);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Delete permanent axis label bitmap for a specific object ID      |
//+------------------------------------------------------------------+
void CDrawingEngine::DeletePermanentAxisLabelsFor(int objId)
  {
   //--- Build the legacy per-object bitmap label name
   string lblName = StringFormat("tp_axislbl_%d", objId);
   //--- Delete the stale chart object if it still exists from a prior session
   if(ObjectFind(0, lblName) >= 0) ObjectDelete(0, lblName);
  }

//+------------------------------------------------------------------+
//| Redraw all stored drawn objects onto the drawings canvas         |
//+------------------------------------------------------------------+
void CDrawingEngine::RedrawAllObjects()
  {
   //--- Clear the entire drawings canvas to fully transparent
   m_canvasDrawings.Erase(0x00000000);
   //--- Reset per-frame prompt and label hit IDs before rendering
   m_addTextPromptObjId       = -1;
   m_labelHitObjIdForSelected = -1;
   //--- Iterate every stored drawn object and render it
   int n = ArraySize(m_drawnObjects);
   for(int i = 0; i < n; i++)
     {
      //--- Skip hidden objects
      if(!m_drawnObjects[i].visible) continue;
      //--- Read this object's stroke color and text color
      color col     = m_drawnObjects[i].objColor;
      color colText = m_drawnObjects[i].textColor;
      //--- Derive selection from the canonical engine ID rather than the per-object flag
      //--- to guarantee at most one selected object even if stale flags are present
      bool sel     = (m_drawnObjects[i].id == m_selectedObjectId);
      bool hovered = (m_drawnObjects[i].id == m_hoveredObjectId);
      //--- Resolve all three anchor points to screen pixel coordinates
      int x1 = 0, y1 = 0, x2 = 0, y2 = 0, x3 = 0, y3 = 0;
      ChartTimePriceToXY(m_chartId, 0, m_drawnObjects[i].time1, m_drawnObjects[i].price1, x1, y1);
      if(m_drawnObjects[i].time2 != 0)
         ChartTimePriceToXY(m_chartId, 0, m_drawnObjects[i].time2, m_drawnObjects[i].price2, x2, y2);
      if(m_drawnObjects[i].time3 != 0)
         ChartTimePriceToXY(m_chartId, 0, m_drawnObjects[i].time3, m_drawnObjects[i].price3, x3, y3);
      //--- Configure handle hide index: hide the dragged handle so it appears picked up
      bool isThisSelected   = (m_drawnObjects[i].id == m_selectedObjectId);
      m_hideHandleIdx = (isThisSelected && m_isDraggingHandle) ? m_draggedHandleIdx : -1;
      //--- Configure handle halo index: highlight the hovered handle except during drag
      bool isThisHandleHost = (m_drawnObjects[i].id == m_hoveredHandleHostId);
      m_haloHandleIdx = (isThisHandleHost && !m_isDraggingHandle && !m_isDraggingObject)
                        ? m_hoveredHandleIdx : -1;
      //--- Dispatch to the appropriate per-tool draw routine
      switch(m_drawnObjects[i].toolType)
        {
         case TOOL_TRENDLINE:
            DrawTrendLineOn(m_canvasDrawings, x1, y1, x2, y2, col, sel, hovered,
                            m_drawnObjects[i].lineWidth,
                            m_drawnObjects[i].lineOpacity,
                            m_drawnObjects[i].lineStyle);
            break;
         case TOOL_HLINE:
            DrawHorizontalLineOn(m_canvasDrawings, y1, col, sel, hovered,
                                 m_drawnObjects[i].lineWidth,
                                 m_drawnObjects[i].lineOpacity,
                                 m_drawnObjects[i].lineStyle);
            break;
         case TOOL_VLINE:
            DrawVerticalLineOn(m_canvasDrawings, x1, col, sel, hovered,
                               m_drawnObjects[i].lineWidth,
                               m_drawnObjects[i].lineOpacity,
                               m_drawnObjects[i].lineStyle);
            break;
         case TOOL_CROSS_LINE:
            DrawCrossLineOn(m_canvasDrawings, x1, y1, col, sel, hovered,
                            m_drawnObjects[i].lineWidth,
                            m_drawnObjects[i].lineOpacity,
                            m_drawnObjects[i].lineStyle);
            break;
         case TOOL_RAY:
            DrawRayLineOn(m_canvasDrawings, x1, y1, x2, y2, col, sel, hovered,
                          m_drawnObjects[i].lineWidth,
                          m_drawnObjects[i].lineOpacity,
                          m_drawnObjects[i].lineStyle);
            break;
         case TOOL_EXTENDED_LINE:
            DrawExtendedLineOn(m_canvasDrawings, x1, y1, x2, y2, col, sel, hovered,
                               m_drawnObjects[i].lineWidth,
                               m_drawnObjects[i].lineOpacity,
                               m_drawnObjects[i].lineStyle);
            break;
         case TOOL_INFO_LINE:
            DrawInfoLineOn(m_canvasDrawings, x1, y1, x2, y2, col,
                           m_drawnObjects[i].time1, m_drawnObjects[i].time2,
                           m_drawnObjects[i].price1, m_drawnObjects[i].price2,
                           sel, hovered, m_isDarkTheme,
                           m_drawnObjects[i].lineWidth,
                           m_drawnObjects[i].lineOpacity,
                           m_drawnObjects[i].lineStyle);
            break;
         case TOOL_TREND_ANGLE:
            DrawTrendAngleOn(m_canvasDrawings, x1, y1, x2, y2, col, sel, hovered,
                             m_isDarkTheme,
                             m_drawnObjects[i].lineWidth,
                             m_drawnObjects[i].lineOpacity,
                             m_drawnObjects[i].lineStyle);
            break;
         case TOOL_RECTANGLE:
            DrawRectangleOn(m_canvasDrawings, x1, y1, x2, y2, col, sel, hovered,
                            m_drawnObjects[i].lineWidth,
                            m_drawnObjects[i].lineOpacity,
                            m_drawnObjects[i].lineStyle,
                            m_drawnObjects[i].fillColor,
                            m_drawnObjects[i].fillOpacity);
            break;
         default: break;
        }
     }
   //--- Reset handle display state after loop to prevent leaking into preview renders
   m_hideHandleIdx = -1;
   m_haloHandleIdx = -1;
   //--- Draw rubber band preview if a two-click placement is in progress after the first click
   if(m_isPreviewActive && m_toolDrawingClickCount == 1)
     {
      //--- Resolve the first click anchor point to screen coordinates
      int px1 = 0, py1 = 0;
      ChartTimePriceToXY(m_chartId, 0, m_drawPoint1Time, m_drawPoint1Price, px1, py1);
      //--- Use the tool's default commit color for the preview stroke
      color previewColor = GetToolDefaultColor(m_previewToolType);
      if(m_previewToolType == TOOL_RECTANGLE)
        {
         //--- Rectangle preview: render the full committed look (fill + border + handles) as the user drags
         DrawRectangleOn(m_canvasDrawings, px1, py1,
                         m_previewMouseX, m_previewMouseY,
                         previewColor, false, true);
        }
      else
        {
         //--- All other tools: render a generic dashed rubber-band line from P1 to the cursor
         DrawRubberBand(px1, py1, m_previewMouseX, m_previewMouseY, m_previewToolType, previewColor);
        }
      //--- Read the current cursor time/price for potential axis label preview use
      double previewPrice; datetime previewTime; int sub;
      ChartXYToTimePrice(m_chartId, m_previewMouseX, m_previewMouseY, sub, previewTime, previewPrice);
     }
   //--- Render permanent axis pills for HLine/VLine directly onto the drawings canvas (no flicker)
   RedrawPermanentAxisLabels();
   //--- Flush the completed drawings canvas to screen
   m_canvasDrawings.Update();
   //--- Update the dedicated hover/select axis label canvases for trendlines and similar tools
   UpdateObjLabels();
  }

//+------------------------------------------------------------------+
//| Draw a rectangle between two diagonal corners                    |
//+------------------------------------------------------------------+
void CDrawingEngine::DrawRectangle(int x1, int y1, int x2, int y2, color objColor, bool selected, bool hovered)
  {
   //--- Delegate to CShapeTools::DrawRectangleOn for fill, border, and handle rendering
   DrawRectangleOn(m_canvasDrawings, x1, y1, x2, y2, objColor, selected, hovered);
  }

#endif // TOOLS_PALETTE_ENGINE_RENDER_MQH
//+------------------------------------------------------------------+