From 7de182bc64f029ef3ff85dbdd252100953894fd2 Mon Sep 17 00:00:00 2001
From: Rop Gonggrijp <rop@gonggri.jp>
Date: Thu, 1 Oct 2020 16:54:49 +0200
Subject: [PATCH 1/8] Initial commit M5Button & M5Display mod

---
 src/M5Display.cpp            |  79 ++++-
 src/M5Display.h              |  46 ++-
 src/M5Stack.h                |  18 +-
 src/utility/Button.cpp       | 140 ---------
 src/utility/Button.h         |  47 ---
 src/utility/M5Button.cpp     | 587 +++++++++++++++++++++++++++++++++++
 src/utility/M5Button.h       | 242 +++++++++++++++
 src/utility/PointAndZone.cpp | 154 +++++++++
 src/utility/PointAndZone.h   |  44 +++
 9 files changed, 1161 insertions(+), 196 deletions(-)
 delete mode 100644 src/utility/Button.cpp
 delete mode 100644 src/utility/Button.h
 create mode 100644 src/utility/M5Button.cpp
 create mode 100644 src/utility/M5Button.h
 create mode 100644 src/utility/PointAndZone.cpp
 create mode 100644 src/utility/PointAndZone.h

diff --git a/src/M5Display.cpp b/src/M5Display.cpp
index 1d920a85..5f5ea038 100644
--- a/src/M5Display.cpp
+++ b/src/M5Display.cpp
@@ -1,8 +1,18 @@
 #include "M5Display.h"
 
+#ifdef M5Stack_M5Core2
+  #include <M5Touch.h>
+  #define TOUCH M5Touch::instance
+#endif
+
 #define BLK_PWM_CHANNEL 7 // LEDC_CHANNEL_7
 
-M5Display::M5Display() : TFT_eSPI() {}
+M5Display::M5Display() : TFT_eSPI() {
+  // So we can use the display without including all of M5Core2 / M5Stack 
+  if (!instance) instance = this;
+}
+
+M5Display* M5Display::instance;
 
 void M5Display::begin() {
   TFT_eSPI::begin();
@@ -607,3 +617,70 @@ void M5Display::drawPngUrl(const char *url, uint16_t x, uint16_t y,
   pngle_destroy(pngle);
   http.end();
 }
+
+
+// Saves and restores font properties, datum, cursor, colors
+
+void M5Display::pushState() {
+  DisplayState s;
+  s.textfont = textfont;
+  s.textsize = textsize;
+  s.textcolor = textcolor;
+  s.textbgcolor = textbgcolor;
+  s.cursor_x = cursor_x;
+  s.cursor_y = cursor_y;
+  s.padX = padX;
+  s.gfxFont = gfxFont;
+  _displayStateStack.push_back(s);
+}
+
+void M5Display::popState() {
+  if (_displayStateStack.empty()) return;
+  DisplayState s = _displayStateStack.back();
+  _displayStateStack.pop_back();
+  textfont = s.textfont;
+  textsize = s.textsize;
+  textcolor = s.textcolor;
+  textbgcolor = s.textbgcolor;
+  cursor_x = s.cursor_x;
+  cursor_y = s.cursor_y;
+  padX = s.padX;
+  if (s.gfxFont && s.gfxFont != gfxFont) setFreeFont(s.gfxFont);
+}
+  
+
+#ifdef M5Stack_M5Core2
+
+  // Mke sure the touch also rotates if it's not there already
+  void M5Display::setRotation(uint8_t r) {
+    TFT_eSPI::setRotation(r);
+    TOUCH->rotation = r;
+  }
+
+  #ifdef TFT_eSPI_TOUCH_EMULATION
+
+    // Emulates the native (resistive) TFT_eSPI touch interface using M5.Touch
+
+    uint8_t M5Display::getTouchRaw(uint16_t *x, uint16_t *y) { return getTouch(x, y); }
+
+    uint16_t M5Display::getTouchRawZ(void) { return (TOUCH->ispressed()) ? 1000 : 0; }
+
+    void M5Display::convertRawXY(uint16_t *x, uint16_t *y)  { return; }
+
+    uint8_t M5Display::getTouch(uint16_t *x, uint16_t *y, uint16_t threshold /* = 600 */) {
+      TOUCH->read();
+      if (TOUCH->points) {
+        *x = TOUCH->point[0].x;
+        *y = TOUCH->point[0].y;
+        return true;
+      }
+      return false;
+    }
+
+    void M5Display::calibrateTouch(uint16_t *data, uint32_t color_fg, uint32_t color_bg, uint8_t size) { return; }
+
+    void M5Display::setTouch(uint16_t *data) { return; }
+
+  #endif /* TFT_eSPI_TOUCH_EMULATION */
+
+#endif /* M5Stack_M5Core2 */
\ No newline at end of file
diff --git a/src/M5Display.h b/src/M5Display.h
index b5818a45..c3e027d5 100644
--- a/src/M5Display.h
+++ b/src/M5Display.h
@@ -1,6 +1,7 @@
 #ifndef _M5DISPLAY_H_
   #define _M5DISPLAY_H_
 
+  #include "utility/Config.h" // This is where Core2 defines would be
   #include <Arduino.h>
   #include <FS.h>
   #include <SPI.h>
@@ -14,9 +15,17 @@
     JPEG_DIV_8,
     JPEG_DIV_MAX
   } jpeg_div_t;
+  
+  struct DisplayState {
+    uint8_t textfont, textsize, datum;
+    const GFXfont* gfxFont;
+    uint32_t textcolor, textbgcolor;
+    int32_t  cursor_x, cursor_y, padX;
+  };
 
   class M5Display : public TFT_eSPI {
     public:
+      static M5Display* instance;
       M5Display();
       void begin();
       void sleep();
@@ -91,7 +100,40 @@
                     uint16_t maxWidth = 0, uint16_t maxHeight = 0,
                     uint16_t offX = 0, uint16_t offY = 0,
                     double scale = 1.0, uint8_t alphaThreshold = 127);
+                    
+
+      // Saves and restores font properties, datum, cursor, colors so code
+      // can be non-invasive. Just make sure that every push is also popped
+      // when you're done to prevent stack from growing out of control.
+      //
+      // (User code can never do this completely because the gfxFont
+      // class variable of TFT_eSPI is protected.)
+      #define M5DISPLAY_HAS_PUSH_POP
+      public:
+        void pushState();
+        void popState();
+      private:
+        std::vector<DisplayState> _displayStateStack;
+    
 
-    private:
+    #ifdef M5Stack_M5Core2
+    
+      public:
+        // Make sure the touch also rotates if it's not there already
+        void setRotation(uint8_t r);
+            
+      #ifdef TFT_eSPI_TOUCH_EMULATION   
+        // Emulates the TFT_eSPI touch interface using M5.Touch
+        public:
+          uint8_t  getTouchRaw(uint16_t *x, uint16_t *y);
+          uint16_t getTouchRawZ(void);
+          void     convertRawXY(uint16_t *x, uint16_t *y);
+          uint8_t  getTouch(uint16_t *x, uint16_t *y, uint16_t threshold = 600);
+          void     calibrateTouch(uint16_t *data, uint32_t color_fg, uint32_t color_bg, uint8_t size);
+          void     setTouch(uint16_t *data);
+      #endif /* TFT_eSPI_TOUCH_EMULATION */
+    
+    #endif /* M5Stack_M5Core2 */
+  
   };
-#endif
+#endif /* _M5DISPLAY_H_ */
diff --git a/src/M5Stack.h b/src/M5Stack.h
index f0f00f85..421578d8 100644
--- a/src/M5Stack.h
+++ b/src/M5Stack.h
@@ -107,7 +107,7 @@
 
     #include "M5Display.h"
     #include "utility/Config.h"
-    #include "utility/Button.h"
+    #include "utility/M5Button.h"
     #include "utility/Speaker.h"
     #include "utility/Power.h"
     #include "utility/CommUtil.h"
@@ -125,17 +125,23 @@
         void begin(bool LCDEnable = true, bool SDEnable = true, bool SerialEnable = true, bool I2CEnable = false);
         void update();
 
+        // Buttons (for things that involve all buttons)
+        M5Buttons Buttons;
+
         // Button API
         #define DEBOUNCE_MS 10
-        Button BtnA = Button(BUTTON_A_PIN, true, DEBOUNCE_MS);
-        Button BtnB = Button(BUTTON_B_PIN, true, DEBOUNCE_MS);
-        Button BtnC = Button(BUTTON_C_PIN, true, DEBOUNCE_MS);
+        Button BtnA = Button(BUTTON_A_PIN, true, DEBOUNCE_MS, "hw", 3, 218, 102, 21, true, "BtnA");
+        Button BtnB = Button(BUTTON_B_PIN, true, DEBOUNCE_MS, "hw", 109, 218, 102, 21, true, "BtnB");
+        Button BtnC = Button(BUTTON_C_PIN, true, DEBOUNCE_MS, "hw", 215, 218, 102, 21, true, "BtnC");
+        
+        // Events
+        M5Events Events;
 
         // SPEAKER
         SPEAKER Speaker;
 
         // LCD
-        M5Display Lcd = M5Display();
+        M5Display Lcd;
 
         //Power
         POWER Power;
@@ -151,7 +157,7 @@
         #endif
 
         // I2C
