kopia lustrzana https://github.com/peterhinch/micropython-micro-gui
121 wiersze
4.4 KiB
Python
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
|