preview
MQL5 Trading Tools (Part 19): Building an Interactive Tools Palette for Chart Drawing

MQL5 Trading Tools (Part 19): Building an Interactive Tools Palette for Chart Drawing

MetaTrader 5Trading |
688 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introduction

In our previous article (Part 18), we combined vector-based rectangles and triangles to create rounded speech bubbles/balloons with orientation control in MetaQuotes Language 5 (MQL5), enabling dynamic UI elements for trading interfaces. In Part 19, we develop an interactive tools palette for chart drawing, featuring draggable panels, resizing, theme switching, and buttons for tools like crosshairs, lines, and shapes. This customizable system enhances analysis with real-time interactions and instructions. We will cover the following topics:

  1. Designing Interactive Tools Palettes for Charts
  2. Implementation in MQL5
  3. Backtesting
  4. Conclusion

By the end, you’ll have a functional palette ready for expanding trading workflows—let’s dive in!


Designing Interactive Tools Palettes for Charts

The interactive tools palette for chart drawing centralizes buttons for essential functions like crosshairs, trendlines, lines, rectangles, Fibonacci, text, and arrows, enabling quick selection and application on trading charts with real-time feedback. It supports dragging, resizing, theme switching between dark and light modes, and minimizing to optimize the workspace, while providing instructions and status updates for intuitive use. This design streamlines analysis by offering a customizable, responsive UI that adapts to user interactions without cluttering the chart. We plan to use canvas objects for the header and panel, manage mouse events for interactivity, define enumerations for resize modes and tools, and handle drawing logic with object creation on charts. In brief, here is a visual representation of our objectives.

TOOL PALETTE OBJECTIVES


Implementation in MQL5

To create the program in MQL5, open the MetaEditor, go to the Navigator, locate the Experts folder, click on the "New" tab, and follow the prompts to create the file. Once it is made, in the coding environment, we will need to declare some input parameters and global variables that we will use throughout the program.

//+------------------------------------------------------------------+
//|                                                Tools Palette.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict

#include <Canvas/Canvas.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CCanvas canvasHeader;                                   //--- Declare header canvas
CCanvas canvasTools;                                    //--- Declare tools canvas

string canvasHeaderName = "HeaderCanvas";               //--- Set header canvas name
string canvasToolsName = "ToolsCanvas";                 //--- Set tools canvas name

input int CanvasX = 20;                                 // Panel X position
input int CanvasY = 30;                                 // Panel Y position
input int CanvasWidth = 350;                            // Panel width
input int CanvasHeight = 200;                           // Panel height
input double BackgroundOpacity = 0.8;                   // Background opacity

bool is_dark_theme = true;                              //--- Set initial dark theme flag
color DarkHeaderColor = C'60,60,60';                    //--- Set dark header color
color DarkHeaderHoverColor = clrRed;                    //--- Set dark header hover color
color DarkHeaderDragColor = clrMediumBlue;              //--- Set dark header drag color
color DarkHeaderTextColor = clrWhite;                   //--- Set dark header text color
color DarkBorderColor = clrBlack;                       //--- Set dark border color
color DarkTopColor = clrBlack;                          //--- Set dark top color
color DarkIconColor = clrWhite;                         //--- Set dark icon color
color LightHeaderColor = clrSilver;                     //--- Set light header color
color LightHeaderHoverColor = clrRed;                   //--- Set light header hover color
color LightHeaderDragColor = clrMediumBlue;             //--- Set light header drag color
color LightHeaderTextColor = clrBlack;                  //--- Set light header text color
color LightBorderColor = clrBlack;                      //--- Set light border color
color LightTopColor = clrWhite;                         //--- Set light top color
color LightIconColor = clrBlack;                        //--- Set light icon color
color DarkInstructionColor = clrLime;                   //--- Set dark instruction color
color LightInstructionColor = clrGreen;                 //--- Set light instruction color
color DarkStatusColor = clrMagenta;                     //--- Set dark status color
color LightStatusColor = clrPurple;                     //--- Set light status color
color DarkActiveBtnColor = clrGreen;                    //--- Set dark active button color
color LightActiveBtnColor = clrDarkGreen;               //--- Set light active button color
color DarkHoverBtnColor = clrDarkGray;                  //--- Set dark hover button color
color LightHoverBtnColor = clrLightGray;                //--- Set light hover button color

int currentCanvasX = CanvasX;                           //--- Initialize current X position
int currentCanvasY = CanvasY;                           //--- Initialize current Y position
int currentWidth = CanvasWidth;                         //--- Initialize current width
int currentHeight = CanvasHeight;                       //--- Initialize current height
bool panel_dragging = false;                            //--- Initialize panel dragging flag
int panel_drag_x = 0, panel_drag_y = 0;                 //--- Initialize drag coordinates
int panel_start_x = 0, panel_start_y = 0;               //--- Initialize start coordinates
bool resizing = false;                                  //--- Initialize resizing flag

enum ENUM_RESIZE_MODE {
   NONE,                                                // No resize
   BOTTOM,                                              // Bottom resize
   RIGHT,                                               // Right resize
   BOTTOM_RIGHT                                         // Bottom-right resize
};

ENUM_RESIZE_MODE resize_mode = NONE;                    //--- Initialize resize mode
ENUM_RESIZE_MODE hover_mode = NONE;                     //--- Initialize hover mode
int resize_start_x = 0, resize_start_y = 0;             //--- Initialize resize start coordinates
int start_width = 0, start_height = 0;                  //--- Initialize start dimensions
const int resize_thickness = 5;                         //--- Set resize thickness
const int min_width = 150;                              //--- Set minimum width
const int min_height = 200;                             //--- Set minimum height
int hover_mouse_local_x = 0;                            //--- Initialize local hover X
int hover_mouse_local_y = 0;                            //--- Initialize local hover Y
bool header_hovered = false;                            //--- Initialize header hover flag
bool minimize_hovered = false;                          //--- Initialize minimize hover flag
bool close_hovered = false;                             //--- Initialize close hover flag
bool theme_hovered = false;                             //--- Initialize theme hover flag
bool resize_hovered = false;                            //--- Initialize resize hover flag
int prev_mouse_state = 0;                               //--- Initialize previous mouse state
int header_height = 27;                                 //--- Set header height
int button_size = 25;                                   //--- Set button size
int theme_x_offset = -75;                               //--- Set theme X offset
int minimize_x_offset = -50;                            //--- Set minimize X offset
int close_x_offset = -25;                               //--- Set close X offset
bool panel_minimized = false;                           //--- Initialize minimized flag

enum TOOL_TYPE {
   TOOL_NONE,                                           // No tool
   TOOL_CROSSHAIR,                                      // Crosshair tool
   TOOL_TRENDLINE,                                      // Trendline tool
   TOOL_HLINE,                                          // Horizontal line tool
   TOOL_VLINE,                                          // Vertical line tool
   TOOL_RECTANGLE,                                      // Rectangle tool
   TOOL_FIBO,                                           // Fibonacci tool
   TOOL_TEXT,                                           // Text tool
   TOOL_ARROW                                           // Arrow tool
};

TOOL_TYPE active_tool = TOOL_NONE;                      //--- Initialize active tool
bool drawing_first_click = false;                       //--- Initialize first click flag
datetime draw_time1 = 0;                                //--- Initialize draw time 1
double draw_price1 = 0.0;                               //--- Initialize draw price 1
long local_chart_id = 0;                                //--- Initialize local chart ID
string current_instruction = "";                        //--- Initialize current instruction

struct ToolButton {                                     // Define tool button structure
   string symbol;                                       // Store symbol
   string font;                                         // Store font
   TOOL_TYPE type;                                      // Store type
   string tooltip;                                      // Store tooltip
};

ToolButton buttons[8];                                  //--- Declare buttons array
int hovered_tool_index = -1;                            //--- Initialize hovered tool index

bool measuring = false;                                 //--- Initialize measuring flag
datetime fixed_time = 0;                                //--- Initialize fixed time
double fixed_price = 0.0;                               //--- Initialize fixed price
ulong last_click_time = 0;                              //--- Initialize last click time

const int tools_y_offset = -1;                          //--- Set tools Y offset

