Newer
Older
Import / research / ui / toolkit / src / CommonWidgets.cpp

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 "Window.h"
#include "Painter.h"
#include "Property.h"
#include "../resources/Images.h"


BEGIN_NAMESPACE


namespace details
{
	Property<bool>   g_unassignedBool;
	Property<String> g_unassignedString;
}


const int s_defaultFontSize = 7;
const char* s_defaultFontFamily = Font::UIFont;// "Segoe UI"; // "MS Shell Dlg 2"  "MS Shell Dlg"


enum ButtonState
{
	DisabledState,
	NormalState,
	DefaultButtonState,
	HoverState,
	DepressedState
};


CheckableWidget::CheckableWidget(Object* a_parent, const char* a_name)
	: CheckableWidget(ObjectPtrCast<Widget>(a_parent), details::g_unassignedBool)
{
}


CheckableWidget::CheckableWidget(Widget* a_parent, AbstractProperty<bool>& a_value)
	: AbstractValueWidget<bool>(a_parent, a_value), checked(a_value)
{
}


HBox::HBox(Widget* a_parent)
	: BaseT(a_parent, "HBox", LP_Horizontal)
{
}


VBox::VBox(Widget* a_parent)
	: BaseT(a_parent, "VBox", LP_Vertical)
{
}


GroupBoxSpacer::GroupBoxSpacer(Widget* a_parent)
	: Widget(a_parent, "GroupBox", LP_Vertical)
{
}


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.value();
	p.setPen(0x000000);
	p.setFontFamily(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);
	p.drawText(15, 0, str.data());
	Size textSize = p.textExtents(str.data());
  int w = textSize.m_width;
  int h = textSize.m_height;

	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, "HSpace", LP_Horizontal)
{
}


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, "VSpace", LP_Vertical)
{
}


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)
{
	Painter p(this);
	p.setFontFamily(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);
	Size textSize = p.textExtents(text().data());
	SizeOptions opts = { { textSize.m_width + 4, 20 },  { textSize.m_width * 2 + 4, 32 },  { textSize.m_width * 3 + 4, 48 } };
	//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(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);
	p.drawText(1, 1, text().data());
}


