//+------------------------------------------------------------------+
//|                                              RSTL (gradient).mq5 |
//|                                Copyright © 2002, Finware.ru Ltd. |
//|                                           http://www.finware.ru/ |
//+------------------------------------------------------------------+
// RSTL = Reference Slow Trend Line
#property copyright   "Copyright © 2002, Finware.ru Ltd. Fog Gradient & Button by Banzai"
#property link        "https://forex-station.com/post1295586880.html#p1295586880"
#property version     "1.00"
#property description "Reference Slow Trend Line with On/Off Toggle Button and Fog Gradient Fill to Candlesticks"
#property strict

#property indicator_chart_window
#property indicator_buffers 5
#property indicator_plots   1

//---- RSTL Color Line (0: Neutral/Gray, 1: Bearish/Tomato, 2: Bullish/DeepSkyBlue)
#property indicator_type1   DRAW_COLOR_LINE
#property indicator_color1  clrGray,clrTomato,clrDeepSkyBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2
#property indicator_label1  "RSTL"

#include <Canvas/Canvas.mqh>

//+------------------------------------------------------------------+
//| 33 Prices Enumeration                                            |
//+------------------------------------------------------------------+
enum enPrices
{
   pr_close,      // Close
   pr_open,       // Open
   pr_high,       // High
   pr_low,        // Low
   pr_median,     // Median
   pr_typical,    // Typical
   pr_weighted,   // Weighted
   pr_average,    // Average (high+low+open+close)/4
   pr_medianb,    // Average median body (open+close)/2
   pr_tbiased,    // Trend biased price
   pr_tbiased2,   // Trend biased (extreme) price
   pr_haclose,    // Heiken ashi close
   pr_haopen,     // Heiken ashi open
   pr_hahigh,     // Heiken ashi high
   pr_halow,      // Heiken ashi low
   pr_hamedian,   // Heiken ashi median
   pr_hatypical,  // Heiken ashi typical
   pr_haweighted, // Heiken ashi weighted
   pr_haaverage,  // Heiken ashi average
   pr_hamedianb,  // Heiken ashi median body
   pr_hatbiased,  // Heiken ashi trend biased price
   pr_hatbiased2, // Heiken ashi trend biased (extreme) price
   pr_habclose,   // Heiken ashi (better formula) close
   pr_habopen,    // Heiken ashi (better formula) open
   pr_habhigh,    // Heiken ashi (better formula) high
   pr_hablow,     // Heiken ashi (better formula) low
   pr_habmedian,  // Heiken ashi (better formula) median
   pr_habtypical, // Heiken ashi (better formula) typical
   pr_habweighted,// Heiken ashi (better formula) weighted
   pr_habaverage, // Heiken ashi (better formula) average
   pr_habmedianb, // Heiken ashi (better formula) median body
   pr_habtbiased, // Heiken ashi (better formula) trend biased price
   pr_habtbiased2 // Heiken ashi (better formula) trend biased (extreme) price
};

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "=== RSTL Settings ==="
input int                RSTLShift             = 0;                  // Horizontal shift of moving average in bars
input enPrices           Price                 = pr_close;           // Price to use for RSTL calculation

input group "=== Fog Gradient Fill Settings ==="
input bool               enableFilling         = true;               // Enable Fog Gradient Fill
input int                fillOpacity           = 128;                // Fog Opacity (0-255)
input color              fogUpColor            = clrDeepSkyBlue;     // Bullish Fog Color
input color              fogDownColor          = clrTomato;          // Bearish Fog Color
input int                candleEmaPeriod       = 1;                  // Candlestick Fast EMA Period (1 = Candle Close)
input ENUM_APPLIED_PRICE candleAppliedPrice    = PRICE_CLOSE;        // Price Applied for Candlesticks
input bool               invertFog             = false;              // Invert Fog (Opaque at Candle, Fade to RSTL)

//Forex-Station copy & paste code; Button code start 11
input group "=== Toggle Button Settings ==="
input string             button_note1          = "------------------------------"; // ------------------------------
input int                btn_Subwindow         = 0;                  // What window to put the button on
input ENUM_BASE_CORNER   btn_corner            = CORNER_LEFT_UPPER;  // chart btn_corner for anchoring
input string             btn_text              = "RSTL: ON";         // Display label
input string             btn_Font              = "Arial";            // button font name
input int                btn_FontSize          = 9;                  // btn__font size
input color              btn_text_ON_color     = clrLime;            // ON color when the button is turned on
input color              btn_text_OFF_color    = clrRed;             // OFF color when the button is turned off
input color              btn_background_color  = clrDimGray;         // background color of the button
input color              btn_border_color      = clrBlack;           // border color the button
input int                button_x              = 20;                 // Horizontal location
input int                button_y              = 40;                 // Vertical location
input int                btn_Width             = 80;                 // btn__width
input int                btn_Height            = 20;                 // btn__height
input string             UniqueButtonID        = "RstlBT";           // Unique Button ID                      
input string             button_note2          = "------------------------------"; // ------------------------------
//Forex-Station copy & paste code; Button code end 11

//+------------------------------------------------------------------+
//| 99 FIR Filter Coefficients for RSTL                              |
//+------------------------------------------------------------------+
const int RSTLPeriod = 99;

