V0.1.5 3-button adjustment mode.

pull/16/head
Peter Hinch 2022-02-06 12:05:38 +00:00
rodzic d2247be40a
commit f7c33cb408
33 zmienionych plików z 249 dodań i 257 usunięć

Wyświetl plik

@ -3,15 +3,17 @@
This is a lightweight, portable, MicroPython GUI library for displays having
drivers subclassed from `framebuf`. Written in Python it runs under a standard
MicroPython firmware build. Options for data input comprise:
* Via from two to five pushbuttons depending on the application.
* Via a switch-based navigation joystick.
* Two pushbuttons: limited capabilities with some widgets unusable for input.
* Three pushbuttons with full capability.
* Five pushbuttons: full capability, less "modal" interface.
* A switch-based navigation joystick: another way to implement five buttons.
* Via two pushbuttons and a rotary encoder such as
[this one](https://www.adafruit.com/product/377).
[this one](https://www.adafruit.com/product/377). An intuitive interface.
It is larger and more complex than `nano-gui` owing to the support for input.
It enables switching between screens and launching modal windows. In addition
to `nano-gui` widgets it supports listboxes, dropdown lists, various means of
entering or displaying floating point values, and other widgets.
to `nano-gui` widgets it supports menus, listboxes, dropdown lists, various
means of entering or displaying floating point values, and other widgets.
It is compatible with all display drivers for
[nano-gui](https://github.com/peterhinch/micropython-nano-gui) so is portable
@ -56,9 +58,12 @@ target and a C device driver (unless you can acquire a suitable binary).
# Project status
Code has been tested on ESP32, Pi Pico and Pyboard. The API shuld be stable.
Code is new and issues are likely: please report any found. The project is
under development so check for updates.
February 2022: This has had a significant upgrade to support use with only
three buttons as devised by Bart Cerneels. Simplified widget import. Existing
users should replace the entire `gui` tree.
Code has been tested on ESP32, Pi Pico and Pyboard. This is under development
so check for updates.
Firmware V1.17 has provided a major boost to text rendering speed on color
display. V1.17 or later is now a requirement for color displays, although
@ -155,10 +160,8 @@ shows a message and has "Yes" and "No" buttons which trigger a callback.
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 Button, CloseButton
from gui.widgets import Label, Button, CloseButton
from gui.core.writer import CWriter
# Font for CWriter
import gui.fonts.arial10 as arial10
from gui.core.colors import *
@ -256,16 +259,20 @@ The GUI requires from 2 to 5 pushbuttons for control. These are:
An alternative is to replace buttons 4 and 5 with a quadrature encoder knob
such as [this one](https://www.adafruit.com/product/377). That device has a
switch which operates when the knob is pressed: this may be wired for the
`Select` button.
`Select` button. This provides the most intuitive operation.
Many widgets such as `Pushbutton` or `Checkbox` objects require only the
`Select` button to operate: it is possible to design an interface with a subset
of `micro-gui` widgets which requires only the first two buttons.
of `micro-gui` widgets which requires only the first two buttons. With three
buttons all widgets may be used without restriction.
Widgets such as `Listbox` objects, dropdown lists (`Dropdown`), and those for
floating point data entry require the `Increase` and `Decrease` buttons (or an
encoder) to select a data item or to adjust the linear value. This is discussed
in [Floating Point Widgets](./README.md#112-floating-point-widgets).
floating point data entry can use the `Increase` and `Decrease` buttons (or an
encoder) to select a data item or to adjust the linear value. If three buttons
are provided, the GUI will enter "adjust" mode in response to a double-click
of `Select`. In this mode `Prev` and `Next` act to decrease and increase the
widget's value. A further double-click restores normal navigation. This is
discussed in [Floating Point Widgets](./README.md#112-floating-point-widgets).
The currently selected `Widget` is identified by a white border: the `focus`
moves between widgets via `Next` and `Prev`. Only `active` `Widget` instances
@ -563,7 +570,8 @@ one second. The GUI will respond by changing the border color from white
(i.e. has focus) to yellow. In this mode a brief press of `increase` or
`decrease` or small movement of an encoder will have a reduced effect (0.05%).
Fine mode may be cancelled by pressing `select` or by moving the focus to
another control.
another control. This also works in three-button mode, with `Next` and `Prev`
performing the adjustments.
In the case of slider and knob controls the precision of fine mode exceeds that
of the visual appearance of the widget: fine changes can be too small to see.
@ -596,8 +604,7 @@ of contiguous RAM.
import hardware_setup # Instantiate display, setup color LUT (if present)
from gui.core.ugui import Screen, ssd
from gui.widgets.label import Label
from gui.widgets.buttons import Button, CloseButton
from gui.widgets import Label, Button, CloseButton
from gui.core.writer import CWriter
# Font for CWriter
@ -940,7 +947,7 @@ constructor and is closed by issuing the `close()` static method.
# 6. Label widget
```python
from gui.widgets.label import Label
from gui.widgets import Label
```
![Image](./images/label.JPG)
@ -1001,8 +1008,7 @@ from gui.core.ugui import Screen
from gui.core.writer import CWriter
from gui.core.colors import *
from gui.widgets.label import Label
from gui.widgets.buttons import CloseButton
from gui.widgets import Label, CloseButton
import gui.fonts.freesans20 as freesans20
@ -1022,7 +1028,7 @@ Screen.change(BaseScreen)
# 7. LED widget
```python
from gui.widgets.led import LED
from gui.widgets import LED
```
![Image](./images/led.JPG)
@ -1061,7 +1067,7 @@ controlled with `led(True)` or `led(False)`.
# 8. Checkbox widget
```python
from gui.widgets.checkbox import Checkbox
from gui.widgets import Checkbox
```
![Image](./images/checkbox.JPG)
This provides for Boolean data entry and display. In the `True` state the
@ -1103,7 +1109,7 @@ Methods:
```python
from gui.core.colors import * # Colors and shapes
from gui.widgets.buttons import Button
from gui.widgets import Button
```
![Image](./images/pushbuttons.JPG)
@ -1145,8 +1151,6 @@ Optional keyword only arguments:
for example media playback symbols.
* `callback=dolittle` Callback function which runs when button is pressed.
* `args=()` A list/tuple of arguments for the above callback.
* `onrelease=False` If `True` the callback will occur when the `select`
pushbutton is released otherwise it will occur when pressed.
Method:
* `greyed_out` Optional Boolean argument `val=None`. If `None` returns the
@ -1182,7 +1186,7 @@ Optional keyword only arguments:
```python
from gui.core.colors import * # Colors and shapes
from gui.widgets.buttons import Button, ButtonList
from gui.widgets import Button, ButtonList
```
A `ButtonList` groups a number of buttons together to implement a button which
@ -1241,7 +1245,7 @@ for t in table: # Buttons overlay each other at same location
```python
from gui.core.colors import * # Colors and shapes
from gui.widgets.buttons import Button, RadioButtons
from gui.widgets import Button, RadioButtons
```
![Image](./images/radiobuttons.JPG)
@ -1288,7 +1292,7 @@ for t in table:
# 12. Listbox widget
```python
from gui.widgets.listbox import Listbox
from gui.widgets import Listbox
```
![Image](./images/listbox.JPG)
@ -1397,7 +1401,7 @@ Screen.change(BaseScreen)
# 13. Dropdown widget
```python
from gui.widgets.dropdown import Dropdown
from gui.widgets import Dropdown
```
![Image](./images/dd_closed.JPG)
@ -1512,7 +1516,7 @@ Screen.change(BaseScreen)
# 14. DialogBox class
```python
from gui.widgets.dialog import DialogBox
from gui.widgets import DialogBox
```
![Image](./images/dialog.JPG)
@ -1566,7 +1570,7 @@ in `gui/demos/screens.py`.
# 15. Textbox widget
```python
from gui.widgets.textbox import Textbox
from gui.widgets import Textbox
```
![Image](./images/textbox.JPG)
@ -1635,7 +1639,7 @@ the oldest (topmost) being discarded as required.
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
from gui.widgets import Meter
```
![Image](./images/meter.JPG)
The two styles of `meter`, both showing a value of 0.65. This `passive` widget
@ -1727,7 +1731,7 @@ behaves similarly for data values between 0.9 and 1.0.
## 16.1 Region class
```python
from gui.widgets.region import Region
from gui.widgets import Region
```
Instantiating a `Region` associates it with a supporting widget (currently only
a `Meter`). Constructor positional args are as follows:
@ -1787,7 +1791,7 @@ callbacks to run as appropriate.
# 17. Slider and HorizSlider widgets
```python
from gui.widgets.sliders import Slider, HorizSlider
from gui.widgets import Slider, HorizSlider
```
![Image](./images/sliders.JPG)
@ -1865,7 +1869,7 @@ around sliders to display all legends.
# 18. Scale widget
```python
from gui.widgets.scale import Scale
from gui.widgets import Scale
```
![Image](./images/scale.JPG)
@ -2002,7 +2006,7 @@ precision. Each visible division on the control represents 10 integer units.
# 19. ScaleLog widget
```python
from gui.widgets.scale_log import ScaleLog
from gui.widgets import ScaleLog
```
![Image](./images/log_scale.JPG)
@ -2142,7 +2146,7 @@ def tickcb(f, c):
# 20. Dial widget
```python
from gui.widgets.dial import Dial, Pointer
from gui.widgets import Dial, Pointer
```
![Image](./images/dial.JPG) ![Image](./images/dial1.JPG)
@ -2225,8 +2229,7 @@ from gui.core.ugui import Screen
from gui.core.writer import CWriter
from gui.core.colors import *
from gui.widgets.dial import Dial, Pointer
from gui.widgets.buttons import CloseButton
from gui.widgets import Dial, Pointer, CloseButton
import gui.fonts.freesans20 as freesans20
async def run(dial):
@ -2259,7 +2262,7 @@ Screen.change(BaseScreen)
# 21. Knob widget
```python
from gui.widgets.knob import Knob
from gui.widgets import Knob
```
![Image](./images/knob.JPG)
@ -2317,7 +2320,7 @@ value changes. This enables dynamic color change.
# 22. Adjuster widget
```python
from gui.widgets.adjuster import Adjuster
from gui.widgets import Adjuster
```
![Image](./images/adjusters.jpg) ![Image](./images/adj_vector.jpg)
@ -2383,7 +2386,7 @@ basis. See code comments for further details.
# 23 Menu class
```python
from gui.widgets.menu import Menu
from gui.widgets import Menu
```
![Image](./images/menu.JPG)

Wyświetl plik

@ -55,4 +55,5 @@ PRECISION = 1
FG = 2
BG = 3
GREY_OUT = 4
color_map = [WHITE, YELLOW, WHITE, BLACK, GREY]
ADJUSTING = 5
color_map = [WHITE, YELLOW, WHITE, BLACK, GREY, LIGHTGREEN]

Wyświetl plik

@ -3,7 +3,7 @@
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019-2022 Peter Hinch
# Requires uasyncio V3
# Credit to Bart Cerneels for devising and prototyping the 3-button mode
import uasyncio as asyncio
from uasyncio import Event
@ -13,15 +13,14 @@ import gc
from gui.core.colors import *
from hardware_setup import ssd
from gui.primitives.delay_ms import Delay_ms
from gui.primitives.switch import Switch
from gui.primitives import Pushbutton
# Globally available singleton objects
display = None # Singleton instance
ssd = None
gc.collect()
__version__ = (0, 1, 4)
__version__ = (0, 1, 5)
# Null function
dolittle = lambda *_ : None
@ -30,60 +29,97 @@ async def _g():
pass
type_coro = type(_g())
# Navigation destinations
_FIRST = const(0)
_NEXT = const(1)
_PREV = const(2)
_LAST = const(3)
# Wrapper for ssd providing buttons and framebuf compatible methods
class Display:
verbose = True
# Input abstracts input from 2-5 pushbuttons or 3 buttons + encoder. Handles
# transitions between modes (normal, precision, adjustment)
class Input:
def __init__(self, objssd, nxt, sel, prev=None, incr=None, decr=None, encoder=False):
global display, ssd
self._next = Switch(nxt)
self._sel = Switch(sel)
self._last = None # Last switch pressed.
def __init__(self, nxt, sel, prev, incr, decr, encoder):
verbose = True
self._encoder = encoder # Encoder in use
self._precision = False # Precision mode
self._adj = False # Adjustment mode
# Count buttons
self._nb = sum(1 for x in (nxt, sel, prev, incr, decr) if x is not None)
# Mandatory buttons
self._next = Pushbutton(nxt)
self._sel = Pushbutton(sel, suppress=True)
# Call current screen bound method
self._next.close_func(self._closure, (self._next, Screen.ctrl_move, _NEXT))
self._sel.close_func(self._closure, (self._sel, Screen.sel_ctrl, 0))
self._sel.open_func(Screen.unsel)
self.height = objssd.height
self.width = objssd.width
self._next.press_func(Screen.ctrl_move, (_NEXT,))
self._sel.release_func(Screen.sel_ctrl)
if encoder or (self._nb > 2): # Can use precision mode when in adjust mode
self._sel.long_func(self.precision, (True,))
if self._nb == 3: # Special case of 3-button interface
self._sel.double_func(self.adj_mode) # Double click toggles adjust
# Optional buttons
self._prev = None
if prev is not None:
self._prev = Switch(prev)
self._prev.close_func(self._closure, (self._prev, Screen.ctrl_move, _PREV))
self._prev = Pushbutton(prev)
self._prev.press_func(Screen.ctrl_move, (_PREV,))
if encoder:
self.verbose and print('Using encoder.')
verbose and print('Using encoder.')
if incr is None or decr is None:
raise ValueError('Must specify pins for encoder.')
from gui.primitives.encoder import Encoder
self._enc = Encoder(incr, decr, div=encoder, callback=Screen.adjust)
else:
self.verbose and print('Using switches.')
verbose and print('Using {:d} switches.'.format(self._nb))
# incr and decr methods get the button as an arg.
if incr is not None:
sup = Switch(incr)
sup.close_func(self._closure, (sup, Screen.adjust, 1))
sup = Pushbutton(incr)
sup.press_func(Screen.adjust, (sup, 1))
if decr is not None:
sdn = Switch(decr)
sdn.close_func(self._closure, (sdn, Screen.adjust, -1))
sdn = Pushbutton(decr)
sdn.press_func(Screen.adjust, (sdn, -1))
def precision(self, val): # Also called by Screen.ctrl_move to cancel mode
if val:
if self._nb == 3 and not self._adj:
self.adj_mode()
self._precision = True
else:
self._precision = False
Screen.redraw_co()
def adj_mode(self, v=None): # Set, clear or toggle adjustment mode
if self._nb == 3: # Called from menu and dropdown widgets
self._adj = not self._adj if v is None else v
# Change button function
if self._adj:
self._prev.press_func(Screen.adjust, (self._prev, -1))
self._next.press_func(Screen.adjust, (self._next, 1))
else:
self._prev.press_func(Screen.ctrl_move, (_PREV,))
self._next.press_func(Screen.ctrl_move, (_NEXT,))
self._precision = False
Screen.redraw_co()
def encoder(self):
return self._encoder
def is_precision(self):
return self._precision
def is_adjust(self):
return self._adj
# Wrapper for ssd providing buttons and framebuf compatible methods
class Display:
def __init__(self, objssd, nxt, sel, prev=None, incr=None, decr=None, encoder=False):
global display, ssd
self.ipdev = Input(nxt, sel, prev, incr, decr, encoder)
self.height = objssd.height
self.width = objssd.width
self._is_grey = False # Not greyed-out
display = self # Populate globals
ssd = objssd
# Reject button presses where a button is already pressed.
# Execute if initialising, if same switch re-pressed or if last switch released
def _closure(self, switch, func, arg):
if (self._last is None) or (self._last == switch) or self._last():
self._last = switch
func(switch, arg)
def print_centred(self, writer, x, y, text, fgcolor=None, bgcolor=None, invert=False):
sl = writer.stringlen(text)
writer.set_textpos(ssd, y - writer.height // 2, x - sl // 2)
@ -208,21 +244,25 @@ class Screen:
rfsh_start = Event() # Refresh pauses until set (set by default).
rfsh_done = Event() # Flag a user task that a refresh was done.
@classmethod
def ctrl_move(cls, _, v):
@classmethod # Called by Input when status change needs redraw of current obj
def redraw_co(cls):
if cls.current_screen is not None:
obj = cls.current_screen.get_obj()
if obj is not None:
obj.draw = True
@classmethod
def ctrl_move(cls, v):
if cls.current_screen is not None:
display.ipdev.precision(False) # Cancel precision mode
cls.current_screen.move(v)
@classmethod
def sel_ctrl(cls, b, _):
def sel_ctrl(cls):
if cls.current_screen is not None:
display.ipdev.precision(False) # Cancel precision mode
cls.current_screen.do_sel()
@classmethod
def unsel(cls):
if cls.current_screen is not None:
cls.current_screen.unsel_i()
# Adjust the value of a widget. If an encoder is used, button arg
# is an int (discarded), val is the delta. If using buttons, 1st
# arg is the button, delta is +1 or -1
@ -269,6 +309,7 @@ class Screen:
cs_new = new_screen
else:
cs_new = cls_new_screen # An object, not a class
display.ipdev.adj_mode(False) # Ensure normal mode
cls.current_screen = cs_new
cs_new.on_open() # Optional subclass method
cs_new._do_open(cs_old) # Clear and redraw
@ -413,10 +454,6 @@ class Screen:
lo.show() # Re-display with new status
co.enter() # Tell object it has currency
co.show()
#elif isinstance(self, Window):
# Special case of Window with one object: leave
# without making changes (Dropdown in particular)
#Screen.back()
done = True
# Move currency to a specific control.
@ -439,11 +476,6 @@ class Screen:
if co is not None:
co.do_sel()
def unsel_i(self):
co = self.get_obj()
if co is not None:
co.unsel()
def do_adj(self, button, val):
co = self.get_obj()
if co is not None and hasattr(co, 'do_adj'):
@ -475,7 +507,7 @@ class Screen:
await asyncio.sleep_ms(500)
gc.collect()
gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())
#print(gc.mem_free())
# print(gc.mem_free())
# Very basic window class. Cuts a rectangular hole in a screen on which
# content may be drawn.
@ -570,7 +602,7 @@ class Widget:
self.bgcolor = bgcolor
# bdcolor is False if no border is to be drawn
self.bdcolor = bdcolor
# Default colors allow restoration after dynamic change
# Default colors allow restoration after dynamic change (Label)
self.def_fgcolor = fgcolor
self.def_bgcolor = bgcolor
self.def_bdcolor = bdcolor
@ -607,7 +639,6 @@ class Widget:
if self.screen != Screen.current_screen:
# Can occur if a control's action is to change screen.
return False # Subclass abandons
self.draw = False
self.draw_border()
# Blank controls' space
@ -629,8 +660,11 @@ class Widget:
h = self.height + 4
if self.has_focus() and not isinstance(self, DummyWidget):
color = color_map[FOCUS]
if hasattr(self, 'precision') and self.precision and self.prcolor is not None:
precision = hasattr(self, 'do_precision') and self.do_precision and display.ipdev.is_precision()
if precision:
color = self.prcolor
elif display.ipdev.is_adjust():
color = color_map[ADJUSTING]
dev.rect(x, y, w, h, color)
self.has_border = True
else:
@ -676,9 +710,6 @@ class Widget:
def do_sel(self): # Select button was pushed
pass
def unsel(self): # Select button was released
pass
def enter(self): # Control has acquired focus
pass
@ -695,6 +726,7 @@ class Widget:
# have do_up and do_down methods which adjust the control's value in a
# time-dependent manner.
class LinearIO(Widget):
def __init__(self, writer, row, col, height, width,
fgcolor, bgcolor, bdcolor,
value=None, active=True, prcolor=False,
@ -704,53 +736,31 @@ class LinearIO(Widget):
super().__init__(writer, row, col, height, width,
fgcolor, bgcolor, bdcolor,
value, active)
# Handle variable precision. Start normal
self.precision = False
self.do_precision = prcolor is not False
if self.do_precision:
# Subclass supports precision mode
# 1 sec long press to set precise
self.lpd = Delay_ms(self.precise, (True,))
# Precision mode can only be entered when the active control has focus.
# In this state it will have a white border. By default this turns yellow
# but subclass can be defeat this with WHITE or another color
self.prcolor = color_map[PRECISION] if prcolor is None else prcolor
# Adjust widget's value. Args: button pressed, amount of increment
def do_adj(self, button, val):
encoder = isinstance(button, int)
d = self.min_delta * 0.1 if self.precision else self.min_delta
d = self.min_delta * 0.1 if self.precision() else self.min_delta
self.value(self.value() + val * d)
if not encoder:
if not display.ipdev.encoder():
asyncio.create_task(self.btnhan(button, val, d))
# Handle increase and decrease buttons. Redefined by textbox.py, scale_log.py
async def btnhan(self, button, up, d):
maxd = self.max_delta if self.precision else d * 4 # Why move fast in precision mode?
maxd = self.max_delta if self.precision() else d * 4 # Why move fast in precision mode?
t = ticks_ms()
while not button():
while button():
await asyncio.sleep_ms(0) # Quit fast on button release
if ticks_diff(ticks_ms(), t) > 500: # Button was held down
d = min(maxd, d * 2)
self.value(self.value() + up * d)
t = ticks_ms()
def precise(self, v): # Timed out while button pressed
self.precision = v
self.draw = True
def do_sel(self): # Select button was pushed
if self.do_precision: # Subclass handles precision mode
if self.precision: # Already in mode
self.precise(False)
else: # Require a long press to enter mode
self.lpd.trigger()
def unsel(self): # Select button was released
self.do_precision and self.lpd.stop()
def leave(self): # Control has lost focus
self.precise(False)
# Get current status (also used by scale_log widget)
def precision(self):
return self.do_precision and display.ipdev.is_precision()
# The dummy enables popup windows by satisfying the need for at least one active
# widget on a screen. It is invisible and is drawn by Window constructor before

Wyświetl plik

@ -6,9 +6,7 @@
# 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
from gui.widgets import Label, Dial, Pointer, CloseButton
# Now import other modules
from cmath import rect, pi

Wyświetl plik

@ -10,13 +10,7 @@ from gui.core.writer import CWriter
import gui.fonts.arial10 as arial10 # Font for CWriter
from gui.core.colors import *
# Widgets
from gui.widgets.label import Label
from gui.widgets.scale import Scale
from gui.widgets.scale_log import ScaleLog
from gui.widgets.buttons import Button, CloseButton
from gui.widgets.sliders import Slider, HorizSlider
from gui.widgets.knob import Knob
from gui.widgets.checkbox import Checkbox
from gui.widgets import Label, Scale, ScaleLog, Button, CloseButton, Slider, HorizSlider, Knob, Checkbox
class BaseScreen(Screen):

Wyświetl plik

@ -8,10 +8,7 @@ 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.widgets import Label, CloseButton, Adjuster, Dial, Pointer
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -7,9 +7,7 @@
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, FloatAdj
from gui.widgets import Label, CloseButton, Adjuster, FloatAdj
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -44,9 +44,7 @@ audio_out = I2S(I2S_ID, **config)
# ======= GUI =======
from gui.widgets.label import Label
from gui.widgets.buttons import Button, CloseButton, CIRCLE
from gui.widgets.sliders import HorizSlider
from gui.widgets.listbox import Listbox
from gui.widgets import Button, CloseButton, HorizSlider, Listbox
from gui.core.writer import CWriter
# Font for CWriter
@ -59,7 +57,7 @@ import gc
import uasyncio as asyncio
import sys
# Initial check on ilesystem
# Initial check on filesystem
try:
subdirs = [x[0] for x in os.ilistdir(root) if x[1] == 0x4000]
if len(subdirs):

Wyświetl plik

@ -7,9 +7,7 @@
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets.buttons import CloseButton
from gui.widgets.checkbox import Checkbox
from gui.widgets.led import LED
from gui.widgets import CloseButton, Checkbox, LED
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -7,9 +7,7 @@
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 Button, CloseButton
from gui.widgets.dialog import DialogBox
from gui.widgets import Label, Button, CloseButton, DialogBox
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -7,9 +7,7 @@
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.widgets import Label, CloseButton, Dropdown
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -7,8 +7,7 @@
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets.buttons import CloseButton
from gui.widgets.sliders import Slider
from gui.widgets import CloseButton, Slider
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -9,8 +9,7 @@ from gui.core.ugui import Screen
from gui.core.writer import CWriter
from gui.core.colors import *
from gui.widgets.listbox import Listbox
from gui.widgets.buttons import CloseButton
from gui.widgets import Listbox, CloseButton
import gui.fonts.freesans20 as font

Wyświetl plik

@ -8,8 +8,7 @@ from gui.core.ugui import Screen, ssd
import gui.fonts.freesans20 as font
from gui.core.writer import CWriter
from gui.widgets.menu import Menu
from gui.widgets.buttons import CloseButton
from gui.widgets import Menu, CloseButton
from gui.core.colors import *
class BaseScreen(Screen):

Wyświetl plik

@ -18,9 +18,7 @@ from collections import OrderedDict
from gui.core.writer import Writer, CWriter
from gui.core.ugui import Screen, ssd
from gui.widgets.graph import PolarGraph, PolarCurve, CartesianGraph, Curve, TSequence
from gui.widgets.label import Label
from gui.widgets.buttons import Button, CloseButton
from gui.widgets.listbox import Listbox
from gui.widgets import Label, Button, CloseButton, Listbox
# Fonts & colors
import gui.fonts.arial10 as arial10

Wyświetl plik

@ -7,8 +7,7 @@
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.widgets import Label, CloseButton
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -7,8 +7,7 @@
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets.buttons import Button, CloseButton
from gui.widgets.label import Label
from gui.widgets import Button, CloseButton, Label
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -8,10 +8,7 @@ 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 Button, RadioButtons, CloseButton
from gui.widgets.listbox import Listbox
from gui.widgets.dropdown import Dropdown
from gui.widgets.dialog import DialogBox
from gui.widgets import Button, RadioButtons, CloseButton, Listbox, Dropdown, DialogBox
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -7,8 +7,7 @@
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 Button, CloseButton
from gui.widgets import Label, Button, CloseButton
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -7,8 +7,7 @@
import hardware_setup
from gui.core.ugui import Screen, ssd
from gui.widgets.buttons import CloseButton
from gui.widgets.sliders import Slider
from gui.widgets import CloseButton, Slider
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -7,9 +7,7 @@
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets.buttons import CloseButton
from gui.widgets.sliders import Slider
from gui.widgets.label import Label
from gui.widgets import CloseButton, Slider, Label
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -15,9 +15,7 @@ from gui.core.writer import CWriter
import uasyncio as asyncio
from gui.core.colors import *
import gui.fonts.arial10 as arial10
from gui.widgets.label import Label
from gui.widgets.textbox import Textbox
from gui.widgets.buttons import Button, CloseButton
from gui.widgets import Label, Textbox, Button, CloseButton
wri = CWriter(ssd, arial10) # verbose = True

Wyświetl plik

@ -10,12 +10,7 @@
import hardware_setup # Create a display instance
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.meter import Meter
from gui.widgets.region import Region
from gui.widgets.led import LED
from gui.widgets import Button, CloseButton, Slider, Label, Meter, Region, LED
from gui.core.writer import CWriter
# Font for CWriter

Wyświetl plik

@ -16,9 +16,7 @@ from gui.core.writer import CWriter
from gui.fonts import font10
from gui.core.colors import *
# Widgets
from gui.widgets.label import Label
from gui.widgets.buttons import Button, CloseButton
from gui.widgets.dial import Pointer, Dial
from gui.widgets import Label, Button, CloseButton, Pointer, Dial
def fwdbutton(wri, row, col, cls_screen, text='Next'):

Wyświetl plik

@ -2,6 +2,24 @@
# Copyright (c) 2018-2020 Peter Hinch
# Released under the MIT License (MIT) - see LICENSE file
_attrs = {
"Delay_ms": "delay_ms",
"Switch": "switch",
"Pushbutton": "pushbutton",
}
# Lazy loader, effectively does:
# global attr
# from .mod import attr
# Filched from uasyncio.__init__.py
def __getattr__(attr):
mod = _attrs.get(attr, None)
if mod is None:
raise AttributeError(attr)
value = getattr(__import__(mod, None, None, True, 1), attr)
globals()[attr] = value
return value
try:
import uasyncio as asyncio

Wyświetl plik

@ -27,7 +27,7 @@ class Delay_ms:
self._tout = asyncio.Event() # Timeout event
self.wait = self._tout.wait # Allow: await wait_ms.wait()
self._ttask = self._fake # Timer task
asyncio.create_task(self._run())
self._mtask = asyncio.create_task(self._run()) #Main task
async def _run(self):
while True:
@ -48,6 +48,8 @@ class Delay_ms:
# API
# trigger may be called from hard ISR.
def trigger(self, duration=0): # Update absolute end time, 0-> ctor default
if self._mtask is None:
raise RuntimeError("Delay_ms.deinit() has run.")
self._tend = ticks_add(ticks_ms(), duration if duration > 0 else self._durn)
self._retn = None # Default in case cancelled.
self._busy = True
@ -69,3 +71,8 @@ class Delay_ms:
def callback(self, func=None, args=()):
self._func = func
self._args = args
def deinit(self):
self.stop()
self._mtask.cancel()
self._mtask = None

Wyświetl plik

@ -1,42 +0,0 @@
# switch.py
# Copyright (c) 2018-2020 Peter Hinch
# Released under the MIT License (MIT) - see LICENSE file
import uasyncio as asyncio
import utime as time
from . import launch
class Switch:
debounce_ms = 50
def __init__(self, pin):
self.pin = pin # Should be initialised for input with pullup
self._open_func = False
self._close_func = False
self.switchstate = self.pin.value() # Get initial state
asyncio.create_task(self.switchcheck()) # Thread runs forever
def open_func(self, func, args=()):
self._open_func = func
self._open_args = args
def close_func(self, func, args=()):
self._close_func = func
self._close_args = args
# Return current state of switch (0 = pressed)
def __call__(self):
return self.switchstate
async def switchcheck(self):
while True:
state = self.pin.value()
if state != self.switchstate:
# State has changed: act on it now.
self.switchstate = state
if state == 0 and self._close_func:
launch(self._close_func, self._close_args)
elif state == 1 and self._open_func:
launch(self._open_func, self._open_args)
# Ignore further state changes until switch has settled
await asyncio.sleep_ms(Switch.debounce_ms)

Wyświetl plik

@ -0,0 +1,39 @@
_attrs = {
"Adjuster": "adjuster",
"FloatAdj": "adjuster",
"Button": "buttons",
"CloseButton": "buttons",
"ButtonList": "buttons",
"RadioButtons": "buttons",
"Checkbox": "checkbox",
"Dial": "dial",
"Pointer": "dial",
"DialogBox": "dialog",
"Dropdown": "dropdown",
"Knob": "knob",
"Label": "label",
"LED": "led",
"Listbox": "listbox",
"SubMenu": "menu",
"Menu": "menu",
"Meter": "meter",
"Region": "region",
"ScaleLog": "scale_log",
"Scale": "scale",
"Slider": "sliders",
"HorizSlider": "sliders",
"Textbox": "textbox",
}
# Lazy loader, effectively does:
# global attr
# from .mod import attr
# Filched from uasyncio.__init__.py
def __getattr__(attr):
mod = _attrs.get(attr, None)
if mod is None:
raise AttributeError(attr)
value = getattr(__import__(mod, None, None, True, 1), attr)
globals()[attr] = value
return value

Wyświetl plik

@ -15,7 +15,7 @@ class Button(Widget):
lit_time = 1000
def __init__(self, writer, row, col, *, shape=RECTANGLE, height=20, width=50,
fgcolor=None, bgcolor=None, bdcolor=False, textcolor=None, litcolor=None, text='',
callback=dolittle, args=[], onrelease=False):
callback=dolittle, args=[]):
sl = writer.stringlen(text)
if shape == CIRCLE: # Only height need be specified
width = max(sl, height)
@ -30,7 +30,6 @@ class Button(Widget):
self.text = text
self.callback = callback
self.callback_args = args
self.onrelease = onrelease
if self.litcolor is not None:
self.delay = Delay_ms(self.shownormal)
@ -77,17 +76,12 @@ class Button(Widget):
self.draw = True # Redisplay
def do_sel(self): # Select was pushed
if not self.onrelease:
self.callback(self, *self.callback_args) # CB takes self as 1st arg.
self.callback(self, *self.callback_args) # CB takes self as 1st arg.
if self.litcolor is not None and self.has_focus(): # CB may have changed focus
self.bgcolor = self.litcolor
self.draw = True # Redisplay
self.delay.trigger(Button.lit_time)
def unsel(self): # Select was released
if self.onrelease:
self.callback(self, *self.callback_args) # Callback not a bound method so pass self
# Preferred way to close a screen or dialog. Produces an X button at the top RHS.
# Note that if the bottom screen is closed, the application terminates.
class CloseButton(Button):

Wyświetl plik

@ -24,7 +24,7 @@ class _ListDialog(Window):
# Calculate Window dimensions
ap_height = lb_height + 6 # Allow for listbox border
ap_width = lb_width + 6
super().__init__(row, col, ap_height, ap_width)
super().__init__(row, col, ap_height, ap_width, draw_border=False)
self.listbox = Listbox(writer, row + 3, col + 3,
elements = dd.elements,
dlines = dlines, width = lb_width,
@ -35,6 +35,7 @@ class _ListDialog(Window):
self.dd = dd
def callback(self, obj_listbox):
display.ipdev.adj_mode(False) # If in 3-button mode, leave adjust mode
Screen.back()
self.dd.value(obj_listbox.value()) # Update it
@ -106,6 +107,7 @@ class Dropdown(Widget):
if len(self.elements) > 1:
args = (self.writer, self.row - 2, self.col - 2, self)
Screen.change(_ListDialog, args = args)
display.ipdev.adj_mode(True) # If in 3-button mode, go into adjust mode
def _despatch(self, _): # Run the callback specified in elements
x = self.els[self()]

Wyświetl plik

@ -6,7 +6,7 @@
# Usage:
# from gui.widgets.menu import Menu
from gui.core.ugui import Window, Screen
from gui.core.ugui import Window, Screen, display
from gui.widgets.buttons import Button
from gui.widgets.listbox import Listbox
from gui.core.colors import *
@ -31,18 +31,20 @@ class SubMenu(Window):
# Calculate Window dimensions
ap_height = lb_height + 6 # Allow for listbox border
ap_width = lb_width + 6
super().__init__(row, col, ap_height, ap_width)
super().__init__(row, col, ap_height, ap_width, draw_border=False)
Listbox(wri, row + 3, col + 3, elements = te, width = lb_width,
fgcolor = button.fgcolor, bgcolor = button.bgcolor, bdcolor=False,
fontcolor = button.textcolor, select_color = menu.select_color,
callback = self.callback)
def callback(self, lbox):
display.ipdev.adj_mode(False) # If in 3-button mode, leave adjust mode
Screen.back()
el = self.elements[lbox.value()] # (text, cb, args)
if len(el) == 2: # Recurse into submenu
args = (self.menu, self.button, el[1])
Screen.change(SubMenu, args = args)
display.ipdev.adj_mode(True) # If in 3-button mode, go into adjust mode
else:
el[1](lbox, *el[2])
@ -77,3 +79,4 @@ class Menu:
def cb(self, button, txt, elements): # Button pushed which calls submenu
args = (self, button, elements)
Screen.change(SubMenu, args = args)
display.ipdev.adj_mode(True) # If in 3-button mode, go into adjust mode

Wyświetl plik

@ -145,14 +145,14 @@ class ScaleLog(LinearIO):
# Adjust widget's value. Args: button pressed, amount of increment
def do_adj(self, button, val):
if isinstance(button, int): # Using an encoder
delta = self.delta * self.encoder_rate * 0.1 if self.precision else self.delta * self.encoder_rate
delta = self.delta * self.encoder_rate * 0.1 if self.precision() else self.delta * self.encoder_rate
self.value(self.value() * (1 + delta)**val)
else: # val == 1 or -1
asyncio.create_task(self.btnhan(button, val))
async def btnhan(self, button, up):
up = up == 1
if self.precision:
if self.precision():
delta = self.delta * 0.1
maxdelta = self.delta
else:
@ -161,7 +161,7 @@ class ScaleLog(LinearIO):
smul= (1 + delta) if up else (1 / (1 + delta))
self.value(self.value() * smul)
t = ticks_ms()
while not button():
while button():
await asyncio.sleep_ms(0) # Quit fast on button release
if ticks_diff(ticks_ms(), t) > 500: # Button was held down
delta = min(maxdelta, delta * 2)

Wyświetl plik

@ -51,4 +51,5 @@ sel = Pin(16, Pin.IN, Pin.PULL_UP) # Operate current 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
decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease control's value
display = Display(ssd, nxt, sel, prev, increase, decrease) #, 5)
display = Display(ssd, nxt, sel, prev) # 3-button mode
# display = Display(ssd, nxt, sel, prev, increase, decrease, 5) # Encoder mode