namespace Upp;
namespace Upp;

enum_property Frame {
	"NullFrame()",
	"FieldFrame()",
	"InsetFrame()",
	"OutsetFrame()",
	"ButtonFrame()",
	"ThinInsetFrame()",
	"ThinOutsetFrame()",
	"BlackFrame()",
	"TopSeparatorFrame()",
	"BottomSeparatorFrame()",
	"LeftSeparatorFrame()",
	"RightSeparatorFrame()",
	"default"
};

enum_property Align {
	"ALIGN_LEFT",
	"ALIGN_CENTER",
	"ALIGN_RIGHT"
};

fn IsUHD()
{
	return GetTextSize("X", StdFont()).cy > 24;
}

fn DPI(n)
{
	return IsUHD() ? 2 * n : n;
}

fn DeflateRect(&r)
{
	r.top++;
	r.left++;
	r.right--;
	r.bottom--;
}

fn DrawFrame(w, &r, c1, c2) {
	w.DrawRect(r.left, r.top, r.right - r.left, 1, c1);
	w.DrawRect(r.left, r.top, 1, r.bottom - r.top, c1);
	w.DrawRect(r.right - 1, r.top, 1, r.bottom - r.top, c2);
	w.DrawRect(r.left, r.bottom - 1, r.right - r.left, 1, c2);
	DeflateRect(r);
}

fn DrawBorder(w, &r, ...)
{
	for(i = 0; i < count(argv); i += 2)
		DrawFrame(w, r, argv[i], argv[i + 1]);
}

fn DrawButtonFrame(w, &r)
{
	if(IsUHD())
		DrawBorder(w, r, :Gray, :Gray, :Gray, :Gray);
	else
		DrawBorder(w, r, :Gray, :Gray);
}

fn DrawEdgeButtonFrame(w, &r)
{
	DrawBorder(w, r, :SWhite, :SWhite, :Gray, :Gray);
}

fn DrawButton(w, r)
{
	w.DrawRect(r, :SWhiteGray);
	DrawButtonFrame(w, r);
}

fn DrawEdgeButton(w, &r)
{
	DrawEdgeButtonFrame(w, r);
	w.DrawRect(r, :SLtGray);
}

fn DrawInsetFrame(w, &r)
{
	DrawButtonFrame(w, r);
}

fn DrawCtrlFrame(w, &r, frame)
{
	width = r.right - r.left;
	height = r.bottom - r.top;
	switch(frame) {
	case "FieldFrame()":
	case "default": IsUHD() ? DrawBorder(w, r, :SGray, :SGray, :SGray, :SGray) : DrawBorder(w, r, :SGray, :SGray); break;
	case "InsetFrame()": DrawBorder(w, r, :Gray, :White, :Black, :LtGray); break;
	case "OutsetFrame()": DrawBorder(w, r, :LtGray, :Black, :White, :Gray); break;
	case "ButtonFrame()": DrawButtonFrame(w, r); break;
	case "ThinInsetFrame()": DrawBorder(w, r, :Gray, :White); break;
	case "ThinOutsetFrame()": DrawBorder(w, r, :White, :Gray); break;
	case "BlackFrame()": DrawBorder(w, r, :SBlack, :SBlack); break;
	case "TopSeparatorFrame()":
		w.DrawRect(r.left, r.top, width, 1, :Gray);
		w.DrawRect(r.left, r.top + 1, width, 1, :White);
		r.top += 2;
		break;
	case "BottomSeparatorFrame()":
		w.DrawRect(r.left, r.bottom - 2, width, 1, :Gray);
		w.DrawRect(r.left, r.bottom - 1, width, 1, :White);
		r.bottom -= 2;
		break;
	case "LeftSeparatorFrame()":
		w.DrawRect(r.left, r.top, 1, height, :Gray);
		w.DrawRect(r.left + 1, r.top, 1, height, :White);
		r.left += 2;
		break;
	case "RightSeparatorFrame()":
		w.DrawRect(r.right - 2, r.top, 1, height, :Gray);
		w.DrawRect(r.right - 1, r.top, 1, height, :White);
		r.right -= 2;
		break;
	}
}

fn GradientColor(fc, tc, i, n)
{
	return Color(
			fc.r + i * (tc.r - fc.r) / n,
			fc.g + i * (tc.g - fc.g) / n,
			fc.b + i * (tc.b - fc.b) / n
	);
}

fn max(a, b)
{
	return a > b ? a : b;
}

fn min(a, b)
{
	return a < b ? a : b;
}

fn abs(a)
{
	return a < 0 ? -a : a;
}

fn atoi(a)
{
	n = 0;
	for(i = 0; i < count(a); i++)
		n = 10 * n + a[i] - '0';
	return n;
}

fn DrawVertArrowFromTip(w, tipx, tipy, dir, l, color)
{
	for(i = 0; i < l; i++) {
		w.DrawRect(tipx - i, tipy, 2 * i + 1, 1, color);
		tipy += dir;
	}
}

fn DrawVertArrow(ww, r, size, dir)
{
	w = r.right - r.left;
	h = r.bottom - r.top;
	
	y = r.top + (h - size) / 2;
	DrawVertArrowFromTip(ww, r.left + w / 2, dir > 0 ? y : y + size, dir, size, :SBlack);
}

fn DrawSpinButtons(w, r)
{
	h = r.bottom - r.top;
	h2 = int(h / 2);
	h7 = int(7 * h / 10);
	x = r.right - h7;
	ah = int(h / 6);
	ax = x + h7 / 2;
	rr = RectC(x, r.top, h7, h2);
	DrawEdgeButton(w, rr);
	DrawVertArrow(w, rr, h / 6, 1);
	rr = RectC(x, r.top + h2, h7, h - h2);
	DrawEdgeButton(w, rr);
	DrawVertArrow(w, rr, h / 6, -1);
}

fn XMinSize()
{
	return Size(8, 13);
}

subctrl NormalSize {
	GetMinSize() { sz = XMinSize(); sz.cy += 2; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 7; return sz; }
};

subctrl Base {
	>NormalSize;

	ViewRect(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		return r;
	}
	ViewSize(w) {
		r = ViewRect(w);
		return Size(r.right - r.left, r.bottom - r.top);
	}
};

subctrl Unknown {
	>NormalSize;

	ViewRect(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		return r;
	}
	ViewSize(w) {
		r = ViewRect(w);
		return Size(r.right - r.left, r.bottom - r.top);
	}
};

