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
|
under development so check for updates. I also plan to upgrade the performance
|
||||||
of some display drivers.
|
of some display drivers.
|
||||||
|
|
||||||
|
The encoder interface is under development and currently is rather erratic.
|
||||||
|
|
||||||
# 0. Contents
|
# 0. Contents
|
||||||
|
|
||||||
1. [Basic concepts](./README.md#1-basic-concepts) Including installation and test.
|
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.
|
13. [Dropdown widget](./README.md#13-dropdown-widget) Dropdown lists.
|
||||||
14. [DialogBox class](./README.md#14-dialogbox-class) Pop-up modal dialog boxes.
|
14. [DialogBox class](./README.md#14-dialogbox-class) Pop-up modal dialog boxes.
|
||||||
15. [Textbox widget](./README.md#15-textbox-widget) Scrolling text display.
|
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. [Meter widget](./README.md#16-meter-widget) Display floats on an analog meter, with data driven callbacks.
|
||||||
16.1 [Tstat widget](./README.md#161-tstat-widget) Meter subclass enables thermostats, alarms etc.
|
16.1 [Region class](./README.md#161-region-class)
|
||||||
16.1.1 [Region class](./README.md#1611-region-class)
|
|
||||||
17. [Slider and HorizSlider widgets](./README.md#17-slider-and-horizslider-widgets) Linear potentiometer float data entry and display
|
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.
|
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.
|
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
|
This is a `Screen` subclass providing for modal windows. As such it has
|
||||||
positional and dimension information. Usage consists of writing a user class
|
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
|
## 5.1 Constructor
|
||||||
|
|
||||||
|
@ -1392,13 +1396,32 @@ the oldest (topmost) being discarded as required.
|
||||||
|
|
||||||
# 16. Meter widget
|
# 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
|
```python
|
||||||
from gui.widgets.meter import Meter
|
from gui.widgets.meter import Meter
|
||||||
```
|
```
|
||||||

|

|
||||||
|
|
||||||
The two styles of `meter`, both showing a value of 0.65. This `passive` widget
|
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.
|
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:
|
Constructor mandatory positional args:
|
||||||
1. `writer` The `Writer` instance (defines font) to use.
|
1. `writer` The `Writer` instance (defines font) to use.
|
||||||
|
@ -1435,55 +1458,45 @@ Methods:
|
||||||
Returns the current value.
|
Returns the current value.
|
||||||
2. `text` Updates the label if present (otherwise throws a `ValueError`). Args:
|
2. `text` Updates the label if present (otherwise throws a `ValueError`). Args:
|
||||||
* `text=None` The text to display. If `None` displays last value.
|
* `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.
|
* `fgcolor=None` Foreground color: if `None` the `Writer` default is used.
|
||||||
* `bgcolor=None` Background color, as per foreground.
|
* `bgcolor=None` Background color, as per foreground.
|
||||||
* `bdcolor=None` Border color. As per above except that if `False` is
|
* `bdcolor=None` Border color. As per above except that if `False` is
|
||||||
passed, no border is displayed. This clears a previously drawn border.
|
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
|
### Legends
|
||||||
|
|
||||||
Depending on the font in use for legends additional space may be required above
|
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.
|
and below the `Meter` to display the top and bottom legends.
|
||||||
|
|
||||||
###### [Contents](./README.md#0-contents)
|
### Example of use of Regions
|
||||||
|
|
||||||
## 16.1 Tstat widget
|
|
||||||
|
|
||||||
```python
|
```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)
|
||||||
```
|
```
|
||||||

|
The callback `ts_cb` will run in response to data values between 0.4 and 0.55:
|
||||||
This example has two data sensitive regions, a control region with hysteresis
|
if the value enters that range having been outside it, if it leaves the range,
|
||||||
and an alarm region.
|
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
|
###### [Contents](./README.md#0-contents)
|
||||||
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.
|
|
||||||
|
|
||||||
The class supports one or more `Region` instances. Visually these appear as
|
## 16.1 Region class
|
||||||
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.
|
```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
|
* `tstat` The parent instance.
|
||||||
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.
|
|
||||||
* `vlo` Low value (0 <= `vlo` <= 1.0).
|
* `vlo` Low value (0 <= `vlo` <= 1.0).
|
||||||
* `vhi` High value (`vlo` < `vhi` <= 1.0).
|
* `vhi` High value (`vlo` < `vhi` <= 1.0).
|
||||||
* `color` For visible band.
|
* `color` For visible band.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# colors.py Micropython GUI library for TFT displays: colors and shapes
|
# colors.py Micropython GUI library for TFT displays: colors and shapes
|
||||||
|
|
||||||
# Released under the MIT License (MIT). See LICENSE.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2019 Peter Hinch
|
# Copyright (c) 2019-2021 Peter Hinch
|
||||||
from hardware_setup import SSD
|
from hardware_setup import SSD
|
||||||
|
|
||||||
# Code can be portable between 4-bit and other drivers by calling create_color
|
# 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
|
# Color used when clearing the screen
|
||||||
BGCOLOR = BLACK
|
BGCOLOR = BLACK
|
||||||
|
|
||||||
# RA8875 defines colors as 3-tuples with greying-out operating on those.
|
|
||||||
# Should we assign GREY to any color > 0?
|
|
||||||
CIRCLE = 1
|
CIRCLE = 1
|
||||||
RECTANGLE = 2
|
RECTANGLE = 2
|
||||||
CLIPPED_RECT = 3
|
CLIPPED_RECT = 3
|
||||||
|
|
|
@ -252,6 +252,8 @@ class Screen:
|
||||||
if forward:
|
if forward:
|
||||||
if isinstance(cls_new_screen, type):
|
if isinstance(cls_new_screen, type):
|
||||||
# Instantiate new screen. __init__ must terminate
|
# 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)
|
new_screen = cls_new_screen(*args, **kwargs)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Must pass Screen class or subclass (not instance)')
|
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.menu import Menu
|
||||||
from gui.widgets.buttons import CloseButton
|
from gui.widgets.buttons import CloseButton
|
||||||
from gui.core.colors import *
|
from gui.core.colors import *
|
||||||
|
from gui.widgets.dialog import DialogBox
|
||||||
|
|
||||||
class BaseScreen(Screen):
|
class BaseScreen(Screen):
|
||||||
|
|
||||||
|
@ -19,7 +20,13 @@ class BaseScreen(Screen):
|
||||||
print('Top level callback', n)
|
print('Top level callback', n)
|
||||||
|
|
||||||
def cb_sm(lb, 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)
|
print('Submenu callback', lb.value(), lb.textvalue(), n)
|
||||||
|
if lb.value() == 0:
|
||||||
|
Screen.change(DialogBox, kwargs = kwargs)
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
mnu = (('Gas', cb_sm, (0,), ('Argon','Neon','Xenon','Radon')),
|
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.buttons import Button, CloseButton
|
||||||
from gui.widgets.sliders import Slider
|
from gui.widgets.sliders import Slider
|
||||||
from gui.widgets.label import Label
|
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.widgets.led import LED
|
||||||
from gui.core.writer import CWriter
|
from gui.core.writer import CWriter
|
||||||
|
|
||||||
|
@ -38,8 +39,8 @@ class BaseScreen(Screen):
|
||||||
sl = Slider(wri, row, col, callback=self.slider_cb,
|
sl = Slider(wri, row, col, callback=self.slider_cb,
|
||||||
bdcolor=RED, slotcolor=BLUE,
|
bdcolor=RED, slotcolor=BLUE,
|
||||||
legends=('0.0', '0.5', '1.0'))
|
legends=('0.0', '0.5', '1.0'))
|
||||||
self.ts = Tstat(wri, row, sl.mcol + 5, divisions = 4, ptcolor=YELLOW, height=100, width=15,
|
self.ts = Meter(wri, row, sl.mcol + 5, ptcolor=YELLOW, height=100, width=15,
|
||||||
style=Tstat.BAR, legends=('0.0', '0.5', '1.0'))
|
style=Meter.BAR, legends=('0.0', '0.5', '1.0'))
|
||||||
reg = Region(self.ts, 0.4, 0.55, MAGENTA, self.ts_cb)
|
reg = Region(self.ts, 0.4, 0.55, MAGENTA, self.ts_cb)
|
||||||
al = Region(self.ts, 0.9, 1.0, RED, self.al_cb)
|
al = Region(self.ts, 0.9, 1.0, RED, self.al_cb)
|
||||||
col = self.ts.mcol + 5
|
col = self.ts.mcol + 5
|
||||||
|
|
|
@ -25,6 +25,7 @@ import uasyncio as asyncio
|
||||||
import utime
|
import utime
|
||||||
import gc
|
import gc
|
||||||
|
|
||||||
|
defaults['focus'] = YELLOW
|
||||||
|
|
||||||
class FooScreen(Screen):
|
class FooScreen(Screen):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -11,9 +11,9 @@ from gui.widgets.buttons import Button
|
||||||
from gui.widgets.listbox import Listbox
|
from gui.widgets.listbox import Listbox
|
||||||
from gui.core.colors import *
|
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
|
# Next and Prev close the listbox without running the callback. This is
|
||||||
# handled by Screen .move bound method
|
# handled by Screen .move bound method
|
||||||
# A SubMenu is a Window containing a Listbox
|
|
||||||
class SubMenu(Window):
|
class SubMenu(Window):
|
||||||
|
|
||||||
def __init__(self, menu, button, elements, cb, args): # menu is parent Menu
|
def __init__(self, menu, button, elements, cb, args): # menu is parent Menu
|
||||||
|
@ -39,6 +39,8 @@ class SubMenu(Window):
|
||||||
Screen.back()
|
Screen.back()
|
||||||
self.cb(obj_listbox, *self.args) # CB can access obj_listbox.value() or .textvalue()
|
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:
|
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,)), ...)
|
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
|
yl -= dy
|
||||||
mcol = max(mcol, l.mcol)
|
mcol = max(mcol, l.mcol)
|
||||||
self.mcol = mcol - 2 # For metrics. Legends never have border.
|
self.mcol = mcol - 2 # For metrics. Legends never have border.
|
||||||
|
self.regions = set()
|
||||||
self.value(value)
|
self.value(value)
|
||||||
|
|
||||||
def value(self, n=None, color=None):
|
def value(self, n=None, color=None):
|
||||||
|
@ -42,6 +43,8 @@ class Meter(Widget):
|
||||||
n = super().value(min(1, max(0, n)))
|
n = super().value(min(1, max(0, n)))
|
||||||
if color is not None:
|
if color is not None:
|
||||||
self.ptcolor = color
|
self.ptcolor = color
|
||||||
|
for r in self.regions:
|
||||||
|
r.check(n)
|
||||||
return n
|
return n
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
|
@ -54,7 +57,10 @@ class Meter(Widget):
|
||||||
x1 = self.col + width
|
x1 = self.col + width
|
||||||
y0 = self.row
|
y0 = self.row
|
||||||
y1 = self.row + height
|
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:
|
if self.divisions > 0:
|
||||||
dy = height / (self.divisions) # Tick marks
|
dy = height / (self.divisions) # Tick marks
|
||||||
for tick in range(self.divisions + 1):
|
for tick in range(self.divisions + 1):
|
||||||
|
@ -68,5 +74,6 @@ class Meter(Widget):
|
||||||
w = width / 2
|
w = width / 2
|
||||||
display.fill_rect(int(x0 + w - 2), y, 4, y1 - y, self.ptcolor)
|
display.fill_rect(int(x0 + w - 2), y, 4, y1 - y, self.ptcolor)
|
||||||
|
|
||||||
def preshow(self, x, y, width, height):
|
def del_region(self, reg):
|
||||||
pass # For subclass
|
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.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2021 Peter Hinch
|
# Copyright (c) 2021 Peter Hinch
|
||||||
|
@ -100,28 +100,3 @@ class Region:
|
||||||
self.wa = v < vlo
|
self.wa = v < vlo
|
||||||
# If it was in old region treat as if leaving it
|
# If it was in old region treat as if leaving it
|
||||||
self.check(v)
|
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