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.
|
mode.
|
||||||
* `linked_sliders.py` One `Slider` updating two others, and a coding "wrinkle"
|
* `linked_sliders.py` One `Slider` updating two others, and a coding "wrinkle"
|
||||||
required for doing this.
|
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.
|
* `dialog.py` `DialogBox` demo. Illustrates the screen change mechanism.
|
||||||
* `screen_change.py` A `Pushbutton` causing a screen change using a re-usable
|
* `screen_change.py` A `Pushbutton` causing a screen change using a re-usable
|
||||||
"forward" button.
|
"forward" button.
|
||||||
|
@ -1251,10 +1252,11 @@ from gui.widgets.listbox import Listbox
|
||||||
A `listbox` with the second item highlighted. Pressing the physical `select`
|
A `listbox` with the second item highlighted. Pressing the physical `select`
|
||||||
button will cause the callback to run.
|
button will cause the callback to run.
|
||||||
|
|
||||||
A `Listbox` is an active widget. Its height is determined by the number of
|
A `Listbox` is an active widget. By default its height is determined by the
|
||||||
entries in it and the font in use. Scrolling is not supported. When the widget
|
number of entries in it and the font in use. It may be reduced by specifying
|
||||||
has focus the currently selected element may be changed using `increase` and
|
`dlines` in which case scrolling will occur. When the widget has focus the
|
||||||
`decrease` buttons. On pressing `select` a callback runs.
|
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:
|
Constructor mandatory positional args:
|
||||||
1. `writer` The `Writer` instance (defines font) to use.
|
1. `writer` The `Writer` instance (defines font) to use.
|
||||||
|
@ -1267,9 +1269,16 @@ Mandatory keyword only argument:
|
||||||
list to have a separate callback.
|
list to have a separate callback.
|
||||||
|
|
||||||
Optional keyword only arguments:
|
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
|
* `width=None` Control width in pixels. By default this is calculated to
|
||||||
accommodate all elements.
|
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
|
* `fgcolor=None` Color of foreground (the control itself). If `None` the
|
||||||
`Writer` foreground default is used.
|
`Writer` foreground default is used.
|
||||||
* `bgcolor=None` Background color of object. If `None` the `Writer` background
|
* `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.
|
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
|
This can be changed by specifying `elements` such that each element comprises a
|
||||||
3-list or 3-tuple with the following contents:
|
3-list or 3-tuple with the following contents:
|
||||||
0. String to display.
|
1. String to display.
|
||||||
1. Callback.
|
2. Callback.
|
||||||
2. Tuple of args (may be ()).
|
3. Tuple of args (may be `()`).
|
||||||
|
|
||||||
In this case constructor args `callback` and `args` must not be supplied. Args
|
In this case constructor args `callback` and `args` must not be supplied. Args
|
||||||
received by the callback functions comprise the `Listbox` instance followed by
|
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.
|
Open dropdown list. When closed, hidden items below are refreshed.
|
||||||
|
|
||||||
A dropdown list. The list, when active, is drawn below the control. The height
|
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. The height of
|
of the control is determined by the height of the font in use. By default the
|
||||||
the list is determined by the number of entries in it and the font in use.
|
height of the list is determined by the number of entries in it and the font in
|
||||||
Scrolling is not supported. The dropdown should be placed high enough on the
|
use. It may be reduced by specifying `dlines` in which case scrolling will
|
||||||
screen to ensure that the list can be displayed
|
occur. The dropdown should be placed high enough on the screen to ensure that
|
||||||
|
the list can be displayed.
|
||||||
|
|
||||||
Constructor mandatory positional args:
|
Constructor mandatory positional args:
|
||||||
1. `writer` The `Writer` instance (defines font) to use.
|
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.
|
each item on the dropdown list to have a separate callback.
|
||||||
|
|
||||||
Optional keyword only arguments:
|
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
|
* `width=None` Control width in pixels. By default this is calculated to
|
||||||
accommodate all elements.
|
accommodate all elements.
|
||||||
* `value=0` Index of currently selected list item.
|
* `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
|
By default the `Dropdown` runs a single callback regardless of the element
|
||||||
chosen. This can be changed by specifying `elements` such that each element
|
chosen. This can be changed by specifying `elements` such that each element
|
||||||
comprises a 3-list or 3-tuple with the following contents:
|
comprises a 3-list or 3-tuple with the following contents:
|
||||||
0. String to display.
|
1. String to display.
|
||||||
1. Callback.
|
2. Callback.
|
||||||
2. Tuple of args (may be ()).
|
3. Tuple of args (may be `()`).
|
||||||
|
|
||||||
In this case constructor args `callback` and `args` must not be supplied. Args
|
In this case constructor args `callback` and `args` must not be supplied. Args
|
||||||
received by the callback functions comprise the `Dropdown` instance followed by
|
received by the callback functions comprise the `Dropdown` instance followed by
|
||||||
|
|
|
@ -26,9 +26,11 @@ class BaseScreen(Screen):
|
||||||
|
|
||||||
col = 2
|
col = 2
|
||||||
row = 2
|
row = 2
|
||||||
|
els = ('hydrogen', 'helium', 'neon', 'argon', 'krypton', 'xenon', 'radon')
|
||||||
self.dd = Dropdown(wri, row, col,
|
self.dd = Dropdown(wri, row, col,
|
||||||
elements = ('hydrogen', 'helium', 'neon', 'xenon', 'radon'),
|
elements = els,
|
||||||
bdcolor = GREEN, bgcolor = DARKGREEN,
|
dlines = 5, # Show 5 lines
|
||||||
|
bdcolor = GREEN,
|
||||||
callback=self.ddcb)
|
callback=self.ddcb)
|
||||||
row += 30
|
row += 30
|
||||||
self.lbl = Label(wri, row, col, self.dd.width, bdcolor=RED)
|
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.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2021 Peter Hinch
|
# Copyright (c) 2021 Peter Hinch
|
||||||
|
|
||||||
|
# 12 Sep 21 Support for scrolling.
|
||||||
|
|
||||||
from gui.core.ugui import Widget, display, Window, Screen
|
from gui.core.ugui import Widget, display, Window, Screen
|
||||||
from gui.core.colors import *
|
from gui.core.colors import *
|
||||||
|
|
||||||
|
@ -17,15 +19,18 @@ class _ListDialog(Window):
|
||||||
def __init__(self, writer, row, col, dd): # dd is parent dropdown
|
def __init__(self, writer, row, col, dd): # dd is parent dropdown
|
||||||
# Need to determine Window dimensions from size of Listbox, which
|
# Need to determine Window dimensions from size of Listbox, which
|
||||||
# depends on number and length of elements.
|
# depends on number and length of elements.
|
||||||
entry_height, lb_height, textwidth = Listbox.dimensions(writer, dd.elements)
|
_, lb_height, dlines, tw = Listbox.dimensions(writer, dd.elements, dd.dlines)
|
||||||
lb_width = textwidth + 2
|
lb_width = tw + 2 # Text width + 2
|
||||||
# Calculate Window dimensions
|
# Calculate Window dimensions
|
||||||
ap_height = lb_height + 6 # Allow for listbox border
|
ap_height = lb_height + 6 # Allow for listbox border
|
||||||
ap_width = lb_width + 6
|
ap_width = lb_width + 6
|
||||||
super().__init__(row, col, ap_height, ap_width)
|
super().__init__(row, col, ap_height, ap_width)
|
||||||
self.listbox = Listbox(writer, row + 3, col + 3, elements = dd.elements, width = lb_width,
|
self.listbox = Listbox(writer, row + 3, col + 3,
|
||||||
fgcolor = dd.fgcolor, bgcolor = dd.bgcolor, bdcolor=False,
|
elements = dd.elements,
|
||||||
fontcolor = dd.fontcolor, select_color = dd.select_color,
|
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)
|
value = dd.value(), callback = self.callback)
|
||||||
self.dd = dd
|
self.dd = dd
|
||||||
|
|
||||||
|
@ -35,8 +40,11 @@ class _ListDialog(Window):
|
||||||
|
|
||||||
|
|
||||||
class Dropdown(Widget):
|
class Dropdown(Widget):
|
||||||
def __init__(self, writer, row, col, *, elements, width=None, value=0,
|
def __init__(self, writer, row, col, *,
|
||||||
fgcolor=None, bgcolor=None, bdcolor=False, fontcolor=None, select_color=DARKBLUE,
|
elements,
|
||||||
|
dlines=None, width=None, value=0,
|
||||||
|
fgcolor=None, bgcolor=None, bdcolor=False,
|
||||||
|
fontcolor=None, select_color=DARKBLUE,
|
||||||
callback=dolittle, args=[]):
|
callback=dolittle, args=[]):
|
||||||
|
|
||||||
self.entry_height = writer.height + 2 # Allow a pixel above and below text
|
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.select_color = select_color
|
||||||
self.fontcolor = self.fgcolor if fontcolor is None else fontcolor
|
self.fontcolor = self.fgcolor if fontcolor is None else fontcolor
|
||||||
self.elements = elements
|
self.elements = elements
|
||||||
|
self.dlines = dlines
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
if super().show():
|
if super().show():
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
# Released under the MIT License (MIT). See LICENSE.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2021 Peter Hinch
|
# Copyright (c) 2021 Peter Hinch
|
||||||
|
|
||||||
|
# 12 Sep 21 Support for scrolling.
|
||||||
|
|
||||||
from gui.core.ugui import Widget, display
|
from gui.core.ugui import Widget, display
|
||||||
from gui.core.colors import *
|
from gui.core.colors import *
|
||||||
|
|
||||||
|
@ -15,16 +18,23 @@ class Listbox(Widget):
|
||||||
ON_MOVE = 1 # Also run whenever the currency moves.
|
ON_MOVE = 1 # Also run whenever the currency moves.
|
||||||
ON_LEAVE = 2 # Also run on exit from the control.
|
ON_LEAVE = 2 # Also run on exit from the control.
|
||||||
|
|
||||||
|
# This is used by dropdown.py
|
||||||
@staticmethod
|
@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
|
entry_height = writer.height + 2 # Allow a pixel above and below text
|
||||||
le = len(elements)
|
# Number of displayable lines
|
||||||
height = entry_height * le + 2
|
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
|
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,
|
def __init__(self, writer, row, col, *,
|
||||||
fgcolor=None, bgcolor=None, bdcolor=False, fontcolor=None, select_color=DARKBLUE,
|
elements,
|
||||||
|
dlines=None, width=None, value=0,
|
||||||
|
fgcolor=None, bgcolor=None, bdcolor=False,
|
||||||
|
fontcolor=None, select_color=DARKBLUE,
|
||||||
callback=dolittle, args=[], also=0):
|
callback=dolittle, args=[], also=0):
|
||||||
|
|
||||||
e0 = elements[0]
|
e0 = elements[0]
|
||||||
|
@ -40,33 +50,49 @@ class Listbox(Widget):
|
||||||
self.elements = elements
|
self.elements = elements
|
||||||
if any(not isinstance(s, str) for s in self.elements):
|
if any(not isinstance(s, str) for s in self.elements):
|
||||||
raise ValueError('Invalid elements arg.')
|
raise ValueError('Invalid elements arg.')
|
||||||
self.entry_height, height, textwidth = self.dimensions(writer, self.elements)
|
# Calculate dimensions
|
||||||
self.also = also
|
self.entry_height, height, self.dlines, tw = self.dimensions(
|
||||||
|
writer, self.elements, dlines)
|
||||||
if width is None:
|
if width is None:
|
||||||
width = textwidth
|
width = tw # Text width
|
||||||
if not isinstance(value, int) or value >= len(elements):
|
|
||||||
value = 0
|
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)
|
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, value, True)
|
||||||
self.cb_args = args
|
self.cb_args = args
|
||||||
self.select_color = select_color
|
self.select_color = select_color
|
||||||
self.fontcolor = fontcolor
|
self.fontcolor = fontcolor
|
||||||
self._value = value # No callback until user selects
|
self._value = value # No callback until user selects
|
||||||
self.ev = value
|
self.ev = value # Value change detection
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
if not super().show(False): # Clear to self.bgcolor
|
if not super().show(False): # Clear to self.bgcolor
|
||||||
return
|
return
|
||||||
|
|
||||||
length = len(self.elements)
|
|
||||||
x = self.col
|
x = self.col
|
||||||
y = self.row
|
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:
|
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)
|
display.print_left(self.writer, x + 2, y + 1, self.elements[n], self.fontcolor, self.select_color)
|
||||||
else:
|
else:
|
||||||
display.print_left(self.writer, x + 2, y + 1, self.elements[n], self.fontcolor, self.bgcolor)
|
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
|
def textvalue(self, text=None): # if no arg return current text
|
||||||
if text is None:
|
if text is None:
|
||||||
|
@ -81,16 +107,24 @@ class Listbox(Widget):
|
||||||
self.value(v)
|
self.value(v)
|
||||||
return 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):
|
def do_adj(self, _, val):
|
||||||
v = self._value
|
v = self._value
|
||||||
if val > 0:
|
if val > 0:
|
||||||
if v:
|
if v:
|
||||||
self.value(v - 1)
|
self._vchange(v -1)
|
||||||
elif val < 0:
|
elif val < 0:
|
||||||
if v < len(self.elements) - 1:
|
if v < len(self.elements) - 1:
|
||||||
self.value(v + 1)
|
self._vchange(v + 1)
|
||||||
if (self.also & Listbox.ON_MOVE): # Treat as if select pressed
|
|
||||||
self.do_sel()
|
|
||||||
|
|
||||||
# Callback runs if select is pressed. Also (if ON_LEAVE) if user changes
|
# 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
|
# 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
|
prev = Pin(18, Pin.IN, Pin.PULL_UP) # Move to previous control
|
||||||
increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value
|
increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value
|
||||||
decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease 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