﻿//+------------------------------------------------------------------+
//|                                           ToolsPalette_Shell.mqh |
//|                            Copyright 2026, Allan Munene Mutiiria.|
//|                                    https://t.me/Forex_Algo_Trader|
//+------------------------------------------------------------------+
#ifndef TOOLS_PALETTE_SHELL_MQH
#define TOOLS_PALETTE_SHELL_MQH

#include "ToolsPalette_Sidebar.mqh"


//+------------------------------------------------------------------+
//| CLASS 9 — Chart event handler: routes all events                 |
//+------------------------------------------------------------------+
class CChartEventHandler : public CSidebarRenderer
  {
protected:
   int m_previousMouseButtonState; // Last observed mouse button state (0 = up, 1 = down)

protected:
   //--- Dispatch incoming chart event to the appropriate sub-handler
   void RouteChartEvent(const int id, const long &lp, const double &dp, const string &sp, TOOL_TYPE &activeTool);
   //--- React to a chart geometry or scroll change event
   void OnChartChangeEvent(TOOL_TYPE activeTool);
   //--- React to a mouse-wheel scroll event over the panel or flyout
   void OnMouseWheelEvent(int mouseX, int mouseY, int wheelDelta, TOOL_TYPE activeTool);
   //--- React to all mouse-move events and dispatch drag/hover logic
   void OnMouseMoveEvent(int mouseX, int mouseY, int mouseButtons, TOOL_TYPE &activeTool);
   //--- Translate panel drag into updated canvas position
   void HandlePanelDragMove(int mouseX, int mouseY, TOOL_TYPE activeTool);
   //--- Finalize a panel drag and attempt edge snapping
   void HandlePanelDragRelease(TOOL_TYPE activeTool);
   //--- Apply live resize while the user drags the bottom grip
   void HandleBottomResizeDrag(int mouseX, int mouseY, TOOL_TYPE activeTool);
   //--- Scroll the sidebar by repositioning its scroll thumb
   void HandleSidebarThumbDrag(int mouseX, int mouseY, TOOL_TYPE activeTool);
   //--- Release the sidebar scroll thumb and redraw
   void HandleSidebarThumbRelease(TOOL_TYPE activeTool);
   //--- Scroll the flyout by repositioning its scroll thumb
   void HandleFlyoutThumbDrag(int mouseX, int mouseY);
   //--- Release the flyout scroll thumb and redraw
   void HandleFlyoutThumbRelease();
   //--- Recompute every hover flag and redraw only when something changed
   void UpdateAllHoverStates(int mouseX, int mouseY, bool overSidebar, bool overFlyout,
                              int lx, int ly, int flx, int fly, TOOL_TYPE activeTool);
   //--- Process 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);
  };

//+------------------------------------------------------------------+
//| Route chart event to the correct handler                         |
//+------------------------------------------------------------------+
void CChartEventHandler::RouteChartEvent(const int id, const long &lp, const double &dp, const string &sp, TOOL_TYPE &activeTool)
  {
   //--- Dispatch chart-geometry change
   if(id == CHARTEVENT_CHART_CHANGE) { OnChartChangeEvent(activeTool); return; }
   //--- Dispatch mouse-wheel scroll
   if(id == CHARTEVENT_MOUSE_WHEEL)  { OnMouseWheelEvent((int)(short)lp, (int)(short)(lp >> 16), (int)dp, activeTool); return; }
   //--- Dispatch mouse-move (no early return — fall through by design)
   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)
  {
   m_isPanelDragging        = false;
   m_isResizingBottomEdge   = false;
   m_isSidebarThumbDragging = false;
   m_isFlyoutThumbDragging  = false;
   //--- Reset magnifier ghost position so it redraws cleanly on next move
   m_lastMagMouseX = -9999;
   m_lastMagMouseY = -9999;
   //--- Restore scroll only outside pointer and no-tool modes
   if(activeTool != TOOL_NONE && activeTool != TOOL_POINTER)
      ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
   //--- Reflow snapped panel to match the updated chart dimensions
   if(m_snapState != SNAP_FLOAT)
     {
      //--- Read new chart width and pin panel to the correct edge
      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 snapped height so panel never overflows the chart bottom
      if(m_snappedSidebarHeight > 0)
         m_snappedSidebarHeight = MathMin(m_snappedSidebarHeight,
            (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) - m_panelY - 8);
      //--- Recompute layout and rebuild canvases at new size
      CalcSidebarHeight();
      ResizeSidebarCanvases(m_sidebarWidth, m_sidebarHeight);
      DrawSidebar(activeTool);
      //--- Reposition flyout if it is currently open
      if(m_isFlyoutVisible) ShowFlyout(m_flyoutActiveCat, activeTool);
     }
   //--- Resize crosshair line canvases to match new chart dimensions
   OnCrosshairChartChange();
   //--- Resize the drawings canvas to match new chart dimensions
   ResizeDrawingsCanvas();
   ChartRedraw();
  }