subctrl TextCtrl {
	GetMinSize() { sz = XMinSize(); sz.cy += 2; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 8; return sz; }

	Text   SetLabel @1 ? "Label of control" ;

	ViewRect(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		return r;
	}
	ViewSize(w) {
		r = ViewRect(w);
		return Size(r.right - r.left, r.bottom - r.top);
	}

	Paint(w) {
		sz = ViewSize(w);
		textsize = GetSmartTextSize(.SetLabel, .SetFont);
		px = 0;
		if(.SetAlign == "ALIGN_CENTER")
			px = (sz.cx - textsize.cx) / 2;
		if(.SetAlign == "ALIGN_RIGHT")
			px = sz.cx - textsize.cx;
		w.DrawSmartText(px, (sz.cy - textsize.cy) / 2, .SetLabel, .SetFont, .SetInk, sz.cx);
	}
};

ctrl Label {
	group "Static";

	GetMinSize() { return XMinSize(); }
	GetStdSize() { sz = GetMinSize(); sz.cy += 6; sz.cx *= 5; return sz; }

	>TextCtrl;

	Doc    SetLabel @0 ? "Label of control";
	Align  SetAlign = ALIGN_LEFT @2;
	Font   SetFont = StdFont() @2;
	Color  SetInk = :SBlack @2;
	Frame  SetFrame @3;
//	Qtf    SetInfo @1 ? "Info of control" ;

	Paint(w) {
		sz = ViewSize(w);
		textsize = GetSmartTextSize(.SetLabel, .SetFont);
		px = 0;
		if(.SetAlign == "ALIGN_CENTER")
			px = (sz.cx - textsize.cx) / 2;
		if(.SetAlign == "ALIGN_RIGHT")
			px = sz.cx - textsize.cx;
		w.DrawSmartText(px, (sz.cy - textsize.cy) / 2, .SetLabel, .SetFont, .SetInk, sz.cx);
	}
	Sample() {
		.SetLabel = "Label";
		.SetFont = Arial(10).Bold().Italic();
	}
};

ctrl StaticText {
	group "Static";

	GetMinSize() { return XMinSize(); }
	GetStdSize() { sz = XMinSize(); sz.cy += 6; sz.cx *= 5; return sz; }

	Doc    SetText ? "Label of control" ;
	Align  SetAlign = ALIGN_LEFT;
	Font   SetFont = StdFont();
	Color  SetInk = :SBlack;
	Frame  SetFrame @1;
//	Qtf    SetInfo @1 ? "Info of control" ;

	ViewRect(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		return r;
	}
	ViewSize(w) {
		r = ViewRect(w);
		return Size(r.right - r.left, r.bottom - r.top);
	}

	Paint(w) {
		sz = ViewSize(w);
		textsize = GetSmartTextSize(.SetText, .SetFont);
		px = 0;
		if(.SetAlign == "ALIGN_CENTER")
			px = (sz.cx - textsize.cx) / 2;
		if(.SetAlign == "ALIGN_RIGHT")
			px = sz.cx - textsize.cx;
		w.DrawSmartText(px, (sz.cy - textsize.cy) / 2, .SetText, .SetFont, .SetInk);
	}
	Sample() {
		.SetText = "Text";
		.SetFont = Arial(10).Bold().Italic();
	}
};

ctrl LabelBox {
	group "Static";

	GetMinSize() { return Size(0, 0); }
	GetStdSize() { return Size(64, 24); }

	>Base;
	Text SetLabel ? "Box label";
	Font SetFont = StdFont();
	Color SetInk = :SBlack;
	Align  SetAlign = ALIGN_LEFT;

	Paint(w) {
		sz = GetSize();
		ts = GetSmartTextSize(.SetLabel, .SetFont);
		d = ts.cy / 2;
		if(.SetAlign == "ALIGN_RIGHT")
			d = sz.cx - d;
		if(.SetAlign == "ALIGN_CENTER")
			d = sz.cx / 2 - d;
		if(2 * GetSmartTextSize("X").cy > sz.cy) {
			d = sz.cy / 2;
			w.DrawSmartText(d, (sz.cy - ts.cy) / 2, .SetLabel, .SetFont, .SetInk, sz.cx);
			w.DrawRect(1, d, d - 1, 1, :Gray);
			w.DrawRect(1, d + 1, d - 1, 1, :Gray);
			w.DrawRect(d + ts.cx, d, sz.cx - ts.cx - d - 2, 1, :Gray);
			w.DrawRect(d + ts.cx, d + 1, sz.cx - ts.cx - d - 2, 1, :Gray);
		}
		else {
			d = ts.cy / 2;
			DrawButtonFrame(w, RectC(0, d, sz.cx, sz.cy - d));
			w.DrawRect(8, 0, ts.cx + 4, ts.cy, :SLtGray);
			w.DrawSmartText(10, 0, .SetLabel, .SetFont, .SetInk);
		}
	}
}

fn DrawClassName(w, r, text)
{
	font = Arial(10);
	sz = GetTextSize(text, font);
	w.DrawRect(r.right - sz.cx - sz.cy, r.bottom - sz.cy, sz.cx + 2 * sz.cy, sz.cy, :SGray);
	w.DrawText(r.right - sz.cx - sz.cy / 2, r.bottom - sz.cy, text, font, :SYellow);
}

ctrl ParentCtrl {
	group "Special";

	GetMinSize() { return Size(0, 0); }
	GetStdSize() { return Size(64, 24); }

	Frame  SetFrame @3;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		DrawBorder(w, r, :SGray, :SGray);

		DrawClassName(w, r, "ParentCtrl");
	}
	Sample() {}
};

ctrl ImageCtrl {
	group "Static";

	GetMinSize() { return Size(0, 0); }
	GetStdSize() { return Size(64, 24); }

	Frame  SetFrame @3;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		DrawBorder(w, r, :SGray, :SGray);
		DrawClassName(w, r, "ImageCtrl");
	}
	Sample() {}
};

ctrl DrawingCtrl {
	group "Static";

	GetMinSize() { return Size(0, 0); }
	GetStdSize() { return Size(64, 24); }

	Frame  SetFrame @3;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		DrawBorder(w, r, :SGray, :SGray);
		DrawClassName(w, r, "DrawingCtrl");
	}
	Sample() {}
};

