At this point, we could add back the functionality of the increment and decrement buttons by adding something like the following to their message handler:

int ReactU32ButtonMessage(Element *element, Message message, int di, void *dp) {
	// ...
	if (message == MSG_CLICKED) {
		// Get the selected object.
		Object *object = SelectedObject();

		// Find the right property...
		for (int i = 0; i < arrlen(object->properties); i++) {
			if (0 == strcmp(object->properties[i], data->key)) {
				// Set the property value.
				object->properties[i].u32 = data->u32Button.clickDelta + ObjectReadU32(object, data->key, 0);
				break;
			}
		}

		// Sync with all the elements that match this property.
		ReactUpdate(NULL, data->key);
	}
	// ...
}

And although it works, in my opinion it's not the best idea to start scattering code that modifies the document state around the codebase. Sure, it doesn't really matter for this simple application, but it quickly becomes a problem as the application grows larger. And it will make our lives hell when trying to implement undo and redo later on.

What we're instead going to do is introduce a Step structure that contains all the information needed to apply a state change to our document, and then add a StepApply function to actually apply a step to the document.

Here's what we're going to change the ReactU32ButtonMessage click handler to:

int ReactU32ButtonMessage(Element *element, Message message, int di, void *dp) {
	// ...
	if (message == MSG_CLICKED) {
		// Setup a new Step structure.
		Step step = { 0 };
		
		// The type of state change modification we're making to the document.
		// In this case, we're changing the value of a property.
		step.type = STEP_SET_PROPERTY;

		// The object we're modifying. Here it's the selected object.
		step.objectID = selectedObjectID;

		// Fill in the new value for the property.
		step.property.type = PROPERTY_U32;
		strcpy(step.property.key, data->key);
		step.property.u32 = data->u32Button.clickDelta + ObjectReadU32(SelectedObject(), data->key, 0);

		// Double check that this new value of the property is actually in the valid range.
		if (step.property.u32 >= data->u32Button.minimum && step.property.u32 <= data->u32Button.maximum) {
			// If so, apply the step.
			StepApply(step, NULL);
		}
	}
	// ...
}

Now let's define the Step structure. It contains a field indicating the type of the document state modification; we'll only need one such type, STEP_SET_PROPERTY. It also contains the ID of the object that is being modified by the step. This could be changed to an array of IDs for more complicated applications where multiple selections are allowed and multiple objects can be edited at once.

typedef struct Step {
#define STEP_SET_PROPERTY (1)
	int type;

	uint64_t objectID;

	// Details for the specific step type.
	union {
		Property property;
		// ...
	};
} Step;

Before we write the StepApply function, let's make a StepFree function. It's good to get this out of the way early on so we don't forget about it later.

void StepFree(Step step) {
	if (step.type == STEP_SET_PROPERTY) {
		// Free the property stored in the step.
		PropertyFree(step.property);
	} else {
		// ... add code here when new step types are introduced.
	}
}

Okay, the last thing to do is write the StepApply function. Although this application only has one object, we'll include functionality in StepApply for switching the selected object to the object specified in the Step.

void StepApply(Step step /* the step to apply */, 
		Element *updateExclude /* the exclude element to pass to ReactUpdate */) {
	bool changedSelectedObject = false;

	// Is the object specified in the step the same as the currently selected object?
	if (selectedObjectID != step.objectID) {
		// No, so update the selected object.
		selectedObjectID = step.objectID;
		changedSelectedObject = true;
	}

	const char *updateKey = NULL; // TODO Set this.

	if (changedSelectedObject) {
		// The selected object changed, so repopulate the interface.
		Populate();
	} else {
		// The selected object stayed the same, so we only need to do a minimal update.
		// Only update the elements matching the property key that was modified.
		ReactUpdate(updateExclude, updateKey);
	}
}

Now we can insert the code specific to the STEP_SET_PROPERTY step type.

// ... as before
const char *updateKey = NULL; // TODO Set this.

if (step.type == STEP_SET_PROPERTY) {
	// Store the property key that will need to be updated in the UI.
	updateKey = step.property.key;

	bool found = false;

	// Look for the property with this key in the object.
	for (int i = 0; i < arrlen(object->properties); i++) {
		if (0 == strcmp(object->properties[i].key, step.property.key)) {
			// Found it! Replace the property in the array.
			object->properties[i] = step.property;
			found = true;
			break;
		}
	}

	if (!found) {
		// The object did not already have the property in its array,
		// so insert it at the end.
		arrput(object->properties, step.property);
	}
} else {
	// ... add more code when new step types are introduced
}

if (changedSelectedObject) {
// ... as before

And now our application is interactive again! =D Step through the code in a debugger to make sure you understand everything that happens when a button is clicked. The outline is this:

  1. The MSG_CLICKED message is received by ReactU32ButtonMessage.
  2. A step structure is prepared, and StepApply is called.
  3. StepApply updates the property in the object's property array.
  4. StepApply calls ReactUpdate with the "count" property key.
  5. ReactUpdate sends MSG_PROPERTY_CHANGED to all the elements bound to the "count" key.
  6. The buttons repaint themselves, and the label updates its stored text.

We're getting very close to the end of tutorial. Next time: undo and redo.

part22.c

Part 21: Tracking reactive elements.

Part 23: Undo and redo.