//+------------------------------------------------------------------+
//| Handle mouse wheel scroll                                        |
//+------------------------------------------------------------------+
void CChartEventHandler::OnMouseWheelEvent(int mouseX, int mouseY, int wheelDelta, TOOL_TYPE activeTool)
  {
   int lx, ly, flx, fly;
   //--- Determine which surface the cursor is over
   bool overSidebar = HitTestOverSidebar(mouseX, mouseY, lx, ly);
   bool overFlyout  = HitTestOverFlyout(mouseX, mouseY, flx, fly);
   //--- Scroll the sidebar category list if it is too tall to fit
   if(overSidebar && m_sidebarMaxVisibleCats < CAT_COUNT)
     {
      //--- Suppress chart scroll so the wheel drives only sidebar scrolling
      ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
      //--- Move scroll offset one step in the wheel direction, clamped to valid range
      m_sidebarScrollPixels = MathMax(0, MathMin(
         m_sidebarScrollPixels + ((wheelDelta < 0) ? MathMax(1, MouseScrollSpeed) : -MathMax(1, MouseScrollSpeed)),
         CalcSidebarMaxScrollPixels()));
      HideFlyout(); DrawSidebar(activeTool); ChartRedraw();
      return;
     }
   //--- Scroll the flyout tool list if it has more items than can be shown
   if(overFlyout && m_isFlyoutVisible && m_flyoutActiveCat != CAT_NONE)
     {
      int nTools = ArraySize(m_categories[(int)m_flyoutActiveCat].tools);
      if(nTools > m_flyoutMaxVisibleItems)
        {
         //--- Suppress chart scroll so the wheel drives only flyout scrolling
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
         //--- Compute maximum scroll offset and clamp new value
         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;
     }
   //--- Cursor is outside both panels — restore chart scroll
   ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
  }

//+------------------------------------------------------------------+
//| Handle panel drag move                                           |
//+------------------------------------------------------------------+
void CChartEventHandler::HandlePanelDragMove(int mouseX, int mouseY, TOOL_TYPE activeTool)
  {
   //--- Read current chart bounds for clamping
   int chartW = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int chartH = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   //--- Compute new panel position, clamped so it stays fully on screen
   m_panelX = MathMax(0, MathMin(chartW - m_sidebarWidth,  mouseX - m_dragOffsetX));
   m_panelY = MathMax(0, MathMin(chartH - m_sidebarHeight, mouseY - m_dragOffsetY));
   //--- Apply updated position to the sidebar chart object
   ObjectSetInteger(0, m_nameSidebar, OBJPROP_XDISTANCE, m_panelX);
   ObjectSetInteger(0, m_nameSidebar, OBJPROP_YDISTANCE, m_panelY);
   //--- Reposition the flyout to track the moving panel
   if(m_isFlyoutVisible) ShowFlyout(m_flyoutActiveCat, activeTool);
   DrawSidebar(activeTool); ChartRedraw();
  }

//+------------------------------------------------------------------+
//| Handle panel drag release                                        |
//+------------------------------------------------------------------+
void CChartEventHandler::HandlePanelDragRelease(TOOL_TYPE activeTool)
  {
   //--- Clear drag flag
   m_isPanelDragging = false;
   //--- Snap to nearest edge if close enough, then reflow layout
   TrySnapToEdge();
   CalcSidebarHeight();
   ResizeSidebarCanvases(m_sidebarWidth, m_sidebarHeight);
   DrawSidebar(activeTool);
   ChartRedraw();
  }

//+------------------------------------------------------------------+
//| Handle bottom edge resize drag                                   |
//+------------------------------------------------------------------+
void CChartEventHandler::HandleBottomResizeDrag(int mouseX, int mouseY, TOOL_TYPE activeTool)
  {
   //--- Compute vertical delta from the drag start position
   int dy     = mouseY - m_bottomResizeDragStartY;
   int chartH = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   //--- Natural height fits all categories without scrolling
   int naturalH = m_headerGripHeight + 8 + CAT_COUNT * (m_categoryButtonSize + 6) - 6 + 10;
   //--- Minimum height shows at least three category buttons
   int minH     = m_headerGripHeight + 8 + 10 + 3 * (m_categoryButtonSize + 6) - 6;
   //--- Maximum height is the full chart height minus panel top offset
   int maxH     = (m_snapState != SNAP_FLOAT)
      ? MathMin(naturalH, chartH - m_panelY - 8)
      : chartH - m_panelY - 8;
   //--- Clamp new height to the valid range
   int newH = MathMax(minH, MathMin(maxH, m_bottomResizeStartHeight + dy));
   //--- Only rebuild canvases if the height actually changed
   if(newH != m_sidebarHeight)
     {
      //--- Store height in the correct slot depending on snap state
      if(m_snapState != SNAP_FLOAT) m_snappedSidebarHeight = newH; else m_sidebarHeight = newH;
      CalcSidebarHeight();
      ResizeSidebarCanvases(m_sidebarWidth, m_sidebarHeight);
      DrawSidebar(activeTool);
      ChartRedraw();
     }
  }

//+------------------------------------------------------------------+
//| Handle sidebar scroll thumb drag                                 |
//+------------------------------------------------------------------+
void CChartEventHandler::HandleSidebarThumbDrag(int mouseX, int mouseY, TOOL_TYPE activeTool)
  {
   //--- Compute track height and available travel distance for the thumb
   int trackH = CalcSidebarViewportPixels(), travel = trackH - m_sidebarScrollThumbHeight;
   if(travel > 0)
     {
      //--- Map mouse delta to scroll pixel offset using the travel ratio
      int dy    = mouseY - m_sidebarThumbDragStartY;
      int maxPx = CalcSidebarMaxScrollPixels();
      int newPx = MathMax(0, MathMin(maxPx,
         m_sidebarThumbDragStartPixels + (int)MathRound((double)dy / travel * maxPx)));
      //--- Redraw only when the scroll position changes
      if(newPx != m_sidebarScrollPixels)
        { m_sidebarScrollPixels = newPx; HideFlyout(); DrawSidebar(activeTool); ChartRedraw(); }
     }
  }

//+------------------------------------------------------------------+
//| Handle sidebar scroll thumb release                              |
//+------------------------------------------------------------------+
void CChartEventHandler::HandleSidebarThumbRelease(TOOL_TYPE activeTool)
  {
   //--- Clear drag flag and repaint to remove the drag highlight
   m_isSidebarThumbDragging = false;
   DrawSidebar(activeTool);
   ChartRedraw();
  }

//+------------------------------------------------------------------+
//| Handle flyout scroll thumb drag                                  |
//+------------------------------------------------------------------+
void CChartEventHandler::HandleFlyoutThumbDrag(int mouseX, int mouseY)
  {
   //--- Bail out if no category is active
   if(m_flyoutActiveCat == CAT_NONE) return;
   int nTools = ArraySize(m_categories[(int)m_flyoutActiveCat].tools);
   //--- Bail out if all items fit without scrolling
   if(nTools <= m_flyoutMaxVisibleItems) return;
   //--- Compute visible track height and available thumb travel
   int trackH = MathMin(nTools, m_flyoutMaxVisibleItems) * m_flyoutItemHeight;
   int travel = trackH - m_flyoutScrollThumbHeight;
   if(travel > 0)
     {
      //--- Map mouse delta to flyout scroll pixel offset
      int dy    = mouseY - m_flyoutThumbDragStartY;
      int maxPx = (nTools - m_flyoutMaxVisibleItems) * m_flyoutItemHeight;
      int newPx = MathMax(0, MathMin(maxPx,
         m_flyoutThumbDragStartPixels + (int)MathRound((double)dy / travel * maxPx)));
      //--- Redraw only when the scroll position actually changes
      if(newPx != m_flyoutScrollPixels)
        { m_flyoutScrollPixels = newPx; DrawFlyoutForCategory(m_flyoutActiveCat, TOOL_NONE); ChartRedraw(); }
     }
  }

//+------------------------------------------------------------------+
//| Handle flyout scroll thumb release                               |
//+------------------------------------------------------------------+
void CChartEventHandler::HandleFlyoutThumbRelease()
  {
   //--- Clear drag flag and repaint to remove the drag highlight
   m_isFlyoutThumbDragging = false;
   DrawFlyoutForCategory(m_flyoutActiveCat, TOOL_NONE);
   ChartRedraw();
  }

//+------------------------------------------------------------------+
//| Recompute all hover states and redraw if changed                 |
//+------------------------------------------------------------------+
void CChartEventHandler::UpdateAllHoverStates(int mouseX, int mouseY, bool overSidebar, bool overFlyout,
                                               int lx, int ly, int flx, int fly, TOOL_TYPE activeTool)
  {
   //--- Capture previous hover state 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;
   //--- Reset all hover flags before recomputing
   m_isCloseButtonHovered = m_isThemeButtonHovered = m_isGripAreaHovered = false;
   m_isBottomResizeHovered = m_isHoveredSidebarScrollArea = m_isHoveredSidebarThumb = false;
   m_isHoveredFlyoutScrollArea = m_isHoveredFlyoutThumb = false;
   //--- Test all sidebar hit regions 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);
      //--- Test scroll area and thumb only when the list actually scrolls
      if(CalcSidebarMaxScrollPixels() > 0)
        {
         int trackY = CalcClipTop(), trackH = CalcSidebarViewportPixels();
         m_isHoveredSidebarScrollArea = (ly >= trackY && ly <= trackY + trackH);
         if(m_isHoveredSidebarScrollArea)
           {
            //--- Compute thumb rail X based on snap direction
            int tw    = m_sidebarScrollThinWidth;
            int thinX = (m_snapState == SNAP_RIGHT) ? 2 : m_sidebarWidth - tw - 2;
            //--- Widen the hit zone by 4 px on each side for easier grabbing
            if(lx >= thinX - 4 && lx <= thinX + tw + 4)
              {
               //--- Derive thumb top position from current scroll ratio
               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);
              }
           }
        }
     }
   //--- Clear category hover when cursor is outside both panels
   else if(!overFlyout) m_hoveredCategory = CAT_NONE;
   //--- Test all flyout hit regions when cursor is over the flyout
   if(overFlyout)
     {
      m_hoveredFlyoutItem = HitTestFlyoutItem(flx, fly);
      //--- Normalize invalid results to the sentinel value
      if(m_hoveredFlyoutItem < 0) m_hoveredFlyoutItem = -1;
      //--- Check whether the flyout tool list is long enough to scroll
      m_isHoveredFlyoutScrollArea = m_isFlyoutVisible && m_flyoutActiveCat != CAT_NONE &&
         (ArraySize(m_categories[(int)m_flyoutActiveCat].tools) > m_flyoutMaxVisibleItems);
      if(m_isHoveredFlyoutScrollArea)
        {
         //--- Derive flyout scroll geometry to test thumb hit
         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;
         //--- Compute base X of the flyout panel body, accounting for pointer side
         int dispBx   = m_flyoutPointerOnLeft ? m_flyoutPointerHeight : 0;
         //--- Rail sits on the interior edge opposite the pointer
         int thinX    = m_flyoutPointerOnLeft ? (dispBx + m_flyoutWidth - tw - 2) : (dispBx + 2);
         //--- Widen flyout scroll rail hit zone by 6 px on each side
         if(flx >= thinX - 6 && flx <= thinX + tw + 6 && fly >= itemsTop && fly <= itemsTop + trackH)
           {
            //--- Derive thumb top position from current flyout scroll ratio
            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);
           }
        }
     }
   //--- Clear flyout hover when cursor is outside both panels
   else if(!overSidebar) { m_hoveredFlyoutItem = -1; m_isHoveredFlyoutScrollArea = false; }
   //--- Auto-show flyout when cursor enters a category button that differs from the active one
   if(overSidebar && m_hoveredCategory != CAT_NONE &&
       !m_isCloseButtonHovered && !m_isThemeButtonHovered && !m_isGripAreaHovered)
     {
      if(m_hoveredCategory != m_flyoutActiveCat) ShowFlyout(m_hoveredCategory, activeTool);
     }
   //--- Auto-hide flyout when cursor leaves both panels, unless on the transition edge
   else if(!overFlyout && m_isFlyoutVisible)
     {
      bool transitEdge = false;
      if(overSidebar)
        {
         //--- Allow a one-quarter-width grace zone at the panel edge nearest the flyout
         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));
        }
      //--- Hide flyout only when cursor is not in the grace transition zone
      if(!transitEdge) { HideFlyout(); ChartRedraw(); }
     }
   //--- Compare new hover state against snapshot and redraw only on change
   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)
  {
   //--- Test sidebar scroll thumb or track click first
   if(overSidebar && CalcSidebarMaxScrollPixels() > 0)
     {
      int trackY = CalcClipTop(), trackH = CalcSidebarViewportPixels(), tw = m_sidebarScrollThinWidth;
      //--- Compute rail X based on snap direction
      int thinX  = (m_snapState == SNAP_RIGHT) ? 2 : m_sidebarWidth - tw - 2;
      //--- Check cursor is within the widened rail hit zone
      if(lx >= thinX - 4 && lx <= thinX + tw + 4 && ly >= trackY && ly <= trackY + trackH)
        {
         //--- Derive current thumb top from scroll ratio
         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)
           {
            //--- Cursor landed on the thumb — begin thumb drag
            m_isSidebarThumbDragging      = true;
            m_sidebarThumbDragStartY      = mouseY;
            m_sidebarThumbDragStartPixels = m_sidebarScrollPixels;
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
            HideFlyout(); DrawSidebar(activeTool); ChartRedraw();
           }
         else
           {
            //--- Cursor hit the track above or below the thumb — page-scroll one step
            int step = m_categoryButtonSize + m_categoryButtonPadding;
            m_sidebarScrollPixels = MathMax(0, MathMin(maxPx,
               m_sidebarScrollPixels + ((ly < sliderY) ? -step : step)));
            HideFlyout(); DrawSidebar(activeTool); ChartRedraw();
           }
         return;
        }
     }
   //--- Test flyout scroll thumb or track click
   if(overFlyout && m_flyoutActiveCat != CAT_NONE)
     {
      int nTools = ArraySize(m_categories[(int)m_flyoutActiveCat].tools);
      if(nTools > m_flyoutMaxVisibleItems)
        {
         //--- Compute flyout scroll geometry
         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);
         //--- Check cursor is within the widened flyout rail hit zone
         if(flx >= thinX - 6 && flx <= thinX + tw + 6 && fly >= itemsTop && fly <= itemsTop + trackH)
           {
            //--- Derive current flyout thumb top from scroll ratio
            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)
              {
               //--- Cursor landed on the flyout thumb — begin thumb drag
               m_isFlyoutThumbDragging      = true;
               m_flyoutThumbDragStartY      = mouseY;
               m_flyoutThumbDragStartPixels = m_flyoutScrollPixels;
               ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
               DrawFlyoutForCategory(m_flyoutActiveCat, activeTool); ChartRedraw();
              }
            else
              {
               //--- Cursor hit the flyout track — page-scroll one item height
               m_flyoutScrollPixels = MathMax(0, MathMin(maxPx,
                  m_flyoutScrollPixels + ((fly < sliderY) ? -m_flyoutItemHeight : m_flyoutItemHeight)));
               DrawFlyoutForCategory(m_flyoutActiveCat, activeTool); ChartRedraw();
              }
            return;
           }
        }
     }
   //--- Begin panel drag if cursor is in the grip area and not on a header button
   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-edge resize if cursor is on 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;
     }
   //--- Close button click — unload the expert
   if(overSidebar && m_isCloseButtonHovered) { ExpertRemove(); return; }

   //--- Pointer mode: delegate to HandlePointerClick for the full decision tree:
   //--- handle-drag on click, whole-object drag on already-selected body, or select otherwise.
   //--- Uses press-and-drag (no double-click timing) for reliable behavior regardless
   //--- of MT5 event-delivery latency on any system.
   if(!overSidebar && !overFlyout &&
      (activeTool == TOOL_NONE || activeTool == TOOL_POINTER))
     {
      //--- Perform hit test and delegate click to the pointer handler
      int hitId = HitTestAllObjects(mouseX, mouseY);
      HandlePointerClick(mouseX, mouseY);
      ChartRedraw();
     }
  }