ctrl StaticRect {
	group "Static";

	GetMinSize() { return Size(0, 0); }
	GetStdSize() { return Size(64, 24); }

	Color  SetPaper = :SLtBlue @2;
	Frame  SetFrame @3;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, .SetPaper);
		DrawBorder(w, r, :SGray, :SGray);
		DrawClassName(w, r, "StaticRect");
	}
	Sample() {}
};

ctrl DataPusher {
	group "Push";

	GetStdSize() { return Size(64, 24); }

	>Base;
	Frame SetFrame = default;
	bool  SetEditable = true @1;
	Text  Tip @1;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
	}
};

ctrl ColorPusher {
	group "Push";

	GetStdSize() { return Size(64, 24); }

	>Base;
	Frame SetFrame = default;
	bool  SetEditable = true @1;
	Text  Tip @1;
	bool  NotNull = false;
	bool  WithVoid = false;
	bool  SColors = false;
	bool  WithText = false;
	bool  WithHex = false;
	bool  Track = false;
	bool  NoRampWheel = false;
	Text  NullText;
	Text  VoidText;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		DeflateRect(r);
		w.DrawRect(r, :LtBlue);
		DrawFrame(w, r, :SBlack, :SBlack);
		if(.WithText || .WithHex) {
			text = .WithText ? "LtBlue" : "#0000FF";
			w.DrawText(r.left + 6, (r.top + r.bottom - GetSmartTextSize(text, StdFont()).cy) / 2,
			           text, StdFont(), :White);
		}
	}
};

ctrl Button {
	group "Push";

	GetMinSize() { sz = XMinSize(); sz.cy += 2; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 7; return sz; }

	Text   SetLabel ? "Button label";
	Font   SetFont = StdFont();
	Frame  SetFrame @1;
	Text   Tip @1;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		DrawButtonFrame(w, r);
		sz = Size(r.right - r.left, r.bottom - r.top);
		n = int(sz.cy / 8);
		w.DrawRect(r.left, r.top, sz.cx, sz.cy, :SWhiteGray);
		textsize = GetSmartTextSize(.SetLabel, .SetFont);
		py = (sz.cy - textsize.cy) / 2;
		if(count(.SetLabel) && (.SetLabel)[0] == 1)
			w.DrawSmartText(r.left, r.top + py, .SetLabel, .SetFont, :SBlack, sz.cx);
		else {
			px = (sz.cx - textsize.cx) / 2;
			w.DrawSmartText(px + r.left, py + r.top, .SetLabel, .SetFont, :SBlack, sz.cx);
		}
	}
}

fn DrawCircle(w, xx, yy, c, color)
{
	if(c > 15) {
		w.DrawRect(xx + 5, yy, c - 10, c, color);
		w.DrawRect(xx, yy + 5, c, c - 10, color);
		w.DrawRect(xx + 3, yy + 1, c - 6, c - 2, color);
		w.DrawRect(xx + 1, yy + 3, c - 2, c - 6, color);
		w.DrawRect(xx + 2, yy + 2, c - 4, c - 4, color);
	}
	else {
		w.DrawRect(xx + 2, yy, c - 4, c, color);
		w.DrawRect(xx, yy + 2, c, c - 4, color);
		w.DrawRect(xx + 1, yy + 1, c - 2, c - 2, color);
	}
}

fn OptionSize()
{
	return IsUHD() ? 24 : 11;
}

fn DrawOption(w, x, y, kind)
{
	osz = OptionSize();
	if(kind == 2) {
		DrawCircle(w, x, y, osz, :Gray);
		if(osz > 15)
			DrawCircle(w, x + 2, y + 2, osz - 4, :SWhite);
		else
			DrawCircle(w, x + 1, y + 1, osz - 2, :SWhite);
	}
	else {
		r = RectC(x, y, osz, osz);
		w.DrawRect(r, :SWhite);
		if(osz > 15) {
			DrawButtonFrame(w, r);
			if(kind == 1)
				w.DrawRect(x + 4, y + osz / 2 - 1, osz - 8, 2, :SBlack);
		}
		else {
			DrawBorder(w, r, :Gray, :Gray);
			if(kind == 1)
				w.DrawRect(x + 3, y + osz / 2 - 1, osz - 6, 2, :SBlack);
		}
	}
}

ctrl Option {
	group "Push";

	Text   SetLabel @1 ? "Option text";
	Font   SetFont = StdFont() @1;
	Frame  SetFrame @2;
	bool   Box;
	bool   AutoBox;
	bool   BlackEdge;
	bool   SwitchImage;
	bool   SetEditable = true @1 ? "Editable";
	bool   ThreeState;
	bool   NotNull;
	Text   Tip @2;

	GetMinSize() { sz = XMinSize(); sz.cy += 2; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 7; sz.cy = max(16, sz.cy); return sz; }

	ViewRect(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		return r;
	}
	ViewSize(w) {
		r = ViewRect(w);
		return Size(r.right - r.left, r.bottom - r.top);
	}

	Paint(w) {
		sz = ViewSize(w);
		kind = .SwitchImage ? 2 : .ThreeState ? 1 : 0;
		osz = OptionSize();
		textsize = GetSmartTextSize(.SetLabel, .SetFont);
		
		if(.Box || .AutoBox) {
			d = textsize.cy / 2;
			DrawButtonFrame(w, RectC(0, d, sz.cx, sz.cy - d));
			d = (textsize.cy - osz) / 2;
			w.DrawRect(8, 0, osz + textsize.cx + 8, textsize.cy, :SLtGray);
			DrawOption(w, 10, d, kind);
			w.DrawSmartText(osz + 10 + DPI(4), 0, .SetLabel, .SetFont);
		}
		else {
			DrawOption(w, 0, (sz.cy - osz) / 2, kind);
			w.DrawSmartText(osz + DPI(4), (sz.cy - textsize.cy) / 2, .SetLabel, .SetFont);
		}
	}
}

ctrl OptionBox {
	> Option;
	
	bool   Box = true;
}

