Support scrolling in Listbox and Dropdown widgets.

pull/8/head
Peter Hinch 2021-09-12 13:52:57 +01:00
rodzic 6ff7206e72
commit 9761fbe50c
6 zmienionych plików z 152 dodań i 49 usunięć

Wyświetl plik

@ -504,7 +504,8 @@ minimal and aim to demonstrate a single technique.
mode.
* `linked_sliders.py` One `Slider` updating two others, and a coding "wrinkle"
required for doing this.
* `dropdown.py` A dropdown list updates a `Label`.
* `dropdown.py` A dropdown list (with scrolling) updates a `Label`.
* `listbox.py` A listbox with scrolling.
* `dialog.py` `DialogBox` demo. Illustrates the screen change mechanism.
* `screen_change.py` A `Pushbutton` causing a screen change using a re-usable
"forward" button.
@ -1251,10 +1252,11 @@ from gui.widgets.listbox import Listbox
A `listbox` with the second item highlighted. Pressing the physical `select`
button will cause the callback to run.
A `Listbox` is an active widget. Its height is determined by the number of
entries in it and the font in use. Scrolling is not supported. When the widget
has focus the currently selected element may be changed using `increase` and
`decrease` buttons. On pressing `select` a callback runs.
A `Listbox` is an active widget. By default its height is determined by the
number of entries in it and the font in use. It may be reduced by specifying
`dlines` in which case scrolling will occur. When the widget has focus the
currently selected element may be changed using `increase` and `decrease`
buttons or by turning the encoder. On pressing `select` a callback runs.
Constructor mandatory positional args:
1. `writer` The `Writer` instance (defines font) to use.
@ -1267,9 +1269,16 @@ Mandatory keyword only argument:
list to have a separate callback.
Optional keyword only arguments:
* `dlines=None` By default the height of the control is determined by the
number of elements. If an integer < number of elements is passed the list
will show that number of lines; its height will correspond. Scrolling will
occur to ensure that the current element is always visible. To indicate when
scrolling is possible, one or two vertical bars will appear to the right of
the list.
* `width=None` Control width in pixels. By default this is calculated to
accommodate all elements.
* `value=0` Index of currently selected list item.
* `value=0` Index of currently selected list item. If necessary the list will
scroll to ensure the item is visible.
* `fgcolor=None` Color of foreground (the control itself). If `None` the
`Writer` foreground default is used.
* `bgcolor=None` Background color of object. If `None` the `Writer` background
@ -1306,10 +1315,10 @@ means of the instance's `value` or `textvalue` methods.
By default the `Listbox` runs a common callback regardless of the item chosen.
This can be changed by specifying `elements` such that each element comprises a
3-list or 3-tuple with the following contents:
0. String to display.
1. Callback.
2. Tuple of args (may be ()).
3-list or 3-tuple with the following contents:
1. String to display.
2. Callback.
3. Tuple of args (may be `()`).
In this case constructor args `callback` and `args` must not be supplied. Args
received by the callback functions comprise the `Listbox` instance followed by
@ -1354,11 +1363,12 @@ Closed dropdown list.
Open dropdown list. When closed, hidden items below are refreshed.
A dropdown list. The list, when active, is drawn below the control. The height
of the control is determined by the height of the font in use. The height of
the list is determined by the number of entries in it and the font in use.
Scrolling is not supported. The dropdown should be placed high enough on the
screen to ensure that the list can be displayed
A dropdown list. The list, when active, is drawn over the control. The height
of the control is determined by the height of the font in use. By default the
height of the list is determined by the number of entries in it and the font in
use. It may be reduced by specifying `dlines` in which case scrolling will
occur. The dropdown should be placed high enough on the screen to ensure that
the list can be displayed.
Constructor mandatory positional args:
1. `writer` The `Writer` instance (defines font) to use.
@ -1371,6 +1381,12 @@ Mandatory keyword only argument:
each item on the dropdown list to have a separate callback.
Optional keyword only arguments:
* `dlines=None` By default the height of the dropdown list is determined by
the number of elements. If an integer < number of elements is passed the list
will show that number of lines; its height will correspond. Scrolling will
occur to ensure that the current element is always visible. To indicate when
scrolling is possible, one or two vertical bars will appear to the right of
the list.
* `width=None` Control width in pixels. By default this is calculated to
accommodate all elements.
* `value=0` Index of currently selected list item.
@ -1414,10 +1430,10 @@ means of the instance's `value` or `textvalue` methods.
By default the `Dropdown` runs a single callback regardless of the element
chosen. This can be changed by specifying `elements` such that each element
comprises a 3-list or 3-tuple with the following contents:
0. String to display.
1. Callback.
2. Tuple of args (may be ()).
comprises a 3-list or 3-tuple with the following contents:
1. String to display.
2. Callback.
3. Tuple of args (may be `()`).
In this case constructor args `callback` and `args` must not be supplied. Args
received by the callback functions comprise the `Dropdown` instance followed by