We begin the implementation by including the Canvas library with "#include <Canvas/Canvas.mqh>" to access core graphical functionalities for building the palette. Next, we declare global canvas objects "canvasHeader" and "canvasTools" for the panel's header and main area, assigning names like "HeaderCanvas" and "ToolsCanvas" for reference, along with user inputs for position, size, and background opacity to allow customization. To support themes, we initialize "is_dark_theme" to true and define color constants for dark and light modes, covering headers, borders, icons, instructions, status, and buttons, enabling visual consistency across switches.

We track panel state with variables for current position and dimensions, dragging flags, resize modes via the "ENUM_RESIZE_MODE" enumeration (NONE, BOTTOM, RIGHT, BOTTOM_RIGHT), hover states, and constants like minimum sizes and thicknesses for usability limits. For tools, we define the "TOOL_TYPE" enumeration listing options from NONE to ARROW, initialize "active_tool" to NONE, and manage drawing flags like "drawing_first_click", along with a "ToolButton" structure holding symbol, font, type, and tooltip, creating an array "buttons[8]" for the palette items. Finally, we set up variables for measuring mode, fixed points, click timing, hovered indices, and a y-offset for tools alignment, preparing for interactive features. We will then need some helper functions to make the code modular.

//+------------------------------------------------------------------+
//| Get theme-aware colors                                           |
//+------------------------------------------------------------------+
color GetHeaderColor() { return is_dark_theme ? DarkHeaderColor : LightHeaderColor; }                //--- Return header color based on theme
color GetHeaderHoverColor() { return is_dark_theme ? DarkHeaderHoverColor : LightHeaderHoverColor; } //--- Return header hover color based on theme
color GetHeaderDragColor() { return is_dark_theme ? DarkHeaderDragColor : LightHeaderDragColor; }    //--- Return header drag color based on theme
color GetHeaderTextColor() { return is_dark_theme ? DarkHeaderTextColor : LightHeaderTextColor; }    //--- Return header text color based on theme
color GetBorderColor() { return is_dark_theme ? DarkBorderColor : LightBorderColor; }                //--- Return border color based on theme
color GetTopColor() { return is_dark_theme ? DarkTopColor : LightTopColor; }                         //--- Return top color based on theme
color GetIconColor() { return is_dark_theme ? DarkIconColor : LightIconColor; }                      //--- Return icon color based on theme
color GetInstructionColor() { return is_dark_theme ? DarkInstructionColor : LightInstructionColor; } //--- Return instruction color based on theme
color GetStatusColor() { return is_dark_theme ? DarkStatusColor : LightStatusColor; }                //--- Return status color based on theme
color GetActiveBtnColor() { return is_dark_theme ? DarkActiveBtnColor : LightActiveBtnColor; }       //--- Return active button color based on theme
color GetHoverBtnColor() { return is_dark_theme ? DarkHoverBtnColor : LightHoverBtnColor; }          //--- Return hover button color based on theme

//+------------------------------------------------------------------+
//| Get tool name from type                                          |
//+------------------------------------------------------------------+
string GetToolName(TOOL_TYPE t) {
   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      if (buttons[i].type == t) {                               //--- Check if type matches
         return StringSubstr(buttons[i].tooltip, 0, StringFind(buttons[i].tooltip, " Tool")); //--- Return tool name
      }
   }
   return "None";                                               //--- Return default if no match
}

//+------------------------------------------------------------------+
//| Check if tool is single-click                                    |
//+------------------------------------------------------------------+
bool IsSingleClickTool(TOOL_TYPE t) {
   switch(t) {                                                  //--- Switch on tool type
      case TOOL_HLINE:                                          //--- Handle horizontal line
      case TOOL_VLINE:                                          //--- Handle vertical line
      case TOOL_TEXT:                                           //--- Handle text
      case TOOL_ARROW:                                          //--- Handle arrow
         return true;                                           //--- Return true for single-click tools
      default:                                                  //--- Handle default
         return false;                                          //--- Return false otherwise
   }
}

We continue by defining a series of getter functions like "GetHeaderColor" to retrieve theme-aware colors, each returning the dark or light variant based on the "is_dark_theme" flag, ensuring consistent styling for headers, hovers, drags, texts, borders, icons, instructions, status, active buttons, and hovers without redundant checks. Next, the "GetToolName" function extracts the name of a tool from its type by looping through the "buttons" array, matching the type, and substringing the tooltip up to " Tool" for clean output, defaulting to "None" if no match. To classify tools, we implement "IsSingleClickTool" using a switch statement on the tool type, returning true for horizontal/vertical lines, text, and arrows that require only one click, and false otherwise for precise event handling. With that done, we can initialize the creation of the header and the palette body.

//+------------------------------------------------------------------+
//| Draw header                                                      |
//+------------------------------------------------------------------+
void DrawHeader() {
   canvasHeader.Erase(0);                                       //--- Erase header canvas

   color header_bg = panel_dragging ? GetHeaderDragColor() : (header_hovered ? GetHeaderHoverColor() : GetHeaderColor()); //--- Get header background color
   canvasHeader.FillRectangle(0, 0, currentWidth - 1, header_height - 1, ColorToARGB(header_bg, 255)); //--- Fill header rectangle

   uint argbBorder = ColorToARGB(GetBorderColor(), 255);        //--- Get border ARGB
   canvasHeader.Rectangle(0, 0, currentWidth - 1, header_height - 1, argbBorder); //--- Draw border rectangle

   canvasHeader.FontSet("Arial Bold", 13);                      //--- Set font for title
   canvasHeader.TextOut(10, (header_height - 13)/2, "Tools Palette", ColorToARGB(GetHeaderTextColor(), 255)); //--- Draw title text

   int button_y = (header_height - 20)/2;                       //--- Compute button Y position

   int theme_button_x = currentWidth + theme_x_offset;          //--- Compute theme button X
   canvasHeader.FontSet("Wingdings", 20);                       //--- Set font for theme symbol
   string theme_symbol = CharToString(91);                      //--- Set theme symbol
   int theme_w = canvasHeader.TextWidth(theme_symbol);          //--- Get theme symbol width
   int theme_x = theme_button_x + (button_size - theme_w) / 2;  //--- Compute theme X position
   color theme_color = theme_hovered ? clrYellow : GetHeaderTextColor(); //--- Get theme color
   canvasHeader.TextOut(theme_x, button_y, theme_symbol, ColorToARGB(theme_color, 255)); //--- Draw theme symbol

   int min_button_x = currentWidth + minimize_x_offset;         //--- Compute minimize button X
   string min_symbol = panel_minimized ? CharToString(111) : CharToString(114); //--- Set minimize symbol
   int min_w = canvasHeader.TextWidth(min_symbol);              //--- Get minimize symbol width
   int min_x = min_button_x + (button_size - min_w) / 2;        //--- Compute minimize X position
   color min_color = minimize_hovered ? clrYellow : GetHeaderTextColor(); //--- Get minimize color
   canvasHeader.TextOut(min_x, button_y, min_symbol, ColorToARGB(min_color, 255)); //--- Draw minimize symbol

   int close_button_x = currentWidth + close_x_offset;          //--- Compute close button X
   canvasHeader.FontSet("Webdings", 20);                        //--- Set font for close symbol
   string close_symbol = CharToString(114);                     //--- Set close symbol
   int close_w = canvasHeader.TextWidth(close_symbol);          //--- Get close symbol width
   int close_x = close_button_x + (button_size - close_w) / 2;  //--- Compute close X position
   color close_color = close_hovered ? clrRed : GetHeaderTextColor(); //--- Get close color
   canvasHeader.TextOut(close_x, button_y, close_symbol, ColorToARGB(close_color, 255)); //--- Draw close symbol

   canvasHeader.Update();                                       //--- Update header canvas
}

