void Graphics::drawRoundedRectangle (const Rectangle<float>& r, const float cornerSize, const float lineThickness) const
{
    drawRoundedRectangle (r.getX(), r.getY(), r.getWidth(), r.getHeight(), cornerSize, lineThickness);
}
void TangoBlock::drawCell (cairo_t *cr, gint x, gint y)
{

	int i;
	cairo_pattern_t *pat = NULL;
	/* the following garbage is derived from the official tango style guide */
	const gdouble colours[8][3][3] = {
					  {{0.93725490196078431, 0.16078431372549021, 0.16078431372549021},
					   {0.8, 0.0, 0.0},
					   {0.64313725490196083, 0.0, 0.0}}, /* red */

					  {{0.54117647058823526, 0.88627450980392153, 0.20392156862745098},
					   {0.45098039215686275, 0.82352941176470584, 0.086274509803921567},
					   {0.30588235294117649, 0.60392156862745094, 0.023529411764705882}}, /* green */

					  {{0.44705882352941179, 0.62352941176470589, 0.81176470588235294},
					   {0.20392156862745098, 0.396078431372549, 0.64313725490196083},
					   {0.12549019607843137, 0.29019607843137257, 0.52941176470588236}}, /* blue */

					  {{0.93333333333333335, 0.93333333333333335, 0.92549019607843142},
					   {0.82745098039215681, 0.84313725490196079, 0.81176470588235294},
					   {0.72941176470588232, 0.74117647058823533, 0.71372549019607845}}, /* white */

					  {{0.9882352941176471, 0.9137254901960784, 0.30980392156862746},
					   {0.92941176470588238, 0.83137254901960789, 0.0},
					   {0.7686274509803922, 0.62745098039215685, 0.0}}, /* yellow */

					  {{0.67843137254901964, 0.49803921568627452, 0.6588235294117647},
					   {0.45882352941176469, 0.31372549019607843, 0.4823529411764706},
					   {0.36078431372549019, 0.20784313725490197, 0.4}}, /* purple */

					  {{0.9882352941176471, 0.68627450980392157, 0.24313725490196078},
					   {0.96078431372549022, 0.47450980392156861, 0.0},
					   {0.80784313725490198, 0.36078431372549019, 0.0}}, /* orange (replacing cyan) */

					  {{0.33, 0.34, 0.32},
					   {0.18, 0.2, 0.21},
					   {0.10, 0.12, 0.13}} /* grey */
					 };

	if (data[x][y].what == EMPTY)
		return;

	if (data[x][y].what == TARGET) {
		i = 7;
	} else {
		i = data[x][y].color;
		i = CLAMP (i, 0, 6);
	}

	if (usegrads) {
		 pat = cairo_pattern_create_linear (x+0.35, y, x+0.55, y+0.9);
		 cairo_pattern_add_color_stop_rgb (pat, 0.0, colours[i][0][0],
						   colours[i][0][1],
						   colours[i][0][2]);
		 cairo_pattern_add_color_stop_rgb (pat, 1.0, colours[i][1][0],
						   colours[i][1][1],
						   colours[i][1][2]);
		 cairo_set_source (cr, pat);
	} else {
		 cairo_set_source_rgb (cr, colours[i][0][0],
				       colours[i][0][1],
				       colours[i][0][2]);
	}

	drawRoundedRectangle (cr, x+0.05, y+0.05, 0.9, 0.9, 0.2);
	cairo_fill_preserve (cr);  /* fill with shaded gradient */


	if (usegrads)
		cairo_pattern_destroy(pat);
	cairo_set_source_rgb(cr, colours[i][2][0],
			     colours[i][2][1],
			     colours[i][2][2]);

	cairo_set_line_width (cr, 0.1);
	cairo_stroke (cr);  /* add darker outline */

	drawRoundedRectangle (cr, x+0.15, y+0.15, 0.7, 0.7, 0.08);
	if (data[x][y].what != TARGET) {
		if (usegrads) {
			pat = cairo_pattern_create_linear (x-0.3, y-0.3, x+0.8, y+0.8);
			switch (i) { /* yellow and white blocks need a brighter highlight */
			case 3:
			case 4:
				cairo_pattern_add_color_stop_rgba (pat, 0.0, 1.0,
								   1.0,
								   1.0,
								   1.0);
				cairo_pattern_add_color_stop_rgba (pat, 1.0, 1.0,
								   1.0,
								   1.0,
								   0.0);
				break;
			default:
				cairo_pattern_add_color_stop_rgba (pat, 0.0, 0.9295,
								   0.9295,
								   0.9295,
								   1.0);
				cairo_pattern_add_color_stop_rgba (pat, 1.0, 0.9295,
								   0.9295,
								   0.9295,
								   0.0);
				break;
			}
			cairo_set_source (cr, pat);
		} else {
			cairo_set_source_rgba (cr, 1.0,
					       1.0,
					       1.0,
					       0.35);
		}
	} else {  /* black preview block, use a much weaker highlight */
		cairo_set_source_rgba (cr, 1.0,
				       1.0,
				       1.0,
				       0.15);
	}
	cairo_stroke (cr);  /* add inner edge highlight */

	if (usegrads && (data[x][y].what != TARGET))
		cairo_pattern_destroy (pat);
}
void Graphics::drawRoundedRectangle (float x, float y, float width, float height,
                                     float cornerSize, float lineThickness) const
{
    drawRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize, lineThickness);
}
void ArduRCT_Graphics::_executeMacroCommand(ardurct_graphicsMacroCommand_t *mc, int16_t x, int16_t y, uint16_t scaleMul, uint16_t scaleDiv) {
    uint8_t group = mc->cmd & GRAPHICS_MACRO_CMD_GROUP_MASK;

    // presets
    if (group == GRAPHICS_MACRO_CMD_GROUP_PRESET) {
        if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_FOREGROUND) _mForegroundColor = mc->color;
        else if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_BACKGROUND) _mBackgroundColor = mc->color;
        else if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_THICKNESS) {
            _mThickness = mc->param[GRAPHICS_MACRO_PARAM_THICKNESS];
            _mIsThicknessScalable = mc->param[GRAPHICS_MACRO_PARAM_THICKNESS_IS_SCALABLE] != 0;
        } else if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_ERASE) fillScreen(_mBackgroundColor);
        else if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_FONT) {
            _mFontSize = mc->param[GRAPHICS_MACRO_PARAM_FONT_SIZE];
            _mIsFontBold = (mc->param[GRAPHICS_MACRO_PARAM_FONT_IS_BOLD] != 0);
            _mIsFontOverlay = (mc->param[GRAPHICS_MACRO_PARAM_FONT_IS_OVERLAY] != 0);
        } else if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_SCALE) {
            _mScaleMul = mc->param[GRAPHICS_MACRO_PARAM_SCALE_MUL];
            _mScaleDiv = 1;
            if ((mc->nbParams > 1) && (mc->param[GRAPHICS_MACRO_PARAM_SCALE_DIV] != 0)) _mScaleDiv = mc->param[GRAPHICS_MACRO_PARAM_SCALE_DIV];
        }
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.println("preset");    
#endif
        return;
    }
    
    int32_t sMul = _mScaleMul;
    sMul = sMul * (scaleMul == 0 ? 1 : scaleMul);
    int32_t sDiv = _mScaleDiv;
    sDiv = sDiv * (scaleDiv == 0 ? 1 : scaleDiv);
    int32_t sX = mc->param[GRAPHICS_MACRO_PARAM_X1];
    sX = sX * sMul/sDiv + x;
    int32_t sY = mc->param[GRAPHICS_MACRO_PARAM_Y1];        
    sY = sY * sMul/sDiv + y;        
    int32_t sThickness = _mThickness;
    if (_mIsThicknessScalable) sThickness = sThickness * sMul/sDiv;    
    
    // lines
    if (group == GRAPHICS_MACRO_CMD_GROUP_LINE) {
        int32_t sX2, sY2;
        if (mc->nbParams < 2) return;
        if (mc->cmd == GRAPHICS_MACRO_CMD_LINE) {
            if (mc->nbParams > 2) {
                sX2 = mc->param[GRAPHICS_MACRO_PARAM_X2];
                sX2 = x + sX2 * sMul/sDiv;
                sY2 = mc->param[GRAPHICS_MACRO_PARAM_Y2];
                sY2 = y + sY2 * sMul/sDiv;
                // p2 becomes the end point
                _mX = mc->param[GRAPHICS_MACRO_PARAM_X2];
                _mY = mc->param[GRAPHICS_MACRO_PARAM_Y2];
            } else {
                sX2 = sX;
                sY2 = sY;
                sX = _mX;
                sX = x + sX * sMul/sDiv;
                sY = _mY;    
                sY = y + sY * sMul/sDiv;    
                // p1 becomes the end point
                _mX = mc->param[GRAPHICS_MACRO_PARAM_X1];
                _mY = mc->param[GRAPHICS_MACRO_PARAM_Y1];
            }
        } else {
            // delta values
            if (mc->nbParams > 2) {
                // p2 = p1+delta
                sX2 = mc->param[GRAPHICS_MACRO_PARAM_X1] + mc->param[GRAPHICS_MACRO_PARAM_X2];
                sX2 = x + sX2 * sMul/sDiv;
                sY2 = mc->param[GRAPHICS_MACRO_PARAM_Y1] + mc->param[GRAPHICS_MACRO_PARAM_Y2];
                sY2 = y + sY2 * sMul/sDiv;
                // p1+delta becomes the end point
                _mX = mc->param[GRAPHICS_MACRO_PARAM_X1] + mc->param[GRAPHICS_MACRO_PARAM_X2];
                _mY = mc->param[GRAPHICS_MACRO_PARAM_Y1] + mc->param[GRAPHICS_MACRO_PARAM_Y2];
            } else {
                // p2 = old_point+delta
                sX2 = _mX + mc->param[GRAPHICS_MACRO_PARAM_X1];
                sX2 = x + sX2 * sMul/sDiv;
                sY2 = _mY + mc->param[GRAPHICS_MACRO_PARAM_Y1];
                sY2 = y + sY2 * sMul/sDiv;
                sX = _mX;
                sX = x + sX * sMul/sDiv;
                sY = _mY;    
                sY = y + sY * sMul/sDiv;    
                // old_point+delta  becomes the end point
                _mX += mc->param[GRAPHICS_MACRO_PARAM_X1];
                _mY += mc->param[GRAPHICS_MACRO_PARAM_Y1];
            }
            
        }
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("line "); Serial.print(sX); Serial.print(" "); Serial.print(sY); Serial.print(" "); 
            Serial.print(sX2); Serial.print(" "); Serial.println(sY2);
#endif
        drawLine(sX, sY, sX2, sY2, _mForegroundColor, sThickness, false);
        return;
    }    
    
    // arcs
    if (group == GRAPHICS_MACRO_CMD_GROUP_ARC) {
        if (mc->nbParams < 1) return;
        boolean reversed = (mc->cmd == GRAPHICS_MACRO_CMD_ARC_REVERSED) || (mc->cmd == GRAPHICS_MACRO_CMD_ARC_FILLED_REVERSED);
        int32_t sRadius = 0;
        if (mc->nbParams == 1) {
            sRadius = mc->param[GRAPHICS_MACRO_PARAM_ARC_1];
            sRadius = sRadius * sMul/sDiv;
            int32_t sArcStartX = _getArcEnd(sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], !reversed, true);
            int32_t sArcStartY = _getArcEnd(sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], !reversed, false);
            int32_t sArcEndX = _getArcEnd(sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], reversed, true);
            int32_t sArcEndY = _getArcEnd(sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], reversed, false);
            sX = _mX;
            sX = x + sX * sMul/sDiv - sArcStartX;
            sY = _mY;
            sY = y + sY * sMul/sDiv - sArcStartY;
            _mX += sArcEndX - sArcStartX;
            _mY += sArcEndY - sArcStartY;

        } else if (mc->nbParams == 3) {
            sRadius = mc->param[GRAPHICS_MACRO_PARAM_ARC_3];
            sRadius = sRadius * sMul/sDiv;
            _mX = mc->param[GRAPHICS_MACRO_PARAM_X1] + _getArcEnd(mc->param[GRAPHICS_MACRO_PARAM_ARC_3], mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], reversed, true);
            _mY = mc->param[GRAPHICS_MACRO_PARAM_Y1] + _getArcEnd(mc->param[GRAPHICS_MACRO_PARAM_ARC_3], mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], reversed, false);
        } else return;
        if ((mc->cmd == GRAPHICS_MACRO_CMD_ARC_REVERSED) || (mc->cmd == GRAPHICS_MACRO_CMD_ARC)) 
            drawArc(sX, sY, sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], _mForegroundColor, sThickness, false);
        else fillArc(sX, sY, sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], _mForegroundColor, false);            
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("arc "); Serial.print(sX); Serial.print(" "); Serial.print(sY); Serial.print(" "); Serial.println(sRadius);
#endif
        return;
    }
    
    // circles
    if (group == GRAPHICS_MACRO_CMD_GROUP_CIRCLE) {
        // we need at least 3 parameters
        if (mc->nbParams < 3) return;
        int32_t sRadius = mc->param[GRAPHICS_MACRO_PARAM_RADIUS];
        sRadius = sRadius * sMul/sDiv;
        if (mc->cmd == GRAPHICS_MACRO_CMD_CIRCLE) drawCircle(sX, sY, sRadius, _mForegroundColor, sThickness, false);
        else fillCircle(sX, sY, sRadius, _mForegroundColor, false);
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("circle "); Serial.print(sX); Serial.print(" "); Serial.print(sY); Serial.print(" "); Serial.println(sRadius);
#endif
        return;
    }
    
    // rectangles
    if (group == GRAPHICS_MACRO_CMD_GROUP_RECTANGLE) {
        // we need at least 4 parameters
        if (mc->nbParams < 4) return;
        int32_t sWidth = mc->param[GRAPHICS_MACRO_PARAM_WIDTH];
        sWidth = sWidth * sMul/sDiv;
        int32_t sHeight = mc->param[GRAPHICS_MACRO_PARAM_HEIGHT];
        sHeight = sHeight * sMul/sDiv;
        if (mc->cmd == GRAPHICS_MACRO_CMD_RECTANGLE) drawRectangle(sX, sY, sWidth, sHeight, _mForegroundColor, sThickness, false);
        else if (mc->cmd == GRAPHICS_MACRO_CMD_RECTANGLE_FILLED) fillRectangle(sX, sY, sWidth, sHeight, _mForegroundColor, false);
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("rectangle "); Serial.print(sX); Serial.print(" "); Serial.print(sY); Serial.print(" "); Serial.print(sWidth); Serial.print(" "); Serial.println(sHeight);
#endif
        // we need at least 1 more parameter to display the rounded rectangle
        if (mc->nbParams < 5) return;
        int32_t sRadius = mc->param[GRAPHICS_MACRO_PARAM_ROUNDING];
        sRadius = sRadius * sMul/sDiv;
        if (mc->cmd == GRAPHICS_MACRO_CMD_RECTANGLE_ROUNDED) drawRoundedRectangle(sX, sY, sWidth, sHeight, sRadius, _mForegroundColor, sThickness, false);
        else if (mc->cmd == GRAPHICS_MACRO_CMD_RECTANGLE_ROUNDED_FILLED) fillRoundedRectangle(sX, sY, sWidth, sHeight, sRadius, _mForegroundColor, false);
        return;
    }
    
    // triangles
    if (group == GRAPHICS_MACRO_CMD_GROUP_TRIANGLE) {
        // we need at least 6 parameters
        if (mc->nbParams < 6) return;
        int32_t sX2 = mc->param[GRAPHICS_MACRO_PARAM_X2];
        sX2 = x + sX2 * sMul/sDiv;
        int32_t sY2 = mc->param[GRAPHICS_MACRO_PARAM_Y2];
        sY2 = y + sY2 * sMul/sDiv;
        int32_t sX3 = mc->param[GRAPHICS_MACRO_PARAM_X3];
        sX3 = x + sX3 * sMul/sDiv;
        int32_t sY3 = mc->param[GRAPHICS_MACRO_PARAM_Y3];
        sY3 = y + sY3 * sMul/sDiv;
        if (mc->cmd == GRAPHICS_MACRO_CMD_TRIANGLE) drawTriangle(sX, sY, sX2, sY2, sX3, sY3, _mForegroundColor, sThickness, false);
        else if (mc->cmd == GRAPHICS_MACRO_CMD_TRIANGLE_FILLED) fillTriangle(sX, sY, sX2, sY2, sX3, sY3, _mForegroundColor, false);
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("triangle "); Serial.print(sX); Serial.print(" "); Serial.print(sY); Serial.print(" "); Serial.print(sX2); Serial.print(" "); Serial.print(sY2); 
            Serial.print(" "); Serial.print(sX3); Serial.print(" "); Serial.println(sY3);
#endif
        return;
    }
        
    // strings
    if (mc->cmd == GRAPHICS_MACRO_CMD_STRING) {
        if (mc->nbParams < 2) return;
        int bc = _backgroundColor;
        if (!_mIsFontOverlay) setBackgroundColor(_mBackgroundColor);
        drawString((char *)mc->text, sX, sY, _mForegroundColor, _mFontSize, _mIsFontBold, _mIsFontOverlay, false);
        if (!_mIsFontOverlay) setBackgroundColor(bc);
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("text "); Serial.println((char *)mc->text);
#endif

        return;
    }
    
    // executes
    if (group == GRAPHICS_MACRO_CMD_GROUP_EXECUTE) {
        if (mc->param[GRAPHICS_MACRO_PARAM_MACRO_NUMBER] >= GRAPHICS_MACRO_MAX_NUMBER) return;
        // read the length in the EEPROM allocation table
        int length = eeprom_read_uint8_t(mc->param[GRAPHICS_MACRO_PARAM_MACRO_NUMBER]);
        if (length == 0xFF) return;
        // read the EEPROM pointers table to get the start
        int start = GRAPHICS_MACRO_MAX_NUMBER + mc->param[GRAPHICS_MACRO_PARAM_MACRO_NUMBER] * GRAPHICS_MACRO_MAX_SIZE;
        // get the compressed macro
        uint8_t buffer[GRAPHICS_MACRO_MAX_SIZE];
        uint8_t i=0;
        while (i < length) {
            buffer[i] = eeprom_read_uint8_t(start+i);
            i++;
        }
        buffer[i] = 0;
        ardurct_graphicsMacroCommand_t emc;
        // uncompress the macro commands and execute them
        if (mc->cmd == GRAPHICS_MACRO_CMD_EXECUTE_WITH_RESET) _initializeMacros();
        i = 0;
        while (i < length) {
            int8_t len = _uncompressMacroCommand(buffer, i, &emc);
            if (len == GRAPHICS_MACRO_FORMAT_ERROR) return;
            _executeMacroCommand(&emc, x, y, scaleMul, scaleDiv);
            i = len;
        }
    }
}