kopia lustrzana https://github.com/peterhinch/micropython-micro-gui
Grid widget: add addressing options.
rodzic
ebfb980e94
commit
e31fbe3603
36
README.md
36
README.md
|
@ -1090,7 +1090,7 @@ Screen.change(BaseScreen)
|
|||
### 6.1.1 Grid widget
|
||||
|
||||
```python
|
||||
from gui.widgets import Grid # File: grid.py
|
||||
from gui.widgets import Grid # Files: grid.py, parse2d.py
|
||||
```
|
||||
![Image](./images/grid.JPG)
|
||||
|
||||
|
@ -1127,9 +1127,35 @@ Constructor args:
|
|||
in the `Label` as defined by `lwidth`.
|
||||
|
||||
Method:
|
||||
* `__getitem__` This enables an individual `Label`'s `value` method to be
|
||||
retrieved using index notation. The args detailed above enable inividual cells
|
||||
to be updated.
|
||||
* `__getitem__` Return a list containing one or more `Label` instances.
|
||||
* `__setitem__` Assign a value to one or more labels. If multiple labels are
|
||||
specified and a single text value is passed, all labels will receive that
|
||||
value. If an iterator is passed, consecutive labels will receive values from
|
||||
the iterator. If the iterator runs out of data, the last value will be
|
||||
repeated.
|
||||
|
||||
Addressing:
|
||||
The `Label` instances may be addressed as a 1D array as follows
|
||||
```python
|
||||
grid[20] = str(42)
|
||||
grid[20:25] = iter([str(n) for n in range(20, 25)])
|
||||
```
|
||||
or as a 2D array:
|
||||
```python
|
||||
grid[2, 5] = "A" # Row == 2, col == 5
|
||||
grid[0:7, 3] = "b" # Populate col 3 of rows 0..6
|
||||
grid[1:3, 1:3] = (str(n) for n in range(25)) # Produces
|
||||
# 0 1
|
||||
# 2 3
|
||||
```
|
||||
Columns are populated from left to right, rows from top to bottom. Unused
|
||||
iterator values are ignored. If an iterator runs out of data the last value is
|
||||
repeated, thus
|
||||
```python
|
||||
grid[1:3, 1:3] = (str(n) for n in range(2)) # Produces
|
||||
# 0 1
|
||||
# 1 1
|
||||
```
|
||||
|
||||
Example uses:
|
||||
```python
|
||||
|
@ -1137,6 +1163,8 @@ colwidth = (20, 30) # Col 0 width is 20, subsequent columns 30
|
|||
self.grid = Grid(wri, row, col, colwidth, rows, cols, justify=Label.CENTRE)
|
||||
self.grid[20] = "" # Clear cell 20 by setting its value to ""
|
||||
self.grid[2, 5] = str(42) # 2D array syntax
|
||||
grid[1:6, 0] = iter("ABCDE") # Label row and col headings
|
||||
grid[0, 1:cols] = (str(x + 1) for x in range(cols))
|
||||
d = {} # For indiviual control of cell appearance
|
||||
d["fgcolor"] = RED
|
||||
d["text"] = str(99)
|
||||
|
|
|
@ -32,8 +32,7 @@ class BaseScreen(Screen):
|
|||
self.lbl = Label(wri, row, col, text = (colwidth + 4) * cols, justify=Label.CENTRE)
|
||||
row = self.lbl.mrow
|
||||
self.grid = Grid(wri, row, col, colwidth, rows, cols, justify=Label.CENTRE)
|
||||
for n, day in enumerate(DateCal.days):
|
||||
self.grid[0, n] = day[:3]
|
||||
self.grid[0, 0:7] = iter([d[:3] for d in DateCal.days]) # 3-char day names
|
||||
|
||||
row = self.grid.mrow + 4
|
||||
ht = 30
|
||||
|
|
|
@ -7,6 +7,18 @@ from gui.core.ugui import Widget, display
|
|||
from gui.core.writer import Writer
|
||||
from gui.core.colors import *
|
||||
from gui.widgets import Label
|
||||
from .parse2d import do_args
|
||||
|
||||
# Given a slice and a maximum address return start and stop addresses (or None on error)
|
||||
# Step value must be 1, hence does not support start > stop (used with step < 0)
|
||||
def _do_slice(sli, nbytes):
|
||||
if not (sli.step is None or sli.step == 1):
|
||||
raise NotImplementedError("only slices with step=1 (or None) are supported")
|
||||
start = sli.start if sli.start is not None else 0
|
||||
stop = sli.stop if sli.stop is not None else nbytes
|
||||
start = min(start if start >= 0 else max(nbytes + start, 0), nbytes)
|
||||
stop = min(stop if stop >= 0 else max(nbytes + stop, 0), nbytes)
|
||||
return (start, stop) if start < stop else None # Caller should check
|
||||
|
||||
# lwidth may be integer Label width in pixels or a tuple/list of widths
|
||||
class Grid(Widget):
|
||||
|
@ -34,28 +46,27 @@ class Grid(Widget):
|
|||
r += self.cheight
|
||||
c = col
|
||||
|
||||
def _idx(self, n):
|
||||
if isinstance(n, tuple) or isinstance(n, list): # list allows old syntax l[[r, c]]
|
||||
if n[0] >= self.nrows:
|
||||
raise ValueError("Grid row index too large")
|
||||
if n[1] >= self.ncols:
|
||||
raise ValueError("Grid col index too large")
|
||||
idx = n[1] + n[0] * self.ncols
|
||||
else:
|
||||
idx = n
|
||||
if idx >= self.ncells:
|
||||
raise ValueError("Grid cell index too large")
|
||||
return idx
|
||||
|
||||
def __getitem__(self, *args): # Return the Label instance
|
||||
return self.cells[self._idx(args[0])]
|
||||
indices = do_args(args, self.nrows, self.ncols)
|
||||
res = []
|
||||
for i in indices:
|
||||
res.append(self.cells[i])
|
||||
return res
|
||||
|
||||
# allow grid[[r, c]] = "foo" or kwargs for Label:
|
||||
# grid[[r, c]] = {"text": str(n), "fgcolor" : RED}
|
||||
def __setitem__(self, *args):
|
||||
v = self.cells[self._idx(args[0])].value
|
||||
x = args[1]
|
||||
_ = v(**x) if isinstance(x, dict) else v(x)
|
||||
x = args[1] # Value
|
||||
indices = do_args(args[: -1], self.nrows, self.ncols)
|
||||
for i in indices:
|
||||
try:
|
||||
z = next(x) # May be a generator
|
||||
except StopIteration:
|
||||
pass # Repeat last value
|
||||
except TypeError:
|
||||
z = x
|
||||
v = self.cells[i].value # method of Label
|
||||
_ = v(**z) if isinstance(x, dict) else v(z)
|
||||
|
||||
def show(self):
|
||||
super().show() # Draw border
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# parse2d.py Parse args for item access dunder methods for a 2D array.
|
||||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2023 Peter Hinch
|
||||
|
||||
|
||||
# Called from __getitem__ or __setitem__ args is a 1-tuple. The single item may be an int or a
|
||||
# slice for 1D access. Or it may be a 2-tuple for 2D access. Items in the 2-tuple may be ints
|
||||
# or slices in any combination.
|
||||
# As a generator it returns offsets into the underlying 1D array or list.
|
||||
def do_args(args, nrows, ncols):
|
||||
# Given a slice and a maximum address return start and stop addresses (or None on error)
|
||||
# Step value must be 1, hence does not support start > stop (used with step < 0)
|
||||
def do_slice(sli, nbytes):
|
||||
step = sli.step if sli.step is not None else 1
|
||||
start = sli.start if sli.start is not None else 0
|
||||
stop = sli.stop if sli.stop is not None else nbytes
|
||||
start = min(start if start >= 0 else max(nbytes + start, 0), nbytes)
|
||||
stop = min(stop if stop >= 0 else max(nbytes + stop, 0), nbytes)
|
||||
ok = (start < stop and step > 0) or (start > stop and step < 0)
|
||||
return (start, stop, step) if ok else None # Caller should check
|
||||
|
||||
def ivalid(n, nmax): # Validate an integer arg, handle -ve args
|
||||
n = n if n >= 0 else nmax + n
|
||||
if n < 0 or n > nmax - 1:
|
||||
raise IndexError("Index out of range")
|
||||
return n
|
||||
|
||||
def fail(n):
|
||||
raise IndexError("Invalid index", n)
|
||||
|
||||
ncells = nrows * ncols
|
||||
n = args[0]
|
||||
if isinstance(n, int): # Index into 1D array
|
||||
yield ivalid(n, ncells)
|
||||
elif isinstance(n, slice): # Slice of 1D array
|
||||
cells = do_slice(n, ncells)
|
||||
if cells is not None:
|
||||
for cell in range(*cells):
|
||||
yield cell
|
||||
elif isinstance(n, tuple) or isinstance(n, list): # list allows for old [[]] syntax
|
||||
if len(n) != 2:
|
||||
fail(n)
|
||||
row = n[0] # May be slice
|
||||
if isinstance(row, int):
|
||||
row = ivalid(row, nrows)
|
||||
col = n[1]
|
||||
if isinstance(col, int):
|
||||
col = ivalid(col, ncols)
|
||||
if isinstance(row, int) and isinstance(col, int):
|
||||
yield row * ncols + col
|
||||
elif isinstance(row, slice) and isinstance(col, int):
|
||||
rows = do_slice(row, nrows)
|
||||
if rows is not None:
|
||||
for row in range(*rows):
|
||||
yield row * ncols + col
|
||||
elif isinstance(row, int) and isinstance(col, slice):
|
||||
cols = do_slice(col, ncols)
|
||||
if cols is not None:
|
||||
for col in range(*cols):
|
||||
yield row * ncols + col
|
||||
elif isinstance(row, slice) and isinstance(col, slice):
|
||||
rows = do_slice(row, nrows)
|
||||
cols = do_slice(col, ncols)
|
||||
if cols is not None and rows is not None:
|
||||
for row in range(*rows):
|
||||
for col in range(*cols):
|
||||
yield row * ncols + col
|
||||
else:
|
||||
fail(n)
|
||||
else:
|
||||
fail(n)
|
Ładowanie…
Reference in New Issue