//+------------------------------------------------------------------+
//| Handle mouse move: dispatch all sub-handlers                     |
//+------------------------------------------------------------------+
void CChartEventHandler::OnMouseMoveEvent(int mouseX, int mouseY, int mouseButtons, TOOL_TYPE &activeTool)
  {
   int lx, ly, flx, fly;
   //--- Determine which panel surface the cursor is over
   bool overSidebar = HitTestOverSidebar(mouseX, mouseY, lx, ly);
   bool overFlyout  = !overSidebar && HitTestOverFlyout(mouseX, mouseY, flx, fly);

   //--- Earliest priority: active drag handlers — matches pill scroll drag pattern
   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; }
   //--- Object handle drag — early exit mirrors the pill drag pattern
   if(m_isDraggingHandle       && mouseButtons == 1) { HandlePointerDragMove(mouseX, mouseY);  ChartRedraw(); m_previousMouseButtonState = mouseButtons; return; }
   if(m_isDraggingHandle       && mouseButtons == 0) { HandlePointerDragRelease();              ChartRedraw(); m_previousMouseButtonState = mouseButtons; return; }
   //--- Whole-object drag — early exit mirrors the pill drag pattern
   if(m_isDraggingObject       && mouseButtons == 1) { HandlePointerDragMove(mouseX, mouseY);  ChartRedraw(); m_previousMouseButtonState = mouseButtons; return; }
   if(m_isDraggingObject       && mouseButtons == 0) { HandlePointerDragRelease();              ChartRedraw(); m_previousMouseButtonState = mouseButtons; return; }
   //--- Recompute hover highlights now that no drag is consuming the event
   UpdateAllHoverStates(mouseX, mouseY, overSidebar, overFlyout, lx, ly, flx, fly, activeTool);
   //--- Manage chart scroll only outside pointer mode (pointer mode owns scroll permanently)
   if(activeTool != TOOL_NONE && activeTool != TOOL_POINTER)
     {
      bool overAny = overSidebar || overFlyout;
      //--- Lock scroll while cursor is over a panel; unlock when over the chart
      if(!m_isSidebarThumbDragging && !m_isPanelDragging && !m_isResizingBottomEdge && !m_isFlyoutThumbDragging)
         ChartSetInteger(0, CHART_MOUSE_SCROLL, !overAny);
     }
   //--- Detect a fresh left-button press by comparing current and previous button state
   if(mouseButtons == 1 && m_previousMouseButtonState == 0)
      HandleMouseClickDown(mouseX, mouseY, overSidebar, overFlyout, lx, ly, flx, fly, activeTool);
   m_previousMouseButtonState = mouseButtons;
  }