Button::Button(Widget* a_parent, const char* a_text)
	: Widget(a_parent, "Button", LP_Vertical, 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()
{
	repaint();
}


void Button::sizeOptions(SizeOptions& a_sizeOptions)
{
	Painter p(this);
	p.setFontFamily(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);
	Size textSize = p.textExtents(text().data());
	SizeOptions opts = { { textSize.m_width + 8, 20 },  { textSize.m_width * 2 + 8, 21 },  { textSize.m_width * 3 + 8, 24 } };
	//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);
	Gradient gradient = { LINEAR_GRADIENT, { { bgA1Cols[(int)state], 0.0 }, { bgB1Cols[(int)state], 1.0 } }, 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(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);

	Size textSize = p.textExtents(text().data());
	p.drawText((width() - textSize.m_width) / 2, (height() - textSize.m_height) / 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(m_mouseDownInButton);
	}

	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 const GimpExportedImage g_scrollUpHandleImage =
{
  7, 6, 4,
  "\355\354\355\377\355\354\355\377\355\354\355\377QQQ\377\355\354\355\377\355"
  "\354\355\377\355\354\355\377\355\354\355\377\355\354\355\377QQQ\377QQQ\377"
  "QQQ\377\355\354\355\377\355\354\355\377\355\354\355\377QQQ\377QQQ\377QQQ"
  "\377QQQ\377QQQ\377\355\354\355\377QQQ\377QQQ\377QQQ\377\355\354\355\377Q"
  "QQ\377QQQ\377QQQ\377QQQ\377QQQ\377\355\354\355\377\355\354\355\377\355\354"
  "\355\377QQQ\377QQQ\377QQQ\377\355\354\355\377\355\354\355\377\355\354\355"
  "\377\355\354\355\377\355\354\355\377QQQ\377",
};


static const GimpExportedImage g_scrollDownHandleImage =
{
  7, 6, 4,
  "QQQ\377\355\354\355\377\355\354\355\377\355\354\355\377\355\354\355\377\355"
  "\354\355\377QQQ\377QQQ\377QQQ\377\355\354\355\377\355\354\355\377\355\354"
  "\355\377QQQ\377QQQ\377QQQ\377QQQ\377QQQ\377\355\354\355\377QQQ\377QQQ\377"
  "QQQ\377\355\354\355\377QQQ\377QQQ\377QQQ\377QQQ\377QQQ\377\355\354\355\377"
  "\355\354\355\377\355\354\355\377QQQ\377QQQ\377QQQ\377\355\354\355\377\355"
  "\354\355\377\355\354\355\377\355\354\355\377\355\354\355\377QQQ\377\355\354"
  "\355\377\355\354\355\377\355\354\355\377",
};


/*
static const GimpExportedImage g_windowResizeHandleImage =
{
  8, 8, 4,
  "\355\354\355\377\355\354\355\377\355\354\355\377\355\354\355\377\355\354"
  "\355\377\355\354\355\377\264\264\264\377\264\264\264\377\355\354\355\377"
  "\355\354\355\377\355\354\355\377\355\354\355\377\355\354\355\377\355\354"
  "\355\377\264\264\264\377\264\264\264\377\355\354\355\377\355\354\355\377"
  "\355\354\355\377\355\354\355\377\355\354\355\377\355\354\355\377\355\354"
  "\355\377\355\354\355\377\355\354\355\377\355\354\355\377\355\354\355\377"
  "\264\264\264\377\264\264\264\377\355\354\355\377\264\264\264\377\264\264"
  "\264\377\355\354\355\377\355\354\355\377\355\354\355\377\264\264\264\377"
  "\264\264\264\377\355\354\355\377\264\264\264\377\264\264\264\377\355\354"
  "\355\377\355\354\355\377\355\354\355\377\355\354\355\377\355\354\355\377"
  "\355\354\355\377\355\354\355\377\355\354\355\377\264\264\264\377\264\264"
  "\264\377\355\354\355\377\264\264\264\377\264\264\264\377\355\354\355\377"
  "\264\264\264\377\264\264\264\377\264\264\264\377\264\264\264\377\355\354"
  "\355\377\264\264\264\377\264\264\264\377\355\354\355\377\264\264\264\377"
  "\264\264\264\377",
};
*/


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);
}


static void drawXBitmapImage(Painter& p, int x, int y, const XBitmapImage& img, bool a_verticalFlip = false)
{
  uint32_t *tmpBuf = new uint32_t[img.width * img.height];
  int srcBytesPerRow = ((img.width + 15) / 16) * 2;
  uint32_t *tmpBufPix = tmpBuf;
  uint8_t *srcLine = (uint8_t*)img.pixel_data;
  if (a_verticalFlip)
  {
    srcLine += (img.height - 1) * srcBytesPerRow;
    srcBytesPerRow *= -1;
  }
  for (int j = 0; j < img.height; j++, srcLine += srcBytesPerRow)
  {
    for (int i = 0; i < img.width; i++, tmpBufPix++)
    {
      *tmpBufPix = (srcLine[i / 8] & (1U << (i & 7))) ? 0xFF000000 : 0x00FFFFFF;
    }
  }
  p.drawPixelBuffer(x, y, (uint8_t*)tmpBuf, img.width, img.height, 4);
  delete[] tmpBuf;
}


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;

  // cause a resize on text property changed
  //connect(text.valueChanged, this, &CheckBox::onTextChanged);
  //updateLayout();
}


void CheckBox::onTextChanged(String a_text)
{
  //fprintf(stderr, "onTextChanged\n");
  //updateLayout();
}