//+------------------------------------------------------------------+
//| Draw tools panel                                                 |
//+------------------------------------------------------------------+
void DrawToolsPanel() {
   canvasTools.Erase(0);                                        //--- Erase tools canvas

   uint argb_fill = ColorToARGB(GetTopColor(), (uchar)(255 * BackgroundOpacity)); //--- Get fill ARGB
   canvasTools.FillRectangle(0, 0, currentWidth - 1, currentHeight - 1, argb_fill); //--- Fill background rectangle

   uint argb_border = ColorToARGB(GetBorderColor(), 255);       //--- Get border ARGB
   canvasTools.Rectangle(0, 0, currentWidth - 1, currentHeight - 1, argb_border); //--- Draw border rectangle

   int columns = 4;                                             //--- Set number of columns
   int padding = 5;                                             //--- Set padding
   int button_width = (currentWidth - (columns + 1) * padding) / columns; //--- Compute button width
   int button_height = 70;                                      //--- Set button height
   int start_x = padding;                                       //--- Set start X
   int start_y = padding;                                       //--- Set start Y

   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      int col = i % columns;                                    //--- Compute column
      int row = i / columns;                                    //--- Compute row
      int x = start_x + col * (button_width + padding);         //--- Compute X position
      int y = start_y + row * (button_height + padding);        //--- Compute Y position

      color btn_bg = (active_tool == buttons[i].type) ? GetActiveBtnColor() : ((i == hovered_tool_index) ? GetHoverBtnColor() : clrNONE); //--- Get button background
      if (btn_bg != clrNONE) canvasTools.FillRectangle(x, y, x + button_width - 1, y + button_height - 1, ColorToARGB(btn_bg, 255)); //--- Fill button rectangle if needed

      canvasTools.FontSet(buttons[i].font, 35);                 //--- Set font for icon
      int text_w = canvasTools.TextWidth(buttons[i].symbol);    //--- Get icon width
      int text_h = canvasTools.TextHeight(buttons[i].symbol);   //--- Get icon height
      int icon_x = x + (button_width - text_w) / 2;             //--- Compute icon X
      int icon_y = y + 5;                                       //--- Set icon Y
      canvasTools.TextOut(icon_x, icon_y, buttons[i].symbol, ColorToARGB(GetIconColor(), 255)); //--- Draw icon

      string tool_name = StringSubstr(buttons[i].tooltip, 0, StringFind(buttons[i].tooltip, " Tool")); //--- Get tool name
      canvasTools.FontSet("Arial Bold", 12);                    //--- Set font for name
      int name_w = canvasTools.TextWidth(tool_name);            //--- Get name width
      canvasTools.TextOut(x + (button_width - name_w) / 2, y + button_height - 20, tool_name, ColorToARGB(GetIconColor(), 255)); //--- Draw tool name
   }

   if (StringLen(current_instruction) > 0) {                    //--- Check if instruction exists
      canvasTools.FontSet("Arial", 14);                         //--- Set font for instruction
      int instr_w = canvasTools.TextWidth(current_instruction); //--- Get instruction width
      int instr_x = (currentWidth - instr_w) / 2;               //--- Compute instruction X
      canvasTools.TextOut(instr_x, currentHeight - 30, current_instruction, ColorToARGB(GetInstructionColor(), 255)); //--- Draw instruction
   }

   string status_text = "Active: " + GetToolName(active_tool);  //--- Set status text
   canvasTools.FontSet("Arial Bold", 14);                       //--- Set font for status
   int status_w = canvasTools.TextWidth(status_text);           //--- Get status width
   int status_x = (currentWidth - status_w) / 2;                //--- Compute status X
   canvasTools.TextOut(status_x, currentHeight - 15, status_text, ColorToARGB(GetStatusColor(), 255)); //--- Draw status

   if (resize_hovered || resizing) {                            //--- Check if resize hovered or resizing
      string icon_font = "Wingdings 3";                         //--- Set icon font
      int icon_size = 25;                                       //--- Set icon size
      uchar icon_code = 0;                                      //--- Initialize icon code
      int angle = 0;                                            //--- Initialize angle
      switch (hover_mode) {                                     //--- Switch on hover mode
         case BOTTOM: icon_code = (uchar)'2'; angle = 0; break; //--- Set for bottom
         case RIGHT: icon_code = (uchar)'1'; angle = 0; break;  //--- Set for right
         case BOTTOM_RIGHT: icon_code = (uchar)'2'; angle = 450; break; //--- Set for bottom-right
      }
      if (icon_code != 0) {                                     //--- Check if icon code set
         canvasTools.FontSet(icon_font, icon_size);             //--- Set font for icon
         canvasTools.FontAngleSet(angle);                       //--- Set font angle
         color icon_color = (resize_hovered || resizing) ? clrRed : GetIconColor(); //--- Get icon color
         int icon_x = (hover_mode == BOTTOM) ? MathMin(MathMax(hover_mouse_local_x - icon_size/2, 0), currentWidth - icon_size) : currentWidth - icon_size - 2; //--- Compute icon X
         int icon_y = (hover_mode == RIGHT) ? MathMin(MathMax(hover_mouse_local_y - icon_size/2, 0), currentHeight - icon_size) : currentHeight - icon_size - 2; //--- Compute icon Y
         if (hover_mode == BOTTOM_RIGHT) { icon_x = currentWidth - icon_size - 10; icon_y = currentHeight - icon_size; } //--- Adjust for bottom-right
         canvasTools.TextOut(icon_x, icon_y, CharToString(icon_code), ColorToARGB(icon_color, 255)); //--- Draw icon
         canvasTools.FontAngleSet(0);                           //--- Reset font angle
      }
   }

   canvasTools.Update();                                        //--- Update tools canvas
}

