risk-position-tool/mql5/Include/ChartKit/CkTheme.mqh
Allan Niñal Allan Niñal 0789ebf50d Free source regenerated: pop-up and push on by default
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:29:00 +08:00
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
//+------------------------------------------------------------------+