86 lines
3.7 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| 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
| |||
//+------------------------------------------------------------------+
|