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:
MSG_CLICKED
message is received by ReactU32ButtonMessage
.StepApply
is called.StepApply
updates the property in the object's property array.StepApply
calls ReactUpdate
with the "count" property key.ReactUpdate
sends MSG_PROPERTY_CHANGED
to all the elements bound to the "count" key.We're getting very close to the end of tutorial. Next time: undo and redo.