Fix widget metrics.

pull/8/head
Peter Hinch 2021-06-25 10:57:25 +01:00
rodzic 3b26ab9528
commit 3f37900d12
9 zmienionych plików z 50 dodań i 30 usunięć

Wyświetl plik

@ -49,13 +49,9 @@ target and a C device driver (unless you can acquire a suitable binary).
# Project status # Project status
Code has been tested on ESP32, Pi Pico and Pyboard. The API shuld be stable. Code has been tested on ESP32, Pi Pico and Pyboard. The API shuld be stable.
Code is new and issues are likely: please report any found. This document is Code is new and issues are likely: please report any found. The project is
under review. I plan to add further demos and to upgrade the performance of under development so check for updates. I also plan to upgrade the performance
some display drivers. of some display drivers.
An issue under investigation is that a soft reset is required after a GUI
application is run and before running another. Otherwise the second application
displays correctly but is unresponsive.
# 0. Contents # 0. Contents
@ -392,6 +388,11 @@ Demos are run by issuing (for example):
```python ```python
>>> import gui.demos.simple >>> import gui.demos.simple
``` ```
If shut down cleanly with the "close" button a demo can be re-run with (e.g.):
```python
gui.demos.simple.test()
```
Before running a different demo the host should be reset (ctrl-d) to clear RAM.
These will run on screens of 128x128 pixels or above. The initial ones are These will run on screens of 128x128 pixels or above. The initial ones are
minimal and aim to demonstrate a single technique. minimal and aim to demonstrate a single technique.
@ -2170,7 +2171,10 @@ class TSeq(Screen):
The "tab order" of widgets on a `Screen` is the order with which they acquire The "tab order" of widgets on a `Screen` is the order with which they acquire
focus with successive presses of the `Next` button. It is determined by the focus with successive presses of the `Next` button. It is determined by the
order in which they are instantiated. order in which they are instantiated. Tab order is important for usability but
instantiating in the best order can conflict with program logic. This happens
if a widget's callback refers to others not yet instantiated. See demos
`dropdown.py` and `linked_sliders.py` for one solution.
The obvious layout for the physical buttons is as per a joystick: The obvious layout for the physical buttons is as per a joystick:
@ -2201,13 +2205,12 @@ have the following bound variables, which should be considered read-only:
* `height` As specified. Does not include border. * `height` As specified. Does not include border.
* `width` Ditto. * `width` Ditto.
* `rows` Height including borders. * `mrow` Maximum absolute row occupied by the widget.
* `cols` Width with borders. * `mcol` Maximum absolute col occupied by the widget.
This support is fairly "micro" and does not take account of labels and legends. The `mrow` and `mcol` values enable other widgets to be positioned relative to
This means that `rows` and `cols` for `Dial`, `Meter`, `Slider` and the one previously instantiated. In the cases of sliders, `Dial` and `Meter`
`HorizSlider` do not necessarily reflect the full amount of space used by the widgets these take account of space ocupied by legends or labels.
control.
The `aclock.py` demo provides a simple example of this approach. The `aclock.py` demo provides a simple example of this approach.
@ -2254,4 +2257,6 @@ Hopefully these are self explanatory. The `Display` methods use the `framebuf`
convention of `x, y` coordinates rather than the `row, col` system used by convention of `x, y` coordinates rather than the `row, col` system used by
micro-gui. micro-gui.
The `primitives.py` demo provides a simple example.
###### [Contents](./README.md#0-contents) ###### [Contents](./README.md#0-contents)

Wyświetl plik

@ -274,10 +274,9 @@ class Screen:
cs_new._do_open(cs_old) # Clear and redraw cs_new._do_open(cs_old) # Clear and redraw
cs_new.after_open() # Optional subclass method cs_new.after_open() # Optional subclass method
if cs_old is None: # Initialising if cs_old is None: # Initialising
try: asyncio.run(Screen.monitor()) # Starts and ends uasyncio
asyncio.run(Screen.monitor()) # Starts and ends uasyncio # Don't do asyncio.new_event_loop() as it prevents re-running
finally: # the same app.
asyncio.new_event_loop()
@classmethod @classmethod
async def monitor(cls): async def monitor(cls):
@ -550,8 +549,9 @@ class Widget:
self.col = col self.col = col
self.height = height self.height = height
self.width = width self.width = width
self.rows = height + 4 # For metrics. Default: allow for border. # Maximum row and col. Defaults for user metrics. May be overridden
self.cols = width + 4 self.mrow = row + height + 2 # in subclass. Allow for border.
self.mcol = col + width + 2
self.visible = True # Used by ButtonList class for invisible buttons self.visible = True # Used by ButtonList class for invisible buttons
self.draw = True # Signals that obect must be redrawn self.draw = True # Signals that obect must be redrawn
self._value = value self._value = value