// TODO: cause a resize on text property changed
void CheckBox::sizeOptions(SizeOptions& a_sizeOptions)
{
	Painter p(this);
	p.setFontFamily(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);
	Size textSize = p.textExtents(text().data());
	SizeOptions opts = { { textSize.m_width*2 + 32, 20 },  { textSize.m_width * 2 + 32, 21 },  { textSize.m_width * 3 + 32, 24 } };
	//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())
  {
    if (c_useRetina)
		  drawXBitmapImage(p, 4, 4, CheckBoxTick);
    else
		  drawGimpExportedImage(p, 4, 4, g_checkBoxTickImage);
  }

	p.setPen(textCols[(int)state]);
	p.setFontFamily(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);
	p.drawText(18, 2, text().data());

	if (drawFocusRect)
	{
		Size textSize = p.textExtents(text().data());
		p.drawFocusRectangle(18, 2, textSize.m_width + 1, textSize.m_height);
	}
}


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)
{
	Painter p(this);
	p.setFontFamily(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);
	Size textSize = p.textExtents(text().data());
	SizeOptions opts = { { textSize.m_width + 32, 20 },  { textSize.m_width * 2 + 32, 21 },  { textSize.m_width * 3 + 32, 24 } };
	//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, 7, 7);
	}

	p.setPen(textCols[(int)state]);
	p.setFontFamily(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);
	p.drawText(18, 2, text().data());

	if (drawFocusRect)
	{
		Size textSize = p.textExtents(text().data());
		p.drawFocusRectangle(18, 2, textSize.m_width + 1, textSize.m_height);
	}
}


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::refresh2);
	connect(disabled.valueChanged, this, &LineEdit::refresh2);
	connect(hasFocus.valueChanged, this, &LineEdit::refresh2);

	m_timerId = startTimer(500);
}


void LineEdit::sizeOptions(SizeOptions& a_sizeOptions)
{
  // TODO: Not sure about the idea of resizing the lineedit based on the text size - might be ugly during editing
  //       Probably should size based on a max-chars property and then have a horizontal scrolling of the text buffer
	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 ( (int(a_event.m_key) >= 'a' && int(a_event.m_key) <= 'z') 
         || (int(a_event.m_key) >= 'A' && int(a_event.m_key) <= 'Z') 
         || (int(a_event.m_key) >= '0' && int(a_event.m_key) <= '9') )
	{
		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(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);

  Size textSize = p.textExtents(text().data());
	p.drawText(6, (height() - textSize.m_height) / 2, text().data());

	textSize = p.textExtents(text().toUtf8().substr(0, m_carrotPosition).c_str());
	int carrotX = textSize.m_width;
  // carrotX += 6;
  carrotX -= 2;
	if ( m_carrotOn )
		p.drawLine(carrotX, 4, carrotX, 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;
}



SliderR::SliderR(Widget* a_parent, AbstractProperty<Range>& a_sliderValue)
	: AbstractValueWidget<Range>(a_parent, a_sliderValue)
{
	disabled = false;
	m_mouseOverSliderHandle = false;
	m_mouseDownInSliderHandle = false;
	hasFocus = false;
}


void SliderR::sizeOptions(SizeOptions& a_sizeOptions)
{
	SizeOptions opts = { { 32, 21 },  { 128, 21 },  { 65536, 21 } };
	a_sizeOptions = opts;
}


void SliderR::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 = sliderHandleRectangle().m_x;
	p.setBrush(edgeCols[(int)state]);
	p.drawRectangle(pixelPos, 0, sliderWidth(), height());
	Gradient gradient = { LINEAR_GRADIENT, { { bgA1Cols[(int)state], 0.0 }, { bgB1Cols[(int)state], 1.0 } }, 0, { { 1, 0, 1 } } };
	gradient.m_data.m_linear.m_y2 = height() - 1;
	p.drawGradient(pixelPos+1, 1, gradient, sliderWidth() - 2, height() - 2);
}


int SliderR::sliderWidth()
{
  return 10;
}


Rectangle SliderR::sliderHandleRectangle()
{
  int pixelPos = (int(value()) * (width() - sliderWidth())) >> 16;
	Rectangle rect = { {{ pixelPos, 0 }}, {{ sliderWidth(), height() }} };
	return rect;
}


void SliderR::mouseLeaveEvent(MouseEvent& a_event)
{
	m_mouseOverSliderHandle = false;
	repaint();
}


void SliderR::mouseEvent(MouseEvent& a_event)
{
	m_mouseOverSliderHandle = isPointInsideRectangle(a_event.m_position, sliderHandleRectangle());

	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() - sliderWidth());
		int64_t newValue = (int(value()) * (width() - sliderWidth())) + ((a_event.m_x - a_event.m_oldX) << 16); // - 10
    Range r = value();
    r.setValue(clamp<int>(newValue / (width() - sliderWidth()), 0, (1<<16)));
    value = r;
	}

	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();
}




