micropython-micro-gui/gui/widgets/listbox.py

113 wiersze
4.3 KiB
Python
Czysty Zwykły widok Historia

2021-06-09 16:11:48 +00:00
# listbox.py Extension to ugui providing the Listbox class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
from gui.core.ugui import Widget, display
from gui.core.colors import *
dolittle = lambda *_ : None
# Behaviour has issues compared to touch displays because movement between
# entries is sequential. This can affect the choice in when the callback runs.
# It always runs when select is pressed. See 'also' ctor arg.
class Listbox(Widget):
ON_MOVE = 1 # Also run whenever the currency moves.
ON_LEAVE = 2 # Also run on exit from the control.
2021-06-09 16:11:48 +00:00
@staticmethod
def dimensions(writer, elements):
entry_height = writer.height + 2 # Allow a pixel above and below text
le = len(elements)
height = entry_height * le + 2
textwidth = max(writer.stringlen(s) for s in elements) + 4
return entry_height, height, textwidth
def __init__(self, writer, row, col, *, elements, width=None, value=0,
fgcolor=None, bgcolor=None, bdcolor=False, fontcolor=None, select_color=DARKBLUE,
callback=dolittle, args=[], also=0):
e0 = elements[0]
# Check whether elements specified as (str, str,...) or ([str, callback, args], [...)
if isinstance(e0, tuple) or isinstance(e0, list):
self.els = elements # Retain original for .despatch
self.elements = [x[0] for x in elements] # Copy text component
if callback is not dolittle:
raise ValueError('Cannot specify callback.')
self.cb = self.despatch
else:
self.cb = callback
self.elements = elements
if any(not isinstance(s, str) for s in self.elements):
raise ValueError('Invalid elements arg.')
self.entry_height, height, textwidth = self.dimensions(writer, self.elements)
2021-06-09 16:11:48 +00:00
self.also = also
if width is None:
width = textwidth
if not isinstance(value, int) or value >= len(elements):
value = 0
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, value, True)
self.cb_args = args
self.select_color = select_color
self.fontcolor = fontcolor
self._value = value # No callback until user selects
self.ev = value
def show(self):
if not super().show(False): # Clear to self.bgcolor
return
length = len(self.elements)
x = self.col
y = self.row
for n in range(length):
if n == self._value:
display.fill_rect(x, y + 1, self.width, self.entry_height - 1, self.select_color)
2021-06-11 07:51:22 +00:00
display.print_left(self.writer, x + 2, y + 1, self.elements[n], self.fontcolor, self.select_color)
2021-06-09 16:11:48 +00:00
else:
2021-06-11 07:51:22 +00:00
display.print_left(self.writer, x + 2, y + 1, self.elements[n], self.fontcolor, self.bgcolor)
2021-06-09 16:11:48 +00:00
y += self.entry_height
def textvalue(self, text=None): # if no arg return current text
if text is None:
return self.elements[self._value]
else: # set value by text
try:
v = self.elements.index(text)
except ValueError:
v = None
else:
if v != self._value:
self.value(v)
return v
def do_adj(self, _, val):
v = self._value
if val > 0:
if v:
self.value(v - 1)
elif val < 0:
if v < len(self.elements) - 1:
self.value(v + 1)
if (self.also & Listbox.ON_MOVE): # Treat as if select pressed
2021-06-09 16:11:48 +00:00
self.do_sel()
# Callback runs if select is pressed. Also (if ON_LEAVE) if user changes
# list currency and then moves off the control. Otherwise if we have a
# callback that refreshes another control, that second control does not
# track currency.
def do_sel(self): # Select was pushed
self.ev = self._value
self.cb(self, *self.cb_args)
def enter(self):
self.ev = self._value # Value change detection
def leave(self):
if (self.also & Listbox.ON_LEAVE) and self._value != self.ev:
2021-06-09 16:11:48 +00:00
self.do_sel()
def despatch(self, _): # Run the callback specified in elements
x = self.els[self()]
x[1](self, *x[2])