// 13 october 2015 #include #include #include #include #include "../../ui.h" uiWindow *mainwin; uiArea *histogram; uiAreaHandler handler; uiSpinbox *datapoints[10]; uiColorButton *colorButton; int currentPoint = -1; // some metrics #define xoffLeft 20 /* histogram margins */ #define yoffTop 20 #define xoffRight 20 #define yoffBottom 20 #define pointRadius 5 // helper to quickly set a brush color static void setSolidBrush(uiDrawBrush *brush, uint32_t color, double alpha) { uint8_t component; brush->Type = uiDrawBrushTypeSolid; component = (uint8_t) ((color >> 16) & 0xFF); brush->R = ((double) component) / 255; component = (uint8_t) ((color >> 8) & 0xFF); brush->G = ((double) component) / 255; component = (uint8_t) (color & 0xFF); brush->B = ((double) component) / 255; brush->A = alpha; } // and some colors // names and values from https://msdn.microsoft.com/en-us/library/windows/desktop/dd370907%28v=vs.85%29.aspx #define colorWhite 0xFFFFFF #define colorBlack 0x000000 #define colorDodgerBlue 0x1E90FF static void pointLocations(double width, double height, double *xs, double *ys) { double xincr, yincr; int i, n; xincr = width / 9; // 10 - 1 to make the last point be at the end yincr = height / 100; for (i = 0; i < 10; i++) { // get the value of the point n = uiSpinboxValue(datapoints[i]); // because y=0 is the top but n=0 is the bottom, we need to flip n = 100 - n; xs[i] = xincr * i; ys[i] = yincr * n; } } static uiDrawPath *constructGraph(double width, double height, int extend) { uiDrawPath *path; double xs[10], ys[10]; int i; pointLocations(width, height, xs, ys); path = uiDrawNewPath(uiDrawFillModeWinding); uiDrawPathNewFigure(path, xs[0], ys[0]); for (i = 1; i < 10; i++) uiDrawPathLineTo(path, xs[i], ys[i]); if (extend) { uiDrawPathLineTo(path, width, height); uiDrawPathLineTo(path, 0, height); uiDrawPathCloseFigure(path); } uiDrawPathEnd(path); return path; } static void graphSize(double clientWidth, double clientHeight, double *graphWidth, double *graphHeight) { *graphWidth = clientWidth - xoffLeft - xoffRight; *graphHeight = clientHeight - yoffTop - yoffBottom; } static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p) { uiDrawPath *path; uiDrawBrush brush; uiDrawStrokeParams sp; uiDrawMatrix m; double graphWidth, graphHeight; double graphR, graphG, graphB, graphA; // fill the area with white setSolidBrush(&brush, colorWhite, 1.0); path = uiDrawNewPath(uiDrawFillModeWinding); uiDrawPathAddRectangle(path, 0, 0, p->AreaWidth, p->AreaHeight); uiDrawPathEnd(path); uiDrawFill(p->Context, path, &brush); uiDrawFreePath(path); // figure out dimensions graphSize(p->AreaWidth, p->AreaHeight, &graphWidth, &graphHeight); // clear sp to avoid passing garbage to uiDrawStroke() // for example, we don't use dashing memset(&sp, 0, sizeof (uiDrawStrokeParams)); // make a stroke for both the axes and the histogram line sp.Cap = uiDrawLineCapFlat; sp.Join = uiDrawLineJoinMiter; sp.Thickness = 2; sp.MiterLimit = uiDrawDefaultMiterLimit; // draw the axes setSolidBrush(&brush, colorBlack, 1.0); path = uiDrawNewPath(uiDrawFillModeWinding); uiDrawPathNewFigure(path, xoffLeft, yoffTop); uiDrawPathLineTo(path, xoffLeft, yoffTop + graphHeight); uiDrawPathLineTo(path, xoffLeft + graphWidth, yoffTop + graphHeight); uiDrawPathEnd(path); uiDrawStroke(p->Context, path, &brush, &sp); uiDrawFreePath(path); // now transform the coordinate space so (0, 0) is the top-left corner of the graph uiDrawMatrixSetIdentity(&m); uiDrawMatrixTranslate(&m, xoffLeft, yoffTop); uiDrawTransform(p->Context, &m); // now get the color for the graph itself and set up the brush uiColorButtonColor(colorButton, &graphR, &graphG, &graphB, &graphA); brush.Type = uiDrawBrushTypeSolid; brush.R = graphR; brush.G = graphG; brush.B = graphB; // we set brush->A below to different values for the fill and stroke // now create the fill for the graph below the graph line path = constructGraph(graphWidth, graphHeight, 1); brush.A = graphA / 2; uiDrawFill(p->Context, path, &brush); uiDrawFreePath(path); // now draw the histogram line path = constructGraph(graphWidth, graphHeight, 0); brush.A = graphA; uiDrawStroke(p->Context, path, &brush, &sp); uiDrawFreePath(path); // now draw the point being hovered over if (currentPoint != -1) { double xs[10], ys[10]; pointLocations(graphWidth, graphHeight, xs, ys); path = uiDrawNewPath(uiDrawFillModeWinding); uiDrawPathNewFigureWithArc(path, xs[currentPoint], ys[currentPoint], pointRadius, 0, 6.23, // TODO pi 0); uiDrawPathEnd(path); // use the same brush as for the histogram lines uiDrawFill(p->Context, path, &brush); uiDrawFreePath(path); } } static int inPoint(double x, double y, double xtest, double ytest) { // TODO switch to using a matrix x -= xoffLeft; y -= yoffTop; return (x >= xtest - pointRadius) && (x <= xtest + pointRadius) && (y >= ytest - pointRadius) && (y <= ytest + pointRadius); } static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e) { double graphWidth, graphHeight; double xs[10], ys[10]; int i; graphSize(e->AreaWidth, e->AreaHeight, &graphWidth, &graphHeight); pointLocations(graphWidth, graphHeight, xs, ys); for (i = 0; i < 10; i++) if (inPoint(e->X, e->Y, xs[i], ys[i])) break; if (i == 10) // not in a point i = -1; currentPoint = i; // TODO only redraw the relevant area uiAreaQueueRedrawAll(histogram); } static void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left) { // do nothing } static void handlerDragBroken(uiAreaHandler *ah, uiArea *a) { // do nothing } static int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e) { // reject all keys return 0; } static void onDatapointChanged(uiSpinbox *s, void *data) { uiAreaQueueRedrawAll(histogram); } static void onColorChanged(uiColorButton *b, void *data) { uiAreaQueueRedrawAll(histogram); } static int onClosing(uiWindow *w, void *data) { uiControlDestroy(uiControl(mainwin)); uiQuit(); return 0; } static int shouldQuit(void *data) { uiControlDestroy(uiControl(mainwin)); return 1; } int main(void) { uiInitOptions o; const char *err; uiBox *hbox, *vbox; int i; uiDrawBrush brush; handler.Draw = handlerDraw; handler.MouseEvent = handlerMouseEvent; handler.MouseCrossed = handlerMouseCrossed; handler.DragBroken = handlerDragBroken; handler.KeyEvent = handlerKeyEvent; memset(&o, 0, sizeof (uiInitOptions)); err = uiInit(&o); if (err != NULL) { fprintf(stderr, "error initializing ui: %s\n", err); uiFreeInitError(err); return 1; } uiOnShouldQuit(shouldQuit, NULL); mainwin = uiNewWindow("libui Histogram Example", 640, 480, 1); uiWindowSetMargined(mainwin, 1); uiWindowOnClosing(mainwin, onClosing, NULL); hbox = uiNewHorizontalBox(); uiBoxSetPadded(hbox, 1); uiWindowSetChild(mainwin, uiControl(hbox)); vbox = uiNewVerticalBox(); uiBoxSetPadded(vbox, 1); uiBoxAppend(hbox, uiControl(vbox), 0); srand(time(NULL)); for (i = 0; i < 10; i++) { datapoints[i] = uiNewSpinbox(0, 100); uiSpinboxSetValue(datapoints[i], rand() % 101); uiSpinboxOnChanged(datapoints[i], onDatapointChanged, NULL); uiBoxAppend(vbox, uiControl(datapoints[i]), 0); } colorButton = uiNewColorButton(); // TODO inline these setSolidBrush(&brush, colorDodgerBlue, 1.0); uiColorButtonSetColor(colorButton, brush.R, brush.G, brush.B, brush.A); uiColorButtonOnChanged(colorButton, onColorChanged, NULL); uiBoxAppend(vbox, uiControl(colorButton), 0); histogram = uiNewArea(&handler); uiBoxAppend(hbox, uiControl(histogram), 1); uiControlShow(uiControl(mainwin)); uiMain(); uiUninit(); return 0; }