kopia lustrzana https://github.com/peterhinch/micropython-nano-gui
224 wiersze
7.4 KiB
Python
224 wiersze
7.4 KiB
Python
![]() |
# eclock.py Unusual clock display for nanogui
|
||
|
# see micropython-epaper/epd-clock
|
||
|
|
||
|
# Released under the MIT License (MIT). See LICENSE.
|
||
|
# Copyright (c) 2023 Peter Hinch
|
||
|
|
||
|
from cmath import rect, phase
|
||
|
from math import sin, cos, pi
|
||
|
from array import array
|
||
|
from gui.core.nanogui import DObject, Writer
|
||
|
from gui.core.colors import *
|
||
|
|
||
|
# **** BEGIN DISPLAY CONSTANTS ****
|
||
|
THETA = pi/3 # Intersection of arc with unit circle
|
||
|
PHI = pi/12 # Arc is +-30 minute segment
|
||
|
|
||
|
# **** BEGIN DERIVED CONSTANTS ****
|
||
|
|
||
|
RADIUS = sin(THETA) / sin(PHI)
|
||
|
XLT = cos(THETA) - RADIUS * cos(PHI) # Convert arc relative to [0,0] relative
|
||
|
RV = pi / 360 # Interpolate arc to 1 minute
|
||
|
TV = RV / 5 # Small increment << I minute
|
||
|
# OR = cos(THETA) - RADIUS * cos(PHI) + 0j # Origin of arc
|
||
|
|
||
|
# **** BEGIN VECTOR CODE ****
|
||
|
# A vector is a line on the complex plane defined by a tuple of two complex
|
||
|
# numbers. Vectors presented for display lie in the unit circle.
|
||
|
|
||
|
def conj(n): # Complex conjugate
|
||
|
return n.real - n.imag * 1j
|
||
|
|
||
|
# Generate vectors comprising sectors of an arc. hrs defines location of arc,
|
||
|
# angle its length.
|
||
|
# 1 <= hrs <= 12 0 <= angle < 60 in normal use
|
||
|
# To print full arc angle == 60
|
||
|
def arc(hrs, angle=60, mul=1.0):
|
||
|
vs = rect(RADIUS * mul, PHI) # Coords relative to arc origin
|
||
|
ve = rect(RADIUS * mul, PHI)
|
||
|
pe = PHI - angle * RV + TV
|
||
|
rv = rect(1, -RV) # Rotation vector for 1 minute (about OR)
|
||
|
rot = rect(1, (3 - hrs) * pi / 6) # hrs rotation (about [0,0])
|
||
|
while phase(vs) > pe:
|
||
|
ve *= rv
|
||
|
# Translate to 0, 0
|
||
|
yield ((vs + XLT) * rot, (ve + XLT) * rot)
|
||
|
vs *= rv
|
||
|
|
||
|
def progress(hrs, angle, mul0, mul1):
|
||
|
vs = rect(RADIUS * mul0, PHI) # Coords relative to arc origin
|
||
|
pe = PHI - angle * RV + TV
|
||
|
rv = rect(1, -RV) # CW Rotation vector for 1 minute (about OR)
|
||
|
rot = rect(1, (3 - hrs) * pi / 6) # hrs rotation (about [0,0])
|
||
|
while phase(vs) > pe: # CW
|
||
|
# Translate to 0, 0
|
||
|
yield (vs + XLT) * rot
|
||
|
vs *= rv
|
||
|
yield (vs + XLT) * rot
|
||
|
pe = PHI
|
||
|
vs = rect(RADIUS * mul1, PHI - angle * RV)
|
||
|
rv = conj(rv) # Reverse direction of rotation
|
||
|
while phase(vs) < pe: # CCW
|
||
|
yield (vs + XLT) * rot
|
||
|
vs *= rv
|
||
|
yield (vs + XLT) * rot
|
||
|
|
||
|
# Hour ticks for main circle
|
||
|
def hticks(length):
|
||
|
segs = 12
|
||
|
phi = 2 * pi / segs
|
||
|
rv = rect(1, phi)
|
||
|
vs = 1 + 0j
|
||
|
ve = vs * (1 - length)
|
||
|
for _ in range(segs):
|
||
|
ve *= rv
|
||
|
vs *= rv
|
||
|
yield vs, ve
|
||
|
|
||
|
# Generate vectors for the minutes ticks
|
||
|
def ticks(hrs, length):
|
||
|
vs = rect(RADIUS, PHI) # Coords relative to arc origin
|
||
|
ve = rect(RADIUS - length, PHI) # Short tick
|
||
|
ve1 = rect(RADIUS - 1.5 * length, PHI) # Long tick
|
||
|
ve2 = rect(RADIUS - 2.0 * length, PHI) # Extra long tick
|
||
|
rv = rect(1, -5 * RV) # Rotation vector for 5 minutes (about OR)
|
||
|
rot = rect(1, (3 - hrs) * pi / 6) # hrs rotation (about [0,0])
|
||
|
for n in range(13):
|
||
|
# Translate to 0, 0
|
||
|
if n == 6: # Overdrawn by hour pointer: visually cleaner if we skip
|
||
|
yield
|
||
|
elif n % 3 == 0:
|
||
|
yield ((vs + XLT) * rot, (ve2 + XLT) * rot) # Extra Long
|
||
|
elif n % 2 == 0:
|
||
|
yield ((vs + XLT) * rot, (ve1 + XLT) * rot) # Long
|
||
|
else:
|
||
|
yield ((vs + XLT) * rot, (ve + XLT) * rot) # Short
|
||
|
vs *= rv
|
||
|
ve *= rv
|
||
|
ve1 *= rv
|
||
|
ve2 *= rv
|
||
|
|
||
|
# Generate vector for the hour line
|
||
|
def hour(hrs):
|
||
|
rot = rect(1, (3 - hrs) * pi / 6) # hrs rotation (about [0,0])
|
||
|
return -rot, rot
|
||
|
|
||
|
# Points for arrow head
|
||
|
def head(hrs):
|
||
|
ve = 1 + 0j
|
||
|
rot = rect(1, (3 - hrs) * pi / 6) # hrs rotation (about [0,0])
|
||
|
yield ve * rot
|
||
|
vs = 0.9 + 0.1j
|
||
|
yield vs * rot
|
||
|
vs = conj(vs)
|
||
|
yield vs * rot
|
||
|
|
||
|
def tail(hrs):
|
||
|
rot = rect(1, (3 - hrs) * pi / 6) # Rotation
|
||
|
xlt = (-1.05 + 0j) * rot # Translation
|
||
|
phi = pi / 3
|
||
|
r = 0.13
|
||
|
vs = rect(r, phi)
|
||
|
vr = rect(1.0, -phi/6) # 6 segments per arc
|
||
|
while phase(vs) > -phi:
|
||
|
yield vs * rot + xlt
|
||
|
vs *= vr
|
||
|
yield vs * rot + xlt
|
||
|
|
||
|
# Generate vector for inner legends
|
||
|
def inner(hrs):
|
||
|
phi = pi * 0.35 #.33
|
||
|
length = 0.75
|
||
|
vec = rect(length, phi)
|
||
|
rot = rect(1, (3 - hrs) * pi / 6) # hrs rotation (about [0,0])
|
||
|
yield vec * rot
|
||
|
yield conj(vec) * rot
|
||
|
|
||
|
# **** BEGIN POPULATE DISPLAY ****
|
||
|
# colors: hour ticks, arc, mins ticks, mins arc, pointer
|
||
|
|
||
|
|
||
|
class EClock(DObject):
|
||
|
def __init__(self, writer, row, col, height, fgcolor=None, bgcolor=None, bdcolor=RED, int_colors=None):
|
||
|
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor)
|
||
|
self.colors = (WHITE, WHITE, WHITE, WHITE, WHITE) if int_colors is None else int_colors
|
||
|
self.radius = height / 2
|
||
|
self.xlat = self.col + 1j * self.row # Translation vector
|
||
|
|
||
|
# Convert from maths coords to graphics (invert y)
|
||
|
# Shift so real and imag are positive (0 <= re <= 2, 0 <= im <= 2)
|
||
|
# Multiply by widget size scalar
|
||
|
# Shift by adding widget position vector
|
||
|
def scale(self, point):
|
||
|
return (conj(point) + 1 + 1j) * self.radius + self.xlat
|
||
|
|
||
|
# Draw a vector scaling it for display and converting to integer x, y
|
||
|
def draw_vec(self, vec, color):
|
||
|
vs = self.scale(vec[0])
|
||
|
ve = self.scale(vec[1])
|
||
|
self.device.line(round(vs.real), round(vs.imag), round(ve.real), round(ve.imag), color)
|
||
|
|
||
|
def draw_poly(self, points, color):
|
||
|
xp = array("h")
|
||
|
for p in points:
|
||
|
p = self.scale(p)
|
||
|
xp.append(round(p.real))
|
||
|
xp.append(round(p.imag))
|
||
|
self.device.poly(0, 0, xp, color, True)
|
||
|
|
||
|
def map_point(self, point):
|
||
|
point = self.scale(point)
|
||
|
return round(point.imag), round(point.real)
|
||
|
|
||
|
def value(self, t):
|
||
|
super().value(t)
|
||
|
self.show()
|
||
|
|
||
|
def show(self): # Called by an async task whenever minutes changes
|
||
|
super().show()
|
||
|
wri = self.writer
|
||
|
dev = self.device
|
||
|
c = self.colors
|
||
|
t = super().value()
|
||
|
mins = t[4]
|
||
|
angle = mins + 30 if mins < 30 else mins - 30
|
||
|
# mins angle
|
||
|
# 5 35
|
||
|
# 29 59
|
||
|
# 31 1
|
||
|
# 59 29
|
||
|
if mins < 30:
|
||
|
hrs = (t[3] % 12)
|
||
|
else:
|
||
|
hrs = (t[3] + 1) % 12
|
||
|
# 0 <= hrs <= 11 changes on half-hour
|
||
|
|
||
|
# Draw graphics.
|
||
|
rad = round(self.height / 2)
|
||
|
row = self.row + rad
|
||
|
col = self.col + rad
|
||
|
dev.ellipse(col, row, rad, rad, self.fgcolor)
|
||
|
for vec in hticks(0.05):
|
||
|
self.draw_vec(vec, c[0])
|
||
|
for vec in arc(hrs): # -30 to +30 arc
|
||
|
self.draw_vec(vec, c[1]) # Arc
|
||
|
for vec in ticks(hrs, 0.05): # Ticks
|
||
|
if vec is not None: # Not overdrawn by hour pointer
|
||
|
self.draw_vec(vec, c[2])
|
||
|
self.draw_poly(progress(hrs, angle, 1.0, 0.99), c[3]) # Elapsed minutes
|
||
|
self.draw_vec(hour(hrs), c[4]) # Chevron shaft
|
||
|
self.draw_poly(head(hrs), c[4]) # Chevron head
|
||
|
self.draw_poly(tail(hrs), c[4]) # Chevron tail
|
||
|
|
||
|
# Inner text
|
||
|
co = round(self.writer.stringlen("+30") / 2) # Row and col offsets to
|
||
|
ro = round(self.writer.height / 2) # position text relative to string centre.
|
||
|
txt = "-30"
|
||
|
for point in inner(hrs):
|
||
|
row, col = self.map_point(point) # Convert to display coords
|
||
|
Writer.set_textpos(dev, row - ro, col - co)
|
||
|
wri.setcolor(self.fgcolor, self.bgcolor)
|
||
|
wri.printstring(txt, False)
|
||
|
txt = "+30"
|
||
|
wri.setcolor() # Restore defaults
|