| //+------------------------------------------------------------------+
|
| //| 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
|
| //+------------------------------------------------------------------+
|