ctrl Switch {
	group "Push";

	GetMinSize() { sz = XMinSize(); sz.cy += 2; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 7; sz.cy = max(16, sz.cy); return sz; }

	Doc    SetLabel;
	Font   SetFont = StdFont();
	bool   SetEditable = true @1 ? "Editable";
//	Qtf    SetInfo @1 ? "Info of control" ;

	Paint(w)
	{
		sz = GetSize();
		ln = [];
		cs = [];
		gap = [];
		sep = [];
		g = 0;
		sp = 0;
		n = count(.SetLabel);
		for(i = 0; i < n; i++) {
			ch = .SetLabel[i];
			if(ch == '^') {
				if(i + 1 < n && .SetLabel[i + 1] == '^') {
					ln[] = '^';
					ln[] = '^';
					i++;
				}
				else {
					g = atoi(ln);
					ln = "";
				}
			}
			else
			if(ch == '|') {
				if(i + 1 < n && .SetLabel[i + 1] == '|') {
					ln[] = '|';
					ln[] = '|';
					i++;
				}
				else {
					g = atoi(ln);
					ln = "";
					sp = 1;
				}
			}
			else
			if(ch >= 32 && ch < 65536) {
				ln[] = ch;
			}
			else
			if(ch == '\n') {
				cs[] = ln;
				gap[] = g;
				sep[] = sp;
				ln = "";
				g = 0;
			}
		}
		cs[] = ln;
		gap[] = g;
		sep[] = sp;
		tcy = GetTextSize("W", .SetFont).cy;
		linecy = max(16, tcy + 2);
		osz = OptionSize();
		setlabel = .SetLabel;
		horz = 0;
		for(pass = 0; pass < 2; pass++) {
			y = 0;
			x = 0;
			for(i = 0; i < count(cs); i++) {
				textsize = GetSmartTextSize(cs[i], .SetFont);
				if(gap[i]) {
					gsz = gap[i] * tcy / 4;
					if(sep[i] && pass) {
						if(horz)
							w.DrawRect(x + y + gsz / 2, y, 1 + IsUHD(), linecy, :SGray);
						else
							w.DrawRect(x, y + gsz / 2, sz.cx, 1 + IsUHD(), :SGray);
					}
					if(horz)
						x += gsz;
					else
						y += gsz;
				}
				if(horz) {
					if(pass) {
						DrawOption(w, x, (sz.cy - osz) / 2, 2);
						w.DrawSmartText(x + osz + DPI(4), (sz.cy - tcy) / 2, cs[i], .SetFont);
					}
					x += osz + DPI(4) + textsize.cx + tcy / 2;
				}
				else {
					if(pass) {
						DrawOption(w, x, y, 2);
						w.DrawSmartText(x + osz + DPI(4), y + (osz - tcy) / 2, cs[i], .SetFont);
					}
					y += linecy;
				}
			}
			if(y > sz.cy)
				horz = 1;
		}
	}
};

ctrl EditField {
	group "Input fields";

	GetMinSize() { sz = XMinSize(); sz.cy += 6; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 8; return sz; }

	Font  SetFont = StdFont() @3;
	int   MaxChars @2;
	bool  AlignRight @1;
	bool  SetEditable = true @4 ? "Editable";
	Frame SetFrame = default @5;
	Text  Tip @5;
	bool  WantFocus = true @7;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		PaintData(w);
	}

	PaintText(w, text) {
		w.DrawText(3, (GetSize().cy - GetTextSize(text, Arial(10)).cy) / 2,
			(.SetEditable ? "" : "R/O ") + text, Arial(10), :SMagenta);
	}

	PaintData(w) {
		PaintText(w, "EditField");
	}
}

ctrl DocEdit {
	group "Editors";

	GetMinSize() { sz = XMinSize(); sz.cy += 6; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 8; return sz; }

	Font  SetFont = StdFont();
	Frame SetFrame = default @1;
	bool  SetEditable = true @1 ? "Editable";
	bool  WantFocus = true;
	Text  Tip @1;
//	Qtf   SetInfo @1 ? "Info of control" ;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		PaintData(w);
	}

	PaintText(w, text) {
		w.DrawText(3, (GetSize().cy - GetTextSize(text, Arial(10)).cy) / 2,
			(.SetEditable ? "" : "R/O ") + text, Arial(10), :SMagenta);
	}

	PaintData(w) {
		PaintText(w, "DocEdit");
	}
}

ctrl LineEdit {
	group "Editors";

	GetMinSize() { sz = XMinSize(); sz.cy += 6; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 8; return sz; }

	Font  SetFont = StdFont() @3;
//	int   MaxChars @2;
//	bool  AlignRight @1;
	bool  SetEditable = true @4 ? "Editable";
	Frame SetFrame = default @5;
	Text  Tip @5;
	bool  WantFocus = true @7;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		PaintData(w);
	}
	
	PaintText(w, text) {
		w.DrawText(3, (GetSize().cy - GetTextSize(text, Arial(10)).cy) / 2,
			(.SetEditable ? "" : "R/O ") + text, Arial(10), :SMagenta);
	}
	
	PaintData(w) {
		PaintText(w, "LineEdit");
	}
}

subctrl EditNotNull {
	>EditField;
	bool NotNull = false;

	PaintMinMax(w, type, minval, maxval) {
		PaintMinMaxText(w, type, minval, maxval, addtext);
	}

	PaintMinMaxText(w, type, minval, maxval, addtext) {
		text = (.NotNull ? "!" : "") + type;
		if(minval != "" && maxval != "")
			text << " " << minval << "..." << maxval;
		else
		if(minval != "")
			text << " >= " << minval;
		else
		if(maxval != "")
			text << " <= " << maxval;
		text << addtext;
		PaintText(w, text);
	}
}

ctrl EditString {
	>EditNotNull;
	raw MaxLen;
	bool TrimLeft = false;
	bool TrimRight = false;

	PaintData(w) {
		text = (.NotNull ? "!" : "") + "Str";
		if(.MaxLen != "")
			text << "(" << .MaxLen << ")";
		PaintText(w, text);
	}
}

ctrl SuggestCtrl {
	>EditString;

	PaintData(w) {
		text = (.NotNull ? "!" : "") + "SuggestCtrl";
		if(.MaxLen != "")
			text << "(" << .MaxLen << ")";
		PaintText(w, text);
	}
}

fn IntStr(x)
{
	return x == :IntNull || x < :DblNullLim ? "" : to_string(x);
}

fn DblStr(x)
{
	return x < :DblNullLim ? "" : to_string(x);
}

ctrl EditInt {
	>EditNotNull;
	int Min;
	int Max;

	PaintData(w) { PaintMinMax(w, "int", IntStr(.Min), IntStr(.Max)); }
}

