This is two examples are same thing, right?
struct data_t{
uint8_t data;
uint8_t size;
};
typedef struct data_t data_t;
typedef struct{
uint8_t data;
uint8_t size;
}data_t;
Then:
data_t Mydata;
Yes. You can even do
typedef struct data_struct data_t;
struct data_struct {
uint8_t data;
uint8_t size;
};
This is actually a common pattern, because then you can use
data_t in the definition of
struct data_struct. While the above will work fine if you replace
data_struct with
data_t, it can confuse some programmers (especially those coming from C++, where the
struct and
typedef namespaces are the same); and it will not work in C++ (again, because
struct and
typedef use the same namespace).
Unless you use
packed structures, you really should reorder your structures so that the fields with largest alignment requirements are listed first. That way, the compiler needs to insert the least padding in the structure, and your structures are no larger than they need to be.
You could also design your widget types as unions within a structure containing the common fields:
struct widget {
struct widget *next;
struct screen *parent;
void (*draw)(struct widget *);
void (*update)(struct widget *);
int (*focus)(struct widget *);
int (*select)(struct widget *);
int16_t x;
int16_t y;
int16_t width;
int16_t height;
int16_t radius;
widgetStateType state; /* Visible, hidden, focused, disabled, ... */
widgetRefreshType refresh;
widgetFrameType frame;
widgetType type; /* Defines which of the unions is valid */
union {
struct anyLabel label;
struct comboBox combo;
};
};
This
struct widget would be the last defined, and contain all the widget types. The label type could be
struct anyLabel {
const int8_t *icon; // Optional button icon
const int8_t *font; // Font for the text label
char *text;
int16_t iconX; // Relative coordinates for the icon
int16_t iconY;
int16_t textX; // Relative coordinates for the text label
int16_t textY;
};
where the icon and the text parts are optional.
Combobox
select() method opens and closes the option list. Let's say we use a list of
anyLabels, each with a defined size, and the list scrollable:
struct listItem {
struct listItem *next;
struct anyLabel label;
int16_t width;
int16_t height;
};
struct comboBox {
int (*select)(struct *widget, struct listItem *);
struct simpleButton label;
int16_t scroll;
widgetStateType state;
widgetRefreshType refresh;
widgetFrameType frame;
struct listItem *list;
}
The comboBox-specific
select() method is only called when one of the list items is chosen. The
state,
refresh, and
frame refer to the comboBox list area.
Before implementing any of this in a microcontroller, I personally would DEFINITELY simulate and test the UI first, and design the widget data types based on the actual needs around that. For the simulator, I believe the
Cairo library would be most appropriate; it too is written in pure C. You can either use the GTK+ widget toolkit (in Linux, MacOS, or Windows), or you can write the simulator in pure C with Cairo under
Xlib (i.e., X-Windows, either in Linux, or in MacOS or Windows using an X-Windows emulation).
As you can see from the
Cairo X11/Xlib example, it creates a simple window of the desired size as a Cairo surface, and the standard X11/Xlib event loop acquires pointer location and clicks, as well as keypresses (and -releases). It should fit well the microcontroller model.
(Instead of drawing to a memory buffer and sending the changes to a display, one draws to the Cairo surface, and updates the visible window with the surface contents. Different C code, yes, but logically very similar; so the control flow and data types should be almost the same in the simulator, as needed on the MCU.)
The idea of the simulator is twofold: One, it lets you experiment (in a desktop environment, thus much easier and faster than a MCU development cycle) with the actual user interface. Two, you can experiment and develop the actual drawing primitives and data types you need on the MCU to describe that user interface efficiently.
Me, I like to find unsuspecting victims to test my UIs without documentation, to see if they can find a feature by navigating the UI if they are only given a desired end result/effect. If not, the UI is not good enough. Some users are stuck in specific expectations ("I want
X on the bottom", "The icons are ugly", "I don't like the colors"), so not all of their input is actionable, but observing (statistics on) what and how they traverse through the UI, tells you how end users too would try to use the UI.
Which gives one the perfect opportunity to optimize the UI to work for the expected use cases with as little effort as possible.
During the development of such UI simulators, I usually end up revamping my data structures completely. What I
imagine I need at the design phase, is often what I need at the implementation phase; and since I firmly believe reality beats theory every time, I adapt my design to work for what I need it to.
I do prefer to do this stuff in the Linux environment, because it is both closest to the MCU environment (same compiler and compiler options, at least, even if a different hardware backend), and is least effort (see Cairo + Xlib above; you ensure the dev libs for these two packages are available, and if you have a desktop environment, you're ready to go). In Windows, a large problem is that Microsoft only provides a C++ compiler, not a real C one, so things you expect from a C compiler no longer apply there. You can use MingW or other GCC ports or even Intel CC, but AFAIK interfacing to the Windows GUI from C is nontrivial. On MacOS, you do need an interface layer, something like XQuartz or XDarwin. On Linux, Wayland users do need e.g. XWayland, but because of the number of X11 applications, it should already be running, unless one uses a self-built highly customized Linux distro.
(XQuartz, XWayland, XDarwin, etc. all provide a "rootless X server". It means X11 applications are drawn as standard application windows for that operating system, but their contents are updated through the X server application. This helper X server does not need to run with elevated privileges, only with the current user privileges, so it really is more like a translator application. WSL includes an automatic layer for this, so you won't even need to run the rootless X server yourself.)
If you want, I could probably whip up a small example, "simulating" say a 128×64 B/W (OLED) or 320×240 15-bit color (BuyDisplay IPS) display, on X11 and Cairo, showing how the various events would be caught by the simulator and forwarded to some basic widget types. If you intend to use a touchscreen, then mouse/pointer works, although there is no "hover" above anything, only touch/push. If you intend to use physical buttons, use the keyboard for those.
Perhaps as a separate topic, in the hopes of others creating small appliances and widgets (no pun intended) with displays would do this too. We already have enough bad user interfaces, and doing better isn't hard; in fact, it can be fun and very much worth the effort.