static const double rstlWeights[99] =
{
   -0.00514293, -0.00398417, -0.00262594, -0.00107121,  0.00066887,  0.00258172,
    0.00465269,  0.00686394,  0.00919334,  0.01161720,  0.01411056,  0.01664635,
    0.01919533,  0.02172747,  0.02421320,  0.02662203,  0.02892446,  0.03109071,
    0.03309496,  0.03490921,  0.03651145,  0.03788045,  0.03899804,  0.03984915,
    0.04042329,  0.04071263,  0.04071263,  0.04042329,  0.03984915,  0.03899804,
    0.03788045,  0.03651145,  0.03490921,  0.03309496,  0.03109071,  0.02892446,
    0.02662203,  0.02421320,  0.02172747,  0.01919533,  0.01664635,  0.01411056,
    0.01161720,  0.00919334,  0.00686394,  0.00465269,  0.00258172,  0.00066887,
   -0.00107121, -0.00262594, -0.00398417, -0.00514293, -0.00609634, -0.00684602,
   -0.00739452, -0.00774847, -0.00791630, -0.00790940, -0.00774085, -0.00742482,
   -0.00697718, -0.00641613, -0.00576108, -0.00502957, -0.00423873, -0.00340812,
   -0.00255923, -0.00170217, -0.00085902, -0.00004113,  0.00073700,  0.00146422,
    0.00213007,  0.00272649,  0.00324752,  0.00368922,  0.00405000,  0.00433024,
    0.00453068,  0.00465046,  0.00469058,  0.00466041,  0.00457855,  0.00442491,
    0.00423019,  0.00399201,  0.00372169,  0.00342736,  0.00311822,  0.00280309,
    0.00249088,  0.00219089,  0.00191283,  0.00166683,  0.00146419,  0.00131867,
    0.00124645,  0.00126836, -0.00401854
};

//+------------------------------------------------------------------+
//| Global Variables & Indicator Buffers                             |
//+------------------------------------------------------------------+
double ExtLineBuffer[];
double ColorBuffer[];

//--- Chronological buffers for fog gradient
double rstlLineBuffer[];
double candleLineBuffer[];
double trendFogBuffer[];

//--- Dynamic arrays for 33 prices calculation
#define _prHABF(_prtype) (_prtype>=pr_habclose && _prtype<=pr_habtbiased2)
#define priceInstances 1
double workHa[][priceInstances*4];
double prices[];

//--- Canvas object & chart properties for fog gradient
//+------------------------------------------------------------------+
//| Fast Canvas subclass with direct pixel memory buffer access      |
//+------------------------------------------------------------------+
class CFastCanvas : public CCanvas
{
public:
   inline void SetPixelFast(const int x, const int y, const uint clr)
   {
      m_pixels[y * m_width + x] = clr;
   }

   inline void SetPixelDirect(const int x, const int y, const uint clr)
   {
      if(x >= 0 && x < m_width && y >= 0 && y < m_height)
         m_pixels[y * m_width + x] = clr;
   }

   inline void EraseFast(const uint clr = 0)
   {
      ArrayFill(m_pixels, 0, ArraySize(m_pixels), clr);
   }

   void DrawVerticalGradientColumn(const int column, const int upperY, const int lowerY,
                                   const double slowLineY, const double invHeight,
                                   const bool invert, const uchar maxAlpha, const uint baseColorRGB)
   {
      if(column < 0 || column >= m_width) return;
      int yStart = (upperY < 0) ? 0 : upperY;
      int yEnd   = (lowerY >= m_height) ? m_height - 1 : lowerY;
      if(yStart > yEnd) return;

      int idx = yStart * m_width + column;
      for(int row = yStart; row <= yEnd; row++, idx += m_width)
      {
         double dist = MathAbs(row - slowLineY);
         double gf   = dist * invHeight;
         if(gf > 1.0) gf = 1.0;
         if(invert)   gf = 1.0 - gf;
         uint a = (uint)(maxAlpha * (1.0 - gf));
         m_pixels[idx] = (a << 24) | baseColorRGB;
      }
   }
};

CFastCanvas obj_Canvas;
int     currentChartWidth    = 0;
int     currentChartHeight   = 0;
int     currentChartScale    = 0;
int     firstVisibleBarIndex = 0;
int     visibleBarsCount     = 0;
double  minPrice             = 0.0;
double  maxPrice             = 0.0;

static datetime lastRedrawTime = 0;
static double   previousTrend  = -1;
string objectPrefix = "RSTL_Fog_";
string canvasName   = "";

//+------------------------------------------------------------------+
//| Button State Tracking Variables                                  |
//+------------------------------------------------------------------+
bool    IndicatorEnabled = true;
bool    IndicatorHidden  = false; // True when indicator is turned OFF via toggle button
string  ToggleButtonName;
string  g_gvHidden;
string  g_prefix;
string  IndicatorName, IndicatorObjPrefix, buttonId;

