Meter class now replaces Tstat.

pull/8/head
Peter Hinch 2021-07-10 18:17:10 +01:00
rodzic 4f42847a99
commit 5fa17bb085
9 zmienionych plików z 79 dodań i 73 usunięć

Wyświetl plik

@ -61,6 +61,8 @@ Code is new and issues are likely: please report any found. The project is
under development so check for updates. I also plan to upgrade the performance
of some display drivers.
The encoder interface is under development and currently is rather erratic.
# 0. Contents
1. [Basic concepts](./README.md#1-basic-concepts) Including installation and test.
@ -104,9 +106,8 @@ of some display drivers.
13. [Dropdown widget](./README.md#13-dropdown-widget) Dropdown lists.
14. [DialogBox class](./README.md#14-dialogbox-class) Pop-up modal dialog boxes.
15. [Textbox widget](./README.md#15-textbox-widget) Scrolling text display.
16. [Meter widget](./README.md#16-meter-widget) Display floats on an analog meter.
16.1 [Tstat widget](./README.md#161-tstat-widget) Meter subclass enables thermostats, alarms etc.
     16.1.1 [Region class](./README.md#1611-region-class)
16. [Meter widget](./README.md#16-meter-widget) Display floats on an analog meter, with data driven callbacks.
16.1 [Region class](./README.md#161-region-class)
17. [Slider and HorizSlider widgets](./README.md#17-slider-and-horizslider-widgets) Linear potentiometer float data entry and display
18. [Scale widget](./README.md#18-scale-widget) High precision float entry and display.
19. [ScaleLog widget](./README.md#19-scalelog-widget) Wide dynamic range float entry and display.
@ -767,7 +768,10 @@ explicitly in code.
This is a `Screen` subclass providing for modal windows. As such it has
positional and dimension information. Usage consists of writing a user class
subclassed from `Window`. Example code is in `demos/screens.py`.
subclassed from `Window`. Example code is in `demos/screens.py`. Code in a
window must not attempt to open another `Window` or `Screen`. Doing so will
raise a `ValueError`. Modal behaviour means that the only valid screen change
is a return to the calling screen.
## 5.1 Constructor
@ -1392,13 +1396,32 @@ the oldest (topmost) being discarded as required.
# 16. Meter widget
This `passive` widget displays a single floating point value on a vertical
linear scale. Optionally it can support data dependent callbacks.
```python
from gui.widgets.meter import Meter
```
![Image](./images/meter.JPG)
The two styles of `meter`, both showing a value of 0.65. This `passive` widget
provides a vertical linear meter display of values scaled between 0.0 and 1.0.
In these examples each meter simply displays a data value.
![Image](./images/tstat.JPG)
This example has two data sensitive regions, a control region with hysteresis
and an alarm region. Callbacks can run in response to specific changes in the
`Meter`'s value emulating data-dependent behaviour including alarms and
controls (like thermostats) having hysteresis.
The class supports one or more `Region` instances. Visually these appear as
colored bands on the scale. If the meter's value enters, leaves or crosses one
of these bands a callback is triggered. This receives an arg indicating the
nature of the change which caused the trigger. For example an alarm might be
triggered when the value, initially below the region, enters it or crosses it.
The alarm might be cleared on exit or if crossed from above. Hysteresis as used
in thermostats is simple to implement. Examples of these techniques may be
found in `gui.demos.tstat.py`.
Regions may be modified, added or removed programmatically.
Constructor mandatory positional args:
1. `writer` The `Writer` instance (defines font) to use.
@ -1435,55 +1458,45 @@ Methods:
Returns the current value.
2. `text` Updates the label if present (otherwise throws a `ValueError`). Args:
* `text=None` The text to display. If `None` displays last value.
* ` invert=False` If true, show inverse text.
* `invert=False` If true, show inverse text.
* `fgcolor=None` Foreground color: if `None` the `Writer` default is used.
* `bgcolor=None` Background color, as per foreground.
* `bdcolor=None` Border color. As per above except that if `False` is
passed, no border is displayed. This clears a previously drawn border.
3. `del_region` Arg: a `Region` instance. Deletes the region. No callback will
run.
### Legends
Depending on the font in use for legends additional space may be required above
and below the `Meter` to display the top and bottom legends.
###### [Contents](./README.md#0-contents)
## 16.1 Tstat widget
### Example of use of Regions
```python
from gui.widgets.tstat import Tstat
# Instantiate Meter
ts = Meter(wri, row, sl.mcol + 5, ptcolor=YELLOW, height=100, width=15,
style=Meter.BAR, legends=('0.0', '0.5', '1.0'))
# Instantiate two Regions and associate with the Meter instance.
reg = Region(ts, 0.4, 0.55, MAGENTA, ts_cb)
al = Region(ts, 0.9, 1.0, RED, al_cb)
```
![Image](./images/tstat.JPG)
This example has two data sensitive regions, a control region with hysteresis
and an alarm region.
The callback `ts_cb` will run in response to data values between 0.4 and 0.55:
if the value enters that range having been outside it, if it leaves the range,
or if successive values are either side of the range. The `al_cb` callback
behaves similarly for data values between 0.9 and 1.0.
The `Tstat` subclass of `Meter` is a `passive` widget with callbacks which run
in response to specific changes in the object's value. Notionally based on a
thermostat, it can implement objects with a variety of data-dependent behaviour
including alarms and controls having hysteresis.
###### [Contents](./README.md#0-contents)
The class supports one or more `Region` instances. Visually these appear as
colored bands on the scale. If the meter's value enters, leaves or crosses one
of these bands a callback is triggered. This receives an arg indicating the
nature of the change which caused the trigger. For example an alarm might be
triggered when the value, initially below the region, enters it or crosses it.
The alarm might be cleared on exit or if crossed from above. Hysteresis as used
in thermostats is simple to implement. Examples of these techniques may be
found in `gui.demos.tstat.py`.
## 16.1 Region class
Regions may be modified, added or removed programmatically.
```python
from gui.widgets.region import Region
```
Instantiating a `Region` associates it with a supporting widget (currently only
a `Meter`). Constructor positional args are as follows:
Constructor args and methods are as per `Meter`. The `Tstat` class adds the
following method:
1. `del_region` Arg: a `Region` instance. Deletes the region. No callback will
run.
### 16.1.1 Region class
Instantiating a `Region` associates it with a `Tstat`. Constructor args are as
follows:
* `tstat` The `Tstat` instance.
* `tstat` The parent instance.
* `vlo` Low value (0 <= `vlo` <= 1.0).
* `vhi` High value (`vlo` < `vhi` <= 1.0).
* `color` For visible band.

Wyświetl plik

@ -1,7 +1,7 @@
# colors.py Micropython GUI library for TFT displays: colors and shapes
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019 Peter Hinch
# Copyright (c) 2019-2021 Peter Hinch
from hardware_setup import SSD
# Code can be portable between 4-bit and other drivers by calling create_color
@ -49,8 +49,6 @@ else:
# Color used when clearing the screen
BGCOLOR = BLACK
# RA8875 defines colors as 3-tuples with greying-out operating on those.
# Should we assign GREY to any color > 0?
CIRCLE = 1
RECTANGLE = 2
CLIPPED_RECT = 3

Wyświetl plik

@ -252,6 +252,8 @@ class Screen:
if forward:
if isinstance(cls_new_screen, type):
# Instantiate new screen. __init__ must terminate
if cs_old is not None and cs_old.__name__ == 'Window':
raise ValueError('Windows are modal.')
new_screen = cls_new_screen(*args, **kwargs)
else:
raise ValueError('Must pass Screen class or subclass (not instance)')

Wyświetl plik

@ -11,6 +11,7 @@ from gui.core.writer import CWriter
from gui.widgets.menu import Menu
from gui.widgets.buttons import CloseButton
from gui.core.colors import *
from gui.widgets.dialog import DialogBox
class BaseScreen(Screen):
@ -19,7 +20,13 @@ class BaseScreen(Screen):
print('Top level callback', n)
def cb_sm(lb, n):
kwargs = {'writer' : wri, 'row': 60, 'col' : 2,
'elements' : (('Yes', GREEN), ('No', RED), ('Foo', YELLOW)),
'label' : 'Test dialog',
}
print('Submenu callback', lb.value(), lb.textvalue(), n)
if lb.value() == 0:
Screen.change(DialogBox, kwargs = kwargs)
super().__init__()
mnu = (('Gas', cb_sm, (0,), ('Argon','Neon','Xenon','Radon')),

Wyświetl plik

@ -13,7 +13,8 @@ from gui.core.ugui import Screen, ssd
from gui.widgets.buttons import Button, CloseButton
from gui.widgets.sliders import Slider
from gui.widgets.label import Label
from gui.widgets.tstat import Tstat, Region
from gui.widgets.meter import Meter
from gui.widgets.region import Region
from gui.widgets.led import LED
from gui.core.writer import CWriter
@ -38,8 +39,8 @@ class BaseScreen(Screen):
sl = Slider(wri, row, col, callback=self.slider_cb,
bdcolor=RED, slotcolor=BLUE,
legends=('0.0', '0.5', '1.0'))
self.ts = Tstat(wri, row, sl.mcol + 5, divisions = 4, ptcolor=YELLOW, height=100, width=15,
style=Tstat.BAR, legends=('0.0', '0.5', '1.0'))
self.ts = Meter(wri, row, sl.mcol + 5, ptcolor=YELLOW, height=100, width=15,
style=Meter.BAR, legends=('0.0', '0.5', '1.0'))
reg = Region(self.ts, 0.4, 0.55, MAGENTA, self.ts_cb)
al = Region(self.ts, 0.9, 1.0, RED, self.al_cb)
col = self.ts.mcol + 5

Wyświetl plik

@ -25,6 +25,7 @@ import uasyncio as asyncio
import utime
import gc
defaults['focus'] = YELLOW
class FooScreen(Screen):
def __init__(self):

Wyświetl plik

@ -11,9 +11,9 @@ from gui.widgets.buttons import Button
from gui.widgets.listbox import Listbox
from gui.core.colors import *
# A SubMenu is a Window containing a Listbox
# Next and Prev close the listbox without running the callback. This is
# handled by Screen .move bound method
# A SubMenu is a Window containing a Listbox
class SubMenu(Window):
def __init__(self, menu, button, elements, cb, args): # menu is parent Menu
@ -39,6 +39,8 @@ class SubMenu(Window):
Screen.back()
self.cb(obj_listbox, *self.args) # CB can access obj_listbox.value() or .textvalue()
# A Menu is a set of Button objects at the top of the screen. On press, Buttons either run the
# user callback or instantiate a SubMenu
class Menu:
def __init__(self, writer, *, height=25, bgcolor=None, fgcolor=None, textcolor=None, select_color=DARKBLUE, args): # ((text, cb, (args,)),(text, cb, (args,), (elements,)), ...)

Wyświetl plik

@ -34,6 +34,7 @@ class Meter(Widget):
yl -= dy
mcol = max(mcol, l.mcol)
self.mcol = mcol - 2 # For metrics. Legends never have border.
self.regions = set()
self.value(value)
def value(self, n=None, color=None):
@ -42,6 +43,8 @@ class Meter(Widget):
n = super().value(min(1, max(0, n)))
if color is not None:
self.ptcolor = color
for r in self.regions:
r.check(n)
return n
def show(self):
@ -54,7 +57,10 @@ class Meter(Widget):
x1 = self.col + width
y0 = self.row
y1 = self.row + height
self.preshow(x0, y1, width, height) # Subclass draws regions
for r in self.regions:
ht = round(height * (r.vhi - r.vlo))
yr = y1 - round(height * r.vhi)
display.fill_rect(x0, yr, width, ht, r.color)
if self.divisions > 0:
dy = height / (self.divisions) # Tick marks
for tick in range(self.divisions + 1):
@ -68,5 +74,6 @@ class Meter(Widget):
w = width / 2
display.fill_rect(int(x0 + w - 2), y, 4, y1 - y, self.ptcolor)
def preshow(self, x, y, width, height):
pass # For subclass
def del_region(self, reg):
self.regions.discard(reg)
self.draw = True

Wyświetl plik

@ -1,4 +1,4 @@
# tstat.py Extension to nanogui providing the Tstat class
# region.py Extension to nanogui providing the Region class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
@ -100,28 +100,3 @@ class Region:
self.wa = v < vlo
# If it was in old region treat as if leaving it
self.check(v)
class Tstat(Meter):
def __init__(self, *args, **kwargs):
self.regions = set()
super().__init__(*args, **kwargs)
def del_region(self, reg):
self.regions.discard(reg)
self.draw = True
# Called by subclass prior to drawing scale and data
def preshow(self, x, y, width, height):
for r in self.regions:
ht = round(height * (r.vhi - r.vlo))
y1 = y - round(height * r.vhi)
display.fill_rect(x, y1, width, ht, r.color)
def value(self, n=None, color=None):
if n is None:
return super().value()
v = super().value(n, color)
for r in self.regions:
r.check(v)
return v