-        CommUtil I2C = CommUtil();
+        CommUtil I2C;
           
         /**
         * Function has been move to Power class.(for compatibility)
diff --git a/src/utility/Button.cpp b/src/utility/Button.cpp
deleted file mode 100644
index 9077399d..00000000
--- a/src/utility/Button.cpp
+++ /dev/null
@@ -1,140 +0,0 @@
-/*----------------------------------------------------------------------*
- * Arduino Button Library v1.0                                          *
- * Jack Christensen May 2011, published Mar 2012                        *
- *                                                                      *
- * Library for reading momentary contact switches like tactile button   *
- * switches. Intended for use in state machine constructs.              *
- * Use the read() function to read all buttons in the main loop,        *
- * which should execute as fast as possible.                            *
- *                                                                      *
- * This work is licensed under the Creative Commons Attribution-        *
- * ShareAlike 3.0 Unported License. To view a copy of this license,     *
- * visit http://creativecommons.org/licenses/by-sa/3.0/ or send a       *
- * letter to Creative Commons, 171 Second Street, Suite 300,            *
- * San Francisco, California, 94105, USA.                               *
- *----------------------------------------------------------------------*/
-
-#include "Button.h"
-
-/*----------------------------------------------------------------------*
- * Button(pin, puEnable, invert, dbTime) instantiates a button object.  *
- * pin      Is the Arduino pin the button is connected to.              *
- * puEnable Enables the AVR internal pullup resistor if != 0 (can also  *
- *          use true or false).                                         *
- * invert   If invert == 0, interprets a high state as pressed, low as  *
- *          released. If invert != 0, interprets a high state as        *
- *          released, low as pressed  (can also use true or false).     *
- * dbTime   Is the debounce time in milliseconds.                       *
- *                                                                      *
- * (Note that invert cannot be implied from puEnable since an external  *
- *  pullup could be used.)                                              *
- *----------------------------------------------------------------------*/
-Button::Button(uint8_t pin, uint8_t invert, uint32_t dbTime) {
-  _pin = pin;
-  _invert = invert;
-  _dbTime = dbTime;
-  pinMode(_pin, INPUT_PULLUP);
-  _state = digitalRead(_pin);
-  if (_invert != 0) _state = !_state;
-  _time = millis();
-  _lastState = _state;
-  _changed = 0;
-  _hold_time = -1;
-  _lastTime = _time;
-  _lastChange = _time;
-  _pressTime = _time;
-}
-
-/*----------------------------------------------------------------------*
- * read() returns the state of the button, 1==pressed, 0==released,     *
- * does debouncing, captures and maintains times, previous states, etc. *
- *----------------------------------------------------------------------*/
-uint8_t Button::read(void) {
-  static uint32_t ms;
-  static uint8_t pinVal;
-
-  ms = millis();
-  pinVal = digitalRead(_pin);
-  if (_invert != 0) pinVal = !pinVal;
-  if (ms - _lastChange < _dbTime) {
-    _lastTime = _time;
-    _time = ms;
-    _changed = 0;
-    return _state;
-  }
-  else {
-    _lastTime = _time;
-    _time = ms;
-    _lastState = _state;
-    _state = pinVal;
-    if (_state != _lastState) {
-      _lastChange = ms;
-      _changed = 1;
-      if (_state) { _pressTime = _time; }
-    }
-    else {
-      _changed = 0;
-    }
-    return _state;
-  }
-}
-
-/*----------------------------------------------------------------------*
- * isPressed() and isReleased() check the button state when it was last *
- * read, and return false (0) or true (!=0) accordingly.                *
- * These functions do not cause the button to be read.                  *
- *----------------------------------------------------------------------*/
-uint8_t Button::isPressed(void) {
-  return _state == 0 ? 0 : 1;
-}
-
-uint8_t Button::isReleased(void) {
-  return _state == 0 ? 1 : 0;
-}
-
-/*----------------------------------------------------------------------*
- * wasPressed() and wasReleased() check the button state to see if it   *
- * changed between the last two reads and return false (0) or           *
- * true (!=0) accordingly.                                              *
- * These functions do not cause the button to be read.                  *
- *----------------------------------------------------------------------*/
-uint8_t Button::wasPressed(void) {
-  return _state && _changed;
-}
-
-uint8_t Button::wasReleased(void) {
-  return !_state && _changed && millis() - _pressTime < _hold_time;
-}
-
-uint8_t Button::wasReleasefor(uint32_t ms) {
-  _hold_time = ms;
-  return !_state && _changed && millis() - _pressTime >= ms;
-}
-/*----------------------------------------------------------------------*
- * pressedFor(ms) and releasedFor(ms) check to see if the button is     *
- * pressed (or released), and has been in that state for the specified  *
- * time in milliseconds. Returns false (0) or true (1) accordingly.     *
- * These functions do not cause the button to be read.                  *
- *----------------------------------------------------------------------*/
-uint8_t Button::pressedFor(uint32_t ms) {
-  return (_state == 1 && _time - _lastChange >= ms) ? 1 : 0;
-}
-
-uint8_t Button::pressedFor(uint32_t ms, uint32_t continuous_time) {
-  if (_state == 1 && _time - _lastChange >= ms && _time - _lastLongPress >= continuous_time) {
-    _lastLongPress = _time;
-    return 1;
-  } 
-  return 0;
-}
-
-uint8_t Button::releasedFor(uint32_t ms) {
-  return (_state == 0 && _time - _lastChange >= ms) ? 1 : 0;
-}
-/*----------------------------------------------------------------------*
- * lastChange() returns the time the button last changed state,         *
- * in milliseconds.                                                     *
- *----------------------------------------------------------------------*/
-uint32_t Button::lastChange(void) {
-  return _lastChange;
-}
diff --git a/src/utility/Button.h b/src/utility/Button.h
deleted file mode 100644
index 13298040..00000000
--- a/src/utility/Button.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*----------------------------------------------------------------------*
- * Arduino Button Library v1.0                                          *
- * Jack Christensen Mar 2012                                            *
- *                                                                      *
- * This work is licensed under the Creative Commons Attribution-        *
- * ShareAlike 3.0 Unported License. To view a copy of this license,     *
- * visit http://creativecommons.org/licenses/by-sa/3.0/ or send a       *
- * letter to Creative Commons, 171 Second Street, Suite 300,            *
- * San Francisco, California, 94105, USA.                               *
- *----------------------------------------------------------------------*/
-#ifndef Button_h
-#define Button_h
-// #if ARDUINO >= 100
-#include <Arduino.h>
-// #else
-// #include <WProgram.h>
-// #endif
-class Button {
-  public:
-    Button(uint8_t pin, uint8_t invert, uint32_t dbTime);
-    uint8_t read();
-    uint8_t isPressed();
-    uint8_t isReleased();
-    uint8_t wasPressed();
-    uint8_t wasReleased();
-    uint8_t pressedFor(uint32_t ms);
-    uint8_t pressedFor(uint32_t ms, uint32_t continuous_time);
-    uint8_t releasedFor(uint32_t ms);
-    uint8_t wasReleasefor(uint32_t ms);
-    uint32_t lastChange();
-
-  private:
-    uint8_t _pin;           //arduino pin number
-    uint8_t _puEnable;      //internal pullup resistor enabled
-    uint8_t _invert;        //if 0, interpret high state as pressed, else interpret low state as pressed
-    uint8_t _state;         //current button state
-    uint8_t _lastState;     //previous button state
-    uint8_t _changed;       //state changed since last read
-    uint32_t _time;         //time of current state (all times are in ms)
-    uint32_t _lastTime;     //time of previous state
-    uint32_t _lastChange;   //time of last state change
-    uint32_t _lastLongPress;   //time of last state change
-    uint32_t _dbTime;       //debounce time
-    uint32_t _pressTime;    //press time
-    uint32_t _hold_time;    //hold time call wasreleasefor
-};
-#endif
diff --git a/src/utility/M5Button.cpp b/src/utility/M5Button.cpp
new file mode 100644
index 00000000..3d3dd3db
--- /dev/null
+++ b/src/utility/M5Button.cpp
@@ -0,0 +1,587 @@
+#include "M5Button.h"
+
+
+// Button class
+
+/* static */ std::vector<Button*> Button::instances;
+
+Button::Button(
+  int16_t x_, int16_t y_, int16_t w_, int16_t h_,
+  bool rot1_ /* = false */,
+  const char* name_ /* = "" */,
+  ButtonColors off_ /*= {NODRAW, NODRAW, NODRAW} */,
+  ButtonColors on_ /* = {NODRAW, NODRAW, NODRAW} */,
+  uint8_t datum_ /* = BUTTON_DATUM */,
+  int16_t dx_ /* = 0 */,
+  int16_t dy_ /* = 0 */,
+  uint8_t r_ /* = 0xFF */
+) : Zone(x_, y_, w_, h_, rot1_) {
+	pin = 0xFF;
+	invert = false;
+	dbTime = 0;
+	strncpy(name, name_, 15);
+	off = off_;
+	on = on_;
+	datum = datum_;
+	dx = dx_;
+	dy = dy_;
+	r =  r_;
+	init();
+}
+
+Button::Button(
+  uint8_t pin_, uint8_t invert_, uint32_t dbTime_, String hw /* = "hw" */,
+  int16_t x_ /* = 0 */, int16_t y_ /* = 0 */, int16_t w_ /* = 0 */, int16_t h_ /* = 0 */,
+  bool rot1_ /* = false */,
+  const char* name_ /* = "" */,
+  ButtonColors off_ /*= {NODRAW, NODRAW, NODRAW} */,
+  ButtonColors on_ /* = {NODRAW, NODRAW, NODRAW} */,
+  uint8_t datum_ /* = BUTTON_DATUM */,
+  int16_t dx_ /* = 0 */,
+  int16_t dy_ /* = 0 */,
+  uint8_t r_ /* = 0xFF */
+) : Zone(x_, y_, w_, h_, rot1_) {
+	pin = pin_;
+	invert = invert_;
+	dbTime = dbTime_;
+	strncpy(name, name_, 15);
+	off = off_;
+	on = on_;
+	datum = datum_;
+	dx = dx_;
+	dy = dy_;
+	r =  r_;
+	init();
+}
+
+Button::~Button() {
+	for(int i = 0; i < instances.size(); ++i) {
+		if (instances[i] == this) {
+			EVENTS->delHandlers(nullptr, this, nullptr);
+			instances.erase(instances.begin() + i);
+			return;
+		}
+	}
+}
+
+Button::operator bool() {
+	return _state;
+}
+
+void Button::init() {
+	_state = _tapWait = _pressing = false;
+	_time = _lastChange = _pressTime = millis();
+	_hold_time = -1;
+	_textFont = _textSize = 0;
+	_freeFont = nullptr;
+	drawFn = nullptr;
+	compat = 0;
+	drawZone = this;
+	strncpy(label, name, 16);
+	instances.push_back(this);
+	draw();
+}
+
+int16_t Button::instanceIndex() {
+	for(int16_t i = 0; i < instances.size(); ++i) {
+		if (instances[i] == this) return i;
+	}
+	return -1;
+}	
+
+bool Button::read() {
+	if (pin == 0xFF) return false;
+	event = Event();
+	uint16_t duration = _time - _lastChange;
+	Point invalid;
+	if (changed) {
+		// Identify deeper meaning, if any, of state change last time
+		changed = false;
+		_lastChange = _time;
+		if (!_state) {
+			if (duration <= MAX_TAP) {
+				if (_tapWait) {
+					EVENTS->fireEvent(0, E_DBLTAP, invalid, invalid, 0, this, nullptr);
+					_tapWait = false;
+					_pressing = false;
+					return _state;
+				} else {
+					_tapWait = true;
+				}
+			} else if (_pressing) {
+				EVENTS->fireEvent(0, E_PRESSED, invalid, invalid, duration, this, nullptr);
+				_pressing = false;
+				return _state;
+			}
+		}
+	} else {
+		// Timeouts
+		if (_tapWait && duration >= MAX_BETWEEN_TAP) {
+			EVENTS->fireEvent(0, E_TAP, invalid, invalid, 0, this, nullptr);
+			_tapWait = false;
+			_pressing = false;
+			return _state;
+		}
+		if (_state && !_pressing && duration > MAX_TAP) {
+			EVENTS->fireEvent(0, E_PRESSING, invalid, invalid, 0, this, nullptr);
+			_pressing = true;
+			return _state;
+		}
+	}
+	// Do an actual read from the pin
+	_time = millis();
+	pinMode(pin, INPUT_PULLUP);
+	uint8_t pinVal = digitalRead(pin);
+	pinVal = invert ? !pinVal : pinVal;
+	if (_time - _lastChange >= dbTime && pinVal != _state){
+		_state = pinVal;
+		if (_state) {
+			EVENTS->fireEvent(0, E_TOUCH, invalid, invalid, 0, this, nullptr);
+			_pressTime = _time;		
+		} else {
+			EVENTS->fireEvent(0, E_RELEASE, invalid, invalid, duration, this, nullptr);
+		}
+		changed = true;
+	}
+	return _state;
+}
+
+bool Button::setState(bool newState) {
+	if (newState != _state) {
+		_state = newState;
+		_lastChange = _time;
+		changed = true;
+		if (_state) _pressTime = _time;
+		draw();
+	}
+	return _state;
+}
+
+bool Button::isPressed() { return _state; }
+
+bool Button::isReleased() { return !_state; }
+
+bool Button::wasPressed() { return _state && changed; }
+
+bool Button::wasReleased() {
+	return (!_state && changed && millis() - _pressTime < _hold_time);
+}
+
+bool Button::wasReleasefor(uint32_t ms) {
+	_hold_time = ms;
+	return (!_state && changed && millis() - _pressTime >= ms);
+}
+
+bool Button::pressedFor(uint32_t ms) {
+	Serial.println(_time);
+	return (_state && _time - _lastChange >= ms);
+}
+
+bool Button::pressedFor(uint32_t ms, uint32_t continuous_time) {
+	if (_state && _time - _lastChange >= ms && _time - _lastLongPress >= continuous_time) {
+		_lastLongPress = _time;
+		return true;
+	} 
+	return false;
+}
+
+bool Button::releasedFor(uint32_t ms) {
+	return (!_state && _time - _lastChange >= ms);
+}
+
+uint32_t Button::lastChange() { return (_lastChange); }
+
+void Button::addHandler(void (*fn)(Event&), uint16_t eventMask /* = E_ALL */) {
+	EVENTS->addHandler(fn, eventMask, this, nullptr);
+}
+
+void Button::delHandlers(void (*fn)(Event&) /* = nullptr */) {
+	EVENTS->delHandlers(fn, this, nullptr);
+}
+
+// visual things for Button
+
+void Button::draw() {
+	if (_state) draw(on); else draw(off);
+} 
+
+void Button::draw(ButtonColors bc) {
+	// use locally set draw function if aplicable, global one otherwise
+	if (drawFn) {
+		drawFn(this, bc);
+	} else if (BUTTONS->drawFn) {
+		BUTTONS->drawFn(this, bc);
+	}
+}
+
+void Button::setLabel(const char* label_) {
+	strncpy(label, label_, 50);
+}
+
+void Button::setFont(const GFXfont* freeFont_) {
+	_freeFont = freeFont_;
+	_textFont = 1;
+}
+
+void Button::setFont(uint8_t textFont_ /* = 0 */) {
+	_freeFont = nullptr;
+	_textFont = textFont_;
+}
+
+void Button::setTextSize(uint8_t textSize_ /* = 0 */) {
+	_textSize = textSize_;
+}
+
+
+
+// M5Buttons class
+
+/* static */ M5Buttons* M5Buttons::instance;
+
+/* static */ void M5Buttons::drawFunction(Button* button, ButtonColors bc) {
+	if (bc.bg == NODRAW && bc.outline == NODRAW && bc.text == NODRAW) return;
+	if (!button || !button->drawZone) return;
+	Button& b = *button;
+	Zone& z = *b.drawZone;
+		
+	uint8_t r = (b.r == 0xFF) ? min(z.w, z.h) / 4 : b.r;
+	
+	if (bc.bg != NODRAW) {
+		if (r >= 2) {
+			TFT->fillRoundRect(z.x, z.y, z.w, z.h, r, bc.bg);
+		} else {
+			TFT->fillRect(z.x, z.y, z.w, z.h, bc.bg);
+		}
+	}
+
+	if (bc.outline != NODRAW) {
+		if (r >= 2) {
+			TFT->drawRoundRect(z.x, z.y, z.w, z.h, r, bc.outline);
+		} else {
+			TFT->drawRect(z.x, z.y, z.w, z.h, bc.outline);
+		}
+	}
+
+	if (bc.text != NODRAW && bc.text != bc.bg && b.label != "") {
+	
+		// figure out where to put the text
+		uint16_t tx, ty;
+		tx = z.x + (z.w / 2);
+		ty = z.y + (z.h / 2);
+		
+		if (!b.compat) {
+			uint8_t margin = max(r / 2, 6);
+			switch (b.datum) {
+			  case TL_DATUM:
+			  case ML_DATUM:
+			  case BL_DATUM:
+				tx = z.x + margin;
+				break;
+			  case TR_DATUM:
+			  case MR_DATUM:
+			  case BR_DATUM:
+				tx = z.x + z.w - margin;
+				break;
+			}
+			switch (b.datum) {
+			  case TL_DATUM:
+			  case TC_DATUM:
+			  case TR_DATUM:
+				ty = z.y + margin;
+				break;
+			  case BL_DATUM:
+			  case BC_DATUM:
+			  case BR_DATUM:
+				ty = z.y + z.h - margin;
+				break;
+			}
+		}
+		
+		// Save state
+		uint8_t tempdatum = TFT->getTextDatum();
+		uint16_t tempPadding = TFT->padX;
+		if (!b.compat) TFT->pushState();
+	
+	
+		// Actual drawing of text
+		TFT->setTextColor(bc.text);
+		if (b._textSize) TFT->setTextSize(b._textSize);
+		  else TFT->setTextSize(BUTTONS->_textSize);
+		if (b._textFont) {
+			if (b._freeFont) TFT->setFreeFont(b._freeFont); 
+			  else TFT->setTextFont(b._textFont);
+		} else {
+			if (BUTTONS->_freeFont) TFT->setFreeFont(BUTTONS->_freeFont);
+			  else TFT->setTextFont(BUTTONS->_textFont);
+		}
+		TFT->setTextDatum(b.datum);
+		TFT->setTextPadding(0);
+		TFT->drawString(b.label, tx + b.dx, ty + b.dy);
+		// Set state back
+		if (!b.compat) {
+			TFT->popState();
+		} else {
+			TFT->setTextDatum(tempdatum);
+			TFT->setTextPadding(tempPadding);
+		}
+	}
+}
+
+M5Buttons::M5Buttons() {
+	if (!instance) instance = this;
+	drawFn = drawFunction;
+	_freeFont = BUTTON_FREEFONT;
+	_textFont = BUTTON_TEXTFONT;
+	_textSize = BUTTON_TEXTSIZE;
+}
+
+void M5Buttons::setUnchanged() {
+	for ( auto button : Button::instances) {
+		if (button->pin == 0xFF) {
+			button->changed = false;
+			button->_time = millis();
+			button->event = Event();
+		}
+	}
+}
+
+Button* M5Buttons::which(Point& p) {
+	for ( auto b : Button::instances ) {
+		if (b->pin == 0xFF && b->contains(p)) return b;
+	}
+	return nullptr;
+}
+
+void M5Buttons::draw() {
+	for ( auto button : Button::instances ) button->draw();
+}
+
+void M5Buttons::setFont(const GFXfont* freeFont_) {
+	_freeFont = freeFont_;
+	_textFont = 1;
+}
+
+void M5Buttons::setFont(uint8_t textFont_) {
+	_freeFont = nullptr;
+	_textFont = textFont_;
+}
+
+void M5Buttons::setTextSize(uint8_t textSize_) {
+	_textSize = textSize_;
+}
+
+
+
+// Gesture class
+
+std::vector<Gesture*> Gesture::instances;
+
+Gesture::Gesture(
+  Zone& fromZone_,
+  Zone& toZone_,
+  const char* name_ /* = "" */,
+  uint16_t maxTime_ /* = 500 */,
+  uint16_t minDistance_ /* = 50 */
+) {
+	fromZone = &fromZone_;
+	toZone = &toZone_;
+	strncpy(name, name_, 15);
+	maxTime = maxTime_;
+	minDistance = minDistance_;
+	detected = false;
+	instances.push_back(this);
+}
+
+Gesture::~Gesture() {
+	for(int i = 0; i < instances.size(); ++i) {
+		if (instances[i] == this) {
+			instances.erase(instances.begin() + i);
+			EVENTS->delHandlers(nullptr, nullptr, this);
+			return;
+		}
+	}
+}
+
+int16_t Gesture::instanceIndex() {
+	for(int16_t i = 0; i < instances.size(); ++i) {
+		if (instances[i] == this) return i;
+	}
+	return -1;
+}	
+
+bool Gesture::test(Event& e) {
+	if (e.duration > maxTime) return false;
+	if (e.from.distanceTo(e.to) < minDistance) return false;
+	if (!fromZone->contains(e.from) || !toZone->contains(e.to)) return false;
+	detected = true;
+	return true;
+}
+
+bool Gesture::wasDetected() { return detected; }
+
+void Gesture::addHandler(void (*fn)(Event&), uint16_t eventMask /* = E_ALL */) {
+	EVENTS->addHandler(fn, eventMask, nullptr, this);
+}
+
+void Gesture::delHandlers(void (*fn)(Event&) /* = nullptr */) {
+	EVENTS->delHandlers(fn, nullptr, this);
+}
+
+
+
+// Event class
+
+Event::Event() {
+	finger = type = duration = 0;
+	button = nullptr;
+	gesture = nullptr;
+}
+
+Event::operator bool() { return (type); }
+
+const char* Event::typeName() {
+	const char *unknown = "E_UNKNOWN";
+	const char *eventNames[NUM_EVENTS] = {
+		"E_TOUCH", 
+		"E_RELEASE",
+		"E_MOVE",
+		"E_GESTURE",
+		"E_TAP",
+		"E_DBLTAP",
+		"E_DRAGGED",
+		"E_PRESSED",
+		"E_PRESSING"
+	};
+	for (uint8_t i = 0; i < NUM_EVENTS; i++) {
+		if ((type >> i) & 1) return eventNames[i];
+	}
+	return unknown;
+}
+
+const char* Event::objName() {
+	const char *empty = "";
+	if (button) return button->name;
+	if (gesture) return gesture->name;
+	return empty;
+};
+
+
+
+// M5Events class
+
+M5Events* M5Events::instance;
+
+M5Events::M5Events() {
+	if (!instance) instance = this;
+}
+
+Event M5Events::fireEvent(uint8_t finger, uint16_t type, Point& from,
+  Point& to, uint16_t duration, Button* button, Gesture* gesture) {
+	Event e;
+	e.finger = finger;
+	e.type = type;
+	e.from = from;
+	e.to = to;
+	e.duration = duration;
+	e.button = button;
+	e.gesture = gesture;
+	if (button) {
+		button->event = e;
+		if (type == E_TOUCH || type == E_RELEASE) button->draw();
+	}
+	for ( auto h : _eventHandlers ) {
+		if (!(h.eventMask & e.type)) continue;
+		if (h.button && h.button != e.button) continue;
+		if (h.gesture && h.gesture != e.gesture) continue;
+		if (h.eventMask & E_BTNONLY && !e.button) continue;
+		h.fn(e);
+	}
+	return e;
+}
+
+void M5Events::addHandler(
+  void (*fn)(Event&),
+  uint16_t eventMask /* = E_ALL */,
+  Button* button /* = nullptr */,
+  Gesture* gesture /* = nullptr */
+) {
+	EventHandler handler;
+	handler.fn = fn;
+	handler.eventMask = eventMask;
+	handler.button = button;
+	handler.gesture = gesture;
+	_eventHandlers.push_back(handler);
+}
+
+void M5Events::delHandlers(
+  void (*fn)(Event&) /* = nullptr */,
+  Button* button /* = nullptr */,
+  Gesture* gesture /* = nullptr */
+) {
+	for(int i = _eventHandlers.size() - 1; i >= 0 ; --i) {
+		if (fn && fn != _eventHandlers[i].fn) continue;
+		if (button && _eventHandlers[i].button != button) continue;
+		if (gesture && _eventHandlers[i].gesture != gesture) continue;
+		_eventHandlers.erase(_eventHandlers.begin() + i);
+	}
+}
+
+
+
+// TFT_eSPI_Button compatibility mode
+
+TFT_eSPI_Button::TFT_eSPI_Button() : Button(0,0,0,0) {
+	compat = true;
+}
+
+void TFT_eSPI_Button::initButton(
+  TFT_eSPI *gfx, 
+  int16_t x, int16_t y, uint16_t w, uint16_t h, 
+  uint16_t outline, uint16_t fill, uint16_t textcolor,
+  char *label,
+  uint8_t textsize
+) {
+	initButtonUL(gfx, x - (w / 2), y - (h / 2), w, h, outline, fill, 
+	  textcolor, label, textsize);
+}
+
+void TFT_eSPI_Button::initButtonUL(
+  TFT_eSPI *gfx,
+  int16_t x_, int16_t y_, uint16_t w_, uint16_t h_, 
+  uint16_t outline, uint16_t fill, uint16_t textcolor, 
+  char *label_,
+  uint8_t textsize_
+) {
+	x = x_;
+	y = y_;
+	w = w_;
+	h = h_;
+	off = {fill, textcolor, outline};
+	on = {textcolor, fill, outline};
+	setTextSize(textsize_);
+	strncpy(label, label_, 9);
+}
+
+void TFT_eSPI_Button::setLabelDatum(int16_t dx_, int16_t dy_, uint8_t datum_ /* = MC_DATUM */) {
+	dx = dx_;
+	dy = dy_;
+	datum = datum_;
+}
+
+void TFT_eSPI_Button::drawButton(bool inverted /* = false */, String long_name /* = "" */) {
+	char oldLabel[51];
+	if (long_name != "") {
+		strncpy(oldLabel, label, 50);
+		strncpy(label, long_name.c_str(), 50);
+	}
+	draw(inverted ? on : off);
+	if (long_name != "") strncpy(label, oldLabel, 50);
+}
+
+bool TFT_eSPI_Button::contains(int16_t x, int16_t y) { return Button::contains(x, y); }
+
+void TFT_eSPI_Button::press(bool p) { setState(p); }
+
+bool TFT_eSPI_Button::justPressed() { return wasPressed(); }
+
+bool TFT_eSPI_Button::justReleased() { return wasReleased(); }
\ No newline at end of file
diff --git a/src/utility/M5Button.h b/src/utility/M5Button.h
new file mode 100644
index 00000000..0cfb3b07
--- /dev/null
+++ b/src/utility/M5Button.h
@@ -0,0 +1,242 @@
+#ifndef _M5BUTTON_H_
+#define _M5BUTTON_H_
+
+class Gesture;
+
+#include <Arduino.h>
+#include "PointAndZone.h"
+#include <M5Display.h>
+#include <Free_Fonts.h>
+
+#define TFT		M5Display::instance
+#define EVENTS	M5Events::instance
+#define BUTTONS	M5Buttons::instance
+
+#define MAX_TAP				150
+#define MAX_BETWEEN_TAP		200
+#define GESTURE_MAXTIME		500
+#define GESTURE_MINDIST		75
+
+#define NUM_EVENTS			9
+#define E_TOUCH				0x0001
+#define E_RELEASE			0x0002
+#define E_MOVE  			0x0004
+#define E_GESTURE			0x0008
+#define E_TAP				0x0010
+#define E_DBLTAP			0x0020
+#define E_DRAGGED			0x0040
+#define E_PRESSED			0x0080
+#define E_PRESSING			0x0100
+
+#define E_ALL				0x0FFF
+#define E_BTNONLY			0x1000
+
+#define NODRAW				0x0120	// Special color value: transparent
+
+#define BUTTON_FREEFONT		FSS9
+#define BUTTON_TEXTFONT		1
+#define BUTTON_TEXTSIZE		1
+#define BUTTON_DATUM		MC_DATUM
+
+struct ButtonColors {
+	uint16_t bg;
+	uint16_t text;
+	uint16_t outline;
+};
+
+class Button;
+	
+class Event {
+  public:
+  	Event();
+  	operator bool();
+	const char* typeName();
+	const char* objName();
+	uint8_t finger;
+	uint16_t type;
+	Point from, to;
+	uint16_t duration;
+	Button* button;
+	Gesture* gesture;
+};
+
+class Button : public Zone {
+  public:
+    static std::vector<Button*> instances;
+	Button(
+	  int16_t x_, int16_t y_, int16_t w_, int16_t h_,
+	  bool rot1_ = false,
+	  const char* name_ = "",
+	  ButtonColors off_ = {NODRAW, NODRAW, NODRAW},
+	  ButtonColors on_ = {NODRAW, NODRAW, NODRAW},
+	  uint8_t datum_ = BUTTON_DATUM,
+	  int16_t dx_ = 0,
+	  int16_t dy_ = 0,
+	  uint8_t r_ = 0xFF
+	);
+	Button(
+	  uint8_t pin, uint8_t invert, uint32_t dbTime, String hw = "hw",
+	  int16_t x_ = 0, int16_t y_ = 0, int16_t w_ = 0, int16_t h_ = 0,
+	  bool rot1_ = false,
+	  const char* name_ = "",
+	  ButtonColors off_ = {NODRAW, NODRAW, NODRAW},
+	  ButtonColors on_ = {NODRAW, NODRAW, NODRAW},
+	  uint8_t datum_ = BUTTON_DATUM,
+	  int16_t dx_ = 0,
+	  int16_t dy_ = 0,
+	  uint8_t r_ = 0xFF
+	);
+	~Button();
+	operator bool();
+	int16_t instanceIndex();
+	bool read();
+	bool setState(bool);
+	bool isPressed();
+	bool isReleased();
+	bool wasPressed();
+	bool wasReleased();
+	bool pressedFor(uint32_t ms);
+	bool pressedFor(uint32_t ms, uint32_t continuous_time);
+	bool releasedFor(uint32_t ms);
+	bool wasReleasefor(uint32_t ms);
+	void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL);
+	void delHandlers(void (*fn)(Event&) = nullptr);
+	uint32_t lastChange();
+	uint8_t finger;
+	bool changed;
+	char name[16];
+	Event event;
+	uint8_t pin;
+	uint32_t dbTime;
+	bool invert;
+  private:
+  	friend class M5Buttons;
+  	void init();
+	bool _state, _tapWait, _pressing;
+	uint32_t _time;
+	uint32_t _lastChange, _lastLongPress, _pressTime, _hold_time;
+	
+  // visual stuff
+  public:
+	void draw(ButtonColors bc);
+	void draw();
+	void setLabel(const char* label_);
+	void setFont(const GFXfont* freeFont_);
+	void setFont(uint8_t textFont_ = 0);
+	void setTextSize(uint8_t textSize_ = 0);
+	ButtonColors off, on;
+	Zone* drawZone;
+	uint8_t datum, r;
+	int16_t dx, dy;
+	void (*drawFn)(Button* b, ButtonColors bc);
+	char label[51];
+	bool compat; // For TFT_eSPI_Button emulation		
+  private:
+	uint8_t _textFont;
+	const GFXfont* _freeFont;
+	uint8_t _textSize;
+};
+
+class M5Buttons {
+  public:
+    static M5Buttons* instance;
+    static void drawFunction(Button* button, ButtonColors bc);
+	M5Buttons();
+    void setUnchanged();
+	Button* which(Point& p);
+	void draw();
+	void setFont(const GFXfont* freeFont_);
+	void setFont(uint8_t textFont_);
+	void setTextSize(uint8_t textSize_);
+	void (*drawFn)(Button* button, ButtonColors bc);
+  private:
+	uint8_t _textFont;
+	const GFXfont* _freeFont;
+	uint8_t _textSize;
+};
+
+class Gesture {
+  public:
+	static std::vector<Gesture*> instances;
+  	Gesture(
+  	  Zone& fromZone_,
+  	  Zone& toZone_,
+  	  const char* name_ = "",
+  	  uint16_t maxTime_ = GESTURE_MAXTIME,
+  	  uint16_t minDistance_ = GESTURE_MINDIST
+  	);
+  	~Gesture();
+  	int16_t instanceIndex();
+  	bool test(Event& e);
+  	bool wasDetected();
+  	void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL);
+  	void delHandlers(void (*fn)(Event&) = nullptr);
+	Zone* fromZone;
+	Zone* toZone;
+  	uint16_t maxTime, minDistance;
+  	char name[16];
+	bool detected;
+};
+
+struct EventHandler {
+	uint16_t eventMask;
+	Button* button;
+	Gesture* gesture;
+	void (*fn)(Event&);
+};
+
+class M5Events {
+  public:
+    M5Events();
+    static M5Events* instance;
+    Event fireEvent(
+      uint8_t finger,
+      uint16_t type,
+      Point& from,
+      Point& to,
+      uint16_t duration,
+      Button* button,
+      Gesture* gesture
+    );
+    void addHandler(
+      void (*fn)(Event&),
+      uint16_t eventMask = E_ALL,
+      Button* button = nullptr,
+      Gesture* gesture = nullptr
+    );
+    void delHandlers(
+      void (*fn)(Event&),
+      Button* button,
+      Gesture* gesture
+    );
+	std::vector<EventHandler> _eventHandlers;
+};
+
+// TFT_eSPI_Button compatibility
+class TFT_eSPI_Button : public Button {
+  public:
+	TFT_eSPI_Button();
+	void initButton(
+	  TFT_eSPI *gfx, 
+	  int16_t x, int16_t y, uint16_t w, uint16_t h, 
+	  uint16_t outline, uint16_t fill, uint16_t textcolor,
+	  char *label,
+	  uint8_t textsize_
+	);
+	void initButtonUL(
+	  TFT_eSPI *gfx,
+	  int16_t x_, int16_t y_, uint16_t w_, uint16_t h_, 
+	  uint16_t outline, uint16_t fill, uint16_t textcolor, 
+	  char *label,
+	  uint8_t textsize_
+	);
+	void     setLabelDatum(int16_t x_delta, int16_t y_delta, uint8_t datum = MC_DATUM);
+	void     drawButton(bool inverted = false, String long_name = "");
+	bool     contains(int16_t x, int16_t y);
+	void     press(bool p);
+	bool     isPressed();
+	bool     justPressed();
+	bool     justReleased();
+};
+
+#endif /* _M5BUTTON_H_ */
\ No newline at end of file
diff --git a/src/utility/PointAndZone.cpp b/src/utility/PointAndZone.cpp
new file mode 100644
index 00000000..837f1ea1
--- /dev/null
+++ b/src/utility/PointAndZone.cpp
@@ -0,0 +1,154 @@
+#include "PointAndZone.h"
+
+
+// Point class
+
+Point::Point(int16_t x_ /* = -1 */, int16_t y_ /* = -1 */) {
+	x = x_;
+	y = y_;
+}
+
+bool Point::operator ==(const Point& p) {
+	return (Equals(p));
+}
+	
+bool Point::operator !=(const Point& p) {
+	return (!Equals(p));
+}
+
+Point::operator char*() {
+	sprintf(_text, "(%d, %d)", x, y);
+	return _text;
+}
+
+void Point::set(int16_t x_ /* = -1 */, int16_t y_ /* = -1 */) {
+	x = x_;
+	y = y_;
+}
+
+bool Point::Equals(const Point& p) {
+	return (x == p.x && y == p.y);
+}
+
+bool Point::in(Zone& z) {
+	return (z.contains(x, y));
+}
+
+bool Point::valid() {
+	return !(x == -1 && y == -1);
+}
+
+uint16_t Point::distanceTo(const Point& p) {
+	int16_t dx = x - p.x;
+	int16_t dy = y - p.y;
+	return sqrt(dx*dx + dy*dy);
+}
+
+void Point::rotate(uint8_t m) {
+	int16_t normal_x = x;
+	int16_t normal_y = y;
+	int16_t inv_x = DISPLAY_WIDTH - 1 - x;
+	int16_t inv_y = DISPLAY_HEIGHT - 1 - y;
+	// inv_y can be negative for area below screen of M5Core2
+	switch (m) {
+	  case 0:
+		x = inv_y;
+		y = normal_x;
+		break;
+	  case 2:
+	    x = normal_y;
+	    y = inv_x;
+	    break;
+	  case 3:
+	  	x = inv_x;
+	  	y = inv_y;
+	  	break;
+	  // rotations 4-7 are mirrored
+	  case 4:
+	  	x = inv_y;
+	  	y = inv_x;
+	  	break;
+	  case 5:
+	  	x = normal_x;
+	  	y = inv_y;
+	  	break;
+	  case 6:
+	  	x = normal_y;
+	  	y = normal_x;
+	  	break;
+	  case 7:
+		x = inv_x;
+		y = normal_y;
+		break;
+	}
+}
+
+
+
+// Zone class
+
+Zone::Zone(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ /* = false */) {
+	set(x_, y_, w_, h_, rot1_);
+}
+
+void Zone::set(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ /* = false */) {
+	x = x_;
+	y = y_;
+	w = w_;
+	h = h_;
+	rot1 = rot1_;
+}
+
+bool Zone::contains(const Point &p) {
+	return contains(p.x, p.y);
+}
+
+bool Zone::contains(int16_t x_, int16_t y_) {
+	#ifdef TFT
+		if (rot1 && TFT->rotation != 1) {
+			Zone t = *this;
+			t.rotate(TFT->rotation);
+			return (y_ >= t.y && y_ <= t.y + t.h && x_ >= t.x && x_ <= t.x + t.w);
+		}
+	#endif
+	return (y_ >= y && y_ <= y + h && x_ >= x && x_ <= x + w);
+}
+
+void Zone::rotate(uint8_t m) {
+	int16_t normal_x = x;
+	int16_t normal_y = y;
+	int16_t inv_x = 319 - x - w;
+	int16_t inv_y = 239 - y - h;	// negative for area outside of screen
+	switch (m) {
+	  case 0:
+		x = inv_y;
+		y = normal_x;
+		break;
+	  case 2:
+	    x = normal_y;
+	    y = inv_x;
+	    break;
+	  case 3:
+	  	x = inv_x;
+	  	y = inv_y;
+	  	break;
+	  // rotations 4-7 are mirrored
+	  case 4:
+	  	x = inv_y;
+	  	y = inv_x;
+	  	break;
+	  case 5:
+	  	x = normal_x;
+	  	y = inv_y;
+	  	break;
+	  case 6:
+	  	x = normal_y;
+	  	y = normal_x;
+	  	break;
+	  case 7:
+		x = inv_x;
+		y = normal_y;
+		break;
+	}
+	if (!(m % 2)) std::swap(w, h);
+}
\ No newline at end of file
diff --git a/src/utility/PointAndZone.h b/src/utility/PointAndZone.h
new file mode 100644
index 00000000..e7671421
--- /dev/null
+++ b/src/utility/PointAndZone.h
@@ -0,0 +1,44 @@
+#ifndef _POINTANDZONE_H_
+#define _POINTANDZONE_H_
+
+
+#include <Arduino.h>
+#include <M5Display.h>
+
+#define DISPLAY_WIDTH	320
+#define DISPLAY_HEIGHT	240
+#define TFT				M5Display::instance
+
+class Zone;
+
+class Point {
+  public:
+	Point(int16_t x_ = -1, int16_t y_ = -1);
+	bool operator ==(const Point& p);
+	bool operator !=(const Point& p);
+	operator char*();
+	void set(int16_t x_ = -1, int16_t y_ = -1);
+    bool in(Zone& z);
+    bool Equals(const Point& p);
+    bool valid();
+    uint16_t distanceTo(const Point& p);
+    void rotate(uint8_t m);
+    int16_t x, y;
+  private:
+    char _text[12];
+};
+
+class Zone {
+  public:
+	Zone(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ = false);
+	void set(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ = false);
+    bool contains(const Point &p);
+    bool contains(int16_t x, int16_t y);
+    void rotate(uint8_t m);
+    int16_t x, y, w, h;
+    // If rot1 is true, the zone specified in rotation 1 coordinates
+    // (stays in same place on physical screen, regardless of rotation.)
+    bool rot1;
+};
+
+#endif /* _POINTANDZONE_H_ */
\ No newline at end of file

From dc8710140a86f4d37b32c06af8b53e334eb277a9 Mon Sep 17 00:00:00 2001
From: Rop Gonggrijp <rop@gonggri.jp>
Date: Thu, 1 Oct 2020 17:10:46 +0200
Subject: [PATCH 2/8] Added example

---
 .../Buttons_and_Events/Buttons_and_Events.ino | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)
 create mode 100644 examples/Basics/Buttons_and_Events/Buttons_and_Events.ino

diff --git a/examples/Basics/Buttons_and_Events/Buttons_and_Events.ino b/examples/Basics/Buttons_and_Events/Buttons_and_Events.ino
new file mode 100644
index 00000000..ed97ce6f
--- /dev/null
+++ b/examples/Basics/Buttons_and_Events/Buttons_and_Events.ino
@@ -0,0 +1,21 @@
+#include <M5Stack.h>
+
+void setup() {
+  M5.begin();
+  M5.BtnA.setLabel("Test");
+  M5.BtnB.setLabel("Wow !");
+  M5.BtnC.setLabel("Amazing !");
+  M5.BtnA.off = M5.BtnB.off = M5.BtnC.off = {BLUE, WHITE, NODRAW};
+  M5.BtnA.on = M5.BtnB.on = M5.BtnC.on = {RED, WHITE, NODRAW};
+  M5.Events.addHandler(eventDisplay);
+  M5.Buttons.draw();
+}
+
+void loop() {
+	M5.update();
+}
+
+void eventDisplay(Event& e) {
+  Serial.printf("\n%-12s %-18s", e.typeName(), e.objName());
+  if (e.type == E_RELEASE || e.type == E_PRESSED) Serial.printf("%5d ms", e.duration);
+}

From 1a25ddd30fd4339eb0ecfcc79c4d402033f5c286 Mon Sep 17 00:00:00 2001
From: Rop Gonggrijp <rop@gonggri.jp>
Date: Fri, 2 Oct 2020 22:20:15 +0200
Subject: [PATCH 3/8] state machine upgrade

---
 src/M5Stack.cpp          |   4 +-
 src/utility/M5Button.cpp | 130 +++++++++++++++++++++++++++------------
 src/utility/M5Button.h   |  22 ++++---
 3 files changed, 105 insertions(+), 51 deletions(-)

