The next little step is to save the data passed to the React*Create functions in a common structure we'll call ReactData. We'll need to access this data during the lifetime of the element so that it can be synced to the document state. In the short term, it'll also allow us to remove the weird ReactU32ButtonEnabledMessage/ReactU32ButtonDisabledMessage split.

// The data associated with each element in the UI that mirrors 
// some property of the selected object in the document.
typedef struct ReactData {
	// The property key.
	const char *key;

	// For different types of element.
	union {
		// For ReactU32DisplayCreate.
		struct {
			const char *prefix; // Not a copy.
		} u32Display;

		// For ReactU32ButtonCreate.
		struct {
			uint32_t minimum, maximum;
			int32_t clickDelta;
		} u32Button;

		// ...
	};
} ReactData;

In each of the element creation functions we allocate this structure on the heap and fill it with the parameters passed in. Note that I'm now setting the button's message handler always to a new function ReactU32ButtonMessage. A pointer to the data structure will be stored in the elements cp pointer, which if you've forgotten is a context pointer that the user of the library (us) is able to use for whatever they want.

Label *ReactU32DisplayCreate(Element *parent, int flags, const char *key, const char *prefix) {
	// Allocate the data structure and store our parameters.
	ReactData *data = (ReactData *) calloc(1, sizeof(ReactData));
	data->key = key;
	data->u32Display.prefix = prefix; // Not a copy.

	// Create the label as before.
	char buffer[64];
	snprintf(buffer, sizeof(buffer), "%s%d", prefix, ObjectReadU32(SelectedObject(), key, 0));
	Label *label = LabelCreate(parent, flags, buffer, -1);
	label->e.cp = data; // Save the data pointer in the cp field.
	label->e.messageUser = ReactU32DisplayMessage;
	return label;
}

Button *ReactU32ButtonCreate(Element *parent, int flags, const char *key, const char *label, 
		uint32_t minimum, uint32_t maximum, int32_t clickDelta) {
	// Allocate the data structure and store our parameters.
	ReactData *data = (ReactData *) calloc(1, sizeof(ReactData));
	data->key = key;
	data->u32Button.minimum = minimum;
	data->u32Button.maximum = maximum;
	data->u32Button.clickDelta = clickDelta;

	// Create the button as before.
	Button *button = ButtonCreate(parent, flags, label, -1);
	button->e.cp = data; // Save the data pointer in the cp field.
	button->e.messageUser = ReactU32ButtonMessage;
	return button;
}

Now let's update the message handlers. We'll need to deallocate the data structure when the element is destroyed.

int ReactU32DisplayMessage(Element *element, Message message, int di, void *dp) {
	// Get the data structure from the cp field.
	ReactData *data = (ReactData *) element->cp;

	if (message == MSG_DESTROY) {
		// Free the data when the element is destroyed.
		free(data);
	}

	return 0;
}

int ReactU32ButtonMessage(Element *element, Message message, int di, void *dp) {
	// Get the data structure from the cp field.
	ReactData *data = (ReactData *) element->cp;

	if (message == MSG_DESTROY) {
		// Free the data when the element is destroyed.
		free(data);
	} else if (message == MSG_BUTTON_GET_COLOR) {
		// Determine whether we the button should be grayed.
		int64_t result = data->u32Button.clickDelta + ObjectReadU32(SelectedObject(), data->key, 0);
		
		if (result < data->u32Button.minimum || result > data->u32Button.maximum) {
			// Gray the button.
			*(uint32_t *) dp = 0xCCCCCC; 
		}
	}

	return 0;
}

The Populate() function doesn't need updating here. In the next article we're going to create a list of all the elements with the ReactData structure, and then use it to refactor things a little bit more.

part20.c

Part 19: Refactoring reactive elements.

Part 21: Tracking reactive elements.