First pass at touch support.

touch
Peter Hinch 2022-07-19 18:31:01 +01:00
rodzic 17554ccf7c
commit 16b73b7502
2 zmienionych plików z 168 dodań i 91 usunięć

Wyświetl plik

@ -15,6 +15,7 @@ from gui.core.colors import *
from hardware_setup import ssd from hardware_setup import ssd
from gui.primitives import Pushbutton from gui.primitives import Pushbutton
# Globally available singleton objects # Globally available singleton objects
display = None # Singleton instance display = None # Singleton instance
ssd = None ssd = None
@ -24,10 +25,13 @@ gc.collect()
__version__ = (0, 1, 6) __version__ = (0, 1, 6)
# Null function # Null function
dolittle = lambda *_ : None dolittle = lambda *_: None
async def _g(): async def _g():
pass pass
type_coro = type(_g()) type_coro = type(_g())
# Navigation destinations # Navigation destinations
@ -36,23 +40,25 @@ _NEXT = const(1)
_PREV = const(2) _PREV = const(2)
_LAST = const(3) _LAST = const(3)
def quiet(): def quiet():
global _vb global _vb
_vb = False _vb = False
# Input abstracts input from 2-5 pushbuttons or 3 buttons + encoder. Handles # Input abstracts input from 2-5 pushbuttons or 3 buttons + encoder. Handles
# transitions between modes (normal, precision, adjustment) # transitions between modes (normal, precision, adjustment)
# BTN class instantiates a push button (may be other than a switch).
class Input: class Input:
def __init__(self, nxt, sel, prev, incr, decr, encoder, BTN):
def __init__(self, nxt, sel, prev, incr, decr, encoder):
self._encoder = encoder # Encoder in use self._encoder = encoder # Encoder in use
self._precision = False # Precision mode self._precision = False # Precision mode
self._adj = False # Adjustment mode self._adj = False # Adjustment mode
# Count buttons # Count buttons
self._nb = sum(1 for x in (nxt, sel, prev, incr, decr) if x is not None) self._nb = sum(1 for x in (nxt, sel, prev, incr, decr) if x is not None)
# Mandatory buttons # Mandatory buttons
self._next = Pushbutton(nxt) self._next = BTN(nxt)
self._sel = Pushbutton(sel, suppress=True) self._sel = BTN(sel, suppress=True)
# Call current screen bound method # Call current screen bound method
self._next.press_func(Screen.ctrl_move, (_NEXT,)) self._next.press_func(Screen.ctrl_move, (_NEXT,))
self._sel.release_func(Screen.sel_ctrl) self._sel.release_func(Screen.sel_ctrl)
@ -62,22 +68,23 @@ class Input:
self._sel.double_func(self.adj_mode) # Double click toggles adjust self._sel.double_func(self.adj_mode) # Double click toggles adjust
# Optional buttons # Optional buttons
if prev is not None: if prev is not None:
self._prev = Pushbutton(prev) self._prev = BTN(prev)
self._prev.press_func(Screen.ctrl_move, (_PREV,)) self._prev.press_func(Screen.ctrl_move, (_PREV,))
if encoder: if encoder:
_vb and print('Using encoder.') _vb and print("Using encoder.")
if incr is None or decr is None: if incr is None or decr is None:
raise ValueError('Must specify pins for encoder.') raise ValueError("Must specify pins for encoder.")
from gui.primitives.encoder import Encoder from gui.primitives.encoder import Encoder
self._enc = Encoder(incr, decr, div=encoder, callback=Screen.adjust) self._enc = Encoder(incr, decr, div=encoder, callback=Screen.adjust)
else: else:
_vb and print('Using {:d} switches.'.format(self._nb)) _vb and print("Using {:d} switches.".format(self._nb))
# incr and decr methods get the button as an arg. # incr and decr methods get the button as an arg.
if incr is not None: if incr is not None:
sup = Pushbutton(incr) sup = BTN(incr)
sup.press_func(Screen.adjust, (sup, 1)) sup.press_func(Screen.adjust, (sup, 1))
if decr is not None: if decr is not None:
sdn = Pushbutton(decr) sdn = BTN(decr)
sdn.press_func(Screen.adjust, (sdn, -1)) sdn.press_func(Screen.adjust, (sdn, -1))
def precision(self, val): # Also called by Screen.ctrl_move to cancel mode def precision(self, val): # Also called by Screen.ctrl_move to cancel mode
@ -111,14 +118,21 @@ class Input:
def is_adjust(self): def is_adjust(self):
return self._adj return self._adj
# Normal way to populate the global display instance # Normal way to populate the global display instance
def Display(objssd, nxt, sel, prev=None, incr=None, decr=None, encoder=False): def Display(objssd, nxt, sel, prev=None, incr=None, decr=None,
ipdev = Input(nxt, sel, prev, incr, decr, encoder) 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) return DisplayIP(objssd, ipdev)
# Wrapper for ssd poviding framebuf compatible methods with abstract input device # Wrapper for ssd poviding framebuf compatible methods with abstract input device
class DisplayIP: class DisplayIP:
def __init__(self, objssd, ipdev): def __init__(self, objssd, ipdev):
global display, ssd global display, ssd
self.ipdev = ipdev self.ipdev = ipdev
@ -148,10 +162,13 @@ class DisplayIP:
# Greying out has only one option given limitation of 4-bit display driver # Greying out has only one option given limitation of 4-bit display driver
# It would be possible to do better with RGB565 but would need inverse transformation # It would be possible to do better with RGB565 but would need inverse transformation
# to (r, g, b), scale and re-convert to integer. # to (r, g, b), scale and re-convert to integer.
def _getcolor(self, color): # Takes in an integer color, bit size dependent on driver def _getcolor(self, color):
return color_map[GREY_OUT] if self._is_grey and color != color_map[BG] else color # Takes in an integer color, bit size dependent on driver
return (
color_map[GREY_OUT] if self._is_grey and color != color_map[BG] else color
)
def usegrey(self, val): # display.usegrey(True) sets greyed-out def usegrey(self, val): # display.usegrey(True) sets greyed-out
self._is_grey = val self._is_grey = val
return self return self
@ -178,49 +195,49 @@ class DisplayIP:
ssd.line(x1, y1, x2, y2, self._getcolor(color)) ssd.line(x1, y1, x2, y2, self._getcolor(color))
# Private method uses physical color # Private method uses physical color
def _circle(self, x0, y0, r, color): # Single pixel circle def _circle(self, x0, y0, r, color): # Single pixel circle
x = -r x = -r
y = 0 y = 0
err = 2 -2*r err = 2 - 2 * r
while x <= 0: while x <= 0:
ssd.pixel(x0 -x, y0 +y, color) ssd.pixel(x0 - x, y0 + y, color)
ssd.pixel(x0 +x, y0 +y, color) ssd.pixel(x0 + x, y0 + y, color)
ssd.pixel(x0 +x, y0 -y, color) ssd.pixel(x0 + x, y0 - y, color)
ssd.pixel(x0 -x, y0 -y, color) ssd.pixel(x0 - x, y0 - y, color)
e2 = err e2 = err
if (e2 <= y): if e2 <= y:
y += 1 y += 1
err += y*2 +1 err += y * 2 + 1
if (-x == y and e2 <= x): if -x == y and e2 <= x:
e2 = 0 e2 = 0
if (e2 > x): if e2 > x:
x += 1 x += 1
err += x*2 +1 err += x * 2 + 1
def circle(self, x0, y0, r, color, width =1): # Draw circle (maybe grey) def circle(self, x0, y0, r, color, width=1): # Draw circle (maybe grey)
color = self._getcolor(color) color = self._getcolor(color)
x0, y0, r = int(x0), int(y0), int(r) x0, y0, r = int(x0), int(y0), int(r)
for r in range(r, r -width, -1): for r in range(r, r - width, -1):
self._circle(x0, y0, r, color) self._circle(x0, y0, r, color)
def fillcircle(self, x0, y0, r, color): # Draw filled circle def fillcircle(self, x0, y0, r, color): # Draw filled circle
color = self._getcolor(color) color = self._getcolor(color)
x0, y0, r = int(x0), int(y0), int(r) x0, y0, r = int(x0), int(y0), int(r)
x = -r x = -r
y = 0 y = 0
err = 2 -2*r err = 2 - 2 * r
while x <= 0: while x <= 0:
ssd.line(x0 -x, y0 -y, x0 -x, y0 +y, color) ssd.line(x0 - x, y0 - y, x0 - x, y0 + y, color)
ssd.line(x0 +x, y0 -y, x0 +x, y0 +y, color) ssd.line(x0 + x, y0 - y, x0 + x, y0 + y, color)
e2 = err e2 = err
if (e2 <= y): if e2 <= y:
y +=1 y += 1
err += y*2 +1 err += y * 2 + 1
if (-x == y and e2 <= x): if -x == y and e2 <= x:
e2 = 0 e2 = 0
if (e2 > x): if e2 > x:
x += 1 x += 1
err += x*2 +1 err += x * 2 + 1
def clip_rect(self, x, y, w, h, color): def clip_rect(self, x, y, w, h, color):
color = self._getcolor(color) color = self._getcolor(color)
@ -273,7 +290,7 @@ class Screen:
cls.current_screen.do_sel() cls.current_screen.do_sel()
# Adjust the value of a widget. If an encoder is used, button arg # Adjust the value of a widget. If an encoder is used, button arg
# is an int (discarded), val is the delta. If using buttons, 1st # is an int (discarded), val is the delta. If using buttons, 1st
# arg is the button, delta is +1 or -1 # arg is the button, delta is +1 or -1
@classmethod @classmethod
def adjust(cls, button, val): def adjust(cls, button, val):
@ -285,11 +302,11 @@ class Screen:
def select(cls, obj): def select(cls, obj):
if cls.current_screen is not None: if cls.current_screen is not None:
return cls.current_screen.move_to(obj) return cls.current_screen.move_to(obj)
@classmethod @classmethod
def show(cls, force): def show(cls, force):
for obj in cls.current_screen.displaylist: for obj in cls.current_screen.displaylist:
if obj.visible: # In a buttonlist only show visible button if obj.visible: # In a buttonlist only show visible button
if force or obj.draw: if force or obj.draw:
obj.show() obj.show()
@ -306,21 +323,21 @@ class Screen:
if forward: if forward:
if isinstance(cls_new_screen, type): if isinstance(cls_new_screen, type):
if isinstance(cs_old, Window): if isinstance(cs_old, Window):
raise ValueError('Windows are modal.') raise ValueError("Windows are modal.")
new_screen = cls_new_screen(*args, **kwargs) new_screen = cls_new_screen(*args, **kwargs)
if not len(new_screen.lstactive): if not len(new_screen.lstactive):
raise ValueError("Screen has no active widgets.") raise ValueError("Screen has no active widgets.")
else: else:
raise ValueError('Must pass Screen class or subclass (not instance)') raise ValueError("Must pass Screen class or subclass (not instance)")
new_screen.parent = cs_old new_screen.parent = cs_old
cs_new = new_screen cs_new = new_screen
else: else:
cs_new = cls_new_screen # An object, not a class cs_new = cls_new_screen # An object, not a class
display.ipdev.adj_mode(False) # Ensure normal mode display.ipdev.adj_mode(False) # Ensure normal mode
cls.current_screen = cs_new cls.current_screen = cs_new
cs_new.on_open() # Optional subclass method cs_new.on_open() # Optional subclass method
cs_new._do_open(cs_old) # Clear and redraw cs_new._do_open(cs_old) # Clear and redraw
cs_new.after_open() # Optional subclass method cs_new.after_open() # Optional subclass method
if cs_old is None: # Initialising if cs_old is None: # Initialising
asyncio.run(Screen.monitor()) # Starts and ends uasyncio asyncio.run(Screen.monitor()) # Starts and ends uasyncio
# Don't do asyncio.new_event_loop() as it prevents re-running # Don't do asyncio.new_event_loop() as it prevents re-running
@ -346,18 +363,18 @@ class Screen:
# no factor, do_refresh confers no benefit, so use synchronous code. # no factor, do_refresh confers no benefit, so use synchronous code.
@classmethod @classmethod
async def auto_refresh(cls): async def auto_refresh(cls):
arfsh = hasattr(ssd, 'do_refresh') # Refresh can be asynchronous. arfsh = hasattr(ssd, "do_refresh") # Refresh can be asynchronous.
# By default rfsh_start is permanently set. User code can clear this. # By default rfsh_start is permanently set. User code can clear this.
cls.rfsh_start.set() cls.rfsh_start.set()
if arfsh: if arfsh:
h = ssd.height h = ssd.height
split = max(y for y in (1,2,3,5,7) if not h % y) split = max(y for y in (1, 2, 3, 5, 7) if not h % y)
if split == 1: if split == 1:
arfsh = False arfsh = False
while True: while True:
await cls.rfsh_start.wait() await cls.rfsh_start.wait()
Screen.show(False) # Update stale controls. No physical refresh. Screen.show(False) # Update stale controls. No physical refresh.
# Now perform physical refresh. # Now perform physical refresh.
if arfsh: if arfsh:
await ssd.do_refresh(split) await ssd.do_refresh(split)
else: else:
@ -372,13 +389,13 @@ class Screen:
if parent is None: # Closing base screen. Quit. if parent is None: # Closing base screen. Quit.
cls.shutdown() cls.shutdown()
else: else:
cls.change(parent, forward = False) cls.change(parent, forward=False)
@classmethod @classmethod
def addobject(cls, obj): def addobject(cls, obj):
cs = cls.current_screen cs = cls.current_screen
if cs is None: if cs is None:
raise OSError('You must create a Screen instance') raise OSError("You must create a Screen instance")
# Populate list of active widgets (i.e. ones that can acquire focus). # Populate list of active widgets (i.e. ones that can acquire focus).
if obj.active: if obj.active:
# Append to active list regrdless of disabled state which may # Append to active list regrdless of disabled state which may
@ -403,18 +420,20 @@ class Screen:
self.width = ssd.width self.width = ssd.width
self.row = 0 self.row = 0
self.col = 0 self.col = 0
if Screen.current_screen is None and Screen.do_gc: # Initialising class and task if (
Screen.current_screen is None and Screen.do_gc
): # Initialising class and task
# Here we create singleton tasks # Here we create singleton tasks
asyncio.create_task(self._garbage_collect()) asyncio.create_task(self._garbage_collect())
Screen.current_screen = self Screen.current_screen = self
self.parent = None self.parent = None
def _do_open(self, old_screen): # Window overrides def _do_open(self, old_screen): # Window overrides
dev = display.usegrey(False) dev = display.usegrey(False)
# If opening a Screen from a Window just blank and redraw covered area # If opening a Screen from a Window just blank and redraw covered area
if isinstance(old_screen, Window): if isinstance(old_screen, Window):
x0, y0, x1, y1, w, h = old_screen._list_dims() x0, y0, x1, y1, w, h = old_screen._list_dims()
dev.fill_rect(x0, y0, w, h, color_map[BG]) # Blank to screen BG dev.fill_rect(x0, y0, w, h, color_map[BG]) # Blank to screen BG
for obj in [z for z in self.displaylist if z.overlaps(x0, y0, x1, y1)]: for obj in [z for z in self.displaylist if z.overlaps(x0, y0, x1, y1)]:
if obj.visible: if obj.visible:
obj.show() obj.show()
@ -465,7 +484,7 @@ class Screen:
# Move currency to a specific control. # Move currency to a specific control.
def move_to(self, obj): def move_to(self, obj):
lo = self.get_obj() # Old current object lo = self.get_obj() # Old current object
for idx in range(len(self.lstactive)) : for idx in range(len(self.lstactive)):
co = self.get_obj(idx) co = self.get_obj(idx)
if co is obj: if co is obj:
self.selected_obj = idx self.selected_obj = idx
@ -484,13 +503,13 @@ class Screen:
def do_adj(self, button, val): def do_adj(self, button, val):
co = self.get_obj() co = self.get_obj()
if co is not None and hasattr(co, 'do_adj'): if co is not None and hasattr(co, "do_adj"):
co.do_adj(button, val) # Widget can handle up/down co.do_adj(button, val) # Widget can handle up/down
else: else:
Screen.current_screen.move(_FIRST if val < 0 else _LAST) Screen.current_screen.move(_FIRST if val < 0 else _LAST)
# Methods optionally implemented in subclass # Methods optionally implemented in subclass
def on_open(self): def on_open(self):
return return
def after_open(self): def after_open(self):
@ -515,9 +534,10 @@ class Screen:
gc.collect() gc.collect()
gc.threshold(gc.mem_free() // 4 + gc.mem_alloc()) gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())
n += 1 n += 1
n &= 0x1f n &= 0x1F
_vb and (not n) and print("Free RAM", gc.mem_free()) _vb and (not n) and print("Free RAM", gc.mem_free())
# Very basic window class. Cuts a rectangular hole in a screen on which # Very basic window class. Cuts a rectangular hole in a screen on which
# content may be drawn. # content may be drawn.
class Window(Screen): class Window(Screen):
@ -535,8 +555,18 @@ class Window(Screen):
def close(): # More intuitive name for popup window def close(): # More intuitive name for popup window
Screen.back() Screen.back()
def __init__(self, row, col, height, width, *, draw_border=True, def __init__(
bgcolor=None, fgcolor=None, writer=None): self,
row,
col,
height,
width,
*,
draw_border=True,
bgcolor=None,
fgcolor=None,
writer=None,
):
Screen.__init__(self) Screen.__init__(self)
self.row = row self.row = row
self.col = col self.col = col
@ -563,12 +593,22 @@ class Window(Screen):
y = self.row y = self.row
return x, y, x + w, y + h, w, h return x, y, x + w, y + h, w, h
# Base class for all displayable objects # Base class for all displayable objects
class Widget: class Widget:
def __init__(
def __init__(self, writer, row, col, height, width, self,
fgcolor, bgcolor, bdcolor, writer,
value=None, active=False): row,
col,
height,
width,
fgcolor,
bgcolor,
bdcolor,
value=None,
active=False,
):
self.active = active self.active = active
# By default widgets cannot be adjusted: no green border in adjust mode # By default widgets cannot be adjusted: no green border in adjust mode
self.adjustable = False self.adjustable = False
@ -597,7 +637,7 @@ class Widget:
# Maximum row and col. Defaults for user metrics. May be overridden # Maximum row and col. Defaults for user metrics. May be overridden
self.mrow = row + height + 2 # in subclass. Allow for border. self.mrow = row + height + 2 # in subclass. Allow for border.
self.mcol = col + width + 2 self.mcol = col + width + 2
self.visible = True # Used by ButtonList class for invisible buttons self.visible = True # Used by ButtonList class for invisible buttons
self.draw = True # Signals that obect must be redrawn self.draw = True # Signals that obect must be redrawn
self._value = value self._value = value
@ -619,13 +659,17 @@ class Widget:
self.def_bdcolor = bdcolor self.def_bdcolor = bdcolor
# has_border is True if a border was drawn # has_border is True if a border was drawn
self.has_border = False self.has_border = False
self.callback = dolittle # Value change callback self.callback = dolittle # Value change callback
self.args = [] self.args = []
def warning(self): def warning(self):
print('Warning: attempt to create {} outside screen dimensions.'.format(self.__class__.__name__)) print(
"Warning: attempt to create {} outside screen dimensions.".format(
self.__class__.__name__
)
)
def value(self, val=None): # User method to get or set value def value(self, val=None): # User method to get or set value
if val is not None: if val is not None:
if type(val) is float: if type(val) is float:
val = min(max(val, 0.0), 1.0) val = min(max(val, 0.0), 1.0)
@ -640,10 +684,12 @@ class Widget:
# Some widgets (e.g. Dial) have an associated Label # Some widgets (e.g. Dial) have an associated Label
def text(self, text=None, invert=False, fgcolor=None, bgcolor=None, bdcolor=None): def text(self, text=None, invert=False, fgcolor=None, bgcolor=None, bdcolor=None):
if hasattr(self, 'label'): if hasattr(self, "label"):
self.label.value(text, invert, fgcolor, bgcolor, bdcolor) self.label.value(text, invert, fgcolor, bgcolor, bdcolor)
else: else:
raise ValueError('Method {}.text does not exist.'.format(self.__class__.__name__)) raise ValueError(
"Method {}.text does not exist.".format(self.__class__.__name__)
)
# Called from subclass prior to populating framebuf with control # Called from subclass prior to populating framebuf with control
def show(self, black=True): def show(self, black=True):
@ -657,11 +703,13 @@ class Widget:
dev = display.usegrey(self._greyed_out) dev = display.usegrey(self._greyed_out)
x = self.col x = self.col
y = self.row y = self.row
dev.fill_rect(x, y, self.width, self.height, color_map[BG] if black else self.bgcolor) dev.fill_rect(
x, y, self.width, self.height, color_map[BG] if black else self.bgcolor
)
return True return True
# Called by Screen.show(). Draw background and bounding box if required. # Called by Screen.show(). Draw background and bounding box if required.
# Border is always 2 pixels wide, outside control's bounding box # Border is always 2 pixels wide, outside control's bounding box
def draw_border(self): def draw_border(self):
if self.screen is Screen.current_screen: if self.screen is Screen.current_screen:
dev = display.usegrey(self._greyed_out) dev = display.usegrey(self._greyed_out)
@ -669,10 +717,14 @@ class Widget:
y = self.row - 2 y = self.row - 2
w = self.width + 4 w = self.width + 4
h = self.height + 4 h = self.height + 4
#print('border', self, display.ipdev.is_adjust()) # print('border', self, display.ipdev.is_adjust())
if self.has_focus() and not isinstance(self, DummyWidget): if self.has_focus() and not isinstance(self, DummyWidget):
color = color_map[FOCUS] color = color_map[FOCUS]
precision = hasattr(self, 'do_precision') and self.do_precision and display.ipdev.is_precision() precision = (
hasattr(self, "do_precision")
and self.do_precision
and display.ipdev.is_precision()
)
if precision: if precision:
color = self.prcolor color = self.prcolor
elif display.ipdev.is_adjust() and self.adjustable: elif display.ipdev.is_adjust() and self.adjustable:
@ -688,7 +740,7 @@ class Widget:
dev.rect(x, y, w, h, self.bdcolor) dev.rect(x, y, w, h, self.bdcolor)
self.has_border = True self.has_border = True
def overlaps(self, xa, ya, xb, yb): # Args must be sorted: xb > xa and yb > ya def overlaps(self, xa, ya, xb, yb): # Args must be sorted: xb > xa and yb > ya
x0 = self.col x0 = self.col
y0 = self.row y0 = self.row
x1 = x0 + self.width x1 = x0 + self.width
@ -733,20 +785,32 @@ class Widget:
# def do_up(self, button) # def do_up(self, button)
# def do_down(self, button) # def do_down(self, button)
# A LinearIO widget uses the up and down buttons to vary a float. Such widgets # A LinearIO widget uses the up and down buttons to vary a float. Such widgets
# have do_up and do_down methods which adjust the control's value in a # have do_up and do_down methods which adjust the control's value in a
# time-dependent manner. # time-dependent manner.
class LinearIO(Widget): class LinearIO(Widget):
def __init__(
def __init__(self, writer, row, col, height, width, self,
fgcolor, bgcolor, bdcolor, writer,
value=None, active=True, prcolor=False, row,
min_delta=0.01, max_delta=0.1): col,
height,
width,
fgcolor,
bgcolor,
bdcolor,
value=None,
active=True,
prcolor=False,
min_delta=0.01,
max_delta=0.1,
):
self.min_delta = min_delta self.min_delta = min_delta
self.max_delta = max_delta self.max_delta = max_delta
super().__init__(writer, row, col, height, width, super().__init__(
fgcolor, bgcolor, bdcolor, writer, row, col, height, width, fgcolor, bgcolor, bdcolor, value, active
value, active) )
self.adjustable = True # Can show adjustable border self.adjustable = True # Can show adjustable border
self.do_precision = prcolor is not False self.do_precision = prcolor is not False
if self.do_precision: if self.do_precision:
@ -761,7 +825,9 @@ class LinearIO(Widget):
# Handle increase and decrease buttons. Redefined by textbox.py, scale_log.py # Handle increase and decrease buttons. Redefined by textbox.py, scale_log.py
async def btnhan(self, button, up, d): async def btnhan(self, button, up, d):
maxd = self.max_delta if self.precision() else d * 4 # Why move fast in precision mode? maxd = (
self.max_delta if self.precision() else d * 4
) # Why move fast in precision mode?
t = ticks_ms() t = ticks_ms()
while button(): while button():
await asyncio.sleep_ms(0) # Quit fast on button release await asyncio.sleep_ms(0) # Quit fast on button release
@ -774,11 +840,21 @@ class LinearIO(Widget):
def precision(self): def precision(self):
return self.do_precision and display.ipdev.is_precision() return self.do_precision and display.ipdev.is_precision()
# The dummy enables popup windows by satisfying the need for at least one active # The dummy enables popup windows by satisfying the need for at least one active
# widget on a screen. It is invisible and is drawn by Window constructor before # widget on a screen. It is invisible and is drawn by Window constructor before
# any user labels.. # any user labels..
class DummyWidget(Widget): class DummyWidget(Widget):
def __init__(self, writer, window): def __init__(self, writer, window):
super().__init__(writer, window.row + 1, window.col + 1, 4, 4, super().__init__(
window.fgcolor, window.bgcolor, False, None, True) writer,
window.row + 1,
window.col + 1,
4,
4,
window.fgcolor,
window.bgcolor,
False,
None,
True,
)

Wyświetl plik

@ -6,6 +6,7 @@ _attrs = {
"Delay_ms": "delay_ms", "Delay_ms": "delay_ms",
"Switch": "switch", "Switch": "switch",
"Pushbutton": "pushbutton", "Pushbutton": "pushbutton",
"Touchbutton": "touchbutton",
} }
# Lazy loader, effectively does: # Lazy loader, effectively does: