//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2026, Allan NiƱal | //| https://www.allanninal.dev/mql5 | //+------------------------------------------------------------------+ //| Chart panel drawing: cached labels and boxes (unchanged text is | //| never resent), sections, "label value" rows and buttons. Light | //| and dark themes. | //+------------------------------------------------------------------+ // SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE) #ifndef RTM_PANEL_MQH #define RTM_PANEL_MQH enum ENUM_PRG_CORNER { PRG_LEFT=0, // Top left PRG_RIGHT=1 // Top right }; #define PRG_PREFIX "RTM_" // chart object prefix (not "PRG_": Prop Rules Guard may share the chart) #define UI_W 318 // panel width #define UI_PAD 10 // inner padding #define UI_VAL 112 // value column #define UI_ROW 16 // row height #define UI_HEAD 24 // title bar height #define UI_GAP 6 // space above a section title #define UI_CHAR 7 // Consolas 9 advance width, px ENUM_PRG_CORNER g_ui_corner=PRG_LEFT; int g_ui_x=10,g_ui_y=26,g_ui_font=9; bool g_ui_light=false; bool g_ui_cb=false; // colour-blind friendly: blue/orange instead of green/red (Chart Kit tokens) bool g_panel_dirty=false; color UiBack() { return(g_ui_light ? C'250,250,251' : C'24,26,30'); } color UiText() { return(g_ui_light ? C'44,48,56' : C'206,212,220'); } color UiLabel() { return(g_ui_light ? C'124,131,142' : C'122,130,142'); } color UiHead() { return(g_ui_light ? C'92,100,112' : C'150,160,175'); } color UiDim() { return(g_ui_light ? C'140,146,157' : C'96,102,112'); } color UiPos() { return(g_ui_cb ? (g_ui_light ? C'0,114,178' : C'86,180,233') : (g_ui_light ? C'38,118,66' : C'126,192,143')); } color UiNeg() { return(g_ui_cb ? (g_ui_light ? C'213,94,0' : C'230,159,0') : (g_ui_light ? C'176,58,58' : C'226,124,124')); } color UiWarn() { return(g_ui_cb ? (g_ui_light ? C'150,120,0' : C'240,228,66') : (g_ui_light ? C'160,112,28' : C'220,178,116')); } color UiAccent() { return(g_ui_light ? C'48,92,148' : C'132,164,206'); } color UiRule() { return(g_ui_light ? C'220,223,228' : C'48,52,58'); } color UiTrack() { return(g_ui_light ? C'232,234,238' : C'40,43,49'); } //--- per-object cache of what was last sent string g_cache_name[]; string g_cache_txt[]; color g_cache_clr[],g_cache_fill[]; int g_cache_x[],g_cache_y[],g_cache_w[],g_cache_h[]; int g_cursor=0; int CacheSlot(const string name) { int n=ArraySize(g_cache_name); if(g_cursor0 ? size : g_ui_font); } SetInt(name,OBJPROP_XDISTANCE,PanelX(x),g_cache_x[slot]); SetInt(name,OBJPROP_YDISTANCE,g_ui_y+y,g_cache_y[slot]); if(g_cache_txt[slot]!=txt) { ObjectSetString(0,name,OBJPROP_TEXT,txt); g_cache_txt[slot]=txt; g_panel_dirty=true; } if(g_cache_clr[slot]!=clr) { ObjectSetInteger(0,name,OBJPROP_COLOR,clr); g_cache_clr[slot]=clr; g_panel_dirty=true; } } void Box(const string id,const int x,const int y,const int w,const int h,const color fill,const color border) { string name=PRG_PREFIX+id; int slot=CacheSlot(name); if(g_cache_x[slot]==INT_MIN && ObjectFind(0,name)<0) { ObjectCreate(0,name,OBJ_RECTANGLE_LABEL,0,0,0); ObjectSetInteger(0,name,OBJPROP_CORNER,PanelCorner()); ObjectSetInteger(0,name,OBJPROP_BORDER_TYPE,BORDER_FLAT); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_BACK,false); } //--- a rectangle label is anchored at its top-left corner, so on the right corner shift it by its width SetInt(name,OBJPROP_XDISTANCE,g_ui_corner==PRG_LEFT ? g_ui_x+x : g_ui_x+UI_W-x,g_cache_x[slot]); SetInt(name,OBJPROP_YDISTANCE,g_ui_y+y,g_cache_y[slot]); SetInt(name,OBJPROP_XSIZE,MathMax(w,1),g_cache_w[slot]); SetInt(name,OBJPROP_YSIZE,MathMax(h,1),g_cache_h[slot]); if(g_cache_fill[slot]!=fill) { ObjectSetInteger(0,name,OBJPROP_BGCOLOR,fill); g_cache_fill[slot]=fill; g_panel_dirty=true; } if(g_cache_clr[slot]!=border) { ObjectSetInteger(0,name,OBJPROP_COLOR,border); g_cache_clr[slot]=border; g_panel_dirty=true; } } void Button(const string id,const int x,const int y,const int w,const string txt,const color fg=clrNONE) { string name=PRG_PREFIX+id; if(ObjectFind(0,name)<0) { ObjectCreate(0,name,OBJ_BUTTON,0,0,0); ObjectSetInteger(0,name,OBJPROP_CORNER,PanelCorner()); ObjectSetInteger(0,name,OBJPROP_YSIZE,18); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetString(0,name,OBJPROP_FONT,"Consolas"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,g_ui_font); } ObjectSetInteger(0,name,OBJPROP_XSIZE,w); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,g_ui_corner==PRG_LEFT ? g_ui_x+x : g_ui_x+UI_W-x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,g_ui_y+y); ObjectSetInteger(0,name,OBJPROP_BGCOLOR,UiBack()); ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,fg==clrNONE ? UiRule() : fg); ObjectSetInteger(0,name,OBJPROP_COLOR,fg==clrNONE ? UiHead() : fg); if(ObjectGetString(0,name,OBJPROP_TEXT)!=txt) ObjectSetString(0,name,OBJPROP_TEXT,txt); ObjectSetInteger(0,name,OBJPROP_STATE,false); } //--- one "label value" row; returns the next y int Row(const string id,const int y,const string label,const string value,const color clr) { int fit=(UI_W-UI_VAL-UI_PAD)/UI_CHAR; Text(id+"_l",UI_PAD,y,label,UiLabel()); Text(id+"_v",UI_VAL,y,StringLen(value)>fit ? StringSubstr(value,0,fit-1)+"~" : value,clr); return(y+UI_ROW); } int Section(const string id,const int y,const string title) { int top=y+UI_GAP; Box(id+"_r",UI_PAD,top+UI_ROW-3,UI_W-2*UI_PAD,1,UiRule(),UiRule()); Text(id+"_t",UI_PAD,top,title,UiHead(),g_ui_font-1); return(top+UI_ROW+2); } void PanelDelete(void) { ObjectsDeleteAll(0,PRG_PREFIX); CacheClear(); } //+------------------------------------------------------------------+ //| Formatting | //+------------------------------------------------------------------+ string Thousands(const double v,const int digits=2) { string s=DoubleToString(MathAbs(v),digits); int dot=StringFind(s,"."); string ip=dot>=0 ? StringSubstr(s,0,dot) : s; string fp=dot>=0 ? StringSubstr(s,dot) : ""; string out=""; int n=StringLen(ip); for(int i=0; i0 && (n-i)%3==0) out+=","; out+=StringSubstr(ip,i,1); } return((v<0 ? "-" : "")+out+fp); } string Money(const double v) { return(Thousands(v)); } string SMoney(const double v) { return((v>0 ? "+" : "")+Thousands(v)); } color Sign(const double v) { return(v>0 ? UiPos() : (v<0 ? UiNeg() : UiDim())); } string Pct(const double v,const int d=1) { return(DoubleToString(v,d)+"%"); } #endif //+------------------------------------------------------------------+