User Interface Design Part 2: Sketching our Interface.

Last time we discussed the theory of user interface design. The idea that user interfaces form a sort of language, complete with nouns and verbs and modifiers. That user interfaces need to be discoverable and consistent, and that parts of discoverability means defining affordances: things the user can see and think “oh, that must be a button I can press” or “oh, that must be an action I can take.”

We even started sketching a potential interface based on the visual language behind LCARS, which groups buttons using rounded rectangles and provides an inverted L.


Designing a Thermostat.

With this article we’ll use the visual language we started sketching last time to put together a display that could potentially be used to control a thermostat. I’m not going to actually build all the firmware for a thermostat–though you could conceivably expand the code here to do exactly that. (If it all goes haywire, however, don’t blame me for blowing up your HVAC.)

First, let’s review our overall screen designs. We have two fundamental screen layouts; one that can be used to select the nouns or groups of nouns we’re operating on, such as “controls”, “schedule”, etc., and the other provides a full wide screen.

Basic Inverted L

No Inverted L

Each screen provides a title area, and an optional back button which we would use to return back to the top level screen.

Now let’s consider for a moment the features we would want on a thermostat.

  • We want to display the temperature.
  • Obviously we want to set the temperature. We want to set the temperature at which we turn on the heater, and when we want to turn on the air conditioner. We also want to be able to turn the fan on or off.
  • We want our thermostat to be programmable. That means we want to set the schedule.
  • We want to allow the user to switch between schedules for the winter, summer, and an eco-friendly setting, and to show the user what schedule we’re following.
  • We want to set the time, and other miscellaneous settings, such as screen blanking. (We won’t implement screen blanking as that requires a hardware modification of the Adafruit display. But we will include the UI elements for doing that.)

Our main screen should display the temperature, of course, as well as provide us a way to set the target temperatures, and to set the other items in our system: the fan, the program schedule, the settings.

With this list of ideas we can start sketching the outline of our page.

First, consider our ‘nouns.’ From the list above we have the fan, the schedule and the settings. We also have the target temperatures. It’d be nice to display a nice curved dial, since traditional thermostats used to be controlled by a circular dial, and show the target temperatures as well as the current temperature and our current schedule.

One potential design which accomplishes this is below. Note that our “nouns” are all on the inverted L bar. We display the time in the upper-left in the area we would use for our screen title. And we create a circular piece of art in the center where we show the temperature as well as the heating and cooling targets.

Design 1

The design–based on our rules we devised with the last post–uses color to show the things we can click on, but also uses shape as well. The design is discoverable–the four things the user can do are clearly labeled on the left. We separate the four items by moving settings hard bottom–this causes settings to stand alone as something separate from the other things we may want to do. And we have a circular piece of artwork which clearly marks the heating and cooling temperatures.

Let’s make one minor modification.

It’s not entirely clear that the “TEMP” button allows us to set our temperatures. We can instead remove that button from the side, and move it into the extra space we have on our screen–with the ‘noun’ (the HVAC target temperatures) being implicit based on the location on the screen:

Design 2

In this design we take advantage of our “grouped buttons” to move the HVAC temperature settings onto the display content area underneath the current temperature. The clarity of this is still there: we continue to use blue–but we use a dark gray background for our buttons so as not to distract too much from the temperature, while preserving our button affordance.

This is the design we will build towards.

Note: Notice there are a thousand different ways we can design our screen. But because we now have a design language we can use, we’ve effectively constrained ourselves: we’ve constrained ourselves to the shape of buttons, to the layout of screens. And those constraints allow us to have some consistency and to make our screens discoverable.

And ultimately easy to use while being cool.


Now that we’ve sketched the design of our screen, let’s build some code.

And this is where our design language starts to shine–because ultimately, at the bottom of the stack, the core of our UI is comprised of two basic symbols: the inverted L shape, and the button.

This is important, because it means all we have to do is build code for our inverted L, and code for our buttons–and the rest is entirely reusable over and over again.

It is important to resist the monotony of our symbol set. That is, it’s important not to think “well, hell; I only have an inverted L and a button with rounded corners–why not create blah blah blah…

Do not do this. That can lead to confusion–and ultimately frustration on the part of your users. Once you’ve designed your visual language, stick to it. This will help with discoverability and with consistency.

And it will streamline your development process.


Our code extends the Adafruit_ILI9341 class, but we will write our changes to only use the Adafruit_GFX library. We do this in order to add our symbol set to our drawing system, and because we want to make use of some internal state while drawing our objects.

The end result looks like this:

class AdaUI: public Adafruit_ILI9341
{
    public:
        /*
         *  Expose our ILI9314 constructors by redirecting to them.
         */

        AdaUI(int8_t _CS, int8_t _DC, int8_t _MOSI, int8_t _SCLK, int8_t _RST = -1, int8_t _MISO = -1) : Adafruit_ILI9341(_CS,_DC,_MOSI,_SCLK,_RST,_MISO)
            {
            }
        AdaUI(int8_t _CS, int8_t _DC, int8_t _RST = -1) : Adafruit_ILI9341(_CS,_DC,_RST)
            {
            }


        /*
         *  Our custom UI elements. We only have two: the rounded separator
         *  bar at top, and the rounded rect with right-aligned text.
         */

        /*  drawTopBar
         *
         *      Draw the curved element in the area provided. This takes five
         *  parameters: the position, width and width of the side bar, along
         *  with the orientation of the curve (upper-left, lower-left,
         *  upper-right,lower-right).
         *
         *      If called without parameters, draws our curve at the default
         *  inverted-L location
         */

        void drawTopBar(int16_t top = 32, AdaUIBarOrientation orient = KBarOrientLL, 
                        int16_t left = 0, int16_t width = 0, int16_t lbarwidth = 80);

        /*  drawButton
         *
         *      The only other custom element in our custom UI is our 
         *  button/rectangle area. This is at a given rectangular area, with
         *  text that can be left, center or right-aligned, and with 
         *  rounded corners. The radius is fixed.
         */

        void drawButton(int16_t x, int16_t y, int16_t w, int16_t h,
                        AdaUICorner corners = 0);

        void drawButton(int16_t x, int16_t y, int16_t w, int16_t h,
                        const __FlashStringHelper *str, int16_t baseline, 
                        AdaUICorner corners = 0,
                        AdaUIAlignment align = KRightAlign);

        void drawButton(int16_t x, int16_t y, int16_t w, int16_t h,
                        const char *str, int16_t baseline, 
                        AdaUICorner corners = 0,
                        AdaUIAlignment align = KRightAlign);
};

We have our two basic symbols: drawTopBar to draw the top bar, with default settings to build an inverted L shape. This builds our basic inverted L symbol, and allows us to flip it around in one of four different orientations as well as draw the inverted L with any width and side width.

Inverted L

And the other draws our button shapes. We have three versions; one that doesn’t draw any text, one that draws in-memory text and one that draws in-program memory text.

Buttons

With this we can now sketch our first screen interface. Note the purpose is not to create a functional screen, only to put together our elements to re-create our design above, and to get a feel for how our elements come together.

The code, now checked into GitHub, when compiled for the Arduino, gives us the following:

Display


Next time we’ll design the rest of our screens, and build code which simplifies the construction of those screens–thus allowing us to quickly prototype our layout.

Published by

William Woody

I'm a software developer who has been writing code for over 30 years in everything from mobile to embedded to client/server. Now I tinker with stuff and occasionally help out someone with their startup.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s