ctrl EditInt64 {
	>EditNotNull;
	int Min;
	int Max;

	PaintData(w) { PaintMinMax(w, "int64", IntStr(.Min), IntStr(.Max)); }
}

ctrl EditIntNotNull {
	>EditInt;

	bool NotNull = true;
};

ctrl EditDouble {
	>EditNotNull;
	double Min;
	double Max;

	PaintData(w) { PaintMinMax(w, "dbl", DblStr(.Min), DblStr(.Max)); }
}

ctrl EditDoubleNotNull {
	>EditDouble;

	bool NotNull = true;
}

ctrl EditFloat {
	>EditNotNull;
	double Min;
	double Max;

	PaintData(w) { PaintMinMax(w, "flt", DblStr(.Min), DblStr(.Max)); }
}

ctrl EditFloatNotNull {
	>EditFloat;

	bool NotNull = true;
}


ctrl EditDate {
	>EditNotNull;
	PaintData(w) { PaintMinMax(w, "Date", "", ""); }
}

ctrl EditDateNotNull {
	>EditDate;

	bool NotNull = true;
}

ctrl EditTime {
	>EditNotNull;
	PaintData(w) { PaintMinMax(w, "Time", "", ""); }
}

ctrl EditTimeNotNull {
	>EditTime;

	bool NotNull = true;
}

ctrl EditIntSpin {
	>EditInt;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		DrawSpinButtons(w, r);
		PaintMinMax(w, "int", IntStr(.Min), IntStr(.Max));
	}
}

ctrl EditInt64Spin {
	>EditInt64;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		DrawSpinButtons(w, r);
		PaintMinMax(w, "int64", IntStr(.Min), IntStr(.Max));
	}
}

ctrl EditDoubleSpin {
	>EditDouble;

	double SetInc = 0.1;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		DrawSpinButtons(w, r);
		PaintMinMaxText(w, "dbl", DblStr(.Min), DblStr(.Max), "+-" + DblStr(.SetInc));
	}
}

ctrl EditFloatSpin {
	>EditDoubleSpin;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		DrawSpinButtons(w, r);
		PaintMinMaxText(w, "flt", DblStr(.Min), DblStr(.Max), "+-" + DblStr(.SetInc));
	}
}


ctrl EditDateSpin {
	>EditDate;

	double SetInc = 1;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		DrawSpinButtons(w, r);
		PaintMinMaxText(w, "date", DblStr(.Min), DblStr(.Max), "+-" + DblStr(.SetInc));
	}
}

ctrl EditTimeSpin {
	>EditTime;

	double SetInc = 1;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		DrawSpinButtons(w, r);
		PaintMinMaxText(w, "time", DblStr(.Min), DblStr(.Max), "+-" + DblStr(.SetInc));
	}
}

fn DrawArrowButton(w, rr, dir)
{
	DrawEdgeButton(w, rr);
	DrawVertArrow(w, rr, OptionSize() / 3, dir);
}

fn DrawHArrowButton(w, rr, dir)
{
	DrawEdgeButton(w, rr);
	DrawHorzArrow(w, rr, dir);
}

fn DrawDropButton(w, rr)
{
	DrawArrowButton(w, rr, -1);
}

fn PaintVScrollBar(w, r)
{
	wd = r.right - r.left;
	up = r;
	r.top = (up.bottom = up.top + wd);
	down = r;
	r.bottom = (down.top = down.bottom - wd);
	DrawArrowButton(w, up, 1);
	DrawArrowButton(w, down, -1);
	w.DrawRect(r, :SWhiteGray);
}

fn PaintHScrollBar(w, r)
{
	hg = r.bottom - r.top;
	left = r;
	r.left = (left.right = left.left + hg);
	right = r;
	r.right = (right.left = right.right - hg);
	DrawHArrowButton(w, left, 1);
	DrawHArrowButton(w, right, -1);
	w.DrawRect(r, :SWhiteGray);
}


ctrl DropList {
	group "Input fields";

	GetMinSize() { sz = XMinSize(); sz.cy += 6; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 8; return sz; }

	Frame SetFrame = default;
	bool  SetEditable = true @2 ? "Editable";
	bool  WantFocus = true;
	bool  DisplayAll = false;
	bool  AlwaysDrop = false @1;
	bool  NotNull = false;
	Text  Tip @2;
//	Qtf   SetInfo @1 ? "Info of control" ;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		n = r.bottom - r.top;
		DrawDropButton(w, RectC(r.right - n, r.top, n, n));
		w.DrawText(3, (GetSize().cy - GetTextSize("", Arial(10)).cy) / 2,
			(.SetEditable ? "" : "R/O ") + "DropList", Arial(10), :SMagenta);
	}
}

ctrl DropTree {
	group "Input fields";

	GetMinSize() { sz = XMinSize(); sz.cy += 6; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 8; return sz; }

	Frame SetFrame = default;
	bool  SetEditable = true @2 ? "Editable";
	bool  WantFocus = true;
	bool  DisplayAll = false;
	bool  AutoResize = false;
	bool  DropFocus = false;
	bool  AlwaysDrop = false @1;
	Text  Tip @2;
//	Qtf   SetInfo @1 ? "Info of control" ;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		n = r.bottom - r.top;
		DrawDropButton(w, RectC(r.right - n, r.top, n, n));
		w.DrawText(3, (GetSize().cy - GetTextSize("", Arial(10)).cy) / 2,
			(.SetEditable ? "" : "R/O ") + "DropTree", Arial(10), :SMagenta);
	}
}

template WithDropChoice {
	bool   Dropping = true;
	String Appending = ", ";
	raw    SetDropLines = 16;
	Text   Tip @1;

	Paint(w) {
		CtrlPaint(w);
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		n = r.bottom - r.top;
		DrawDropButton(w, RectC(r.right - n, r.top, n, n));
	}
};


fn PaintTab(w, x, y, text, color)
{
	tsz = GetSmartTextSize(text, StdFont());
	h = 4 * tsz.cy / 3;
	sz = Size(tsz.cx + tsz.cy, h);
	r = RectC(x, y, sz.cx, sz.cy);
	w.DrawRect(r, color);
	DrawButtonFrame(w, r);
	w.DrawSmartText(x + tsz.cy / 2, y + 4, text, StdFont(), :SBlack, sz.cx);
	sz.cx -= 4;
	return sz;
}

