1
0
mirror of https://github.com/taigrr/arduinolibs synced 2025-01-18 04:33:12 -08:00

Simple widget set for LiquidCrystal displays

This commit is contained in:
Rhys Weatherley 2012-04-04 12:08:04 +10:00
parent 500c6d6931
commit 4b2cdd3102
16 changed files with 1011 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
backup

60
Form/Form.pde Normal file
View File

@ -0,0 +1,60 @@
/*
This example demonstrates how to use the Form and Field classes from the
FreetronicsLCD library to provide a simple UI on the 16x2 LCD display.
*/
// include the library code:
#include <FreetronicsLCD.h>
#include <Form.h>
#include <TextField.h>
#include <TimeField.h>
#include <IntField.h>
#include <BoolField.h>
// Initialize the LCD
FreetronicsLCD lcd;
// Note: if you are using the USBDroid and have reassigned pin D9 on the LCD shield to some
// other pin (e.g. A1), then you will need to initialize the shield with something like:
// FreetronicsLCD lcd(A1);
// See also: http://www.freetronics.com/pages/combining-the-lcd-keypad-shield-and-the-usbdroid
// Create the main form and its fields.
Form mainForm(lcd);
TextField welcomeField(mainForm, "Form example", "v1.0");
TimeField timeField(mainForm, "Time since reset", 24, TIMEFIELD_READ_ONLY);
IntField volumeField(mainForm, "Volume", 0, 100, 5, 85, "%");
BoolField ledField(mainForm, "Status LED", "On", "Off", true);
TimeField durationField(mainForm, "Timer duration", 24, TIMEFIELD_READ_WRITE);
#define STATUS_LED 13
void setup() {
// Status LED initially on.
pinMode(STATUS_LED, OUTPUT);
digitalWrite(STATUS_LED, HIGH);
// Enable the screen saver, which will automatically blank the screen after 10 seconds.
// The screen will wake up again when a button is pressed or lcd.display() is called.
lcd.enableScreenSaver();
// Show the main form for the first time.
mainForm.show();
}
void loop() {
// Update the number of seconds since reset:
timeField.setValue(millis() / 1000);
// Dispatch button events to the main form.
int button = lcd.getButton();
if (mainForm.dispatch(button) == FORM_CHANGED) {
if (mainForm.isCurrent(ledField)) {
if (ledField.value())
digitalWrite(STATUS_LED, HIGH);
else
digitalWrite(STATUS_LED, LOW);
}
}
}

View File

@ -0,0 +1,83 @@
#include "BoolField.h"
BoolField::BoolField(const String &label)
: Field(label)
, _printLen(0)
, _value(false)
{
}
BoolField::BoolField(Form &form, const String &label)
: Field(form, label)
, _printLen(0)
, _value(false)
{
}
BoolField::BoolField(Form &form, const String &label, const String &trueLabel, const String &falseLabel, bool value)
: Field(form, label)
, _trueLabel(trueLabel)
, _falseLabel(falseLabel)
, _printLen(0)
, _value(value)
{
}
int BoolField::dispatch(int event)
{
if (event == LCD_BUTTON_UP || event == LCD_BUTTON_DOWN ||
event == LCD_BUTTON_SELECT) {
setValue(!_value);
return FORM_CHANGED;
} else {
return -1;
}
}
void BoolField::enterField(bool reverse)
{
Field::enterField(reverse);
printValue();
}
void BoolField::setValue(bool value)
{
if (value != _value) {
_value = value;
if (isCurrent())
printValue();
}
}
void BoolField::setTrueLabel(const String &trueLabel)
{
_trueLabel = trueLabel;
if (isCurrent())
printValue();
}
void BoolField::setFalseLabel(const String &falseLabel)
{
_falseLabel = falseLabel;
if (isCurrent())
printValue();
}
void BoolField::printValue()
{
unsigned int len;
lcd()->setCursor(0, 1);
if (_value) {
lcd()->print(_trueLabel);
len = _trueLabel.length();
while (len++ < _printLen)
lcd()->write(' ');
_printLen = _trueLabel.length();
} else {
lcd()->print(_falseLabel);
len = _falseLabel.length();
while (len++ < _printLen)
lcd()->write(' ');
_printLen = _falseLabel.length();
}
}