Slider::Slider(Widget* a_parent, AbstractProperty<int>& a_sliderValue)
	: AbstractValueWidget<int>(a_parent, a_sliderValue)
{
	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 = sliderHandleRectangle().m_x;
	p.setBrush(edgeCols[(int)state]);
	p.drawRectangle(pixelPos, 0, sliderWidth(), height());
	Gradient gradient = { LINEAR_GRADIENT, { { bgA1Cols[(int)state], 0.0 }, { bgB1Cols[(int)state], 1.0 } }, 0, { { 1, 0, 1 } } };
	gradient.m_data.m_linear.m_y2 = height() - 1;
	p.drawGradient(pixelPos+1, 1, gradient, sliderWidth() - 2, height() - 2);
}


int Slider::sliderWidth()
{
  return 10;
}


Rectangle Slider::sliderHandleRectangle()
{
  int pixelPos = (value() * (width() - sliderWidth())) >> 16;
	Rectangle rect = { {{ pixelPos, 0 }}, {{ sliderWidth(), 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, sliderHandleRectangle());

	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() - sliderWidth());
		int64_t newValue = (value() * (width() - sliderWidth())) + ((a_event.m_x - a_event.m_oldX) << 16); // - 10
		value = clamp<int>(newValue / (width() - sliderWidth()), 0, (1<<16));
	}

	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();
}


ScrollBar::ScrollBar(Widget* a_parent, AbstractProperty<int>& a_scrollValue)
	: AbstractValueWidget<int>(a_parent, a_scrollValue)
{
	value = 50;
	minValue = 0;
	maxValue = 100;
  proportional = true;
  pageSize = 20;
  lineSize = 1;
	disabled = false;
	hasFocus = false;
}


void ScrollBar::sizeOptions(SizeOptions& a_sizeOptions)
{
	SizeOptions opts = { { 17, 64 },  { 17, 128 },  { 17, 65536 } };
	a_sizeOptions = opts;
}


void ScrollBar::paintEvent(PaintEvent& a_event)
{
	Painter p(this);
/*
	ButtonState state = NormalState;
	if (disabled())
		state = DisabledState;
	//	state = DepressedState;
	//	state = HoverState;
	else if (hasFocus())
		state = DefaultButtonState;
*/

  p.setBrush(0xf0f0f0);
	p.drawRectangle(0, 0, width(), height());
  p.setBrush(0xe8e8e8);
	p.drawRectangle(getUpHandleRectangle());
	p.drawRectangle(getDownHandleRectangle());
	p.setBrush(0xd9d9d9);
	p.drawRectangle(getScrollHandleRectangle());

  if (c_useRetina) {
    drawXBitmapImage(p, 3, 3, ScrollBarArrow);
    drawXBitmapImage(p, 3, height()-3-7, ScrollBarArrow, true);
  } else {
    drawGimpExportedImage(p, 5, 5, g_scrollUpHandleImage);
    drawGimpExportedImage(p, 5, height()-5-7, g_scrollDownHandleImage);
  }
}