diff --git a/src/M5Stack.cpp b/src/M5Stack.cpp
index 94729c5f..c784c1dc 100644
--- a/src/M5Stack.cpp
+++ b/src/M5Stack.cpp
@@ -53,9 +53,7 @@ void M5Stack::begin(bool LCDEnable, bool SDEnable, bool SerialEnable, bool I2CEn
 
 void M5Stack::update() {
   //Button update
-  BtnA.read();
-  BtnB.read();
-  BtnC.read();
+  Buttons.update();
 
   //Speaker update
   Speaker.update();
diff --git a/src/utility/M5Button.cpp b/src/utility/M5Button.cpp
index 3d3dd3db..41f13ad3 100644
--- a/src/utility/M5Button.cpp
+++ b/src/utility/M5Button.cpp
@@ -69,7 +69,8 @@ Button::operator bool() {
 }
 
 void Button::init() {
-	_state = _tapWait = _pressing = false;
+	_state = _tapWait = _pressing = _manuallyRead = false;
+	_setState = 0xFF;
 	_time = _lastChange = _pressTime = millis();
 	_hold_time = -1;
 	_textFont = _textSize = 0;
@@ -77,6 +78,9 @@ void Button::init() {
 	drawFn = nullptr;
 	compat = 0;
 	drawZone = this;
+	tapTime = TAP_TIME;
+	dbltapTime = DBLTAP_TIME;
+	longPressTime = LONGPRESS_TIME;
 	strncpy(label, name, 16);
 	instances.push_back(this);
 	draw();
@@ -89,8 +93,9 @@ int16_t Button::instanceIndex() {
 	return -1;
 }	
 
-bool Button::read() {
-	if (pin == 0xFF) return false;
+bool Button::read(bool manualRead /* = true */) {
+	// if (pin == 0xFF) return false;
+	if (manualRead) _manuallyRead = true;
 	event = Event();
 	uint16_t duration = _time - _lastChange;
 	Point invalid;
@@ -99,42 +104,62 @@ bool Button::read() {
 		changed = false;
 		_lastChange = _time;
 		if (!_state) {
-			if (duration <= MAX_TAP) {
-				if (_tapWait) {
-					EVENTS->fireEvent(0, E_DBLTAP, invalid, invalid, 0, this, nullptr);
-					_tapWait = false;
+			if (!_cancelled) {
+				if (duration <= tapTime) {
+					if (_tapWait) {
+						EVENTS->fireEvent(0, E_DBLTAP, invalid, invalid, 0, this, nullptr);
+						_tapWait = false;
+						_pressing = false;
+						_longPressing = false;
+						return _state;
+					} else {
+						_tapWait = true;
+					}
+				} else if (_pressing) {
+					EVENTS->fireEvent(0, _longPressing ? E_LONGPRESSED : E_PRESSED, invalid, invalid, duration, this, nullptr);
 					_pressing = false;
+					_longPressing = false;
 					return _state;
-				} else {
-					_tapWait = true;
 				}
-			} else if (_pressing) {
-				EVENTS->fireEvent(0, E_PRESSED, invalid, invalid, duration, this, nullptr);
-				_pressing = false;
-				return _state;
 			}
 		}
 	} else {
-		// Timeouts
-		if (_tapWait && duration >= MAX_BETWEEN_TAP) {
-			EVENTS->fireEvent(0, E_TAP, invalid, invalid, 0, this, nullptr);
-			_tapWait = false;
-			_pressing = false;
-			return _state;
-		}
-		if (_state && !_pressing && duration > MAX_TAP) {
-			EVENTS->fireEvent(0, E_PRESSING, invalid, invalid, 0, this, nullptr);
-			_pressing = true;
-			return _state;
+		if (_cancelled) {
+			if (!_state) _cancelled = false;
+		} else {
+			// Timeouts
+			if (_tapWait && duration >= dbltapTime) {
+				EVENTS->fireEvent(0, E_TAP, invalid, invalid, 0, this, nullptr);
+				_tapWait = false;
+				_pressing = false;
+				return _state;
+			}
+			if (_state && !_pressing && duration > tapTime) {
+				if (tapTime) EVENTS->fireEvent(0, E_PRESSING, invalid, invalid, 0, this, nullptr);
+				_pressing = true;
+				return _state;
+			}
+			if (longPressTime && _state && !_longPressing && duration > longPressTime) {
+				EVENTS->fireEvent(0, E_LONGPRESSING, invalid, invalid, 0, this, nullptr);
+				_longPressing = true;
+				return _state;
+			}
 		}
 	}
-	// Do an actual read from the pin
+	// Do an actual read from the pin (or _setState)
 	_time = millis();
-	pinMode(pin, INPUT_PULLUP);
-	uint8_t pinVal = digitalRead(pin);
-	pinVal = invert ? !pinVal : pinVal;
-	if (_time - _lastChange >= dbTime && pinVal != _state){
-		_state = pinVal;
+	uint8_t newState = false;
+	if (_setState == 0xFF) {
+		if (pin != 0xFF) {
+			pinMode(pin, INPUT_PULLUP);
+			newState = (digitalRead(pin));
+			newState = invert ? !newState : newState;
+		}
+	} else {
+		newState = _setState;	
+	}
+	if (_time - _lastChange >= dbTime && newState != _state){
+		_state = newState;
 		if (_state) {
 			EVENTS->fireEvent(0, E_TOUCH, invalid, invalid, 0, this, nullptr);
 			_pressTime = _time;		
@@ -146,17 +171,28 @@ bool Button::read() {
 	return _state;
 }
 
-bool Button::setState(bool newState) {
-	if (newState != _state) {
-		_state = newState;
-		_lastChange = _time;
-		changed = true;
-		if (_state) _pressTime = _time;
-		draw();
-	}
-	return _state;
+void Button::setState(bool newState) { _setState = newState; }
+
+void Button::freeState() {_setState = 0xFF; }
+
+void Button::cancel() {
+	_cancelled = true;
+	_tapWait = false;
+	draw(off);
 }
 
+
+// bool Button::setState(bool newState) {
+// 	if (newState != _state) {
+// 		_state = newState;
+// 		_lastChange = _time;
+// 		changed = true;
+// 		if (_state) _pressTime = _time;
+// 		draw();
+// 	}
+// 	return _state;
+// }
+
 bool Button::isPressed() { return _state; }
 
 bool Button::isReleased() { return !_state; }
@@ -346,7 +382,9 @@ void M5Buttons::setUnchanged() {
 }
 
 Button* M5Buttons::which(Point& p) {
-	for ( auto b : Button::instances ) {
+	if (!Button::instances.size()) return nullptr;
+	for(int i = Button::instances.size() - 1; i >= 0 ; --i) {
+		Button* b = Button::instances[i];
 		if (b->pin == 0xFF && b->contains(p)) return b;
 	}
 	return nullptr;
@@ -356,6 +394,12 @@ void M5Buttons::draw() {
 	for ( auto button : Button::instances ) button->draw();
 }
 
+void M5Buttons::update() {
+	for ( auto button : Button::instances ) {
+		if (!button->_manuallyRead) button->read(false);
+	}
+}
+
 void M5Buttons::setFont(const GFXfont* freeFont_) {
 	_freeFont = freeFont_;
 	_textFont = 1;
@@ -441,6 +485,7 @@ Event::operator bool() { return (type); }
 
 const char* Event::typeName() {
 	const char *unknown = "E_UNKNOWN";
+	const char *none = "E_NONE";
 	const char *eventNames[NUM_EVENTS] = {
 		"E_TOUCH", 
 		"E_RELEASE",
@@ -450,8 +495,11 @@ const char* Event::typeName() {
 		"E_DBLTAP",
 		"E_DRAGGED",
 		"E_PRESSED",
-		"E_PRESSING"
+		"E_PRESSING",
+		"E_LONGPRESSED",
+		"E_LONGPRESSING"
 	};
+	if (!type) return none;
 	for (uint8_t i = 0; i < NUM_EVENTS; i++) {
 		if ((type >> i) & 1) return eventNames[i];
 	}
diff --git a/src/utility/M5Button.h b/src/utility/M5Button.h
index 0cfb3b07..b7f9860f 100644
--- a/src/utility/M5Button.h
+++ b/src/utility/M5Button.h
@@ -12,12 +12,13 @@ class Gesture;
 #define EVENTS	M5Events::instance
 #define BUTTONS	M5Buttons::instance
 
-#define MAX_TAP				150
-#define MAX_BETWEEN_TAP		200
+#define TAP_TIME			150
+#define DBLTAP_TIME			200
+#define LONGPRESS_TIME		0
 #define GESTURE_MAXTIME		500
 #define GESTURE_MINDIST		75
 
-#define NUM_EVENTS			9
+#define NUM_EVENTS			11
 #define E_TOUCH				0x0001
 #define E_RELEASE			0x0002
 #define E_MOVE  			0x0004
@@ -27,6 +28,8 @@ class Gesture;
 #define E_DRAGGED			0x0040
 #define E_PRESSED			0x0080
 #define E_PRESSING			0x0100
+#define E_LONGPRESSED		0x0200
+#define E_LONGPRESSING		0x0400
 
 #define E_ALL				0x0FFF
 #define E_BTNONLY			0x1000
@@ -89,8 +92,10 @@ class Button : public Zone {
 	~Button();
 	operator bool();
 	int16_t instanceIndex();
-	bool read();
-	bool setState(bool);
+	bool read(bool manualRead = true);
+	void setState(bool);
+	void freeState();
+	void cancel();
 	bool isPressed();
 	bool isReleased();
 	bool wasPressed();
@@ -107,12 +112,14 @@ class Button : public Zone {
 	char name[16];
 	Event event;
 	uint8_t pin;
-	uint32_t dbTime;
+	uint16_t dbTime;
 	bool invert;
+	uint16_t tapTime, dbltapTime, longPressTime;
   private:
   	friend class M5Buttons;
   	void init();
-	bool _state, _tapWait, _pressing;
+	bool _state, _tapWait, _pressing, _longPressing, _cancelled, _manuallyRead;
+	uint8_t _setState;
 	uint32_t _time;
 	uint32_t _lastChange, _lastLongPress, _pressTime, _hold_time;
 	
@@ -145,6 +152,7 @@ class M5Buttons {
     void setUnchanged();
 	Button* which(Point& p);
 	void draw();
+	void update();
 	void setFont(const GFXfont* freeFont_);
 	void setFont(uint8_t textFont_);
 	void setTextSize(uint8_t textSize_);

From 3df932cc3465f7c75eb8d246a6b01e17ad3b15de Mon Sep 17 00:00:00 2001
From: Rop Gonggrijp <rop@gonggri.jp>
Date: Fri, 2 Oct 2020 22:22:44 +0200
Subject: [PATCH 4/8] Remove stray debug println

---
 src/utility/M5Button.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/utility/M5Button.cpp b/src/utility/M5Button.cpp
index 41f13ad3..0bc07d54 100644
--- a/src/utility/M5Button.cpp
+++ b/src/utility/M5Button.cpp
@@ -209,7 +209,6 @@ bool Button::wasReleasefor(uint32_t ms) {
 }
 
 bool Button::pressedFor(uint32_t ms) {
-	Serial.println(_time);
 	return (_state && _time - _lastChange >= ms);
 }
 

From 297285f9859f1bc40dcc8441b6154f6447d253fd Mon Sep 17 00:00:00 2001
From: Rop Gonggrijp <rop@gonggri.jp>
Date: Sat, 10 Oct 2020 21:15:23 +0200
Subject: [PATCH 5/8] Ready, documentation, some examples and all...

---
 .../Combi_Buttons_AB_BC.ino                   |   60 +
 .../Responsive_Labels/Responsive_Labels.ino   |   12 +
 keywords.txt                                  |   97 +-
 src/M5Display.cpp                             |   64 +-
 src/M5Display.h                               |   68 +-
 src/M5Stack.cpp                               |    5 +-
 src/M5Stack.h                                 |   20 +-
 src/utility/Config.h                          |    3 +
 src/utility/M5Button.cpp                      | 1061 ++++++++--------
 src/utility/M5Button.h                        | 1092 +++++++++++++----
 src/utility/PointAndZone.cpp                  |  269 ++--
 src/utility/PointAndZone.h                    |  200 ++-
 12 files changed, 2012 insertions(+), 939 deletions(-)
 create mode 100644 examples/M5Button/Combi_Buttons_AB_BC/Combi_Buttons_AB_BC.ino
 create mode 100644 examples/M5Button/Responsive_Labels/Responsive_Labels.ino

diff --git a/examples/M5Button/Combi_Buttons_AB_BC/Combi_Buttons_AB_BC.ino b/examples/M5Button/Combi_Buttons_AB_BC/Combi_Buttons_AB_BC.ino
new file mode 100644
index 00000000..aad1826e
--- /dev/null
+++ b/examples/M5Button/Combi_Buttons_AB_BC/Combi_Buttons_AB_BC.ino
@@ -0,0 +1,60 @@
+/*
+
+  This example will show that the original buttons will not fire any
+  high-level events after the combinations of buttons are detected, and their
+  buttons will generate these events instead. Notice how it is not printing
+  the low-level (E_TOUCH and E_RELEASE) events to not clutter the display.
+
+  See https://github.com/m5stack/M5Stack/blob/master/src/utility/M5Button.h
+  for complete documentation of buttons and events.
+
+*/
+
+#include <M5Stack.h>
+
+// Shortcuts: the & means it's a reference to the same object
+Button& A = M5.BtnA;
+Button& B = M5.BtnB;
+Button& C = M5.BtnC;
+
+// New buttons, not tied to hardware pins
+Button AB = Button(55, 193, 102, 21, true, "BtnAB");
+Button BC = Button(161, 193, 102, 21, true, "BtnBC");
+
+void setup() {
+  M5.begin();
+  A.off = B.off = C.off = AB.off = BC.off = {BLUE, WHITE, NODRAW};
+  A.on  = B.on  = C.on  = AB.on  = BC.on  = {RED,  WHITE, NODRAW};
+  M5.Buttons.draw();
+  M5.Buttons.addHandler(eventDisplay, E_ALL - E_TOUCH - E_RELEASE);
+  M5.Buttons.addHandler(buttonDown, E_TOUCH);
+  M5.Buttons.addHandler(buttonUp, E_RELEASE);
+}
+
+void loop() {
+  M5.update();
+}
+
+void buttonDown(Event& e) {
+  if (A && B && !AB) {
+    A.cancel();
+    B.cancel();
+    AB.fingerDown();
+  }
+  if (B && C && !BC) {
+    B.cancel();
+    C.cancel();
+    BC.fingerDown();
+  }
+}
+
+void buttonUp(Event& e) {
+  if (AB && !(A && B)) AB.fingerUp();
+  if (BC && !(B && C)) BC.fingerUp();
+}
+
+
+void eventDisplay(Event& e) {
+  Serial.printf("%-14s %-18s   %5d ms\n", e.typeName(), e.objName(),
+                e.duration);
+}
diff --git a/examples/M5Button/Responsive_Labels/Responsive_Labels.ino b/examples/M5Button/Responsive_Labels/Responsive_Labels.ino
new file mode 100644
index 00000000..b0faa992
--- /dev/null
+++ b/examples/M5Button/Responsive_Labels/Responsive_Labels.ino
@@ -0,0 +1,12 @@
+#include <M5Stack.h>
+
+void setup() {
+  M5.BtnA.off = M5.BtnB.off = M5.BtnC.off = {BLUE, WHITE, NODRAW};
+  M5.BtnA.on  = M5.BtnB.on  = M5.BtnC.on  = {RED,  WHITE, NODRAW};
+  M5.begin();
+  M5.Buttons.draw();
+}
+
+void loop() {
+  M5.update();
+}
diff --git a/keywords.txt b/keywords.txt
index 68a18694..8716f7c8 100644
--- a/keywords.txt
+++ b/keywords.txt
@@ -27,14 +27,104 @@ BtnC	KEYWORD1
 begin	KEYWORD2
 update	KEYWORD2
 Power	KEYWORD2
+
+
+# Zone
+Zone	KEYWORD1
+rot1	KEYWORD2
+set	KEYWORD2
+contains	KEYWORD2
+rotate	KEYWORD2
+
+# Point
+Point	KEYWORD1
+in	KEYWORD2
+Equals	KEYWORD2
+distanceTo	KEYWORD2
+directionTo	KEYWORD2
+isDirectionTo	KEYWORD2
+valid	KEYWORD2
+
+ButtonColors	KEYWORD1
+bg	KEYWORD2
+text	KEYWORD2
+outline	KEYWORD2
+
+Event	KEYWORD1
+TFT_eSPI_Button	KEYWORD1
+
+instance	KEYWORD2
+instances	KEYWORD2
+
+#Events
+fireEvent	KEYWORD2
+typeName	KEYWORD2
+objName	KEYWORD2
+finger	KEYWORD2
+type	KEYWORD2
+from	KEYWORD2
+to	KEYWORD2
+duration	KEYWORD2
+distance	KEYWORD2
+direction	KEYWORD2
+isDirection KEYWORD2
+button	KEYWORD2
+gesture	KEYWORD2
+
+E_TOUCH	LITERAL1
+E_RELEASE	LITERAL1
+E_MOVE	LITERAL1
+E_GESTURE	LITERAL1
+E_TAP	LITERAL1
+E_DBLTAP	LITERAL1
+E_DRAGGED	LITERAL1
+E_PRESSED	LITERAL1
+E_PRESSING	LITERAL1
+E_LONGPRESSED	LITERAL1
+E_LONGPRESSING	LITERAL1
+
+UP	LITERAL1
+DOWN	LITERAL1
+LEFT	LITERAL1
+RIGHT	LITERAL1
+
+# Button
+Button	KEYWORD1
 isPressed	KEYWORD2
 wasPressed	KEYWORD2
 pressedFor	KEYWORD2
-
 isReleased	KEYWORD2
 wasReleased	KEYWORD2
 releasedFor	KEYWORD2
 wasReleasefor	KEYWORD2
+addHandler	KEYWORD2
+delHandlers	KEYWORD2
+lastChange	KEYWORD2
+cancel	KEYWORD2
+fingerDown	KEYWORD2
+fingerUp	KEYWORD2
+fingerMove	KEYWORD2
+name	KEYWORD2
+setLabel	KEYWORD2
+event	KEYWORD2
+tapTime	KEYWORD2
+dbltapTime	KEYWORD2
+longpressTime	KEYWORD2
+instanceIndex	KEYWORD2
+drawFn	KEYWORD2
+repeatDelay	KEYWORD2
+repeatinterval	KEYWORD2
+on	KEYWORD2
+off	KEYWORD2
+dx	KEYWORD2
+dy	KEYWORD2
+
+
+#Buttons
+Buttons	KEYWORD1
+draw	KEYWORD2
+which	KEYWORD2
+fireEvent	KEYWORD2
 
 held	KEYWORD2
 repeat	KEYWORD2
@@ -72,8 +162,12 @@ textWrap	KEYWORD2
 fontWidth	KEYWORD2
 fontHeight	KEYWORD2
 setFont	KEYWORD2
+setTextFont	KEYWORD2
+setFreeFont	KEYWORD2
 setTextColor	KEYWORD2
 setTextSize	KEYWORD2
+pushState	KEYWORD2
+popState	KEYWORD2
 
 WHITE	LITERAL1
 BLACK	LITERAL1
@@ -82,3 +176,4 @@ GRAY	LITERAL1
 RED	LITERAL1
 BLUE	LITERAL1
 GREEN	LITERAL1
+NODRAW	LITERAL1
diff --git a/src/M5Display.cpp b/src/M5Display.cpp
index 5f5ea038..e86e07f7 100644
--- a/src/M5Display.cpp
+++ b/src/M5Display.cpp
@@ -1,19 +1,18 @@
 #include "M5Display.h"
 
 #ifdef M5Stack_M5Core2
-  #include <M5Touch.h>
-  #define TOUCH M5Touch::instance
-#endif
+#include <M5Touch.h>
+#endif /* M5Stack_M5Core2 */
 
 #define BLK_PWM_CHANNEL 7 // LEDC_CHANNEL_7
 
+// So we can use this instance without including all of M5Core2 / M5Stack
+M5Display* M5Display::instance;
+
 M5Display::M5Display() : TFT_eSPI() {
-  // So we can use the display without including all of M5Core2 / M5Stack 
   if (!instance) instance = this;
 }
 
-M5Display* M5Display::instance;
-
 void M5Display::begin() {
   TFT_eSPI::begin();
   setRotation(1);
@@ -49,7 +48,7 @@ void M5Display::drawBitmap(int16_t x0, int16_t y0, int16_t w, int16_t h, const u
 }
 
 void M5Display::drawBitmap(int16_t x0, int16_t y0, int16_t w, int16_t h, uint16_t *data) {
-  bool swap = getSwapBytes();  
+  bool swap = getSwapBytes();
   setSwapBytes(true);
   pushImage((int32_t)x0, (int32_t)y0, (uint32_t)w, (uint32_t)h, data);
   setSwapBytes(swap);
@@ -647,40 +646,41 @@ void M5Display::popState() {
   padX = s.padX;
   if (s.gfxFont && s.gfxFont != gfxFont) setFreeFont(s.gfxFont);
 }
-  
 
 #ifdef M5Stack_M5Core2
 
-  // Mke sure the touch also rotates if it's not there already
-  void M5Display::setRotation(uint8_t r) {
-    TFT_eSPI::setRotation(r);
-    TOUCH->rotation = r;
-  }
-
-  #ifdef TFT_eSPI_TOUCH_EMULATION
+#ifdef TFT_eSPI_TOUCH_EMULATION
 
-    // Emulates the native (resistive) TFT_eSPI touch interface using M5.Touch
+// Emulates the native (resistive) TFT_eSPI touch interface using M5.Touch
 
-    uint8_t M5Display::getTouchRaw(uint16_t *x, uint16_t *y) { return getTouch(x, y); }
+uint8_t M5Display::getTouchRaw(uint16_t *x, uint16_t *y) {
+  return getTouch(x, y);
+}
 
-    uint16_t M5Display::getTouchRawZ(void) { return (TOUCH->ispressed()) ? 1000 : 0; }
+uint16_t M5Display::getTouchRawZ(void) {
+  return (TOUCH->ispressed()) ? 1000 : 0;
+}
 
-    void M5Display::convertRawXY(uint16_t *x, uint16_t *y)  { return; }
+void M5Display::convertRawXY(uint16_t *x, uint16_t *y) { return; }
 
-    uint8_t M5Display::getTouch(uint16_t *x, uint16_t *y, uint16_t threshold /* = 600 */) {
-      TOUCH->read();
-      if (TOUCH->points) {
-        *x = TOUCH->point[0].x;
-        *y = TOUCH->point[0].y;
-        return true;
-      }
-      return false;
-    }
+uint8_t M5Display::getTouch(uint16_t *x, uint16_t *y,
+                            uint16_t threshold /* = 600 */) {
+  TOUCH->read();
+  if (TOUCH->points) {
+    *x = TOUCH->point[0].x;
+    *y = TOUCH->point[0].y;
+    return true;
+  }
+  return false;
+}
 
-    void M5Display::calibrateTouch(uint16_t *data, uint32_t color_fg, uint32_t color_bg, uint8_t size) { return; }
+void M5Display::calibrateTouch(uint16_t *data, uint32_t color_fg,
+                               uint32_t color_bg, uint8_t size) {
+  return;
+}
 
-    void M5Display::setTouch(uint16_t *data) { return; }
+void M5Display::setTouch(uint16_t *data) { return; }
 
-  #endif /* TFT_eSPI_TOUCH_EMULATION */
+#endif /* TFT_eSPI_TOUCH_EMULATION */
 
-#endif /* M5Stack_M5Core2 */
\ No newline at end of file
+#endif /* M5Stack_M5Core2 */
diff --git a/src/M5Display.h b/src/M5Display.h
index c3e027d5..3a1b2fbe 100644
--- a/src/M5Display.h
+++ b/src/M5Display.h
@@ -1,10 +1,11 @@
 #ifndef _M5DISPLAY_H_
   #define _M5DISPLAY_H_
 
-  #include "utility/Config.h" // This is where Core2 defines would be
   #include <Arduino.h>
   #include <FS.h>
   #include <SPI.h>
+
+  #include "utility/Config.h"  // This is where Core2 defines would be
   #include "utility/In_eSPI.h"
   #include "utility/Sprite.h"
 
@@ -15,12 +16,12 @@
     JPEG_DIV_8,
     JPEG_DIV_MAX
   } jpeg_div_t;
-  
+
   struct DisplayState {
     uint8_t textfont, textsize, datum;
-    const GFXfont* gfxFont;
+    const GFXfont *gfxFont;
     uint32_t textcolor, textbgcolor;
-    int32_t  cursor_x, cursor_y, padX;
+    int32_t cursor_x, cursor_y, padX;
   };
 
   class M5Display : public TFT_eSPI {
@@ -47,7 +48,7 @@
         #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(ESP32_PARALLEL)
           if(!inTransaction) {
             if (!locked) {
-              locked = true; 
+              locked = true;
               SPI.endTransaction();
             }
           }
@@ -100,40 +101,35 @@
                     uint16_t maxWidth = 0, uint16_t maxHeight = 0,
                     uint16_t offX = 0, uint16_t offY = 0,
                     double scale = 1.0, uint8_t alphaThreshold = 127);
-                    
-
-      // Saves and restores font properties, datum, cursor, colors so code
-      // can be non-invasive. Just make sure that every push is also popped
-      // when you're done to prevent stack from growing out of control.
-      //
-      // (User code can never do this completely because the gfxFont
-      // class variable of TFT_eSPI is protected.)
-      #define M5DISPLAY_HAS_PUSH_POP
-      public:
-        void pushState();
-        void popState();
-      private:
-        std::vector<DisplayState> _displayStateStack;
-    
+
+    // Saves and restores font properties, datum, cursor and colors so
+    // code can be non-invasive. Just make sure that every push is also
+    // popped when you're done to prevent stack from growing.
+    //
+    // (User code can never do this completely because the gfxFont
+    // class variable of TFT_eSPI is protected.)
+    #define M5DISPLAY_HAS_PUSH_POP
+     public:
+      void pushState();
+      void popState();
+
+     private:
+      std::vector<DisplayState> _displayStateStack;
 
     #ifdef M5Stack_M5Core2
-    
-      public:
-        // Make sure the touch also rotates if it's not there already
-        void setRotation(uint8_t r);
-            
-      #ifdef TFT_eSPI_TOUCH_EMULATION   
+
+      #ifdef TFT_eSPI_TOUCH_EMULATION
         // Emulates the TFT_eSPI touch interface using M5.Touch
-        public:
-          uint8_t  getTouchRaw(uint16_t *x, uint16_t *y);
-          uint16_t getTouchRawZ(void);
-          void     convertRawXY(uint16_t *x, uint16_t *y);
-          uint8_t  getTouch(uint16_t *x, uint16_t *y, uint16_t threshold = 600);
-          void     calibrateTouch(uint16_t *data, uint32_t color_fg, uint32_t color_bg, uint8_t size);
-          void     setTouch(uint16_t *data);
+       public:
+        uint8_t getTouchRaw(uint16_t *x, uint16_t *y);
+        uint16_t getTouchRawZ(void);
+        void convertRawXY(uint16_t *x, uint16_t *y);
+        uint8_t getTouch(uint16_t *x, uint16_t *y, uint16_t threshold = 600);
+        void calibrateTouch(uint16_t *data, uint32_t color_fg, uint32_t color_bg,
+                            uint8_t size);
+        void setTouch(uint16_t *data);
       #endif /* TFT_eSPI_TOUCH_EMULATION */
-    
+
     #endif /* M5Stack_M5Core2 */
-  
-  };
+};
 #endif /* _M5DISPLAY_H_ */
diff --git a/src/M5Stack.cpp b/src/M5Stack.cpp
index c784c1dc..3567adbb 100644
--- a/src/M5Stack.cpp
+++ b/src/M5Stack.cpp
@@ -30,6 +30,9 @@ void M5Stack::begin(bool LCDEnable, bool SDEnable, bool SerialEnable, bool I2CEn
   // LCD INIT
   if (LCDEnable == true) {
     Lcd.begin();
+
+    // draw global buttons, set up before the display was ready
+    Buttons.draw();
   }
 
   // TONE
@@ -47,7 +50,7 @@ void M5Stack::begin(bool LCDEnable, bool SDEnable, bool SerialEnable, bool I2CEn
     Serial.println("OK");
   }
 
-  // if use M5GO button, need set gpio15 OD or PP mode to avoid affecting the wifi signal  
+  // if use M5GO button, need set gpio15 OD or PP mode to avoid affecting the wifi signal
   pinMode(15, OUTPUT_OPEN_DRAIN);
 }
 
diff --git a/src/M5Stack.h b/src/M5Stack.h
index 421578d8..6d6a5d7c 100644
--- a/src/M5Stack.h
+++ b/src/M5Stack.h
@@ -95,7 +95,7 @@
 
 #ifndef _M5STACK_H_
   #define _M5STACK_H_
-  
+
   #if defined(ESP32)
 
     #include "gitTagVersion.h"
@@ -130,12 +130,12 @@
 
         // Button API
         #define DEBOUNCE_MS 10
-        Button BtnA = Button(BUTTON_A_PIN, true, DEBOUNCE_MS, "hw", 3, 218, 102, 21, true, "BtnA");
-        Button BtnB = Button(BUTTON_B_PIN, true, DEBOUNCE_MS, "hw", 109, 218, 102, 21, true, "BtnB");
-        Button BtnC = Button(BUTTON_C_PIN, true, DEBOUNCE_MS, "hw", 215, 218, 102, 21, true, "BtnC");
-        
-        // Events
-        M5Events Events;
+        Button BtnA = Button(BUTTON_A_PIN, true, DEBOUNCE_MS, "hw",
+                             3, 218, 102, 21, true, "BtnA");
+        Button BtnB = Button(BUTTON_B_PIN, true, DEBOUNCE_MS, "hw",
+                             109, 218, 102, 21, true, "BtnB");
+        Button BtnC = Button(BUTTON_C_PIN, true, DEBOUNCE_MS, "hw",
+                             215, 218, 102, 21, true, "BtnC");
 
         // SPEAKER
         SPEAKER Speaker;
@@ -158,7 +158,7 @@
 
         // I2C
         CommUtil I2C;
-          
+
         /**
         * Function has been move to Power class.(for compatibility)
         * This name will be removed in a future release.
@@ -166,11 +166,11 @@
         void setPowerBoostKeepOn(bool en) __attribute__((deprecated));
         void setWakeupButton(uint8_t button) __attribute__((deprecated));
         void powerOFF() __attribute__((deprecated));
-        
+
       private:
           bool isInited;
     };
-    
+
     extern M5Stack M5;
     #define m5 M5
     #define lcd Lcd
diff --git a/src/utility/Config.h b/src/utility/Config.h
index 7b390837..9996b3a0 100644
--- a/src/utility/Config.h
+++ b/src/utility/Config.h
@@ -1,6 +1,9 @@
 #ifndef _CONFIG_H_
   #define _CONFIG_H_
 
+  #define TFT     M5Display::instance
+  #define BUTTONS	M5Buttons::instance
+
   // Screen
   #define TFT_LED_PIN 32
   #define TFT_DC_PIN 27
diff --git a/src/utility/M5Button.cpp b/src/utility/M5Button.cpp
index 0bc07d54..3f05a026 100644
--- a/src/utility/M5Button.cpp
+++ b/src/utility/M5Button.cpp
@@ -1,634 +1,715 @@
 #include "M5Button.h"
 
-
 // Button class
 
 /* static */ std::vector<Button*> Button::instances;
 
-Button::Button(
-  int16_t x_, int16_t y_, int16_t w_, int16_t h_,
-  bool rot1_ /* = false */,
-  const char* name_ /* = "" */,
-  ButtonColors off_ /*= {NODRAW, NODRAW, NODRAW} */,
-  ButtonColors on_ /* = {NODRAW, NODRAW, NODRAW} */,
-  uint8_t datum_ /* = BUTTON_DATUM */,
-  int16_t dx_ /* = 0 */,
-  int16_t dy_ /* = 0 */,
-  uint8_t r_ /* = 0xFF */
-) : Zone(x_, y_, w_, h_, rot1_) {
-	pin = 0xFF;
-	invert = false;
-	dbTime = 0;
-	strncpy(name, name_, 15);
-	off = off_;
-	on = on_;
-	datum = datum_;
-	dx = dx_;
-	dy = dy_;
-	r =  r_;
-	init();
-}
-
-Button::Button(
-  uint8_t pin_, uint8_t invert_, uint32_t dbTime_, String hw /* = "hw" */,
-  int16_t x_ /* = 0 */, int16_t y_ /* = 0 */, int16_t w_ /* = 0 */, int16_t h_ /* = 0 */,
-  bool rot1_ /* = false */,
-  const char* name_ /* = "" */,
-  ButtonColors off_ /*= {NODRAW, NODRAW, NODRAW} */,
-  ButtonColors on_ /* = {NODRAW, NODRAW, NODRAW} */,
-  uint8_t datum_ /* = BUTTON_DATUM */,
-  int16_t dx_ /* = 0 */,
-  int16_t dy_ /* = 0 */,
-  uint8_t r_ /* = 0xFF */
-) : Zone(x_, y_, w_, h_, rot1_) {
-	pin = pin_;
-	invert = invert_;
-	dbTime = dbTime_;
-	strncpy(name, name_, 15);
-	off = off_;
-	on = on_;
-	datum = datum_;
-	dx = dx_;
-	dy = dy_;
-	r =  r_;
-	init();
+Button::Button(int16_t x_, int16_t y_, int16_t w_, int16_t h_,
+               bool rot1_ /* = false */, const char* name_ /* = "" */,
+               ButtonColors off_ /*= {NODRAW, NODRAW, NODRAW} */,
+               ButtonColors on_ /* = {NODRAW, NODRAW, NODRAW} */,
+               uint8_t datum_ /* = BUTTON_DATUM */, int16_t dx_ /* = 0 */,
+               int16_t dy_ /* = 0 */, uint8_t r_ /* = 0xFF */
+               )
+    : Zone(x_, y_, w_, h_, rot1_) {
+  _pin = 0xFF;
+  _invert = false;
+  _dbTime = 0;
+  strncpy(_name, name_, 15);
+  off = off_;
+  on = on_;
+  datum = datum_;
+  dx = dx_;
+  dy = dy_;
+  r = r_;
+  init();
+}
+
+Button::Button(uint8_t pin_, uint8_t invert_, uint32_t dbTime_,
+               String hw_ /* = "hw" */, int16_t x_ /* = 0 */,
+               int16_t y_ /* = 0 */, int16_t w_ /* = 0 */, int16_t h_ /* = 0 */,
+               bool rot1_ /* = false */, const char* name_ /* = "" */,
+               ButtonColors off_ /*= {NODRAW, NODRAW, NODRAW} */,
+               ButtonColors on_ /* = {NODRAW, NODRAW, NODRAW} */,
+               uint8_t datum_ /* = BUTTON_DATUM */, int16_t dx_ /* = 0 */,
+               int16_t dy_ /* = 0 */, uint8_t r_ /* = 0xFF */
+               )
+    : Zone(x_, y_, w_, h_, rot1_) {
+  _pin = pin_;
+  _invert = invert_;
+  _dbTime = dbTime_;
+  strncpy(_name, name_, 15);
+  off = off_;
+  on = on_;
+  datum = datum_;
+  dx = dx_;
+  dy = dy_;
+  r = r_;
+  init();
 }
 
 Button::~Button() {
-	for(int i = 0; i < instances.size(); ++i) {
-		if (instances[i] == this) {
-			EVENTS->delHandlers(nullptr, this, nullptr);
-			instances.erase(instances.begin() + i);
-			return;
-		}
-	}
+  for (int i = 0; i < instances.size(); ++i) {
+    if (instances[i] == this) {
+      BUTTONS->delHandlers(nullptr, this, nullptr);
+      instances.erase(instances.begin() + i);
+      return;
+    }
+  }
 }
 
-Button::operator bool() {
-	return _state;
-}
+Button::operator bool() { return _state; }
+
+bool Button::operator==(const Button& b) { return (this == &b); }
+bool Button::operator!=(const Button& b) { return (this != &b); }
+bool Button::operator==(Button* b) { return (this == b); }
+bool Button::operator!=(Button* b) { return (this != b); }
 
 void Button::init() {
-	_state = _tapWait = _pressing = _manuallyRead = false;
-	_setState = 0xFF;
-	_time = _lastChange = _pressTime = millis();
-	_hold_time = -1;
-	_textFont = _textSize = 0;
-	_freeFont = nullptr;
-	drawFn = nullptr;
-	compat = 0;
-	drawZone = this;
-	tapTime = TAP_TIME;
-	dbltapTime = DBLTAP_TIME;
-	longPressTime = LONGPRESS_TIME;
-	strncpy(label, name, 16);
-	instances.push_back(this);
-	draw();
+  _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();
 }
 
 int16_t Button::instanceIndex() {
-	for(int16_t i = 0; i < instances.size(); ++i) {
-		if (instances[i] == this) return i;
-	}
-	return -1;
-}	
+  for (int16_t i = 0; i < instances.size(); ++i) {
+    if (instances[i] == this) return i;
+  }
+  return -1;
+}
 
 bool Button::read(bool manualRead /* = true */) {
-	// if (pin == 0xFF) return false;
-	if (manualRead) _manuallyRead = true;
-	event = Event();
-	uint16_t duration = _time - _lastChange;
-	Point invalid;
-	if (changed) {
-		// Identify deeper meaning, if any, of state change last time
-		changed = false;
-		_lastChange = _time;
-		if (!_state) {
-			if (!_cancelled) {
-				if (duration <= tapTime) {
-					if (_tapWait) {
-						EVENTS->fireEvent(0, E_DBLTAP, invalid, invalid, 0, this, nullptr);
-						_tapWait = false;
-						_pressing = false;
-						_longPressing = false;
-						return _state;
-					} else {
-						_tapWait = true;
-					}
-				} else if (_pressing) {
-					EVENTS->fireEvent(0, _longPressing ? E_LONGPRESSED : E_PRESSED, invalid, invalid, duration, this, nullptr);
-					_pressing = false;
-					_longPressing = false;
-					return _state;
-				}
-			}
-		}
-	} else {
-		if (_cancelled) {
-			if (!_state) _cancelled = false;
-		} else {
-			// Timeouts
-			if (_tapWait && duration >= dbltapTime) {
-				EVENTS->fireEvent(0, E_TAP, invalid, invalid, 0, this, nullptr);
-				_tapWait = false;
-				_pressing = false;
-				return _state;
-			}
-			if (_state && !_pressing && duration > tapTime) {
-				if (tapTime) EVENTS->fireEvent(0, E_PRESSING, invalid, invalid, 0, this, nullptr);
-				_pressing = true;
-				return _state;
-			}
-			if (longPressTime && _state && !_longPressing && duration > longPressTime) {
-				EVENTS->fireEvent(0, E_LONGPRESSING, invalid, invalid, 0, this, nullptr);
-				_longPressing = true;
-				return _state;
-			}
-		}
-	}
-	// Do an actual read from the pin (or _setState)
-	_time = millis();
-	uint8_t newState = false;
-	if (_setState == 0xFF) {
-		if (pin != 0xFF) {
-			pinMode(pin, INPUT_PULLUP);
-			newState = (digitalRead(pin));
-			newState = invert ? !newState : newState;
-		}
-	} else {
-		newState = _setState;	
-	}
-	if (_time - _lastChange >= dbTime && newState != _state){
-		_state = newState;
-		if (_state) {
-			EVENTS->fireEvent(0, E_TOUCH, invalid, invalid, 0, this, nullptr);
-			_pressTime = _time;		
-		} else {
-			EVENTS->fireEvent(0, E_RELEASE, invalid, invalid, duration, this, nullptr);
-		}
-		changed = true;
-	}
-	return _state;
-}
-
-void Button::setState(bool newState) { _setState = newState; }
-
-void Button::freeState() {_setState = 0xFF; }
+  if (manualRead) _manuallyRead = true;
+  uint32_t duration = _time - _pressTime;
+  if (_changed) {
+    _changed = false;
+    _lastChange = _time;
+    if (!_state && !_cancelled && postReleaseEvents()) return _state;
+  } else {
+    if (!_cancelled && timeoutEvents()) return _state;
+    if (!_state) _cancelled = false;
+  }
+  // Do actual read from the pin if this is a hardware button
+  _time = millis();
+  uint8_t newState = false;
+  if (_pin != 0xFF) {
+    newState = (digitalRead(_pin));
+    newState = _invert ? !newState : newState;
+    if (newState != _state && _time - _lastChange >= _dbTime) {
+      if (newState) fingerDown();
+      if (!newState) fingerUp();
+    }
+  }
+  return _state;
+}
+
+void Button::fingerDown(Point pos /* = Point() */,
+                        uint8_t finger /* = 0 */) {
+  _finger = finger;
+  _currentPt[finger] = _fromPt[finger] = pos;
+  if (!_state && !_currentPt[1 - finger]) {
+    // other finger not here
+    _state = true;
+    _changed = true;
+    _pressTime = _time;
+    draw();
+  }
+  BUTTONS->fireEvent(finger, E_TOUCH, pos, pos, 0, this, nullptr);
+}
+
+void Button::fingerUp(uint8_t finger /* = 0  */) {
+  uint32_t duration = _time - _pressTime;
+  _finger = finger;
+  _toPt[finger] = _currentPt[finger];
+  _currentPt[finger] = Point();
+  if (_state && !_currentPt[1 - finger]) {
+    // other finger not here
+    _state = false;
+    _changed = true;
+    draw();
+  }
+  BUTTONS->fireEvent(finger, E_RELEASE, _fromPt[finger], _toPt[finger],
+                     duration, this, nullptr);
+}
+
+void Button::fingerMove(Point pos, uint8_t finger) {
+  BUTTONS->fireEvent(finger, E_MOVE, _currentPt[finger], pos,
+                     _time - _lastChange, this, nullptr);
+  _currentPt[finger] = pos;
+}
+
+bool Button::postReleaseEvents() {
+  uint32_t duration = _time - _pressTime;
+  if (_toPt[_finger] && !contains(_toPt[_finger])) {
+    BUTTONS->fireEvent(_finger, E_DRAGGED, _fromPt[_finger], _toPt[_finger],
+                       duration, this, nullptr);
+    _tapWait = false;
+    _pressing = false;
+    _longPressing = false;
+    return true;
+  }
+  if (duration <= tapTime) {
+    if (_tapWait) {
+      BUTTONS->fireEvent(_finger, E_DBLTAP, _fromPt[_finger], _toPt[_finger],
+                         duration, this, nullptr);
+      _tapWait = false;
+      _pressing = false;
+      _longPressing = false;
+      return true;
+    }
+    _tapWait = true;
+  } else if (_pressing) {
+    BUTTONS->fireEvent(_finger, _longPressing ? E_LONGPRESSED : E_PRESSED,
+                       _fromPt[_finger], _toPt[_finger], duration, this,
+                       nullptr);
+    _pressing = false;
+    _longPressing = false;
+    return true;
+  }
+  return false;
+}
+
+bool Button::timeoutEvents() {
+  uint32_t duration = _time - _pressTime;
+  if (_tapWait && duration >= dbltapTime) {
+    BUTTONS->fireEvent(_finger, E_TAP, _fromPt[_finger], _toPt[_finger],
+                       duration, this, nullptr);
+    _tapWait = false;
+    _pressing = false;
+    return true;
+  }
+  if (!_state) return false;
+  if ((!_pressing && duration > tapTime) ||
+      (repeatDelay && duration > repeatDelay &&
+       _time - _lastRepeat > repeatInterval)) {
+    BUTTONS->fireEvent(_finger, E_PRESSING, _fromPt[_finger],
+                       _currentPt[_finger], duration, this, nullptr);
+    _lastRepeat = _time;
+    _pressing = true;
+    return true;
+  }
+  if (longPressTime && !_longPressing && duration > longPressTime) {
+    BUTTONS->fireEvent(_finger, E_LONGPRESSING, _fromPt[_finger],
+                       _currentPt[_finger], duration, this, nullptr);
+    _longPressing = true;
+    return true;
+  }
+  return false;
+}
 
 void Button::cancel() {
-	_cancelled = true;
-	_tapWait = false;
-	draw(off);
+  _cancelled = true;
+  _tapWait = false;
+  draw(off);
 }
 
-
-// bool Button::setState(bool newState) {
-// 	if (newState != _state) {
-// 		_state = newState;
-// 		_lastChange = _time;
-// 		changed = true;
-// 		if (_state) _pressTime = _time;
-// 		draw();
-// 	}
-// 	return _state;
-// }
+char* Button::name() { return _name; }
 
 bool Button::isPressed() { return _state; }
 
 bool Button::isReleased() { return !_state; }
 
-bool Button::wasPressed() { return _state && changed; }
+bool Button::wasPressed() { return _state && _changed; }
 
 bool Button::wasReleased() {
-	return (!_state && changed && millis() - _pressTime < _hold_time);
+  return (!_state && _changed && millis() - _pressTime < _hold_time);
 }
 
 bool Button::wasReleasefor(uint32_t ms) {
-	_hold_time = ms;
-	return (!_state && changed && millis() - _pressTime >= ms);
+  _hold_time = ms;
+  return (!_state && _changed && millis() - _pressTime >= ms);
 }
 
 bool Button::pressedFor(uint32_t ms) {
-	return (_state && _time - _lastChange >= ms);
+  return (_state && _time - _lastChange >= ms);
 }
 
 bool Button::pressedFor(uint32_t ms, uint32_t continuous_time) {
-	if (_state && _time - _lastChange >= ms && _time - _lastLongPress >= continuous_time) {
-		_lastLongPress = _time;
-		return true;
-	} 
-	return false;
+  if (_state && _time - _lastChange >= ms &&
+      _time - _lastLongPress >= continuous_time) {
+    _lastLongPress = _time;
+    return true;
+  }
+  return false;
 }
 
 bool Button::releasedFor(uint32_t ms) {
-	return (!_state && _time - _lastChange >= ms);
+  return (!_state && _time - _lastChange >= ms);
 }
 
 uint32_t Button::lastChange() { return (_lastChange); }
 
 void Button::addHandler(void (*fn)(Event&), uint16_t eventMask /* = E_ALL */) {
-	EVENTS->addHandler(fn, eventMask, this, nullptr);
+  BUTTONS->addHandler(fn, eventMask, this, nullptr);
 }
 
 void Button::delHandlers(void (*fn)(Event&) /* = nullptr */) {
-	EVENTS->delHandlers(fn, this, nullptr);
+  BUTTONS->delHandlers(fn, this, nullptr);
 }
 
 // visual things for Button
 
 void Button::draw() {
-	if (_state) draw(on); else draw(off);
-} 
+  if (_state)
+    draw(on);
+  else
+    draw(off);
+}
 
-void Button::draw(ButtonColors bc) {
-	// use locally set draw function if aplicable, global one otherwise
-	if (drawFn) {
-		drawFn(this, bc);
-	} else if (BUTTONS->drawFn) {
-		BUTTONS->drawFn(this, bc);
-	}
+void Button::erase(uint16_t color /* = BLACK */) {
+  draw({color, NODRAW, NODRAW});
 }
 
-void Button::setLabel(const char* label_) {
-	strncpy(label, label_, 50);
+void Button::draw(ButtonColors bc) {
+  // use locally set draw function if aplicable, global one otherwise
+  if (drawFn) {
+    drawFn(*this, bc);
+  } else if (BUTTONS->drawFn) {
+    BUTTONS->drawFn(*this, bc);
+  }
 }
 
+char* Button::label() { return _label; }
+
+void Button::setLabel(const char* label_) { strncpy(_label, label_, 50); }
+
 void Button::setFont(const GFXfont* freeFont_) {
-	_freeFont = freeFont_;
-	_textFont = 1;
+  _freeFont = freeFont_;
+  _textFont = 1;
 }
 
 void Button::setFont(uint8_t textFont_ /* = 0 */) {
-	_freeFont = nullptr;
-	_textFont = textFont_;
+  _freeFont = nullptr;
+  _textFont = textFont_;
 }
 
-void Button::setTextSize(uint8_t textSize_ /* = 0 */) {
-	_textSize = textSize_;
-}
-
-
+void Button::setTextSize(uint8_t textSize_ /* = 0 */) { _textSize = textSize_; }
 
 // M5Buttons class
 
 /* static */ M5Buttons* M5Buttons::instance;
 
-/* static */ void M5Buttons::drawFunction(Button* button, ButtonColors bc) {
-	if (bc.bg == NODRAW && bc.outline == NODRAW && bc.text == NODRAW) return;
-	if (!button || !button->drawZone) return;
-	Button& b = *button;
-	Zone& z = *b.drawZone;
-		
-	uint8_t r = (b.r == 0xFF) ? min(z.w, z.h) / 4 : b.r;
-	
-	if (bc.bg != NODRAW) {
-		if (r >= 2) {
-			TFT->fillRoundRect(z.x, z.y, z.w, z.h, r, bc.bg);
-		} else {
-			TFT->fillRect(z.x, z.y, z.w, z.h, bc.bg);
-		}
-	}
-
-	if (bc.outline != NODRAW) {
-		if (r >= 2) {
-			TFT->drawRoundRect(z.x, z.y, z.w, z.h, r, bc.outline);
-		} else {
-			TFT->drawRect(z.x, z.y, z.w, z.h, bc.outline);
-		}
-	}
-
-	if (bc.text != NODRAW && bc.text != bc.bg && b.label != "") {
-	
-		// figure out where to put the text
-		uint16_t tx, ty;
-		tx = z.x + (z.w / 2);
-		ty = z.y + (z.h / 2);
-		
-		if (!b.compat) {
-			uint8_t margin = max(r / 2, 6);
-			switch (b.datum) {
-			  case TL_DATUM:
-			  case ML_DATUM:
-			  case BL_DATUM:
-				tx = z.x + margin;
-				break;
-			  case TR_DATUM:
-			  case MR_DATUM:
-			  case BR_DATUM:
-				tx = z.x + z.w - margin;
-				break;
-			}
-			switch (b.datum) {
-			  case TL_DATUM:
-			  case TC_DATUM:
-			  case TR_DATUM:
-				ty = z.y + margin;
-				break;
-			  case BL_DATUM:
-			  case BC_DATUM:
-			  case BR_DATUM:
-				ty = z.y + z.h - margin;
-				break;
-			}
-		}
-		
-		// Save state
-		uint8_t tempdatum = TFT->getTextDatum();
-		uint16_t tempPadding = TFT->padX;
-		if (!b.compat) TFT->pushState();
-	
-	
-		// Actual drawing of text
-		TFT->setTextColor(bc.text);
-		if (b._textSize) TFT->setTextSize(b._textSize);
-		  else TFT->setTextSize(BUTTONS->_textSize);
-		if (b._textFont) {
-			if (b._freeFont) TFT->setFreeFont(b._freeFont); 
-			  else TFT->setTextFont(b._textFont);
-		} else {
-			if (BUTTONS->_freeFont) TFT->setFreeFont(BUTTONS->_freeFont);
-			  else TFT->setTextFont(BUTTONS->_textFont);
-		}
-		TFT->setTextDatum(b.datum);
-		TFT->setTextPadding(0);
-		TFT->drawString(b.label, tx + b.dx, ty + b.dy);
-		// Set state back
-		if (!b.compat) {
-			TFT->popState();
-		} else {
-			TFT->setTextDatum(tempdatum);
-			TFT->setTextPadding(tempPadding);
-		}
-	}
+/* static */ void M5Buttons::drawFunction(Button& b, ButtonColors bc) {
+  if (bc.bg == NODRAW && bc.outline == NODRAW && bc.text == NODRAW) return;
+  Zone z = (b.drawZone) ? b.drawZone : b;
+  if (z.rot1) z.rotate(TFT->rotation);
+
+  uint8_t r = (b.r == 0xFF) ? min(z.w, z.h) / 4 : b.r;
+
+  if (bc.bg != NODRAW) {
+    if (r >= 2) {
+      TFT->fillRoundRect(z.x, z.y, z.w, z.h, r, bc.bg);
+    } else {
+      TFT->fillRect(z.x, z.y, z.w, z.h, bc.bg);
+    }
+  }
+
+  if (bc.outline != NODRAW) {
+    if (r >= 2) {
+      TFT->drawRoundRect(z.x, z.y, z.w, z.h, r, bc.outline);
+    } else {
+      TFT->drawRect(z.x, z.y, z.w, z.h, bc.outline);
+    }
+  }
+
+  if (bc.text != NODRAW && bc.text != bc.bg && b._label != "") {
+    // figure out where to put the text
+    uint16_t tx, ty;
+    tx = z.x + (z.w / 2);
+    ty = z.y + (z.h / 2);
+
+    if (!b._compat) {
+      uint8_t margin = max(r / 2, 6);
+      switch (b.datum) {
+        case TL_DATUM:
+        case ML_DATUM:
+        case BL_DATUM:
+          tx = z.x + margin;
+          break;
+        case TR_DATUM:
+        case MR_DATUM:
+        case BR_DATUM:
+          tx = z.x + z.w - margin;
+          break;
+      }
+      switch (b.datum) {
+        case TL_DATUM:
+        case TC_DATUM:
+        case TR_DATUM:
+          ty = z.y + margin;
+          break;
+        case BL_DATUM:
+        case BC_DATUM:
+        case BR_DATUM:
+          ty = z.y + z.h - margin;
+          break;
+      }
+    }
+
+    // Save state
+    uint8_t tempdatum = TFT->getTextDatum();
+    uint16_t tempPadding = TFT->padX;
+    if (!b._compat) TFT->pushState();
+
+    // Actual drawing of text
+    TFT->setTextColor(bc.text);
+    if (b._textSize)
+      TFT->setTextSize(b._textSize);
+    else
+      TFT->setTextSize(BUTTONS->_textSize);
+    if (b._textFont) {
+      if (b._freeFont)
+        TFT->setFreeFont(b._freeFont);
+      else
+        TFT->setTextFont(b._textFont);
+    } else {
+      if (BUTTONS->_freeFont)
+        TFT->setFreeFont(BUTTONS->_freeFont);
+      else
+        TFT->setTextFont(BUTTONS->_textFont);
+    }
+    TFT->setTextDatum(b.datum);
+    TFT->setTextPadding(0);
+    TFT->drawString(b._label, tx + b.dx, ty + b.dy);
+    // Set state back
+    if (!b._compat) {
+      TFT->popState();
+    } else {
+      TFT->setTextDatum(tempdatum);
+      TFT->setTextPadding(tempPadding);
+    }
+  }
 }
 
 M5Buttons::M5Buttons() {
-	if (!instance) instance = this;
-	drawFn = drawFunction;
-	_freeFont = BUTTON_FREEFONT;
-	_textFont = BUTTON_TEXTFONT;
-	_textSize = BUTTON_TEXTSIZE;
-}
-
-void M5Buttons::setUnchanged() {
-	for ( auto button : Button::instances) {
-		if (button->pin == 0xFF) {
-			button->changed = false;
-			button->_time = millis();
-			button->event = Event();
-		}
-	}
+  if (!instance) instance = this;
+  drawFn = drawFunction;
+  _freeFont = BUTTON_FREEFONT;
+  _textFont = BUTTON_TEXTFONT;
+  _textSize = BUTTON_TEXTSIZE;
 }
 
 Button* M5Buttons::which(Point& p) {
-	if (!Button::instances.size()) return nullptr;
-	for(int i = Button::instances.size() - 1; i >= 0 ; --i) {
-		Button* b = Button::instances[i];
-		if (b->pin == 0xFF && b->contains(p)) return b;
-	}
-	return nullptr;
+  if (!Button::instances.size()) return nullptr;
+  for (int i = Button::instances.size() - 1; i >= 0; --i) {
+    Button* b = Button::instances[i];
+    if (b->_pin == 0xFF && b->contains(p)) return b;
+  }
+  return nullptr;
 }
 
 void M5Buttons::draw() {
-	for ( auto button : Button::instances ) button->draw();
+  for (auto button : Button::instances) button->draw();
 }
 
 void M5Buttons::update() {
-	for ( auto button : Button::instances ) {
-		if (!button->_manuallyRead) button->read(false);
-	}
+#ifdef _M5TOUCH_H_
+  for (auto gesture : Gesture::instances) gesture->_detected = false;
+  BUTTONS->event = Event();
+  if (TOUCH->wasRead || _leftovers) {
+    _finger[TOUCH->point0finger].current = TOUCH->point[0];
+    _finger[1 - TOUCH->point0finger].current = TOUCH->point[1];
+    _leftovers = true;
+    for (uint8_t i = 0; i < 2; i++) {
+      if (i == 1) _leftovers = false;
+      Finger& fi = _finger[i];
+      Point& curr = fi.current;
+      Point prev = fi.previous;
+      fi.previous = fi.current;
+      if (curr == prev) continue;
+      if (!prev && curr) {
+        // A new touch happened
+        fi.startTime = millis();
+        fi.startPoint = curr;
+        fi.button = BUTTONS->which(curr);
+        if (fi.button) {
+          fi.button->fingerDown(curr, i);
+          return;
+        }
+      } else if (prev && !curr) {
+        // Finger removed
+        uint16_t duration = millis() - fi.startTime;
+        for (auto gesture : Gesture::instances) {
+          if (gesture->test(fi.startPoint, prev, duration)) {
+            BUTTONS->fireEvent(i, E_GESTURE, fi.startPoint, prev, duration,
+                               nullptr, gesture);
+            if (fi.button) fi.button->cancel();
+            break;
+          }
+        }
+        if (fi.button) {
+          fi.button->fingerUp(i);
+          return;
+        }
+      } else {
+        // Finger moved
+        if (fi.button) {
+          fi.button->fingerMove(curr, i);
+          return;
+        }
+      }
+    }
+  }
+#endif /* _M5TOUCH_H_ */
+
+  for (auto button : Button::instances) {
+    if (!button->_manuallyRead) button->read(false);
+  }
 }
 
 void M5Buttons::setFont(const GFXfont* freeFont_) {
-	_freeFont = freeFont_;
-	_textFont = 1;
+  _freeFont = freeFont_;
+  _textFont = 1;
 }
 
 void M5Buttons::setFont(uint8_t textFont_) {
-	_freeFont = nullptr;
-	_textFont = textFont_;
+  _freeFont = nullptr;
+  _textFont = textFont_;
+}
+
+void M5Buttons::setTextSize(uint8_t textSize_) { _textSize = textSize_; }
+
+Event M5Buttons::fireEvent(uint8_t finger, uint16_t type, Point& from,
+                           Point& to, uint16_t duration, Button* button,
+                           Gesture* gesture) {
+  Event e;
+  e.finger = finger;
+  e.type = type;
+  e.from = from;
+  e.to = to;
+  e.duration = duration;
+  e.button = button;
+  e.gesture = gesture;
+  if (button) button->event = e;
+  event = e;
+  for (auto h : _eventHandlers) {
+    if (!(h.eventMask & e.type)) continue;
+    if (h.button && h.button != e.button) continue;
+    if (h.gesture && h.gesture != e.gesture) continue;
+    h.fn(e);
+  }
+  return e;
+}
+
+void M5Buttons::addHandler(void (*fn)(Event&), uint16_t eventMask /* = E_ALL */,
+                           Button* button /* = nullptr */,
+                           Gesture* gesture /* = nullptr */
+) {
+  EventHandler handler;
+  handler.fn = fn;
+  handler.eventMask = eventMask;
+  handler.button = button;
+  handler.gesture = gesture;
+  _eventHandlers.push_back(handler);
 }
 
-void M5Buttons::setTextSize(uint8_t textSize_) {
-	_textSize = textSize_;
+void M5Buttons::delHandlers(void (*fn)(Event&) /* = nullptr */,
+                            Button* button /* = nullptr */,
+                            Gesture* gesture /* = nullptr */
+) {
+  for (int i = _eventHandlers.size() - 1; i >= 0; --i) {
+    if (fn && fn != _eventHandlers[i].fn) continue;
+    if (button && _eventHandlers[i].button != button) continue;
+    if (gesture && _eventHandlers[i].gesture != gesture) continue;
+    _eventHandlers.erase(_eventHandlers.begin() + i);
+  }
 }
 
-
-
 // Gesture class
 
 std::vector<Gesture*> Gesture::instances;
 
-Gesture::Gesture(
-  Zone& fromZone_,
-  Zone& toZone_,
-  const char* name_ /* = "" */,
-  uint16_t maxTime_ /* = 500 */,
-  uint16_t minDistance_ /* = 50 */
+Gesture::Gesture(Zone fromZone_, Zone toZone_, const char* name_ /* = "" */,
+                 uint16_t minDistance_ /* = GESTURE_MINDIST */,
+                 int16_t direction_ /* = INVALID_VALUE */,
+                 uint8_t plusminus_ /* = PLUSMINUS */, bool rot1_ /* = false */,
+                 uint16_t maxTime_ /* = GESTURE_MAXTIME */
+) {
+  fromZone = fromZone_;
+  toZone = toZone_;
+  strncpy(_name, name_, 15);
+  minDistance = minDistance_;
+  direction = direction_;
+  plusminus = plusminus_;
+  rot1 = rot1_;
+  maxTime = maxTime_;
+  _detected = false;
+  instances.push_back(this);
+}
+
+Gesture::Gesture(const char* name_ /* = "" */,
+                 uint16_t minDistance_ /* = GESTURE_MINDIST */,
+                 int16_t direction_ /* = INVALID_VALUE */,
+                 uint8_t plusminus_ /* = PLUSMINUS */, bool rot1_ /* = false */,
+                 uint16_t maxTime_ /* = GESTURE_MAXTIME */
 ) {
-	fromZone = &fromZone_;
-	toZone = &toZone_;
-	strncpy(name, name_, 15);
-	maxTime = maxTime_;
-	minDistance = minDistance_;
-	detected = false;
-	instances.push_back(this);
+  fromZone = ANYWHERE;
+  toZone = ANYWHERE;
+  strncpy(_name, name_, 15);
+  minDistance = minDistance_;
+  direction = direction_;
+  plusminus = plusminus_;
+  rot1 = rot1_;
+  maxTime = maxTime_;
+  _detected = false;
+  instances.push_back(this);
 }
 
 Gesture::~Gesture() {
-	for(int i = 0; i < instances.size(); ++i) {
-		if (instances[i] == this) {
-			instances.erase(instances.begin() + i);
-			EVENTS->delHandlers(nullptr, nullptr, this);
-			return;
-		}
-	}
+  for (int i = 0; i < instances.size(); ++i) {
+    if (instances[i] == this) {
+      instances.erase(instances.begin() + i);
+      BUTTONS->delHandlers(nullptr, nullptr, this);
+      return;
+    }
+  }
 }
 
+Gesture::operator bool() { return _detected; }
+
 int16_t Gesture::instanceIndex() {
-	for(int16_t i = 0; i < instances.size(); ++i) {
-		if (instances[i] == this) return i;
-	}
-	return -1;
-}	
+  for (int16_t i = 0; i < instances.size(); ++i) {
+    if (instances[i] == this) return i;
+  }
+  return -1;
+}
 
-bool Gesture::test(Event& e) {
-	if (e.duration > maxTime) return false;
-	if (e.from.distanceTo(e.to) < minDistance) return false;
-	if (!fromZone->contains(e.from) || !toZone->contains(e.to)) return false;
-	detected = true;
-	return true;
+char* Gesture::name() { return _name; }
+
+bool Gesture::test(Point& from, Point& to, uint16_t duration) {
+  if (from.distanceTo(to) < minDistance) return false;
+  if (fromZone && !fromZone.contains(from)) return false;
+  if (toZone && !toZone.contains(to)) return false;
+  if (direction != INVALID_VALUE &&
+      !from.isDirectionTo(to, direction, plusminus, rot1))
+    return false;
+  if (duration > maxTime) return false;
+  _detected = true;
+  return true;
 }
 
-bool Gesture::wasDetected() { return detected; }
+bool Gesture::wasDetected() { return _detected; }
 
 void Gesture::addHandler(void (*fn)(Event&), uint16_t eventMask /* = E_ALL */) {
-	EVENTS->addHandler(fn, eventMask, nullptr, this);
+  BUTTONS->addHandler(fn, eventMask, nullptr, this);
 }
 
 void Gesture::delHandlers(void (*fn)(Event&) /* = nullptr */) {
-	EVENTS->delHandlers(fn, nullptr, this);
+  BUTTONS->delHandlers(fn, nullptr, this);
 }
 
-
-
 // Event class
 
 Event::Event() {
-	finger = type = duration = 0;
-	button = nullptr;
-	gesture = nullptr;
+  finger = type = duration = 0;
+  from = to = Point();
+  button = nullptr;
+  gesture = nullptr;
 }
 
-Event::operator bool() { return (type); }
+Event::operator uint16_t() { return type; }
 
 const char* Event::typeName() {
-	const char *unknown = "E_UNKNOWN";
-	const char *none = "E_NONE";
-	const char *eventNames[NUM_EVENTS] = {
-		"E_TOUCH", 
-		"E_RELEASE",
-		"E_MOVE",
-		"E_GESTURE",
-		"E_TAP",
-		"E_DBLTAP",
-		"E_DRAGGED",
-		"E_PRESSED",
-		"E_PRESSING",
-		"E_LONGPRESSED",
-		"E_LONGPRESSING"
-	};
-	if (!type) return none;
-	for (uint8_t i = 0; i < NUM_EVENTS; i++) {
-		if ((type >> i) & 1) return eventNames[i];
-	}
-	return unknown;
+  const char* unknown = "E_UNKNOWN";
+  const char* none = "E_NONE";
+  const char* eventNames[NUM_EVENTS] = {
+      "E_TOUCH",    "E_RELEASE",     "E_MOVE",        "E_GESTURE",
+      "E_TAP",      "E_DBLTAP",      "E_DRAGGED",     "E_PRESSED",
+      "E_PRESSING", "E_LONGPRESSED", "E_LONGPRESSING"};
+  if (!type) return none;
+  for (uint8_t i = 0; i < NUM_EVENTS; i++) {
+    if ((type >> i) & 1) return eventNames[i];
+  }
+  return unknown;
 }
 
 const char* Event::objName() {
-	const char *empty = "";
-	if (button) return button->name;
-	if (gesture) return gesture->name;
-	return empty;
+  const char* empty = "";
+  if (gesture) return gesture->name();
+  if (button) return button->name();
+  return empty;
 };
 
-
-
-// M5Events class
-
-M5Events* M5Events::instance;
-
-M5Events::M5Events() {
-	if (!instance) instance = this;
-}
-
-Event M5Events::fireEvent(uint8_t finger, uint16_t type, Point& from,
-  Point& to, uint16_t duration, Button* button, Gesture* gesture) {
-	Event e;
-	e.finger = finger;
-	e.type = type;
-	e.from = from;
-	e.to = to;
-	e.duration = duration;
-	e.button = button;
-	e.gesture = gesture;
-	if (button) {
-		button->event = e;
-		if (type == E_TOUCH || type == E_RELEASE) button->draw();
-	}
-	for ( auto h : _eventHandlers ) {
-		if (!(h.eventMask & e.type)) continue;
-		if (h.button && h.button != e.button) continue;
-		if (h.gesture && h.gesture != e.gesture) continue;
-		if (h.eventMask & E_BTNONLY && !e.button) continue;
-		h.fn(e);
-	}
-	return e;
-}
-
-void M5Events::addHandler(
-  void (*fn)(Event&),
-  uint16_t eventMask /* = E_ALL */,
-  Button* button /* = nullptr */,
-  Gesture* gesture /* = nullptr */
-) {
-	EventHandler handler;
-	handler.fn = fn;
-	handler.eventMask = eventMask;
-	handler.button = button;
-	handler.gesture = gesture;
-	_eventHandlers.push_back(handler);
-}
-
-void M5Events::delHandlers(
-  void (*fn)(Event&) /* = nullptr */,
-  Button* button /* = nullptr */,
-  Gesture* gesture /* = nullptr */
-) {
-	for(int i = _eventHandlers.size() - 1; i >= 0 ; --i) {
-		if (fn && fn != _eventHandlers[i].fn) continue;
-		if (button && _eventHandlers[i].button != button) continue;
-		if (gesture && _eventHandlers[i].gesture != gesture) continue;
-		_eventHandlers.erase(_eventHandlers.begin() + i);
-	}
+uint16_t Event::direction(bool rot1 /* = false */) {
+  return from.directionTo(to, rot1);
 }
 
+bool Event::isDirection(int16_t wanted, uint8_t plusminus /* = PLUSMINUS */,
+                        bool rot1 /* = false */) {
+  return from.isDirectionTo(to, wanted, plusminus, rot1);
+}
 
+uint16_t Event::distance() { return from.distanceTo(to); }
 
 // TFT_eSPI_Button compatibility mode
 
-TFT_eSPI_Button::TFT_eSPI_Button() : Button(0,0,0,0) {
-	compat = true;
-}
+TFT_eSPI_Button::TFT_eSPI_Button() : Button(0, 0, 0, 0) { _compat = true; }
 
-void TFT_eSPI_Button::initButton(
-  TFT_eSPI *gfx, 
-  int16_t x, int16_t y, uint16_t w, uint16_t h, 
-  uint16_t outline, uint16_t fill, uint16_t textcolor,
-  char *label,
-  uint8_t textsize
-) {
-	initButtonUL(gfx, x - (w / 2), y - (h / 2), w, h, outline, fill, 
-	  textcolor, label, textsize);
+void TFT_eSPI_Button::initButton(TFT_eSPI* gfx, int16_t x, int16_t y,
+                                 uint16_t w, uint16_t h, uint16_t outline,
+                                 uint16_t fill, uint16_t textcolor,
+                                 char* label_, uint8_t textsize) {
+  initButtonUL(gfx, x - (w / 2), y - (h / 2), w, h, outline, fill, textcolor,
+               label_, textsize);
 }
 
-void TFT_eSPI_Button::initButtonUL(
-  TFT_eSPI *gfx,
-  int16_t x_, int16_t y_, uint16_t w_, uint16_t h_, 
-  uint16_t outline, uint16_t fill, uint16_t textcolor, 
-  char *label_,
-  uint8_t textsize_
-) {
-	x = x_;
-	y = y_;
-	w = w_;
-	h = h_;
-	off = {fill, textcolor, outline};
-	on = {textcolor, fill, outline};
-	setTextSize(textsize_);
-	strncpy(label, label_, 9);
+void TFT_eSPI_Button::initButtonUL(TFT_eSPI* gfx, int16_t x_, int16_t y_,
+                                   uint16_t w_, uint16_t h_, uint16_t outline,
+                                   uint16_t fill, uint16_t textcolor,
+                                   char* label_, uint8_t textsize_) {
+  x = x_;
+  y = y_;
+  w = w_;
+  h = h_;
+  off = {fill, textcolor, outline};
+  on = {textcolor, fill, outline};
+  setTextSize(textsize_);
+  strncpy(_label, label_, 9);
 }
 
-void TFT_eSPI_Button::setLabelDatum(int16_t dx_, int16_t dy_, uint8_t datum_ /* = MC_DATUM */) {
-	dx = dx_;
-	dy = dy_;
-	datum = datum_;
+void TFT_eSPI_Button::setLabelDatum(int16_t dx_, int16_t dy_,
+                                    uint8_t datum_ /* = MC_DATUM */) {
+  dx = dx_;
+  dy = dy_;
+  datum = datum_;
 }
 
-void TFT_eSPI_Button::drawButton(bool inverted /* = false */, String long_name /* = "" */) {
-	char oldLabel[51];
-	if (long_name != "") {
-		strncpy(oldLabel, label, 50);
-		strncpy(label, long_name.c_str(), 50);
-	}
-	draw(inverted ? on : off);
-	if (long_name != "") strncpy(label, oldLabel, 50);
+void TFT_eSPI_Button::drawButton(bool inverted /* = false */,
+                                 String long_name /* = "" */) {
+  char oldLabel[51];
+  if (long_name != "") {
+    strncpy(oldLabel, _label, 50);
+    strncpy(_label, long_name.c_str(), 50);
+  }
+  draw(inverted ? on : off);
+  if (long_name != "") strncpy(_label, oldLabel, 50);
 }
 
-bool TFT_eSPI_Button::contains(int16_t x, int16_t y) { return Button::contains(x, y); }
+bool TFT_eSPI_Button::contains(int16_t x, int16_t y) {
+  return Button::contains(x, y);
+}
 
-void TFT_eSPI_Button::press(bool p) { setState(p); }
+void TFT_eSPI_Button::press(bool p) {
+  if (p)
+    fingerDown();
+  else
+    fingerUp();
+}
 
 bool TFT_eSPI_Button::justPressed() { return wasPressed(); }
 
-bool TFT_eSPI_Button::justReleased() { return wasReleased(); }
\ No newline at end of file
+bool TFT_eSPI_Button::justReleased() { return wasReleased(); }
diff --git a/src/utility/M5Button.h b/src/utility/M5Button.h
index b7f9860f..533c1204 100644
--- a/src/utility/M5Button.h
+++ b/src/utility/M5Button.h
@@ -1,250 +1,906 @@
+/*
+
+== M5Button: Buttons, Gestures and Events ==
+
+  * Hardware button support that is 100% Arduino Button Library compatible.
+
+  * Buttons on the screen, either as labels above the original M5Stack's
+    hardware buttons or anywhere on the touch screen of the Core2.
+
+  * Zone and Point objects to work with screen locations are areas. Functions
+    for distance, direction and more.
+
+  * Touch gestures that are processed before the buttons, so you can still
+    use gestures when the screen is full of buttons.
+
+  * Buttons and gestures send events that you can attach handler routines to,
+    or poll in a loop. Events include tap, doubletap, pressed, dragged and
+    more. Support for key repeat.
+
+  * Extensive rotation support, including support for buttons and gestures
+    that stay referenced to the physical screen regardless of rotation.
+
+  * Intuitive, consistent and well-documented API.
+
+  * Emulation of the (much less feature-rich) TFT_eSPI_Button class. This
+    goes together well with M5Touch's emulation of the TFT_eSPI resistive
+    touch screen interface to run a lot of existing programs without
+    modification.
+
+  This library was written for the M5Stack series of devices, but was made to
+  be general enough to be produce pretty visual buttons with any TFT_eSPI
+  display. Its more advanced features need the M5Touch interface, although
+  other input methods could be implemented.
+
+
+== Point and Zone: Describing Points and Areas on the Screen ==
+
+  The Point and Zone classes allow you to create variables that hold a point
+  or an area on the screen. You can
+
+  Point(x, y)
+
+    Holds a point on the screen. Has members x and y that hold the
+    coordinates of a touch. Values -1 for x and y indicate an invalid value,
+    and that's what a point starts out with if you declare it without
+    parameters. The 'valid()' method tests if a point is valid. If you
+    explicitly evaluate a Point as a boolean ("if (p) ..."), you also get
+    whether the point is valid, so this is equivalent to writing "if
+    (p.valid()) ...".
+
+  Zone(x, y, w, h)
+
+    Holds a rectangular area on the screen. Members x, y, w and h are for the
+    x and y coordinate of the top-left corner and the width and height of the
+    rectangle.
+
+  The 'set' method allows you to change the properties of an existing Point
+  or Zone. Using the 'in' or 'contains' method you can test if a point lies
+  in a zone.
+
+  The PointAndZone library also provides the low-level support for direction
+  from one point to another and for screen rotation translations.
+
+  The documentation in src/utility/PointAndZone.h provides more details about
+  rotation and examples covering most of the above.
+
+
+== Buttons ==
+
+  You can create classic Arduino buttons that act on the voltage on a pin of
+  the controller. On the M5Stack Core2, you can also create buttons that act
+  on touch within a given rectangle on the screen. If you want, that same
+  rectangle will also be used for a graphical representation of the button
+  that will show a text label in a colored background with a colored outline.
+  The colors of background, text, and outline can be configured, both for the
+  'off' and the 'on' state of the button.
+
+  Whether on the M5Stack with hardware buttons or on the Core2 with a touch
+  screen, buttons are special forms of the 'Zone' object, meaning that all
+  functions that apply to 'Zone' objects also work on buttons. On the M5Stack
+  with buttons, while this zone cannot be used for touch input, it can still
+  be used to display a button that responds to the button state.
+
+
+== Hardware Buttons ==
+
+  For hardware buttons, use the classic Arduino way of setting up the button,
+  by providing the pin, whether the pin is inverted and the debounce time.
+
+
+    #include <M5Stack.h>
+
+    Button myButton(39, true, 10);
+
+    void setup() {
+      M5.begin();
+    }
+
+    void loop() {
+      M5.update();
+      if (myButton.wasPressed()) Serial.print("* ");
+    }
+
+
+  This would set up 'myButton' with the inverse state of pin 39, and a
+  debounce time of 10 milliseconds. Because pin 39 is the left button on an
+  M5Stack with buttons, this sketch will output a star to the serial port
+  every time you release the button. (And because pin 39 is the interrupt
+  wire for the touch screen on the Core2, it also happens to output a star on
+  that device every time you touch the screen.)
+
+    Note that the sketch uses 'M5.update()' instead of 'myButton.read()'. You
+    don't need to read() your buttons explicitly anymore. All buttons created
+    with M5Button are automatically read by 'M5.update()'. (Unless you read
+    them with 'myButton.read()', in which case 'M5.update()' stops doing that
+    to avoid you missing things.)
+
+  The next sections will describe buttons and gestures on the touch screen,
+  but if you have an M5Stack device without a touch screen: keep reading
+  because many events work on hardware buttons too. Hardware buttons can have
+  responsive representation on the screen, we'll get to that also.
+
+
+== Buttons Using the Touch Screen ==
+
+    Note: It may make sense to also read the documentation in the M5Touch.h
+    file, as tells you about the touch sensor and the lower-level touch
+    interface that is underneath the M5Button library.
+
+  To have button that reacts to the touch sensor, all you need to do is
+  create a variable for the Button and providing the coordinates (x, y, width
+  and height). These buttons can be used in two ways. You can either use them
+  the way you would a normal Arduino button, or you can provide handler
+  functions to process various events for the button. We'll talk about the
+  events later, but here's the same simple sketch from above again but now it
+  defines a 100x50 pixel touch button near the top-right of the screen. Note
+  that this button does not show anything on the sreen just yet.
+
+
+    #include <M5Core2.h>
+
+    Button myButton(10, 10, 200, 100);
+
+    void setup() {
+      M5.begin();
+    }
+
+    void loop() {
+      M5.update();
+      if (myButton.wasPressed()) Serial.print("* ");
+    }
+
+
+  'wasPressed()' will only be true once when you press the button. You can
+  also use the other Arduino button functions such as 'isPressed()' that is
+  true as soon and as long as the button is touched. Note that the buttons
+  only become pressed if the touch starts within the button, not if you swipe
+  over it, and that they will stay pressed as long as the finger touches,
+  even if it leaves the button area. You may want read about the events
+  further down to distinguish between different kinds of button-presses.
+
+  On the Core2 the three buttons M5.BtnA, M5.BtnB and M5.BtnC from the older
+  M5Stack units come already implemented as touch buttons that lie just below
+  the screen where the three circles are.
+
+
+== Buttons with visual appearance ==
+
+  If you want you button to show on the screen, all you need to do is provide
+  a set of three colors for the background of the button, the text printed on
+  it and the outline of the button. Using yet the same skech again:
+
+
+    #include <M5Core2.h>
+
+    Button myButton(10, 10, 200, 100, false, "I'm a button !",
+                    {BLACK, WHITE, WHITE});
+
+    void setup() {
+      M5.begin();
+    }
+
+    void loop() {
+      M5.update();
+      if (myButton.wasPressed()) Serial.print("* ");
+    }
+
+
+  As you can see the colors are provided in {curly braces}, that's because
+  they are one variable, of the 'ButtonColors' type. Especialy if you're
+  going to define a bunch of buttons, you're better off replacing the button
+  line by:
+
+
+    ButtonColors col = {BLACK, WHITE, WHITE};
+    Button myButton(10, 10, 200, 100, false, "I'm a button !", col);
+
+
+  The order there is background, text, outline. If you do not want any of
+  these components drawn, simply put NODRAW in that position. The thing we are
+  defining here is what the button draws in its 'off' state. Since we haven
+  specified anything to draw in the 'on' state, the button just stays like it
+  is, regardless of whether it's pressed. Thus, if we say
+
+
+    ButtonColors onCol = {BLACK, WHITE, WHITE};
+    ButtonColors offCol = {RED, WHITE, WHITE};
+    Button myButton(10, 10, 200, 100, false, "I'm a button !", onCol, offCol);
+
+
+  the button background would turn red if the button was pressed. The button
+  colors can also be addressed directly. "myButton.on.bg = BLUE;" will turn
+  the background blue in the on state. The other two properties of the
+  ButtonColors variable are predicatably called 'text' and 'outline'.
+
+  If you run the sketches you will see the text is placed in the center of
+  the button and the buttons have visually please round edges. The corner
+  radius defaults to a quarter of the shortest side of the button. You can
+  change all this with the remaining parameters when setting up the button:
+
+
+    Button myButton(10, 10, 200, 100, false, "I'm a button !", onCol, offCol,
+                    TL_DATUM, 0, 10, 0);
+
+
+  These last parameters indicate where to put the label in the TFT_eSPI
+  standard datum values, top-left in this case. The values after that are the
+  dx and dy, meaning the offsets from the default position. In this case
+  that's no left-right offset and 10 pixels down. Negative values move the
+  other way. The last value is the corner radius. In this case it would draw
+  an ugly 1980's rectangular button.
+
+  You can make a button draw its current state with "myButton.draw()", or all
+  buttons do that with "M5.Buttons.draw()". You can also call draw with a
+  ButtonColors variable so "myButton.draw({BLUE, WHITE, WHITE})" draws it
+  with those colors. Until the next state-change comes along that is, if you
+  have colors for the new state defined.
+
+  Note that the text provided here is the name of the buttton. A button
+  always keeps the same name, but the label (that which is shown) can change,
+  but initialises to the name. Use 'myButton.setLabel("New text!")' to change
+  it.
+
+
+== Visual Buttons (Labels) with Hardware Buttons ==
+
+  You can have a visual representation of teh state of a hardware button on
+  the screen, for example right above the hardware butttons of the original
+  M5Stack. We'll call these buttons "labels", but they're regular buttons
+  that just respond to a physical button insetad of the touch sensor. If you
+  want to display a label on the screen that responds to the state of a
+  hardware button, just set up a hardware button up as usual, but then follow
+  the parameter list with "hw" (in quotes), followed by the parameters of the
+  touch button below.
+
+  The hardware buttons in the older M5Stack devices are already set up to
+  display labels: all you need is supply colors. Their initialisation (in
+  M5Stack.h in this library) looks like this:
+
+
+    Button BtnA = Button(BUTTON_A_PIN, true, DEBOUNCE_MS,
+                         "hw", 3, 218, 102, 21, true, "BtnA");
+    Button BtnB = Button(BUTTON_B_PIN, true, DEBOUNCE_MS,
+                         "hw", 109, 218, 102, 21, true, "BtnB");
+    Button BtnC = Button(BUTTON_C_PIN, true, DEBOUNCE_MS,
+                         "hw", 215, 218, 102, 21, true, "BtnC");
+
+
+  As you can see: its just a hardware button that has a zone to display the
+  label. So the sketch below is all that is needed to show repsonsive labels
+  on the M5Stack:
+
+
+    #include <M5Stack.h>
+
+    void setup() {
+      M5.BtnA.off = M5.BtnB.off = M5.BtnC.off = {BLUE, WHITE, NODRAW};
+      M5.BtnA.on  = M5.BtnB.on  = M5.BtnC.on  = {RED,  WHITE, NODRAW};
+      M5.begin();
+      M5.Buttons.draw();
+    }
+
+    void loop() {
+      M5.update();
+    }
+
+
+  If you looked closely you might have noticed that the mysterious fifth
+  argument has changed from 'false' to 'true'. This argument is called
+  'rot1', and it determines that the location of this Zone or Button is
+  specified in rotation one, i.e. the normal default screen rotation. What
+  that means is that no matter what rotation you set the dsplay to, these
+  button will always stay in the same place. The documentation in
+  src/utility/PointAndZone.h has more details if you want to know more about
+  this. You will only ever need rot1 if you need multiple screen rotations
+  AND you want objects to stay in the same physical place regardless.
+
+
+== Events ==
+
+  Buttons (and gestures, but we'll get to those later) have a set of simple
+  functions to see if they are pressed or not. These Arduino-compatible
+  functions work fine for that purpose. But if you want to detect whether a
+  button received a short tap, or even a double-tap on many buttons
+  simultaneously, you find yourself writing quite a bit of code for that.
+
+  Events are M5Button's way of making it easier to react to events on
+  hardware buttons or the touch screen. For this you need to define one or
+  more event handler functions. This is done like this:
+
+    void myHandler(Event& e) { ... }
+
+  It's important to do it exactly this way, only changing the name of the
+  function. You can then set things up so that this function receives events.
+
+  Here's an events-based sketch for the Core2. We'll base it on the same
+  buton we've seen before.
+
+
+    #include <M5Core2.h>
+
+    ButtonColors onCol = {BLACK, WHITE, WHITE};
+    ButtonColors offCol = {RED, WHITE, WHITE};
+    Button myButton(10, 10, 200, 100, false, "I'm a button !", onCol, offCol);
+
+    void setup() {
+      M5.begin();
+      myButton.addHandler(touched, E_TOUCH);
+      myButton.addHandler(released, E_RELEASE);
+    }
+
+    void loop() {
+      M5.update();
+    }
+
+    void touched(Event& e) {
+      Serial.println("Touched!");
+    }
+
+    void released(Event& e) {
+      Serial.println("Released!");
+    }
+
+
+  Note that the function name is provided without the brackets. Here's two
+  ways you can set up a handler function to receive events:
+
+    M5.Buttons.addHandler(myHandler);
+
+    - or -
+
+    myButton.addHandler(myHandler);
+
+  The first form receives all the events relating to all buttons and
+  gestures, the second form only receives the events for that specific
+  button. After the name of the function, without the brackets, you can
+  pecify which events the function needs to receive. You can add together (or
+  "bitwise or") the names of the events if you want a handler function to
+  reive multiple events.
+
+  The Event object that is passed to the handler function contains all sorts
+  of information about the event: where on the screen it started, where it
+  ended, the duration, etc. etc.
+
+  Let's first look at all the possible events and when are fired. The first
+  three events always happen when a finger touches the display.
+
+  E_TOUCH, E_MOVE and E_RELEASE
+
+    The E_TOUCH and E_RELEASE events fire when a button is pressed and
+    released. On a touch sensor, E_MOVE will fire every time it detects the
+    finger has moved. These events cannot be prevented from firing, like most
+    of the other ones. So every time your finger touches the display it will
+    fire E_TOUCH and then E_MOVEs until finally, when you release your
+    finger, an E_RELEASE.
+
+  E_PRESSING and E_LONGPRESSING
+
+    There are also events that happen while the button is still pressed.
+    These are E_PRESSING and E_LONGPRESSING. E_PRESSING happens as soon as
+    M5Button is sure that's not just a short tap (more later). The maximum
+    time for a tap is settable, but defaults to 150 ms. So if teh button is
+    still held 150 ms after E_TOUCH, E_PRESSING fires. Just once, unless you
+    have set up a key repeat, more about that later too. Then at some point
+    you might get a E_LONGPRESSING, if you have set up a duration for that to
+    happen.
+
+  E_TAP, E_DBLTAP, E_PRESSED, E_LONGPRESSED and E_DRAGGED
+
+    Unless the keypress is cancelled (more later), exactly one of these
+    events will fire after the button has been released, after E_RELEASE has
+    fired. Think of these as final decisions on what kind of keypress this
+    was. (E_TAP takes a tiny bit longer before it fires because M5Button
+    needs to make it wasn't a doubletap, in that case E-DBLTAP wil fire
+    instead.)
+
+    So tap and doubletap are sort of obvious, E_LONGPRESSED fires if the key
+    was pressed more that the set time in ms. E_DRAGGED fires if the finger
+    has moved outside of the button area when it was released. E_PRESSED is
+    fires in all other cases.
+
+  E_GESTURE
+
+    Doesn't really fit in with the others, but is the event that gets fired
+    when a gesture is detected.
+
+
+  If at any point after the pressing of a button, "myButton.cancel()" is
+  called, no further high-level events for that button will fire. What that
+  means is nothing other than possible E_MOVEs and one E_RELEASE event will
+  fire for that button until it is released and then pressed again. This is
+  used internally when a gesture is detected, so that when a touch gesture
+  starts on a button, there won't be an E_PRESSED, or any of the others.
+
+  The second thing to look at more closely is the 'Event' object itself. When
+  you set up a handler function like this
+
+    void myhandler(Event& e) {
+
+  what that means is you're creating a function that recives a (reference to)
+  an event. That event has all sorts of properties that we can look at.
+
+
+    e.type
+
+      The type of event, such as E_TOUCH or E_TAP from above. The event
+      itself, when you evaluate it, also returns the type. What that means is
+      that "if (e.type == E_TAP) .." is equivalent with "if (e == E_TAP) .."
+
+    e.finger
+
+      0 or 1, whether this is the first or second finger detected on the
+      touch screen. Left at zero on the M5Stack with buttons.
+
+    e.from and e.to
+
+      Points that say from where to where this event happened. Left at
+      invalid for the M5Stack with buttons.
+
+    e.duration
+
+      Duration of the event in milliseconds.
+
+    e.button and e.gesture
+
+      Pointers to the button and possibly gesture attached to the event. What
+      that means is that you can use all the methods for button as long as
+      you precede them with "e.button->". Note the '->' there because this is
+      a pointer to an object.
+
+    other methods
+
+      Additionally, you can ask for the name of the event as text by using
+      "e.typeName()" and get the name of the gesture or button with
+      "e.objName()". "e.direction()" gives the direction, for instance of a
+      gesture or of an E_RELEASE event, where it gives direction between
+      E_TOUCH and E_RELEASE. "e.isDirectionTo(0,30)" will output true if the
+      swipe was upwards, plus or minus 30 degrees.
+
+
+  When you add a handler function you can also specify what events it should
+  receive by supplying it as the second argument after the handler function.
+  If you want to register multiple events for the same function, don't
+  register the handler twice, but simply add (or bitwise or) the event
+  values. The default value there is the pseudo-event E_ALL, which is simply
+  a value with all the event bits turned on. You can also subtract event type
+  values from E_ALL to exclude them.
+
+  Here are some examples of ways to add a handler function:
+
+
+    button1.addHandler(b1Function, E_TOUCH + E_RELEASE);
+
+      b1Function only get these two events for button1.
+
+
+    M5.Buttons.addHandler(btnHandle, E_ALL - E_MOVE);
+
+      btnHandle gets all events, except E_MOVE.
+
+
+    swipeUp.addHandler(nextPage);
+
+      Handler nextPage is called when swipeUp gesture detected.
+
+
+  Note that all handler functions must be of the "void someName(Event& e)"
+  type, even if they plan to completely ignore the event that is passed to
+  them.
+
+
+  If your event reads date or calls functions in e.button or e.gesture,
+  remember that these are pointers. Without going into too much detail, it
+  means it must do so with the -> notation, so to read the button x position,
+  you would say "e.button->x".
+
+  Please have a look at the example sketch (see below) to understand how this
+  all works and run the sketch to see all the events printed to the serial
+  port.
+
+
+== In Loop vs. Event Handlers ==
+
+  Button and Gesture objects have an 'event' method that returns the event
+  that was detected this time around by 'M5.update()'. Each event comes in
+  it's own rotation of 'M5.update()', so if you prefer to detect events this
+  way and not with handler routines that's fine too.
+
+  If nothing was detected, the event type will be set to E_NONE with a value
+  of 0, so you can do "if (myButton.event) ...". 'M5.Buttons.event' has the
+  event detected this time around, regardless of what button or gesture it
+  was attached to.
+
+
+== M5.background ==
+
+  Only one button can become pressed for any spot om the touch screen. If you
+  define overlapping buttons, the first defined button forthe overlap become
+  pressed and gets all subsequent events.
+
+  One special button, "M5.background", was defined before any others, and it
+  has the size of the entire touch sensor. This means it gets all events
+  where the first touch was not within any of the defined buttons.
+
+
+== Gestures on the Touch Screen ==
+
+  Whenever a finger is released from the touch screen and before any
+  higher-level button events are fired, the library first checs whether this
+  was aybe a gesture. When you can degine gestures, you can optionally
+  specify the zone in which the gesture must start, the zone in which it must
+  end, the minimum distance the finger must have travelled, the direction it
+  has travelled in and the maximum time the gesture may take.
+
+    Gesture exampleGesture(fromZone, toZone, "exampleName", minimumDistance,
+    direction, plusminus, ro1, maxTime)
+
+  Where fromZone and toZone can be valid zones or the word "ANYWHERE". If you
+  want to specify neither fromZone nor toZone, you can also leave them off
+  completely. The minimum distance defaults to 75 pixels. The direction
+  (default: don't care) is in compass degrees (so 180 is down), but the
+  compiler defines UP, DOWN, LEFT and right are provided for convenience. The
+  plusminus deines how many degress off-course the gesture may be, and the
+  rot1 flag defines whether this direction is relative to the current
+  rotation, or as seen in rotation 1. maxTime is in milliseconds as usual and
+  defaults to 500 ms.
+
+  here are a few examples of valid gesture definitions:
+
+
+    Gesture swipeDown("swipe down", 100, DOWN, 30);
+
+      Down (plus or minus 30 degrees) for at least 100 pixels within 500 ms
+
+
+    Gesture fromTop(Zone(0, 0, 360, 30), ANYWHERE, "from top", 100, DOWN, 30);
+
+      The same but starting from within the top 30 pixels. (If you make that
+      too narrow you may miss the swipe because the sensor 'sees' only once
+      every 13 ms or so.
+
+
+  (Note that if you defines both these gestures in this orderm the second one
+  would never fire because any swipe that matched number two woul first match
+  number one and fire that one instead.)
+
+  Gestures have a 'wasDetected()' method if you want to detect them in the
+  main loop, or you attach a handler usin the same way you use for buttons,
+  with "myEvent.addhandler(myHandler)"
+
+
+    #include <M5Core2.h>
+
+    Gesture swipeDown("swipe down", DOWN, 30);
+
+    void setup() {
+      M5.begin();
+    }
+
+    void loop() {
+      M5.update();
+      if (swipeDown.wasDetected()) Serial.println("Swiped down!");
+    }
+
+
+== Advanced Hints and Tricks
+
+  ## drawFn
+
+    If you look at the source code for the library you will see that the
+    drawing of the button is done by a static function in the M5Buttons
+    object. It's defined as
+
+      void M5Buttons::drawFunction(Button& b, ButtonColors bc)
+
+    If you make your own function that takes the same arguments but that does
+    something different, you can make the library us it by saying
+    "M5.Buttons.drawFn = myFunction". You can even do that on a per-button
+    basis with "myButton.drawFn = myFunction".
+
+
+  ## drawZone
+
+    A Button instance _is_ also a Zone object, in that it descends from it.
+    Which means a Button has all the methods of a Zone object, as well as its
+    own. But it contains another zone, called drawZone. This allows you to
+    have the visual representation happen somewhere else than where the
+    button is on the touch sensor. Normally this is set to "invalid zone",
+    but if you set it to a valid screen area, the button will be drawn there.
+    This is used internally to put the optional labels for the off-screen
+    buttons on the Core2 on the screen just above their touch areas.
+
+
+  ## Drawing is Non-Invasive
+
+    This library uses a brand-new feature of the M5Display object --
+    M5.Lcd.popState() and M5.lcd.pushState() -- that allows it to save and
+    reload the complete display driver state before and after drawing a
+    button. What that means is that you can draw to the display without
+    worrying that the button drawing will mess with your font setting, cursor
+    position or anything else that is display-related.
+
+
+  ## TFT_ePI_Button Emulation
+
+    This libary also defines an object called TFT_eSPI_Button, which is the
+    old way of doing buttons that comes as an optional extra with the display
+    library. Together with M5Touch's emulation of the TFT_eSPI touch
+    interface (written for the older resistive touch-screens), you can use it
+    to run software made for those APIs. Do not use either fornew code: the
+    native interfaces are much more powerful.
+
+
+  ## Buttons and Variable Scope
+
+    Buttons come into existence and are drawn in their initial state when
+    their variables are defined and are not detected anymore when their
+    variables are removed from memory when the function they were defined in
+    returns. Except for global buttons - defined outside any functions: their
+    variables always exist. The programmer has to take responsability for
+    erasing expired buttons off the screen because Button doesnt know what is
+    supposed to be in the background. If you're not clearing the entire
+    screen anyway, this can be done with "myButton.erase(BLACK)" if the
+    background is to be black.
+
+*/
+
 #ifndef _M5BUTTON_H_
 #define _M5BUTTON_H_
 
 class Gesture;
 
 #include <Arduino.h>
-#include "PointAndZone.h"
-#include <M5Display.h>
 #include <Free_Fonts.h>
+#include <M5Display.h>
+
+#include "PointAndZone.h"
+#include "utility/Config.h"
+
+#ifdef M5Stack_M5Core2
+#include <M5Touch.h>
+#endif /* M5Stack_M5Core2 */
+
+#define BUTTON_FREEFONT FSS9
+#define BUTTON_TEXTFONT 1
+#define BUTTON_TEXTSIZE 1
+#define BUTTON_DATUM MC_DATUM
+
+#define TAP_TIME 150
+#define DBLTAP_TIME 300
+#define LONGPRESS_TIME 0
+#define REPEAT_DELAY 0
+#define REPEAT_INTERVAL 200
+
+#define GESTURE_MAXTIME 500
+#define GESTURE_MINDIST 75
+#define ANYWHERE Zone()
 
-#define TFT		M5Display::instance
-#define EVENTS	M5Events::instance
-#define BUTTONS	M5Buttons::instance
-
-#define TAP_TIME			150
-#define DBLTAP_TIME			200
-#define LONGPRESS_TIME		0
-#define GESTURE_MAXTIME		500
-#define GESTURE_MINDIST		75
-
-#define NUM_EVENTS			11
-#define E_TOUCH				0x0001
-#define E_RELEASE			0x0002
-#define E_MOVE  			0x0004
-#define E_GESTURE			0x0008
-#define E_TAP				0x0010
-#define E_DBLTAP			0x0020
-#define E_DRAGGED			0x0040
-#define E_PRESSED			0x0080
-#define E_PRESSING			0x0100
-#define E_LONGPRESSED		0x0200
-#define E_LONGPRESSING		0x0400
-
-#define E_ALL				0x0FFF
-#define E_BTNONLY			0x1000
-
-#define NODRAW				0x0120	// Special color value: transparent
-
-#define BUTTON_FREEFONT		FSS9
-#define BUTTON_TEXTFONT		1
-#define BUTTON_TEXTSIZE		1
-#define BUTTON_DATUM		MC_DATUM
+#define NUM_EVENTS 11
+#define E_TOUCH 0x0001
+#define E_RELEASE 0x0002
+#define E_MOVE 0x0004
+#define E_GESTURE 0x0008
+#define E_TAP 0x0010
+#define E_DBLTAP 0x0020
+#define E_DRAGGED 0x0040
+#define E_PRESSED 0x0080
+#define E_PRESSING 0x0100
+#define E_LONGPRESSED 0x0200
+#define E_LONGPRESSING 0x0400
+
+#define E_ALL 0x0FFF
+
+#define NODRAW 0x0120  // Special color value: transparent
 
 struct ButtonColors {
-	uint16_t bg;
-	uint16_t text;
-	uint16_t outline;
+  uint16_t bg;
+  uint16_t text;
+  uint16_t outline;
 };
 
 class Button;
-	
+class Event;
+
+#ifdef _M5TOUCH_H_
+struct Finger {
+  Point current, previous, startPoint, tapPoint;
+  uint32_t startTime, tapTime;
+  Button* button;
+};
+#endif
+
 class Event {
-  public:
-  	Event();
-  	operator bool();
-	const char* typeName();
-	const char* objName();
-	uint8_t finger;
-	uint16_t type;
-	Point from, to;
-	uint16_t duration;
-	Button* button;
-	Gesture* gesture;
+ public:
+  Event();
+  operator uint16_t();
+  const char* typeName();
+  const char* objName();
+  uint16_t direction(bool rot1 = false);
+  bool isDirection(int16_t wanted, uint8_t plusminus = PLUSMINUS,
+                   bool rot1 = false);
+  uint16_t distance();
+  uint8_t finger;
+  uint16_t type;
+  Point from, to;
+  uint16_t duration;
+  Button* button;
+  Gesture* gesture;
 };
 
 class Button : public Zone {
-  public:
-    static std::vector<Button*> instances;
-	Button(
-	  int16_t x_, int16_t y_, int16_t w_, int16_t h_,
-	  bool rot1_ = false,
-	  const char* name_ = "",
-	  ButtonColors off_ = {NODRAW, NODRAW, NODRAW},
-	  ButtonColors on_ = {NODRAW, NODRAW, NODRAW},
-	  uint8_t datum_ = BUTTON_DATUM,
-	  int16_t dx_ = 0,
-	  int16_t dy_ = 0,
-	  uint8_t r_ = 0xFF
-	);
-	Button(
-	  uint8_t pin, uint8_t invert, uint32_t dbTime, String hw = "hw",
-	  int16_t x_ = 0, int16_t y_ = 0, int16_t w_ = 0, int16_t h_ = 0,
-	  bool rot1_ = false,
-	  const char* name_ = "",
-	  ButtonColors off_ = {NODRAW, NODRAW, NODRAW},
-	  ButtonColors on_ = {NODRAW, NODRAW, NODRAW},
-	  uint8_t datum_ = BUTTON_DATUM,
-	  int16_t dx_ = 0,
-	  int16_t dy_ = 0,
-	  uint8_t r_ = 0xFF
-	);
-	~Button();
-	operator bool();
-	int16_t instanceIndex();
-	bool read(bool manualRead = true);
-	void setState(bool);
-	void freeState();
-	void cancel();
-	bool isPressed();
-	bool isReleased();
-	bool wasPressed();
-	bool wasReleased();
-	bool pressedFor(uint32_t ms);
-	bool pressedFor(uint32_t ms, uint32_t continuous_time);
-	bool releasedFor(uint32_t ms);
-	bool wasReleasefor(uint32_t ms);
-	void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL);
-	void delHandlers(void (*fn)(Event&) = nullptr);
-	uint32_t lastChange();
-	uint8_t finger;
-	bool changed;
-	char name[16];
-	Event event;
-	uint8_t pin;
-	uint16_t dbTime;
-	bool invert;
-	uint16_t tapTime, dbltapTime, longPressTime;
-  private:
-  	friend class M5Buttons;
-  	void init();
-	bool _state, _tapWait, _pressing, _longPressing, _cancelled, _manuallyRead;
-	uint8_t _setState;
-	uint32_t _time;
-	uint32_t _lastChange, _lastLongPress, _pressTime, _hold_time;
-	
+ public:
+  static std::vector<Button*> instances;
+  Button(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ = false,
+         const char* name_ = "", ButtonColors off_ = {NODRAW, NODRAW, NODRAW},
+         ButtonColors on_ = {NODRAW, NODRAW, NODRAW},
+         uint8_t datum_ = BUTTON_DATUM, int16_t dx_ = 0, int16_t dy_ = 0,
+         uint8_t r_ = 0xFF);
+  Button(uint8_t pin_, uint8_t invert_, uint32_t dbTime_, String hw = "hw",
+         int16_t x_ = 0, int16_t y_ = 0, int16_t w_ = 0, int16_t h_ = 0,
+         bool rot1_ = false, const char* name_ = "",
+         ButtonColors off_ = {NODRAW, NODRAW, NODRAW},
+         ButtonColors on_ = {NODRAW, NODRAW, NODRAW},
+         uint8_t datum_ = BUTTON_DATUM, int16_t dx_ = 0, int16_t dy_ = 0,
+         uint8_t r_ = 0xFF);
+  ~Button();
+  operator bool();
+  bool operator==(const Button& b);
+  bool operator!=(const Button& b);
+  bool operator==(Button* b);
+  bool operator!=(Button* b);
+  int16_t instanceIndex();
+  bool read(bool manualRead = true);
+  void fingerDown(Point pos = Point(), uint8_t finger = 0);
+  void fingerUp(uint8_t finger = 0);
+  void fingerMove(Point pos, uint8_t finger);
+  void cancel();
+  bool isPressed();
+  bool isReleased();
+  bool wasPressed();
+  bool wasReleased();
+  bool pressedFor(uint32_t ms);
+  bool pressedFor(uint32_t ms, uint32_t continuous_time);
+  bool releasedFor(uint32_t ms);
+  bool wasReleasefor(uint32_t ms);
+  void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL);
+  void delHandlers(void (*fn)(Event&) = nullptr);
+  char* name();
+  uint32_t lastChange();
+  Event event;
+  uint16_t tapTime, dbltapTime, longPressTime;
+  uint16_t repeatDelay, repeatInterval;
+
+ protected:
+  void init();
+  bool postReleaseEvents();
+  bool timeoutEvents();
+  friend class M5Buttons;
+  char _name[16];
+  uint8_t _pin;
+  uint16_t _dbTime;
+  bool _invert;
+  bool _changed, _state, _tapWait, _pressing;
+  bool _longPressing, _cancelled, _manuallyRead;
+  uint8_t _setState;
+  uint32_t _time, _lastRepeat;
+  uint32_t _lastChange, _lastLongPress, _pressTime, _hold_time;
+  uint8_t _finger;
+  Point _fromPt[2], _toPt[2], _currentPt[2];
+
   // visual stuff
-  public:
-	void draw(ButtonColors bc);
-	void draw();
-	void setLabel(const char* label_);
-	void setFont(const GFXfont* freeFont_);
-	void setFont(uint8_t textFont_ = 0);
-	void setTextSize(uint8_t textSize_ = 0);
-	ButtonColors off, on;
-	Zone* drawZone;
-	uint8_t datum, r;
-	int16_t dx, dy;
-	void (*drawFn)(Button* b, ButtonColors bc);
-	char label[51];
-	bool compat; // For TFT_eSPI_Button emulation		
-  private:
-	uint8_t _textFont;
-	const GFXfont* _freeFont;
-	uint8_t _textSize;
-};
+ public:
+  void draw(ButtonColors bc);
+  void draw();
+  void erase(uint16_t color = BLACK);
+  void setLabel(const char* label_);
+  void setFont(const GFXfont* freeFont_);
+  void setFont(uint8_t textFont_ = 0);
+  void setTextSize(uint8_t textSize_ = 0);
+  char* label();
+  ButtonColors off, on;
+  Zone drawZone;
+  uint8_t datum, r;
+  int16_t dx, dy;
+  void (*drawFn)(Button& b, ButtonColors bc);
 
-class M5Buttons {
-  public:
-    static M5Buttons* instance;
-    static void drawFunction(Button* button, ButtonColors bc);
-	M5Buttons();
-    void setUnchanged();
-	Button* which(Point& p);
-	void draw();
-	void update();
-	void setFont(const GFXfont* freeFont_);
-	void setFont(uint8_t textFont_);
-	void setTextSize(uint8_t textSize_);
-	void (*drawFn)(Button* button, ButtonColors bc);
-  private:
-	uint8_t _textFont;
-	const GFXfont* _freeFont;
-	uint8_t _textSize;
+ protected:
+  bool _compat;  // For TFT_eSPI_Button emulation
+  char _label[51];
+  uint8_t _textFont;
+  const GFXfont* _freeFont;
+  uint8_t _textSize;
 };
 
 class Gesture {
-  public:
-	static std::vector<Gesture*> instances;
-  	Gesture(
-  	  Zone& fromZone_,
-  	  Zone& toZone_,
-  	  const char* name_ = "",
-  	  uint16_t maxTime_ = GESTURE_MAXTIME,
-  	  uint16_t minDistance_ = GESTURE_MINDIST
-  	);
-  	~Gesture();
-  	int16_t instanceIndex();
-  	bool test(Event& e);
-  	bool wasDetected();
-  	void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL);
-  	void delHandlers(void (*fn)(Event&) = nullptr);
-	Zone* fromZone;
-	Zone* toZone;
-  	uint16_t maxTime, minDistance;
-  	char name[16];
-	bool detected;
+ public:
+  static std::vector<Gesture*> instances;
+  Gesture(Zone fromZone_, Zone toZone_, const char* name_ = "",
+          uint16_t minDistance_ = GESTURE_MINDIST,
+          int16_t direction_ = INVALID_VALUE, uint8_t plusminus_ = PLUSMINUS,
+          bool rot1_ = false, uint16_t maxTime_ = GESTURE_MAXTIME);
+  Gesture(const char* name_ = "", uint16_t minDistance_ = GESTURE_MINDIST,
+          int16_t direction_ = INVALID_VALUE, uint8_t plusminus_ = PLUSMINUS,
+          bool rot1_ = false, uint16_t maxTime_ = GESTURE_MAXTIME);
+  ~Gesture();
+  operator bool();
+  int16_t instanceIndex();
+  bool test(Point& from, Point& to, uint16_t duration);
+  bool wasDetected();
+  void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL);
+  void delHandlers(void (*fn)(Event&) = nullptr);
+  char* name();
+  Zone fromZone;
+  Zone toZone;
+  Event event;
+  int16_t direction;
+  uint8_t plusminus;
+  bool rot1;
+  uint16_t maxTime, minDistance;
+
+ protected:
+  friend class M5Buttons;
+  bool _detected;
+  char _name[16];
 };
 
 struct EventHandler {
-	uint16_t eventMask;
-	Button* button;
-	Gesture* gesture;
-	void (*fn)(Event&);
+  uint16_t eventMask;
+  Button* button;
+  Gesture* gesture;
+  void (*fn)(Event&);
 };
 
-class M5Events {
-  public:
-    M5Events();
-    static M5Events* instance;
-    Event fireEvent(
-      uint8_t finger,
-      uint16_t type,
-      Point& from,
-      Point& to,
-      uint16_t duration,
-      Button* button,
-      Gesture* gesture
-    );
-    void addHandler(
-      void (*fn)(Event&),
-      uint16_t eventMask = E_ALL,
-      Button* button = nullptr,
-      Gesture* gesture = nullptr
-    );
-    void delHandlers(
-      void (*fn)(Event&),
-      Button* button,
-      Gesture* gesture
-    );
-	std::vector<EventHandler> _eventHandlers;
+class M5Buttons {
+ public:
+  static M5Buttons* instance;
+  static void drawFunction(Button& b, ButtonColors bc);
+  M5Buttons();
+  Button* which(Point& p);
+  void draw();
+  void update();
+  void setFont(const GFXfont* freeFont_);
+  void setFont(uint8_t textFont_);
+  void setTextSize(uint8_t textSize_);
+  void (*drawFn)(Button& b, ButtonColors bc);
+  Event fireEvent(uint8_t finger, uint16_t type, Point& from, Point& to,
+                  uint16_t duration, Button* button, Gesture* gesture);
+  void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL,
+                  Button* button = nullptr, Gesture* gesture = nullptr);
+  void delHandlers(void (*fn)(Event&), Button* button, Gesture* gesture);
+  Event event;
+  std::vector<EventHandler> _eventHandlers;
+
+ protected:
+  uint8_t _textFont;
+  const GFXfont* _freeFont;
+  uint8_t _textSize;
+  bool _leftovers;
+
+#ifdef _M5TOUCH_H_
+ protected:
+  Finger _finger[2];
+#endif
 };
 
-// TFT_eSPI_Button compatibility
+// TFT_eSPI_Button compatibility emulation
 class TFT_eSPI_Button : public Button {
-  public:
-	TFT_eSPI_Button();
-	void initButton(
-	  TFT_eSPI *gfx, 
-	  int16_t x, int16_t y, uint16_t w, uint16_t h, 
-	  uint16_t outline, uint16_t fill, uint16_t textcolor,
-	  char *label,
-	  uint8_t textsize_
-	);
-	void initButtonUL(
-	  TFT_eSPI *gfx,
-	  int16_t x_, int16_t y_, uint16_t w_, uint16_t h_, 
-	  uint16_t outline, uint16_t fill, uint16_t textcolor, 
-	  char *label,
-	  uint8_t textsize_
-	);
-	void     setLabelDatum(int16_t x_delta, int16_t y_delta, uint8_t datum = MC_DATUM);
-	void     drawButton(bool inverted = false, String long_name = "");
-	bool     contains(int16_t x, int16_t y);
-	void     press(bool p);
-	bool     isPressed();
-	bool     justPressed();
-	bool     justReleased();
+ public:
+  TFT_eSPI_Button();
+  void initButton(TFT_eSPI* gfx, int16_t x, int16_t y, uint16_t w, uint16_t h,
+                  uint16_t outline, uint16_t fill, uint16_t textcolor,
+                  char* label_, uint8_t textsize_);
+  void initButtonUL(TFT_eSPI* gfx, int16_t x_, int16_t y_, uint16_t w_,
+                    uint16_t h_, uint16_t outline, uint16_t fill,
+                    uint16_t textcolor, char* label_, uint8_t textsize_);
+  void setLabelDatum(int16_t x_delta, int16_t y_delta,
+                     uint8_t datum = MC_DATUM);
+  void drawButton(bool inverted = false, String long_name = "");
+  bool contains(int16_t x, int16_t y);
+  void press(bool p);
+  bool isPressed();
+  bool justPressed();
+  bool justReleased();
 };
 
-#endif /* _M5BUTTON_H_ */
\ No newline at end of file
+#endif /* _M5BUTTON_H_ */
diff --git a/src/utility/PointAndZone.cpp b/src/utility/PointAndZone.cpp
index 837f1ea1..7a97be50 100644
--- a/src/utility/PointAndZone.cpp
+++ b/src/utility/PointAndZone.cpp
@@ -1,154 +1,181 @@
 #include "PointAndZone.h"
 
-
 // Point class
 
-Point::Point(int16_t x_ /* = -1 */, int16_t y_ /* = -1 */) {
-	x = x_;
-	y = y_;
+Point::Point(int16_t x_ /* = INVALID_VALUE */,
+             int16_t y_ /* = INVALID_VALUE */) {
+  x = x_;
+  y = y_;
 }
 
-bool Point::operator ==(const Point& p) {
-	return (Equals(p));
-}
-	
-bool Point::operator !=(const Point& p) {
-	return (!Equals(p));
-}
+bool Point::operator==(const Point& p) { return (Equals(p)); }
+
+bool Point::operator!=(const Point& p) { return (!Equals(p)); }
 
 Point::operator char*() {
-	sprintf(_text, "(%d, %d)", x, y);
-	return _text;
+  if (valid()) {
+    sprintf(_text, "(%d, %d)", x, y);
+  } else {
+    strncpy(_text, "(invalid)", 12);
+  }
+  return _text;
 }
 
-void Point::set(int16_t x_ /* = -1 */, int16_t y_ /* = -1 */) {
-	x = x_;
-	y = y_;
-}
+Point::operator bool() { return !(x == INVALID_VALUE && y == INVALID_VALUE); }
 
-bool Point::Equals(const Point& p) {
-	return (x == p.x && y == p.y);
+void Point::set(int16_t x_ /* = INVALID_VALUE */,
+                int16_t y_ /* = INVALID_VALUE */) {
+  x = x_;
+  y = y_;
 }
 
-bool Point::in(Zone& z) {
-	return (z.contains(x, y));
-}
+bool Point::Equals(const Point& p) { return (x == p.x && y == p.y); }
 
-bool Point::valid() {
-	return !(x == -1 && y == -1);
-}
+bool Point::in(Zone& z) { return (z.contains(x, y)); }
+
+bool Point::valid() { return !(x == INVALID_VALUE && y == INVALID_VALUE); }
 
 uint16_t Point::distanceTo(const Point& p) {
-	int16_t dx = x - p.x;
-	int16_t dy = y - p.y;
-	return sqrt(dx*dx + dy*dy);
+  int16_t dx = x - p.x;
+  int16_t dy = y - p.y;
+  return sqrt(dx * dx + dy * dy) + 0.5;
 }
 
-void Point::rotate(uint8_t m) {
-	int16_t normal_x = x;
-	int16_t normal_y = y;
-	int16_t inv_x = DISPLAY_WIDTH - 1 - x;
-	int16_t inv_y = DISPLAY_HEIGHT - 1 - y;
-	// inv_y can be negative for area below screen of M5Core2
-	switch (m) {
-	  case 0:
-		x = inv_y;
-		y = normal_x;
-		break;
-	  case 2:
-	    x = normal_y;
-	    y = inv_x;
-	    break;
-	  case 3:
-	  	x = inv_x;
-	  	y = inv_y;
-	  	break;
-	  // rotations 4-7 are mirrored
-	  case 4:
-	  	x = inv_y;
-	  	y = inv_x;
-	  	break;
-	  case 5:
-	  	x = normal_x;
-	  	y = inv_y;
-	  	break;
-	  case 6:
-	  	x = normal_y;
-	  	y = normal_x;
-	  	break;
-	  case 7:
-		x = inv_x;
-		y = normal_y;
-		break;
-	}
+uint16_t Point::directionTo(const Point& p, bool rot1 /* = false */) {
+  // 57.29578 =~ 180/pi, see https://stackoverflow.com/a/62486304
+  uint16_t a = (uint16_t)(450.5 + (atan2(p.y - y, p.x - x) * 57.29578));
+#ifdef TFT
+  if (rot1) {
+    if (TFT->rotation < 4) {
+      a = (((TFT->rotation + 3) % 4) * 90) + a;
+    } else {
+      a = (((TFT->rotation + 1) % 4) * 90) - a;
+    }
+  }
+#endif /* TFT */
+  a = (360 + a) % 360;
+  return a;
 }
 
+bool Point::isDirectionTo(const Point& p, int16_t wanted,
+                          uint8_t plusminus /* = PLUSMINUS */,
+                          bool rot1 /* = false */) {
+  uint16_t a = directionTo(p, rot1);
+  return (min(abs(wanted - a), 360 - abs(wanted - a)) <= plusminus);
+}
 
+void Point::rotate(uint8_t m) {
+  if (m == 1 || !valid()) return;
+  int16_t normal_x = x;
+  int16_t normal_y = y;
+  int16_t inv_x = TFT_WIDTH - 1 - x;
+  int16_t inv_y = TFT_HEIGHT - 1 - y;
+  // inv_y can be negative for area below screen of M5Core2
+  switch (m) {
+    case 0:
+      x = inv_y;
+      y = normal_x;
+      break;
+    case 2:
+      x = normal_y;
+      y = inv_x;
+      break;
+    case 3:
+      x = inv_x;
+      y = inv_y;
+      break;
+    // rotations 4-7 are mirrored
+    case 4:
+      x = inv_y;
+      y = inv_x;
+      break;
+    case 5:
+      x = normal_x;
+      y = inv_y;
+      break;
+    case 6:
+      x = normal_y;
+      y = normal_x;
+      break;
+    case 7:
+      x = inv_x;
+      y = normal_y;
+      break;
+  }
+}
 
 // Zone class
 
-Zone::Zone(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ /* = false */) {
-	set(x_, y_, w_, h_, rot1_);
+Zone::Zone(int16_t x_ /* = INVALID_VALUE */, int16_t y_ /* = INVALID_VALUE */,
+           int16_t w_ /* = 0 */, int16_t h_ /* = 0 */, bool rot1_ /* = false */
+) {
+  set(x_, y_, w_, h_, rot1_);
 }
 
-void Zone::set(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ /* = false */) {
-	x = x_;
-	y = y_;
-	w = w_;
-	h = h_;
-	rot1 = rot1_;
-}
+Zone::operator bool() { return !(x == INVALID_VALUE && y == INVALID_VALUE); }
 
-bool Zone::contains(const Point &p) {
-	return contains(p.x, p.y);
+void Zone::set(int16_t x_, int16_t y_, int16_t w_, int16_t h_,
+               bool rot1_ /* = false */) {
+  x = x_;
+  y = y_;
+  w = w_;
+  h = h_;
+  rot1 = rot1_;
 }
 
+bool Zone::valid() { return !(x == INVALID_VALUE && y == INVALID_VALUE); }
+
+bool Zone::contains(const Point& p) { return contains(p.x, p.y); }
+
 bool Zone::contains(int16_t x_, int16_t y_) {
-	#ifdef TFT
-		if (rot1 && TFT->rotation != 1) {
-			Zone t = *this;
-			t.rotate(TFT->rotation);
-			return (y_ >= t.y && y_ <= t.y + t.h && x_ >= t.x && x_ <= t.x + t.w);
-		}
-	#endif
-	return (y_ >= y && y_ <= y + h && x_ >= x && x_ <= x + w);
+
+#ifdef TFT
+  if (rot1 && TFT->rotation != 1) {
+    Zone t = *this;
+    t.rotate(TFT->rotation);
+    return (y_ >= t.y && y_ <= t.y + t.h && x_ >= t.x && x_ <= t.x + t.w);
+  }
+#endif /* TFT */
+
+  return (y_ >= y && y_ <= y + h && x_ >= x && x_ <= x + w);
 }
 
 void Zone::rotate(uint8_t m) {
-	int16_t normal_x = x;
-	int16_t normal_y = y;
-	int16_t inv_x = 319 - x - w;
-	int16_t inv_y = 239 - y - h;	// negative for area outside of screen
-	switch (m) {
-	  case 0:
-		x = inv_y;
-		y = normal_x;
-		break;
-	  case 2:
-	    x = normal_y;
-	    y = inv_x;
-	    break;
-	  case 3:
-	  	x = inv_x;
-	  	y = inv_y;
-	  	break;
-	  // rotations 4-7 are mirrored
-	  case 4:
-	  	x = inv_y;
-	  	y = inv_x;
-	  	break;
-	  case 5:
-	  	x = normal_x;
-	  	y = inv_y;
-	  	break;
-	  case 6:
-	  	x = normal_y;
-	  	y = normal_x;
-	  	break;
-	  case 7:
-		x = inv_x;
-		y = normal_y;
-		break;
-	}
-	if (!(m % 2)) std::swap(w, h);
-}
\ No newline at end of file
+  if (m == 1) return;
+  int16_t normal_x = x;
+  int16_t normal_y = y;
+  int16_t inv_x = TFT_WIDTH - 1 - x - w;
+  int16_t inv_y = TFT_HEIGHT - 1 - y - h;  // negative for area below screen
+  switch (m) {
+    case 0:
+      x = inv_y;
+      y = normal_x;
+      break;
+    case 2:
+      x = normal_y;
+      y = inv_x;
+      break;
+    case 3:
+      x = inv_x;
+      y = inv_y;
+      break;
+    // rotations 4-7 are mirrored
+    case 4:
+      x = inv_y;
+      y = inv_x;
+      break;
+    case 5:
+      x = normal_x;
+      y = inv_y;
+      break;
+    case 6:
+      x = normal_y;
+      y = normal_x;
+      break;
+    case 7:
+      x = inv_x;
+      y = normal_y;
+      break;
+  }
+  if (!(m % 2)) std::swap(w, h);
+}
diff --git a/src/utility/PointAndZone.h b/src/utility/PointAndZone.h
index e7671421..b09757b1 100644
--- a/src/utility/PointAndZone.h
+++ b/src/utility/PointAndZone.h
@@ -1,44 +1,184 @@
+/*
+
+== The PointAndZone Library ==
+
+  This library was written to supply Point and Zone, common primitives for
+  M5Display and M5Button, libraries originally written for the M5Stack series
+  of devices. They could be useful for many other applications, especially
+  anything based on a TFT_eSPI display driver.
+
+
+== Point and Zone: Describing Points and Areas on the Screen ==
+
+  The Point and Zone classes allow you to create variables that hold a point
+  or an area on the screen. You can
+
+  Point(x, y)
+    Holds a point on the screen. Has members x and y that hold the
+    coordinates of a touch. Values -1 for x and y indicate an invalid value,
+    and that's what a point starts out with if you declare it without
+    parameters. The 'valid()' method tests if a point is valid. If you
+    explicitly evaluate a Point as a boolean ("if (p) ..."), you also get
+    whether the point is valid, so this is equivalent to writing "if
+    (p.valid()) ...".
+
+  Zone(x, y, w, h)
+    Holds a rectangular area on the screen. Members x, y, w and h are for the
+    x and y coordinate of the top-left corner and the width and height of the
+    rectangle.
+
+  The 'set' method allows you to change the properties of an existing Point
+  or Zone. Using the 'in' or 'contains' method you can test if a point lies
+  in a zone.
+
+  The PointAndZone library also provides the low-level support for direction
+  from one point to another and for screen rotation translations.
+
+
+== Looking for Directions? ==
+
+  This library allows you to find the distance in pixels between two Point
+  objects with "A.distanceTo(B)". Using "A.directionTo(B)" will output a
+  compass course in degrees from A to B. That is, if A lies directly above A
+  on the screen the output will be 0, if B lies to the left of A, the output
+  will be 270. You can also test whether a direction matches some other
+  direction by using "A.isDirectionTo(B, 0, 30)". What this does is take the
+  direction from A to B and output 'true' if it is 0, plus or minus 30
+  degrees. (So either between 330 and 359 or between 0 and 30).
+
+  Do note that unlike in math, on a display the Y-axis points downwards. So
+  pixel coordinates (10, 10) are at direction 135 deg from (0, 0).
+
+  As a last argument to the direction functions, you can supply 'rot1' again
+  (default is 'false'). Just like in zones and buttons, what that means is
+  that the direction is output as if the rotation 1 coordinate system was
+  used.
+
+  Distance and drection functionality is used in Gesture recognition in the
+  M5Button highler level library. Its 'Event' objects have methods that look
+  very much like these, except the 'To' in the name is missing because Events
+  have a starting and ending point so you can just print
+  "myEvent.direction()" or state "if (myEvent.isDirection(0,30) ..."
+
+
+== Some Examples ==
+
+  Point a;
+  Point b(50, 120);
+  Serial.println(a.valid());                    // 0
+  Serial.println(a);                            // (invalid)
+  a.set(10, 30);
+  Serial.println(a);					                  // (10,30)
+  Serial.println(a.valid());                    // 1
+  Serial.println(b.y);                          // 120
+  Serial.println(a.distanceTo(b));              // 98
+  Serial.println(a.directionTo(b));             // 156
+  Serial.println(a.isDirectionTo(b, 0, 30));    // 0
+  Zone z(0,0,100, 100);
+  Serial.println(z.w);                          // 100
+  Serial.println(z.contains(a));                // 1
+  Serial.println(b.in(z));                      // 0
+
+
+== Screen Rotation and the 'rot1' Property ==
+
+  TL;DR:  just set it to 'false' if you don't need anything special,
+          otherwise read the rest of this section.
+
+  The TFT_eSPI library allows you to rotate the screen. On M5Stack devices,
+  you do this with "M5.Lcd.SetRotation", supplying a number between 0 and 7.
+  Numbers 0-3 are the obvious rotations, with 1 being the default. 4-7 are
+  the other 4 mirrored, so you would not be using these unless you want to
+  look at the display through a mirror for a homebrew heads-up display or
+  mini-teleprompter.
+
+  The M5Touch library for the Core2 device rotates the data it reads from the
+  sensor to match the display using the 'rotate' function on the Point
+  objects it reads. So if the display is upside down (rotation 3), the (0,
+  0)-point is diagonally accross from where it is in rotation 1. (And the
+  area that was below the screen at y 240-279 now has negative y-values.)
+
+  Zones (and buttons, because they are extensions of zones) have a 'rot1'
+  property. This would normally set to false, meaning the coordinates are
+  treated as specified in the current rotation. If it is set to 'true'
+  however, the library treats the cordinates as if they are specified in
+  rotation 1 and internally rotates them before evaluating whether a Point is
+  in a Zone. The Button object also rotates the coordinates before drawing
+  the button.
+
+  So by setting 'rot1' to true, you can creates zones and buttons that stay
+  in the same place even is the screen is rotated. On the Core2, this is used
+  to define the BtnA through BtnC virtual below-screen buttons, which should
+  always be in the area below the screen where the circles are printed,
+  regardless of rotation.
+
+
+== Porting ==
+
+  To use this library on other devices, simply replace these two lines
+
+    #include <M5Display.h>        // so that we can get the rotation
+    #include "utility/Config.h"   // Defines 'TFT', a pointer to the display
+
+  by whatever you need to do to make the symbol 'TFT' be a pointer to a
+  TFT_eSPI-derived display device that has a 'rotation' variable. If you
+  don't need rotation just delete the lines: the direction functions and the
+  'contains' function will now simply ignore their 'rot1' parameters.
+
+*/
+
 #ifndef _POINTANDZONE_H_
 #define _POINTANDZONE_H_
 
-
 #include <Arduino.h>
-#include <M5Display.h>
+#include <M5Display.h>        // so that we can get the rotation
+#include "utility/Config.h"   // Defines 'TFT', a pointer to the display
+
+#define INVALID_VALUE -32768
+#define PLUSMINUS 45  // default value for isDirectionTo
+
+#define UP        0
+#define RIGHT    90
+#define DOWN    180
+#define LEFT    270
 
-#define DISPLAY_WIDTH	320
-#define DISPLAY_HEIGHT	240
-#define TFT				M5Display::instance
 
 class Zone;
 
 class Point {
-  public:
-	Point(int16_t x_ = -1, int16_t y_ = -1);
-	bool operator ==(const Point& p);
-	bool operator !=(const Point& p);
-	operator char*();
-	void set(int16_t x_ = -1, int16_t y_ = -1);
-    bool in(Zone& z);
-    bool Equals(const Point& p);
-    bool valid();
-    uint16_t distanceTo(const Point& p);
-    void rotate(uint8_t m);
-    int16_t x, y;
-  private:
-    char _text[12];
+ public:
+  Point(int16_t x_ = INVALID_VALUE, int16_t y_ = INVALID_VALUE);
+  bool operator==(const Point& p);
+  bool operator!=(const Point& p);
+  explicit operator bool();
+  operator char*();
+  void set(int16_t x_ = -1, int16_t y_ = -1);
+  bool valid();
+  bool in(Zone& z);
+  bool Equals(const Point& p);
+  uint16_t distanceTo(const Point& p);
+  uint16_t directionTo(const Point& p, bool rot1 = false);
+  bool isDirectionTo(const Point& p, int16_t wanted,
+                     uint8_t plusminus = PLUSMINUS, bool rot1 = false);
+  void rotate(uint8_t m);
+  int16_t x, y;
+
+ private:
+  char _text[12];
 };
 
 class Zone {
-  public:
-	Zone(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ = false);
-	void set(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ = false);
-    bool contains(const Point &p);
-    bool contains(int16_t x, int16_t y);
-    void rotate(uint8_t m);
-    int16_t x, y, w, h;
-    // If rot1 is true, the zone specified in rotation 1 coordinates
-    // (stays in same place on physical screen, regardless of rotation.)
-    bool rot1;
+ public:
+  Zone(int16_t x_ = INVALID_VALUE, int16_t y_ = INVALID_VALUE, int16_t w_ = 0,
+       int16_t h_ = 0, bool rot1_ = false);
+  explicit operator bool();
+  bool valid();
+  void set(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ = false);
+  bool contains(const Point& p);
+  bool contains(int16_t x, int16_t y);
+  void rotate(uint8_t m);
+  int16_t x, y, w, h;
+  bool rot1;
 };
 
-#endif /* _POINTANDZONE_H_ */
\ No newline at end of file
+#endif /* _POINTANDZONE_H_ */

From 1dc092eaa7a9adfcd7f62bff08a745039e5db922 Mon Sep 17 00:00:00 2001
From: Rop Gonggrijp <rop@gonggri.jp>
Date: Sun, 11 Oct 2020 12:02:44 +0200
Subject: [PATCH 6/8] Have corect state handling E_TOUCH and E_RLEASE

---
 src/utility/M5Button.cpp |  6 ++----
 src/utility/M5Button.h   | 10 +++++-----
 2 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/src/utility/M5Button.cpp b/src/utility/M5Button.cpp
index 3f05a026..bd7f442f 100644
--- a/src/utility/M5Button.cpp
+++ b/src/utility/M5Button.cpp
@@ -94,7 +94,6 @@ int16_t Button::instanceIndex() {
 
 bool Button::read(bool manualRead /* = true */) {
   if (manualRead) _manuallyRead = true;
-  uint32_t duration = _time - _pressTime;
   if (_changed) {
     _changed = false;
     _lastChange = _time;
@@ -326,7 +325,7 @@ void Button::setTextSize(uint8_t textSize_ /* = 0 */) { _textSize = textSize_; }
     }
   }
 
-  if (bc.text != NODRAW && bc.text != bc.bg && b._label != "") {
+  if (bc.text != NODRAW && bc.text != bc.bg && strlen(b._label)) {
     // figure out where to put the text
     uint16_t tx, ty;
     tx = z.x + (z.w / 2);
@@ -483,7 +482,7 @@ void M5Buttons::setFont(uint8_t textFont_) {
 
 void M5Buttons::setTextSize(uint8_t textSize_) { _textSize = textSize_; }
 
-Event M5Buttons::fireEvent(uint8_t finger, uint16_t type, Point& from,
+void M5Buttons::fireEvent(uint8_t finger, uint16_t type, Point& from,
                            Point& to, uint16_t duration, Button* button,
                            Gesture* gesture) {
   Event e;
@@ -502,7 +501,6 @@ Event M5Buttons::fireEvent(uint8_t finger, uint16_t type, Point& from,
     if (h.gesture && h.gesture != e.gesture) continue;
     h.fn(e);
   }
-  return e;
 }
 
 void M5Buttons::addHandler(void (*fn)(Event&), uint16_t eventMask /* = E_ALL */,
diff --git a/src/utility/M5Button.h b/src/utility/M5Button.h
index 533c1204..a8960ca6 100644
--- a/src/utility/M5Button.h
+++ b/src/utility/M5Button.h
@@ -254,7 +254,7 @@
   touch button below.
 
   The hardware buttons in the older M5Stack devices are already set up to
-  display labels: all you need is supply colors. Their initialisation (in
+  display labels: all you need is supply colors. Their initialization (in
   M5Stack.h in this library) looks like this:
 
 
@@ -489,7 +489,7 @@
   them.
 
 
-  If your event reads date or calls functions in e.button or e.gesture,
+  If your event reads data or calls functions in e.button or e.gesture,
   remember that these are pointers. Without going into too much detail, it
   means it must do so with the -> notation, so to read the button x position,
   you would say "e.button->x".
@@ -863,15 +863,15 @@ class M5Buttons {
   void setFont(uint8_t textFont_);
   void setTextSize(uint8_t textSize_);
   void (*drawFn)(Button& b, ButtonColors bc);
-  Event fireEvent(uint8_t finger, uint16_t type, Point& from, Point& to,
-                  uint16_t duration, Button* button, Gesture* gesture);
+  void fireEvent(uint8_t finger, uint16_t type, Point& from, Point& to,
+                 uint16_t duration, Button* button, Gesture* gesture);
   void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL,
                   Button* button = nullptr, Gesture* gesture = nullptr);
   void delHandlers(void (*fn)(Event&), Button* button, Gesture* gesture);
   Event event;
-  std::vector<EventHandler> _eventHandlers;
 
  protected:
+  std::vector<EventHandler> _eventHandlers;
   uint8_t _textFont;
   const GFXfont* _freeFont;
   uint8_t _textSize;

From 37864821c922704db7f1e40d485865e024deb12d Mon Sep 17 00:00:00 2001
From: Rop Gonggrijp <rop@gonggri.jp>
Date: Mon, 12 Oct 2020 22:16:36 +0200
Subject: [PATCH 7/8] Add hide() feature

---
 src/utility/M5Button.cpp | 10 +++++++++-
 src/utility/M5Button.h   |  9 +++++++++
 2 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/src/utility/M5Button.cpp b/src/utility/M5Button.cpp
index bd7f442f..5b45fdd6 100644
--- a/src/utility/M5Button.cpp
+++ b/src/utility/M5Button.cpp
@@ -274,6 +274,7 @@ void Button::erase(uint16_t color /* = BLACK */) {
 }
 
 void Button::draw(ButtonColors bc) {
+  _hidden = false;
   // use locally set draw function if aplicable, global one otherwise
   if (drawFn) {
     drawFn(*this, bc);
@@ -282,6 +283,11 @@ void Button::draw(ButtonColors bc) {
   }
 }
 
+void Button::hide(uint16_t color /* = NODRAW */) {
+  _hidden = true;
+  if (color != NODRAW) erase(color);
+}
+
 char* Button::label() { return _label; }
 
 void Button::setLabel(const char* label_) { strncpy(_label, label_, 50); }
@@ -298,6 +304,7 @@ void Button::setFont(uint8_t textFont_ /* = 0 */) {
 
 void Button::setTextSize(uint8_t textSize_ /* = 0 */) { _textSize = textSize_; }
 
+
 // M5Buttons class
 
 /* static */ M5Buttons* M5Buttons::instance;
@@ -406,7 +413,8 @@ Button* M5Buttons::which(Point& p) {
   if (!Button::instances.size()) return nullptr;
   for (int i = Button::instances.size() - 1; i >= 0; --i) {
     Button* b = Button::instances[i];
-    if (b->_pin == 0xFF && b->contains(p)) return b;
+    // Always return button when i == 0 --> background
+    if (!i || (b->_pin == 0xFF && !b->_hidden && b->contains(p))) return b;
   }
   return nullptr;
 }
diff --git a/src/utility/M5Button.h b/src/utility/M5Button.h
index a8960ca6..b36a401b 100644
--- a/src/utility/M5Button.h
+++ b/src/utility/M5Button.h
@@ -241,6 +241,13 @@
   but initialises to the name. Use 'myButton.setLabel("New text!")' to change
   it.
 
+  With "myButton.hide()" you can make a button temporarily invisible to the
+  touch sensor. You can specify an optional clor value to draw over the button
+  if you want to make it visually disappear also. myButton.draw() makes it
+  visible to the touch sensor again, even if you have no colors defined, so
+  nothing shows on the screen. "MyButton.erase()" only paints over the button,
+  in a color you can specify (default black).
+
 
 == Visual Buttons (Labels) with Hardware Buttons ==
 
@@ -792,6 +799,7 @@ class Button : public Zone {
  public:
   void draw(ButtonColors bc);
   void draw();
+  void hide(uint16_t color = NODRAW);
   void erase(uint16_t color = BLACK);
   void setLabel(const char* label_);
   void setFont(const GFXfont* freeFont_);
@@ -805,6 +813,7 @@ class Button : public Zone {
   void (*drawFn)(Button& b, ButtonColors bc);
 
  protected:
+  bool _hidden;
   bool _compat;  // For TFT_eSPI_Button emulation
   char _label[51];
   uint8_t _textFont;

From a6bcd65d2291e49bdff50d63a646ab43e0a74c71 Mon Sep 17 00:00:00 2001
From: Rop Gonggrijp <rop@gonggri.jp>
Date: Wed, 14 Oct 2020 14:06:41 +0200
Subject: [PATCH 8/8] documentation fixes, name() -> getName(), rotation bugfix

---
 .../Buttons_and_Events/Buttons_and_Events.ino |   2 +-
 src/M5Stack.cpp                               |   3 -
 src/utility/M5Button.cpp                      |   9 +-
 src/utility/M5Button.h                        | 185 ++++++++++++------
 src/utility/PointAndZone.cpp                  |   8 +-
 src/utility/PointAndZone.h                    |  35 ++--
 6 files changed, 158 insertions(+), 84 deletions(-)

diff --git a/examples/Basics/Buttons_and_Events/Buttons_and_Events.ino b/examples/Basics/Buttons_and_Events/Buttons_and_Events.ino
index ed97ce6f..558a23d0 100644
--- a/examples/Basics/Buttons_and_Events/Buttons_and_Events.ino
+++ b/examples/Basics/Buttons_and_Events/Buttons_and_Events.ino
@@ -7,7 +7,7 @@ void setup() {
   M5.BtnC.setLabel("Amazing !");
   M5.BtnA.off = M5.BtnB.off = M5.BtnC.off = {BLUE, WHITE, NODRAW};
   M5.BtnA.on = M5.BtnB.on = M5.BtnC.on = {RED, WHITE, NODRAW};
-  M5.Events.addHandler(eventDisplay);
+  M5.Buttons.addHandler(eventDisplay);
   M5.Buttons.draw();
 }
 
diff --git a/src/M5Stack.cpp b/src/M5Stack.cpp
index 3567adbb..20b696f2 100644
--- a/src/M5Stack.cpp
+++ b/src/M5Stack.cpp
@@ -30,9 +30,6 @@ void M5Stack::begin(bool LCDEnable, bool SDEnable, bool SerialEnable, bool I2CEn
   // LCD INIT
   if (LCDEnable == true) {
     Lcd.begin();
-
-    // draw global buttons, set up before the display was ready
-    Buttons.draw();
   }
 
   // TONE
diff --git a/src/utility/M5Button.cpp b/src/utility/M5Button.cpp
index 5b45fdd6..9e8f2cd5 100644
--- a/src/utility/M5Button.cpp
+++ b/src/utility/M5Button.cpp
@@ -94,6 +94,7 @@ int16_t Button::instanceIndex() {
 
 bool Button::read(bool manualRead /* = true */) {
   if (manualRead) _manuallyRead = true;
+  event = Event();
   if (_changed) {
     _changed = false;
     _lastChange = _time;
@@ -216,7 +217,7 @@ void Button::cancel() {
   draw(off);
 }
 
-char* Button::name() { return _name; }
+char* Button::getName() { return _name; }
 
 bool Button::isPressed() { return _state; }
 
@@ -594,7 +595,7 @@ int16_t Gesture::instanceIndex() {
   return -1;
 }
 
-char* Gesture::name() { return _name; }
+char* Gesture::getName() { return _name; }
 
 bool Gesture::test(Point& from, Point& to, uint16_t duration) {
   if (from.distanceTo(to) < minDistance) return false;
@@ -645,8 +646,8 @@ const char* Event::typeName() {
 
 const char* Event::objName() {
   const char* empty = "";
-  if (gesture) return gesture->name();
-  if (button) return button->name();
+  if (gesture) return gesture->getName();
+  if (button) return button->getName();
   return empty;
 };
 
diff --git a/src/utility/M5Button.h b/src/utility/M5Button.h
index b36a401b..427bf8ac 100644
--- a/src/utility/M5Button.h
+++ b/src/utility/M5Button.h
@@ -7,7 +7,7 @@
   * Buttons on the screen, either as labels above the original M5Stack's
     hardware buttons or anywhere on the touch screen of the Core2.
 
-  * Zone and Point objects to work with screen locations are areas. Functions
+  * Zone and Point objects to work with screen locations and areas. Functions
     for distance, direction and more.
 
   * Touch gestures that are processed before the buttons, so you can still
@@ -17,8 +17,8 @@
     or poll in a loop. Events include tap, doubletap, pressed, dragged and
     more. Support for key repeat.
 
-  * Extensive rotation support, including support for buttons and gestures
-    that stay referenced to the physical screen regardless of rotation.
+  * Extensive screen rotation support, including support for buttons and
+    gestures that stay referenced to the physical screen regardless of rotation.
 
   * Intuitive, consistent and well-documented API.
 
@@ -36,7 +36,7 @@
 == Point and Zone: Describing Points and Areas on the Screen ==
 
   The Point and Zone classes allow you to create variables that hold a point
-  or an area on the screen. You can
+  or an area on the screen.
 
   Point(x, y)
 
@@ -127,8 +127,8 @@
     file, as tells you about the touch sensor and the lower-level touch
     interface that is underneath the M5Button library.
 
-  To have button that reacts to the touch sensor, all you need to do is
-  create a variable for the Button and providing the coordinates (x, y, width
+  To have a button that reacts to the touch sensor, all you need to do is
+  create a variable for the Button and provide the coordinates (x, y, width
   and height). These buttons can be used in two ways. You can either use them
   the way you would a normal Arduino button, or you can provide handler
   functions to process various events for the button. We'll talk about the
@@ -242,17 +242,17 @@
   it.
 
   With "myButton.hide()" you can make a button temporarily invisible to the
-  touch sensor. You can specify an optional clor value to draw over the button
-  if you want to make it visually disappear also. myButton.draw() makes it
-  visible to the touch sensor again, even if you have no colors defined, so
+  touch sensor. You can specify an optional color value to draw over the
+  button if you want to make it visually disappear also. myButton.draw() makes
+  it visible to the touch sensor again, even if you have no colors defined, so
   nothing shows on the screen. "MyButton.erase()" only paints over the button,
   in a color you can specify (default black).
 
 
 == Visual Buttons (Labels) with Hardware Buttons ==
 
-  You can have a visual representation of teh state of a hardware button on
-  the screen, for example right above the hardware butttons of the original
+  You can have a visual representation of the state of a hardware button on
+  the screen, for example right above the hardware buttons of the original
   M5Stack. We'll call these buttons "labels", but they're regular buttons
   that just respond to a physical button insetad of the touch sensor. If you
   want to display a label on the screen that responds to the state of a
@@ -296,13 +296,23 @@
   argument has changed from 'false' to 'true'. This argument is called
   'rot1', and it determines that the location of this Zone or Button is
   specified in rotation one, i.e. the normal default screen rotation. What
-  that means is that no matter what rotation you set the dsplay to, these
+  that means is that no matter what rotation you set the display to, these
   button will always stay in the same place. The documentation in
   src/utility/PointAndZone.h has more details if you want to know more about
   this. You will only ever need rot1 if you need multiple screen rotations
   AND you want objects to stay in the same physical place regardless.
 
 
+== M5.Buttons ==
+
+  Apart from the class "Button" that you use to create buttons of your own,
+  there is an instance called "M5.Buttons" (plural), that is used to talk to
+  the M5Button library for things that involve all buttons. For instance:
+  "M5.Buttons.setFont" sets a font for all buttons, and you can use
+  "M5.Buttons.addHandler" to add a handler that gets events for all buttons
+  (and gestures).
+
+
 == Events ==
 
   Buttons (and gestures, but we'll get to those later) have a set of simple
@@ -349,8 +359,9 @@
     }
 
 
-  Note that the function name is provided without the brackets. Here's two
-  ways you can set up a handler function to receive events:
+  Note that the function names "touched" and "released" are provided to
+  addHandler without the parenthesis. Here's two ways you can set up a handler
+  function to receive events:
 
     M5.Buttons.addHandler(myHandler);
 
@@ -358,19 +369,18 @@
 
     myButton.addHandler(myHandler);
 
-  The first form receives all the events relating to all buttons and
-  gestures, the second form only receives the events for that specific
-  button. After the name of the function, without the brackets, you can
-  pecify which events the function needs to receive. You can add together (or
-  "bitwise or") the names of the events if you want a handler function to
-  reive multiple events.
+  The first form receives all the events relating to all buttons and gestures,
+  the second form only receives the events for that specific button. After the
+  name of the function, without the brackets, you can specify which events the
+  function needs to receive. You can add together (or "bitwise or") the names
+  of the events if you want a handler function to reive multiple events.
 
   The Event object that is passed to the handler function contains all sorts
   of information about the event: where on the screen it started, where it
   ended, the duration, etc. etc.
 
-  Let's first look at all the possible events and when are fired. The first
-  three events always happen when a finger touches the display.
+  Let's first look at all the possible events and when they are fired. The
+  first three events always happen when a finger touches the display.
 
   E_TOUCH, E_MOVE and E_RELEASE
 
@@ -386,7 +396,7 @@
     There are also events that happen while the button is still pressed.
     These are E_PRESSING and E_LONGPRESSING. E_PRESSING happens as soon as
     M5Button is sure that's not just a short tap (more later). The maximum
-    time for a tap is settable, but defaults to 150 ms. So if teh button is
+    time for a tap is settable, but defaults to 150 ms. So if the button is
     still held 150 ms after E_TOUCH, E_PRESSING fires. Just once, unless you
     have set up a key repeat, more about that later too. Then at some point
     you might get a E_LONGPRESSING, if you have set up a duration for that to
@@ -394,12 +404,11 @@
 
   E_TAP, E_DBLTAP, E_PRESSED, E_LONGPRESSED and E_DRAGGED
 
-    Unless the keypress is cancelled (more later), exactly one of these
-    events will fire after the button has been released, after E_RELEASE has
-    fired. Think of these as final decisions on what kind of keypress this
-    was. (E_TAP takes a tiny bit longer before it fires because M5Button
-    needs to make it wasn't a doubletap, in that case E-DBLTAP wil fire
-    instead.)
+    Unless the keypress is cancelled (more later), exactly one of these events
+    will fire after the button has been released, after E_RELEASE has fired.
+    Think of these as final decisions on what kind of keypress this was.
+    (E_TAP takes a tiny bit longer before it fires because M5Button needs to
+    make sure it wasn't a doubletap, in that case E_DBLTAP wil fire instead.)
 
     So tap and doubletap are sort of obvious, E_LONGPRESSED fires if the key
     was pressed more that the set time in ms. E_DRAGGED fires if the finger
@@ -448,12 +457,21 @@
 
       Duration of the event in milliseconds.
 
-    e.button and e.gesture
+    e.button
+
+      Pointer to the button attached to the event. What that means is that you
+      can use all the methods for button as long as you precede them with
+      "e.button->". Note the '->' there because this is a pointer to an
+      object.
 
-      Pointers to the button and possibly gesture attached to the event. What
-      that means is that you can use all the methods for button as long as
-      you precede them with "e.button->". Note the '->' there because this is
-      a pointer to an object.
+    e.gesture
+
+      e.gesture is a pointer to the gesture attached to the event, and may be
+      null if the event is not a gesture. So unless you know for sure this
+      event is a gesture (because handler attached to that gesture or because
+      you asked for E_GESTURE events only), this pointer needs to be tested
+      using "if (e.gesture)" before using -> methods on it, oterwise your
+      program will crash.
 
     other methods
 
@@ -506,6 +524,36 @@
   port.
 
 
+== Taps, Doubletaps, Longpresses and Key Repeat ==
+
+  Some features are best explained with some examples:
+
+    myButton.tapTime = 0;
+
+      Turns off detection of taps and doubletaps, the button will fire
+      E_PRESSING immediately when pressed. Any other value makes that the
+      maximum time a tap can take in milliseconds, and thus the wait tme
+      before "E_PRESSING" fires.
+
+    mybutton.tapWait = 0;
+
+      Turns off detection of doubletaps only. Any other value makes that the
+      wait before an E_TAP fires, because M5Button is still waiting to see if
+      it's maybe a doubletap.
+
+    mybutton.longPressTime = 700;
+
+      Sets up the button to fire an E_LONGPRESSING after 700 ms, and then fire
+      E_LONGPRESSED instead of E_PRESSED when the button is released. By
+      default this is set to zero, meaning longpress detection is off.
+
+    myButton.repeatDelay = 500;
+    myButton.repeatInterval = 250;
+
+      Makes the button repeat the sending of its E_PRESSING event every 250
+      milliseconds if key is held for 500 ms.
+
+
 == In Loop vs. Event Handlers ==
 
   Button and Gesture objects have an 'event' method that returns the event
@@ -515,14 +563,31 @@
 
   If nothing was detected, the event type will be set to E_NONE with a value
   of 0, so you can do "if (myButton.event) ...". 'M5.Buttons.event' has the
-  event detected this time around, regardless of what button or gesture it
-  was attached to.
+  event detected this time around, regardless of what button or gesture it was
+  attached to. This example prints a star to serial if it is doubletapped.
+
+    #include <M5Core2.h>
+
+    Button myButton(50,70,220, 100, false, "Button",
+                    {YELLOW, BLACK, NODRAW},
+                    {RED, BLACK, NODRAW} );
+
+    void setup() {
+      M5.begin();
+      M5.Buttons.setFont(FSS18);
+      M5.Buttons.draw();
+    }
+
+    void loop() {
+      M5.update();
+      if (myButton.event == E_DBLTAP) Serial.print("* ");
+    }
 
 
 == M5.background ==
 
-  Only one button can become pressed for any spot om the touch screen. If you
-  define overlapping buttons, the first defined button forthe overlap become
+  Only one button can become pressed for any spot on the touch screen. If you
+  define overlapping buttons, the first defined button for the overlap become
   pressed and gets all subsequent events.
 
   One special button, "M5.background", was defined before any others, and it
@@ -533,11 +598,11 @@
 == Gestures on the Touch Screen ==
 
   Whenever a finger is released from the touch screen and before any
-  higher-level button events are fired, the library first checs whether this
-  was aybe a gesture. When you can degine gestures, you can optionally
-  specify the zone in which the gesture must start, the zone in which it must
-  end, the minimum distance the finger must have travelled, the direction it
-  has travelled in and the maximum time the gesture may take.
+  higher-level button events are fired, the library first checks whether this
+  was perhaps a gesture. When you define gestures, you can optionally specify
+  the zone in which the gesture must start, the zone in which it must end, the
+  minimum distance the finger must have travelled, the direction it has
+  travelled in and the maximum time the gesture may take.
 
     Gesture exampleGesture(fromZone, toZone, "exampleName", minimumDistance,
     direction, plusminus, ro1, maxTime)
@@ -546,39 +611,40 @@
   want to specify neither fromZone nor toZone, you can also leave them off
   completely. The minimum distance defaults to 75 pixels. The direction
   (default: don't care) is in compass degrees (so 180 is down), but the
-  compiler defines UP, DOWN, LEFT and right are provided for convenience. The
-  plusminus deines how many degress off-course the gesture may be, and the
-  rot1 flag defines whether this direction is relative to the current
-  rotation, or as seen in rotation 1. maxTime is in milliseconds as usual and
-  defaults to 500 ms.
+  compiler defines DIR_UP, DIR_DOWN, DIR_LEFT and DIR_RIGHT are provided for
+  convenience. The plusminus deines how many degress off-course the gesture
+  may be, and the rot1 flag defines whether this direction is relative to the
+  current rotation, or as seen in rotation 1. maxTime is in milliseconds as
+  usual and defaults to 500 ms. DIR_ANY can be used for direction if you need
+  to specify it in order provide a rot1 or maximum time value.
 
   here are a few examples of valid gesture definitions:
 
 
-    Gesture swipeDown("swipe down", 100, DOWN, 30);
+    Gesture swipeDown("swipe down", 100, DIR_DOWN, 30);
 
       Down (plus or minus 30 degrees) for at least 100 pixels within 500 ms
 
 
-    Gesture fromTop(Zone(0, 0, 360, 30), ANYWHERE, "from top", 100, DOWN, 30);
+    Gesture fromTop(Zone(0, 0, 360, 30), ANYWHERE, "from top", 100, DIR_DOWN, 30);
 
       The same but starting from within the top 30 pixels. (If you make that
       too narrow you may miss the swipe because the sensor 'sees' only once
       every 13 ms or so.
 
 
-  (Note that if you defines both these gestures in this orderm the second one
-  would never fire because any swipe that matched number two woul first match
+  (Note that if you defined both these gestures in this order the second one
+  would never fire because any swipe that matched number two would first match
   number one and fire that one instead.)
 
   Gestures have a 'wasDetected()' method if you want to detect them in the
-  main loop, or you attach a handler usin the same way you use for buttons,
-  with "myEvent.addhandler(myHandler)"
+  main loop, or you attach a handler the same way you would for a button,
+  with "myGesture.addhandler(myHandler)"
 
 
     #include <M5Core2.h>
 
-    Gesture swipeDown("swipe down", DOWN, 30);
+    Gesture swipeDown("swipe down", DIR_DOWN, 30);
 
     void setup() {
       M5.begin();
@@ -601,7 +667,7 @@
       void M5Buttons::drawFunction(Button& b, ButtonColors bc)
 
     If you make your own function that takes the same arguments but that does
-    something different, you can make the library us it by saying
+    something different, you can make the library use it by saying
     "M5.Buttons.drawFn = myFunction". You can even do that on a per-button
     basis with "myButton.drawFn = myFunction".
 
@@ -634,7 +700,7 @@
     old way of doing buttons that comes as an optional extra with the display
     library. Together with M5Touch's emulation of the TFT_eSPI touch
     interface (written for the older resistive touch-screens), you can use it
-    to run software made for those APIs. Do not use either fornew code: the
+    to run software made for those APIs. Do not use either for new code: the
     native interfaces are much more powerful.
 
 
@@ -772,9 +838,10 @@ class Button : public Zone {
   bool wasReleasefor(uint32_t ms);
   void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL);
   void delHandlers(void (*fn)(Event&) = nullptr);
-  char* name();
+  char* getName();
   uint32_t lastChange();
   Event event;
+  uint16_t userData;
   uint16_t tapTime, dbltapTime, longPressTime;
   uint16_t repeatDelay, repeatInterval;
 
@@ -838,7 +905,7 @@ class Gesture {
   bool wasDetected();
   void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL);
   void delHandlers(void (*fn)(Event&) = nullptr);
-  char* name();
+  char* getName();
   Zone fromZone;
   Zone toZone;
   Event event;
diff --git a/src/utility/PointAndZone.cpp b/src/utility/PointAndZone.cpp
index 7a97be50..09a84baa 100644
--- a/src/utility/PointAndZone.cpp
+++ b/src/utility/PointAndZone.cpp
@@ -68,8 +68,8 @@ void Point::rotate(uint8_t m) {
   if (m == 1 || !valid()) return;
   int16_t normal_x = x;
   int16_t normal_y = y;
-  int16_t inv_x = TFT_WIDTH - 1 - x;
-  int16_t inv_y = TFT_HEIGHT - 1 - y;
+  int16_t inv_x = HIGHEST_X - x;
+  int16_t inv_y = HIGHEST_Y - y;
   // inv_y can be negative for area below screen of M5Core2
   switch (m) {
     case 0:
@@ -114,7 +114,9 @@ Zone::Zone(int16_t x_ /* = INVALID_VALUE */, int16_t y_ /* = INVALID_VALUE */,
 
 Zone::operator bool() { return !(x == INVALID_VALUE && y == INVALID_VALUE); }
 
-void Zone::set(int16_t x_, int16_t y_, int16_t w_, int16_t h_,
+void Zone::set(int16_t x_ /* = INVALID_VALUE */,
+               int16_t y_ /* = INVALID_VALUE */,
+               int16_t w_ /* = 0 */, int16_t h_ /* = 0 */,
                bool rot1_ /* = false */) {
   x = x_;
   y = y_;
diff --git a/src/utility/PointAndZone.h b/src/utility/PointAndZone.h
index b09757b1..bed059b3 100644
--- a/src/utility/PointAndZone.h
+++ b/src/utility/PointAndZone.h
@@ -11,11 +11,12 @@
 == Point and Zone: Describing Points and Areas on the Screen ==
 
   The Point and Zone classes allow you to create variables that hold a point
-  or an area on the screen. You can
+  or an area on the screen.
 
   Point(x, y)
-    Holds a point on the screen. Has members x and y that hold the
-    coordinates of a touch. Values -1 for x and y indicate an invalid value,
+
+    Holds a point on the screen. Has members x and y that hold the coordinates
+    of a touch. Values INVALID_VALUE for x and y indicate an invalid value,
     and that's what a point starts out with if you declare it without
     parameters. The 'valid()' method tests if a point is valid. If you
     explicitly evaluate a Point as a boolean ("if (p) ..."), you also get
@@ -23,6 +24,7 @@
     (p.valid()) ...".
 
   Zone(x, y, w, h)
+
     Holds a rectangular area on the screen. Members x, y, w and h are for the
     x and y coordinate of the top-left corner and the width and height of the
     rectangle.
@@ -54,7 +56,7 @@
   that the direction is output as if the rotation 1 coordinate system was
   used.
 
-  Distance and drection functionality is used in Gesture recognition in the
+  Distance and direction functionality is used in Gesture recognition in the
   M5Button highler level library. Its 'Event' objects have methods that look
   very much like these, except the 'To' in the name is missing because Events
   have a starting and ending point so you can just print
@@ -68,7 +70,7 @@
   Serial.println(a.valid());                    // 0
   Serial.println(a);                            // (invalid)
   a.set(10, 30);
-  Serial.println(a);					                  // (10,30)
+  Serial.println(a);                            // (10,30)
   Serial.println(a.valid());                    // 1
   Serial.println(b.y);                          // 120
   Serial.println(a.distanceTo(b));              // 98
@@ -106,9 +108,9 @@
   in a Zone. The Button object also rotates the coordinates before drawing
   the button.
 
-  So by setting 'rot1' to true, you can creates zones and buttons that stay
-  in the same place even is the screen is rotated. On the Core2, this is used
-  to define the BtnA through BtnC virtual below-screen buttons, which should
+  So by setting 'rot1' to true, you can create zones and buttons that stay in
+  the same place even if the screen is rotated. On the Core2, this is used to
+  define the BtnA through BtnC virtual below-screen buttons, which should
   always be in the area below the screen where the circles are printed,
   regardless of rotation.
 
@@ -137,10 +139,14 @@
 #define INVALID_VALUE -32768
 #define PLUSMINUS 45  // default value for isDirectionTo
 
-#define UP        0
-#define RIGHT    90
-#define DOWN    180
-#define LEFT    270
+#define DIR_UP                 0
+#define DIR_RIGHT             90
+#define DIR_DOWN             180
+#define DIR_LEFT             270
+#define DIR_ANY    INVALID_VALUE
+
+#define HIGHEST_X 319         // Can't trust TFT_WIDTH, driver is portrait
+#define HIGHEST_Y 239
 
 
 class Zone;
@@ -152,7 +158,7 @@ class Point {
   bool operator!=(const Point& p);
   explicit operator bool();
   operator char*();
-  void set(int16_t x_ = -1, int16_t y_ = -1);
+  void set(int16_t x_ = INVALID_VALUE, int16_t y_ = INVALID_VALUE);
   bool valid();
   bool in(Zone& z);
   bool Equals(const Point& p);
@@ -173,7 +179,8 @@ class Zone {
        int16_t h_ = 0, bool rot1_ = false);
   explicit operator bool();
   bool valid();
-  void set(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ = false);
+  void set(int16_t x_ = INVALID_VALUE, int16_t y_ = INVALID_VALUE,
+           int16_t w_ = 0 , int16_t h_ = 0, bool rot1_ = false);
   bool contains(const Point& p);
   bool contains(int16_t x, int16_t y);
   void rotate(uint8_t m);