kopia lustrzana https://github.com/peterhinch/micropython-micro-gui
Listbox and Dropdown support dynamic element lists.
rodzic
1ac1fd0976
commit
2f9c0ee474
|
@ -65,7 +65,7 @@ target and a C device driver (unless you can acquire a suitable binary).
|
||||||
|
|
||||||
# Project status
|
# Project status
|
||||||
|
|
||||||
Sept 2024: Dropdown and Listbox support dynamically variable lists of elements.
|
Sept 2024: Dropdown and Listbox widgets support dynamically variable lists of elements.
|
||||||
April 2024: Add screen replace feature for non-tree navigation.
|
April 2024: Add screen replace feature for non-tree navigation.
|
||||||
Sept 2023: Add "encoder only" mode suggested by @eudoxos.
|
Sept 2023: Add "encoder only" mode suggested by @eudoxos.
|
||||||
April 2023: Add limited ePaper support, grid widget, calendar and epaper demos.
|
April 2023: Add limited ePaper support, grid widget, calendar and epaper demos.
|
||||||
|
@ -689,6 +689,7 @@ Some of these require larger screens. Required sizes are specified as
|
||||||
* `calendar.py` Demo of grid control (240x320 - but could be reduced).
|
* `calendar.py` Demo of grid control (240x320 - but could be reduced).
|
||||||
* `listbox_var.py` Listbox with dynamically variable elements.
|
* `listbox_var.py` Listbox with dynamically variable elements.
|
||||||
* `dropdown_var.py` Dropdown with dynamically variable elements.
|
* `dropdown_var.py` Dropdown with dynamically variable elements.
|
||||||
|
* `dropdown_var_tuple.py ` Dropdown with dynamically variable tuple elements.
|
||||||
|
|
||||||
###### [Contents](./README.md#0-contents)
|
###### [Contents](./README.md#0-contents)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
# dropdown_var_tuple.py micro-gui demo of Dropdown widget with changeable elements
|
||||||
|
|
||||||
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
|
# Copyright (c) 2024 Peter Hinch
|
||||||
|
|
||||||
|
# hardware_setup must be imported before other modules because of RAM use.
|
||||||
|
import hardware_setup # Create a display instance
|
||||||
|
from gui.core.ugui import Screen, Window, ssd
|
||||||
|
|
||||||
|
from gui.widgets import Label, Button, CloseButton, Dropdown
|
||||||
|
from gui.core.writer import CWriter
|
||||||
|
|
||||||
|
# Font for CWriter
|
||||||
|
import gui.fonts.font10 as font
|
||||||
|
from gui.core.colors import *
|
||||||
|
|
||||||
|
|
||||||
|
class BaseScreen(Screen):
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
super().__init__()
|
||||||
|
wri = CWriter(ssd, font, GREEN, BLACK, verbose=False)
|
||||||
|
|
||||||
|
# Create new dropdown entries
|
||||||
|
tuples = (
|
||||||
|
("Iron", self.mcb, ("Fe",)),
|
||||||
|
("Copper", self.mcb, ("Cu",)),
|
||||||
|
("Lead", self.mcb, ("Pb",)),
|
||||||
|
("Zinc", self.mcb, ("Zn",)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def newtup():
|
||||||
|
n = 0
|
||||||
|
while True:
|
||||||
|
yield tuples[n]
|
||||||
|
n = (n + 1) % len(tuples)
|
||||||
|
|
||||||
|
self.ntup = newtup() # Instantiate the generator
|
||||||
|
|
||||||
|
col = 2
|
||||||
|
row = 2
|
||||||
|
self.els = [
|
||||||
|
("Hydrogen", self.cb, ("H",)),
|
||||||
|
("Helium", self.cb, ("He",)),
|
||||||
|
("Neon", self.cb, ("Ne",)),
|
||||||
|
("Xenon", self.cb, ("Xe",)),
|
||||||
|
("Radon", self.cb_radon, ("Ra",)),
|
||||||
|
("Uranium", self.cb_radon, ("U",)),
|
||||||
|
("Plutonium", self.cb_radon, ("Pu",)),
|
||||||
|
("Actinium", self.cb_radon, ("Ac",)),
|
||||||
|
]
|
||||||
|
self.dd = Dropdown(
|
||||||
|
wri,
|
||||||
|
row,
|
||||||
|
col,
|
||||||
|
elements=self.els,
|
||||||
|
dlines=5, # Show 5 lines
|
||||||
|
bdcolor=GREEN,
|
||||||
|
)
|
||||||
|
row += 30
|
||||||
|
self.lbl = Label(wri, row, col, self.dd.width, bdcolor=RED)
|
||||||
|
b = Button(wri, 2, 120, text="del", callback=self.delcb)
|
||||||
|
b = Button(wri, b.mrow + 2, 120, text="add", callback=self.addcb)
|
||||||
|
b = Button(wri, b.mrow + 10, 120, text="h2", callback=self.gocb, args=("Hydrogen",))
|
||||||
|
b = Button(wri, b.mrow + 2, 120, text="fe", callback=self.gocb, args=("Iron",))
|
||||||
|
CloseButton(wri)
|
||||||
|
|
||||||
|
def gocb(self, _, txt): # Go button callback: Move currency to specified entry
|
||||||
|
self.dd.textvalue(txt)
|
||||||
|
|
||||||
|
def addcb(self, _): # Add button callback
|
||||||
|
self.els.append(next(self.ntup)) # Append a new entry
|
||||||
|
self.dd.update()
|
||||||
|
|
||||||
|
def delcb(self, _): # Delete button callback
|
||||||
|
del self.els[self.dd.value()] # Delete current entry
|
||||||
|
self.dd.update()
|
||||||
|
self.lbl.value(self.dd.textvalue())
|
||||||
|
|
||||||
|
def cb(self, dd, s):
|
||||||
|
self.lbl.value(self.dd.textvalue())
|
||||||
|
print("Gas", s)
|
||||||
|
|
||||||
|
def mcb(self, dd, s):
|
||||||
|
self.lbl.value(self.dd.textvalue())
|
||||||
|
print("Metal", s)
|
||||||
|
|
||||||
|
def cb_radon(self, dd, s): # Yeah, Radon is a gas too...
|
||||||
|
self.lbl.value(self.dd.textvalue())
|
||||||
|
print("Radioactive", s)
|
||||||
|
|
||||||
|
def after_open(self):
|
||||||
|
self.lbl.value(self.dd.textvalue())
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
print("Dropdown demo.")
|
||||||
|
Screen.change(BaseScreen)
|
||||||
|
|
||||||
|
|
||||||
|
test()
|
|
@ -1,8 +1,9 @@
|
||||||
# dropdown.py Extension to ugui providing the Dropdown class
|
# dropdown.py Extension to ugui providing the Dropdown class
|
||||||
|
|
||||||
# Released under the MIT License (MIT). See LICENSE.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2021 Peter Hinch
|
# Copyright (c) 2021-2024 Peter Hinch
|
||||||
|
|
||||||
|
# 13 Sep 24 Support dynamic elements list.
|
||||||
# 12 Sep 21 Support for scrolling.
|
# 12 Sep 21 Support for scrolling.
|
||||||
|
|
||||||
from gui.core.ugui import Widget, display, Window, Screen
|
from gui.core.ugui import Widget, display, Window, Screen
|
||||||
|
@ -14,11 +15,14 @@ dolittle = lambda *_: None
|
||||||
|
|
||||||
# Next and Prev close the listbox without updating the Dropdown. This is
|
# Next and Prev close the listbox without updating the Dropdown. This is
|
||||||
# handled by Screen .move bound method
|
# handled by Screen .move bound method
|
||||||
|
# Callback where elements are tuples: the listbox calls its local _ListDialog callback.
|
||||||
|
# This changes the DialogBox value, so its callback runs. This is the ._despatch method
|
||||||
|
# which runs the callback of the currently selected element.
|
||||||
class _ListDialog(Window):
|
class _ListDialog(Window):
|
||||||
def __init__(self, writer, row, col, dd): # dd is parent dropdown
|
def __init__(self, writer, row, col, dd, dlines, els): # 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.
|
||||||
_, lb_height, dlines, tw = Listbox.dimensions(writer, dd.elements, dd.dlines)
|
_, lb_height, dlines, tw = Listbox.dimensions(writer, els, dlines)
|
||||||
lb_width = tw + 2 # Text width + 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
|
||||||
|
@ -28,7 +32,7 @@ class _ListDialog(Window):
|
||||||
writer,
|
writer,
|
||||||
row + 3,
|
row + 3,
|
||||||
col + 3,
|
col + 3,
|
||||||
elements=dd.elements,
|
elements=els,
|
||||||
dlines=dlines,
|
dlines=dlines,
|
||||||
width=lb_width,
|
width=lb_width,
|
||||||
fgcolor=dd.fgcolor,
|
fgcolor=dd.fgcolor,
|
||||||
|
@ -38,6 +42,7 @@ class _ListDialog(Window):
|
||||||
select_color=dd.select_color,
|
select_color=dd.select_color,
|
||||||
value=dd.value(),
|
value=dd.value(),
|
||||||
callback=self.callback,
|
callback=self.callback,
|
||||||
|
also=Listbox.NOCB, # Force passed callback even if elements are tuples
|
||||||
)
|
)
|
||||||
self.dd = dd
|
self.dd = dd
|
||||||
|
|
||||||
|
@ -70,15 +75,16 @@ class Dropdown(Widget):
|
||||||
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
|
||||||
height = self.entry_height
|
height = self.entry_height
|
||||||
self.select_color = select_color
|
self.select_color = select_color
|
||||||
self.dlines = dlines # Referenced by _ListDialog
|
self.dlines = dlines # Passed to _ListDialog
|
||||||
|
|
||||||
# Check whether elements specified as (str, str,...) or ([str, callback, args], [...)
|
# Check whether elements specified as (str, str,...) or ([str, callback, args], [...)
|
||||||
self.simple = isinstance(elements[0], str)
|
self.simple = isinstance(elements[0], str)
|
||||||
self.els = elements # Retain original
|
self.els = elements # Retain original
|
||||||
# Listbox works with text component only because it has a single callback.
|
|
||||||
self.elements = elements if self.simple else [x[0] for x in elements]
|
|
||||||
if width is None: # Allow for square at end for arrow
|
if width is None: # Allow for square at end for arrow
|
||||||
self.textwidth = max(writer.stringlen(s) for s in elements)
|
if self.simple:
|
||||||
|
self.textwidth = max(writer.stringlen(s) for s in elements)
|
||||||
|
else:
|
||||||
|
self.textwidth = max(writer.stringlen(s[0]) for s in elements)
|
||||||
width = self.textwidth + 2 + height
|
width = self.textwidth + 2 + height
|
||||||
else:
|
else:
|
||||||
self.textwidth = width
|
self.textwidth = width
|
||||||
|
@ -91,7 +97,6 @@ class Dropdown(Widget):
|
||||||
super()._set_callbacks(callback, args) # Callback runs on value change
|
super()._set_callbacks(callback, args) # Callback runs on value change
|
||||||
|
|
||||||
def update(self): # Elements list has changed. Extract text component for dropdown.
|
def update(self): # Elements list has changed. Extract text component for dropdown.
|
||||||
self.elements = self.els if self.simple else [x[0] for x in self.els]
|
|
||||||
# Ensure sensible _value if list size is reduced.
|
# Ensure sensible _value if list size is reduced.
|
||||||
self._value = min(self._value, len(self.els) - 1)
|
self._value = min(self._value, len(self.els) - 1)
|
||||||
self.show()
|
self.show()
|
||||||
|
@ -101,15 +106,23 @@ class Dropdown(Widget):
|
||||||
return
|
return
|
||||||
self._draw(x := self.col, y := self.row)
|
self._draw(x := self.col, y := self.row)
|
||||||
if self._value is not None:
|
if self._value is not None:
|
||||||
display.print_left(self.writer, x, y + 1, self.elements[self._value], self.fontcolor)
|
s = self.els[self._value] if self.simple else self.els[self._value][0]
|
||||||
|
display.print_left(self.writer, x, y + 1, s, self.fontcolor)
|
||||||
|
|
||||||
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:
|
||||||
return self.elements[self._value]
|
r = self.els[self._value]
|
||||||
|
return r if self.simple else r[0]
|
||||||
else: # set value by text
|
else: # set value by text
|
||||||
try:
|
try:
|
||||||
v = self.elements.index(text)
|
if self.simple:
|
||||||
except ValueError:
|
v = self.els.index(text)
|
||||||
|
else: # More RAM-efficient than converting to list and using .index
|
||||||
|
q = (p[0] for p in self.els)
|
||||||
|
v = 0
|
||||||
|
while next(q) != text:
|
||||||
|
v += 1
|
||||||
|
except (ValueError, StopIteration):
|
||||||
v = None
|
v = None
|
||||||
else:
|
else:
|
||||||
if v != self._value:
|
if v != self._value:
|
||||||
|
@ -141,8 +154,8 @@ class Dropdown(Widget):
|
||||||
)
|
)
|
||||||
|
|
||||||
def do_sel(self): # Select was pushed
|
def do_sel(self): # Select was pushed
|
||||||
if len(self.elements) > 1:
|
if len(self.els) > 1:
|
||||||
args = (self.writer, self.row - 2, self.col - 2, self)
|
args = (self.writer, self.row - 2, self.col - 2, self, self.dlines, self.els)
|
||||||
Screen.change(_ListDialog, args=args)
|
Screen.change(_ListDialog, args=args)
|
||||||
display.ipdev.adj_mode(True) # If in 3-button mode, go into adjust mode
|
display.ipdev.adj_mode(True) # If in 3-button mode, go into adjust mode
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# Released under the MIT License (MIT). See LICENSE.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2021-2024 Peter Hinch
|
# Copyright (c) 2021-2024 Peter Hinch
|
||||||
|
|
||||||
# 11 Sep 24 Support variable list contents.
|
# 13 Sep 24 Support dynamic elements list.
|
||||||
# 12 Sep 21 Support for scrolling.
|
# 12 Sep 21 Support for scrolling.
|
||||||
|
|
||||||
from gui.core.ugui import Widget, display
|
from gui.core.ugui import Widget, display
|
||||||
|
@ -19,6 +19,7 @@ dolittle = lambda *_: None
|
||||||
class Listbox(Widget):
|
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.
|
||||||
|
NOCB = 4 # When used in a dropdown, force passed callback.
|
||||||
|
|
||||||
# This is used by dropdown.py and menu.py
|
# This is used by dropdown.py and menu.py
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -57,8 +58,8 @@ class Listbox(Widget):
|
||||||
self.els = elements
|
self.els = elements
|
||||||
# Check whether elements specified as (str, str,...) or ([str, callback, args], [...)
|
# Check whether elements specified as (str, str,...) or ([str, callback, args], [...)
|
||||||
self.simple = isinstance(self.els[0], str)
|
self.simple = isinstance(self.els[0], str)
|
||||||
self.cb = callback if self.simple else self.despatch
|
self.cb = callback if (self.simple or also == 4) else self.despatch
|
||||||
if not self.simple and callback is not dolittle:
|
if not (self.simple or also == 4) and callback is not dolittle:
|
||||||
raise ValueError("Cannot specify callback.")
|
raise ValueError("Cannot specify callback.")
|
||||||
# Iterate text values
|
# Iterate text values
|
||||||
q = (p for p in self.els) if self.simple else (p[0] for p in self.els)
|
q = (p for p in self.els) if self.simple else (p[0] for p in self.els)
|
||||||
|
@ -138,8 +139,6 @@ class Listbox(Widget):
|
||||||
return r if self.simple else r[0]
|
return r if self.simple else r[0]
|
||||||
else: # set value by text
|
else: # set value by text
|
||||||
try:
|
try:
|
||||||
# print(text)
|
|
||||||
# print(self.els.index(text))
|
|
||||||
if self.simple:
|
if self.simple:
|
||||||
v = self.els.index(text)
|
v = self.els.index(text)
|
||||||
else: # More RAM-efficient than converting to list and using .index
|
else: # More RAM-efficient than converting to list and using .index
|
||||||
|
@ -147,7 +146,7 @@ class Listbox(Widget):
|
||||||
v = 0
|
v = 0
|
||||||
while next(q) != text:
|
while next(q) != text:
|
||||||
v += 1
|
v += 1
|
||||||
except ValueError:
|
except (ValueError, StopIteration):
|
||||||
v = None
|
v = None
|
||||||
else:
|
else:
|
||||||
if v != self._value:
|
if v != self._value:
|
||||||
|
|
Ładowanie…
Reference in New Issue