diff --git a/gui/core/ugui.py b/gui/core/ugui.py index 57137e7..683e491 100644 --- a/gui/core/ugui.py +++ b/gui/core/ugui.py @@ -22,7 +22,7 @@ ssd = None _vb = True gc.collect() -__version__ = (0, 1, 6) +__version__ = (0, 1, 7) # Null function dolittle = lambda *_: None @@ -119,28 +119,15 @@ class Input: return self._adj -# Normal way to populate the global display instance -def Display(objssd, nxt, sel, prev=None, incr=None, decr=None, - encoder=False, touch=False): - if touch: - from gui.primitives import Touchbutton - - ipdev = Input(nxt, sel, prev, incr, decr, encoder, Touchbutton) - else: - ipdev = Input(nxt, sel, prev, incr, decr, encoder, Pushbutton) - return DisplayIP(objssd, ipdev) - - -# Wrapper for ssd poviding framebuf compatible methods with abstract input device +# Wrapper for global ssd object providing framebuf compatible methods. +# Must be subclassed: subclass provides input device and populates globals +# display and ssd. class DisplayIP: - def __init__(self, objssd, ipdev): - global display, ssd + def __init__(self, ipdev): self.ipdev = ipdev - self.height = objssd.height - self.width = objssd.width + self.height = ssd.height + self.width = ssd.width self._is_grey = False # Not greyed-out - display = self # Populate globals - ssd = objssd def print_centred(self, writer, x, y, text, fgcolor=None, bgcolor=None, invert=False): sl = writer.stringlen(text) @@ -261,6 +248,22 @@ class DisplayIP: ssd.hline(x + c - z, y + h - z - 1, l, color) +# Define an input device and populate global ssd and display objects. +class Display(DisplayIP): + def __init__(self, objssd, nxt, sel, prev=None, incr=None, decr=None, + encoder=False, touch=False): + global display, ssd + ssd = objssd + if touch: + from gui.primitives import ESP32Touch + + ipdev = Input(nxt, sel, prev, incr, decr, encoder, ESP32Touch) + else: + ipdev = Input(nxt, sel, prev, incr, decr, encoder, Pushbutton) + super().__init__(ipdev) + display = self + + class Screen: do_gc = True # Allow user to take control of GC current_screen = None diff --git a/gui/primitives/__init__.py b/gui/primitives/__init__.py index 16e74ca..3aac9f3 100644 --- a/gui/primitives/__init__.py +++ b/gui/primitives/__init__.py @@ -6,7 +6,7 @@ _attrs = { "Delay_ms": "delay_ms", "Switch": "switch", "Pushbutton": "pushbutton", - "Touchbutton": "touchbutton", + "ESP32Touch": "pushbutton", } # Lazy loader, effectively does: diff --git a/gui/primitives/pushbutton.py b/gui/primitives/pushbutton.py index 78756bb..dd1a386 100644 --- a/gui/primitives/pushbutton.py +++ b/gui/primitives/pushbutton.py @@ -6,6 +6,10 @@ import uasyncio as asyncio import utime as time from . import launch, Delay_ms +try: + from machine import TouchPad +except ImportError: + pass class Pushbutton: debounce_ms = 50 @@ -105,3 +109,25 @@ class Pushbutton: def deinit(self): self._run.cancel() + + +class ESP32Touch(Pushbutton): + sensitivity = 0.9 + def __init__(self, pin, suppress=False): + self._thresh = 0 # Detection threshold + self._rawval = 0 + try: + self._pad = TouchPad(pin) + except ValueError: + raise ValueError(pin) # Let's have a bit of information :) + super().__init__(pin, suppress, False) + + # Current logical button state: True == touched + def rawstate(self): + rv = self._pad.read() # ~220μs + if rv > self._rawval: # Either initialisation or pad was touched + self._rawval = rv # when initialised and has now been released + self._thresh = round(rv * ESP32Touch.sensitivity) + return False # Untouched + return rv < self._thresh + diff --git a/gui/primitives/touchbutton.py b/gui/primitives/touchbutton.py deleted file mode 100644 index 3b463f9..0000000 --- a/gui/primitives/touchbutton.py +++ /dev/null @@ -1,122 +0,0 @@ -# touchbutton.py - -# Copyright (c) 2018-2022 Peter Hinch -# Released under the MIT License (MIT) - see LICENSE file - -# API is as per Pushbutton class with the following deviations: -# Assumes button is untouched at start so lacks sense constructor arg. -# Has sensitivity class variable. - -import uasyncio as asyncio -import utime as time -from machine import TouchPad -from . import launch, Delay_ms - -class Touchbutton: - sensitivity = 0.9 - debounce_ms = 50 - long_press_ms = 1000 - double_click_ms = 400 - def __init__(self, pin, suppress=False): - try: - self._pad = TouchPad(pin) - except ValueError: - raise ValueError(pin) # Let's have a bit of information :) - self._thresh = 0 # Detection threshold - self._supp = suppress - self._dblpend = False # Doubleclick waiting for 2nd click - self._dblran = False # Doubleclick executed user function - self._tf = False - self._ff = False - self._df = False - self._ld = False # Delay_ms instance for long press - self._dd = False # Ditto for doubleclick - self.state = False # Initial state - self._run = asyncio.create_task(self.buttoncheck()) # Thread runs forever - - def press_func(self, func=False, args=()): - self._tf = func - self._ta = args - - def release_func(self, func=False, args=()): - self._ff = func - self._fa = args - - def double_func(self, func=False, args=()): - self._df = func - self._da = args - if func: # If double timer already in place, leave it - if not self._dd: - self._dd = Delay_ms(self._ddto) - else: - self._dd = False # Clearing down double func - - def long_func(self, func=False, args=()): - if func: - if self._ld: - self._ld.callback(func, args) - else: - self._ld = Delay_ms(func, args) - else: - self._ld = False - - async def _get_thresh(self): - await asyncio.sleep_ms(50) - self._thresh = round(self._pad.read() * Touchbutton.sensitivity) - # print(self._thresh) - - # Current logical button state: True == pressed - def rawstate(self): - return self._pad.read() < self._thresh - - # Current debounced state of button (True == pressed) - def __call__(self): - return self.state - - def _ddto(self): # Doubleclick timeout: no doubleclick occurred - self._dblpend = False - if self._supp and not self.state: - if not self._ld or (self._ld and not self._ld()): - launch(self._ff, self._fa) - - async def buttoncheck(self): - await self._get_thresh() - while True: - state = self.rawstate() - # State has changed: act on it now. - if state != self.state: - self.state = state - if state: # Button pressed: launch pressed func - if self._tf: - launch(self._tf, self._ta) - if self._ld: # There's a long func: start long press delay - self._ld.trigger(Touchbutton.long_press_ms) - if self._df: - if self._dd(): # Second click: timer running - self._dd.stop() - self._dblpend = False - self._dblran = True # Prevent suppressed launch on release - launch(self._df, self._da) - else: - # First click: start doubleclick timer - self._dd.trigger(Touchbutton.double_click_ms) - self._dblpend = True # Prevent suppressed launch on release - else: # Button release. Is there a release func? - if self._ff: - if self._supp: - d = self._ld - # If long delay exists, is running and doubleclick status is OK - if not self._dblpend and not self._dblran: - if (d and d()) or not d: - launch(self._ff, self._fa) - else: - launch(self._ff, self._fa) - if self._ld: - self._ld.stop() # Avoid interpreting a second click as a long push - self._dblran = False - # Ignore state changes until switch has settled - # See https://github.com/peterhinch/micropython-async/issues/69 - await asyncio.sleep_ms(Touchbutton.debounce_ms) - - def deinit(self): - self._run.cancel()