Skip to content

Commit 51bcce7

Browse files
authored
Create menuFields.h
1 parent 14e0939 commit 51bcce7

File tree

1 file changed

+261
-0
lines changed

1 file changed

+261
-0
lines changed

src/menuFields.h

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/********************
2+
www.r-site.net
3+
Nov. 2014 Rui Azevedo - ruihfazevedo(@rrob@)gmail.com
4+
implementing menu fields as options that show a value
5+
value (variable reference) can be changed by either using:
6+
menuField - for numeric varibles between range and optinally with a tune speed
7+
menuChoose - Use menu like navigation to select variable value
8+
menuToggle - cycle list of possible values
9+
10+
class menuValue is used as a menu prompt with an associated value for menuChoose and menuToggle
11+
12+
this classes are implemented as templates to accomodate virtually any value type
13+
14+
creative commons license 3.0: Attribution-ShareAlike CC BY-SA
15+
This software is furnished "as is", without technical support, and with no
16+
warranty, express or implied, as to its usefulness for any purpose.
17+
18+
Thread Safe: No
19+
Extensible: Yes
20+
21+
v2.0 - Calling action on every elements
22+
23+
***/
24+
25+
#ifndef RSITE_ARDUINOP_MENU_FIELDS
26+
#define RSITE_ARDUINOP_MENU_FIELDS
27+
28+
#include "menu.h"
29+
30+
//prompts holding values for choose and toggle
31+
//TODO: 'action' is not needed here, we could implement it but its kinda redundant
32+
// either implement it or remove it (by forking class derivation earlier)
33+
// not that we dont need a function to be called on variable change,
34+
// but this function MUST be defined on menu levell, not on setting level
35+
// rah Nov.2014
36+
template<typename T>
37+
class menuValue:public prompt {
38+
public:
39+
T value;
40+
inline menuValue(const char * text,T value):prompt(text),value(value) {}
41+
inline menuValue(const char * text,T value,promptAction action)
42+
:prompt(text,action),value(value) {}
43+
};
44+
45+
//Prompt linked to a variable
46+
//TODO: implement Escape on a field cancels the editting (undo) restoring the value
47+
static const char* numericChars="0123456789.";
48+
template <typename T>
49+
class menuField:public menuNode {
50+
public:
51+
T& value;
52+
T lastDrawn;
53+
const char* units;
54+
T low,high,step,tune;
55+
bool tunning;
56+
promptFeedback (*func)();
57+
char ch;
58+
T tmp;
59+
menuField(T &value,const char * text,const char *units,T low,T high,T step,T tune=0,promptFeedback (*func)()=nothing)
60+
:menuNode(text),value(value),units(units),low(low),high(high),step(step),tune(tune),func(func),tunning(false),ch(0),tmp(value) {}
61+
virtual bool needRedraw(menuOut&,bool selected) {
62+
//if (selected&&menu::activeNode==this) {
63+
bool r=value!=lastDrawn;
64+
lastDrawn=value;
65+
return r;
66+
/*} else if (tmp!=value) {
67+
tmp=value;
68+
return true;
69+
}*/
70+
}
71+
virtual void printTo(menuOut& p) {
72+
print_P(p,text);
73+
p.print(activeNode==this?(tunning?'>':':'):' ');
74+
p.print(value);
75+
print_P(p,units);
76+
}
77+
void clamp() {
78+
if (value<low) value=low;
79+
#ifdef ONLY_UP_KEY
80+
else if (value>high) value=low;
81+
#else
82+
else if (value>high) value=high;
83+
#endif
84+
}
85+
//lazy drawing, we have no drawing position here... so we will ask the menu to redraw
86+
virtual promptFeedback activate(menuOut& p,Stream&c,bool canExit=false) {
87+
if (activeNode!=this) {
88+
if (action(*this,p,c)) return true;;
89+
ox=activeNode->ox;
90+
oy=activeNode->oy;
91+
previousMenu=(menu*)activeNode;
92+
activeNode=this;
93+
p.lastSel=-1;
94+
previousMenu->printMenu(p,previousMenu->canExit);
95+
}
96+
previousMenu->printMenu(p,previousMenu->canExit);
97+
if (!c.available()) return 0;
98+
if (strchr(numericChars,c.peek())) {//a numeric value was entered
99+
value=c.parseFloat();
100+
tunning=false;
101+
activeNode=previousMenu;
102+
c.flush();
103+
ch=menu::enterCode;
104+
} else {
105+
ch=c.read();
106+
tmp=value;
107+
if (ch==menu::enterCode) {
108+
if (tunning||!tune) {//then exit edition
109+
tunning=false;
110+
activeNode=previousMenu;
111+
c.flush();
112+
} else tunning=true;
113+
} else if (ch=='+') value+=tunning?tune:step;
114+
else if (ch=='-') value-=tunning?tune:step;
115+
}
116+
clamp();
117+
if (value!=tmp||ch==menu::enterCode) {
118+
func();//call update functions
119+
p.lastSel=-1;
120+
previousMenu->printMenu(p,previousMenu->canExit);
121+
}
122+
return 0;
123+
}
124+
};
125+
126+
#ifdef DEBUG
127+
#include <menuPrint.h>
128+
inline Stream& operator<<(Stream& o,prompt& p) {
129+
menuPrint mo(o);
130+
p.printTo(mo);
131+
return o;
132+
}
133+
#endif
134+
135+
template<typename T>
136+
class menuVariant:public menu {
137+
public:
138+
T& target;
139+
menuVariant(T& target,const char *text,unsigned int sz,menuValue<T>* const data[]):
140+
menu(text,sz,(prompt**)data),target(target) {sync();}
141+
virtual bool needRedraw(menuOut&p,bool selected) {
142+
//Serial<<*(prompt*)this<<".needRedraw?"<<endl;
143+
bool nr=((menuValue<T>*)pgmPtrNear(data[sel]))->value!=target;//||p.lastSel!=sel;
144+
//T v=((menuValue<T>*)pgmPtrNear(data[sel]))->value;
145+
//if (nr) Serial<<"Variant need redraw:"<<*this<<endl<<"value:"<<v<<" target:"<<target<<" sel:"<<sel<<" lastSel:"<<p.lastSel<<endl;;
146+
return nr;
147+
}
148+
void sync() {//if possible make selection match the target value
149+
sel=0;
150+
for(int n=0;n<sz;n++)
151+
if (((menuValue<T>*)pgmPtrNear(data[n]))->value==target)
152+
sel=n;
153+
}
154+
virtual void printTo(menuOut& p) {
155+
menuVariant<T>::sync();
156+
print_P(p,text);
157+
p.print(' ');
158+
((prompt*)pgmPtrNear(data[sel]))->printTo(p);
159+
//print_P(p,((menuValue<T>*)pgmPtrNear(data[sel]))->text);
160+
}
161+
};
162+
163+
template<typename T>
164+
class menuSelect: public menuVariant<T> {
165+
public:
166+
menuSelect(const char *text,unsigned int sz,menuValue<T>* const data[],T& target):
167+
menuVariant<T>(target,text,sz,data) {menuVariant<T>::sync();}
168+
virtual void printTo(menuOut& p) {
169+
menuVariant<T>::sync();
170+
print_P(p,menu::text);
171+
p.print(menu::activeNode==this?':':' ');
172+
((prompt*)pgmPtrNear(menu::data[menu::sel]))->printTo(p);
173+
}
174+
promptFeedback activate(menuOut& p,Stream& c,bool) {
175+
if (menu::activeNode!=this) {
176+
if (menuVariant<T>::action(*this,p,c)) return true;
177+
this->setPosition(menuNode::activeNode->ox,menuNode::activeNode->oy);
178+
menu::previousMenu=(menu*)menu::activeNode;
179+
menu::activeNode=this;
180+
this->canExit=false;
181+
if (p.top>menu::sel) p.top=menu::sel;
182+
else if (menu::sel+1>p.maxY) p.top=menu::sel-p.maxY+1;
183+
p.lastSel=-1;//redraw only affected option
184+
}
185+
int lsel=menu::sel;
186+
int op=menu::menuKeys(p,c,false);
187+
if (op>=0&&op<this->menu::sz) {
188+
menu::sel=op;
189+
menuValue<T>* cp=(menuValue<T>*)pgmPtrNear(this->menu::data[op]);
190+
if (cp->enabled) {
191+
menuVariant<T>::target=cp->value;
192+
cp->activate(p,c,true);
193+
p.lastSel=-1;//redraw only affected option
194+
//and exit
195+
this->menu::activeNode=this->menu::previousMenu;
196+
c.flush();//reset the encoder
197+
}
198+
}
199+
if (menu::sel!=lsel) {
200+
menuVariant<T>::target=((menuValue<T>*)&menu::operator[](menu::sel))->value;
201+
p.lastSel=-1;//redraw only affected option
202+
}
203+
menu::previousMenu->menu::printMenu(p,menu::previousMenu->canExit);
204+
//Serial<<"sel:"<<menu::sel<<" op:"<<op<<endl;
205+
return false;
206+
}
207+
};
208+
209+
template<typename T>
210+
class menuChoice: public menuVariant<T> {
211+
public:
212+
menuChoice(const char *text,unsigned int sz,menuValue<T>* const data[],T& target):
213+
menuVariant<T>(target,text,sz,data) {menuVariant<T>::sync();}
214+
215+
//ignore canExit (this exists by select), however we could use a cancel option instead of Exit
216+
promptFeedback activate(menuOut& p,Stream& c,bool) {
217+
if (menu::activeNode!=this) {
218+
if (menuVariant<T>::action(*this,p,c)) return true;
219+
this->setPosition(menuNode::activeNode->ox,menuNode::activeNode->oy);
220+
this->menu::previousMenu=(menu*)menu::activeNode;
221+
menu::activeNode=this;
222+
this->canExit=false;
223+
if (p.top>menu::sel) p.top=menu::sel;
224+
else if (menu::sel+1>p.maxY) p.top=menu::sel-p.maxY+1;
225+
}
226+
int op=-1;
227+
menu::printMenu(p,false);
228+
op=menu::menuKeys(p,c,false);
229+
if (op>=0&&op<this->menu::sz) {
230+
this->menu::sel=op;
231+
menuValue<T>* cp=(menuValue<T>*)pgmPtrNear(this->menu::data[op]);
232+
if (cp->enabled) {
233+
this->menuVariant<T>::target=cp->value;
234+
cp->activate(p,c,true);
235+
//and exit
236+
this->menu::activeNode=this->menu::previousMenu;
237+
c.flush();//reset the encoder
238+
}
239+
}
240+
return false;
241+
}
242+
};
243+
template<typename T>
244+
class menuToggle: public menuVariant<T> {
245+
public:
246+
247+
menuToggle(const char *text,unsigned int sz,menuValue<T>* const data[],T& target):
248+
menuVariant<T>(target,text,sz,data) {menuVariant<T>::sync();}
249+
250+
promptFeedback activate(menuOut& p,Stream& c,bool canExit) {
251+
if (menuVariant<T>::action(*this,p,c)) return true;
252+
this->menu::sel++;
253+
if (this->menu::sel>=this->menu::sz) this->menu::sel=0;
254+
p.lastSel=-1;//redraw only affected option
255+
menuValue<T>* cp=(menuValue<T>*)pgmPtrNear(this->menu::data[menu::sel]);
256+
this->menuVariant<T>::target=cp->value;
257+
cp->activate(p,c,true);
258+
return 0;
259+
}
260+
};
261+
#endif

0 commit comments

Comments
 (0)