View File

@ -0,0 +1,34 @@
#ifndef BoolField_h
#define BoolField_h
#include "Field.h"
class BoolField : public Field {
public:
explicit BoolField(const String &label);
BoolField(Form &form, const String &label);
BoolField(Form &form, const String &label, const String &trueLabel, const String &falseLabel, bool value);
int dispatch(int event);
void enterField(bool reverse);
bool value() const { return _value; }
void setValue(bool value);
String trueLabel() const { return _trueLabel; }
void setTrueLabel(const String &trueLabel);
String falseLabel() const { return _falseLabel; }
void setFalseLabel(const String &falseLabel);
private:
String _trueLabel;
String _falseLabel;
int _printLen;
bool _value;
void printValue();
};
#endif

View File

@ -0,0 +1,71 @@
#include "Field.h"
Field::Field(const String &label)
: _label(label)
, _form(0)
, next(0)
, prev(0)
{
}
Field::Field(Form &form, const String &label)
: _label(label)
, _form(0)
, next(0)
, prev(0)
{
form.addField(this);
}
Field::~Field()
{
if (_form)
_form->removeField(this);
}
int Field::dispatch(int event)
{
// Nothing to do here.
return -1;
}
void Field::enterField(bool reverse)
{
// Print the label and then position the cursor on the second line.
// We assume that the screen has just been cleared.
lcd()->print(_label);
lcd()->setCursor(0, 1);
}
void Field::exitField()
{
// Nothing to do here.
}
void Field::setLabel(const String &label)
{
if (isCurrent()) {
unsigned int prevLen = _label.length();
unsigned int newLen = label.length();
_label = label;
lcd()->setCursor(0, 0);
lcd()->print(label);
while (newLen++ < prevLen)
lcd()->write(' ');
updateCursor();
} else {
_label = label;
}
}
bool Field::isCurrent() const
{
if (!_form->isVisible())
return false;
return _form->currentField() == this;
}
void Field::updateCursor()
{
// Nothing to do here.
}

View File

@ -0,0 +1,38 @@
#ifndef Field_h
#define Field_h
#include "Form.h"
class Field {
public:
explicit Field(const String &label);
Field(Form &form, const String &label);
~Field();
Form *form() const { return _form; }
virtual int dispatch(int event);
virtual void enterField(bool reverse);
virtual void exitField();
String label() const { return _label; }
void setLabel(const String &label);
bool isCurrent() const;
protected:
LiquidCrystal *lcd() const { return _form->_lcd; }
virtual void updateCursor();
private:
String _label;
Form *_form;
Field *next;
Field *prev;
friend class Form;
};
#endif

View File

