micropython-micro-gui/gui/widgets/vectors.py

121 wiersze
4.4 KiB
Python

# vectors.py Extension to ugui providing vector display
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# VectorDial class is display-only: no practical way to perform
# input on multiple vectors
from micropython import const
from gui.core.ugui import Screen, Widget, display
from gui.widgets.label import Label
from gui.core.colors import *
import cmath
conj = lambda v : v.real - v.imag * 1j # Complex conjugate
# Draw a vector in complex coordinates. Origin and end are complex.
# End is relative to origin.
def pline(origin, vec, color):
xs, ys = origin.real, origin.imag
display.line(round(xs), round(ys), round(xs + vec.real), round(ys - vec.imag), color)
# 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.
def arrow(origin, vec, lc, color):
ccw = cmath.exp(3j * cmath.pi/4) # Unit vectors
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
pline(origin, vec, color) # Origin to tip
pline(origin, start, color) # Origin to tail
pline(origin + conj(vec), chev*ccw*uv, color) # Tip chevron
pline(origin + conj(vec), chev*cw*uv, color)
if length > lc: # Confusing appearance of very short vectors with tail chevron
pline(origin + conj(start), chev*ccw*uv, color) # Tail chevron
pline(origin + conj(start), chev*cw*uv, color)
# Vector display
class Pointer:
def __init__(self, dial):
dial.vectors.add(self)
self.dial = dial
self.color = WHITE
self.val = 0j
def value(self, v=None, color=None):
if color is not None:
self.color = color
if v is not None:
if isinstance(v, complex):
l = cmath.polar(v)[0]
newval = v / l if l > 1 else v # Max length = 1.0
else:
raise ValueError('Pointer value must be complex.')
if v != self.val and self.dial.screen is Screen.current_screen:
self.show(newval)
self.val = newval
return self.val
def show(self, newval=None):
v = self.val if newval is None else newval
dial = self.dial
color = self.color
vor = dial.vor # Dial's origin as a vector
r = dial.radius * (1 - dial.TICKLEN)
if dial.arrow:
arrow(vor, r * v, 5, color)
else:
pline(vor, r * v, color)
self.dial.draw = True # Mark dial as dirty
class VectorDial(Widget):
TICKLEN = 0.1
def __init__(self, writer, row, col, *,
height=100, fgcolor=None, bgcolor=None, bdcolor=None,
ticks=4, arrow=False, pip=None):
super().__init__(writer, row, col, height, height,
fgcolor, bgcolor, bdcolor, 0, False) # Display only
self.arrow = arrow
self.pip = self.fgcolor if pip is None else pip
radius = height / 2
self.radius = radius
self.ticks = ticks
self.xorigin = col + radius
self.yorigin = row + radius
self.vor = self.xorigin + 1j * self.yorigin # Origin as a vector
self.vectors = set()
self.draw = True
def show(self):
if super().show():
# cache bound variables
ticks = self.ticks
radius = self.radius
xo = self.xorigin
yo = self.yorigin
vor = self.vor
vtstart = (1 - self.TICKLEN) * radius + 0j # start of tick
vtick = self.TICKLEN * radius + 0j # tick
vrot = cmath.exp(2j * cmath.pi/ticks) # unit rotation
for _ in range(ticks):
pline(vor + conj(vtstart), vtick, self.fgcolor)
vtick *= vrot
vtstart *= vrot
display.circle(xo, yo, radius, self.fgcolor)
vshort = 1000 # Length of shortest vector
for v in self.vectors:
val = v.value() * radius # val is complex
vshort = min(vshort, cmath.polar(val)[0])
v.show()
if isinstance(self.pip, tuple) and vshort > 9:
display.fillcircle(xo, yo, 3, self.pip)
self.draw = False