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
|
### 6.1.1 Grid widget
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from gui.widgets import Grid # File: grid.py
|
from gui.widgets import Grid # Files: grid.py, parse2d.py
|
||||||
```
|
```
|
||||||
![Image](./images/grid.JPG)
|
![Image](./images/grid.JPG)
|
||||||
|
|
||||||
|
@ -1127,9 +1127,35 @@ Constructor args:
|
||||||
in the `Label` as defined by `lwidth`.
|
in the `Label` as defined by `lwidth`.
|
||||||
|
|
||||||
Method:
|
Method:
|
||||||
* `__getitem__` This enables an individual `Label`'s `value` method to be
|
* `__getitem__` Return a list containing one or more `Label` instances.
|
||||||
retrieved using index notation. The args detailed above enable inividual cells
|
* `__setitem__` Assign a value to one or more labels. If multiple labels are
|
||||||
to be updated.
|
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:
|
Example uses:
|
||||||
```python
|
```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 = Grid(wri, row, col, colwidth, rows, cols, justify=Label.CENTRE)
|
||||||
self.grid[20] = "" # Clear cell 20 by setting its value to ""
|
self.grid[20] = "" # Clear cell 20 by setting its value to ""
|
||||||
self.grid[2, 5] = str(42) # 2D array syntax
|
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 = {} # For indiviual control of cell appearance
|
||||||
d["fgcolor"] = RED
|
d["fgcolor"] = RED
|
||||||
d["text"] = str(99)
|
d["text"] = str(99)
|
||||||
|
|
|
@ -32,8 +32,7 @@ class BaseScreen(Screen):
|
||||||
self.lbl = Label(wri, row, col, text = (colwidth + 4) * cols, justify=Label.CENTRE)
|
self.lbl = Label(wri, row, col, text = (colwidth + 4) * cols, justify=Label.CENTRE)
|
||||||
row = self.lbl.mrow
|
row = self.lbl.mrow
|
||||||
self.grid = Grid(wri, row, col, colwidth, rows, cols, justify=Label.CENTRE)
|
self.grid = Grid(wri, row, col, colwidth, rows, cols, justify=Label.CENTRE)
|
||||||
for n, day in enumerate(DateCal.days):
|
self.grid[0, 0:7] = iter([d[:3] for d in DateCal.days]) # 3-char day names
|
||||||
self.grid[0, n] = day[:3]
|
|
||||||
|
|
||||||
row = self.grid.mrow + 4
|
row = self.grid.mrow + 4
|
||||||
ht = 30
|
ht = 30
|
||||||
|
|
|
@ -7,6 +7,18 @@ from gui.core.ugui import Widget, display
|
||||||
from gui.core.writer import Writer
|
from gui.core.writer import Writer
|
||||||
from gui.core.colors import *
|
from gui.core.colors import *
|
||||||
from gui.widgets import Label
|
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
|
# lwidth may be integer Label width in pixels or a tuple/list of widths
|
||||||
class Grid(Widget):
|
class Grid(Widget):
|
||||||
|
@ -34,28 +46,27 @@ class Grid(Widget):
|
||||||
r += self.cheight
|
r += self.cheight
|
||||||
c = col
|
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
|
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:
|
# allow grid[[r, c]] = "foo" or kwargs for Label:
|
||||||
# grid[[r, c]] = {"text": str(n), "fgcolor" : RED}
|
# grid[[r, c]] = {"text": str(n), "fgcolor" : RED}
|
||||||
def __setitem__(self, *args):
|
def __setitem__(self, *args):
|
||||||
v = self.cells[self._idx(args[0])].value
|
x = args[1] # Value
|
||||||
x = args[1]
|
indices = do_args(args[: -1], self.nrows, self.ncols)
|
||||||
_ = v(**x) if isinstance(x, dict) else v(x)
|
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):
|
def show(self):
|
||||||
super().show() # Draw border
|
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