|
| 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