@ -0,0 +1,149 @@
#include "Form.h"
#include "Field.h"
Form::Form(LiquidCrystal &lcd)
: _lcd(&lcd)
, first(0)
, last(0)
, current(0)
{
}
Form::~Form()
{
Field *field = first;
Field *next;
while (field != 0) {
next = field->next;
field->_form = 0;
field->next = 0;
field->prev = 0;
field = next;
}
}
int Form::dispatch(int event)
{
if (current) {
int exitval = current->dispatch(event);
if (exitval >= 0)
return exitval;
}
if (event == LCD_BUTTON_LEFT)
prevField();
else if (event == LCD_BUTTON_RIGHT)
nextField();
return 0;
}
void Form::nextField()
{
Field *field = current;
if (!field)
field = first;
if (field && field->next)
field = field->next;
else
field = first;
setCurrentField(field);
}
void Form::prevField()
{
Field *field = current;
if (!field)
field = last;
if (field && field->prev)
field = field->prev;
else
field = last;
setCurrentField(field);
}
void Form::defaultField()
{
setCurrentField(first);
}
void Form::addField(Field *field)
{
if (field->_form)
return; // Already added to a form.
field->_form = this;
field->next = 0;
field->prev = last;
if (last)
last->next = field;
else
first = field;
last = field;
}
void Form::removeField(Field *field)
{
if (field->_form != this)
return; // Not a member of this form.
if (current == field) {
if (field->next)
setCurrentField(field->next);
else if (field->prev)
setCurrentField(field->prev);
else
setCurrentField(0);
}
if (field->next)
field->next->prev = field->prev;
else
last = field->prev;
if (field->prev)
field->prev->next = field->next;
else
first = field->next;
field->_form = 0;
field->next = 0;
field->prev = 0;
}
void Form::setCurrentField(Field *field)
{
if (field && field->_form != this)
return; // Wrong form.
if (visible) {
bool reverse = false;
if (current) {
current->exitField();
if (field->next == current)
reverse = true;
else if (!field->next && current == first)
reverse = true;
}
current = field;
_lcd->clear();
if (current)
current->enterField(reverse);
} else {
current = field;
}
}
void Form::show()
{
if (!visible) {
if (!current)
current = first;
visible = true;
_lcd->clear();
if (current)
current->enterField(false);
}
}
void Form::hide()
{
if (visible) {
if (current)
current->exitField();
visible = false;
_lcd->clear();
}
}

View File

@ -0,0 +1,43 @@
#ifndef Form_h
#define Form_h
#include "FreetronicsLCD.h"
class Field;
#define FORM_CHANGED 1
class Form {
public:
explicit Form(LiquidCrystal &lcd);
~Form();
int dispatch(int event);
void nextField();
void prevField();
void defaultField();
void addField(Field *field);
void removeField(Field *field);
Field *currentField() const { return current; }
void setCurrentField(Field *field);
bool isCurrent(Field &field) const { return current == &field; }
void show();
void hide();
bool isVisible() const { return visible; }
private:
LiquidCrystal *_lcd;
Field *first;
Field *last;
Field *current;
bool visible;
friend class Field;
};
#endif

View File

@ -0,0 +1,104 @@
#include "IntField.h"
IntField::IntField(const String &label)
: Field(label)
, _minValue(0)
, _maxValue(100)
, _stepValue(1)
, _value(0)
, _printLen(0)
{
}
IntField::IntField(Form &form, const String &label)
: Field(form, label)
, _minValue(0)
, _maxValue(100)
, _stepValue(1)
, _value(0)
, _printLen(0)
{
}
IntField::IntField(Form &form, const String &label, int minValue, int maxValue, int value)
: Field(form, label)
, _minValue(minValue)
, _maxValue(maxValue)
, _stepValue(1)
, _value(value)
, _printLen(0)
{
}
IntField::IntField(Form &form, const String &label, int minValue, int maxValue, int value, const String &suffix)
: Field(form, label)
, _minValue(minValue)
, _maxValue(maxValue)
, _stepValue(1)
, _value(value)
, _printLen(0)
, _suffix(suffix)
{
}
IntField::IntField(Form &form, const String &label, int minValue, int maxValue, int stepValue, int value, const String &suffix)
: Field(form, label)
, _minValue(minValue)
, _maxValue(maxValue)
, _stepValue(stepValue)
, _value(value)
, _printLen(0)
, _suffix(suffix)
{
}
int IntField::dispatch(int event)
{
if (event == LCD_BUTTON_UP) {
setValue(_value + _stepValue);
return FORM_CHANGED;
} else if (event == LCD_BUTTON_DOWN) {
setValue(_value - _stepValue);
return FORM_CHANGED;
}
return -1;
}
void IntField::enterField(bool reverse)
{
Field::enterField(reverse);
printValue();
}
void IntField::setValue(int value)
{
if (value < _minValue)
value = _minValue;
else if (value > _maxValue)
value = _maxValue;
if (value != _value) {
_value = value;
if (isCurrent())
printValue();
}
}
void IntField::setSuffix(const String &suffix)
{
_suffix = suffix;
if (isCurrent())
printValue();
}
void IntField::printValue()
{
String str(_value);
if (_suffix.length())
str += _suffix;
lcd()->setCursor(0, 1);
lcd()->print(str);
unsigned int len = str.length();
while (len++ < _printLen)
lcd()->write(' ');
_printLen = str.length();
}