To create the initial rendering, we implement the "DrawHeader" function to render the palette's header on the canvas, starting by erasing it with Erase set to 0, then filling a rectangle with dynamic background color from getters like "GetHeaderDragColor" if dragging, "GetHeaderHoverColor" if hovered, or standard otherwise, converted to ARGB. Next, we draw the border using Rectangle with ARGB from "GetBorderColor", set bold "Arial" font for the title "Tools Palette" with FontSet and TextOut in the theme text color. To add controls, we compute button Y centering, then for theme, set Wingdings font, position and color the '[' symbol based on hover, and draw with "TextOut"; similarly for minimize (toggle 'o' or 'r' symbol) and close ('X' in Webdings), adjusting colors for hover states like yellow or red. Finally, we update the canvas with Update to display changes. You can customize the icons using your own characters if needed. See a set of the font characters you can use.

SYMBOL FONTS

In the "DrawToolsPanel" function, we erase the tools canvas, fill it with the top color ARGB adjusted for "BackgroundOpacity", and draw the border rectangle. We lay out buttons in 4 columns with padding, loop to fill backgrounds if active or hovered using "GetActiveBtnColor" or "GetHoverBtnColor", set custom fonts for icons like 'v' in "Wingdings", center and draw them with TextOut in icon color, then add bold tool names below. If there's an instruction in "current_instruction", we draw it centered near the bottom with "GetInstructionColor"; add active tool status similarly. To indicate resizing, if "resize_hovered" or "resizing", we set "Wingdings 3" font, determine arrow code and angle per "hover_mode" (e.g., '2' at 450 for bottom-right), position dynamically, draw in red or icon color, and reset angle. We conclude by updating the canvas with "Update". We can now initialize this by drawing it using the following logic.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   currentCanvasX = CanvasX;                                    //--- Set current X from input
   currentCanvasY = CanvasY;                                    //--- Set current Y from input
   currentWidth = CanvasWidth;                                  //--- Set current width from input
   currentHeight = CanvasHeight;                                //--- Set current height from input

   if (!canvasHeader.CreateBitmapLabel(0, 0, canvasHeaderName, currentCanvasX, currentCanvasY, currentWidth, header_height, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create header canvas
      Print("Failed to create Header Canvas");                  //--- Print failure message
      return(INIT_FAILED);                                      //--- Return initialization failure
   }

   if (!canvasTools.CreateBitmapLabel(0, 0, canvasToolsName, currentCanvasX, currentCanvasY + header_height + tools_y_offset, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create tools canvas
      Print("Failed to create Tools Canvas");                   //--- Print failure message
      return(INIT_FAILED);                                      //--- Return initialization failure
   }

   buttons[0].symbol = CharToString('v'); buttons[0].font = "Wingdings"; buttons[0].type = TOOL_CROSSHAIR; buttons[0].tooltip = "Crosshair Tool"; //--- Initialize button 0
   buttons[1].symbol = CharToString('.'); buttons[1].font = "Wingdings 3"; buttons[1].type = TOOL_TRENDLINE; buttons[1].tooltip = "Trendline Tool"; //--- Initialize button 1
   buttons[2].symbol = CharToString('*'); buttons[2].font = "Wingdings 3"; buttons[2].type = TOOL_HLINE; buttons[2].tooltip = "Horizontal Line Tool"; //--- Initialize button 2
   buttons[3].symbol = CharToString('+'); buttons[3].font = "Wingdings 3"; buttons[3].type = TOOL_VLINE; buttons[3].tooltip = "Vertical Line Tool"; //--- Initialize button 3
   buttons[4].symbol = CharToString('c'); buttons[4].font = "Webdings"; buttons[4].type = TOOL_RECTANGLE; buttons[4].tooltip = "Rectangle Tool"; //--- Initialize button 4
   buttons[5].symbol = CharToString('"'); buttons[5].font = "Webdings"; buttons[5].type = TOOL_FIBO; buttons[5].tooltip = "Fibonacci Tool"; //--- Initialize button 5
   buttons[6].symbol = CharToString('>'); buttons[6].font = "Webdings"; buttons[6].type = TOOL_TEXT; buttons[6].tooltip = "Text Tool"; //--- Initialize button 6
   buttons[7].symbol = CharToString(225); buttons[7].font = "Wingdings"; buttons[7].type = TOOL_ARROW; buttons[7].tooltip = "Arrow Tool"; //--- Initialize button 7

   DrawHeader();                                                //--- Draw header
   DrawToolsPanel();                                            //--- Draw tools panel

   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);            //--- Enable mouse move events
   ChartRedraw();                                               //--- Redraw chart
   local_chart_id = ChartID();                                  //--- Set local chart ID
   return(INIT_SUCCEEDED);                                      //--- Return initialization success
}

We proceed in the OnInit event handler by initializing current panel positions and dimensions from user inputs, ensuring alignment with configured values. Next, we create the header canvas using CreateBitmapLabel with ARGB normalize for transparency, logging failures, and returning INIT_FAILED if unsuccessful, and similarly for the tools canvas positioned below the header with a y-offset. To populate the palette, we initialize the "buttons" array entries with specific symbols (e.g., 'v' for crosshair), fonts like Wingdings, tool types from the enumeration, and tooltips for hover info. We then draw the header and tools panel via their respective functions, enable mouse move events with ChartSetInteger to capture interactions, redraw the chart, store the local chart ID for object management, and return INIT_SUCCEEDED. Upon compilation, we get the following outcome.

PANEL INITIALIZATION

We can see the panel is initialised correctly. What we now need to do is breathe life into the panel to handle interactivity and theme changes. We will define some more helper functions to get that done easily.

//+------------------------------------------------------------------+
//| Toggle theme                                                     |
//+------------------------------------------------------------------+
void ToggleTheme() {
   is_dark_theme = !is_dark_theme;                              //--- Toggle theme flag
   DrawHeader();                                                //--- Redraw header
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Toggle minimize                                                  |
//+------------------------------------------------------------------+
void ToggleMinimize() {
   panel_minimized = !panel_minimized;                          //--- Toggle minimized flag
   if (panel_minimized) {                                       //--- Check if minimized
      canvasTools.Destroy();                                    //--- Destroy tools canvas
   } else {                                                     //--- Handle restored
      canvasTools.CreateBitmapLabel(0, 0, canvasToolsName, currentCanvasX, currentCanvasY + header_height + tools_y_offset, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE); //--- Recreate tools canvas
      DrawToolsPanel();                                         //--- Redraw tools panel
   }
   DrawHeader();                                                //--- Redraw header
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Is mouse over header excluding buttons                           |
//+------------------------------------------------------------------+
bool IsMouseOverHeader(int mx, int my) {
   if (mx < currentCanvasX || mx > currentCanvasX + currentWidth || my < currentCanvasY || my > currentCanvasY + header_height) return false; //--- Check outside header
   int btn_left = currentCanvasX + currentWidth + theme_x_offset; //--- Compute buttons left
   if (mx >= btn_left && mx <= btn_left + button_size * 3) return false; //--- Check over buttons
   return true;                                                 //--- Return true if over header
}

//+------------------------------------------------------------------+
//| Is mouse over specific button                                    |
//+------------------------------------------------------------------+
bool IsMouseOverButton(int mx, int my, int offset) {
   int btn_left = currentCanvasX + currentWidth + offset;       //--- Compute button left
   return (mx >= btn_left && mx <= btn_left + button_size && my >= currentCanvasY && my <= currentCanvasY + header_height); //--- Return if over button
}

//+------------------------------------------------------------------+
//| Is mouse over resize area                                        |
//+------------------------------------------------------------------+
bool IsMouseOverResize(int mx, int my, ENUM_RESIZE_MODE &rm) {
   if (panel_minimized) return false;                           //--- Return false if minimized
   int tx = currentCanvasX;                                     //--- Set tools X
   int ty = currentCanvasY + header_height + tools_y_offset;    //--- Set tools Y
   int tr = tx + currentWidth;                                  //--- Set right
   int tb = ty + currentHeight;                                 //--- Set bottom
   bool over_right = mx >= tr - resize_thickness && mx <= tr && my >= ty && my <= tb; //--- Check over right
   bool over_bottom = my >= tb - resize_thickness && my <= tb && mx >= tx && mx <= tr; //--- Check over bottom
   if (over_bottom && over_right) { rm = BOTTOM_RIGHT; return true; } //--- Set and return bottom-right
   if (over_bottom) { rm = BOTTOM; return true; }               //--- Set and return bottom
   if (over_right) { rm = RIGHT; return true; }                 //--- Set and return right
   return false;                                                //--- Return false otherwise
}

//+------------------------------------------------------------------+
//| Get hovered tool                                                 |
//+------------------------------------------------------------------+
int GetHoveredTool(int mx, int my) {
   int lx = mx - currentCanvasX;                                //--- Compute local X
   int ly = my - (currentCanvasY + header_height + tools_y_offset); //--- Compute local Y
   if (lx < 0 || lx > currentWidth || ly < 0 || ly > currentHeight) return -1; //--- Return -1 if outside

   int columns = 4;                                             //--- Set columns
   int padding = 5;                                             //--- Set padding
   int button_width = (currentWidth - (columns + 1) * padding) / columns; //--- Compute button width
   int button_height = 70;                                      //--- Set button height

   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      int col = i % columns;                                    //--- Compute column
      int row = i / columns;                                    //--- Compute row
      int x = padding + col * (button_width + padding);         //--- Compute X
      int y = padding + row * (button_height + padding);        //--- Compute Y
      if (lx >= x && lx < x + button_width && ly >= y && ly < y + button_height) return i; //--- Return index if over
   }
   return -1;                                                   //--- Return -1 if none
}

//+------------------------------------------------------------------+
//| Toggle tool activation                                           |
//+------------------------------------------------------------------+
void ToggleTool(TOOL_TYPE type) {
   if (active_tool == type) {                                   //--- Check if already active
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Handle crosshair deactivation
         DeleteCrosshair();                                     //--- Delete crosshair
         measuring = false;                                     //--- Reset measuring
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
      }
      active_tool = TOOL_NONE;                                  //--- Deactivate tool
      drawing_first_click = false;                              //--- Reset first click
      current_instruction = "";                                 //--- Clear instruction
   } else {                                                     //--- Handle activation
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Deactivate previous crosshair
         DeleteCrosshair();                                     //--- Delete crosshair
         measuring = false;                                     //--- Reset measuring
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
      }
      active_tool = type;                                       //--- Set active tool
      drawing_first_click = false;                              //--- Reset first click
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Handle crosshair instruction
         current_instruction = "Move mouse for crosshair. Double-click to start measuring."; //--- Set instruction
      } else {                                                  //--- Handle other tools
         current_instruction = "Click on chart to draw.";       //--- Set instruction
      }
   }
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Deactivate current tool on Escape                                |
//+------------------------------------------------------------------+
void DeactivateTool() {
   if (active_tool == TOOL_CROSSHAIR) {                         //--- Check if crosshair active
      DeleteCrosshair();                                        //--- Delete crosshair
      measuring = false;                                        //--- Reset measuring
      ChartSetInteger(0, CHART_MOUSE_SCROLL, true);             //--- Enable scroll
   }
   active_tool = TOOL_NONE;                                     //--- Deactivate tool
   drawing_first_click = false;                                 //--- Reset first click
   current_instruction = "";                                    //--- Clear instruction
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Delete measure objects                                           |
//+------------------------------------------------------------------+
void DeleteMeasureObjects() {
   ObjectDelete(local_chart_id, "Cross_HF");                    //--- Delete fixed horizontal
   ObjectDelete(local_chart_id, "Cross_VF");                    //--- Delete fixed vertical
   ObjectDelete(local_chart_id, "Measure_Line");                //--- Delete measure line
   ObjectDelete(local_chart_id, "Measure_Label");               //--- Delete measure label
}

//+------------------------------------------------------------------+
//| Delete crosshair                                                 |
//+------------------------------------------------------------------+
void DeleteCrosshair() {
   ObjectDelete(local_chart_id, "Cross_H");                     //--- Delete moving horizontal
   ObjectDelete(local_chart_id, "Cross_V");                     //--- Delete moving vertical
   DeleteMeasureObjects();                                      //--- Delete measure objects
   ChartRedraw();                                               //--- Redraw chart
}

Here, we implement the "ToggleTheme" function to switch between dark and light modes by inverting the "is_dark_theme" flag, then redrawing the header and tools panel to apply new colors, and refreshing the chart for immediate visibility. Next, we create "ToggleMinimize" to alternate the panel state with "panel_minimized", destroying the tools canvas when minimizing to save space or recreating it when restoring, followed by header redraw and chart update. To detect interactions, we define "IsMouseOverHeader," which checks if the mouse is within the header bounds but excludes the button area starting from the theme offset, returning true only for draggable regions.

Similarly, "IsMouseOverButton" verifies mouse position over a specific button by calculating its left edge from offset and comparing it to the button size and header height. For resizing, "IsMouseOverResize" evaluates if the mouse is near the bottom, right, or corner edges of the tools panel (ignoring if minimized), updating the reference mode like "BOTTOM_RIGHT" and returning true if detected. We add "GetHoveredTool" to identify the button under the mouse by converting to local coordinates, iterating a 4-column grid with padding, and returning the index if within a button's bounds, or -1 otherwise.

To manage tools, "ToggleTool" activates or deactivates based on type, handling crosshair cleanup if switching, resetting drawing flags, and setting appropriate instructions like "Click on chart to draw" before redrawing the panel. "DeactivateTool" fully resets the active tool, particularly clearing crosshair elements and enabling scroll, then updates instructions and redraws. Finally, we provide cleanup with "DeleteMeasureObjects" removing fixed crosshair and measure line/label, and "DeleteCrosshair" deleting moving crosshair plus calling the measure delete function, followed by chart redraw. We will now handle the objects' functions and their creation.

//+------------------------------------------------------------------+
//| Handle chart drawing for tools                                   |
//+------------------------------------------------------------------+
void HandleDrawing(int mx, int my) {
   datetime time;                                               //--- Declare time
   double price;                                                //--- Declare price
   int sw;                                                      //--- Declare subwindow
   if (!ChartXYToTimePrice(local_chart_id, mx, my, sw, time, price)) return; //--- Convert XY to time/price or return

   string name = "Tool_" + TimeToString(TimeCurrent(), TIME_SECONDS); //--- Set object name
   color col = clrBlue;                                         //--- Set color
   ENUM_OBJECT obj_type = OBJ_VLINE;                            //--- Initialize object type
   datetime time1 = time;                                       //--- Set time1
   double price1 = price;                                       //--- Set price1
   datetime time2 = 0;                                          //--- Set time2
   double price2 = 0.0;                                         //--- Set price2
   bool create = true;                                          //--- Set create flag
   bool valid_obj = false;                                      //--- Set valid object flag

   string tool_name = GetToolName(active_tool);                 //--- Get tool name

   if (IsSingleClickTool(active_tool)) {                        //--- Check if single-click tool
      switch (active_tool) {                                    //--- Switch on tool
         case TOOL_HLINE:                                       //--- Handle horizontal line
            obj_type = OBJ_HLINE;                               //--- Set type
            time1 = 0;                                          //--- Set time1
            price1 = price;                                     //--- Set price1
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_VLINE:                                       //--- Handle vertical line
            obj_type = OBJ_VLINE;                               //--- Set type
            time1 = time;                                       //--- Set time1
            price1 = 0;                                         //--- Set price1
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_TEXT:                                        //--- Handle text
            obj_type = OBJ_TEXT;                                //--- Set type
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_ARROW:                                       //--- Handle arrow
            obj_type = OBJ_ARROW;                               //--- Set type
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
      }
   } else {                                                     //--- Handle two-click tools
      if (!drawing_first_click) {                               //--- Check if first click
         draw_time1 = time;                                     //--- Set draw time1
         draw_price1 = price;                                   //--- Set draw price1
         drawing_first_click = true;                            //--- Set first click flag
         create = false;                                        //--- Set no create
         current_instruction = "Click second point for " + tool_name + "."; //--- Set instruction
      } else {                                                  //--- Handle second click
         ChartXYToTimePrice(local_chart_id, mx, my, sw, time2, price2); //--- Get time2/price2
         drawing_first_click = false;                           //--- Reset first click
         time1 = draw_time1;                                    //--- Set time1
         price1 = draw_price1;                                  //--- Set price1
         current_instruction = "Click on chart to draw.";       //--- Set instruction
      }
      switch (active_tool) {                                    //--- Switch on tool
         case TOOL_TRENDLINE: obj_type = OBJ_TREND; valid_obj = true; break; //--- Set trendline
         case TOOL_RECTANGLE: obj_type = OBJ_RECTANGLE; valid_obj = true; break; //--- Set rectangle
         case TOOL_FIBO: obj_type = OBJ_FIBO; valid_obj = true; break; //--- Set fibo
      }
   }

   if (create && valid_obj) {                                   //--- Check if create and valid
      if (ObjectCreate(local_chart_id, name, obj_type, sw, time1, price1, time2, price2)) { //--- Create object
         ObjectSetInteger(local_chart_id, name, OBJPROP_COLOR, col); //--- Set color
         ObjectSetInteger(local_chart_id, name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
         ObjectSetInteger(local_chart_id, name, OBJPROP_WIDTH, 1); //--- Set width
         if (obj_type == OBJ_TEXT) ObjectSetString(local_chart_id, name, OBJPROP_TEXT, "Text"); //--- Set text
         if (obj_type == OBJ_ARROW) ObjectSetInteger(local_chart_id, name, OBJPROP_ARROWCODE, 233); //--- Set arrow code
         if (obj_type == OBJ_HLINE || obj_type == OBJ_VLINE) ObjectSetInteger(local_chart_id, name, OBJPROP_STYLE, STYLE_DASH); //--- Set dash for lines
         ObjectSetInteger(local_chart_id, name, OBJPROP_SELECTABLE, true); //--- Set selectable
         ObjectSetInteger(local_chart_id, name, OBJPROP_SELECTED, true); //--- Set selected
         ChartRedraw(local_chart_id);                           //--- Redraw chart
         active_tool = TOOL_NONE;                               //--- Deactivate tool
         current_instruction = "";                              //--- Clear instruction
      }
   }
   DrawToolsPanel();                                            //--- Redraw tools panel
}

To handle chart drawing, we implement the "HandleDrawing" function to manage the creation of chart objects based on the active tool and mouse position, beginning by converting coordinates to time and price with ChartXYToTimePrice, returning early if unsuccessful. Next, we generate a unique object name using current time, set default blue color and vertical line type, then branch for single-click tools via "IsSingleClickTool", switching to configure types like OBJ_HLINE for horizontal (resetting times), OBJ_VLINE for vertical (resetting prices), OBJ_TEXT or OBJ_ARROW, marking valid and setting create flag.

For two-click tools, we capture the first point if "drawing_first_click" is false, updating globals and instruction without creation; on the second click, retrieve the end point, reset the flag, and configure types such as OBJ_TREND for trendline, OBJ_RECTANGLE, or OBJ_FIBO, validating the object. If creation is flagged and valid, we use ObjectCreate with parameters, set properties like color, style, width, text, or arrow code if applicable, dash for lines, selectable and selected states, then redraw the chart with ChartRedraw and deactivate the tool, clearing instructions. Finally, we refresh the tools panel with "DrawToolsPanel" to reflect changes. Let us now handle the crosshair; the others are straightforward.

//+------------------------------------------------------------------+
//| Update moving crosshair                                          |
//+------------------------------------------------------------------+
void UpdateMovingCrosshair(datetime time, double price) {
   string h_name = "Cross_H";                                   //--- Set horizontal name
   string v_name = "Cross_V";                                   //--- Set vertical name

   if (ObjectFind(local_chart_id, h_name) < 0) {                //--- Check if horizontal exists
      ObjectCreate(local_chart_id, h_name, OBJ_HLINE, 0, 0, price); //--- Create horizontal line
      ObjectSetInteger(local_chart_id, h_name, OBJPROP_COLOR, clrRed); //--- Set color
      ObjectSetInteger(local_chart_id, h_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, h_name, 0, 0, price);          //--- Move horizontal
   }

   if (ObjectFind(local_chart_id, v_name) < 0) {                //--- Check if vertical exists
      ObjectCreate(local_chart_id, v_name, OBJ_VLINE, 0, time, 0); //--- Create vertical line
      ObjectSetInteger(local_chart_id, v_name, OBJPROP_COLOR, clrRed); //--- Set color
      ObjectSetInteger(local_chart_id, v_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, v_name, 0, time, 0);           //--- Move vertical
   }
}

//+------------------------------------------------------------------+
//| Update fixed crosshair                                           |
//+------------------------------------------------------------------+
void UpdateFixedCrosshair() {
   string hf_name = "Cross_HF";                                 //--- Set fixed horizontal name
   string vf_name = "Cross_VF";                                 //--- Set fixed vertical name

   if (ObjectFind(local_chart_id, hf_name) < 0) {               //--- Check if fixed horizontal exists
      ObjectCreate(local_chart_id, hf_name, OBJ_HLINE, 0, 0, fixed_price); //--- Create fixed horizontal
      ObjectSetInteger(local_chart_id, hf_name, OBJPROP_COLOR, clrGreen); //--- Set color
      ObjectSetInteger(local_chart_id, hf_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, hf_name, 0, 0, fixed_price);   //--- Move fixed horizontal
   }

   if (ObjectFind(local_chart_id, vf_name) < 0) {               //--- Check if fixed vertical exists
      ObjectCreate(local_chart_id, vf_name, OBJ_VLINE, 0, fixed_time, 0); //--- Create fixed vertical
      ObjectSetInteger(local_chart_id, vf_name, OBJPROP_COLOR, clrGreen); //--- Set color
      ObjectSetInteger(local_chart_id, vf_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, vf_name, 0, fixed_time, 0);    //--- Move fixed vertical
   }
}

We implement the "UpdateMovingCrosshair" function to manage a dynamic crosshair on the chart, defining names for horizontal "Cross_H" and vertical "Cross_V" lines, checking if each exists with ObjectFind (negative return if not), creating them via "ObjectCreate" as OBJ_HLINE or "OBJ_VLINE" with red solid style if absent, or moving them with ObjectMove to the current time and price if present, providing real-time cursor tracking. Next, we create the "UpdateFixedCrosshair" function for a static reference crosshair, using names "Cross_HF" and "Cross_VF", similarly checking existence, creating with green solid style at fixed_time and fixed_price, or updating positions, enabling persistent markers during measuring mode for comparisons. Now, we will handle its click.

//+------------------------------------------------------------------+
//| Handle crosshair click                                           |
//+------------------------------------------------------------------+
void HandleCrosshairClick(datetime time, double price) {
   ulong now = GetMicrosecondCount();                           //--- Get current microseconds
   if (now - last_click_time < 500000) {                        //--- Check for double-click
      if (!measuring) {                                         //--- Check not measuring
         fixed_time = time;                                     //--- Set fixed time
         fixed_price = price;                                   //--- Set fixed price
         measuring = true;                                      //--- Set measuring flag
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);         //--- Disable scroll
         current_instruction = "Measuring. Double-click to exit."; //--- Set instruction
      } else {                                                  //--- Handle exit measuring
         measuring = false;                                     //--- Reset measuring
         DeleteMeasureObjects();                                //--- Delete objects
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
         current_instruction = "Move mouse for crosshair. Double-click to start measuring."; //--- Set instruction
      }
      DrawToolsPanel();                                         //--- Redraw tools panel
      last_click_time = 0;                                      //--- Reset click time
   } else {                                                     //--- Handle single click
      last_click_time = now;                                    //--- Update click time
   }
}

We implement the "HandleCrosshairClick" function to process clicks during crosshair mode, retrieving current microseconds with GetMicrosecondCount to detect double-clicks within 500 ms of "last_click_time". If double-click, we toggle measuring: if not active, set fixed point to current time and price, enable measuring, disable chart scroll with ChartSetInteger, update instruction; if active, reset measuring, delete objects via "DeleteMeasureObjects", re-enable scroll, and restore default instruction, then redraw the tools panel and reset click time. For single clicks, we update "last_click_time" to now, allowing detection of subsequent clicks as doubles. Let us now define a logic to update the measuring line between the crosshairs.

//+------------------------------------------------------------------+
//| Update measure line                                              |
//+------------------------------------------------------------------+
void UpdateMeasureLine(datetime time, double price) {
   string line_name = "Measure_Line";                           //--- Set measure line name
   if (ObjectFind(local_chart_id, line_name) < 0) {             //--- Check if line exists
      ObjectCreate(local_chart_id, line_name, OBJ_TREND, 0, fixed_time, fixed_price, time, price); //--- Create trend line
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_COLOR, clrBlue); //--- Set color
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_STYLE, STYLE_DASHDOT); //--- Set style
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_WIDTH, 1); //--- Set width
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_RAY, false); //--- Disable ray
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, line_name, 0, fixed_time, fixed_price); //--- Move point 0
      ObjectMove(local_chart_id, line_name, 1, time, price);    //--- Move point 1
   }
}