ctrl TabCtrl {
	group "Complex";

	GetStdSize() { return Size(150, 100); }

//	Font SetFont = StdFont();
	bool AcceptCurrent = false;
//	Qtf  SetInfo @1 ? "Info of control" ;

	Paint(w) {
		r = GetRect();
		x0 = x = r.left;
		y = r.top;
		tsz = PaintTab(w, x, y, "Tab 1", :SWhiteGray);
		x += tsz.cx;
		x += PaintTab(w, x, y + 3, "Tab 2", :SLtGray).cx;
		x += PaintTab(w, x, y + 3, "Tab 3", :SLtGray).cx;
		r.top += tsz.cy;
		DrawButton(w, r);
		PaintTab(w, x0, y, "Tab 1", :SWhiteGray);
		if(IsUHD())
			w.DrawRect(2, r.top - 2, tsz.cx, 4, :SWhiteGray);
		else
			w.DrawRect(1, r.top - 1, tsz.cx + 2, 2, :SWhiteGray);
}
};

fn PaintHeaderTab(w, r, text)
{
	DrawEdgeButtonFrame(w, r);
	w.DrawText(r.left + 2, (r.top + r.bottom - GetTextSize(text).cy) >> 1, text, StdFont());
}

fn PaintHeaderTabs(w, ...)
{
	for(i = 0; i < count(argv); i += 2)
		PaintHeaderTab(w, argv[i], argv[i + 1]);
}

fn PaintCenterText(w, x, y, text, fnt, color)
{
	sz = GetTextSize(text, fnt);
	w.DrawText(x - sz.cx / 2, y - sz.cy / 2, text, fnt, color);
}

ctrl ArrayCtrl {
	group "Complex";

	GetStdSize() { return Size(150, 100); }
	Frame SetFrame = default @1;
	bool AutoHideSb = false;
	bool Header = true;
	bool Inserting = false;
	bool Appending = false;
	bool AppendLine = false;
	bool Removing = false;
	bool AskRemove = true;
	bool Duplicating = false;
	bool Moving = false;
	bool Track = false;
	bool VertGrid = true;
	bool HorzGrid = true;
	bool NoCursor = false;
	bool MouseMoveCursor = false;
	bool MultiSelect = false;
	int  SetLineCy;

//	Qtf  SetInfo @1 ? "Info of control" ;

	Paint(w) {
		PaintArray(w, GetRect());
	}

	PaintArray(w, r) {
		col1 = "A";
		col2 = "B";
		col3 = "C";
		fontcy = GetTextSize(col1).cy;
		DrawCtrlFrame(w, r, .SetFrame);
		if(!.AutoHideSb) {
			rsb = r;
			r.right = rsb.left = rsb.right - fontcy - 4;
			PaintVScrollBar(w, rsb);
		}
		wd = r.right - r.left;
		hdrcy = .Header ? fontcy + 4 : 0;
		third = wd / 3;
		r1 = r;
		r1.bottom = r1.top + hdrcy;
		r1.right = r.left + third;
		r2 = r1;
		r2.right = (r2.left = r1.right) + third;
		r3 = r2;
		r3.left = r2.right;
		r3.right = r.right;
		if(hdrcy)
			PaintHeaderTabs(w, r1, col1, r2, col2, r3, col3);
		linecy = .SetLineCy;
		if(is_void(linecy) || linecy <= 0)
			linecy = fontcy + 1;
		celltop = r1.bottom;
		cellht = 3 * linecy;
		cellbot = celltop + cellht + 1;
		w.DrawRect(r.left, celltop, wd, cellht, :SWhite);
		if(.NoCursor == "false")
			w.DrawRect(r.left, celltop + linecy, wd, linecy, :SBlue);
		for(i = 1; i <= 3; i++)
		{
			if(.HorzGrid)
				w.DrawRect(r.left, celltop + linecy * i, wd, 1, :SLtGray);
			if(.VertGrid)
				w.DrawRect(r.left + i * third - 1, celltop, 1, cellht, :SLtGray);
		}
		w.DrawRect(r.left, cellbot, wd, r.bottom - cellbot, :SWhite.r < 50 ? Color(90, 90, 90) : Color(160, 160, 160));
		w.DrawText(3, r.bottom - fontcy, "ArrayCtrl", Arial(10), :SMagenta);
	}
};

fn DrawTreeItem(w, x, y, text, kind)
{
	osz = OptionSize();
	textsize = GetTextSize(text);
	if(kind)
		DrawOption(w, x, y + (textsize.cy - osz) / 2, 1);
	w.DrawText(x + osz + 2, y, text, StdFont(), :SBlack);
}

ctrl TreeCtrl {
	group "Complex";

	GetStdSize() { return Size(150, 100); }
	Frame SetFrame = default @1;

//	Qtf  SetInfo @1 ? "Info of control" ;

	Paint(w) {
		r = GetRect();
		w.DrawRect(r, :SWhite);
		fontcy = GetTextSize("X").cy;
		DrawCtrlFrame(w, r, .SetFrame);
		rsb = r;
		r.right = rsb.left = rsb.right - fontcy - 4;
		PaintVScrollBar(w, rsb);
		w.DrawText(3, r.bottom - fontcy, "TreeCtrl", Arial(10), :SMagenta);
		DeflateRect(r);
		DeflateRect(r);
		DrawTreeItem(w, r.left + 2 + 0 * fontcy, r.top + 2 + 0 * fontcy, "Root", 1);
		DrawTreeItem(w, r.left + 2 + 1 * fontcy, r.top + 2 + 1 * fontcy, "Node 1", 0);
		DrawTreeItem(w, r.left + 2 + 1 * fontcy, r.top + 2 + 2 * fontcy, "Node 2", 1);
		DrawTreeItem(w, r.left + 2 + 2 * fontcy, r.top + 2 + 3 * fontcy, "Node 3", 0);
	}
};

ctrl ColumnList {
	group "Complex";

	GetStdSize() { return Size(150, 100); }
	Frame SetFrame = default @1;

//	Qtf  SetInfo @1 ? "Info of control" ;

	Paint(w) {
		r = GetRect();
		w.DrawRect(r, :SWhite);
		fontcy = GetTextSize("X").cy;
		DrawCtrlFrame(w, r, .SetFrame);
		rsb = r;
		r.right = rsb.left = rsb.right - fontcy - 4;
		PaintVScrollBar(w, rsb);
		w.DrawText(3, 3, "ColumnList", Arial(10), :SMagenta);
	}
};