View File

@ -0,0 +1,44 @@
#ifndef IntField_h
#define IntField_h
#include "Field.h"
class IntField : public Field {
public:
explicit IntField(const String &label);
IntField(Form &form, const String &label);
IntField(Form &form, const String &label, int minValue, int maxValue, int value);
IntField(Form &form, const String &label, int minValue, int maxValue, int value, const String &suffix);
IntField(Form &form, const String &label, int minValue, int maxValue, int stepValue, int value, const String &suffix);
int dispatch(int event);
void enterField(bool reverse);
int minValue() const { return _minValue; }
void setMinValue(int value) { _minValue = value; }
int maxValue() const { return _maxValue; }
void setMaxValue(int value) { _maxValue = value; }
int stepValue() const { return _stepValue; }
void setStepValue(int value) { _stepValue = value; }
int value() const { return _value; }
void setValue(int value);
String suffix() const { return _suffix; }
void setSuffix(const String &suffix);
private:
int _minValue;
int _maxValue;
int _stepValue;
int _value;
int _printLen;
String _suffix;
void printValue();
};
#endif

View File

@ -0,0 +1,38 @@
#include "TextField.h"
TextField::TextField(const String &label)
: Field(label)
{
}
TextField::TextField(Form &form, const String &label)
: Field(form, label)
{
}
TextField::TextField(Form &form, const String &label, const String &value)
: Field(form, label)
, _value(value)
{
}
void TextField::enterField(bool reverse)
{
Field::enterField(reverse);
lcd()->print(_value);
}
void TextField::setValue(const String &value)
{
if (isCurrent()) {
unsigned int prevLen = _value.length();
unsigned int newLen = value.length();
_value = value;
lcd()->setCursor(0, 1);
lcd()->print(value);
while (newLen++ < prevLen)
lcd()->write(' ');
} else {
_value = value;
}
}

View File

@ -0,0 +1,21 @@
#ifndef TextField_h
#define TextField_h
#include "Field.h"
class TextField : public Field {
public:
explicit TextField(const String &label);
TextField(Form &form, const String &label);
TextField(Form &form, const String &label, const String &value);
void enterField(bool reverse);
String value() const { return _value; }
void setValue(const String &value);
private:
String _value;
};
#endif

View File

