
class WidgetInfoBase
{
public:
	WidgetInfoBase(const char* a_decl) {}
};

#define CONCAT2(x,y) \
	x##y

#define CONCAT(x,y) \
	CONCAT2(x,y)

#define DECLARE_WIDGET_INFO(name, x) \
	struct name : public WidgetInfoBase { \
		name() : WidgetInfoBase(x) {} \
	} CONCAT(g_register,name);

#define DECLARE_WIDGET(decl) \
	decl; \
	DECLARE_WIDGET_INFO( CONCAT(WidgetInfo,__COUNTER__) , #decl)


#include "CommonWidgets.h"
#include "Painter.h"


BEGIN_NAMESPACE


enum ButtonState
{
	DisabledState,
	NormalState,
	DefaultButtonState,
	HoverState,
	DepressedState
};


CheckableWidget::CheckableWidget(Widget* a_parent, AbstractProperty<bool>& a_value)
	: AbstractValueWidget<bool>(a_parent, a_value), checked(a_value)
{
}


HBox::HBox(Widget* a_parent)
	: Widget(a_parent, true, 0, 0, 10, 10)
{
}


VBox::VBox(Widget* a_parent)
	: Widget(a_parent, false, 0, 0, 10, 10)
{
}


GroupBoxSpacer::GroupBoxSpacer(Widget* a_parent)
	: Widget(a_parent, false, 0, 0, 10, 10)
{
}


void GroupBoxSpacer::sizeOptions(SizeOptions& a_sizeOptions)
{
	SizeOptions opts = { { 1, 8 },  { 1, 8 },  { 65536, 8 } };
	a_sizeOptions = opts;
}


GroupBox::GroupBox(Widget* a_parent, AbstractProperty<String>& a_title)
	: AbstractValueWidget<String>(a_parent, a_title), title(a_title), m_titleSpace(this)
{
	layoutMargin = 15;
}


void GroupBox::paintEvent(PaintEvent& a_event)
{
	Painter p(this);
	String str = title();
	int w, h;
	p.setPen(0x000000);
	p.setFontFamily("Segoe UI"); // "MS Shell Dlg 2"  "MS Shell Dlg"
	p.setFontSize(15);
	p.textExtents(w, h, str.data());
	p.drawText(15, 0, str.data());

	p.setPen(0xC0C0C0);
	p.drawLine(3, h/2, 12, h/2);
	p.drawLine(3, h/2, 3, height() - 3);
	p.drawLine(3, height() - 3, width() -3, height() - 3);
	p.drawLine(width() -3, h/2, width() -3, height() - 3);
	p.drawLine(w + 15 + 3, h/2, width() -3, h/2);
}
	

HSpace::HSpace(Widget* a_parent)
	: Widget(a_parent, true, 0, 0, 10, 10)
{
}


void HSpace::sizeOptions(SizeOptions& a_sizeOptions)
{
	SizeOptions opts = { { 64, 10 },  { 128, 10 },  { 65536, 65536 } };
	a_sizeOptions = opts;
}


VSpace::VSpace(Widget* a_parent)
	: Widget(a_parent, false, 0, 0, 10, 10)
{
}


void VSpace::sizeOptions(SizeOptions& a_sizeOptions)
{
	SizeOptions opts = { { 10, 64 },  { 10, 128 },  { 65536, 65536 } };
	a_sizeOptions = opts;
}


Label::Label(Widget* a_parent, AbstractProperty<String>& a_text)
	: AbstractValueWidget<String>(a_parent, a_text), text(a_text)
{
	flags = WF_EraseBackground;
	//connect(text.valueChanged, this, &Widget::eraseBackground);
	disabled = false;
}


void Label::sizeOptions(SizeOptions& a_sizeOptions)
{
	int len = (int)text().toUtf8().size();
	SizeOptions opts = { { len*8, 20 },  { len*16+32, 32 },  { len*16+300, 48 } };
	a_sizeOptions = opts;
}


void Label::paintEvent(PaintEvent& a_event)
{
	Painter p(this);
	p.setPen(disabled() ? 0xFF838383 : 0xFF000000);
	p.setFontFamily("Segoe UI"); // "MS Shell Dlg 2"  "MS Shell Dlg"
	p.setFontSize(15);
	p.drawText(1, 1, text().data());
}


Button::Button(Widget* a_parent, const char* a_text)
	: Widget(a_parent, false, 0, 0, 100, 32)
{
	text = a_text;
	m_mouseDownInButton = false;
	m_mouseOverButton = false;
	disabled = false;
	isDefault = false;
	hasFocus = false;

	// If any properties change, refresh the button
	connect(text.valueChanged, this, &Button::refresh);
	connect(disabled.valueChanged, this, &Button::refresh);
	connect(isDefault.valueChanged, this, &Button::refresh);
	connect(hasFocus.valueChanged, this, &Button::refresh);
}


void Button::refresh(Variant& newValue)
{
	repaint();
}


void Button::sizeOptions(SizeOptions& a_sizeOptions)
{
	int len = (int)text().toUtf8().size();
	SizeOptions opts = { { len*8, 20 },  { len*12+16, 21 },  { len*12+32, 24 } };
	a_sizeOptions = opts;
}


void Button::paintEvent(PaintEvent& a_event)
{
	Painter p(this);

	bool drawFocusRect = !disabled() && hasFocus();
	ButtonState state = NormalState;
	if (disabled())
		state = DisabledState;
	else if (m_mouseOverButton && m_mouseDownInButton)
		state = DepressedState;
	else if (m_mouseOverButton != m_mouseDownInButton)
		state = HoverState;
	else if (isDefault() || hasFocus())
		state = DefaultButtonState;

	uint32_t bgA1Cols[5] = { 0xefefef, 0xf0f0f0, 0xf0f0f0, 0xecf4fc, 0xdaecfc };
	uint32_t bgB1Cols[5] = { 0xefefef, 0xe5e5e5, 0xe5e5e5, 0xdcecfc, 0xc4e0fc };
	uint32_t edgeCols[5] = { 0xd9d9d9, 0xacacac, 0x3399ff, 0x7eb4ea, 0x569de5 };
	uint32_t textCols[5] = { 0x838383, 0x000000, 0x000000, 0x000000, 0x000000 };

	p.setBrush(edgeCols[(int)state]);
	p.drawRectangle(0, 0, width(), height());

	//p.setBrush(bgA1Cols[(int)state]);
	//p.drawRectangle(1, 1, width() - 2, height() - 2);
	CUTE_Gradient gradient = { CUTE_LINEAR_GRADIENT, bgA1Cols[(int)state], bgB1Cols[(int)state], 0, { { 1, 0, 1 } } };
	gradient.m_data.m_linear.m_y2 = height() - 1;
	p.drawGradient(1, 1, gradient, width() - 2, height() - 2);

	p.setPen(textCols[(int)state]);
	p.setFontFamily("Segoe UI"); // "MS Shell Dlg 2"  "MS Shell Dlg"
	p.setFontSize(15);

	int textWidth, textheight;
	p.textExtents(textWidth, textheight, text().data());
	p.drawText((width() - textWidth) / 2, (height() - textheight) / 2, text().data());

	if (drawFocusRect)
		p.drawFocusRectangle(2, 2, width() - 4, height() - 4);
}


void Button::mouseEnterEvent(MouseEvent& a_event)
{
	m_mouseOverButton = true;
	repaint();
}


void Button::mouseLeaveEvent(MouseEvent& a_event)
{
	m_mouseOverButton = false;
	repaint();
}


void Button::mouseEvent(MouseEvent& a_event)
{
	if (!a_event.m_oldButtons && a_event.m_buttons)
		m_mouseDownInButton = true;

	if (a_event.m_oldButtons && !a_event.m_buttons)
	{
		// mouse up while in button
		if (m_mouseDownInButton && m_mouseOverButton && !disabled())
			activated();
	}

	if (!a_event.m_buttons)
		m_mouseDownInButton = false;

	if (a_event.m_oldButtons != a_event.m_buttons)
		repaint();
}
	

struct GimpExportedImage
{
  unsigned int 	 width;
  unsigned int 	 height;
  unsigned int 	 bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */ 
  unsigned char	 pixel_data[11 * 11 * 4 + 1];
};


static const GimpExportedImage g_checkBoxTickImage = 
{
  11, 11, 4,
  "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
  "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
  "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
  "\0\377\377\377\0\377\377\377\0\377\377\377\0\177\177\177\200\337\337\337"
  "\40\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0"
  "\377\377\377\0\377\377\377\0\377\377\377\0\257\257\257P\0\0\0\377\17\17\17"
  "\360\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377"
  "\0\377\377\377\0\377\377\377\0\357\357\357\20\17\17\17\360\0\0\0\377\177"
  "\177\177\200\377\377\377\0\377\377\377\0\377\377\377\0\177\177\177\200\317"
  "\317\3170\377\377\377\0\377\377\377\0OOO\260\0\0\0\377///\320\377\377\377"
  "\0\377\377\377\0\377\377\377\0\237\237\237`\0\0\0\377\17\17\17\360\317\317"
  "\3170\257\257\257P\0\0\0\377\0\0\0\377\317\317\3170\377\377\377\0\377\377"
  "\377\0\377\377\377\0\357\357\357\20///\320\0\0\0\377\0\0\0\377\17\17\17\360"
  "\0\0\0\377\177\177\177\200\377\377\377\0\377\377\377\0\377\377\377\0\377"
  "\377\377\0\377\377\377\0\357\357\357\20???\300\0\0\0\377\0\0\0\377///\320"
  "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
  "\377\377\0\377\377\377\0\377\377\377\0___\240\0\0\0\377\317\317\3170\377"
  "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377"
  "\377\0\377\377\377\0\377\377\377\0\377\377\377\0\317\317\3170\377\377\377"
  "\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0"
  "\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377"
  "\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0\377\377\377\0",
};


static const GimpExportedImage g_comboBoxHandleImage =
{
  7, 4, 4,
  "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\352"
  "\352\352\0\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\352\352\352"
  "\0\352\352\352\0\352\352\352\0\0\0\0\377\0\0\0\377\0\0\0\377\352\352\352"
  "\0\352\352\352\0\352\352\352\0\352\352\352\0\352\352\352\0\0\0\0\377\352"
  "\352\352\0\352\352\352\0\352\352\352\0",
};


static void drawGimpExportedImage(Painter& p, int x, int y, const GimpExportedImage& img)
{
	p.drawPixelBuffer(x, y, img.pixel_data, img.width, img.height, img.bytes_per_pixel);
}


CheckBox::CheckBox(Widget* a_parent, AbstractProperty<bool>& a_checked, const char* a_text)
	: CheckableWidget(a_parent, a_checked)
{
	text = a_text;
	m_mouseDownInButton = false;
	m_mouseOverButton = false;
	disabled = false;
	isDefault = false;
	hasFocus = false;
	checked = false;
}


void CheckBox::sizeOptions(SizeOptions& a_sizeOptions)
{
	int len = (int)text().toUtf8().size();
	SizeOptions opts = { { len*8+16, 20 },  { len*12+24, 21 },  { len*12+32, 24 } };
	a_sizeOptions = opts;
}


void CheckBox::paintEvent(PaintEvent& a_event)
{
	Painter p(this);

	bool drawFocusRect = !disabled() && hasFocus();
	ButtonState state = NormalState;
	if (disabled())
		state = DisabledState;
	else if (m_mouseOverButton && m_mouseDownInButton)
		state = DepressedState;
	else if (m_mouseOverButton != m_mouseDownInButton)
		state = HoverState;
	//else if (isDefault() || hasFocus())
		//state = DefaultButtonState;

	uint32_t bgA1Cols[5] = { 0xe6e6e6, 0xffffff, 0, 0xf3f9ff, 0xd9ecff };
	uint32_t edgeCols[5] = { 0xbcbcbc, 0x707070, 0, 0x3399ff, 0x007cde };
	uint32_t textCols[5] = { 0x838383, 0x000000, 0, 0x000000, 0x000000 };

	p.setBrush(edgeCols[(int)state]);
	p.drawRectangle(3, 3, 13, 13);
	p.setBrush(bgA1Cols[(int)state]);
	p.drawRectangle(4, 4, 11, 11);

	if (checked())
		drawGimpExportedImage(p, 4, 4, g_checkBoxTickImage);

	p.setPen(textCols[(int)state]);
	p.setFontFamily("Segoe UI"); // "MS Shell Dlg 2"  "MS Shell Dlg"
	p.setFontSize(15);
	p.drawText(18, 2, text().data());

	if (drawFocusRect)
	{
		int textWidth, textheight;
		p.textExtents(textWidth, textheight, text().data());
		p.drawFocusRectangle(18, 2, textWidth + 1, textheight);
	}
}


void CheckBox::toggle()
{
	printf("check box toggled\n");
	checked = !checked();
}


void CheckBox::mouseEnterEvent(MouseEvent& a_event)
{
	m_mouseOverButton = true;
	repaint();
}


void CheckBox::mouseLeaveEvent(MouseEvent& a_event)
{
	m_mouseOverButton = false;
	repaint();
}


void CheckBox::mouseEvent(MouseEvent& a_event)
{
	if (!a_event.m_oldButtons && a_event.m_buttons)
		m_mouseDownInButton = true;

	if (a_event.m_oldButtons && !a_event.m_buttons)
	{
		// mouse up while in button
		if (m_mouseDownInButton && m_mouseOverButton && !disabled())
			toggle();
	}

	if (!a_event.m_buttons)
		m_mouseDownInButton = false;

	if (a_event.m_oldButtons != a_event.m_buttons)
		repaint();
}


RadioButton::RadioButton(Widget* a_parent, AbstractProperty<bool>& a_checked, const char* a_text)
	: CheckableWidget(a_parent, a_checked)
{
	text = a_text;
	m_mouseDownInButton = false;
	m_mouseOverButton = false;
	disabled = false;
	isDefault = false;
	hasFocus = false;
	checked = false;
}


void RadioButton::sizeOptions(SizeOptions& a_sizeOptions)
{
	int len = (int)text().toUtf8().size();
	SizeOptions opts = { { len*8, 20 },  { len*12+16, 21 },  { len*12+32, 24 } };
	a_sizeOptions = opts;
}


void RadioButton::paintEvent(PaintEvent& a_event)
{
	Painter p(this);

	bool drawFocusRect = !disabled() && hasFocus();
	ButtonState state = NormalState;
	if (disabled())
		state = DisabledState;
	else if (m_mouseOverButton && m_mouseDownInButton)
		state = DepressedState;
	else if (m_mouseOverButton != m_mouseDownInButton)
		state = HoverState;
	//else if (isDefault() || hasFocus())
		//state = DefaultButtonState;

	uint32_t bgA1Cols[5] = { 0xe6e6e6, 0xffffff, 0, 0xf3f9ff, 0xd9ecff };
	uint32_t edgeCols[5] = { 0xbcbcbc, 0x707070, 0, 0x3399ff, 0x007cde };
	uint32_t textCols[5] = { 0x838383, 0x000000, 0, 0x000000, 0x000000 };

	p.setBrush(0xff000000 | edgeCols[(int)state]);
	p.drawEllipse(3, 3, 13, 13);
	p.setBrush(0xff000000 | bgA1Cols[(int)state]);
	p.drawEllipse(4, 4, 11, 11);

	if (checked()) {
		p.setBrush(0xff000000 | textCols[(int)state]);
		p.drawEllipse(6, 6, 6, 6);
	}

	p.setPen(textCols[(int)state]);
	p.setFontFamily("Segoe UI"); // "MS Shell Dlg 2"  "MS Shell Dlg"
	p.setFontSize(15);
	p.drawText(18, 2, text().data());

	if (drawFocusRect)
	{
		int textWidth, textheight;
		p.textExtents(textWidth, textheight, text().data());
		p.drawFocusRectangle(18, 2, textWidth + 1, textheight);
	}
}


void RadioButton::toggle()
{
	printf("radio button toggled\n");
	if (!checked())
		checked = true; // Only set to on from clicks, off happens from selecting a different radio button
	//checked = !checked();
}


void RadioButton::mouseEnterEvent(MouseEvent& a_event)
{
	m_mouseOverButton = true;
	repaint();
}


void RadioButton::mouseLeaveEvent(MouseEvent& a_event)
{
	m_mouseOverButton = false;
	repaint();
}


void RadioButton::mouseEvent(MouseEvent& a_event)
{
	if (!a_event.m_oldButtons && a_event.m_buttons)
		m_mouseDownInButton = true;

	if (a_event.m_oldButtons && !a_event.m_buttons)
	{
		// mouse up while in button
		if (m_mouseDownInButton && m_mouseOverButton && !disabled())
			toggle();
	}

	if (!a_event.m_buttons)
		m_mouseDownInButton = false;

	if (a_event.m_oldButtons != a_event.m_buttons)
		repaint();
}


LineEdit::LineEdit(Widget* a_parent, AbstractProperty<String>& a_text)
	: AbstractValueWidget<String>(a_parent, a_text), text(a_text)
{
	m_mouseOver = false;
	disabled = false;
	hasFocus = false;
	m_carrotPosition = 0;

	// If any properties change, refresh the button
	connect(text.valueChanged, this, &LineEdit::refresh);
	connect(disabled.valueChanged, this, &LineEdit::refresh);
	connect(hasFocus.valueChanged, this, &LineEdit::refresh);

	m_timerId = startTimer(500);
}


void LineEdit::sizeOptions(SizeOptions& a_sizeOptions)
{
	int len = (int)text().toUtf8().size();
	SizeOptions opts = { { len*8, 24 },  { len*12+16, 28 },  { len*12+32, 32 } };
	a_sizeOptions = opts;
}


void LineEdit::timerEvent(TimerEvent& a_event)
{
	if (a_event.m_timerId == m_timerId)
	{
		m_carrotOn = !m_carrotOn;
		repaint();
	}
}


void LineEdit::keyEvent(KeyEvent& a_event)
{
	if ( a_event.m_state != KS_Pressed )
		return;

	std::string t = text().toUtf8();
	if ( a_event.m_key == Key_BackSpace )
	{
		if ( m_carrotPosition )
		{
			m_carrotPosition--;
			std::string t2;
			if (m_carrotPosition != t.size())
				t2 = t.substr(m_carrotPosition+1, -1);
			text = t.substr(0, m_carrotPosition) + t2;
		}
	}
	else if ( a_event.m_key == Key_Delete )
	{
		if ( m_carrotPosition != t.size() )
			text = t.substr(0, m_carrotPosition) + 
					t.substr(m_carrotPosition+1, -1);
	}
	else if ( a_event.m_key == Key_Left )
		m_carrotPosition--;
	else if ( a_event.m_key == Key_Right )
		m_carrotPosition++;
	else if ( a_event.m_key >= 'a' || a_event.m_key <= 'z' )
	{
		if ( m_carrotPosition != t.size() )
			text = t.substr(0, m_carrotPosition) + 
				char(a_event.m_key) + t.substr(m_carrotPosition, -1);
		else
			text = t + char(a_event.m_key);
		m_carrotPosition++;
	}

	m_carrotPosition = clamp<int>(m_carrotPosition, 0, (int)text().toUtf8().size());
	m_carrotOn = true;
	repaint();
}


void LineEdit::paintEvent(PaintEvent& a_event)
{
	Painter p(this);

	// bool drawFocusRect = !disabled() && hasFocus();
	int state = 1;//NormalState;
	if (disabled())
		state = 0;//DisabledState;
	else if (m_mouseOver || hasFocus())
		state = 2;//ActiveState;

	uint32_t edgeCols[5] = { 0xd9d9d9, 0xacacac, 0x3399ff, 0x7eb4ea, 0x569de5 };
	uint32_t textCols[5] = { 0x838383, 0x000000, 0x000000, 0x000000, 0x000000 };

	p.setBrush(edgeCols[(int)state]);
	p.drawRectangle(0, 0, width(), height());

	p.setBrush(0xffffff);
	p.drawRectangle(1, 1, width() - 2, height() - 2);

	p.setPen(textCols[(int)state]);
	p.setFontFamily("Segoe UI"); // "MS Shell Dlg 2"  "MS Shell Dlg"
	p.setFontSize(15);

	int textWidth, textheight;
	p.textExtents(textWidth, textheight, text().data());
	p.drawText(6, (height() - textheight) / 2, text().data());

	int carrotX = 0;
	p.textExtents(carrotX, textheight, text().toUtf8().substr(0, m_carrotPosition).c_str());
	if ( m_carrotOn )
		p.drawLine(carrotX + 6, 4, carrotX + 6, height() - 4);
}


void LineEdit::mouseEnterEvent(MouseEvent& a_event)
{
	m_mouseOver = true;
	repaint();
}


void LineEdit::mouseLeaveEvent(MouseEvent& a_event)
{
	m_mouseOver = false;
	repaint();
}


void LineEdit::mouseEvent(MouseEvent& a_event)
{
	if (!a_event.m_oldButtons && a_event.m_buttons)
	{
		hasFocus = true;
	}
}


static bool isPointInsideRectangle(const Point& a_point, const Rectangle& a_rect)
{
	if (   a_point.m_x >  a_rect.m_x
		&& a_point.m_x < (a_rect.m_x + a_rect.m_width)
		&& a_point.m_y >  a_rect.m_y
		&& a_point.m_y < (a_rect.m_y + a_rect.m_height) )
		return true;
	return false;
}


Slider::Slider(Widget* a_parent, AbstractProperty<int>& a_sliderValue)
	: AbstractValueWidget<int>(a_parent, a_sliderValue)
{
	value = 0;
	disabled = false;
	m_mouseOverSliderHandle = false;
	m_mouseDownInSliderHandle = false;
	hasFocus = false;
}


void Slider::sizeOptions(SizeOptions& a_sizeOptions)
{
	SizeOptions opts = { { 32, 21 },  { 128, 21 },  { 65536, 21 } };
	a_sizeOptions = opts;
}


void Slider::paintEvent(PaintEvent& a_event)
{
	Painter p(this);

	// bool drawFocusRect = !disabled() && hasFocus();

	ButtonState state = NormalState;
	if (disabled())
		state = DisabledState;
	else if (m_mouseOverSliderHandle && m_mouseDownInSliderHandle)
		state = DepressedState;
	else if (m_mouseOverSliderHandle != m_mouseDownInSliderHandle)
		state = HoverState;
	else if (hasFocus())
		state = DefaultButtonState;

	uint32_t bgA1Cols[5] = { 0xefefef, 0xf0f0f0, 0xf0f0f0, 0xecf4fc, 0xdaecfc };
	uint32_t bgB1Cols[5] = { 0xefefef, 0xe5e5e5, 0xe5e5e5, 0xdcecfc, 0xc4e0fc };
	uint32_t edgeCols[5] = { 0xd9d9d9, 0xacacac, 0x3399ff, 0x7eb4ea, 0x569de5 };
	uint32_t textCols[5] = { 0x838383, 0x000000, 0x000000, 0x000000, 0x000000 };

	// Draw the groove
	p.setBrush(0xd9d9d9);
	p.drawRectangle(0, (height()-4)/2, width(), 4);
	p.setBrush(0xf0f0f0);
	p.drawRectangle(1, ((height()-4)/2)+1, width()-2, 2);

	// Draw the handle
	int pixelPos = value();
	p.setBrush(edgeCols[(int)state]);
	p.drawRectangle(pixelPos, 0, 10, height());
	CUTE_Gradient gradient = { CUTE_LINEAR_GRADIENT, bgA1Cols[(int)state], bgB1Cols[(int)state], 0, { { 1, 0, 1 } } };
	gradient.m_data.m_linear.m_y2 = height() - 1;
	p.drawGradient(pixelPos+1, 1, gradient, 8, height() - 2);
}


Rectangle Slider::getSliderHandleRectangle()
{
	Rectangle rect = { {{ value(), 0 }}, {{ 8, height() }} };
	return rect;
}


void Slider::mouseLeaveEvent(MouseEvent& a_event)
{
	m_mouseOverSliderHandle = false;
	repaint();
}


void Slider::mouseEvent(MouseEvent& a_event)
{
	m_mouseOverSliderHandle = isPointInsideRectangle(a_event.m_position, getSliderHandleRectangle());

	if (!a_event.m_buttons)
		m_mouseDownInSliderHandle = false;

	if (a_event.m_oldButtons && a_event.m_buttons && m_mouseDownInSliderHandle)
	{
		int newValue = value() + a_event.m_x - a_event.m_oldX;
		value = clamp<int>(newValue, 0, width()-10);
	}

	if (!a_event.m_oldButtons && a_event.m_buttons)
	{
		m_mouseDownInSliderHandle = m_mouseOverSliderHandle;

		// TODO: focus rect is only when tab key is pressed and tabbing through widgets
		hasFocus = true;
	}
	repaint();
}


ComboBox::ComboBox(Widget* a_parent, AbstractProperty<int>& a_value)
	: AbstractValueWidget<int>(a_parent, a_value), currentIndex(a_value)
{
	value = 0;
	text = "";
	m_mouseDownInButton = false;
	m_mouseOverButton = false;
	disabled = false;
	isDefault = false;
	hasFocus = false;

	// If any properties change, refresh the button
	connect(text.valueChanged, this, &ComboBox::refresh);
	connect(disabled.valueChanged, this, &ComboBox::refresh);
	connect(isDefault.valueChanged, this, &ComboBox::refresh);
	connect(hasFocus.valueChanged, this, &ComboBox::refresh);
}


void ComboBox::refresh(Variant& newValue)
{
	repaint();
}


void ComboBox::sizeOptions(SizeOptions& a_sizeOptions)
{
	int len = (int)text().toUtf8().size();
	SizeOptions opts = { { len*8, 24 },  { len*12+16, 24 },  { 65536, 24 } };
	a_sizeOptions = opts;
}


void ComboBox::paintEvent(PaintEvent& a_event)
{
	Painter p(this);

	bool drawFocusRect = !disabled() && hasFocus();
	ButtonState state = NormalState;
	if (disabled())
		state = DisabledState;
	else if (m_mouseOverButton && m_mouseDownInButton)
		state = DepressedState;
	else if (m_mouseOverButton != m_mouseDownInButton)
		state = HoverState;
	else if (isDefault() || hasFocus())
		state = DefaultButtonState;

	uint32_t bgA1Cols[5] = { 0xefefef, 0xf0f0f0, 0xf0f0f0, 0xecf4fc, 0xdaecfc };
	uint32_t bgB1Cols[5] = { 0xefefef, 0xe5e5e5, 0xe5e5e5, 0xdcecfc, 0xc4e0fc };
	uint32_t edgeCols[5] = { 0xd9d9d9, 0xacacac, 0x3399ff, 0x7eb4ea, 0x569de5 };
	uint32_t textCols[5] = { 0x838383, 0x000000, 0x000000, 0x000000, 0x000000 };

	p.setBrush(edgeCols[(int)state]);
	p.drawRectangle(0, 0, width(), height());

	CUTE_Gradient gradient = { CUTE_LINEAR_GRADIENT, bgA1Cols[(int)state], bgB1Cols[(int)state], 0, { { 1, 0, 1 } } };
	gradient.m_data.m_linear.m_y2 = height() - 1;
	p.drawGradient(1, 1, gradient, width() - 2, height() - 2);

	p.setPen(textCols[(int)state]);
	p.setFontFamily("Segoe UI"); // "MS Shell Dlg 2"  "MS Shell Dlg"
	p.setFontSize(15);

	int textWidth, textheight;
	p.textExtents(textWidth, textheight, text().data());
	p.drawText(4, (height() - textheight) / 2, text().data());

	drawGimpExportedImage(p, width() - 14, (height()-3)/2, g_comboBoxHandleImage);

	if (drawFocusRect)
		p.drawFocusRectangle(2, 2, width() - 4, height() - 4);
}


void ComboBox::mouseEnterEvent(MouseEvent& a_event)
{
	m_mouseOverButton = true;
	repaint();
}


void ComboBox::mouseLeaveEvent(MouseEvent& a_event)
{
	m_mouseOverButton = false;
	repaint();
}


void ComboBox::mouseEvent(MouseEvent& a_event)
{
	if (!a_event.m_oldButtons && a_event.m_buttons)
		m_mouseDownInButton = true;

	if (a_event.m_oldButtons && !a_event.m_buttons)
	{
		// mouse up while in button
		if (m_mouseDownInButton && m_mouseOverButton && !disabled())
			activated();
	}

	if (!a_event.m_buttons)
		m_mouseDownInButton = false;

	if (a_event.m_oldButtons != a_event.m_buttons)
		repaint();
}
	

void ComboBox::addItem(String a_itemText)
{
	m_items.push_back(a_itemText);
	if (m_items.size() >= (size_t)value())
		text = m_items[value()];
}


void ComboBox::keyEvent(KeyEvent& a_event)
{
	if ( a_event.m_state != KS_Pressed )
		return;
	int item = value();
	if ( a_event.m_key == Key_Up )
		item--;
	if ( a_event.m_key == Key_Down )
		item++;
	value = clamp<int>(item, 0, (int)m_items.size() - 1);
	text = m_items[value()];
	repaint();
}


ProgressBar::ProgressBar(Widget* a_parent, AbstractProperty<int>& a_value)
	: AbstractValueWidget<int>(a_parent, a_value), progress(a_value)
{
	value = 30;
	// If any properties change, refresh the button
	connect(progress.valueChanged, this, &ProgressBar::refresh);
}


void ProgressBar::refresh(Variant& newValue)
{
	repaint();
}


void ProgressBar::sizeOptions(SizeOptions& a_sizeOptions)
{
	SizeOptions opts = { { 64, 26 },  { 128, 26 },  { 65536, 26 } };
	a_sizeOptions = opts;
}


void ProgressBar::paintEvent(PaintEvent& a_event)
{
	Painter p(this);

	uint32_t border = 0xbcbcbc;
	uint32_t progressCol = 0x06b025;
	uint32_t incompleteCol = 0xe6e6e6;
	int valueInPixels = (value() * width()) / 100;

	p.setBrush(border);
	p.drawRectangle(0, 0, width(), height());
	p.setBrush(progressCol);
	p.drawRectangle(1, 1, valueInPixels, height()-2);
	p.setBrush(incompleteCol);
	p.drawRectangle(valueInPixels+1, 1, width()-valueInPixels-2, height()-2);
}


END_NAMESPACE
