Add Adjuster widget and demos.

pull/8/head
Peter Hinch 2021-11-08 11:35:06 +00:00
rodzic c2b858e18e
commit 35064c3501
5 zmienionych plików z 246 dodań i 33 usunięć

118
README.md
Wyświetl plik

@ -117,21 +117,22 @@ there is a workround if it's impossible to upgrade. See
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.
20. [Dial widget](./README.md#20-dial-widget) Display multiple vectors. 20. [Dial widget](./README.md#20-dial-widget) Display multiple vectors.
21. [Knob widget](./README.md#21-knob-widget) Rotary potentiometer float entry. 21. [Knob widget](./README.md#21-knob-widget) Rotary potentiometer float entry.
22. [Menu class](./README.md#22-menu-class) 22. [Adjuster widget](./README.md#22-adjuster-widget) Space saving way to enter floats.
23. [Graph plotting](./README.md#22-graph-plotting) Widgets for Cartesian and polar graphs. 23. [Menu class](./README.md#23-menu-class)
23.1 [Concepts](./README.md#231-concepts) 24. [Graph plotting](./README.md#24-graph-plotting) Widgets for Cartesian and polar graphs.
     23.1.1 [Graph classes](./README.md#2311-graph-classes) 24.1 [Concepts](./README.md#241-concepts)
     23.1.2 [Curve classes](./README.md#2312-curve-classes)      24.1.1 [Graph classes](./README.md#2411-graph-classes)
     23.1.3 [Coordinates](./README.md#2313-coordinates)      24.1.2 [Curve classes](./README.md#2412-curve-classes)
23.2 [Graph classes](./README.md#231-graph-classes)      24.1.3 [Coordinates](./README.md#2413-coordinates)
     23.2.1 [Class CartesianGraph](./README.md#2321-class-cartesiangraph) 24.2 [Graph classes](./README.md#242-graph-classes)
     23.2.2 [Class PolarGraph](./README.md#2322-class-polargraph)      24.2.1 [Class CartesianGraph](./README.md#2421-class-cartesiangraph)
23.3 [Curve classes](./README.md#233-curve-classes)      24.2.2 [Class PolarGraph](./README.md#2422-class-polargraph)
     23.3.1 [Class Curve](./README.md#2331-class-curve) 24.3 [Curve classes](./README.md#243-curve-classes)
     23.3.2 [Class PolarCurve](./README.md#2332-class-polarcurve)      24.3.1 [Class Curve](./README.md#2431-class-curve)
23.4 [Class TSequence](./README.md#234-class-tsequence) Plotting realtime, time sequential data.      24.3.2 [Class PolarCurve](./README.md#2432-class-polarcurve)
24. [Old firmware](./README.md#24-old-firmware) For users of color displays who can't run current firmware. 24.4 [Class TSequence](./README.md#244-class-tsequence) Plotting realtime, time sequential data.
25. [Realtime applications](./README.md#25-realtime-applications) Accommodating tasks requiring fast RT performance. 25. [Old firmware](./README.md#25-old-firmware) For users of color displays who can't run current firmware.
26. [Realtime applications](./README.md#26-realtime-applications) Accommodating tasks requiring fast RT performance.
[Appendix 1 Application design](./README.md#appendix-1-application-design) Tab order, button layout, encoder interface, use of graphics primitives [Appendix 1 Application design](./README.md#appendix-1-application-design) Tab order, button layout, encoder interface, use of graphics primitives
# 1. Basic concepts # 1. Basic concepts
@ -520,6 +521,8 @@ minimal and aim to demonstrate a single technique.
* `tbox.py` Text boxes and user-controlled scrolling. * `tbox.py` Text boxes and user-controlled scrolling.
* `tstat.py` A demo of the `Meter` class with data sensitive regions. * `tstat.py` A demo of the `Meter` class with data sensitive regions.
* `menu.py` A multi-level menu. * `menu.py` A multi-level menu.
* `adjuster.py` Simple demo of the `Adjuster` control.
* `adjust_vec.py` A pair of `Adjuster`s vary a vector.
### 1.11.2 Test scripts ### 1.11.2 Test scripts
@ -2272,8 +2275,8 @@ Optional keyword only arguments:
* `bdcolor=False` Color of border. If `False` no border will be drawn. If a * `bdcolor=False` Color of border. If `False` no border will be drawn. If a
color is provided, a border line will be drawn around the control. color is provided, a border line will be drawn around the control.
* `prcolor=None` If `active`, in precision mode the white focus border changes * `prcolor=None` If `active`, in precision mode the white focus border changes
to yellow to for a visual indication. An alternative color can be provided. to yellow for a visual indication. An alternative color can be provided.
`WHITE` will defeat this change. `WHITE` defeats this change; `False` disables precision mode.
* `callback=dolittle` Callback function runs when the user moves the knob or * `callback=dolittle` Callback function runs when the user moves the knob or
the value is changed programmatically. the value is changed programmatically.
* `args=[]` A list/tuple of arguments for above callback. * `args=[]` A list/tuple of arguments for above callback.
@ -2296,7 +2299,58 @@ value changes. This enables dynamic color change.
###### [Contents](./README.md#0-contents) ###### [Contents](./README.md#0-contents)
# 22 Menu class # 22. Adjuster widget
```python
from gui.widgets.adjuster import Adjuster
```
The `Adjuster` is a space saving version of the `Knob`. It is normally paired
with a `Label` which provides user feedback of the value. It has a range of
0.0 to 1.0 and a visual arc of 270°. User code can provide arbitrary scaling
or nonlinear operation. This is demonstrated in `demos/adjuster.py`. The
widget was inspired by discussions with the author of
[this project](https://www.instructables.com/Poor-Mans-Waveform-Generator-Based-on-RP2040-Raspb/).
Constructor mandatory positional args:
1. `writer` The `Writer` instance. This defines the control's height.
2. `row` Location on screen.
3. `col`
Optional keyword only arguments:
* `value=0.0` Initial value. By default the knob will be at its most
counter-clockwise position.
* `fgcolor=None` Color of foreground (the control itself). If `None` the
`Writer` foreground default is used.
* `bgcolor=None` Background color of object. If `None` the `Writer` background
default is used.
* `color=None` Fill color for the control knob. Default: no fill.
* `prcolor=None` In precision mode the white focus border changes to yellow
for a visual indication. An alternative color can be provided. `WHITE` defeats
the change; `False` disables precision mode.
* `callback=dolittle` Callback function runs when the user moves the knob or
the value is changed programmatically.
* `args=[]` A list/tuple of arguments for above callback.
Methods:
* `greyed_out` Optional Boolean argument `val=None`. If `None` returns the
current 'greyed out' status of the control. Otherwise enables or disables it,
showing it in its new state.
* `value` Optional argument `val`. If set, adjusts the pointer to
correspond to the new value. The move callback will run. The method constrains
the range to 0.0 to 1.0. Always returns the control's value.
### Callback
The callback receives an initial arg being the widget instance followed by any
user supplied args. The callback can be a bound method, typically of a `Screen`
subclass. The callback runs when the widget is instantiated and whenever the
value changes. Typically the callback will adjust the text displayed on a
linked label.
###### [Contents](./README.md#0-contents)
# 23 Menu class
```python ```python
from gui.widgets.menu import Menu from gui.widgets.menu import Menu
@ -2404,7 +2458,7 @@ different callback if the application required it.
###### [Contents](./README.md#0-contents) ###### [Contents](./README.md#0-contents)
# 23. Graph Plotting # 24. Graph Plotting
```python ```python
from gui.widgets.graph import PolarGraph, PolarCurve, CartesianGraph, Curve, TSequence from gui.widgets.graph import PolarGraph, PolarCurve, CartesianGraph, Curve, TSequence
@ -2417,7 +2471,7 @@ from gui.widgets.graph import PolarGraph, PolarCurve, CartesianGraph, Curve, TSe
For example code see `gui/demos/plot.py`. For example code see `gui/demos/plot.py`.
## 23.1 Concepts ## 24.1 Concepts
Data for Cartesian graphs constitutes a sequence of x, y pairs, for polar Data for Cartesian graphs constitutes a sequence of x, y pairs, for polar
graphs it is a sequence of complex `z` values. The module supports three graphs it is a sequence of complex `z` values. The module supports three
@ -2427,13 +2481,13 @@ common cases:
3. One or more `y` values arrive gradually. The `X` axis represents time. This 3. One or more `y` values arrive gradually. The `X` axis represents time. This
is a simplifying case of 2. is a simplifying case of 2.
### 23.1.1 Graph classes ### 24.1.1 Graph classes
A user program first instantiates a graph object (`PolarGraph` or A user program first instantiates a graph object (`PolarGraph` or
`CartesianGraph`). This creates an empty graph image upon which one or more `CartesianGraph`). This creates an empty graph image upon which one or more
curves may be plotted. Graphs are passive widgets so cannot accept user input. curves may be plotted. Graphs are passive widgets so cannot accept user input.
### 23.1.2 Curve classes ### 24.1.2 Curve classes
The user program then instantiates one or more curves (`Curve` or The user program then instantiates one or more curves (`Curve` or
`PolarCurve`) as appropriate to the graph. Curves may be assigned colors to `PolarCurve`) as appropriate to the graph. Curves may be assigned colors to
@ -2448,7 +2502,7 @@ Where it is required to plot realtime data as it arrives, this is achieved
via calls to the curve's `point` method. If a prior point exists it causes a via calls to the curve's `point` method. If a prior point exists it causes a
line to be drawn connecting the point to the last one drawn. line to be drawn connecting the point to the last one drawn.
### 23.1.3 Coordinates ### 24.1.3 Coordinates
`PolarGraph` and `CartesianGraph` objects are subclassed from `Widget` and are `PolarGraph` and `CartesianGraph` objects are subclassed from `Widget` and are
positioned accordingly by `row` and `col` with a 2-pixel outside border. The positioned accordingly by `row` and `col` with a 2-pixel outside border. The
@ -2464,9 +2518,9 @@ unit circle but will be clipped to the rectangular graph boundary.
###### [Contents](./README.md#0-contents) ###### [Contents](./README.md#0-contents)
## 23.2 Graph classes ## 24.2 Graph classes
### 23.2.1 Class CartesianGraph ### 24.2.1 Class CartesianGraph
Constructor. Constructor.
Mandatory positional arguments: Mandatory positional arguments:
@ -2493,7 +2547,7 @@ Keyword only arguments (all optional):
Method: Method:
* `show` No args. Redraws the empty graph. Used when plotting time sequences. * `show` No args. Redraws the empty graph. Used when plotting time sequences.
### 23.2.2 Class PolarGraph ### 24.2.2 Class PolarGraph
Constructor. Constructor.
Mandatory positional arguments: Mandatory positional arguments:
@ -2518,9 +2572,9 @@ Method:
###### [Contents](./README.md#0-contents) ###### [Contents](./README.md#0-contents)
## 23.3 Curve classes ## 24.3 Curve classes
### 23.3.1 Class Curve ### 24.3.1 Class Curve
The Cartesian curve constructor takes the following positional arguments: The Cartesian curve constructor takes the following positional arguments:
@ -2560,7 +2614,7 @@ To plot x values from 1000 to 4000 we would set the `origin` x value to 1000
and the `excursion` x value to 3000. The `excursion` values scale the plotted and the `excursion` x value to 3000. The `excursion` values scale the plotted
values to fit the corresponding axis. values to fit the corresponding axis.
### 23.3.2 Class PolarCurve ### 24.3.2 Class PolarCurve
The constructor takes the following positional arguments: The constructor takes the following positional arguments:
@ -2594,7 +2648,7 @@ Complex points should lie within the unit circle to be drawn within the grid.
###### [Contents](./README.md#0-contents) ###### [Contents](./README.md#0-contents)
## 23.4 Class TSequence ## 24.4 Class TSequence
A common task is the acquisition and plotting of real time data against time, A common task is the acquisition and plotting of real time data against time,
such as hourly temperature and air pressure readings. This class facilitates such as hourly temperature and air pressure readings. This class facilitates
@ -2658,7 +2712,7 @@ class TSeq(Screen):
``` ```
###### [Contents](./README.md#0-contents) ###### [Contents](./README.md#0-contents)
# 24. Old firmware # 25. Old firmware
Current firmware is highly recommended. For users of color displays who cannot Current firmware is highly recommended. For users of color displays who cannot
run V1.17 or later it is possible to run under V1.15+. This involves copying run V1.17 or later it is possible to run under V1.15+. This involves copying
@ -2666,7 +2720,7 @@ run V1.17 or later it is possible to run under V1.15+. This involves copying
to `gui/core/writer.py`. This uses Python code to render text if the firmware to `gui/core/writer.py`. This uses Python code to render text if the firmware
or driver are unable to support fast rendering. or driver are unable to support fast rendering.
# 25. Realtime applications # 26. Realtime applications
Screen refresh is performed in a continuous loop with yields to the scheduler. Screen refresh is performed in a continuous loop with yields to the scheduler.
In normal applications this works well, however a significant proportion of In normal applications this works well, however a significant proportion of

Wyświetl plik

@ -0,0 +1,52 @@
# adjust_vec.py Demo of Adjusters linked to a dial
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# hardware_setup must be imported before other modules because of RAM use.
import hardware_setup # Create a display instance
import cmath
from gui.core.ugui import Screen, ssd
from gui.widgets.label import Label
from gui.widgets.buttons import CloseButton
from gui.widgets.adjuster import Adjuster
from gui.widgets.dial import Dial, Pointer
from gui.core.writer import CWriter
# Font for CWriter
import gui.fonts.font10 as font
from gui.core.colors import *
class BaseScreen(Screen):
def __init__(self):
super().__init__()
wri = CWriter(ssd, font, GREEN, BLACK)
col = 2
row = 2
self.dial = Dial(wri, row, col, bdcolor=None)
self.vec = Pointer(self.dial)
a = Adjuster(wri, row, self.dial.mcol + 2, fgcolor=BLUE, callback=self.phi_cb)
Label(wri, row, a.mcol + 2, "ϕ", fgcolor=BLUE)
a = Adjuster(wri, row + 20, self.dial.mcol + 2, value=1, fgcolor=MAGENTA, callback=self.r_cb)
Label(wri, row + 20, a.mcol + 2, "r", fgcolor=MAGENTA)
CloseButton(wri) # Quit the application
def phi_cb(self, adj):
v = adj.value()
r, phi = cmath.polar(self.vec.value())
self.vec.value(cmath.rect(r, v * 2 * cmath.pi))
def r_cb(self, adj):
v = adj.value()
r, phi = cmath.polar(self.vec.value())
self.vec.value(cmath.rect(v, phi))
def test():
print('Alter a vector using Adjuster control.')
Screen.change(BaseScreen) # A class is passed here, not an instance.
test()

Wyświetl plik

@ -0,0 +1,58 @@
# adjuster.py Demo of Adjusters linked to Labels
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# hardware_setup must be imported before other modules because of RAM use.
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets.label import Label
from gui.widgets.buttons import CloseButton
from gui.widgets.adjuster import Adjuster
from gui.core.writer import CWriter
# Font for CWriter
import gui.fonts.font10 as font
from gui.core.colors import *
class BaseScreen(Screen):
def __init__(self):
super().__init__()
wri = CWriter(ssd, font, GREEN, BLACK)
col = 2
row = 2
self.lbl1 = Label(wri, row, col, 60, bdcolor=RED)
a = Adjuster(wri, row, self.lbl1.mcol + 2, fgcolor=RED, callback=self.adj1_callback)
Label(wri, row, a.mcol + 2, "Simple")
row = self.lbl1.mrow + 5
self.lbl2 = Label(wri, row, col, 60, bdcolor=RED)
a =Adjuster(wri, row, self.lbl2.mcol + 2, fgcolor=RED, value=0.5, callback=self.adj2_callback)
Label(wri, row, a.mcol + 2, "Scale")
row = self.lbl2.mrow + 5
self.lbl3 = Label(wri, row, col, 60, bdcolor=YELLOW)
a = Adjuster(wri, row, self.lbl3.mcol + 2, fgcolor=YELLOW, callback=self.adj3_callback)
Label(wri, row, a.mcol + 2, "Log")
CloseButton(wri) # Quit the application
def adj1_callback(self, adj):
v = adj.value() # Typically do mapping here
self.lbl1.value(f'{v:4.2f}')
def adj2_callback(self, adj):
v = (adj.value() - 0.5) * 10 # Scale and offset
self.lbl2.value(f'{v:4.2f}')
def adj3_callback(self, adj):
v = 10 ** (3 * adj.value()) # Log 3 decades
self.lbl3.value(f'{v:4.2f}')
def test():
print('Demo of Adjuster control.')
Screen.change(BaseScreen) # A class is passed here, not an instance.
test()

Wyświetl plik

@ -0,0 +1,49 @@
# adjuster.py Tiny control knob (rotary potentiometer) widget
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
from gui.core.ugui import LinearIO, display
import math
TWOPI = 2 * math.pi
# Null function
dolittle = lambda *_ : None
# *********** CONTROL KNOB CLASS ***********
class Adjuster(LinearIO):
def __init__(self, writer, row, col, *, value=0.0,
fgcolor=None, bgcolor=None, color=None, prcolor=None,
callback=dolittle, args=[]):
height = writer.height # Match a user-linked Label
super().__init__(writer, row, col, height, height, fgcolor,
bgcolor, False, value, True, prcolor)
super()._set_callbacks(callback, args)
radius = height / 2
self.arc = 1.5 * math.pi # Usable angle of control
self.radius = radius
self.xorigin = col + radius
self.yorigin = row + radius
self.color = color
self.draw = True # Ensure a redraw on next refresh
# Run callback (e.g. to set dynamic colors)
self.callback(self, *self.args)
def show(self):
if super().show(False): # Honour bgcolor
arc = self.arc
radius = self.radius
if self.color is not None:
display.fillcircle(self.xorigin, self.yorigin, radius, self.color)
display.circle(self.xorigin, self.yorigin, radius, self.fgcolor)
display.circle(self.xorigin, self.yorigin, radius, self.fgcolor)
self._drawpointer(self._value, self.fgcolor) # draw new
def _drawpointer(self, value, color):
arc = self.arc
length = self.radius - 1
angle = value * arc - arc / 2
x_end = int(self.xorigin + length * math.sin(angle))
y_end = int(self.yorigin - length * math.cos(angle))
display.line(int(self.xorigin), int(self.yorigin), x_end, y_end, color)

Wyświetl plik

@ -51,4 +51,4 @@ sel = Pin(16, Pin.IN, Pin.PULL_UP) # Operate current control
prev = Pin(18, Pin.IN, Pin.PULL_UP) # Move to previous control prev = Pin(18, Pin.IN, Pin.PULL_UP) # Move to previous control
increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value
decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease control's value decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease control's value
display = Display(ssd, nxt, sel, prev, increase, decrease) display = Display(ssd, nxt, sel, prev, increase, decrease, 5)