ctrl RichTextView {
	group "Complex";

	GetMinSize() { sz = XMinSize(); sz.cy += 6; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 8; return sz; }

	Frame SetFrame = default @1;
	Color Background = :SWhite;
	bool  VCenter = false;
	bool  NoSb = false @0;
	bool  AutoHideSb = false;
	int   HMargins = 0;
	int   VMargins = 0;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, .Background);
		w.DrawText(3, 3, "RichTextView", Arial(10), :SMagenta);
	}
}

ctrl RichTextCtrl {
	group "Static";

	>RichTextView;

	GetMinSize() { sz = XMinSize(); sz.cy += 6; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 8; return sz; }

	Frame SetFrame = NullFrame() @1;
	Color Background = Null @0;
	bool  NoSb = false @0;
	bool  AutoHideSb = true @0;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		if(!is_void(.Background))
			w.DrawRect(r, .Background);
		w.DrawText(3, 3, "RichTextCtrl", Arial(10), :SMagenta);
	}
}

ctrl DropDate {
	group "Input fields";

	GetMinSize() { sz = XMinSize(); sz.cy += 6; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 13; return sz; }

	bool  SelectAll = true;
	bool  SwapMonthYear = false;
	bool  OneButton = false;
	bool  NotNull = false;
	Frame SetFrame = default;
	bool  SetEditable = true @2 ? "Editable";
	bool  WantFocus = true;
	Text  Tip @2;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		n = r.bottom - r.top;
		DrawDropButton(w, RectC(r.right - n, r.top, n, n));
		w.DrawText(3, (GetSize().cy - GetTextSize("", Arial(10)).cy) / 2,
			(.SetEditable ? "" : "R/O ") + (.NotNull ? "!" : "") + "25/07/2022", Arial(10), :SMagenta);
	}
}

ctrl DropTime {
	group "Input fields";

	GetMinSize() { sz = XMinSize(); sz.cy += 6; return sz; }
	GetStdSize() { sz = GetMinSize(); sz.cx *= 15; return sz; }

	bool  SelectAll = true;
	bool  SwapMonthYear = false;
	bool  Seconds = true;
	bool  DayEnd = false;
	bool  TimeAlways = false;
	bool  NotNull = false;
	Frame SetFrame = default;
	bool  SetEditable = true @2 ? "Editable";
	bool  WantFocus = true @2;
	Text  Tip @2;

	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);
		w.DrawRect(r, :SWhite);
		n = r.bottom - r.top;
		DrawDropButton(w, RectC(r.right - n, r.top, n, n));
		w.DrawText(3, (GetSize().cy - GetTextSize("", Arial(10)).cy) / 2,
			(.SetEditable ? "" : "R/O ") + (.NotNull ? "!" : "") + "25/07/2022 10:34:58", Arial(10), :SMagenta);
	}
}

fn DrawHorzArrowFromTip(w, tipx, tipy, dir, l, color)
{
	for(i = 0; i < l; i++) {
		w.DrawRect(tipx, tipy - i, 1, 2 * i + 1, color);
		tipx += dir;
	}
}

fn DrawHorzArrow(ww, r, dir)
{
	size = OptionSize() / 3;

	w = r.right - r.left;
	h = r.bottom - r.top;
	
	x = r.left + (h - size) / 2;
	DrawHorzArrowFromTip(ww, dir > 0 ? x : x + size, r.top + h / 2, dir, size, :SBlack);
}

ctrl Clock {
	group "Input fields";
	GetMinSize() { return Size(150, 156); }
	GetStdSize() { return Size(150, 156); }
	Frame SetFrame = BlackFrame() @2;
	bool  SetEditable = true @2 ? "Editable";
	bool  WantFocus = true @2;
	bool  Seconds = true;
	Text  Tip @2;

	PaintPtr(w, cmx, cmy, pos, m, d, color, cf) {
		dx = m * sin(pos * 2 * 3.1415);
		dy = m * cos(pos * 2 * 3.1415);

		sx = cmx - dx * 35 / 2.0;
		sy = cmy + dy * 35 / 2.0;
		ex = cmx + dx * cf;
		ey = cmy - dy * cf;

		w.DrawLine(sx, sy, ex, ey, d, color);
	}

	Paint(w) {
		ts = GetTextSize("X");
		r = GetRect();
		ro = GetRect();
		b = r.bottom;
		hs = 20;
		r.bottom = hs;
		w.DrawRect(r, :SLtBlue);
		r.top = r.bottom;
		r.bottom = b;
		w.DrawRect(r, :SWhite);
		width = r.right - r.left;
		height = r.bottom - r.top;

		w.DrawRect(0, hs, 5, ro.bottom - hs, Color(200, 200, 200));
		w.DrawRect(ro.right - 5, hs, 5, ro.bottom - hs, Color(200, 200, 200));

		cmx = width / 2;
		cmy = height / 2 + hs;

		cf = min(cmy - 15, cmx) - 5;
		for(i = 1; i <= 60; i++)
		{
			x = cmx + (0.95 * sin(i * 3.1415 / 30.0) * cf);
			y = cmy - (0.95 * cos(i * 3.1415 / 30.0) * cf);
			if(i % 5 == 0)
				w.DrawRect(x, y, 2, 2, :SBlack);
			else
				w.DrawRect(x, y, 1, 1, :SBlack);
		}

		fnt = Arial(10);

		for(i = 1; i <= 12; i++)
		{
			x = cmx + (0.8 * sin(i * 3.1415 / 6.0) * cf);
			y = cmy - (0.8 * cos(i * 3.1415 / 6.0) * cf);
			PaintCenterText(w, x, y, to_string(i), .fnt, :SBlack);
		}

		hour = 10;
		minute = 34;
		second = 15;

		tm = hour * 3600 + minute * 60 + second;
		PaintPtr(w, cmx, cmy, tm / 3600 / 12, 0.5, 5, :SRed, cf);
		PaintPtr(w, cmx, cmy, tm / 3600, 0.6, 3, :SBlue, cf);
		PaintPtr(w, cmx, cmy, tm / 60, 0.75, 2, :SBlack, cf);

		r = Rect(ro.left + 3, ro.top + 2, ro.left + 3 + 16, ro.top + 18);
		left = r.left;
		r.left += 15 + 25;
		r.right += 15 + 25;
		PaintCenterText(w, (r.right + left) / 2, (r.top + r.bottom) / 2, to_string(hour), .fnt, :SWhite);

		r = Rect(ro.right - 3 - 16, ro.top + 2, ro.right - 3, ro.top + 18);
		right = r.right;
		r.left -= 15 + 25;
		r.right -= 15 + 25;
		PaintCenterText(w, (right + r.left) / 2, (r.top + r.bottom) / 2, to_string(minute), .fnt, :SWhite);

		DrawCtrlFrame(w, ro, .SetFrame);
	}
}