@ -0,0 +1,209 @@
#include "TimeField.h"
#define EDIT_HOUR 0
#define EDIT_MINUTE_TENS 1
#define EDIT_MINUTE 2
#define EDIT_SECOND_TENS 3
#define EDIT_SECOND 4
TimeField::TimeField(const String &label)
: Field(label)
, _value(0)
, _maxHours(24)
, _printLen(0)
, _readOnly(false)
, editField(EDIT_HOUR)
{
}
TimeField::TimeField(Form &form, const String &label)
: Field(form, label)
, _value(0)
, _maxHours(24)
, _printLen(0)
, _readOnly(false)
, editField(EDIT_HOUR)
{
}
TimeField::TimeField(Form &form, const String &label, int maxHours, bool readOnly)
: Field(form, label)
, _value(0)
, _maxHours(maxHours)
, _printLen(0)
, _readOnly(readOnly)
, editField(EDIT_HOUR)
{
}
int TimeField::dispatch(int event)
{
unsigned long newValue;
if (_readOnly)
return -1;
if (event == LCD_BUTTON_UP) {
newValue = _value;
if (editField == EDIT_HOUR) {
newValue += 60 * 60;
} else if (editField == EDIT_MINUTE_TENS) {
if (((newValue / 60) % 60) >= 50)
newValue -= 50 * 60;
else
newValue += 10 * 60;
} else if (editField == EDIT_MINUTE) {
if (((newValue / 60) % 60) == 59)
newValue -= 59 * 60;
else
newValue += 60;
} else if (editField == EDIT_SECOND_TENS) {
if ((newValue % 60) >= 50)
newValue -= 50;
else
newValue += 10;
} else {
if ((newValue % 60) == 59)
newValue -= 59;
else
newValue += 1;
}
setValue(newValue);
return FORM_CHANGED;
} else if (event == LCD_BUTTON_DOWN) {
newValue = _value;
if (editField == EDIT_HOUR) {
if (newValue < 60 * 60)
newValue += ((unsigned long)(_maxHours - 1)) * 60 * 60;
else
newValue -= 60 * 60;
} else if (editField == EDIT_MINUTE_TENS) {
if (((newValue / 60) % 60) < 10)
newValue += 50 * 60;
else
newValue -= 10 * 60;
} else if (editField == EDIT_MINUTE) {
if (((newValue / 60) % 60) == 0)
newValue += 59 * 60;
else
newValue -= 60;
} else if (editField == EDIT_SECOND_TENS) {
if ((newValue % 60) < 10)
newValue += 50;
else
newValue -= 10;
} else {
if ((newValue % 60) == 0)
newValue += 59;
else
newValue -= 1;
}
setValue(newValue);
return FORM_CHANGED;
} else if (event == LCD_BUTTON_LEFT) {
if (editField != EDIT_HOUR) {
--editField;
printTime();
return 0;
}
} else if (event == LCD_BUTTON_RIGHT) {
if (editField != EDIT_SECOND) {
++editField;
printTime();
return 0;
}
}
return -1;
}
void TimeField::enterField(bool reverse)
{
Field::enterField(reverse);
if (reverse)
editField = EDIT_SECOND;
else
editField = EDIT_HOUR;
printTime();
if (!_readOnly)
lcd()->cursor();
}
void TimeField::exitField()
{
if (!_readOnly)
lcd()->noCursor();
Field::exitField();
}
void TimeField::setValue(unsigned long value)
{
unsigned long maxSecs = ((unsigned long)_maxHours) * 60 * 60;
value %= maxSecs;
if (value != _value) {
_value = value;
if (isCurrent())
printTime();
}
}
void TimeField::setReadOnly(bool value)
{
if (_readOnly != value) {
_readOnly = value;
printTime();
if (isCurrent()) {
if (value)
lcd()->cursor();
else
lcd()->noCursor();
}
}
}
void TimeField::printTime()
{
lcd()->setCursor(0, 1);
int col = printField(_value / (60 * 60));
int hourCol = col - 1;
lcd()->write(':');
++col;
col += printField((_value / 60) % 60);
int minuteCol = col - 1;
lcd()->write(':');
++col;
col += printField(_value % 60);
int secondCol = col - 1;
int tempCol = col;
while (tempCol++ < _printLen)
lcd()->write(' ');
_printLen = col;
if (!_readOnly) {
if (editField == EDIT_HOUR)
lcd()->setCursor(hourCol, 1);
else if (editField == EDIT_MINUTE_TENS)
lcd()->setCursor(minuteCol - 1, 1);
else if (editField == EDIT_MINUTE)
lcd()->setCursor(minuteCol, 1);
else if (editField == EDIT_SECOND_TENS)
lcd()->setCursor(secondCol - 1, 1);
else
lcd()->setCursor(secondCol, 1);
}
}
int TimeField::printField(unsigned long value)
{
if (value < 100) {
lcd()->write('0' + (int)(value / 10));
lcd()->write('0' + (int)(value % 10));
return 2;
}
unsigned long divisor = 100;
while ((value / divisor) >= 10)
divisor *= 10;
int digits = 0;
while (divisor > 0) {
lcd()->write('0' + (int)((value / divisor) % 10));
divisor /= 10;
++digits;
}
return digits;
}

