Reputation: 256
I have a basic 4x20 character LCD that I would like to use for displaying a menu with buttons using an Arduino as the driver (limited standard library support).
I'm thinking of producing an interface class GraphicObject that all graphical objects then inherit from (such as Button, MenuItem etc.). It seems logical that the GraphicObject class should have a draw method which can be overridden.
At the moment, I have a class called Lcd which covers the low level drawing of text and character positioning. In order to draw anything, I will need access to one of these Lcd objects. If I include a pointer to the Lcd object within my GraphicObject or derived objects, I couple them and make them Lcd only objects. If I change the type of display device these classes aren't suitable any more.
How can the classes be organised to keep them loosely coupled and allow for a change in display types at a later date? Should I have LCDButton LCDMenuItem which then inherit from a Button and MenuItem interface, then create additional objects for the other display device (OtherDisplayButton OtherDisplayMenuItem)?
Any suggested reading? I've looked at many examples, but none of them seem to go into details about the function of a draw method and whether the device should be accessed directly or through another controlling object.
Thanks
Brief code idea overview
#include "Arduino.h"
class Lcd {
public:
struct Parameters {
uint_fast8_t numberLines;
uint_fast8_t numberCharacters;
// uint_fast8_t* lineOffsets;
Print* serial; // Can be any Serial device (all inherit from Print).
};
protected:
Parameters parameters_;
const uint_fast8_t escapeCode_ = 0x1B;
const uint_fast8_t directCode_ = 0xFE;
void sendCommand_(uint_fast8_t command, uint_fast8_t parameter = 0);
void sendCommandDirect_(uint_fast8_t command);
public:
Lcd(Parameters parameters);
void clearDisplay(void);
void moveCursor(uint_fast8_t line, uint_fast8_t character);
void resetDisplay(void);
void customCharacter(const uint_fast8_t address,
const uint_fast8_t characterMap[8]);
void write(uint8_t character);
// Boilerplate print forwarders.
void print(const char character);
void print(const String &string);
void print(const char string[]);
// Boilerplate println forwarders.
void println(const char character);
void println(const String &string);
void println(const char string[]);
void println(void);
};
class GraphicObject {
virtual void draw(void)=0;
};
class Button: public GraphicObject {
public:
typedef void (*buttonAction)(void);
virtual void setText(const String text)=0;
virtual const String getText() =0;
virtual bool isActive()=0;
virtual void setActive(bool)=0;
virtual void setAction(buttonAction action)=0;
};
class MenuItem: public Button {
public:
typedef void (*menuAction)(void);
virtual MenuItem* parentItem()=0;
virtual const MenuItem* addItem(String text, menuAction action)=0;
};
class VScrollbar: public GraphicObject {
public:
virtual void setAtTop(bool atTop);
virtual void setAtBottom(bool atBottom);
};
class LcdButton: public Button {
private:
Lcd* lcd_;
String text_;bool active_;
public:
LcdButton(Lcd* lcd);
void draw(void);
void setText(String text);
const String getText();bool isActive();
void setActive(bool);
void setAction(Button::buttonAction action);
};
class LcdWindow: public GraphicObject {
private:
LcdButton* lcdButtons_ = nullptr;
public:
enum class Position
: uint_fast8_t {
LEFT,
RIGHT
};
bool addButton(LcdButton* lcdButton, uint_fast8_t line, uint_fast8_t offset);
bool addVScrollbar(VScrollbar* vScrollbar, Position position);
void draw();
};
int main(void) {
Lcd::Parameters lcdParameters;
lcdParameters.numberCharacters = 20;
lcdParameters.numberLines = 4;
lcdParameters.serial = &Serial1;
Lcd lcd = Lcd(lcdParameters);
LcdButton myButton(&lcd);
myButton.setText("Select");
myButton.setActive(true);
LcdWindow lcdWindow;
lcdWindow.addButton(&myButton, 1, 1);
lcdWindow.draw();
while (1){}
return 0;
}
Upvotes: 0
Views: 139
Reputation: 787
There are different ways to do this. In principle, you should define an interface (API) to your low-level LCD driver module(s) and call only functions of your low level api to draw something. The implementations of this api can then exchanged without the need to change the high level code.
The simplest way to do this is to define an abstract c++ base class where all lcd driver implementations have to be derived from. The base class should have virtual methods that need to be overloaded by the derived implementations.
But a litte information: virtual methods in c++ classes requires the compiler to generate a method pointer table for every object that is created when the object is instantiated; this needs some more memory. Also, all function calls to objects of these classes are indirect (the compiler generates code that first lookup the real function pointer and then calls the function using this pointer), which makes the resulting code slightly slower.
Upvotes: 1