//+------------------------------------------------------------------+
//| Forward Declarations                                             |
//+------------------------------------------------------------------+
string GetButtonText(bool isEnabled);
void   CreateToggleButton();
void   UpdateToggleButton();
void   SavePlotStyles();
void   RestorePlotStyles();
void   ApplyPlotsStyle();
void   ToggleIndicatorVisibility();
void   BringButtonsToFront();
void   Redraw(void);
int    BarWidth(int chartScale);
int    ShiftToX(int barShift);
int    PriceToY(double price);
double GetAppliedPrice(ENUM_APPLIED_PRICE applied, double o, double h, double l, double c);
double getPrice(int tprice, const double& open[], const double& close[], const double& high[], const double& low[], int i, int _bars, int instanceNo=0);
void   DrawFilling(const double &slowLineValues[], const double &fastLineValues[], const double &trendValues[], color fillUpColor, color fillDownColor, uchar fillAlpha = 255, int extendShift = 0);

//+------------------------------------------------------------------+
//| Price Engine (33 prices)                                         |
//+------------------------------------------------------------------+
double getPrice(int tprice, const double& open[], const double& close[], const double& high[], const double& low[], int i, int _bars, int instanceNo=0)
{
   if (tprice>=pr_haclose)
   {
      if (ArrayRange(workHa,0)!= _bars) ArrayResize(workHa,_bars); instanceNo*=4;
        
      double haOpen;
      if (i>0)
      {
         if (workHa[i-1][instanceNo+2]!=0 || workHa[i-1][instanceNo+3]!=0)
            haOpen  = (workHa[i-1][instanceNo+2] + workHa[i-1][instanceNo+3])/2.0;
         else
            haOpen  = (open[i-1]+close[i-1])/2.0;
      }
      else
         haOpen  = (open[i]+close[i])/2.0;

      double haClose = (open[i] + high[i] + low[i] + close[i]) * 0.25;
      if (_prHABF(tprice))
      {
         if (high[i]!=low[i])
            haClose = (open[i]+close[i])/2.0+(((close[i]-open[i])/(high[i]-low[i]))*MathAbs((close[i]-open[i])/2.0));
         else
            haClose = (open[i]+close[i])/2.0;
      }
      double haHigh  = MathMax(high[i], MathMax(haOpen,haClose));
      double haLow   = MathMin(low[i] , MathMin(haOpen,haClose));

      if (haOpen < haClose) { workHa[i][instanceNo+0] = haLow;  workHa[i][instanceNo+1] = haHigh; }
      else                  { workHa[i][instanceNo+0] = haHigh; workHa[i][instanceNo+1] = haLow;  }
                             workHa[i][instanceNo+2] = haOpen;
                             workHa[i][instanceNo+3] = haClose;
        
      switch (tprice)
      {
         case pr_haclose:
         case pr_habclose:    return(haClose);
         case pr_haopen:
         case pr_habopen:     return(haOpen);
         case pr_hahigh:
         case pr_habhigh:     return(haHigh);
         case pr_halow:
         case pr_hablow:      return(haLow);
         case pr_hamedian:
         case pr_habmedian:   return((haHigh+haLow)/2.0);
         case pr_hamedianb:
         case pr_habmedianb:  return((haOpen+haClose)/2.0);
         case pr_hatypical:
         case pr_habtypical:  return((haHigh+haLow+haClose)/3.0);
         case pr_haweighted:
         case pr_habweighted: return((haHigh+haLow+haClose+haClose)/4.0);
         case pr_haaverage:
         case pr_habaverage:  return((haHigh+haLow+haClose+haOpen)/4.0);
         case pr_hatbiased:
         case pr_habtbiased:
            if (haClose>haOpen)
                  return((haHigh+haClose)/2.0);
            else  return((haLow+haClose)/2.0);        
         case pr_hatbiased2:
         case pr_habtbiased2:
            if (haClose>haOpen)  return(haHigh);
            if (haClose<haOpen)  return(haLow);
                                 return(haClose);        
      }
   }
  
   switch (tprice)
   {
      case pr_close:     return(close[i]);
      case pr_open:      return(open[i]);
      case pr_high:      return(high[i]);
      case pr_low:       return(low[i]);
      case pr_median:    return((high[i]+low[i])/2.0);
      case pr_medianb:   return((open[i]+close[i])/2.0);
      case pr_typical:   return((high[i]+low[i]+close[i])/3.0);
      case pr_weighted:  return((high[i]+low[i]+close[i]+close[i])/4.0);
      case pr_average:   return((high[i]+low[i]+close[i]+open[i])/4.0);
      case pr_tbiased:  
            if (close[i]>open[i])
                  return((high[i]+close[i])/2.0);
            else  return((low[i]+close[i])/2.0);        
      case pr_tbiased2:  
            if (close[i]>open[i]) return(high[i]);
            if (close[i]<open[i]) return(low[i]);
                                  return(close[i]);        
   }
   return(0);
}

//+------------------------------------------------------------------+
//| Save current plot widths & styles from chart to GlobalVariables  |
//+------------------------------------------------------------------+
void SavePlotStyles()
{
   if(g_prefix == "") return;
   int w = (int)PlotIndexGetInteger(0, PLOT_LINE_WIDTH);
   int s = (int)PlotIndexGetInteger(0, PLOT_LINE_STYLE);
   if(w > 0)
      GlobalVariableSet(g_prefix + IntegerToString(ChartID()) + "_W0", (double)w);
   if(s >= 0)
      GlobalVariableSet(g_prefix + IntegerToString(ChartID()) + "_S0", (double)s);
}