ctrl Calendar {
	group "Input fields";

	GetMinSize() { return Size(180, 120); }
	GetStdSize() { return Size(236, 156); }

	Frame SetFrame = BlackFrame();
	bool  SetEditable = true @2 ? "Editable";
	bool  WantFocus = true @2;
	bool  SelectAll = true;
	bool  SwapMonthYear = false;
	bool  OneButton = false;
	Text  Tip @2;

	Paint(w) {
		ts = GetTextSize("X");
		r = GetRect();
		ro = GetRect();
		f = StdFont();
		b = r.bottom;
		hs = 20;
		r.bottom = hs;
		w.DrawRect(r, :SLtBlue);
		r.top = r.bottom;
		r.bottom = b;
		w.DrawRect(r, :SWhite);
		width = r.right - r.left;
		height = r.bottom - r.top - 15;
		sw = width / 8;
		sh = height / 7;
		w.DrawRect(r.left + 4, r.top + sh, width - 8, 1, :SBlack);
		w.DrawRect(r.left + sw, r.top + 4, 1, height - 8, :SBlack);
		ts = GetTextSize("Wk");
		ty = r.top + (sh - ts.cy) / 2;
		w.DrawText(r.left + (sw - ts.cx) / 2 , ty, "Wk", f, :SBlack);

		days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
		for(i = 0; i < 7; i++)
			w.DrawText(r.left + sw + i * sw + (sw - ts.cx) / 2, ty, days[i], f, :SBlack);

		d = 1;
		tn = GetTextSize("0");
		gray = 0;
		fnt = f;
		for(j = 0; j < 6; j++)
		{
			ty = r.top + sh  + j * sh + (sh - ts.cy) / 2;
			w.DrawText(r.left + (sw - tn.cx) / 2, ty, to_string(j + 1), f, :SBlack);

			for(i = 0; i < 7; i++)
			{
				if(d == 19)
				{
					w.DrawRect(r.left + sw + i * sw + 0, r.top + sh + j * sh + 0, sw, sh, :SBlack);
					w.DrawRect(r.left + sw + i * sw + 1, r.top + sh + j * sh + 1, sw - 2, sh - 2, :SYellow);
					fnt = f.Bold(1);
				}
				day = to_string(d);
				ts = GetTextSize(day, fnt);

				w.DrawText(r.left + sw + i * sw + (sw - ts.cx) / 2, ty, day, fnt, gray ? :SGray : (i == 6 ? :SRed : :SBlack));
				fnt = f.Bold(false);
				if(++d > 31)
				{
					d = 1;
					gray = 1;
				}
			}
		}

		today = "Today: 25/07/2022";
		ts = GetTextSize(today, StdFont().Bold());
		w.DrawText((r.right - ts.cx) / 2, r.bottom - 3 - ts.cy, today, f.Bold(), :SBlue);

		r = Rect(ro.left + 3, ro.top + 2, ro.left + 3 + 16, ro.top + 18);
		left = r.left;
		r.left += 15 + 65;
		r.right += 15 + 65;
		PaintCenterText(w, (left + r.right) / 2, (r.top + r.bottom) / 2, "January", f.Bold(), :SWhite);

		r = Rect(ro.right - 3 - 16, ro.top + 2, ro.right - 3, ro.top + 18);
		right = r.right;
		r.left -= 15 + 40;
		r.right -= 15 + 40;
		PaintCenterText(w, (r.left + right) / 2, (r.top + r.bottom) / 2, "2022", f.Bold(), :SWhite);

		DrawCtrlFrame(w, ro, .SetFrame);
	}
}

ctrl SliderCtrl {
	group "Progress";

	GetMinSize() { return Size(0, 0); }
	GetStdSize() { return Size(64, 24); }
	
	Frame  SetFrame @1;
	bool Jump = false;
	
	Paint(w) {
		r = GetRect();
		DrawCtrlFrame(w, r, .SetFrame);

		sz = Size(r.right - r.left, r.bottom - r.top);

		halfX = int(r.left + r.right) >> 1;
		halfY = int(r.top + r.bottom) >> 1;
		
		osz = 3 * OptionSize() / 2;
		osz2 = int(osz / 2);
		osz4 = int(osz / 4);

		if (sz.cx < sz.cy) {
			DrawInsetFrame(w, Rect(halfX - 2, r.top + 2, halfX + 2, r.bottom - 2));
			DrawButton(w, RectC(halfX - osz2, halfY - osz4, osz, osz2));
		} else {
			DrawInsetFrame(w, Rect(r.left + 2, halfY - 2, r.right - 2, halfY + 2));
			DrawButton(w, RectC(halfX - osz4, halfY - osz2, osz2, osz));
		}
	}
}

ctrl ProgressIndicator {
	group "Progress";

	GetMinSize() { return Size(0, 0); }
	GetStdSize() { return Size(64, 24); }

	Frame  SetFrame @1;

	Paint(w) {
		r = GetRect();

		DrawCtrlFrame(w, r, .SetFrame);
		DrawInsetFrame(w, r);

		sz = Size(r.right - r.left, r.bottom - r.top);

		w.DrawRect(r.left, r.top, sz.cx, sz.cy, :SBlack);
		DeflateRect(r);
		sz = Size(r.right - r.left, r.bottom - r.top);
		w.DrawRect(r.left, r.top, sz.cx, sz.cy, :SWhite);

		if(sz.cx > sz.cy) {
			w.DrawRect(r.left, r.top, sz.cx >> 2, sz.cy, :SLtGreen);
		}
		else {
			w.DrawRect(r.left, r.bottom - (sz.cy >> 2), sz.cx, sz.cy >> 2, :SLtGreen);
		}
	}
}
