kopia lustrzana https://github.com/peterhinch/micropython-micro-gui
Support scrolling in Listbox and Dropdown widgets.
rodzic
6ff7206e72
commit
9761fbe50c
54
README.md
54
README.md
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Ładowanie…
Reference in New Issue