kopia lustrzana https://github.com/peterhinch/micropython-nano-gui
180 wiersze
6.5 KiB
Python
180 wiersze
6.5 KiB
Python
# nanogui.py Displayable objects based on the Writer and CWriter classes
|
|
# V0.4 Peter Hinch 1st Nov 2020
|
|
|
|
# Released under the MIT License (MIT). See LICENSE.
|
|
# Copyright (c) 2018-2020 Peter Hinch
|
|
|
|
# Base class for a displayable object. Subclasses must implement .show() and .value()
|
|
# Has position, colors and border definition.
|
|
# border: False no border None use bgcolor, int: treat as color
|
|
|
|
import cmath
|
|
from gui.core.writer import Writer
|
|
import framebuf
|
|
import gc
|
|
|
|
def _circle(dev, x0, y0, r, color): # Single pixel circle
|
|
x = -r
|
|
y = 0
|
|
err = 2 -2*r
|
|
while x <= 0:
|
|
dev.pixel(x0 -x, y0 +y, color)
|
|
dev.pixel(x0 +x, y0 +y, color)
|
|
dev.pixel(x0 +x, y0 -y, color)
|
|
dev.pixel(x0 -x, y0 -y, color)
|
|
e2 = err
|
|
if (e2 <= y):
|
|
y += 1
|
|
err += y*2 +1
|
|
if (-x == y and e2 <= x):
|
|
e2 = 0
|
|
if (e2 > x):
|
|
x += 1
|
|
err += x*2 +1
|
|
|
|
def circle(dev, x0, y0, r, color, width =1): # Draw circle
|
|
x0, y0, r = int(x0), int(y0), int(r)
|
|
for r in range(r, r -width, -1):
|
|
_circle(dev, x0, y0, r, color)
|
|
|
|
def fillcircle(dev, x0, y0, r, color): # Draw filled circle
|
|
x0, y0, r = int(x0), int(y0), int(r)
|
|
x = -r
|
|
y = 0
|
|
err = 2 -2*r
|
|
while x <= 0:
|
|
dev.line(x0 -x, y0 -y, x0 -x, y0 +y, color)
|
|
dev.line(x0 +x, y0 -y, x0 +x, y0 +y, color)
|
|
e2 = err
|
|
if (e2 <= y):
|
|
y +=1
|
|
err += y*2 +1
|
|
if (-x == y and e2 <= x):
|
|
e2 = 0
|
|
if (e2 > x):
|
|
x += 1
|
|
err += x*2 +1
|
|
|
|
# Line defined by polar coords; origin and line are complex
|
|
def polar(dev, origin, line, color):
|
|
xs, ys = origin.real, origin.imag
|
|
theta = cmath.polar(line)[1]
|
|
dev.line(round(xs), round(ys), round(xs + line.real), round(ys - line.imag), color)
|
|
|
|
def conj(v): # complex conjugate
|
|
return v.real - v.imag * 1j
|
|
|
|
# Draw an arrow; origin and vec are complex, scalar lc defines length of chevron.
|
|
# cw and ccw are unit vectors of +-3pi/4 radians for chevrons (precompiled)
|
|
def arrow(dev, origin, vec, lc, color, ccw=cmath.exp(3j * cmath.pi/4), cw=cmath.exp(-3j * cmath.pi/4)):
|
|
length, theta = cmath.polar(vec)
|
|
uv = cmath.rect(1, theta) # Unit rotation vector
|
|
start = -vec
|
|
if length > 3 * lc: # If line is long
|
|
ds = cmath.rect(lc, theta)
|
|
start += ds # shorten to allow for length of tail chevrons
|
|
chev = lc + 0j
|
|
polar(dev, origin, vec, color) # Origin to tip
|
|
polar(dev, origin, start, color) # Origin to tail
|
|
polar(dev, origin + conj(vec), chev*ccw*uv, color) # Tip chevron
|
|
polar(dev, origin + conj(vec), chev*cw*uv, color)
|
|
if length > lc: # Confusing appearance of very short vectors with tail chevron
|
|
polar(dev, origin + conj(start), chev*ccw*uv, color) # Tail chevron
|
|
polar(dev, origin + conj(start), chev*cw*uv, color)
|
|
|
|
# If a (framebuf based) device is passed to refresh, the screen is cleared.
|
|
# None causes pending widgets to be drawn and the result to be copied to hardware.
|
|
# The pend mechanism enables a displayable object to postpone its renedering
|
|
# until it is complete: efficient for e.g. Dial which may have multiple Pointers
|
|
def refresh(device, clear=False):
|
|
if not isinstance(device, framebuf.FrameBuffer):
|
|
raise ValueError('Device must be derived from FrameBuffer.')
|
|
if device not in DObject.devices:
|
|
DObject.devices[device] = set()
|
|
device.fill(0)
|
|
else:
|
|
if clear:
|
|
DObject.devices[device].clear() # Clear the pending set
|
|
device.fill(0)
|
|
else:
|
|
for obj in DObject.devices[device]:
|
|
obj.show()
|
|
DObject.devices[device].clear()
|
|
device.show()
|
|
|
|
# Displayable object: effectively an ABC for all GUI objects.
|
|
class DObject():
|
|
devices = {} # Index device instance, value is a set of pending objects
|
|
|
|
@classmethod
|
|
def _set_pend(cls, obj):
|
|
cls.devices[obj.device].add(obj)
|
|
|
|
def __init__(self, writer, row, col, height, width, fgcolor, bgcolor, bdcolor):
|
|
writer.set_clip(True, True, False) # Disable scrolling text
|
|
self.writer = writer
|
|
device = writer.device
|
|
self.device = device
|
|
if row < 0:
|
|
row = 0
|
|
self.warning()
|
|
elif row + height >= device.height:
|
|
row = device.height - height - 1
|
|
self.warning()
|
|
if col < 0:
|
|
col = 0
|
|
self.warning()
|
|
elif col + width >= device.width:
|
|
row = device.width - width - 1
|
|
self.warning()
|
|
self.row = row
|
|
self.col = col
|
|
self.width = width
|
|
self.height = height
|
|
self._value = None # Type depends on context but None means don't display.
|
|
# Current colors
|
|
if fgcolor is None:
|
|
fgcolor = writer.fgcolor
|
|
if bgcolor is None:
|
|
bgcolor = writer.bgcolor
|
|
if bdcolor is None:
|
|
bdcolor = fgcolor
|
|
self.fgcolor = fgcolor
|
|
self.bgcolor = bgcolor
|
|
# bdcolor is False if no border is to be drawn
|
|
self.bdcolor = bdcolor
|
|
# Default colors allow restoration after dynamic change
|
|
self.def_fgcolor = fgcolor
|
|
self.def_bgcolor = bgcolor
|
|
self.def_bdcolor = bdcolor
|
|
# has_border is True if a border was drawn
|
|
self.has_border = False
|
|
|
|
def warning(self):
|
|
print('Warning: attempt to create {} outside screen dimensions.'.format(self.__class__.__name__))
|
|
|
|
# Blank working area
|
|
# Draw a border if .bdcolor specifies a color. If False, erase an existing border
|
|
def show(self):
|
|
wri = self.writer
|
|
dev = self.device
|
|
dev.fill_rect(self.col, self.row, self.width, self.height, self.bgcolor)
|
|
if isinstance(self.bdcolor, bool): # No border
|
|
if self.has_border: # Border exists: erase it
|
|
dev.rect(self.col - 2, self.row - 2, self.width + 4, self.height + 4, self.bgcolor)
|
|
self.has_border = False
|
|
elif self.bdcolor: # Border is required
|
|
dev.rect(self.col - 2, self.row - 2, self.width + 4, self.height + 4, self.bdcolor)
|
|
self.has_border = True
|
|
|
|
def value(self, v=None):
|
|
if v is not None:
|
|
self._value = v
|
|
return self._value
|
|
|
|
def text(self, text=None, invert=False, fgcolor=None, bgcolor=None, bdcolor=None):
|
|
if hasattr(self, 'label'):
|
|
self.label.value(text, invert, fgcolor, bgcolor, bdcolor)
|
|
else:
|
|
raise ValueError('Attempt to update nonexistent label.')
|