kopia lustrzana https://github.com/peterhinch/micropython-micro-gui
Meter class now replaces Tstat.
rodzic
4f42847a99
commit
5fa17bb085
87
README.md
87
README.md
|
@ -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
|
||||
```
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
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)
|
||||
```
|
||||

|
||||
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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)')
|
||||
|
|
|
@ -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')),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -25,6 +25,7 @@ import uasyncio as asyncio
|
|||
import utime
|
||||
import gc
|
||||
|
||||
defaults['focus'] = YELLOW
|
||||
|
||||
class FooScreen(Screen):
|
||||
def __init__(self):
|
||||
|
|
|
@ -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,)), ...)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Ładowanie…
Reference in New Issue