//+------------------------------------------------------------------+
//| Update measure label                                             |
//+------------------------------------------------------------------+
void UpdateMeasureLabel(int mx, int my, datetime time, double price) {
   string label_name = "Measure_Label";                         //--- Set measure label name
   long period_sec = PeriodSeconds(_Period);                    //--- Get period seconds
   long virtual_fixed = fixed_time / period_sec;                //--- Compute virtual fixed
   long virtual_time = time / period_sec;                       //--- Compute virtual time
   int bars_diff = (int)MathAbs(virtual_fixed - virtual_time);  //--- Compute bars difference
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);      //--- Get point value
   long digits = SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);     //--- Get digits
   double pip_size = (digits == 3 || digits == 5) ? point * 10 : point; //--- Compute pip size
   double pips = MathAbs(price - fixed_price) / pip_size;       //--- Compute pips
   string price_diff = DoubleToString(MathAbs(price - fixed_price), (int)digits); //--- Format price diff
   string text = StringFormat("%d bars, %.1f pips, Price Diff: %s", bars_diff, pips, price_diff); //--- Format text

   if (ObjectFind(local_chart_id, label_name) < 0) {            //--- Check if label exists
      ObjectCreate(local_chart_id, label_name, OBJ_LABEL, 0, 0, 0); //--- Create label
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_XDISTANCE, mx + 20); //--- Set X distance
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_YDISTANCE, my); //--- Set Y distance
      ObjectSetString(local_chart_id, label_name, OBJPROP_TEXT, text); //--- Set text
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_FONTSIZE, 9); //--- Set font size
      ObjectSetString(local_chart_id, label_name, OBJPROP_FONT, "Arial"); //--- Set font
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_COLOR, clrBlue); //--- Set color
   } else {                                                     //--- Handle existing
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_XDISTANCE, mx + 20); //--- Update X distance
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_YDISTANCE, my); //--- Update Y distance
      ObjectSetString(local_chart_id, label_name, OBJPROP_TEXT, text); //--- Update text
   }
}

