Buttonクラス 画面を切り替えながら、ソフトウェアの設定を行う。
Pageクラスを作成して、その中に、複数のButtonを所属させます。オブジェクト指向言語であるC++の特性を利用して、画面ページをクラス化、ページ単位で、画面の遷移を行い、UI操作を簡便にしました。Buttonクラスのコンストラクタとデストラクタが画面の遷移を円滑に処理します。
コンストラクタが起動して、Buttonがdrawメソッドで描画されます。このとき、TFT液晶のテキスト描画設定が保存され、ボタン描画ためのテキスト設定に変更されます(drawメソッド内で実行されるTFT->pushState())。描画し終わったら、TFT->popState();でもとのテキスト描画設定に、戻しています。
自作したPageインスタンス が、スコープの範囲を出ると、デストラクタが起動。PageインスタンスとButtonインスタンスが、メモリから消去されていきます。画面描画はまだ残っているので、fillscreen(BLACK)で消去しています。
メモリの確保と、解放をC言語でmallocなどを使って記述することを考えると、オブジェクト指向言語であるC++の利便性は抜群です。
自主トレ3 カウントダウンタイマー の動作内容
メインの画面から、Menu画面に移行します。メニュー画面でカウント値の設定を指定したら、次の画面(Page1)で、PlusボタンとMinusボタンで、カウント値を決定します。OKボタンをおしたら、メイン画面に戻り、カウントダウンを始めます。
まず、Buttonインスタンスを理解するために、M5Core2のヘッダーファイルを、覗いてみます。
class M5Core2 {
public:
M5Core2();
[[deprecated("It is recommended to use M5Unified Lib, which will be discontinued soon.")]]
void begin(bool LCDEnable = true, bool SDEnable = true,
bool SerialEnable = true, bool I2CEnable = false,
mbus_mode_t mode = kMBusModeOutput, bool SpeakerEnable = true);
void update();
void shutdown();
int shutdown(int seconds);
int shutdown(const RTC_TimeTypeDef &RTC_TimeStruct);
int shutdown(const RTC_DateTypeDef &RTC_DateStruct,
const RTC_TimeTypeDef &RTC_TimeStruct);
// LCD
M5Display Lcd;
// Power
AXP Axp;
// Touch
M5Touch Touch;
// Buttons (global button and gesture functions)
M5Buttons Buttons;
// Default "button" that gets events where there is no button.
Button background = Button(0, 0, TOUCH_W, TOUCH_H, true, "background");
// Touch version of the buttons on older M5stack cores, below screen
Button BtnA = Button(10, 240, 110, 40, true, "BtnA");
Button BtnB = Button(130, 240, 70, 40, true, "BtnB");
Button BtnC = Button(230, 240, 80, 40, true, "BtnC");
MPU6886 IMU;
// I2C
CommUtil I2C;
RTC Rtc;
Speaker Spk;
/**
* Functions have been moved to Power class for compatibility.
* These will be removed in a future release.
*/
void setPowerBoostKeepOn(bool en) __attribute__((deprecated));
void setWakeupButton(uint8_t button) __attribute__((deprecated));
void powerOFF() __attribute__((deprecated));
private:
bool isInited;
};
extern M5Core2 M5;
液晶全体がbackgroundという、Buttonクラスインスタンスとしてオブジェクト化されています。それと、画面下のAボタン、Bボタン、Cボタンの3つが、インスタンス化されています。Buttonクラスの定義を見てみますと、
class Button : public Zone {
public:
static std::vector<Button*> instances;
<略>
};
std::vectorというC++の動的配列を扱うコンテナクラスがstaticメンバー変数として定義されています。
Buttonクラスのコンストラクタが起動すると、作られたButtonインスタンスは、このvectorにpush_back、つまり、動的配列の末尾に追加されていきます。
最初のvectorのsizeは、4個(backgroundと、A,B,Cボタン)ということになります。
<略>
void Button::init() {
_state = _tapWait = _pressing = _manuallyRead = false;
_time = _lastChange = _pressTime = millis();
_hold_time = -1;
_textFont = _textSize = 0;
_freeFont = nullptr;
drawFn = nullptr;
_compat = 0;
drawZone = Zone();
tapTime = TAP_TIME;
dbltapTime = DBLTAP_TIME;
longPressTime = LONGPRESS_TIME;
repeatDelay = REPEAT_DELAY;
repeatInterval = REPEAT_INTERVAL;
strncpy(_label, _name, 16);
if (_pin != 0xFF) pinMode(_pin, INPUT_PULLUP);
instances.push_back(this);
draw();
}
<略>
今回の実行コード
#ifndef __MYBUTTON1_HPP__
#define __MYBUTTON1_HPP__
#include <M5Core2.h>
#define BUTTONSIZE_H 150
#define BUTTONSIZE_V 50
#define BUTTONGAP_H 70
/*********************************************/
// Menuページ
/*********************************************/
//menuページのコールバック関数
//静的に確保する必要があるので、クラス外宣言。
extern void menuBtnResetCallBack(Event& e);
extern void menuBtnSetPeriodCallBack(Event& e);
extern void menuBtnSetCOMMANDCallBack(Event& e);
extern void menuBtnReturnCallBack(Event& e);
/// @brief Menuページクラス
class PageMenu
{
public:
PageMenu();
~PageMenu();
void SetButtonInfo(void);//ボタンの設定
public:
//ボタンカラー(背景色、テキスト色、アウトライン色)
//menuクラス Resetボタン
Button menuBtnReset = Button( 10, 20,BUTTONSIZE_H, BUTTONSIZE_V,false,
"RESET",{RED,WHITE,TFT_TRANSPARENT},{MAGENTA,WHITE,TFT_TRANSPARENT},MC_DATUM,0,0,10);
//menuクラス Period設定ボタン
Button menuBtnSetPeriod = Button( 10, 20+BUTTONGAP_H*1,BUTTONSIZE_H, BUTTONSIZE_V,false,
"PERIOD",{GREEN,DARKGREEN,TFT_TRANSPARENT},{DARKGREEN,WHITE,TFT_TRANSPARENT},MC_DATUM,0,0,10);
//menuクラス COMMANDボタン
Button menuBtnSetCOMMAND = Button( 10, 20+BUTTONGAP_H*2,BUTTONSIZE_H, BUTTONSIZE_V,false,
"COMMAND",{YELLOW,DARKGREEN,TFT_TRANSPARENT},{ORANGE,WHITE,TFT_TRANSPARENT},MC_DATUM,0,0,10);
//menuクラス Returnボタン
Button menuBtnReturn = Button(170, 40+BUTTONGAP_H*2,BUTTONSIZE_H, BUTTONSIZE_V,false,
"RETURN",{CYAN,BLUE,TFT_TRANSPARENT},{BLUE,WHITE,TFT_TRANSPARENT},MC_DATUM,0,0,10);
public:
static bool close; //pageを閉じる
static int nextPageNumber; //次ページ番号
static String selectedMenuText; //選択したMenu文字列(次ページに渡す)
};
/*********************************************/
// Page1 値設定ページ
/*********************************************/
//page1ページのコールバック関数
//静的に確保する必要があるので、クラス外宣言。
extern void page1BtnPlusCallBack(Event& e);
extern void page1BtnMinusCallBack(Event& e);
extern void page1BtnOKCallBack(Event& e);
extern void page1BtnCancelCallBack(Event& e);
//Page1クラス
class Page1
{
public:
Page1();
~Page1();
public:
//page1 +ボタン
Button page1BtnPlus = Button( 10,20+BUTTONGAP_H*1, BUTTONSIZE_H, BUTTONSIZE_V,false,
"+:Plus",{CYAN,BLUE,TFT_TRANSPARENT},{BLUE,WHITE,TFT_TRANSPARENT},MC_DATUM,0,0,10);
//page1 -ボタン
Button page1BtnMinus = Button( 20+BUTTONSIZE_H,20+BUTTONGAP_H*1, BUTTONSIZE_H, BUTTONSIZE_V,false,
"-:Minus",{CYAN,BLUE,TFT_TRANSPARENT},{BLUE,WHITE,TFT_TRANSPARENT},MC_DATUM,0,0,10);
//page1 OKボタン
Button page1BtnOK = Button( 10, 20+BUTTONGAP_H*2, BUTTONSIZE_H, BUTTONSIZE_V,false,
"OK",{YELLOW,DARKGREEN,TFT_TRANSPARENT},{ORANGE,WHITE,TFT_TRANSPARENT},MC_DATUM,0,0,10);
//page1 Cancelボタン
Button page1BtnCancel= Button( 20+BUTTONSIZE_H, 50+BUTTONGAP_H*2,BUTTONSIZE_H, BUTTONSIZE_V,false,
"CANCELL",{LIGHTGREY,DARKGREY,TFT_TRANSPARENT},{DARKGREY,LIGHTGREY,TFT_TRANSPARENT},MC_DATUM,0,0,10);
static bool close;//Page1を閉じる
static bool OK; //OKボタン押下
static int number;//カウンタ値
static bool enCountDown;//カウント開始フラグ
};
/// @brief 汎用ボタン情報設定
/// @param bt Buttonクラスインスタンスの参照
/// @param size テキストサイズ
/// @param freeFont_ フォント指定参照
/// @param fn コールバック関数
/// @param eventMask コールバックイベントマスク(ex.E_RELEASE)
extern void BtnInfoSet(Button &bt, int size,
const GFXfont *freeFont_, EventHandlerCallback fn
,uint16_t eventMask = (uint16_t)4095U);
#endif
#include "myButton1.hpp"
/*********************************************/
// Menuページ
/*********************************************/
//PageMenuクラス staticメンバ変数宣言
bool PageMenu::close=false; //Menuを閉じる
String PageMenu::selectedMenuText=""; //次ページに表示する文字列
int PageMenu::nextPageNumber=0; //次ページ展開するとき使用。
//Menuページ Resetボタン処理
void menuBtnResetCallBack(Event& e)
{
PageMenu::selectedMenuText="RESET";
PageMenu::close=true;
}
//Menuページ Periodボタン処理
void menuBtnSetPeriodCallBack(Event& e)
{
PageMenu::selectedMenuText="Period";
PageMenu::nextPageNumber=1;
PageMenu::close=true;
}
//Menuページ COMMANDボタン処理
void menuBtnSetCOMMANDCallBack(Event& e)
{
PageMenu::selectedMenuText="COMMAND";
PageMenu::close=true;
}
//Menuページ Returnボタン処理
void menuBtnReturnCallBack(Event& e)
{
PageMenu::nextPageNumber=0;
PageMenu::close=true;
}
//PageMenuクラスコンストラクタ
PageMenu::PageMenu()
{
Serial.println("PageMenu Construction");
SetButtonInfo();
PageMenu::nextPageNumber=0;
PageMenu::close=false;
}
//PageMenuクラスデストラクタ
PageMenu::~PageMenu()
{
Serial.println("PageMenu destruction");
}
/// ボタンの設定
void PageMenu::SetButtonInfo(void)
{
BtnInfoSet(menuBtnReset,1,&FreeSans12pt7b,&menuBtnReturnCallBack,E_RELEASE);
BtnInfoSet(menuBtnSetPeriod,1,&FreeSans12pt7b,&menuBtnSetPeriodCallBack,E_RELEASE);
BtnInfoSet(menuBtnSetCOMMAND,1,&FreeSans12pt7b,&menuBtnSetCOMMANDCallBack,E_RELEASE);
BtnInfoSet(menuBtnReturn,1,&FreeSans12pt7b,&menuBtnReturnCallBack,E_RELEASE);
}
/*********************************************/
// Page1 カウンタ値設定ページ
/*********************************************/
//Page1クラス static メンバー変数
bool Page1::close=false; //Page1を閉じる
bool Page1::OK=false; //OKボタン押下
int Page1::number=0; //カウンタ値設定
bool Page1::enCountDown=false;//カウントダウン開始
//page1クラス Plusボタン処理
void page1BtnPlusCallBack(Event& e)
{
Page1::number++;
TFT->setCursor(150,50);
TFT->setTextSize(2);
TFT->fillRect(140,20,160,40,BLACK);
TFT->drawRect(140,20,160,40,YELLOW);
TFT->printf("Num:%4d",Page1::number);
}
//page1クラス Minusボタン処理
void page1BtnMinusCallBack(Event& e)
{
Page1::number--;
TFT->setCursor(150,50);
TFT->setTextSize(2);
TFT->fillRect(140,20,160,40,BLACK);
TFT->drawRect(140,20,160,40,YELLOW);
TFT->printf("Num:%4d",Page1::number);
}
//page1クラス OKボタン処理
void page1BtnOKCallBack(Event& e)
{
Page1::OK=true;
Page1::close=true;
if(Page1::number>0);
{
Page1::enCountDown=true;
}
}
//page1クラス Cancelボタン処理
void page1BtnCancelCallBack(Event& e)
{
Page1::enCountDown=false;
Page1::OK=false;
Page1::close=true;
}
//Page1クラスコンストラクタ
//カウンタ値の設定
Page1::Page1()
{
Serial.println("Page1 Construction");
Page1::OK=false;//OKボタン押下
Page1::enCountDown=false;//カウントダウン開始フラグ
Page1::close=false;//Page1閉じる
//前ページの設定文字列表示
TFT->setCursor(3,50);
TFT->setTextSize(2);
TFT->printf("%s",PageMenu::selectedMenuText);
//カウンタ値の表示
TFT->setCursor(150,50);
TFT->setTextSize(2);
TFT->drawRect(140,20,160,40,YELLOW);
TFT->printf("Num:%4d",Page1::number);
//ボタン設定、表示
BtnInfoSet(page1BtnPlus,1,&FreeSans12pt7b,&page1BtnPlusCallBack,E_RELEASE);
BtnInfoSet(page1BtnMinus,1,&FreeSans12pt7b,&page1BtnMinusCallBack,E_RELEASE);
BtnInfoSet(page1BtnOK,1,&FreeSans12pt7b,&page1BtnOKCallBack,E_RELEASE);
BtnInfoSet(page1BtnCancel,1,&FreeSans12pt7b,&page1BtnCancelCallBack,E_RELEASE);
}
//Page1クラス デストラクタ
Page1::~Page1()
{
Serial.println("Page1 destruction");
}
//*****************************************
//utilities
//*****************************************
/// @brief Buttonクラス情報設定関数
/// @param bt Buttonインスタンス
/// @param size TextSize
/// @param freeFont_ freefonttype
void BtnInfoSet(Button &bt, int size, const GFXfont *freeFont_,
EventHandlerCallback fn,uint16_t eventMask)
{
bt.setFreeFont(freeFont_);
bt.setTextSize(size);
bt.addHandler(fn,eventMask);
bt.draw();
}
#include <M5Core2.h>
#include "myButton1.hpp"
//Buttonクラスの練習帳
//Page展開
#define LCDWIDE 320
#define LCDHEIGHT 240
#define ZONE_WIDE 130
#define ZONE_HEIGHT 50
#define ZONE_WIDE_HALF 50
#define ZONE_HEIGHT_HALF 25
TFT_eSprite canvas = TFT_eSprite(&M5.Lcd);
void Page0Paint(void);
void setup()
{
M5.begin();
canvas.createSprite(320, 240); //Create a 320x240 canvas.
canvas.setColorDepth(8); // Set color depth.
pinMode(32,OUTPUT); //ブザー
Page1::enCountDown=false;
Page1::number=0;
Page1::OK=false;
Page0Paint(); //画面描画
}
static int counter;
void loop()
{
M5.update();
if(Page1::enCountDown)
{//カウントダウン中----------------------------
digitalWrite(32,1);
delay(200);
digitalWrite(32,0);
counter--;
if(counter==0)
{
Page1::enCountDown=false;
Page0Paint();
canvas.setCursor(70,220-20);
canvas.setTextColor(RED);
canvas.println("Time UP!!");
canvas.pushSprite(0,0);
do
{
//for(int j=0; j<4; j++)
{
digitalWrite(32,1);
delay(50);
digitalWrite(32,0);
delay(50);
}
M5.update();
if(M5.BtnC.wasPressed())
{
break;
}
}while(1);
counter=Page1::number;
}
}
//ボタンA Menu表示(PageMenuを開く)
if(M5.BtnA.wasPressed())
{
canvas.fillScreen(BLACK);
canvas.pushSprite(0,0);
PageMenu pmenu;
while(1)
{
M5.update();
if(PageMenu::close)
{
//Serial.println("Close");
break;
}
delay(10);
}
canvas.fillScreen(BLACK);
canvas.pushSprite(0,0);
//Page1の処理
if(PageMenu::nextPageNumber==1)
{//Periodの設定
Page1 page1;
while(1)
{
M5.update();
if(Page1::close)
{
break;
}
delay(10);
}
if(Page1::enCountDown)
{
counter=Page1::number;
}
}
canvas.fillScreen(BLACK);
canvas.pushSprite(0,0);
}
if(M5.BtnB.wasPressed())
{
if(Page1::number>0)
{
Page1::enCountDown=true;
}
}
Page0Paint();
if(Page1::enCountDown)
delay(1000);
else
delay(1);
}
void Page0Paint(void)
{
canvas.fillScreen(BLACK);
canvas.setFreeFont(&FreeSans12pt7b);
canvas.setTextDatum(MC_DATUM);
canvas.setTextSize(0);
canvas.setCursor(0,20);
canvas.setTextColor(RED);
canvas.println("M5StackCore2");
canvas.setTextColor(ORANGE);
canvas.println("Button Class Test");
canvas.setTextColor(GREEN);
canvas.println("Some pages have buttons.");
if(Page1::enCountDown)
{
canvas.setCursor(50,220-20);
canvas.setFreeFont(&Orbitron_Light_32);
canvas.setTextColor(GREEN);
canvas.println("Count Down");
}
canvas.setFreeFont(&FreeSans12pt7b);
canvas.setTextColor(CYAN,LIGHTGREY);
canvas.setCursor(30,230);canvas.print("SET");
canvas.setCursor(110,230);canvas.print("START");
canvas.setCursor(230,230);canvas.print("STOP");
canvas.fillRect(70,110-10,180,60,BLUE);
canvas.setCursor(80,160-10);
canvas.setTextColor(WHITE);
for(int w=0; w<8; w++)
{
canvas.drawRect(70-w,110-w-10,180+w*2,60+w*2,ORANGE);
}
canvas.setFreeFont(&Orbitron_Light_32);
canvas.printf("Num:%4d",counter);
canvas.pushSprite(0,0);
}