Rectangle ScrollBar::getUpHandleRectangle()
{
	Rectangle rect = { {{ 0, 0 }}, {{ width(), 17 }} };
	return rect;
}


Rectangle ScrollBar::getDownHandleRectangle()
{
	Rectangle rect = { {{ 0, height()-17 }}, {{ width(), 17 }} };
	return rect;
}


int ScrollBar::getScrollBarHandleHeight()
{
  const int minScrollButtonHeight = 17;
  int scrollButtonHeight = minScrollButtonHeight;
  if (proportional())
  {
    int arrowHeight = 17;
    int valueRange = maxValue() - minValue();
    scrollButtonHeight = ((height() - arrowHeight * 2) * pageSize()) / (valueRange + pageSize());
    scrollButtonHeight = std::max<int>(minScrollButtonHeight, scrollButtonHeight);
  }
  return scrollButtonHeight;
}


Rectangle ScrollBar::getScrollHandleRectangle()
{
  int arrowHeight = 17;
  int scrollButtonHeight = getScrollBarHandleHeight();
  int scrollableDistance = height() - (scrollButtonHeight + arrowHeight*2);
  int valueRange = maxValue() - minValue();
  int pos = (scrollableDistance * (value() - minValue())) / valueRange; 
	Rectangle rect = { {{ 0, pos + arrowHeight }}, {{ width(), scrollButtonHeight }} };
	return rect;
}


void ScrollBar::mouseEvent(MouseEvent& a_event)
{
  if (a_event.m_buttons)
  {
    Rectangle scrollBarRect = getScrollHandleRectangle();
    if (!a_event.m_oldButtons)
    {
      bool mouseOverUpButton = isPointInsideRectangle(a_event.m_position, getUpHandleRectangle());
      bool mouseOverDownButton = isPointInsideRectangle(a_event.m_position, getDownHandleRectangle());
      bool mouseDownInScrollBarHandle = isPointInsideRectangle(a_event.m_position, scrollBarRect);
      if (mouseOverUpButton)
      {
        printf("line up\n");
        value = clamp<int>(value() - lineSize(), minValue(), maxValue());
        // TODO: need to start a time event to auto-repeat the scrolling
      }
      else if (mouseOverDownButton)
      {
        printf("line down\n");
        value = clamp<int>(value() + lineSize(), minValue(), maxValue());
        // TODO: need to start a time event to auto-repeat the scrolling
      }
      else if (mouseDownInScrollBarHandle)
      {
        int arrowHeight = 17;
        int scrollButtonHeight = getScrollBarHandleHeight();
        int scrollableDistance = height() - (scrollButtonHeight + arrowHeight*2);
        int valueRange = maxValue() - minValue();
        m_moveStartValue = value() * scrollableDistance - a_event.m_y * valueRange;
        m_onHandle = true;
      }
      else
      {
        int bottom = scrollBarRect.m_y + scrollBarRect.m_height + 1;
        Rectangle pgUpArea   = { {{ 0, 18 }}, {{ width(), scrollBarRect.m_y - 18 }} };
        Rectangle pgDownArea = { {{ 0, bottom }}, {{ width(), height() - 18 - bottom }} };
        bool mouseOverPgUpButton = isPointInsideRectangle(a_event.m_position, pgUpArea);
        bool mouseOverPgDownButton = isPointInsideRectangle(a_event.m_position, pgDownArea);
        if (mouseOverPgUpButton)
        {
          printf("page up\n");
          value = clamp<int>(value() - pageSize(), minValue(), maxValue());
        }
        else if (mouseOverPgDownButton)
        {
          printf("page down\n");
          value = clamp<int>(value() + pageSize(), minValue(), maxValue());
        }
      }
    }

    if (m_onHandle)
    {
      // Move the scrollbar if mouse down on the scrollbar
      int arrowHeight = 17;
      int scrollButtonHeight = getScrollBarHandleHeight();
      int scrollableDistance = height() - (scrollButtonHeight + arrowHeight*2);
      int valueRange = maxValue() - minValue();
      int newPixelPos = m_moveStartValue + a_event.m_y * valueRange;
      int newValue = clamp<int>(newPixelPos / scrollableDistance, minValue(), maxValue());
      value = newValue;
    }

    repaint();
  }
  else
  {
    m_onHandle = false;
  }
}