Wyświetl plik

@ -26,9 +26,11 @@ class BaseScreen(Screen):
col = 2
row = 2
els = ('hydrogen', 'helium', 'neon', 'argon', 'krypton', 'xenon', 'radon')
self.dd = Dropdown(wri, row, col,
elements = ('hydrogen', 'helium', 'neon', 'xenon', 'radon'),
bdcolor = GREEN, bgcolor = DARKGREEN,
elements = els,
dlines = 5, # Show 5 lines
bdcolor = GREEN,
callback=self.ddcb)
row += 30
self.lbl = Label(wri, row, col, self.dd.width, bdcolor=RED)

Wyświetl plik

@ -0,0 +1,42 @@
# listbox.py micro-gui demo of Listbox class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# hardware_setup must be imported before other modules because of RAM use.
from hardware_setup import ssd # Create a display instance
from gui.core.ugui import Screen
from gui.core.writer import CWriter
from gui.core.colors import *
from gui.widgets.listbox import Listbox
from gui.widgets.buttons import CloseButton
import gui.fonts.freesans20 as font
class BaseScreen(Screen):
def __init__(self):
def cb(lb, s):
print('Gas', s)
def cb_radon(lb, s): # Yeah, Radon is a gas too...
print('Radioactive', s)
super().__init__()
wri = CWriter(ssd, font, GREEN, BLACK, verbose=False)
els = (('Hydrogen', cb, ('H',)),
('Helium', cb, ('He',)),
('Neon', cb, ('Ne',)),
('Xenon', cb, ('Xe',)),
('Radon', cb_radon, ('Ra',)),
('Uranium', cb_radon, ('U',)),
('Plutonium', cb_radon, ('Pu',)),
('Actinium', cb_radon, ('Ac',)),
)
Listbox(wri, 2, 2,
elements = els, dlines=5, bdcolor=RED, value=1, also=Listbox.ON_LEAVE)
#bdcolor = RED, fgcolor=RED, fontcolor = YELLOW, select_color=BLUE, value=1)
CloseButton(wri)
Screen.change(BaseScreen)

Wyświetl plik