//+------------------------------------------------------------------+
//| Restore plot widths & styles after timeframe switch              |
//+------------------------------------------------------------------+
void RestorePlotStyles()
{
   if(g_prefix == "") return;
   string gvW = g_prefix + IntegerToString(ChartID()) + "_W0";
   string gvS = g_prefix + IntegerToString(ChartID()) + "_S0";

   if(GlobalVariableCheck(gvW))
   {
      int w = (int)GlobalVariableGet(gvW);
      if(w > 0) PlotIndexSetInteger(0, PLOT_LINE_WIDTH, w);
   }
   if(GlobalVariableCheck(gvS))
   {
      int s = (int)GlobalVariableGet(gvS);
      if(s >= 0) PlotIndexSetInteger(0, PLOT_LINE_STYLE, s);
   }
}

//+------------------------------------------------------------------+
//| Helper to resolve button text based on state                     |
//+------------------------------------------------------------------+
string GetButtonText(bool isEnabled)
{
   string t = btn_text;
   if(isEnabled)
   {
      if(StringFind(t, "OFF") >= 0) StringReplace(t, "OFF", "ON");
      else if(StringFind(t, "Off") >= 0) StringReplace(t, "Off", "On");
      else if(StringFind(t, "off") >= 0) StringReplace(t, "off", "on");
   }
   else
   {
      if(StringFind(t, "ON") >= 0) StringReplace(t, "ON", "OFF");
      else if(StringFind(t, "On") >= 0) StringReplace(t, "On", "Off");
      else if(StringFind(t, "on") >= 0) StringReplace(t, "on", "off");
   }
   return t;
}