//+------------------------------------------------------------------+
//| CLASS 10 — Top-level indicator class (entry point)              |
//+------------------------------------------------------------------+
class CToolsSidebar : public CChartEventHandler
  {
private:
   TOOL_TYPE m_currentActiveTool; // Currently active tool type
   string    m_currentInstruction; // Instruction string shown to the user for the active tool

public:
                     CToolsSidebar()  { InitDefaults(); }  // Initialize all defaults on construction
                    ~CToolsSidebar() { Destroy(); }         // Clean up all resources on destruction
   bool              Init(long chartId);
   void              Destroy();
   void              OnEvent(const int id, const long &lp, const double &dp, const string &sp);
   void              OnTimer();

private:
   void              InitDefaults();
   void              ToggleTool(TOOL_TYPE toolType);
   void              DeactivateCurrentTool();
   void              CleanupCrosshairOnToolSwitch();
   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 object name assignments
   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";
   m_nameDrawings            = "ToolsPalette_Drawings";
   m_nameObjPriceLabel1      = "ToolsPalette_ObjPriceLabel1";
   m_nameObjTimeLabel1       = "ToolsPalette_ObjTimeLabel1";
   m_nameObjPriceLabel2      = "ToolsPalette_ObjPriceLabel2";
   m_nameObjTimeLabel2       = "ToolsPalette_ObjTimeLabel2";
   m_nameObjPriceLabel3      = "ToolsPalette_ObjPriceLabel3";
   m_nameObjTimeLabel3       = "ToolsPalette_ObjTimeLabel3";
   m_objLabelsVisible        = false;
   //--- Rendering and supersampling parameters
   m_supersampleFactor       = 4;
   m_categoryButtonSize      = 36;
   m_categoryButtonPadding   = 6;
   m_panelCornerRadius       = 10;
   m_headerGripHeight        = 92;
   m_sidebarWidth            = 48;
   m_sidebarHeight           = 0;
   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 defaults
   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 header button hover flags
   m_hoveredCategory         = CAT_NONE;
   m_isCloseButtonHovered    = false;
   m_isThemeButtonHovered    = false;
   m_isGripAreaHovered       = false;
   //--- Flyout layout and state defaults
   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 default
   m_isDarkTheme             = StartDark;
   //--- Crosshair canvas and visibility 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;
   m_isMeasureVertVisible        = false;
   m_isMeasureHorizVisible       = false;
   m_isMeasurePriceLabelVisible  = false;
   m_isMeasureTimeLabelVisible   = false;
   m_isMeasureDiagonalVisible    = false;
   //--- Measure-mode interaction state
   m_isMeasuringActive       = false;
   m_measureAnchorTime       = 0;
   m_measureAnchorPrice      = 0.0;
   m_measureAnchorPixelX     = 0;
   m_measureAnchorPixelY     = 0;
   m_lastClickTimeMicros     = 0;
   //--- Magnifier ghost position sentinels (-9999 = not yet positioned)
   m_lastMagMouseX           = -9999;
   m_lastMagMouseY           = -9999;
   //--- Drawing engine counters and point buffers
   m_drawnObjectCounter      = 0;
   m_drawnObjectCount        = 0;
   m_toolDrawingClickCount   = 0;
   m_drawPoint1Time          = 0;
   m_drawPoint2Time          = 0;
   m_drawPoint1Price         = 0.0;
   m_drawPoint2Price         = 0.0;
   //--- Clear the drawn-objects array
   ArrayResize(m_drawnObjects, 0);
   //--- Hit-testing and selection state defaults
   m_hoveredObjectId         = -1;
   m_hoveredHandleIdx        = -1;
   m_hoveredHandleHostId     = -1;
   //--- Handle display hints for per-tool drawers
   m_currentObjIsActive      = 0;
   m_hideHandleIdx           = -1;
   m_haloHandleIdx           = -1;
   m_selectedObjectId        = -1;
   m_draggedHandleIdx        = -1;
   m_isDraggingHandle        = false;
   m_isDraggingObject        = false;
   m_dragLastMouseX          = 0;
   m_dragLastMouseY          = 0;
   m_lastPointerClickMicros  = 0;
   m_lastPointerClickHitId   = -1;
   //--- Text-annotation prompt state defaults
   m_addTextPromptObjId      = -1;
   m_addTextPromptX1         = 0;
   m_addTextPromptY1         = 0;
   m_addTextPromptX2         = 0;
   m_addTextPromptY2         = 0;
   //--- Label hit-region state defaults
   m_labelHitActive          = false;
   m_labelHitObjIdForLast    = -1;
   m_labelHitObjIdForSelected = -1;
   m_labelHitX1              = 0;
   m_labelHitY1              = 0;
   m_labelHitX2              = 0;
   m_labelHitY2              = 0;
   //--- Allocate rotated-corner hit arrays (fixed size 4) and zero all entries
   ArrayResize(m_addTextPromptCornerX, 4);
   ArrayResize(m_addTextPromptCornerY, 4);
   ArrayResize(m_labelHitCornerX,      4);
   ArrayResize(m_labelHitCornerY,      4);
   for(int _ci = 0; _ci < 4; _ci++)
     {
      m_addTextPromptCornerX[_ci] = 0; m_addTextPromptCornerY[_ci] = 0;
      m_labelHitCornerX[_ci]      = 0; m_labelHitCornerY[_ci]      = 0;
     }
   //--- Hit-test threshold in pixels
   m_hitThreshold            = 8;
   //--- Info panel rect sentinels (-1 = no panel drawn yet)
   m_lastInfoPanelX1         = -1;
   m_lastInfoPanelY1         = -1;
   m_lastInfoPanelX2         = -1;
   m_lastInfoPanelY2         = -1;
   //--- Rubber-band preview state defaults
   m_isPreviewActive         = false;
   m_previewMouseX           = 0;
   m_previewMouseY           = 0;
   m_previewToolType         = TOOL_NONE;
   //--- Label-editing state defaults
   m_isEditingLabel          = false;
   m_labelEditBuffer         = "";
   m_labelCaretPos           = 0;
   m_labelSelectionAnchor    = -1;    // -1 = no active selection
   m_savedKeyboardControl    = true;  // MT5 default for CHART_KEYBOARD_CONTROL
   m_savedQuickNavigation    = true;  // MT5 default for CHART_QUICK_NAVIGATION
   m_keyboardOverrideActive  = false;
   //--- Mouse and tool interaction state defaults
   m_previousMouseButtonState = 0;
   m_currentActiveTool        = TOOL_NONE;
   m_currentInstruction       = "";
  }