For the measuring line, we implement the "UpdateMeasureLine" function to display a connecting line during measuring mode, defining a name "Measure_Line", checking existence with ObjectFind, creating it as OBJ_TREND from fixed to current time/price with blue dash-dot style, width 1, and no ray if absent, or moving its points with ObjectMove if present, providing visual linkage between points. We create the "UpdateMeasureLabel" function to show dynamic metrics near the mouse, calculating bars difference by dividing times by period seconds from PeriodSeconds to get virtual bars and taking absolute with MathAbs, then pips using symbol point from SymbolInfoDouble adjusted for 3 or 5 digits (multiplying by 10), absolute price diff formatted to digits from the SymbolInfoInteger function.

To format the text with StringFormat including bars, pips, rounded to 1 decimal, and price diff, we check label existence, create as OBJ_LABEL at the upper-left corner with mouse-offset position, "Arial" font size 9, blue color if new, or update position and text if existing, enhancing user feedback on measurements. This label computation is crucial as it accurately quantifies time and price spans across varying symbol precisions and periods, aiding precise analysis without manual calculations. To bring the palette to life, we will handle chart events in the event handler for intuitive interactions.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if (id == CHARTEVENT_KEYDOWN) {                              //--- Check key down event
      if (lparam == 27) {                                       //--- Check escape key
         DeactivateTool();                                      //--- Deactivate tool
      }
      return;                                                   //--- Return
   }
   if (id != CHARTEVENT_MOUSE_MOVE) return;                     //--- Return if not mouse move
   int mx = (int)lparam;                                        //--- Set mouse X
   int my = (int)dparam;                                        //--- Set mouse Y
   int ms = (int)sparam;                                        //--- Set mouse state

   bool prev_hh = header_hovered;                               //--- Store previous header hover
   bool prev_th = theme_hovered;                                //--- Store previous theme hover
   bool prev_mh = minimize_hovered;                             //--- Store previous minimize hover
   bool prev_ch = close_hovered;                                //--- Store previous close hover
   bool prev_rh = resize_hovered;                               //--- Store previous resize hover

   header_hovered = IsMouseOverHeader(mx, my);                  //--- Update header hover
   theme_hovered = IsMouseOverButton(mx, my, theme_x_offset);   //--- Update theme hover
   minimize_hovered = IsMouseOverButton(mx, my, minimize_x_offset); //--- Update minimize hover
   close_hovered = IsMouseOverButton(mx, my, close_x_offset);   //--- Update close hover
   resize_hovered = IsMouseOverResize(mx, my, hover_mode);      //--- Update resize hover

   string resize_tooltip = "";                                  //--- Initialize resize tooltip
   if (resize_hovered) {                                        //--- Check resize hovered
      switch (hover_mode) {                                     //--- Switch on hover mode
         case BOTTOM: resize_tooltip = "Resize Bottom"; break;  //--- Set bottom tooltip
         case RIGHT: resize_tooltip = "Resize Right"; break;    //--- Set right tooltip
         case BOTTOM_RIGHT: resize_tooltip = "Resize Bottom-Right"; break; //--- Set bottom-right tooltip
      }
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, resize_tooltip); //--- Set tooltip
   } else if (!panel_minimized && hovered_tool_index >= 0) {    //--- Check tool hovered
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, buttons[hovered_tool_index].tooltip); //--- Set tool tooltip
   } else {                                                     //--- Handle no hover
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, ""); //--- Clear tooltip
   }

   if (prev_hh != header_hovered || prev_th != theme_hovered || prev_mh != minimize_hovered || prev_ch != close_hovered || prev_rh != resize_hovered) { //--- Check if hovers changed
      DrawHeader();                                             //--- Redraw header
      if (!panel_minimized) DrawToolsPanel();                   //--- Redraw tools if not minimized
      ChartRedraw();                                            //--- Redraw chart
   }

   if (!panel_minimized) {                                      //--- Check not minimized
      int ht = GetHoveredTool(mx, my);                          //--- Get hovered tool
      if (ht != hovered_tool_index) {                           //--- Check if changed
         hovered_tool_index = ht;                               //--- Update index
         DrawToolsPanel();                                      //--- Redraw tools
         ChartRedraw();                                         //--- Redraw chart
      }
   }

   if (resize_hovered || resizing) {                            //--- Check resize hovered or resizing
      hover_mouse_local_x = mx - currentCanvasX;                //--- Update local X
      hover_mouse_local_y = my - (currentCanvasY + header_height + tools_y_offset); //--- Update local Y
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   if (ms == 1 && prev_mouse_state == 0) {                      //--- Check mouse down
      if (header_hovered) {                                     //--- Check header hovered
         panel_dragging = true;                                 //--- Set dragging flag
         panel_drag_x = mx;                                     //--- Set drag X
         panel_drag_y = my;                                     //--- Set drag Y
         panel_start_x = currentCanvasX;                        //--- Set start X
         panel_start_y = currentCanvasY;                        //--- Set start Y
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);         //--- Disable scroll
         DrawHeader();                                          //--- Redraw header
         ChartRedraw();                                         //--- Redraw chart
      } else if (theme_hovered) {                               //--- Check theme hovered
         ToggleTheme();                                         //--- Toggle theme
      } else if (minimize_hovered) {                            //--- Check minimize hovered
         ToggleMinimize();                                      //--- Toggle minimize
      } else if (close_hovered) {                               //--- Check close hovered
         OnDeinit(0);                                           //--- Call deinit
      } else if (!panel_minimized) {                            //--- Check not minimized
         int ht = GetHoveredTool(mx, my);                       //--- Get hovered tool
         if (ht >= 0) {                                         //--- Check valid tool
            ToggleTool(buttons[ht].type);                       //--- Toggle tool
         } else if (active_tool != TOOL_NONE && active_tool != TOOL_CROSSHAIR) { //--- Check active tool
            HandleDrawing(mx, my);                              //--- Handle drawing
         } else if (IsMouseOverResize(mx, my, resize_mode)) {   //--- Check resize area
            resizing = true;                                    //--- Set resizing flag
            resize_start_x = mx;                                //--- Set start X
            resize_start_y = my;                                //--- Set start Y
            start_width = currentWidth;                         //--- Set start width
            start_height = currentHeight;                       //--- Set start height
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);      //--- Disable scroll
            DrawToolsPanel();                                   //--- Redraw tools
            ChartRedraw();                                      //--- Redraw chart
         }
      }
   }

   if (panel_dragging && ms == 1) {                             //--- Check dragging
      int dx = mx - panel_drag_x;                               //--- Compute delta X
      int dy = my - panel_drag_y;                               //--- Compute delta Y
      currentCanvasX = MathMax(0, MathMin((int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - currentWidth, panel_start_x + dx)); //--- Update X
      currentCanvasY = MathMax(0, MathMin((int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) - header_height - (panel_minimized ? 0 : currentHeight) - tools_y_offset, panel_start_y + dy)); //--- Update Y
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_XDISTANCE, currentCanvasX); //--- Set header X
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_YDISTANCE, currentCanvasY); //--- Set header Y
      if (!panel_minimized) {                                   //--- Check not minimized
         ObjectSetInteger(0, canvasToolsName, OBJPROP_XDISTANCE, currentCanvasX); //--- Set tools X
         ObjectSetInteger(0, canvasToolsName, OBJPROP_YDISTANCE, currentCanvasY + header_height + tools_y_offset); //--- Set tools Y
      }
      ChartRedraw();                                            //--- Redraw chart
   }

   if (resizing && ms == 1) {                                   //--- Check resizing
      int dx = mx - resize_start_x;                             //--- Compute delta X
      int dy = my - resize_start_y;                             //--- Compute delta Y
      int nw = MathMax(min_width, start_width + (resize_mode == RIGHT || resize_mode == BOTTOM_RIGHT ? dx : 0)); //--- Compute new width
      int nh = MathMax(min_height, start_height + (resize_mode == BOTTOM || resize_mode == BOTTOM_RIGHT ? dy : 0)); //--- Compute new height
      nw = MathMin(nw, (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - currentCanvasX); //--- Clamp width
      nh = MathMin(nh, (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) - currentCanvasY - header_height - tools_y_offset); //--- Clamp height
      currentWidth = nw;                                        //--- Update width
      currentHeight = nh;                                       //--- Update height
      canvasTools.Resize(currentWidth, currentHeight);          //--- Resize tools canvas
      ObjectSetInteger(0, canvasToolsName, OBJPROP_XSIZE, currentWidth); //--- Set tools X size
      ObjectSetInteger(0, canvasToolsName, OBJPROP_YSIZE, currentHeight); //--- Set tools Y size
      canvasHeader.Resize(currentWidth, header_height);         //--- Resize header canvas
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_XSIZE, currentWidth); //--- Set header X size
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_YSIZE, header_height); //--- Set header Y size
      DrawHeader();                                             //--- Redraw header
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   if (ms == 0 && prev_mouse_state == 1) {                      //--- Check mouse up
      panel_dragging = false;                                   //--- Reset dragging
      resizing = false;                                         //--- Reset resizing
      ChartSetInteger(0, CHART_MOUSE_SCROLL, true);             //--- Enable scroll
      DrawHeader();                                             //--- Redraw header
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   datetime time = 0;                                           //--- Initialize time
   double price = 0.0;                                          //--- Initialize price
   int sw = 0;                                                  //--- Initialize subwindow
   if (ChartXYToTimePrice(local_chart_id, mx, my, sw, time, price)) { //--- Convert XY to time/price
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Check crosshair active
         UpdateMovingCrosshair(time, price);                    //--- Update moving crosshair
         if (measuring) {                                       //--- Check measuring
            UpdateFixedCrosshair();                             //--- Update fixed crosshair
            UpdateMeasureLine(time, price);                     //--- Update measure line
            UpdateMeasureLabel(mx, my, time, price);            //--- Update measure label
         }
         ChartRedraw();                                         //--- Redraw chart
      }
      if (ms == 1 && prev_mouse_state == 0 && active_tool == TOOL_CROSSHAIR && 
          !header_hovered && !theme_hovered && !minimize_hovered && !close_hovered && !resize_hovered && 
          (panel_minimized || GetHoveredTool(mx, my) == -1)) {   //--- Check crosshair click conditions
         HandleCrosshairClick(time, price);                      //--- Handle click
         ChartRedraw();                                          //--- Redraw chart
      }
   }

   prev_mouse_state = ms;                                       //--- Update previous state
}