// TODO: tabbars
// TODO: scrollbars
// TODO: list / tree / table widgets
ListView::ListView(Widget* a_parent, ItemList& a_list)
	: Widget(a_parent), m_list(a_list)
{
}


void ListView::keyEvent(KeyEvent& a_event)
{
  a_event.accept(); // eat the event so nothing behind this view will get it
}


void ListView::paintEvent(PaintEvent& a_event)
{
	Painter p(this);

	p.setBrush(0x222222);
	p.drawRectangle(0, 0, width(), height());

	p.setBrush(0xf7f7f7);
	p.drawRectangle(1, 1, width()-2, height()-2);

	p.setFontFamily(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);

  for (size_t i = 0; i < m_list.m_items.size(); i++)
  {
    Rectangle itemRect{ {{ 1, 1 + int(i)*20 }}, {{ width()-2, 20 }} };
    bool mouseOverItem = isPointInsideRectangle(m_mousePos, itemRect);
    p.setBrush(mouseOverItem ? 0x3a9bfc /*5555ee*/ : 0xf7f7f7); // 58, 155, 252
	  p.setPen(mouseOverItem ? 0xf7f7f7 : 0x000000);
    p.drawRectangle(itemRect.m_x, itemRect.m_y, itemRect.m_width, itemRect.m_height);
    String text = m_list.m_items[i];
    //Size textSize = p.textExtents(text.c_str());
    p.drawText(4, 4 + i * 20, text.c_str());
  }
}


void ListView::mouseEnterEvent(MouseEvent& a_event)
{
  a_event.accept(); // eat the event so nothing behind this view will get it
	m_mouseOver = true;
	repaint();
}


void ListView::mouseLeaveEvent(MouseEvent& a_event)
{
  a_event.accept(); // eat the event so nothing behind this view will get it
	m_mouseOver = false;
	repaint();
}


void ListView::mouseEvent(MouseEvent& a_event)
{
  a_event.accept(); // eat the event so nothing behind this view will get it
	if (m_mouseOver)
    m_mousePos = a_event.m_position;

	if (m_mouseOver)
		repaint();

	if (a_event.m_oldButtons && !a_event.m_buttons)
	{
		// mouse up while in button
		if (m_mouseOver) {
  printf("list view mouse event\n");
      for (size_t i = 0; i < m_list.m_items.size(); i++)
      {
        Rectangle itemRect{ {{ 1, 1 + int(i)*20 }}, {{ width()-2, 20 }} };
        if (isPointInsideRectangle(m_mousePos, itemRect))
        {
          m_list.currentIndex = i;
          m_list.text = m_list.m_items[i];
          break;
        }
      }
      int idx = m_list.currentIndex.value();
      m_list.changed(idx);
  printf("end list view mouse event\n");
    }
	}
}


ComboBox::ComboBox(Widget* a_parent, AbstractProperty<int>& a_value)
	: AbstractValueWidget<int>(a_parent, a_value), m_list(a_value)
{
	value = 0;
	m_list.text = "";
	m_mouseDownInButton = false;
	m_mouseOverButton = false;
	disabled = false;
	isDefault = false;
	hasFocus = false;

	// If any properties change, refresh the button
	connect(m_list.text.valueChanged, this, &ComboBox::refresh2);
	connect(disabled.valueChanged, this, &ComboBox::refresh2);
	connect(isDefault.valueChanged, this, &ComboBox::refresh2);
	connect(hasFocus.valueChanged, this, &ComboBox::refresh2);
	connect(m_list.changed, this, &ComboBox::listChanged);
}


