#include "Painter.h"
#include "Window.h"
#include "Widget.h"
BEGIN_NAMESPACE
Widget::Widget(Widget* a_parent, bool a_layoutDirection, int a_x, int a_y, int a_width, int a_height)
: m_parent(a_parent), m_layoutDirection(a_layoutDirection)
{
layoutMargin = 5;
layoutSpacing = 5;
backgroundBrush = 0xF0F0F0;
m_windowX = m_windowY = 0;
m_watchingMouse = false;
setGeometry(a_x, a_y, a_width, a_height);
flags = WF_None;
if (a_parent)
a_parent->addChild(this);
}
Widget::~Widget()
{
Widget *w = (m_children.size()) ? m_children.front() : 0;
while (w)
{
delete w;
w = (m_children.size()) ? m_children.front() : 0;
}
if (m_parent)
m_parent->m_children.remove(this);
}
void Widget::addChild(Widget* a_widget)
{
m_children.push_back(a_widget);
if (m_parent)
m_parent->updateLayout();
else
updateLayout();
repaint();
}
void Widget::updateLayout()
{
// recalculate layout
int childCount = (int)m_children.size();
if (childCount)
{
SizeOptions childTotalSize;
Vector<SizeOptions> childSizes;
childSizes.resize(childCount);
memset(&childTotalSize, 0, sizeof(childTotalSize));
int i = 0;
for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it)
{
(*it)->sizeOptions(childSizes[i]);
childTotalSize.m_minimum.m_width += childSizes[i].m_minimum.m_width;
childTotalSize.m_minimum.m_height += childSizes[i].m_minimum.m_height;
childTotalSize.m_preferred.m_width += childSizes[i].m_preferred.m_width;
childTotalSize.m_preferred.m_height += childSizes[i].m_preferred.m_height;
childTotalSize.m_maximum.m_width += childSizes[i].m_maximum.m_width;
childTotalSize.m_maximum.m_height += childSizes[i].m_maximum.m_height;
i++;
}
int x = 0, y = 0, wid = width(), heig = height();
int extraSpaceForMarginsAndSpacing = (childCount - 1) * layoutSpacing() + layoutMargin() * 2;
if (m_layoutDirection)
{
// horz
if ( wid > childTotalSize.m_maximum.m_width )
{
// TODO: need to do padding horizontally
wid = childTotalSize.m_maximum.m_width;
}
if ( wid < childTotalSize.m_minimum.m_width )
{
// TODO: can't meet the constraint
wid = childTotalSize.m_minimum.m_width;
}
if ( wid < extraSpaceForMarginsAndSpacing )
{
// TODO: can't meet the constraint
wid = extraSpaceForMarginsAndSpacing;
}
int w = wid - extraSpaceForMarginsAndSpacing;
int h = heig - layoutMargin() * 2;
int remainder = w - childTotalSize.m_preferred.m_width;
Vector<int> proposedChildWidths;
Vector<bool> childWidthDone;
int childWidthDoneCount = 0;
proposedChildWidths.resize(childCount);
childWidthDone.resize(childCount);
for (int i = 0; i < childCount; i++)
{
proposedChildWidths[i] = childSizes[i].m_preferred.m_width;
childWidthDone[i] = false;
}
while (abs(remainder) > (childCount-childWidthDoneCount))
{
int distributedRemainder = remainder / (childCount-childWidthDoneCount);
for (int i = 0; i < childCount; i++)
{
if (!childWidthDone[i]) {
if (remainder < 0) {
if (proposedChildWidths[i] + distributedRemainder < childSizes[i].m_minimum.m_width)
{
proposedChildWidths[i] = childSizes[i].m_minimum.m_width;
childWidthDone[i] = true;
childWidthDoneCount++;
} else {
proposedChildWidths[i] += distributedRemainder;
}
} else {
if (proposedChildWidths[i] + distributedRemainder > childSizes[i].m_maximum.m_width)
{
proposedChildWidths[i] = childSizes[i].m_maximum.m_width;
childWidthDone[i] = true;
childWidthDoneCount++;
} else {
proposedChildWidths[i] += distributedRemainder;
}
}
}
}
int newTotalProposedWidth = 0;
for (int i = 0; i < childCount; i++)
newTotalProposedWidth += proposedChildWidths[i];
remainder = w - newTotalProposedWidth;
if (childWidthDoneCount >= childCount)
break;
}
int i = 0;
y = x = layoutMargin();
for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it, x += proposedChildWidths[i] + layoutSpacing(), i++)
(*it)->setGeometry(x, y, proposedChildWidths[i], h);
} else {
// vert
if ( heig > childTotalSize.m_maximum.m_height )
{
// TODO: need to do padding horizontally
heig = childTotalSize.m_maximum.m_height;
}
if ( heig < childTotalSize.m_minimum.m_height )
{
// TODO: can't meet the constraint
heig = childTotalSize.m_minimum.m_height;
}
if ( heig < extraSpaceForMarginsAndSpacing )
{
// TODO: can't meet the constraint
heig = extraSpaceForMarginsAndSpacing;
}
int h = heig - extraSpaceForMarginsAndSpacing;
int w = wid - layoutMargin() * 2;
int remainder = h - childTotalSize.m_preferred.m_height;
Vector<int> proposedChildHeight;
Vector<bool> childHeightDone;
int childHeightDoneCount = 0;
proposedChildHeight.resize(childCount);
childHeightDone.resize(childCount);
for (int i = 0; i < childCount; i++)
{
proposedChildHeight[i] = childSizes[i].m_preferred.m_height;
childHeightDone[i] = false;
}
while (abs(remainder) > (childCount-childHeightDoneCount))
{
int distributedRemainder = remainder / (childCount-childHeightDoneCount);
for (int i = 0; i < childCount; i++)
{
if (!childHeightDone[i]) {
if (remainder < 0) {
if (proposedChildHeight[i] + distributedRemainder < childSizes[i].m_minimum.m_height)
{
proposedChildHeight[i] = childSizes[i].m_minimum.m_height;
childHeightDone[i] = true;
childHeightDoneCount++;
} else {
proposedChildHeight[i] += distributedRemainder;
}
} else {
if (proposedChildHeight[i] + distributedRemainder > childSizes[i].m_maximum.m_height)
{
proposedChildHeight[i] = childSizes[i].m_maximum.m_height;
childHeightDone[i] = true;
childHeightDoneCount++;
} else {
proposedChildHeight[i] += distributedRemainder;
}
}
}
}
int newTotalProposedHeight = 0;
for (int i = 0; i < childCount; i++)
newTotalProposedHeight += proposedChildHeight[i];
remainder = h - newTotalProposedHeight;
if (childHeightDoneCount >= childCount)
break;
}
int i = 0;
y = x = layoutMargin();
for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it, y += proposedChildHeight[i] + layoutSpacing(), i++)
(*it)->setGeometry(x, y, w, proposedChildHeight[i]);
}
}
}
void Widget::sizeOptions(SizeOptions& a_sizeOptions)
{
a_sizeOptions.m_minimum.m_width = 0;
a_sizeOptions.m_minimum.m_height = 0;
a_sizeOptions.m_preferred.m_width = 256;
a_sizeOptions.m_preferred.m_height = 256;
a_sizeOptions.m_maximum.m_width = 65536;
a_sizeOptions.m_maximum.m_height = 65536;
//return;
int childCount = (int)m_children.size();
if (!childCount)
{
// TODO
// Bottom level widgets that have no children ought to implement sizeOptions
return;
}
SizeOptions childSizes;
if (m_layoutDirection)
{
// horiz
a_sizeOptions.m_minimum.m_height = 0;
a_sizeOptions.m_preferred.m_height = 0;
a_sizeOptions.m_maximum.m_height = 65536;
a_sizeOptions.m_minimum.m_width = 0;
a_sizeOptions.m_preferred.m_width = 0;
a_sizeOptions.m_maximum.m_width = 0;
for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it)
{
(*it)->sizeOptions(childSizes);
a_sizeOptions.m_minimum.m_width += childSizes.m_minimum.m_width;
a_sizeOptions.m_preferred.m_width += childSizes.m_preferred.m_width;
a_sizeOptions.m_maximum.m_width += childSizes.m_maximum.m_width;
a_sizeOptions.m_minimum.m_height = std::max(a_sizeOptions.m_minimum.m_height, childSizes.m_minimum.m_height);
a_sizeOptions.m_preferred.m_height += childSizes.m_preferred.m_height;
a_sizeOptions.m_maximum.m_height = std::min(a_sizeOptions.m_maximum.m_height, childSizes.m_maximum.m_height);
}
a_sizeOptions.m_preferred.m_height /= childCount; // average of preferred heights
int extraSpaceForMarginsAndSpacing = layoutMargin() * 2;
a_sizeOptions.m_minimum.m_height += extraSpaceForMarginsAndSpacing;
a_sizeOptions.m_preferred.m_height += extraSpaceForMarginsAndSpacing;
a_sizeOptions.m_maximum.m_height += extraSpaceForMarginsAndSpacing;
extraSpaceForMarginsAndSpacing += (childCount - 1) * layoutSpacing();
a_sizeOptions.m_minimum.m_width += extraSpaceForMarginsAndSpacing;
a_sizeOptions.m_preferred.m_width += extraSpaceForMarginsAndSpacing;
a_sizeOptions.m_maximum.m_width += extraSpaceForMarginsAndSpacing;
} else {
// vert
a_sizeOptions.m_minimum.m_width = 0;
a_sizeOptions.m_preferred.m_width = 0;
a_sizeOptions.m_maximum.m_width = 65536;
a_sizeOptions.m_minimum.m_height = 0;
a_sizeOptions.m_preferred.m_height = 0;
a_sizeOptions.m_maximum.m_height = 0;
for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it)
{
(*it)->sizeOptions(childSizes);
a_sizeOptions.m_minimum.m_height += childSizes.m_minimum.m_height;
a_sizeOptions.m_preferred.m_height += childSizes.m_preferred.m_height;
a_sizeOptions.m_maximum.m_height += childSizes.m_maximum.m_height;
a_sizeOptions.m_minimum.m_width = std::max(a_sizeOptions.m_minimum.m_width, childSizes.m_minimum.m_width);
a_sizeOptions.m_preferred.m_width += childSizes.m_preferred.m_width;
a_sizeOptions.m_maximum.m_width = std::min(a_sizeOptions.m_maximum.m_width, childSizes.m_maximum.m_width);
}
a_sizeOptions.m_preferred.m_width /= childCount; // average of preferred heights
int extraSpaceForMarginsAndSpacing = layoutMargin() * 2;
a_sizeOptions.m_minimum.m_width += extraSpaceForMarginsAndSpacing;
a_sizeOptions.m_preferred.m_width += extraSpaceForMarginsAndSpacing;
a_sizeOptions.m_maximum.m_width += extraSpaceForMarginsAndSpacing;
extraSpaceForMarginsAndSpacing += (childCount - 1) * layoutSpacing();
a_sizeOptions.m_minimum.m_height += extraSpaceForMarginsAndSpacing;
a_sizeOptions.m_preferred.m_height += extraSpaceForMarginsAndSpacing;
a_sizeOptions.m_maximum.m_height += extraSpaceForMarginsAndSpacing;
}
// ensure min is less than max and clamp the preferred to the min and max
if ( a_sizeOptions.m_maximum.m_height < a_sizeOptions.m_minimum.m_height )
a_sizeOptions.m_maximum.m_height = a_sizeOptions.m_minimum.m_height = (a_sizeOptions.m_maximum.m_height + a_sizeOptions.m_minimum.m_height) / 2;
if ( a_sizeOptions.m_maximum.m_width < a_sizeOptions.m_minimum.m_width )
a_sizeOptions.m_maximum.m_width = a_sizeOptions.m_minimum.m_width = (a_sizeOptions.m_maximum.m_width + a_sizeOptions.m_minimum.m_width) / 2;
if ( a_sizeOptions.m_preferred.m_height < a_sizeOptions.m_minimum.m_height )
a_sizeOptions.m_preferred.m_height = a_sizeOptions.m_minimum.m_height;
if ( a_sizeOptions.m_preferred.m_width < a_sizeOptions.m_minimum.m_width )
a_sizeOptions.m_preferred.m_width = a_sizeOptions.m_minimum.m_width;
if ( a_sizeOptions.m_preferred.m_height > a_sizeOptions.m_maximum.m_height )
a_sizeOptions.m_preferred.m_height = a_sizeOptions.m_maximum.m_height;
if ( a_sizeOptions.m_preferred.m_width > a_sizeOptions.m_maximum.m_width )
a_sizeOptions.m_preferred.m_width = a_sizeOptions.m_maximum.m_width;
}
void Widget::setGeometry(int a_x, int a_y, int a_width, int a_height)
{
m_windowX = m_x = a_x;
m_windowY = m_y = a_y;
m_goalWidth = a_width;
m_goalHeight = a_height;
if (m_parent)
{
m_windowX = m_parent->m_windowX + a_x;
m_windowY = m_parent->m_windowY + a_y;
updateTarget(); // update target buffer
}
updateLayout();
}
TimerId Widget::startTimer(int a_milliSeconds)
{
if (m_parent)
return m_parent->startTimer(a_milliSeconds);
return 0;
}
void Widget::killTimer(TimerId a_timerId)
{
if (m_parent)
m_parent->killTimer(a_timerId);
}
#include <stdio.h>
void Widget::eraseBackground(int i)
{
Painter p(this);
p.setBrush(backgroundBrush());
//printf("draw rect: %i %i %i %i\n", 0, 0, width(), height());
p.drawRectangle(0, 0, width(), height());
}
int Widget::width()
{
return targetBuffer().m_width;
}
int Widget::height()
{
return targetBuffer().m_height;
}
void Widget::sizeEvent(SizeEvent& a_event)
{
}
void Widget::paintEvent(PaintEvent& a_event)
{
eraseBackground();
}
void Widget::timerEvent(TimerEvent& a_event)
{
}
void Widget::keyEvent(KeyEvent& a_event)
{
}
void Widget::mouseEvent(MouseEvent& a_event)
{
}
void Widget::mouseEnterEvent(MouseEvent& a_event)
{
}
void Widget::mouseLeaveEvent(MouseEvent& a_event)
{
}
void Widget::wheelEvent(WheelEvent& a_event)
{
}
void Widget::updateTarget()
{
CUTE_PaintTarget& parentTarget = m_parent->targetBuffer();
m_target.m_width = clamp(parentTarget.m_width - m_x - 1, 0, m_goalWidth);
m_target.m_height = clamp(parentTarget.m_height - m_y - 1, 0, m_goalHeight);
m_target.m_pixels = parentTarget.m_pixels + m_y * (parentTarget.m_strideBytes/4) + m_x;
m_target.m_strideBytes = parentTarget.m_strideBytes;
m_target.m_format = parentTarget.m_format;
}
void Widget::repaint()
{
Rectangle rectangle = { { { 0, 0 } }, { { width(), height() } } };
update(rectangle);
}
void Widget::update(Rectangle& a_rectangle)
{
if (m_parent)
{
a_rectangle.m_x += m_x;
a_rectangle.m_y += m_y;
m_parent->update(a_rectangle); // This recursively calculates m_windowX and m_windowY
// When this gets up to the top level widget it will be a window object with overloaded update function
}
}
void Widget::event(Event& a_event)
{
a_event.m_commonEvent.reject();
switch (a_event.m_type)
{
case Event::ET_TimerEvent:
timerEvent(a_event.m_timerEvent);
break;
case Event::ET_PaintEvent:
if (flags() == WF_EraseBackground)
eraseBackground();
paintEvent(a_event.m_paintEvent);
break;
case Event::ET_SizeEvent:
if (m_parent)
updateTarget();
sizeEvent(a_event.m_sizeEvent);
break;
default:
break;
}
for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it)
{
(*it)->event(a_event);
if (a_event.m_commonEvent.m_accepted)
return;
}
bool mouseInside = false;
switch (a_event.m_type)
{
case Event::ET_MouseEvent:
mouseInside = ((a_event.m_mouseEvent.m_position.m_x > m_windowX)
&& (a_event.m_mouseEvent.m_position.m_y > m_windowY)
&& (a_event.m_mouseEvent.m_position.m_x < (m_windowX + width()))
&& (a_event.m_mouseEvent.m_position.m_y < (m_windowY + height())));
if (m_mouseEntered && !mouseInside)
{
mouseLeaveEvent(a_event.m_mouseEvent);
}
if (!m_mouseEntered && mouseInside)
{
mouseEnterEvent(a_event.m_mouseEvent);
}
m_mouseEntered = mouseInside;
if (mouseInside || m_watchingMouse )
{
a_event.m_mouseEvent.m_x -= m_windowX;
a_event.m_mouseEvent.m_y -= m_windowY;
a_event.m_mouseEvent.m_oldX -= m_windowX;
a_event.m_mouseEvent.m_oldY -= m_windowY;
if (!a_event.m_mouseEvent.m_oldButtons && a_event.m_mouseEvent.m_buttons)
m_watchingMouse = true;
if (!a_event.m_mouseEvent.m_buttons)
m_watchingMouse = false;
mouseEvent(a_event.m_mouseEvent);
a_event.m_mouseEvent.m_x += m_windowX;
a_event.m_mouseEvent.m_y += m_windowY;
a_event.m_mouseEvent.m_oldX += m_windowX;
a_event.m_mouseEvent.m_oldY += m_windowY;
}
break;
case Event::ET_WheelEvent:
wheelEvent(a_event.m_wheelEvent);
break;
case Event::ET_KeyEvent:
keyEvent(a_event.m_keyEvent);
break;
default:
break;
}
}
END_NAMESPACE