//+------------------------------------------------------------------+
//| Create Toggle Button                                             |
//+------------------------------------------------------------------+
void CreateToggleButton()
{
   if(ObjectFind(0, buttonId) < 0)
   {
      int y_pos = button_y;
      int obj_total = ObjectsTotal(0, -1, OBJ_BUTTON);
      bool collision = true;
      while(collision)
      {
         collision = false;
         for(int i = 0; i < obj_total; i++)
         {
            string name = ObjectName(0, i, -1, OBJ_BUTTON);
            if(name == buttonId) continue;
            if((ENUM_BASE_CORNER)ObjectGetInteger(0, name, OBJPROP_CORNER) != btn_corner) continue;
            if(ObjectGetInteger(0, name, OBJPROP_XDISTANCE) == button_x &&
               ObjectGetInteger(0, name, OBJPROP_YDISTANCE) == y_pos)
            {
               y_pos += btn_Height + 4;
               collision = true;
               break;
            }
         }
      }

      ObjectCreate(0, buttonId, OBJ_BUTTON, btn_Subwindow, 0, 0);

      ObjectSetInteger(0, buttonId, OBJPROP_CORNER,       btn_corner);
      ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE,    button_x);
      ObjectSetInteger(0, buttonId, OBJPROP_YDISTANCE,    y_pos);
      ObjectSetInteger(0, buttonId, OBJPROP_XSIZE,        btn_Width);
      ObjectSetInteger(0, buttonId, OBJPROP_YSIZE,        btn_Height);
      ObjectSetString (0, buttonId, OBJPROP_FONT,         btn_Font);
      ObjectSetInteger(0, buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
      ObjectSetInteger(0, buttonId, OBJPROP_BGCOLOR,      btn_background_color);
      ObjectSetInteger(0, buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
      ObjectSetInteger(0, buttonId, OBJPROP_BORDER_TYPE,  BORDER_RAISED);
      ObjectSetInteger(0, buttonId, OBJPROP_SELECTABLE,   false);
      ObjectSetInteger(0, buttonId, OBJPROP_HIDDEN,       true);
      ObjectSetInteger(0, buttonId, OBJPROP_TIMEFRAMES,   OBJ_ALL_PERIODS);
      ObjectSetInteger(0, buttonId, OBJPROP_ZORDER,       100);
   }

   UpdateToggleButton();
}

//+------------------------------------------------------------------+
//| Update Toggle Button Appearance                                  |
//+------------------------------------------------------------------+
void UpdateToggleButton()
{
   if(ObjectFind(0, buttonId) < 0) return;

   IndicatorEnabled = !IndicatorHidden;

   string labelText  = GetButtonText(IndicatorEnabled);
   color  textColor  = IndicatorEnabled ? btn_text_ON_color : btn_text_OFF_color;

   ObjectSetString (0, buttonId, OBJPROP_TEXT,         labelText);
   ObjectSetString (0, buttonId, OBJPROP_FONT,         btn_Font);
   ObjectSetInteger(0, buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
   ObjectSetInteger(0, buttonId, OBJPROP_COLOR,        textColor);
   ObjectSetInteger(0, buttonId, OBJPROP_BGCOLOR,      btn_background_color);
   ObjectSetInteger(0, buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
   ObjectSetInteger(0, buttonId, OBJPROP_CORNER,       btn_corner);
   ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE,    button_x);
   ObjectSetInteger(0, buttonId, OBJPROP_XSIZE,        btn_Width);
   ObjectSetInteger(0, buttonId, OBJPROP_YSIZE,        btn_Height);
   ObjectSetInteger(0, buttonId, OBJPROP_STATE,        IndicatorEnabled);
   ObjectSetInteger(0, buttonId, OBJPROP_ZORDER,       100);

   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Apply Plot Styles based on Button Toggle State                   |
//+------------------------------------------------------------------+
void ApplyPlotsStyle()
{
   bool showIndicator = !IndicatorHidden;

   PlotIndexSetInteger(0, PLOT_DRAW_TYPE, showIndicator ? DRAW_COLOR_LINE : DRAW_NONE);
   PlotIndexSetString (0, PLOT_LABEL, showIndicator ? "RSTL" : "");
   PlotIndexSetInteger(0, PLOT_SHOW_DATA, showIndicator);
   PlotIndexSetDouble (0, PLOT_EMPTY_VALUE, 0.0);
}

//+------------------------------------------------------------------+
//| Toggle Indicator Visibility                                      |
//+------------------------------------------------------------------+
void ToggleIndicatorVisibility()
{
   if(!IndicatorHidden)
      SavePlotStyles();

   IndicatorHidden  = !IndicatorHidden;
   IndicatorEnabled = !IndicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, IndicatorHidden ? 1.0 : 0.0);

   ApplyPlotsStyle();
   if(!IndicatorHidden)
      RestorePlotStyles();

   UpdateToggleButton();

   if(IndicatorHidden)
   {
      if(enableFilling)
      {
         obj_Canvas.Erase(0);
         obj_Canvas.Update(false);
      }
   }
   else
   {
      if(enableFilling)
      {
         Redraw();
      }
   }

   BringButtonsToFront();
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Ensure all buttons on chart have higher ZORDER                   |
//+------------------------------------------------------------------+
void BringButtonsToFront()
{
   int total = ObjectsTotal(0, -1, OBJ_BUTTON);
   for(int i = 0; i < total; i++)
   {
      string name = ObjectName(0, i, -1, OBJ_BUTTON);
      if(name != "")
      {
         if(ObjectGetInteger(0, name, OBJPROP_ZORDER) < 100)
            ObjectSetInteger(0, name, OBJPROP_ZORDER, 100);
      }
   }
}

//+------------------------------------------------------------------+
//| Convert applied price enum to price value                        |
//+------------------------------------------------------------------+
double GetAppliedPrice(ENUM_APPLIED_PRICE applied, double o, double h, double l, double c)
{
   switch(applied)
   {
      case PRICE_OPEN:     return o;
      case PRICE_HIGH:     return h;
      case PRICE_LOW:      return l;
      case PRICE_MEDIAN:   return (h + l) * 0.5;
      case PRICE_TYPICAL:  return (h + l + c) / 3.0;
      case PRICE_WEIGHTED: return (h + l + c + c) * 0.25;
      case PRICE_CLOSE:
      default:             return c;
   }
}

//+------------------------------------------------------------------+
//| Convert chart scale to bar width                                 |
//+------------------------------------------------------------------+
int BarWidth(int chartScale)
{
   if(chartScale < 0) chartScale = 0;
   if(chartScale > 5) chartScale = 5;
   return (1 << chartScale);
}

//+------------------------------------------------------------------+
//| Convert bar shift to x pixel                                     |
//+------------------------------------------------------------------+
int ShiftToX(int barShift)
{
   return (int)((firstVisibleBarIndex - barShift) * BarWidth(currentChartScale) - 1);
}

//+------------------------------------------------------------------+
//| Convert price to y pixel                                         |
//+------------------------------------------------------------------+
int PriceToY(double price)
{
   double range = maxPrice - minPrice;
   if(range <= 0.0) return 0;
   return (int)MathRound(currentChartHeight * (maxPrice - price) / range - 1);
}

//+------------------------------------------------------------------+
//| Fill area between two lines using gradient alpha                 |
//| (High performance direct pixel buffer implementation)            |
//+------------------------------------------------------------------+
void DrawFilling(const double &slowLineValues[], const double &fastLineValues[], const double &trendValues[], color fillUpColor, color fillDownColor, uchar fillAlpha = 255, int extendShift = 0)
{
   if(IndicatorHidden) return;
   int firstVisibleBar = firstVisibleBarIndex;
   int totalBarsToDraw = visibleBarsCount + MathAbs(extendShift);
   int bufferSize      = (int)ArraySize(slowLineValues);
   if(bufferSize == 0 || bufferSize != ArraySize(fastLineValues) || bufferSize != ArraySize(trendValues)) return;

   int previousX  = -1;
   int previousY1 = -1;
   int previousY2 = -1;

   uint baseUpRGB   = ColorToARGB(fillUpColor, 255) & 0x00FFFFFF;
   uint baseDownRGB = ColorToARGB(fillDownColor, 255) & 0x00FFFFFF;

   for(int offset = 0; offset < totalBarsToDraw; offset++)
   {
      int barPosition = firstVisibleBar - offset;
      int x = ShiftToX(barPosition);
      if(previousX >= currentChartWidth) break;

      int dataBarShift = firstVisibleBar - offset;
      int slowBarIndex = bufferSize - 1 - (dataBarShift - extendShift);
      int fastBarIndex = bufferSize - 1 - dataBarShift;
      if(slowBarIndex < 0 || slowBarIndex >= bufferSize || fastBarIndex < 0 || fastBarIndex >= bufferSize)
      {
         previousX = -1;
         continue;
      }
      double value1 = slowLineValues[slowBarIndex];
      double value2 = fastLineValues[fastBarIndex];
      if(value1 == EMPTY_VALUE || value2 == EMPTY_VALUE || value1 <= 0.0 || value2 <= 0.0)
      {
         previousX = -1;
         continue;
      }
      int y1 = PriceToY(value1);
      int y2 = PriceToY(value2);
      double currentTrend = trendValues[slowBarIndex];
      uint baseColorRGB = (currentTrend == 0.0) ? baseUpRGB : baseDownRGB;

      if(previousX != -1 && x > previousX)
      {
         double deltaX    = x - previousX;
         double invDeltaX = 1.0 / deltaX;
         double dY1       = (y1 - previousY1) * invDeltaX;
         double dY2       = (y2 - previousY2) * invDeltaX;

         int startColumn = MathMax(0, previousX);
         int endColumn   = MathMin(x, currentChartWidth - 1);

         double interpolatedY1 = previousY1 + (startColumn - previousX) * dY1;
         double interpolatedY2 = previousY2 + (startColumn - previousX) * dY2;

         for(int column = startColumn; column <= endColumn; column++)
         {
            int upperY    = (int)MathRound(MathMin(interpolatedY1, interpolatedY2));
            int lowerY    = (int)MathRound(MathMax(interpolatedY1, interpolatedY2));
            double height = MathAbs(interpolatedY1 - interpolatedY2);

            if(height > 0.0)
            {
               double invHeight = 1.0 / height;
               obj_Canvas.DrawVerticalGradientColumn(column, upperY, lowerY, interpolatedY1, invHeight, invertFog, fillAlpha, baseColorRGB);
            }

            interpolatedY1 += dY1;
            interpolatedY2 += dY2;
         }
      }

      previousX  = x;
      previousY1 = y1;
      previousY2 = y2;

      if(x >= currentChartWidth) break;
   }
}

//+------------------------------------------------------------------+
//| Redraw the canvas                                                |
//+------------------------------------------------------------------+
void Redraw(void)
{
   if(!enableFilling) return;
   if(currentChartWidth <= 0 || currentChartHeight <= 0) return;
   if(IndicatorHidden)
   {
      obj_Canvas.Erase(0);
      obj_Canvas.Update(false);
      return;
   }

   if(obj_Canvas.Width() != currentChartWidth || obj_Canvas.Height() != currentChartHeight)
      obj_Canvas.Resize(currentChartWidth, currentChartHeight);

   uint defaultColor = 0;
   obj_Canvas.Erase(defaultColor);
   DrawFilling(rstlLineBuffer, candleLineBuffer, trendFogBuffer, fogUpColor, fogDownColor, (uchar)fillOpacity, RSTLShift);
   obj_Canvas.Update(false);
}

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- Set up unique IDs and global variables for state tracking
   IndicatorObjPrefix = "__" + btn_text + "__";
   buttonId           = IndicatorObjPrefix + UniqueButtonID;
   ToggleButtonName   = buttonId;
   IndicatorName      = "RSTL (gradient) BT [" + UniqueButtonID + "]";
   g_prefix           = "RSTL_Fog_BT_" + UniqueButtonID + "_";
   g_gvHidden         = g_prefix + IntegerToString(ChartID()) + "_Hidden";
   objectPrefix       = "RSTL_Fog_" + UniqueButtonID + "_";

   // Clean up any old duplicate or phantom buttons
   int total_objs = ObjectsTotal(0, -1, OBJ_BUTTON);
   for(int i = total_objs - 1; i >= 0; i--)
   {
      string bName = ObjectName(0, i, -1, OBJ_BUTTON);
      if(StringFind(bName, UniqueButtonID) >= 0 || StringFind(bName, "RSTL_Fog_BT") >= 0)
      {
         if(bName != buttonId)
            ObjectDelete(0, bName);
      }
   }

   // When parameters are changed by user in properties dialog, delete old global variables and respect inputs strictly
   bool isParamChange = (UninitializeReason() == REASON_PARAMETERS);
   if(isParamChange && g_gvHidden != "")
   {
      GlobalVariableDel(g_gvHidden);
      GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_W0");
      GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_S0");
   }

   // Restore toggle state across timeframe/symbol changes
   bool hasSavedState = false;
   bool savedHidden   = false;

   if(!isParamChange)
   {
      if(ObjectFind(0, buttonId) >= 0)
      {
         string btnTxt = ObjectGetString(0, buttonId, OBJPROP_TEXT);
         long   btnSt  = ObjectGetInteger(0, buttonId, OBJPROP_STATE);

         if(StringFind(btnTxt, "OFF") >= 0 || StringFind(btnTxt, "Off") >= 0 || btnSt == 0)
            savedHidden = true;
         else if(StringFind(btnTxt, "ON") >= 0 || StringFind(btnTxt, "On") >= 0 || btnSt != 0)
            savedHidden = false;
         hasSavedState = true;
      }

      if(!hasSavedState && GlobalVariableCheck(g_gvHidden))
      {
         savedHidden   = (GlobalVariableGet(g_gvHidden) > 0.5);
         hasSavedState = true;
      }
   }

   if(hasSavedState)
      IndicatorHidden = savedHidden;
   else
      IndicatorHidden = false;

   IndicatorEnabled = !IndicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, IndicatorHidden ? 1.0 : 0.0);

   // Set chart properties
   currentChartWidth    = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   currentChartHeight   = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   currentChartScale    = (int)ChartGetInteger(0, CHART_SCALE);
   firstVisibleBarIndex = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
   visibleBarsCount     = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
   minPrice             = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   maxPrice             = ChartGetDouble(0, CHART_PRICE_MAX, 0);

   // Fallback dimensions in case chart window is not fully initialized yet
   if(currentChartWidth <= 0)  currentChartWidth  = 1000;
   if(currentChartHeight <= 0) currentChartHeight = 600;

   // Set index buffers
   SetIndexBuffer(0, ExtLineBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, ColorBuffer, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2, rstlLineBuffer, INDICATOR_CALCULATIONS);
   SetIndexBuffer(3, candleLineBuffer, INDICATOR_CALCULATIONS);
   SetIndexBuffer(4, trendFogBuffer, INDICATOR_CALCULATIONS);

   PlotIndexSetInteger(0, PLOT_SHIFT, RSTLShift);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, RSTLPeriod);
   PlotIndexSetDouble (0, PLOT_EMPTY_VALUE, 0.0);

   ApplyPlotsStyle();
   if(!isParamChange)
      RestorePlotStyles();
   SavePlotStyles();
   CreateToggleButton();
   BringButtonsToFront();

   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   IndicatorSetString(INDICATOR_SHORTNAME, IndicatorName);

   // Create canvas for fog gradient fill
   if(enableFilling)
   {
      canvasName = objectPrefix + "Canvas";
      ObjectDelete(0, canvasName);
      if(!obj_Canvas.CreateBitmapLabel(0, 0, canvasName, 0, 0, currentChartWidth, currentChartHeight, COLOR_FORMAT_ARGB_NORMALIZE))
      {
         Print("Failed to create canvas for fog gradient");
         return(INIT_FAILED);
      }
      ObjectSetInteger(0, canvasName, OBJPROP_SELECTABLE, false);

      if(IndicatorHidden)
      {
         obj_Canvas.Erase(0);
         obj_Canvas.Update(false);
      }
   }

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(reason == REASON_CHARTCHANGE && !IndicatorHidden)
   {
      SavePlotStyles();
   }

   if(enableFilling)
   {
      obj_Canvas.Destroy();
      if(canvasName != "")
         ObjectDelete(0, canvasName);
   }

   if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE || reason == REASON_PARAMETERS)
   {
      ObjectDelete(0, buttonId);
      ObjectDelete(0, ToggleButtonName);
      if(g_gvHidden != "")
         GlobalVariableDel(g_gvHidden);

      if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE)
      {
         GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_W0");
         GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_S0");
      }
   }
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(
   const int        rates_total,
   const int        prev_calculated,
   const datetime&  time[],
   const double&    open[],
   const double&    high[],
   const double&    low[],
   const double&    close[],
   const long&      tick_volume[],
   const long&      volume[],
   const int&       spread[]
)
{
   if(rates_total < RSTLPeriod)
      return(0);

   if(prev_calculated <= 0)
   {
      CreateToggleButton();
      ApplyPlotsStyle();
      if(!IndicatorHidden)
         RestorePlotStyles();
   }

   // 1. Calculate price buffer (33 prices support)
   if(ArraySize(prices) != rates_total)
      ArrayResize(prices, rates_total);

   int pstart = (prev_calculated > rates_total || prev_calculated <= 0) ? 0 : prev_calculated - 1;
   for(int i = pstart; i < rates_total && !IsStopped(); i++)
      prices[i] = getPrice(Price, open, close, high, low, i, rates_total);

   // 2. Calculate Candlestick EMA line buffer for fog
   for(int i = pstart; i < rates_total && !IsStopped(); i++)
   {
      double cPr = GetAppliedPrice(candleAppliedPrice, open[i], high[i], low[i], close[i]);
      if(candleEmaPeriod <= 1 || i == 0)
         candleLineBuffer[i] = cPr;
      else
      {
         double alpha = 2.0 / (candleEmaPeriod + 1.0);
         candleLineBuffer[i] = alpha * cPr + (1.0 - alpha) * candleLineBuffer[i - 1];
      }
   }

   // 3. Initialize early bars if first run
   int first;
   if(prev_calculated > rates_total || prev_calculated <= 0)
   {
      first = RSTLPeriod - 1; // 99 - 1 = 98
      PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, RSTLPeriod);
      PlotIndexSetDouble (0, PLOT_EMPTY_VALUE, 0.0);

      for(int b = 0; b < first; b++)
      {
         ExtLineBuffer[b]   = 0.0;
         ColorBuffer[b]     = 0;
         rstlLineBuffer[b]  = EMPTY_VALUE;
         trendFogBuffer[b]  = 0.0;
      }
   }
   else
   {
      first = prev_calculated - 1;
      if(first < RSTLPeriod - 1)
         first = RSTLPeriod - 1;
   }

   // 4. Main indicator calculation loop
   for(int bar = first; bar < rates_total && !IsStopped(); bar++)
   {
      double rstl = 0.0;
      for(int k = 0; k < 99; k++)
         rstl += rstlWeights[k] * prices[bar - k];

      ExtLineBuffer[bar]  = rstl;
      rstlLineBuffer[bar] = rstl;

      // Color and trend slope determination
      if(bar > 0 && ExtLineBuffer[bar - 1] > 0.0)
      {
         if(ExtLineBuffer[bar] > ExtLineBuffer[bar - 1])
         {
            ColorBuffer[bar]    = 2;    // Bullish (Up) -> clrDeepSkyBlue
            trendFogBuffer[bar] = 0.0;  // 0.0 = Up
         }
         else if(ExtLineBuffer[bar] < ExtLineBuffer[bar - 1])
         {
            ColorBuffer[bar]    = 1;    // Bearish (Down) -> clrTomato
            trendFogBuffer[bar] = 1.0;  // 1.0 = Down
         }
         else
         {
            ColorBuffer[bar]    = ColorBuffer[bar - 1];
            trendFogBuffer[bar] = trendFogBuffer[bar - 1];
         }
      }
      else
      {
         ColorBuffer[bar]    = 0;
         trendFogBuffer[bar] = 0.0;
      }
   }

   if(!enableFilling) return(rates_total);

   if(IndicatorHidden)
   {
      return(rates_total);
   }

   // Canvas fog gradient redraw logic
   bool isNewBar = (rates_total > prev_calculated);
   bool hasTrendChanged = false;
   if(rates_total > 0 && trendFogBuffer[rates_total - 1] != previousTrend)
   {
      hasTrendChanged = true;
      previousTrend   = trendFogBuffer[rates_total - 1];
   }

   // Update chart properties
   bool hasChartChanged = false;
   int newChartWidth      = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int newChartHeight     = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   int newChartScale      = (int)ChartGetInteger(0, CHART_SCALE);
   int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
   int newVisibleBars     = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
   double newMinPrice     = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   double newMaxPrice     = ChartGetDouble(0, CHART_PRICE_MAX, 0);

   if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight)
   {
      obj_Canvas.Resize(newChartWidth, newChartHeight);
      currentChartWidth  = newChartWidth;
      currentChartHeight = newChartHeight;
      hasChartChanged    = true;
   }
   if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount ||
      newMinPrice != minPrice || newMaxPrice != maxPrice)
   {
      currentChartScale    = newChartScale;
      firstVisibleBarIndex = newFirstVisibleBar;
      visibleBarsCount     = newVisibleBars;
      minPrice             = newMinPrice;
      maxPrice             = newMaxPrice;
      hasChartChanged      = true;
   }

   static double lastRedrawClose = 0.0;
   double currentClose = (rates_total > 0 && ArraySize(close) >= rates_total) ? close[rates_total - 1] : 0.0;
   bool priceChanged   = (MathAbs(currentClose - lastRedrawClose) > _Point * 0.5);

   datetime currentTime = TimeCurrent();
   if(prev_calculated <= 0 || isNewBar || hasTrendChanged || hasChartChanged || (priceChanged && (currentTime - lastRedrawTime >= 1)))
   {
      Redraw();
      lastRedrawTime  = currentTime;
      lastRedrawClose = currentClose;
   }

   return(rates_total);
}

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      string altId = "_" + UniqueButtonID + IndicatorObjPrefix + "_BT_";
      if(sparam == buttonId || sparam == ToggleButtonName || sparam == altId)
      {
         ToggleIndicatorVisibility();
         return;
      }
   }

   if(id == CHARTEVENT_CHART_CHANGE)
   {
      if(!IndicatorHidden)
         SavePlotStyles();
      BringButtonsToFront();

      if(enableFilling)
      {
         int newChartWidth  = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
         int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
         if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight)
         {
            obj_Canvas.Resize(newChartWidth, newChartHeight);
            currentChartWidth  = newChartWidth;
            currentChartHeight = newChartHeight;
            if(!IndicatorHidden)
               Redraw();
            else
            {
               obj_Canvas.Erase(0);
               obj_Canvas.Update(false);
            }
            return;
         }
         int newChartScale          = (int)ChartGetInteger(0, CHART_SCALE);
         int newFirstVisibleBar     = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
         int newVisibleBars         = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
         double newMinPrice         = ChartGetDouble(0, CHART_PRICE_MIN, 0);
         double newMaxPrice         = ChartGetDouble(0, CHART_PRICE_MAX, 0);
         if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount ||
            newMinPrice != minPrice || newMaxPrice != maxPrice)
         {
            currentChartScale    = newChartScale;
            firstVisibleBarIndex = newFirstVisibleBar;
            visibleBarsCount     = newVisibleBars;
            minPrice             = newMinPrice;
            maxPrice             = newMaxPrice;
            if(!IndicatorHidden)
               Redraw();
            else
            {
               obj_Canvas.Erase(0);
               obj_Canvas.Update(false);
            }
         }
      }
   }
}
//+------------------------------------------------------------------+
