Now that we've got a hierarchy of elements positioned on a window that we can send messages to, we're ready to actually start rendering things to the screen! Unsurprisingly (since I've already mentioned it) this will involve sending 'paint' messages of some form to elements to instruct them to draw themselves. Before we can do that though, we've got some setup to complete.

We start by defining the Painter structure. We going to be doing software rendering, and this structure contains all the information an element will need to paint itself.

typedef struct Painter {
	Rectangle clip; // The rectangle the element should draw into.
	uint32_t *bits; // The bitmap itself. bits[y * painter->width + x] gives the RGB value of pixel (x, y).
	int width, height; // The width and height of the bitmap.
} Painter;

We next add the DrawBlock function to fill a rectangular region of pixels with a specific color. We'll add additional rendering primitives in the next article.

void DrawBlock(Painter *painter, Rectangle rectangle, uint32_t color) {
	// Intersect the rectangle we want to fill with the clip, i.e. the rectangle we're allowed to draw into.
	rectangle = RectangleIntersection(painter->clip, rectangle);

	// For every pixel inside the rectangle...
	for (int y = rectangle.t; y < rectangle.b; y++) {
		for (int x = rectangle.l; x < rectangle.r; x++) {
			// Set the pixel to the given color.
			painter->bits[y * painter->width + x] = color;
		}
	}

	// Note that the y loop is the outer one, so that memory access to painter->bits is more sequential.
	// (i.e. it's slightly faster this way)
}

We're now ready to introduce the MSG_PAINT message. To ask an element to draw itself, we send it a MSG_PAINT message, with a pointer to the Painter passed in dp. For example, ElementMessage(element, MSG_PAINT, 0, &painter);. And the receiving element might process it something like this:

int MyElementMessage(Element *element, Message message, int di, void *dp) {
	if (message == MSG_PAINT) {
		Painter *painter = (Painter *) dp;
		DrawBlock(painter,               // Draw onto the bitmap of the provided painter.
				element->bounds, // Fill the bounds of the element; this will be clipped to the painter's clip.
				0xFF0000);       // Use the solid red color.
	} else {
		// ...
	}

	return 0;
}

However, the descendent of an element are able to paint within its bounds. Thus to get a complete bitmap image of a certain region of the window, we need to paint the top level element, then recurse into painting the descendents. And all of this needs to be with the correct clipping behaviour. Therefore we introduce the _ElementPaint function to do this all for us:

void _ElementPaint(Element *element, Painter *painter) {
	// Compute the intersection of where the element is allowed to draw, element->clip,
	// with the area requested to be drawn, painter->clip.
	Rectangle clip = RectangleIntersection(element->clip, painter->clip);

	// If the above regions do not overlap, return here,
	// and do not recurse into our descendant elements
	// (since their clip rectangles are contained within element->clip).
	if (!RectangleValid(clip)) {
		return;
	}

	// Set the painter's clip and ask the element to paint itself.
	painter->clip = clip;
	ElementMessage(element, MSG_PAINT, 0, painter);

	// Recurse into each child, restoring the clip each time.
	for (uintptr_t i = 0; i < element->childCount; i++) {
		painter->clip = clip;
		_ElementPaint(element->children[i], painter);
	}
}

When an input event occurs, like moving the mouse cursor, various processing will take place (we'll start working with input events in a few articles). This processing will cause elements to need to be repainted as their contents changes, or as they are moved, created or destroyed. Because repainting a region of the screen requires recurses through a large portion of the element hierarchy, we will want to 'queue up' all the repainting that needs to happen until the end of the input event cycle, when everything is ready. We start by adding a Rectangle field to Window to keep track of the area of the window that needs to be repainted at the next 'update point' (exactly where update points occur will be obvious once we've implemented input event processing).

typedef struct Window {
	Element e;
	uint32_t *bits;
	int width, height;
	Rectangle updateRegion; // NEW!
	// ...
} Window;

Next, we add the function ElementRepaint, which the user of the library calls whenever they need an element, or some portion of its bounds, to be repainted.

void ElementRepaint(Element *element, Rectangle *region) {
	if (!region) {
		// If the region to repaint was not specified, use the whole bounds of the element.
		region = &element->bounds;
	}

	// Intersect the region to repaint with the element's clip.
	Rectangle r = RectangleIntersection(*region, element->clip);

	// If the intersection is non-empty...
	if (RectangleValid(r)) {
		// Set the window's updateRegion to be the smallest rectangle containing both
		// the previous value of the updateRegion and the new rectangle we need to repaint.
		if (RectangleValid(element->window->updateRegion)) {
			element->window->updateRegion = RectangleBounding(element->window->updateRegion, r);
		} else {
			element->window->updateRegion = r;
		}
	}
}

Finally, we need the function to finish the update and actually do the repainting.

void _Update() {
	for (uintptr_t i = 0; i < global.windowCount; i++) {
		Window *window = global.windows[i];

		// Is there anything marked for repaint?
		if (RectangleValid(window->updateRegion)) {
			// Setup the painter using the window's buffer.
			Painter painter;
			painter.bits = window->bits;
			painter.width = window->width;
			painter.height = window->height;
			painter.clip = RectangleIntersection(RectangleMake(0, window->width, 0, window->height), window->updateRegion);

			// Paint everything in the update region.
			_ElementPaint(&window->e, &painter);

			// Tell the platform layer to put the result onto the screen.
			_WindowEndPaint(window, &painter);

			// Clear the update region, ready for the next input event cycle.
			window->updateRegion = RectangleMake(0, 0, 0, 0);
		}
	}
}

All that remains is to implement the function _WindowEndPaint in the platform layers. As usual with the platform specific code, I won't go through it in the article here because it really isn't that important or interesting. You can grab the full code for the article in the link below. The only thing to note is that now when the window is resized, the platform specific code will call _Update() after it has sent the MSG_LAYOUT message to the root of hierarchy, and that WindowMessage now calls ElementRepaint when it receives MSG_LAYOUT. The test code for this article shows a simple hierarchy of 4 elements, each of which draws itself in a different color. They are automatically repainted as the window is resized. I advise you to step through all the code that runs in response to resizing the window in a debugger if you want to really understand it.

A picture of the window running the test code. It contains a magenta background (element A), with a gray rectangle in the center (element B). At the left side of the gray rectangle there is a blue rectangle (element C), and at the right side of the gray rectangle there is a green rectangle (element D).

part6.c

Part 5: Layouting and bounds.

Part 7: Drawing text and boxes.