Wyświetl plik

@ -25,9 +25,9 @@ from gui.core.colors import *
async def aclock(dial, lbldate, lbltim): async def aclock(dial, lbldate, lbltim):
# Return a unit vector of phase phi. Multiplying by this will # Return a unit vector of phase phi. Multiplying by this will
# rotate a vector anticlockwise which is mathematically correct. # rotate a vector anticlockwise which is mathematically correct.
# Alas clocks modelled on sundials were invented in the northern # Alas clocks, modelled on sundials, were invented in the northern
# hemisphere. Otherwise they would have rotated widdershins # hemisphere. Otherwise they would have rotated widdershins like
# in accordance with maths. Hence negative sign when called. # the maths. Hence negative sign when called.
def uv(phi): def uv(phi):
return rect(1, phi) return rect(1, phi)
@ -70,13 +70,14 @@ class BaseScreen(Screen):
} }
wri = CWriter(ssd, font, GREEN, BLACK, verbose=False) wri = CWriter(ssd, font, GREEN, BLACK, verbose=False)
gap = 4 # Vertical gap between widgets
dial = Dial(wri, 2, 2, height = 70, ticks = 12, dial = Dial(wri, 2, 2, height = 70, ticks = 12,
fgcolor = GREEN, pip = GREEN) fgcolor = GREEN, pip = GREEN)
# Set up clock display: instantiate labels # Set up clock display: instantiate labels
row = dial.rows + gap # Demo of relative positioning.
gap = 4 # Vertical gap between widgets
row = dial.mrow + gap
lbldate = Label(wri, row, 2, 100, **labels) lbldate = Label(wri, row, 2, 100, **labels)
row += lbldate.rows + gap row = lbldate.mrow + gap
lbltim = Label(wri, row, 2, '00.00.00', **labels) lbltim = Label(wri, row, 2, '00.00.00', **labels)
self.reg_task(aclock(dial, lbldate, lbltim)) self.reg_task(aclock(dial, lbldate, lbltim))
CloseButton(wri) CloseButton(wri)

Wyświetl plik

