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