//+------------------------------------------------------------------+
//| Initialise all canvases and register chart event hooks           |
//+------------------------------------------------------------------+
bool CToolsSidebar::Init(long chartId)
  {
   //--- Apply default values first so every field is in a known state
   InitDefaults();
   m_chartId = chartId;
   //--- Pin the panel to the correct edge based on initial snap state
   m_panelX  = (m_snapState == SNAP_RIGHT)
      ? (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - m_sidebarWidth : 0;
   //--- Populate all category and tool data structures
   InitAllCategoriesAndTools();
   //--- Load color tokens for the current theme
   ApplyTheme();
   //--- Compute initial sidebar height from category count and geometry
   CalcSidebarHeight();
   //--- Create low-resolution and high-resolution sidebar canvases
   if(!m_canvasSidebar.CreateBitmapLabel(0, 0, m_nameSidebar, 0, 0, m_sidebarWidth, m_sidebarHeight, COLOR_FORMAT_ARGB_NORMALIZE))
     { Print("Failed to create sidebar canvas"); return false; }
   if(!m_canvasSidebarHighRes.Create("ToolsPalette_SidebarHR", m_sidebarWidth * m_supersampleFactor, m_sidebarHeight * m_supersampleFactor, COLOR_FORMAT_ARGB_NORMALIZE))
     { Print("Failed to create sidebar HR canvas"); return false; }
   //--- Create low-resolution and high-resolution flyout canvases
   if(!m_canvasFlyout.CreateBitmapLabel(0, 0, m_nameFlyout, 0, 0, 200, 200, COLOR_FORMAT_ARGB_NORMALIZE))
     { Print("Failed to create flyout canvas"); return false; }
   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 all crosshair overlay canvases
   if(!CreateCrosshairCanvases()) return false;
   //--- Create the full-chart drawings canvas
   if(!CreateDrawingsCanvas()) return false;
   //--- Create axis-label canvases for selected drawn objects
   if(!CreateObjLabelCanvases()) return false;
   //--- Position the sidebar chart object at the computed panel location
   ObjectSetInteger(0, m_nameSidebar, OBJPROP_XDISTANCE, m_panelX);
   ObjectSetInteger(0, m_nameSidebar, OBJPROP_YDISTANCE, m_panelY);
   //--- Assign Z-orders so the flyout renders above the sidebar
   ObjectSetInteger(0, m_nameSidebar, OBJPROP_ZORDER, 100);
   ObjectSetInteger(0, m_nameFlyout,  OBJPROP_ZORDER, 200);
   //--- Perform the initial render with the flyout hidden
   HideFlyout();
   DrawSidebar(m_currentActiveTool);
   //--- Re-apply position after draw in case DrawSidebar adjusted anything
   ObjectSetInteger(0, m_nameSidebar, OBJPROP_XDISTANCE, m_panelX);
   ObjectSetInteger(0, m_nameSidebar, OBJPROP_YDISTANCE, m_panelY);
   //--- Enable mouse-move, mouse-wheel, and chart-scroll events
   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()
  {
   //--- Hide crosshair overlays before tearing down canvases
   CleanupCrosshairOnToolSwitch();
   //--- Kill the cursor-blink timer if a label edit is still in progress
   if(m_isEditingLabel) EventKillTimer();
   //--- Restore keyboard nav that was overridden during label editing.
   //--- EndKeyboardOverride is idempotent — safe to call even when not active.
   EndKeyboardOverride();
   m_currentActiveTool = TOOL_NONE;
   //--- Destroy sidebar canvases and remove chart objects
   m_canvasSidebar.Destroy();        ObjectDelete(0, m_nameSidebar);
   m_canvasSidebarHighRes.Destroy();
   //--- Destroy flyout canvases and remove chart objects
   m_canvasFlyout.Destroy();         ObjectDelete(0, m_nameFlyout);
   m_canvasFlyoutHighRes.Destroy();
   //--- Destroy crosshair overlay canvases
   DestroyCrosshairCanvases();
   //--- Destroy the drawings canvas
   DestroyDrawingsCanvas();
   //--- Destroy axis-label canvases
   DestroyObjLabelCanvases();
   //--- Restore chart scrolling so the chart is usable after indicator removal
   ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
  }

//+------------------------------------------------------------------+
//| Toggle the given tool on or off                                  |
//+------------------------------------------------------------------+
void CToolsSidebar::ToggleTool(TOOL_TYPE toolType)
  {
   FinalizeOpenLabelEdit();
   DeselectAll();
   CancelInProgressPlacement();
   //--- Hide crosshair overlays left over from a previous crosshair session
   CleanupCrosshairOnToolSwitch();
   //--- Re-clicking the active tool or clicking Pointer toggles the tool off
   if(toolType == TOOL_POINTER || m_currentActiveTool == toolType)
     {
      m_currentActiveTool     = TOOL_NONE;
      m_toolDrawingClickCount = 0;
      m_currentInstruction    = "";
      //--- Restore scroll when no tool is active
      ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
     }
   else
     {
      m_currentActiveTool     = toolType;
      m_toolDrawingClickCount = 0;
      //--- Record this tool as the last-used for its category so the tile shows its icon
      RecordToolSelection(toolType);
      if(toolType == TOOL_CROSSHAIR)
        {
         m_currentInstruction = "Move mouse for crosshair. Double-click to measure.";
         ShowAllCrosshairElements();
         //--- Crosshair manages its own scroll behavior
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
        }
      else if(toolType == TOOL_POINTER || toolType == TOOL_NONE)
        {
         m_currentInstruction = "";
         //--- Pointer mode locks scroll to prevent chart panning during object interaction
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
        }
      else
        {
         //--- Drawing tool — allow scroll between placement clicks
         m_currentInstruction = "Click on chart to place " + GetToolLabel(toolType) + ".";
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
        }
     }
  }

//+------------------------------------------------------------------+
//| Deactivate current tool and redraw                               |
//+------------------------------------------------------------------+
void CToolsSidebar::DeactivateCurrentTool()
  {
   //--- Same three-step cleanup as ToggleTool — commit label, drop selection, cancel placement
   FinalizeOpenLabelEdit();
   DeselectAll();
   CleanupCrosshairOnToolSwitch();
   //--- Flush path-tool accumulator arrays to prevent stale points contaminating the next path
   if(m_currentActiveTool == TOOL_PATH)
      CancelPath();
   //--- Reset all active-tool tracking state
   m_currentActiveTool     = TOOL_NONE;
   m_toolDrawingClickCount = 0;
   m_currentInstruction    = "";
   m_isPreviewActive       = false;
   //--- Restore chart scroll on full tool deactivation
   ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
   DrawSidebar(m_currentActiveTool);
   ChartRedraw();
  }

//+------------------------------------------------------------------+
//| Clean up crosshair when switching tools                          |
//+------------------------------------------------------------------+
void CToolsSidebar::CleanupCrosshairOnToolSwitch()
  {
   //--- Only act when the crosshair tool was active or a measure session is open
   if(m_currentActiveTool == TOOL_CROSSHAIR || m_isMeasuringActive)
     {
      //--- Hide all crosshair and measure overlays
      HideAllCrosshairElements();
      if(m_isMeasuringActive)
        {
         //--- End measure session, remove measure chart objects, and restore scroll
         m_isMeasuringActive = false;
         DeleteAllMeasureObjects();
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
        }
     }
  }

//+------------------------------------------------------------------+
//| Update crosshair canvases for the current cursor position        |
//+------------------------------------------------------------------+
void CToolsSidebar::HandleCrosshairMouseMove(int mouseX, int mouseY, bool overSidebar, bool overFlyout)
  {
   //--- Only run when the crosshair tool is active
   if(m_currentActiveTool != TOOL_CROSSHAIR) return;
   //--- Hide all crosshair elements when cursor is over a panel
   if(overSidebar || overFlyout) { HideAllCrosshairElements(); return; }
   //--- Ensure all crosshair elements are visible over the chart
   ShowAllCrosshairElements();
   datetime barTime; double barPrice; int subWindow;
   //--- Convert pixel coordinates to chart time and price
   if(ChartXYToTimePrice(m_chartId, mouseX, mouseY, subWindow, barTime, barPrice))
     {
      //--- Reposition the vertical crosshair line to the cursor X
      UpdateCrossVerticalPosition(mouseX);
      //--- Reposition the horizontal crosshair line to the cursor Y
      UpdateCrossHorizontalPosition(mouseY);
      //--- Refresh price and time axis labels at the cursor position
      UpdateCrosshairAxisLabels(mouseX, mouseY, barTime, barPrice);
      //--- Move the reticle circle to the cursor
      UpdateReticlePosition(mouseX, mouseY);
      //--- Refresh the magnifier lens at the cursor position
      UpdateMagnifierPosition(mouseX, mouseY, barTime, barPrice);
      //--- Update measure overlay if a measure session is active
      if(m_isMeasuringActive)
        {
         int fx = 0, fy = 0;
         //--- Project anchor time/price back to pixel coordinates
         if(ChartTimePriceToXY(m_chartId, 0, m_measureAnchorTime, m_measureAnchorPrice, fx, fy))
           {
            //--- Show measure lines now that both endpoints are available
            ShowMeasureLines();
            UpdateMeasureVerticalPosition(fx);
            UpdateMeasureHorizontalPosition(fy);
            //--- Refresh anchor-point axis labels
            UpdateMeasureAnchorLabels();
           }
         //--- Update the diagonal line from anchor to current cursor
         UpdateMeasureDiagonalLine(mouseX, mouseY);
         //--- Refresh the measurement info label with delta values
         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)
  {
   //--- Escape key: cancel label edit, cancel rubber-band preview, or deactivate the active tool
   if(id == CHARTEVENT_KEYDOWN && lp == 27)
     {
      //--- If a label is being typed, discard the edit
      if(m_isEditingLabel) { CancelLabel(); ChartRedraw(); return; }
      if(m_isPreviewActive)
        {
         //--- Flush path accumulator arrays when escaping mid-path
         if(m_previewToolType == TOOL_PATH)
            CancelPath();
         //--- Clear preview flags and restore clean canvas state
         m_isPreviewActive       = false;
         m_toolDrawingClickCount = 0;
         RedrawAllObjects();
         ChartRedraw();
         return;
        }
      //--- No sub-state was active — fully deactivate the current tool
      DeactivateCurrentTool();
      return;
     }

   //--- Delete key: remove the currently selected drawn object
   if(id == CHARTEVENT_KEYDOWN && lp == 46)
     {
      DeleteSelectedObject();
      ChartRedraw();
      return;
     }

   //--- Handle all mouse-move events with the full routing logic
   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);

      //--- Enforce scroll lock in pointer/no-tool mode, writing only when the state differs
      //--- to avoid a CHART_CHANGE event storm that disrupts click/double-click detection
      if(m_currentActiveTool == TOOL_NONE || m_currentActiveTool == TOOL_POINTER)
        {
         if((bool)ChartGetInteger(0, CHART_MOUSE_SCROLL) != false)
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
        }

      //--- Theme toggle button click: rebuild the entire UI with the new color set
      if(mouseButtons == 1 && m_previousMouseButtonState == 0 &&
         overSidebar && m_isThemeButtonHovered)
        {
         ToggleTheme();
         DrawSidebar(m_currentActiveTool);
         if(m_isFlyoutVisible) DrawFlyoutForCategory(m_flyoutActiveCat, m_currentActiveTool);
         //--- Redraw crosshair lines in the new theme colors
         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;
        }

      //--- Flyout item selection click:
      //---   Action category (CAT_DELETE) → invoke the action directly, no tool activation.
      //---   Regular tool category → activate the hovered tool via ToggleTool.
      if(mouseButtons == 1 && m_previousMouseButtonState == 0 &&
         overFlyout && m_hoveredFlyoutItem >= 0 && m_flyoutActiveCat != CAT_NONE)
        {
         if(IsActionCategory(m_flyoutActiveCat))
           {
            //--- Only action category currently is CAT_DELETE — extend with more branches as needed
            if(m_flyoutActiveCat == CAT_DELETE)
              {
               //--- Run the three-step tool-switch cleanup before mass delete
               FinalizeOpenLabelEdit();
               DeselectAll();
               CancelInProgressPlacement();
               //--- Remove all drawn objects from the canvas
               ClearAllDrawnObjects();
              }
            HideFlyout();
            DrawSidebar(m_currentActiveTool);
            ChartRedraw();
            m_previousMouseButtonState = mouseButtons;
            return;
           }

         //--- Validate item index before activating the tool
         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;
        }

      if(mouseButtons == 1 && m_previousMouseButtonState == 0 &&
         overSidebar && m_hoveredCategory != CAT_NONE &&
         !m_isCloseButtonHovered && !m_isThemeButtonHovered && !m_isGripAreaHovered &&
         ArraySize(m_categories[(int)m_hoveredCategory].tools) > 0)
        {
         int nT = ArraySize(m_categories[(int)m_hoveredCategory].tools);
         TOOL_TYPE pickedTool;
         if(nT == 1)
           {
            //--- Single-tool category — toggle the only tool in the category
            pickedTool = m_categories[(int)m_hoveredCategory].tools[0].toolType;
           }
         else
           {
            //--- Multi-tool — use the last-used tool; fall back to first if slot is uninitialized
            pickedTool = m_lastUsedToolPerCategory[(int)m_hoveredCategory];
            if(pickedTool == TOOL_NONE)
               pickedTool = m_categories[(int)m_hoveredCategory].tools[0].toolType;
           }
         ToggleTool(pickedTool);
         HideFlyout();
         DrawSidebar(m_currentActiveTool);
         ChartRedraw();
         m_previousMouseButtonState = mouseButtons;
         return;
        }

      //--- Crosshair double-click: toggle the measure mode anchor
      if(mouseButtons == 1 && m_previousMouseButtonState == 0 &&
         m_currentActiveTool == TOOL_CROSSHAIR && !overSidebar && !overFlyout)
        {
         datetime barTime; double barPrice; int subWindow;
         //--- Convert click pixel to chart time/price before delegating
         if(ChartXYToTimePrice(m_chartId, mouseX, mouseY, subWindow, barTime, barPrice))
            HandleCrosshairDoubleClick(mouseX, mouseY, barTime, barPrice);
         m_previousMouseButtonState = mouseButtons;
         return;
        }

      //--- Drawing tool placement click on the chart area
      if(mouseButtons == 1 && m_previousMouseButtonState == 0 &&
         m_currentActiveTool != TOOL_NONE     &&
         m_currentActiveTool != TOOL_CROSSHAIR &&
         m_currentActiveTool != TOOL_POINTER   &&
         !overSidebar && !overFlyout)
        {
         TOOL_TYPE toolBeforeClick = m_currentActiveTool;
         //--- Delegate the click to the drawing engine
         HandleDrawingClick(mouseX, mouseY, m_currentActiveTool, m_currentInstruction);
         //--- If the drawing click completed the object, run the full deactivation path
         if(toolBeforeClick != TOOL_NONE && m_currentActiveTool == TOOL_NONE)
           {
            m_isPreviewActive       = false;
            m_toolDrawingClickCount = 0;
            //--- Lock scroll to match the pointer/no-tool default
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
            //--- Clear hover state so the new object is purely selected, not also hovered.
            //--- Without this, handles + halo appear immediately, conflicting with the
            //--- click-outside-to-deselect flow.
            m_hoveredObjectId     = -1;
            m_hoveredHandleIdx    = -1;
            m_hoveredHandleHostId = -1;
            //--- Force a redraw so the newly placed object's handles appear immediately
            RedrawAllObjects();
           }
         DrawSidebar(m_currentActiveTool);
         ChartRedraw();
         m_previousMouseButtonState = mouseButtons;
         return;
        }

      //--- Refresh crosshair canvas positions for the current cursor location
      HandleCrosshairMouseMove(mouseX, mouseY, overSidebar, overFlyout);

      //--- Update rubber-band preview when a multi-click tool is mid-placement
      if(m_isPreviewActive && !overSidebar && !overFlyout)
        {
         UpdatePreviewMousePos(mouseX, mouseY);
         RedrawAllObjects();
         ChartRedraw();
        }

      //--- Detect hovered drawn objects when no drag is active and pointer mode is on
      if((m_currentActiveTool == TOOL_NONE || m_currentActiveTool == TOOL_POINTER) &&
         !m_isDraggingHandle && !m_isDraggingObject && !overSidebar && !overFlyout)
         HandlePointerMouseMove(mouseX, mouseY);

      //--- Route remaining events including sidebar/panel drag sub-handlers
      RouteChartEvent(id, lp, dp, sp, m_currentActiveTool);

      //--- Build tooltip text from the hovered category or flyout item
      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;
        }
      //--- Apply tooltip string to the sidebar chart object
      ObjectSetString(0, m_nameSidebar, OBJPROP_TOOLTIP, tip);
      return;
     }

   //--- Route all non-mouse-move events (chart change, wheel, keyboard) through the standard path
   RouteChartEvent(id, lp, dp, sp, m_currentActiveTool);
  }

//+------------------------------------------------------------------+
//| Expert timer function                                            |
//+------------------------------------------------------------------+
void CToolsSidebar::OnTimer()
  {
//---
   //--- Only act during an active label-edit session
   if(m_isEditingLabel)
     {
      //--- Trigger a full redraw to advance the cursor blink state
      RedrawAllObjects();
      ChartRedraw();
     }
//---
  }


#endif // TOOLS_PALETTE_SHELL_MQH
//+------------------------------------------------------------------+