We use the OnChartEvent event handler to manage all user interactions with the palette and tools, starting by checking for CHARTEVENT_KEYDOWN to detect the escape key (lparam 27) and deactivate the current tool with "DeactivateTool", then returning early. In case you are wondering, the escape character is code 27 in the ASCII table. See below.

ASCII CHARACTER 27 - ESCAPE

Next, if not a CHARTEVENT_MOUSE_MOVE, we exit; otherwise, extract mouse X, Y, and state from parameters, store previous hover flags, and update them using functions like "IsMouseOverHeader", "IsMouseOverButton" for theme/minimize/close, and "IsMouseOverResize", which sets "hover_mode". Next, we set tooltips on the tools canvas with ObjectSetString for resize modes or hovered tool buttons, clearing if none; if any hover changed, redraw header and tools (if not minimized), followed by the ChartRedraw function. If not minimized, we detect hovered tool index with "GetHoveredTool" and redraw if changed; for resize states, update local hover coordinates and redraw tools.

On mouse down (ms 1, prev 0), we initiate dragging if header hovered by setting flags, disabling scroll, and redrawing header; toggle theme or minimize if respective buttons hovered; call deinit if close; or if a tool button hovered, toggle its type; handle non-crosshair drawing with "HandleDrawing"; start resizing if over resize area by setting flags, starts, and disabling scroll before redrawing tools. During drag (ms 1), we calculate deltas, clamp new positions to chart bounds with the MathMax and MathMin functions, update globals, and set object distances for header and tools (if visible), then redraw the chart. For resizing (ms 1), compute and clamp new dimensions based on mode, update globals, resize canvases with Resize and set sizes via ObjectSetInteger, then redraw header, tools, and chart.

