The next thing we're going to do is work towards keeping a list of the UI elements that are connected to document state. This will allow us to easily keep the UI and document in sync. The first step of this is moving out the creation of such elements into separate functions, rather than directly in Populate(), as we'll need to introduce more setup code per-element going forwards.

We add a function to create buttons that are bound to a PROPERTY_U32 property of the selected object.

Button *ReactU32ButtonCreate(
		Element *parent    /* the parent of the button */,    
		int flags          /* the flags to create the button with */, 
		const char *key    /* the property key */,
		const char *label  /* the button's label */, 
		uint32_t minimum   /* the minimum allowed value of the property */, 
		uint32_t maximum   /* the maximum allowed value of the property */, 
		int32_t clickDelta /* the change to apply to the property when the button is clicked */) {
	// Create the button.
	Button *button = ButtonCreate(parent, flags, label, -1);

	// Calculate the result of the property after clicking the button,
	// and check whether this is in the allowed range of the property.
	int64_t result = clickDelta + ObjectReadU32(SelectedObject(), key, 0);
	bool disabled = result < minimum || result > maximum;

	// Choose the correct message handler for the button depending on whether it is disabled or not.
	button->e.messageUser = disabled ? ReactU32ButtonDisabledMessage : ReactU32ButtonEnabledMessage;

	// Return the buttons in case the caller wants to mess with it.
	return button;
}

We'll also need to create those message handlers for the button. They don't really do anything yet though.

int ReactU32ButtonEnabledMessage(Element *element, Message message, int di, void *dp) {
	return 0;
}

int ReactU32ButtonDisabledMessage(Element *element, Message message, int di, void *dp) {
	if (message == MSG_BUTTON_GET_COLOR) {
		*(uint32_t *) dp = 0xCCCCCC; 
	}

	return 0;
}

Let's also create a function for making labels bound to PROPERTY_U32 properties.

int ReactU32DisplayMessage(Element *element, Message message, int di, void *dp) {
	// Don't do anything, yet!
	return 0;
}

Label *ReactU32DisplayCreate(
		Element *parent    /* the parent element of the label */, 
		int flags          /* the flags to create the label with */, 
		const char *key    /* the property key */, 
		const char *prefix /* the prefix to show on the label before the value */) {
	// Format the string to show on the label, with the prefix and value.
	char buffer[64];
	snprintf(buffer, sizeof(buffer), "%s%d", prefix, ObjectReadU32(SelectedObject(), key, 0));

	// Create the label element and set its message handler.
	Label *label = LabelCreate(parent, flags, buffer, -1);
	label->e.messageUser = ReactU32DisplayMessage;

	// Return the label in case the caller wants to mess with it.
	return label;
}

The code in the Populate() function for the OBJECT_COUNTER type now simplifies down to:

Panel *row = PanelCreate(container, PANEL_HORIZONTAL | ELEMENT_H_FILL);
ReactU32ButtonCreate(&row->e, 0, "count", "-", 0, 10, -1);
ReactU32DisplayCreate(&row->e, ELEMENT_H_FILL | LABEL_CENTER, "count", "Count: ");
ReactU32ButtonCreate(&row->e, 0, "count", "+", 0, 10, 1);

This is really nice! Just a single function call per element. And at this point, the Populate() function is almost done for the tutorial. It's not going to get any more complicated, even as we add interactivity, undo/redo and serialization. And also, if we were to extend this application, we can reuse the ReactU32ButtonCreate and ReactU32DisplayCreate functions for any additional properties we wanted.

part19.c

Part 18: Reading state to create the UI.

Part 20: Saving data for reactive elements.