Add aclock, dropdown and primitives demos.

pull/8/head
Peter Hinch 2021-06-24 18:42:16 +01:00
rodzic 0d74bd317f
commit 3b26ab9528
20 zmienionych plików z 271 dodań i 16 usunięć

Wyświetl plik

@ -396,15 +396,19 @@ Demos are run by issuing (for example):
These will run on screens of 128x128 pixels or above. The initial ones are
minimal and aim to demonstrate a single technique.
* `simple.py` Minimal demo discussed below. `Button` presses print to REPL.
* `checkbox` A `Checkbox` controlling an `LED`.
* `checkbox.py` A `Checkbox` controlling an `LED`.
* `slider.py` A `Slider` whose color varies with its value.
* `slider_label.py` A `Slider` updating a `Label`. Good for trying precision
mode.
* `linked_slider.py` One `Slider` updating two others, and a coding "wrinkle"
* `linked_sliders.py` One `Slider` updating two others, and a coding "wrinkle"
required for doing this.
* `dropdown.py` A dropdown list updates a `Label`.
* `dialog.py` `DialogBox` demo. Illustrates the screen change mechanism.
* `screen_change.py` A `Pushbutton` causing a screen change using a re-usable
"forward" button.
* `primitives.py` Use of graphics primitives.
* `aclock.py` An analog clock using the `Dial` vector display. Also shows
screen layout using widget metrics. Has a simple `uasyncio` task.
* `tbox.py` Text boxes and user-controlled scrolling.
### 1.11.2 Test scripts
@ -766,17 +770,21 @@ Constructor args:
The constructor displays the string at the required location.
Methods:
1. `value` Redraws the label. This takes the following args:
* `text=None` The text to display. If `None` displays last value.
* ` 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.
Returns the current text string.
Method:
`value` Redraws the label. This takes the following args:
* `text=None` The text to display. If `None` displays last value.
* `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.
Returns the current text string.
If populating a label would cause it to extend beyond the screen boundary a
If the `value` method is called with a text string too long for the `Label` a
`ValueError` will be thrown. The width specified to the constructor should be
sufficient for all possible values.
If constructing a label would cause it to extend beyond the screen boundary a
warning is printed at the console. The label may appear at an unexpected place.
The following is a complete "Hello world" script.
```python
@ -1368,6 +1376,11 @@ Methods:
* `bdcolor=None` Border color. As per above except that if `False` is
passed, no border is displayed. This clears a previously drawn border.
### 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)
# 17. Slider and HorizSlider widgets
@ -1441,6 +1454,11 @@ 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. This enables dynamic color change. See `gui/demos/active.py`.
### Legends
Depending on the font in use for legends additional space may be required
around sliders to display all legends.
###### [Contents](./README.md#0-contents)
# 18. Scale widget
@ -2173,8 +2191,30 @@ The apparently obvious solution of designing a vertical `Scale` is tricky owing
to the fact that the length of the internal text can be substantial and
variable.
## Screen layout
Widgets are positioned using absolute `row` and `col` coordinates. These may
optionally be calculated using the metrics of other widgets. This facilitates
relative positioning which can make layouts easier to modify. Such layouts can
also automatically adapt to changes of fonts. To simplify this, all widgets
have the following bound variables, which should be considered read-only:
* `height` As specified. Does not include border.
* `width` Ditto.
* `rows` Height including borders.
* `cols` Width with borders.
This support is fairly "micro" and does not take account of labels and legends.
This means that `rows` and `cols` for `Dial`, `Meter`, `Slider` and
`HorizSlider` do not necessarily reflect the full amount of space used by the
control.
The `aclock.py` demo provides a simple example of this approach.
## Use of graphics primitives
See [demo primitives.py](./gui/demos/primitives.py).
These notes are for those wishing to draw directly to the `Screen` instance.
This is done by providing the user `Screen` class with an `after_open()` method
which is written to issue the display driver calls.

Wyświetl plik

@ -550,6 +550,8 @@ class Widget:
self.col = col
self.height = height
self.width = width
self.rows = height + 4 # For metrics. Default: allow for border.
self.cols = width + 4
self.visible = True # Used by ButtonList class for invisible buttons
self.draw = True # Signals that obect must be redrawn
self._value = value

Wyświetl plik

@ -0,0 +1,89 @@
# aclock.py micro-gui analog clock demo.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# Initialise hardware and framebuf before importing modules.
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets.label import Label
from gui.widgets.dial import Dial, Pointer
from gui.widgets.buttons import CloseButton
# Now import other modules
from cmath import rect, pi
import uasyncio as asyncio
import time
from gui.core.writer import CWriter
# Font for CWriter
import gui.fonts.arial10 as font
from gui.core.colors import *
# Adjust passed Dial and Label instances to show current time and date.
async def aclock(dial, lbldate, lbltim):
# Return a unit vector of phase phi. Multiplying by this will
# rotate a vector anticlockwise which is mathematically correct.
# Alas clocks modelled on sundials were invented in the northern
# hemisphere. Otherwise they would have rotated widdershins
# in accordance with maths. Hence negative sign when called.
def uv(phi):
return rect(1, phi)
def suffix(n):
if n in (1, 21, 31):
return 'st'
if n in (2, 22):
return 'nd'
if n in (3, 23):
return 'rd'
return th
days = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')
months = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
hrs = Pointer(dial)
mins = Pointer(dial)
secs = Pointer(dial)
hstart = 0 + 0.7j # Pointer lengths. Will rotate relative to top.
mstart = 0 + 1j
sstart = 0 + 1j
while True:
t = time.localtime()
hrs.value(hstart * uv(-t[3] * pi/6 - t[4] * pi / 360), CYAN)
mins.value(mstart * uv(-t[4] * pi/30), CYAN)
secs.value(sstart * uv(-t[5] * pi/30), RED)
lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5]))
lbldate.value('{} {}{} {} {}'.format(days[t[6]], t[2], suffix(t[2]), months[t[1] - 1], t[0]))
await asyncio.sleep(1)
class BaseScreen(Screen):
def __init__(self):
super().__init__()
labels = {'bdcolor' : RED,
'fgcolor' : WHITE,
'bgcolor' : DARKGREEN,
}
wri = CWriter(ssd, font, GREEN, BLACK, verbose=False)
gap = 4 # Vertical gap between widgets
dial = Dial(wri, 2, 2, height = 70, ticks = 12,
fgcolor = GREEN, pip = GREEN)
# Set up clock display: instantiate labels
row = dial.rows + gap
lbldate = Label(wri, row, 2, 100, **labels)
row += lbldate.rows + gap
lbltim = Label(wri, row, 2, '00.00.00', **labels)
self.reg_task(aclock(dial, lbldate, lbltim))
CloseButton(wri)
def test():
print('Analog clock demo.')
Screen.change(BaseScreen)
test()

Wyświetl plik

@ -1,5 +1,8 @@
# active.py micro-gui demo of widgets that respond to user control
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# Create SSD instance. Must be done first because of RAM use.
import hardware_setup
from gui.core.ugui import Screen, ssd

Wyświetl plik

@ -1,5 +1,8 @@
# checkbox.py Minimal micro-gui demo showing a Checkbox updating an LED.
# 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

Wyświetl plik

@ -1,5 +1,8 @@
# dialog.py micro-gui demo of the DialogBox class
# 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, Window, ssd

Wyświetl plik

@ -0,0 +1,45 @@
# dropdown.py micro-gui demo of Dropdown class
# 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, Window, ssd
from gui.widgets.label import Label
from gui.widgets.buttons import CloseButton
from gui.widgets.dropdown import Dropdown
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, verbose=False)
col = 2
row = 2
Dropdown(wri, row, col,
elements = ('hydrogen', 'helium', 'neon', 'xenon', 'radon'),
bdcolor = GREEN, bgcolor = DARKGREEN,
callback=self.ddcb)
row += 30
self.lbl = Label(wri, row, col, 90, bdcolor=RED)
CloseButton(wri) # Quit the application
def ddcb(self, dd):
if hasattr(self, 'lbl'):
self.lbl.value(dd.textvalue())
def test():
print('Dropdown demo.')
Screen.change(BaseScreen)
test()

Wyświetl plik

@ -1,5 +1,8 @@
# linked_sliders.py Minimal micro-gui demo one Slider controlling two others.
# 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

Wyświetl plik

@ -0,0 +1,45 @@
# primitives.py micro-gui demo of use of graphics primitives
# 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, Window, ssd, display
from gui.widgets.label import Label
from gui.widgets.buttons import CloseButton
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, verbose=False)
col = 2
row = 2
Label(wri, row, col, 'Primitives')
CloseButton(wri) # Quit the application
def after_open(self):
display.usegrey(False)
# Coordinates are x, y as per framebuf
# circle method is in Display class only
display.circle(70, 70, 30, RED, 2)
# These methods exist in framebuf, so also in SSD and Display
ssd.hline(0, 127, 128, BLUE)
ssd.vline(127, 0, 128, BLUE)
def test():
print('Primitives demo.')
Screen.change(BaseScreen)
test()

Wyświetl plik

@ -1,5 +1,8 @@
# screen_change.py Minimal micro-gui demo showing a Button causing a screen change.
# 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

Wyświetl plik

@ -1,5 +1,8 @@
# screens.py micro-gui demo of multiple screens, dropdowns etc
# 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, Window, ssd

Wyświetl plik

@ -1,5 +1,8 @@
# simple.py Minimal micro-gui demo.
# 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

Wyświetl plik

@ -1,5 +1,8 @@
# slider.py Minimal micro-gui demo showing a Slider with variable color.
# 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
from gui.core.ugui import Screen, ssd

Wyświetl plik

@ -1,5 +1,8 @@
# slider_label.py Minimal micro-gui demo showing a Slider controlling a Label.
# 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

Wyświetl plik

@ -1,5 +1,8 @@
# various.py micro-gui demo of multiple controls on a large display
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# Initialise hardware and framebuf before importing modules.
# Create SSD instance. Must be done first because of RAM use.
import hardware_setup

Wyświetl plik

@ -56,7 +56,7 @@ async def ptr_test(dial):
ptr.value(v, RED)
await asyncio.sleep_ms(200)
# Analog clock demo. Note this could also be achieved using the Dial class.
# Analog clock demo.
async def aclock(dial, lbldate, lbltim):
uv = lambda phi : rect(1, phi) # Return a unit vector of phase phi
days = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',

Wyświetl plik

@ -11,7 +11,8 @@ class Checkbox(Widget):
def __init__(self, writer, row, col, *, height=30, fillcolor=None,
fgcolor=None, bgcolor=None, bdcolor=False,
callback=dolittle, args=[], value=False, active=True):
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor, value, active)
super().__init__(writer, row, col, height, height, fgcolor,
bgcolor, bdcolor, value, active)
super()._set_callbacks(callback, args)
self.fillcolor = fillcolor

Wyświetl plik

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

Wyświetl plik

@ -23,13 +23,13 @@ class Meter(Widget):
Label(writer, row + height + writer.height // 2, col, label)
self.style = style
self.ptcolor = ptcolor if ptcolor is not None else self.fgcolor
if legends is not None: # Legends
if legends is not None: # Legends are static
x = col + width + 4
y = row + height
dy = 0 if len(legends) <= 1 else height / (len(legends) -1)
yl = y - writer.height / 2 # Start at bottom
for legend in legends:
Label(writer, int(yl), x, legend)
l = Label(writer, round(yl), x, legend)
yl -= dy
self.value(value)

Wyświetl plik

@ -64,6 +64,7 @@ class Slider(LinearIO):
display.rect(self.slot_x0, self.slot_y0, slot_w, slot_len, self.fgcolor)
txtcolor = GREY if self.greyed_out() else self.fontcolor
# Dynamic processing of legends supports greying-out
if self.legends is not None:
if len(self.legends) <= 1:
dy = 0