View File

@ -0,0 +1,40 @@
#ifndef TimeField_h
#define TimeField_h
#include "Field.h"
#define TIMEFIELD_READ_ONLY true
#define TIMEFIELD_READ_WRITE false
class TimeField : public Field {
public:
explicit TimeField(const String &label);
TimeField(Form &form, const String &label);
TimeField(Form &form, const String &label, int maxHours, bool readOnly);
int dispatch(int event);
void enterField(bool reverse);
void exitField();
unsigned long value() const { return _value; }
void setValue(unsigned long value);
int maxHours() const { return _maxHours; }
void setMaxHours(int maxHours) { _maxHours = maxHours; }
bool isReadOnly() const { return _readOnly; }
void setReadOnly(bool value);
private:
unsigned long _value;
int _maxHours;
int _printLen;
bool _readOnly;
uint8_t editField;
void printTime();
int printField(unsigned long value);
};
#endif

View File

@ -0,0 +1,60 @@
/*
This example demonstrates how to use the Form and Field classes from the
FreetronicsLCD library to provide a simple UI on the 16x2 LCD display.
*/
// include the library code:
#include <FreetronicsLCD.h>
#include <Form.h>
#include <TextField.h>
#include <TimeField.h>
#include <IntField.h>
#include <BoolField.h>
// Initialize the LCD
FreetronicsLCD lcd;
// Note: if you are using the USBDroid and have reassigned pin D9 on the LCD shield to some
// other pin (e.g. A1), then you will need to initialize the shield with something like:
// FreetronicsLCD lcd(A1);
// See also: http://www.freetronics.com/pages/combining-the-lcd-keypad-shield-and-the-usbdroid
// Create the main form and its fields.
Form mainForm(lcd);
TextField welcomeField(mainForm, "Form example", "v1.0");
TimeField timeField(mainForm, "Time since reset", 24, TIMEFIELD_READ_ONLY);
IntField volumeField(mainForm, "Volume", 0, 100, 5, 85, "%");
BoolField ledField(mainForm, "Status LED", "On", "Off", true);
TimeField durationField(mainForm, "Timer duration", 24, TIMEFIELD_READ_WRITE);
#define STATUS_LED 13
void setup() {
// Status LED initially on.
pinMode(STATUS_LED, OUTPUT);
digitalWrite(STATUS_LED, HIGH);
// Enable the screen saver, which will automatically blank the screen after 10 seconds.
// The screen will wake up again when a button is pressed or lcd.display() is called.
lcd.enableScreenSaver();
// Show the main form for the first time.
mainForm.show();
}
void loop() {
// Update the number of seconds since reset:
timeField.setValue(millis() / 1000);
// Dispatch button events to the main form.
int button = lcd.getButton();
if (mainForm.dispatch(button) == FORM_CHANGED) {
if (mainForm.isCurrent(ledField)) {
if (ledField.value())
digitalWrite(STATUS_LED, HIGH);
else
digitalWrite(STATUS_LED, LOW);
}
}
}

View File

@ -1,5 +1,21 @@
FreetronicsLCD KEYWORD1 FreetronicsLCD KEYWORD1
Form KEYWORD1
Field KEYWORD1
BoolField KEYWORD1
IntField KEYWORD1
TextField KEYWORD1
TimeField KEYWORD1
getButton KEYWORD2 getButton KEYWORD2
enableScreenSaver KEYWORD2 enableScreenSaver KEYWORD2
disableScreenSaver KEYWORD2 disableScreenSaver KEYWORD2
isScreenSaved KEYWORD2 isScreenSaved KEYWORD2
show KEYWORD2
hide KEYWORD2
dispatch KEYWORD2
isCurrent KEYWORD2
setLabel KEYWORD2
setValue KEYWORD2
value KEYWORD2