AceUtils
0.6.0
Useful Arduino utilties which are too small as separate libraries, but complex enough to be shared among multiple projects, and often have external dependencies to other libraries.
|
Deprecated: The mode_group.h
header and its ModeGroup
and ModeNavigator
are deprecated. This was an attempt to encode the UI transitions of a small application (e.g. a clock with an LCD or LED display, and some buttons) in a way that was data driven. The alternative was to use a series of switch-statements that dispatched to short code fragments to handle external events (such as button presses, and periodic clock signals). It turned out that while using switch-statements seemed simplistic and verbose, it was far easier to understand and maintain compared to the ModeGroup
and ModeNavigator
framework which became difficult to understand after a few months away from the code. I think this is another example where simple and verbose code wins over clever code because the execution path of the simple code is explicit and easier to follow.
The ModeGroup
struct defines a tree of sibling and parent/child modes which define the different modes of a clock that can be controlled by 2 buttons.
ModeGroup
.The ModeNavigator
extracts common navigation code to promote code reuse.
The advantage of using these classes is that the hierarchy of modes is data-driven instead of being hardcoded into various switch
statements. It is far easier to rearrange the UI of the clock, placing modes in different order, or at different levels, using a data-driven specification. When the navigation was in code, it was quite difficult to make changes without spending a lot of time fixing subtle UI bugs.
This package is used by:
(Don't have the energy to write a full documentation of how to use this library. I wrote some notes to myself below because I had trouble remembering how this worked, but it's mostly for my own benefit. The next best thing is to look at the source code for one of the above clocks.)
The ModeRecord
is a UI rendering mode, arranged in a hierarchical tree. It contains a modeId
that uniquely identifies the mode, and a nullable ModeGroup
pointer containing the children of this particular mode.
The ModeGroup
is an array of ModeRecords
and a pointer back to the parent ModeGroup
.
The tree hierarchy should be defined starting from the bottom leaf ModeRecords up to the root ModeGroup. A single rootModeGroup
needs to be defined. Since each ModeGroup
contains a pointer up to the parent, it is sometimes necessary to define the parent with an extern
statement to resolve the forward reference.
For example, here is a UI hierarchy where a SingleClick of the Mode button cycles through the siblings in the tree. But a LongPress of the Mode button causes the UI to move down to the child, or move up to the parent if already at the child:
The ModeGroup
and ModeRecord
definition looks something like this (see clocks/OneZoneClock/OneZoneClock.ino
The ModeNavigator
is a class that traverses this tree hierarchy given a pointer to the root ModeGroup
:
Currently the changeGroup()
method supports only a 2-level hierarchy. If the number of levels in the tree becomes more than 2, then we need to add upGroup()
and a downGroup()
methods.
I tried creating a tree hierarchy using the traditional File
and Directory
metaphor, like this:
The problem with this data structure was that it seemed impossible to create this recursive data structure statically, at compile time, because both the parent
and the children
needed to be defined for each node.
We could try to create the bare nodes, then use a setup()
method to create the links between nodes. But the problem with that is that the number of children in each node is not determined at compiled time, so each node would require a dynamically resizable array of ModeNode*
elements.
It occurs to me that it might be possible to use a doubly-linked list of sibling nodes to avoid dynamic resizing. But I didn't want to spend much time on this. The data structure described above collects all the siblings into a single ModeGroup
structure at compile-time, so it avoids having to call a setup()
function at the start of the application.
Anyway, I consider this to be a fairly big hack that works for me. Not really fit for production use.