@ -3,6 +3,8 @@
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# 12 Sep 21 Support for scrolling.
from gui.core.ugui import Widget, display, Window, Screen
from gui.core.colors import *
@ -17,15 +19,18 @@ class _ListDialog(Window):
def __init__(self, writer, row, col, dd): # dd is parent dropdown
# Need to determine Window dimensions from size of Listbox, which
# depends on number and length of elements.
entry_height, lb_height, textwidth = Listbox.dimensions(writer, dd.elements)
lb_width = textwidth + 2
_, lb_height, dlines, tw = Listbox.dimensions(writer, dd.elements, dd.dlines)
lb_width = tw + 2 # Text width + 2
# Calculate Window dimensions
ap_height = lb_height + 6 # Allow for listbox border
ap_width = lb_width + 6
super().__init__(row, col, ap_height, ap_width)
self.listbox = Listbox(writer, row + 3, col + 3, elements = dd.elements, width = lb_width,
fgcolor = dd.fgcolor, bgcolor = dd.bgcolor, bdcolor=False,
fontcolor = dd.fontcolor, select_color = dd.select_color,
self.listbox = Listbox(writer, row + 3, col + 3,
elements = dd.elements,
dlines = dlines, width = lb_width,
fgcolor = dd.fgcolor, bgcolor = dd.bgcolor,
bdcolor=False, fontcolor = dd.fontcolor,
select_color = dd.select_color,
value = dd.value(), callback = self.callback)
self.dd = dd
@ -35,8 +40,11 @@ class _ListDialog(Window):
class Dropdown(Widget):
def __init__(self, writer, row, col, *, elements, width=None, value=0,
fgcolor=None, bgcolor=None, bdcolor=False, fontcolor=None, select_color=DARKBLUE,
def __init__(self, writer, row, col, *,
elements,
dlines=None, width=None, value=0,
fgcolor=None, bgcolor=None, bdcolor=False,
fontcolor=None, select_color=DARKBLUE,
callback=dolittle, args=[]):
self.entry_height = writer.height + 2 # Allow a pixel above and below text
@ -60,6 +68,7 @@ class Dropdown(Widget):
self.select_color = select_color
self.fontcolor = self.fgcolor if fontcolor is None else fontcolor
self.elements = elements
self.dlines = dlines
def show(self):
if super().show():

Wyświetl plik

@ -2,6 +2,9 @@
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# 12 Sep 21 Support for scrolling.
from gui.core.ugui import Widget, display
from gui.core.colors import *
@ -15,16 +18,23 @@ class Listbox(Widget):
ON_MOVE = 1 # Also run whenever the currency moves.
ON_LEAVE = 2 # Also run on exit from the control.
# This is used by dropdown.py
@staticmethod
def dimensions(writer, elements):
def dimensions(writer, elements, dlines):
# Height of a single entry in list.
entry_height = writer.height + 2 # Allow a pixel above and below text
le = len(elements)
height = entry_height * le + 2
# Number of displayable lines
dlines = len(elements) if dlines is None else dlines
# Height of control
height = entry_height * dlines + 2
textwidth = max(writer.stringlen(s) for s in elements) + 4
return entry_height, height, textwidth
return entry_height, height, dlines, textwidth
def __init__(self, writer, row, col, *, elements, width=None, value=0,
fgcolor=None, bgcolor=None, bdcolor=False, fontcolor=None, select_color=DARKBLUE,
def __init__(self, writer, row, col, *,
elements,
dlines=None, width=None, value=0,
fgcolor=None, bgcolor=None, bdcolor=False,
fontcolor=None, select_color=DARKBLUE,
callback=dolittle, args=[], also=0):
e0 = elements[0]
@ -40,33 +50,49 @@ class Listbox(Widget):
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)
self.also = also
# Calculate dimensions
self.entry_height, height, self.dlines, tw = self.dimensions(
writer, self.elements, dlines)
if width is None:
width = textwidth
if not isinstance(value, int) or value >= len(elements):
value = 0
width = tw # Text width
self.also = also
self.ntop = 0 # Top visible line
if not isinstance(value, int):
value = 0 # Or ValueError?
elif value >= self.dlines: # Must scroll
value = min(value, len(elements) - 1)
self.ntop = value - self.dlines + 1
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
self.ev = value # Value change detection
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):
eh = self.entry_height
ntop = self.ntop
dlines = self.dlines
for n in range(ntop, ntop + dlines):
if n == self._value:
display.fill_rect(x, y + 1, self.width, self.entry_height - 1, self.select_color)
display.fill_rect(x, y + 1, self.width, eh - 1, self.select_color)
display.print_left(self.writer, x + 2, y + 1, self.elements[n], self.fontcolor, self.select_color)
else:
display.print_left(self.writer, x + 2, y + 1, self.elements[n], self.fontcolor, self.bgcolor)
y += self.entry_height
y += eh
# Draw a vertical line to hint at scrolling
x = self.col + self.width - 2
if ntop:
display.vline(x, self.row, eh - 1, self.fgcolor)
if ntop + dlines < len(self.elements):
y = self.row + (dlines - 1) * eh
display.vline(x, y, eh - 1, self.fgcolor)
def textvalue(self, text=None): # if no arg return current text
if text is None:
@ -81,16 +107,24 @@ class Listbox(Widget):
self.value(v)
return v
def _vchange(self, vnew): # A value change is taking place
# Handle scrolling
if vnew >= self.ntop + self.dlines:
self.ntop = vnew - self.dlines + 1
elif vnew < self.ntop:
self.ntop = vnew
self.value(vnew)
if (self.also & Listbox.ON_MOVE): # Treat as if select pressed
self.do_sel()
def do_adj(self, _, val):
v = self._value
if val > 0:
if v:
self.value(v - 1)
self._vchange(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
self.do_sel()
self._vchange(v + 1)
# 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

Wyświetl plik

@ -51,4 +51,4 @@ sel = Pin(16, Pin.IN, Pin.PULL_UP) # Operate current control
prev = Pin(18, Pin.IN, Pin.PULL_UP) # Move to previous control
increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value
decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease control's value
display = Display(ssd, nxt, sel, prev, increase, decrease, 5)
display = Display(ssd, nxt, sel, prev, increase, decrease)