//+------------------------------------------------------------------+ //| CkTheme.mqh | //| Copyright 2026, Allan NiƱal | //| https://www.allanninal.dev/mql5 | //+------------------------------------------------------------------+ //| Chart kit colour tokens. Semantic names, never literals in the | //| products: pos/neg (profit/loss, buy/sell), acc (entry, neutral), | //| warn, ink/label/dim (text), zone fills pre-blended with the | //| chart background (a fill never hides candles and never relies on | //| object alpha). Theme AUTO reads the chart background. | //| Colour-blind mode swaps green/red for blue/orange (Okabe-Ito); | //| shapes and text carry the meaning too, never colour alone. | //| Values are checked by tests/test_tokens.py (contrast >= 3:1). | //+------------------------------------------------------------------+ #ifndef CK_THEME_MQH #define CK_THEME_MQH enum ENUM_CK_THEME { CK_THEME_AUTO=0, // Auto (from the chart background) CK_THEME_DARK=1, // Dark CK_THEME_LIGHT=2 // Light }; struct CkTheme { bool light,cb; color bg; // the chart background the fills are blended with color ink,label,dim; color pos,neg,warn,acc; color zone_pos,zone_neg; // risk box fills }; #define CK_ZONE_DARK 0.22 // fill strength over a dark chart #define CK_ZONE_LIGHT 0.16 // over a light chart color CkRGB(const int r,const int g,const int b) { return((color)((r&255)|((g&255)<<8)|((b&255)<<16))); } int CkR(const color c) { return((int)(c&255)); } int CkG(const color c) { return((int)((c>>8)&255)); } int CkB(const color c) { return((int)((c>>16)&255)); } //--- WCAG relative luminance, 0 (black) .. 1 (white) double CkLin(const int v) { double s=v/255.0; return(s<=0.04045 ? s/12.92 : MathPow((s+0.055)/1.055,2.4)); } double CkLuma(const color c) { return(0.2126*CkLin(CkR(c))+0.7152*CkLin(CkG(c))+0.0722*CkLin(CkB(c))); } //--- `fg` laid over `bg` at strength `a` (0..1), as an opaque colour color CkBlend(const color fg,const color bg,const double a) { return(CkRGB((int)MathRound(CkR(bg)+(CkR(fg)-CkR(bg))*a),(int)MathRound(CkG(bg)+(CkG(fg)-CkG(bg))*a), (int)MathRound(CkB(bg)+(CkB(fg)-CkB(bg))*a))); } //--- fill the tokens for `mode`; `cb` = colour-blind friendly; `chart_bg` < 0 reads the chart's own background void CkThemeLoad(CkTheme &t,const ENUM_CK_THEME mode,const bool cb,const long chart_bg=-1) { color bg=(color)(chart_bg>=0 ? chart_bg : ChartGetInteger(0,CHART_COLOR_BACKGROUND)); t.bg=bg; t.cb=cb; t.light=(mode==CK_THEME_LIGHT || (mode==CK_THEME_AUTO && CkLuma(bg)>0.4)); if(t.light) { t.ink=CkRGB(30,37,40); t.label=CkRGB(95,107,113); t.dim=CkRGB(154,164,169); t.pos=cb ? CkRGB(0,114,178) : CkRGB(46,134,87); t.neg=cb ? CkRGB(213,94,0) : CkRGB(193,73,62); t.warn=cb ? CkRGB(150,120,0) : CkRGB(168,118,26); t.acc=CkRGB(47,111,166); if(cb) t.acc=CkRGB(90,90,90); // keep the entry apart from the blue of "buy/profit" } else { t.ink=CkRGB(215,222,226); t.label=CkRGB(139,151,158); t.dim=CkRGB(91,102,108); t.pos=cb ? CkRGB(86,180,233) : CkRGB(98,184,140); t.neg=cb ? CkRGB(230,159,0) : CkRGB(224,123,112); t.warn=cb ? CkRGB(240,228,66): CkRGB(217,166,72); t.acc=CkRGB(115,169,214); if(cb) t.acc=CkRGB(200,200,200); } double a=t.light ? CK_ZONE_LIGHT : CK_ZONE_DARK; t.zone_pos=CkBlend(t.pos,bg,a); t.zone_neg=CkBlend(t.neg,bg,a); } #endif //+------------------------------------------------------------------+