On mouse up (ms 0, prev 1), reset dragging and resizing flags, re-enable scroll, redraw header and tools, and refresh chart. We convert mouse to time/price with ChartXYToTimePrice, and if crosshair is active, update moving crosshair; if measuring, also update fixed, measure line, and label; on qualifying down click outside palette, handle with "HandleCrosshairClick" and redraw. Finally, update "prev_mouse_state" to the current for next events, ensuring responsive, stateful interactivity. Finally, we need to delete our objects when not needed.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   canvasHeader.Destroy();                                      //--- Destroy header canvas
   canvasTools.Destroy();                                       //--- Destroy tools canvas
   DeleteCrosshair();                                           //--- Delete crosshair
   measuring = false;                                           //--- Reset measuring flag
   ChartSetInteger(0, CHART_MOUSE_SCROLL, true);                //--- Enable mouse scroll

   int total = ObjectsTotal(local_chart_id);                    //--- Get total objects
   for (int i = total - 1; i >= 0; i--) {                       //--- Loop over objects backward
      string obj_name = ObjectName(local_chart_id, i);          //--- Get object name
      if (StringFind(obj_name, "Tool_") == 0) {                 //--- Check if our tool
         ObjectDelete(local_chart_id, obj_name);                //--- Delete object
      }
   }

   ChartRedraw();                                               //--- Redraw chart
}

In the OnDeinit event handler, we perform cleanup by destroying the header and tools canvases with the Destroy method, removing any active crosshair through "DeleteCrosshair", resetting the measuring flag to false, and re-enabling mouse scroll via the ChartSetInteger function. To ensure no leftover objects, we retrieve the total count with ObjectsTotal on the local chart, loop backward for safe deletion, fetch names using ObjectName, and remove those prefixed "Tool_" with the ObjectDelete function. Finally, we invoke ChartRedraw to update the chart, completing resource release upon program exit. Upon compilation, we get the following outcome.

CROSSHAIR IN ACTION

From the visualization, we can see that we have created an interactive tools palette, hence achieving our objectives. What now remains is testing the workability of the system, and that is handled in the preceding section.


Backtesting

We did the testing, and below is the compiled visualization in a single Graphics Interchange Format (GIF) bitmap image format.

TOOLS PALETTE GIF


Conclusion

In conclusion, we’ve built an interactive tools palette in MQL5 for chart drawing, featuring draggable and resizable panels with theme switching between dark and light modes. We incorporated buttons for various tools like crosshairs, trendlines, lines, rectangles, Fibonacci retracements, text, and arrows, managing mouse events for seamless activation and on-chart interactions. This customizable system enhances trading analysis with real-time feedback and instructions, providing a versatile UI for efficient workflows. With this interactive tools palette, you’re equipped to streamline chart annotations and measurements, ready for integration into advanced trading strategies. In the preceding parts, we will upscale to a more detailed palette with more organized tools. Keep tuned!

Attached files |
Tools_Palette.mq5 (62.96 KB)
Creating Custom Indicators in MQL5 (Part 7): Hybrid Time Price Opportunity (TPO) Market Profiles for Session Analysis Creating Custom Indicators in MQL5 (Part 7): Hybrid Time Price Opportunity (TPO) Market Profiles for Session Analysis
In this article, we develop a custom indicator in MQL5 for hybrid Time Price Opportunity (TPO) market profiles, supporting multiple session timeframes such as intraday, daily, weekly, monthly, and fixed periods with timezone adjustments. The indicator quantizes prices into a grid, tracks session data including highs, lows, opens, and closes, and calculates key elements like the point of control and value area based on TPO counts. It renders profiles visually on the chart with customizable colors for TPO letters, single prints, value areas, POC, and close markers, enabling detailed session analysis
Market Simulation (Part 15): Sockets (IX) Market Simulation (Part 15): Sockets (IX)
In this article, we will discuss one of the possible solutions to what we have been trying to demonstrate—namely, how to allow an Excel user to perform an action in MetaTrader 5 without sending orders or opening or closing positions. The idea is that the user employs Excel to conduct fundamental analysis of a particular symbol. And by using only Excel, they can instruct an expert advisor running in MetaTrader 5 to open or close a specific position.
Market Simulation (Part 16): Sockets (X) Market Simulation (Part 16): Sockets (X)
We are close to completing this challenge. However, before we begin, I want you to try to understand these two articles—this one and the previous one. That way, you will truly understand the next article, in which I will cover exclusively the part related to MQL5 programming. But I will also try to make it understandable. If you do not understand these last two articles, it will be difficult for you to understand the next one, because the material accumulates. The more things there are to do, the more you need to create and understand in order to achieve the goal.
Introduction to MQL5 (Part 41): Beginner Guide to File Handling in MQL5 (III) Introduction to MQL5 (Part 41): Beginner Guide to File Handling in MQL5 (III)
Learn how to read a CSV file in MQL5 and organize its trading data into dynamic arrays. This article shows step by step how to count file elements, store all data in a single array, and separate each column into dedicated arrays, laying the foundation for advanced analysis and trading performance visualization.