void ComboBox::listChanged()
{
  if (m_dropDownMenu)
    m_dropDownMenu->delayDelete();
  m_dropDownMenu = nullptr;
  m_active = false;
	repaint();
}


void ComboBox::refresh2()
{
	repaint();
}


void ComboBox::sizeOptions(SizeOptions& a_sizeOptions)
{
	int len = (int)m_list.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;

  // TODO: avoid all the hard-coding and drive all the paintEvent code for all the widgets
  // from css / data files with styling info - spacing, sizes, colors, pixmaps, etc.
	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());

	Gradient gradient = { LINEAR_GRADIENT, { { bgA1Cols[(int)state], 0.0 }, { bgB1Cols[(int)state], 1.0 } }, 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(s_defaultFontFamily);
	p.setFontSize(s_defaultFontSize);

	Size textSize = p.textExtents(m_list.text().data());
	p.drawText(4, 2 + (height() - textSize.m_height) / 2, m_list.text().data());

  if (c_useRetina)
    drawXBitmapImage(p, width() - 14, (height() - 9) / 2, ComboBoxArrow);
  else
    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()) {
      if (!m_active)
      {
        m_active = true;

        /*
        // This is other approach trying to get work where we create a floating window
        // This requires creating a window with no titlebar, border etc.
        // This is more standard approach probably, but requires making it move as
        // the parent window moves and resizes so is more tricky, but it will allow the
        // combobox to spill out beyond the borders of the window it is in.
        //
        // Problem of it moving with the window can be solved by closing the combo once
        // any click outside the combo or start to move the window.
        //
        Rectangle r = worldGeometry();
        r.m_width = width();
        r.m_height = m_items.size() * 15;
        m_dropDownMenu = Window("Combo", true, WF_NoTitle);
        m_dropDownMenu->setWorldGeometry(r);
        */

        // This approach is creating a widget that doesn't follow the normal layout,
        // it is unmanaged. This unmanaged area needs to be drawn last, so extra event
        // logic is needed. It also means the mouse event handling needs to handle that too.
        // The drop down menu can't spill outside the window borders, so it looks non-standard.
        if (!m_dropDownMenu)
        {
          int w = width() + 40;
          int h = 2 + m_list.m_items.size() * 10;
          h *= 2; // ?? why do I need to double it? I think DrawText issue
          h--;

#define ENABLE_NATIVE_POPUPS 0
#if ENABLE_NATIVE_POPUPS
          // This is how it is done using the windowing system to create a native popup window
          m_dropDownMenu = new Window("Combo", false, WF_NoTitle);
	        Rectangle worldPos = worldGeometry();
          printf("pos:  %i  %i\n", worldPos.m_x + 10, worldPos.m_y + height());
          m_dropDownMenu->setNewSize(w, h);
          ((Window*)m_dropDownMenu)->setPosition(worldPos.m_x + 10, worldPos.m_y - height());
          //m_dropDownMenu->setGeometry(worldPos.m_x + 10, worldPos.m_y + height(), w, h);
#else
          // This is how it can be done as a region in the parent window
          m_dropDownMenu = new Widget(this, "Combo", LP_Unmanaged, -10, height(), w, h);
#endif

          m_dropDownMenu->layoutMargin = 0;
          new ListView(m_dropDownMenu, m_list);
          m_dropDownMenu->setGeometry(0, height(), w, h);
        }

        activated();
      } else {
        m_active = false;
        //if (m_dropDownMenu)
        //  m_dropDownMenu->delayDelete();
        delete m_dropDownMenu;
        m_dropDownMenu = nullptr;
        
        deactivated();
      }
    }
	}

	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_list.addItem(a_itemText);
}


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_list.m_items.size() - 1);
	m_list.text = m_list.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::refresh2);
}


void ProgressBar::refresh2()
{
	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