@ -47,12 +47,13 @@ class BaseScreen(Screen):
self.vslider = Slider(wri, 2, 2, callback=self.slider_cb, self.vslider = Slider(wri, 2, 2, callback=self.slider_cb,
bdcolor=RED, slotcolor=BLUE, bdcolor=RED, slotcolor=BLUE,
legends=('0.0', '0.5', '1.0'), value=0.5) legends=('0.0', '0.5', '1.0'), value=0.5)
#Label(wri, 2, self.vslider.mcol, 'FF')
col = 80 col = 80
row = 15 row = 15
self.hslider = HorizSlider(wri, row, col, callback=self.slider_cb, self.hslider = HorizSlider(wri, row, col, callback=self.slider_cb,
bdcolor=GREEN, slotcolor=BLUE, bdcolor=GREEN, slotcolor=BLUE,
legends=('0.0', '0.5', '1.0'), value=0.7) legends=('0.0', '0.5', '1.0'), value=0.7)
Label(wri, self.hslider.mrow, self.hslider.mcol, 'FF')
row += 30 row += 30
self.scale = Scale(wri, row, col, width = 150, tickcb = tickcb, self.scale = Scale(wri, row, col, width = 150, tickcb = tickcb,
pointercolor=RED, fontcolor=YELLOW, bdcolor=CYAN, pointercolor=RED, fontcolor=YELLOW, bdcolor=CYAN,

Wyświetl plik

@ -61,8 +61,11 @@ class FooScreen(Screen):
m0 = Meter(wri, 10, 240, divisions = 4, ptcolor=YELLOW, height=80, width=15, m0 = Meter(wri, 10, 240, divisions = 4, ptcolor=YELLOW, height=80, width=15,
label='Meter example', style=Meter.BAR, legends=('0.0', '0.5', '1.0')) label='Meter example', style=Meter.BAR, legends=('0.0', '0.5', '1.0'))
#Label(wri, 2, m0.mcol, 'FF')
# Instantiate displayable objects. bgcolor forces complete redraw. # Instantiate displayable objects. bgcolor forces complete redraw.
dial = Dial(wri, 2, 2, height = 75, ticks = 12, bgcolor=BLACK, bdcolor=None, label=120) # Border in fg color dial = Dial(wri, 2, 2, height = 75, ticks = 12, bgcolor=BLACK, bdcolor=None, label=120) # Border in fg color
#Label(wri, dial.mrow, 2, 'FF')
#Label(wri, dial.mrow, dial.mcol, 'FF')
scale = Scale(wri, 2, 100, width = 124, tickcb = tickcb, scale = Scale(wri, 2, 100, width = 124, tickcb = tickcb,
pointercolor=RED, fontcolor=YELLOW, bdcolor=CYAN) pointercolor=RED, fontcolor=YELLOW, bdcolor=CYAN)

Wyświetl plik

@ -41,7 +41,10 @@ async def ptr_test(dial):
ptr = Pointer(dial) ptr = Pointer(dial)
v = 0j v = 0j
steps = 20 # No. of interpolation steps steps = 20 # No. of interpolation steps
grv = lambda : urandom.getrandbits(16) / 2**15 - 1 # Random: range -1.0 to +1.0 # BUG getting a weird visual flicker on occasion, with yellow
# being briefly displayed. Where is that coming from?
# Does not seem to be affected by max value. TODO
grv = lambda : urandom.getrandbits(16) / 2**15 - 1 # Random: range -1.0 to +0.999
while True: while True:
v1 = grv() + 1j * grv() # Random vector v1 = grv() + 1j * grv() # Random vector
dv = (v1 - v) / steps # Interpolation vector dv = (v1 - v) / steps # Interpolation vector

Wyświetl plik

@ -67,8 +67,9 @@ class Dial(Widget):
self.pip = self.fgcolor if pip is None else pip self.pip = self.fgcolor if pip is None else pip
if label is not None: if label is not None:
self.label = Label(writer, row + height + 3, col, label) self.label = Label(writer, row + height + 3, col, label)
#self.cols = max(self.cols, self.label.cols) # Adjust metrics
#self.rows += 3 + self.label.rows self.mrow = self.label.mrow - 2 # Label never has border
self.mcol = max(self.mcol, self.label.mcol - 2)
radius = int(height / 2) radius = int(height / 2)
self.radius = radius self.radius = radius
self.ticks = ticks self.ticks = ticks

Wyświetl plik

@ -24,6 +24,7 @@ class Meter(Widget):
self.style = style self.style = style
self.ptcolor = ptcolor if ptcolor is not None else self.fgcolor self.ptcolor = ptcolor if ptcolor is not None else self.fgcolor
if legends is not None: # Legends are static if legends is not None: # Legends are static
mcol = 0
x = col + width + 4 x = col + width + 4
y = row + height y = row + height
dy = 0 if len(legends) <= 1 else height / (len(legends) -1) dy = 0 if len(legends) <= 1 else height / (len(legends) -1)
@ -31,6 +32,8 @@ class Meter(Widget):
for legend in legends: for legend in legends:
l = Label(writer, round(yl), x, legend) l = Label(writer, round(yl), x, legend)
yl -= dy yl -= dy
mcol = max(mcol, l.mcol)
self.mcol = mcol - 2 # For metrics. Legends never have border.
self.value(value) self.value(value)
def value(self, n=None, color=None): def value(self, n=None, color=None):

Wyświetl plik

@ -28,6 +28,9 @@ class Slider(LinearIO):
super()._set_callbacks(callback, args) super()._set_callbacks(callback, args)
self.divisions = divisions self.divisions = divisions
self.legends = legends self.legends = legends
if legends is not None: # Adjust column metric
ml = max((writer.stringlen(l) for l in legends))
self.mcol += ml + 2 # Strings are rendered 2 pixels right of border
self.fontcolor = self.fgcolor if fontcolor is None else fontcolor self.fontcolor = self.fgcolor if fontcolor is None else fontcolor
self.slotcolor = self.bgcolor if slotcolor is None else slotcolor self.slotcolor = self.bgcolor if slotcolor is None else slotcolor
# Define slider # Define slider