initial commit, display with commands working
This commit is contained in:
106
Pico_ePaper_Code/pythonNanoGui/gui/widgets/dial.py
Normal file
106
Pico_ePaper_Code/pythonNanoGui/gui/widgets/dial.py
Normal file
@@ -0,0 +1,106 @@
|
||||
# dial.py Dial and Pointer classes for nano-gui
|
||||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2018-2020 Peter Hinch
|
||||
|
||||
import cmath
|
||||
from gui.core.nanogui import DObject, circle, fillcircle
|
||||
from gui.widgets.label import Label
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
class Pointer():
|
||||
def __init__(self, dial):
|
||||
self.dial = dial
|
||||
self.val = 0 + 0j
|
||||
self.color = None
|
||||
|
||||
def value(self, v=None, color=None):
|
||||
self.color = color
|
||||
if v is not None:
|
||||
if isinstance(v, complex):
|
||||
l = cmath.polar(v)[0]
|
||||
if l > 1:
|
||||
self.val = v/l
|
||||
else:
|
||||
self.val = v
|
||||
else:
|
||||
raise ValueError('Pointer value must be complex.')
|
||||
self.dial.vectors.add(self)
|
||||
self.dial._set_pend(self.dial) # avoid redrawing for each vector
|
||||
return self.val
|
||||
|
||||
class Dial(DObject):
|
||||
CLOCK = 0
|
||||
COMPASS = 1
|
||||
def __init__(self, writer, row, col, *, height=50,
|
||||
fgcolor=None, bgcolor=None, bdcolor=False, ticks=4,
|
||||
label=None, style=0, pip=None):
|
||||
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor)
|
||||
self.style = style
|
||||
self.pip = self.fgcolor if pip is None else pip
|
||||
if label is not None:
|
||||
self.label = Label(writer, row + height + 3, col, label)
|
||||
radius = int(height / 2)
|
||||
self.radius = radius
|
||||
self.ticks = ticks
|
||||
self.xorigin = col + radius
|
||||
self.yorigin = row + radius
|
||||
self.vectors = set()
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
# cache bound variables
|
||||
dev = self.device
|
||||
ticks = self.ticks
|
||||
radius = self.radius
|
||||
xo = self.xorigin
|
||||
yo = self.yorigin
|
||||
# vectors (complex)
|
||||
vor = xo + 1j * yo
|
||||
vtstart = 0.9 * radius + 0j # start of tick
|
||||
vtick = 0.1 * radius + 0j # tick
|
||||
vrot = cmath.exp(2j * cmath.pi/ticks) # unit rotation
|
||||
for _ in range(ticks):
|
||||
polar(dev, vor + conj(vtstart), vtick, self.fgcolor)
|
||||
vtick *= vrot
|
||||
vtstart *= vrot
|
||||
circle(dev, xo, yo, radius, self.fgcolor)
|
||||
vshort = 1000 # Length of shortest vector
|
||||
for v in self.vectors:
|
||||
color = self.fgcolor if v.color is None else v.color
|
||||
val = v.value() * radius # val is complex
|
||||
vshort = min(vshort, cmath.polar(val)[0])
|
||||
if self.style == Dial.CLOCK:
|
||||
polar(dev, vor, val, color)
|
||||
else:
|
||||
arrow(dev, vor, val, 5, color)
|
||||
if isinstance(self.pip, int) and vshort > 5:
|
||||
fillcircle(dev, xo, yo, 2, self.pip)
|
||||
|
||||
45
Pico_ePaper_Code/pythonNanoGui/gui/widgets/label.py
Normal file
45
Pico_ePaper_Code/pythonNanoGui/gui/widgets/label.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# label.py Label class for nano-gui
|
||||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2018-2020 Peter Hinch
|
||||
|
||||
from gui.core.nanogui import DObject
|
||||
from gui.core.writer import Writer
|
||||
|
||||
# text: str display string int save width
|
||||
class Label(DObject):
|
||||
def __init__(self, writer, row, col, text, invert=False, fgcolor=None, bgcolor=None, bdcolor=False):
|
||||
# Determine width of object
|
||||
if isinstance(text, int):
|
||||
width = text
|
||||
text = None
|
||||
else:
|
||||
width = writer.stringlen(text)
|
||||
height = writer.height
|
||||
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor)
|
||||
if text is not None:
|
||||
self.value(text, invert)
|
||||
|
||||
def value(self, text=None, invert=False, fgcolor=None, bgcolor=None, bdcolor=None):
|
||||
txt = super().value(text)
|
||||
# Redraw even if no text supplied: colors may have changed.
|
||||
self.invert = invert
|
||||
self.fgcolor = self.def_fgcolor if fgcolor is None else fgcolor
|
||||
self.bgcolor = self.def_bgcolor if bgcolor is None else bgcolor
|
||||
if bdcolor is False:
|
||||
self.def_bdcolor = False
|
||||
self.bdcolor = self.def_bdcolor if bdcolor is None else bdcolor
|
||||
self.show()
|
||||
return txt
|
||||
|
||||
def show(self):
|
||||
txt = super().value()
|
||||
if txt is None: # No content to draw. Future use.
|
||||
return
|
||||
super().show() # Draw or erase border
|
||||
wri = self.writer
|
||||
dev = self.device
|
||||
Writer.set_textpos(dev, self.row, self.col)
|
||||
wri.setcolor(self.fgcolor, self.bgcolor)
|
||||
wri.printstring(txt, self.invert)
|
||||
wri.setcolor() # Restore defaults
|
||||
28
Pico_ePaper_Code/pythonNanoGui/gui/widgets/led.py
Normal file
28
Pico_ePaper_Code/pythonNanoGui/gui/widgets/led.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# led.py LED class for nano-gui
|
||||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2018-2020 Peter Hinch
|
||||
|
||||
from gui.core.nanogui import DObject, fillcircle, circle
|
||||
from gui.widgets.label import Label
|
||||
|
||||
class LED(DObject):
|
||||
def __init__(self, writer, row, col, *, height=12,
|
||||
fgcolor=None, bgcolor=None, bdcolor=None, label=None):
|
||||
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor)
|
||||
if label is not None:
|
||||
self.label = Label(writer, row + height + 3, col, label)
|
||||
self.radius = self.height // 2
|
||||
|
||||
def color(self, c=None):
|
||||
self.fgcolor = self.bgcolor if c is None else c
|
||||
self.show()
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
wri = self.writer
|
||||
dev = self.device
|
||||
r = self.radius
|
||||
fillcircle(dev, self.col + r, self.row + r, r, self.fgcolor)
|
||||
if isinstance(self.bdcolor, int):
|
||||
circle(dev, self.col + r, self.row + r, r, self.bdcolor)
|
||||
63
Pico_ePaper_Code/pythonNanoGui/gui/widgets/meter.py
Normal file
63
Pico_ePaper_Code/pythonNanoGui/gui/widgets/meter.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# meter.py Meter class for nano-gui
|
||||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2018-2020 Peter Hinch
|
||||
|
||||
from gui.core.nanogui import DObject
|
||||
from gui.widgets.label import Label
|
||||
|
||||
|
||||
class Meter(DObject):
|
||||
BAR = 1
|
||||
LINE = 0
|
||||
def __init__(self, writer, row, col, *, height=50, width=10,
|
||||
fgcolor=None, bgcolor=None, ptcolor=None, bdcolor=None,
|
||||
divisions=5, label=None, style=0, legends=None, value=None):
|
||||
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor)
|
||||
self.divisions = divisions
|
||||
if label is not None:
|
||||
Label(writer, row + height + 3, col, label)
|
||||
self.style = style
|
||||
if legends is not None: # Legends
|
||||
x = col + width + 4
|
||||
y = row + height
|
||||
dy = 0 if len(legends) <= 1 else height / (len(legends) -1)
|
||||
yl = y - writer.height / 2 # Start at bottom
|
||||
for legend in legends:
|
||||
Label(writer, int(yl), x, legend)
|
||||
yl -= dy
|
||||
self.ptcolor = ptcolor if ptcolor is not None else self.fgcolor
|
||||
self.value(value)
|
||||
|
||||
def value(self, n=None, color=None):
|
||||
if n is None:
|
||||
return super().value()
|
||||
n = super().value(min(1, max(0, n)))
|
||||
if color is not None:
|
||||
self.ptcolor = color
|
||||
self.show()
|
||||
return n
|
||||
|
||||
def show(self):
|
||||
super().show() # Draw or erase border
|
||||
val = super().value()
|
||||
wri = self.writer
|
||||
dev = self.device
|
||||
width = self.width
|
||||
height = self.height
|
||||
x0 = self.col
|
||||
x1 = self.col + width
|
||||
y0 = self.row
|
||||
y1 = self.row + height
|
||||
if self.divisions > 0:
|
||||
dy = height / (self.divisions) # Tick marks
|
||||
for tick in range(self.divisions + 1):
|
||||
ypos = int(y0 + dy * tick)
|
||||
dev.hline(x0 + 2, ypos, x1 - x0 - 4, self.fgcolor)
|
||||
|
||||
y = int(y1 - val * height) # y position of slider
|
||||
if self.style == self.LINE:
|
||||
dev.hline(x0, y, width, self.ptcolor) # Draw pointer
|
||||
else:
|
||||
w = width / 2
|
||||
dev.fill_rect(int(x0 + w - 2), y, 4, y1 - y, self.ptcolor)
|
||||
123
Pico_ePaper_Code/pythonNanoGui/gui/widgets/scale.py
Normal file
123
Pico_ePaper_Code/pythonNanoGui/gui/widgets/scale.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# scale.py Extension to nano-gui providing the Scale class
|
||||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2020 Peter Hinch
|
||||
|
||||
# Usage:
|
||||
# from gui.widgets.scale import Scale
|
||||
|
||||
from gui.core.nanogui import DObject
|
||||
from gui.core.writer import Writer
|
||||
from gui.core.colors import BLACK
|
||||
|
||||
class Scale(DObject):
|
||||
def __init__(self, writer, row, col, *,
|
||||
ticks=200, legendcb=None, tickcb=None,
|
||||
height=0, width=100, bdcolor=None, fgcolor=None, bgcolor=None,
|
||||
pointercolor=None, fontcolor=None):
|
||||
if ticks % 2:
|
||||
raise ValueError('ticks arg must be divisible by 2')
|
||||
self.ticks = ticks
|
||||
self.tickcb = tickcb
|
||||
def lcb(f):
|
||||
return '{:3.1f}'.format(f)
|
||||
self.legendcb = legendcb if legendcb is not None else lcb
|
||||
bgcolor = BLACK if bgcolor is None else bgcolor
|
||||
text_ht = writer.font.height()
|
||||
ctrl_ht = 12 # Minimum height for ticks
|
||||
# Add 2 pixel internal border to give a little more space
|
||||
min_ht = text_ht + 6 # Ht of text, borders and gap between text and ticks
|
||||
if height < min_ht + ctrl_ht:
|
||||
height = min_ht + ctrl_ht # min workable height
|
||||
else:
|
||||
ctrl_ht = height - min_ht # adjust ticks for greater height
|
||||
width &= 0xfffe # Make divisible by 2: avoid 1 pixel pointer offset
|
||||
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor)
|
||||
self.fontcolor = fontcolor if fontcolor is not None else self.fgcolor
|
||||
self.x0 = col + 2
|
||||
self.x1 = col + self.width - 2
|
||||
self.y0 = row + 2
|
||||
self.y1 = row + self.height - 2
|
||||
self.ptrcolor = pointercolor if pointercolor is not None else self.fgcolor
|
||||
# Define tick dimensions
|
||||
ytop = self.y0 + text_ht + 2 # Top of scale graphic (2 pixel gap)
|
||||
ycl = ytop + (self.y1 - ytop) // 2 # Centre line
|
||||
self.sdl = round(ctrl_ht * 1 / 3) # Length of small tick.
|
||||
self.sdy0 = ycl - self.sdl // 2
|
||||
self.mdl = round(ctrl_ht * 2 / 3) # Medium tick
|
||||
self.mdy0 = ycl - self.mdl // 2
|
||||
self.ldl = ctrl_ht # Large tick
|
||||
self.ldy0 = ycl - self.ldl // 2
|
||||
|
||||
def show(self):
|
||||
wri = self.writer
|
||||
dev = self.device
|
||||
x0: int = self.x0 # Internal rectangle occupied by scale and text
|
||||
x1: int = self.x1
|
||||
y0: int = self.y0
|
||||
y1: int = self.y1
|
||||
dev.fill_rect(x0, y0, x1 - x0, y1 - y0, self.bgcolor)
|
||||
super().show()
|
||||
# Scale is drawn using ints. Each division is 10 units.
|
||||
val: int = self._value # 0..ticks*10
|
||||
# iv increments for each tick. Its value modulo N determines tick length
|
||||
iv: int # val / 10 at a tick position
|
||||
d: int # val % 10: offset relative to a tick position
|
||||
fx: int # X offset of current tick in value units
|
||||
if val >= 100: # Whole LHS of scale will be drawn
|
||||
iv, d = divmod(val - 100, 10) # Initial value
|
||||
fx = 10 - d
|
||||
iv += 1
|
||||
else: # Scale will scroll right
|
||||
iv = 0
|
||||
fx = 100 - val
|
||||
|
||||
# Window shows 20 divisions, each of which corresponds to 10 units of value.
|
||||
# So pixels per unit value == win_width/200
|
||||
win_width: int = x1 - x0
|
||||
ticks: int = self.ticks # Total # of ticks visible and hidden
|
||||
while True:
|
||||
x: int = x0 + (fx * win_width) // 200 # Current X position
|
||||
ys: int # Start Y position for tick
|
||||
yl: int # tick length
|
||||
if x > x1 or iv > ticks: # Out of space or data (scroll left)
|
||||
break
|
||||
if not iv % 10:
|
||||
txt = self.legendcb(self._fvalue(iv * 10))
|
||||
tlen = wri.stringlen(txt)
|
||||
Writer.set_textpos(dev, y0, min(x, x1 - tlen))
|
||||
wri.setcolor(self.fontcolor, self.bgcolor)
|
||||
wri.printstring(txt)
|
||||
wri.setcolor()
|
||||
ys = self.ldy0 # Large tick
|
||||
yl = self.ldl
|
||||
elif not iv % 5:
|
||||
ys = self.mdy0
|
||||
yl = self.mdl
|
||||
else:
|
||||
ys = self.sdy0
|
||||
yl = self.sdl
|
||||
if self.tickcb is None:
|
||||
color = self.fgcolor
|
||||
else:
|
||||
color = self.tickcb(self._fvalue(iv * 10), self.fgcolor)
|
||||
dev.vline(x, ys, yl, color) # Draw tick
|
||||
fx += 10
|
||||
iv += 1
|
||||
|
||||
dev.vline(x0 + (x1 - x0) // 2, y0, y1 - y0, self.ptrcolor) # Draw pointer
|
||||
|
||||
def _to_int(self, v):
|
||||
return round((v + 1.0) * self.ticks * 5) # 0..self.ticks*10
|
||||
|
||||
def _fvalue(self, v=None):
|
||||
return v / (5 * self.ticks) - 1.0
|
||||
|
||||
def value(self, val=None): # User method to get or set value
|
||||
if val is not None:
|
||||
val = min(max(val, - 1.0), 1.0)
|
||||
v = self._to_int(val)
|
||||
if v != self._value:
|
||||
self._value = v
|
||||
self.show()
|
||||
return self._fvalue(self._value)
|
||||
126
Pico_ePaper_Code/pythonNanoGui/gui/widgets/textbox.py
Normal file
126
Pico_ePaper_Code/pythonNanoGui/gui/widgets/textbox.py
Normal file
@@ -0,0 +1,126 @@
|
||||
# textbox.py Extension to nanogui providing the Textbox class
|
||||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2020 Peter Hinch
|
||||
|
||||
# Usage:
|
||||
# from gui.widgets.textbox import Textbox
|
||||
|
||||
from gui.core.nanogui import DObject
|
||||
from gui.core.writer import Writer
|
||||
|
||||
# Reason for no tab support in private/reason_for_no_tabs
|
||||
|
||||
class Textbox(DObject):
|
||||
def __init__(self, writer, row, col, width, nlines, *, bdcolor=None, fgcolor=None,
|
||||
bgcolor=None, clip=True):
|
||||
height = nlines * writer.height
|
||||
devht = writer.device.height
|
||||
devwd = writer.device.width
|
||||
if ((row + height + 2) > devht) or ((col + width + 2) > devwd):
|
||||
raise ValueError('Textbox extends beyond physical screen.')
|
||||
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor)
|
||||
self.nlines = nlines
|
||||
self.clip = clip
|
||||
self.lines = []
|
||||
self.start = 0 # Start line for display
|
||||
|
||||
def _add_lines(self, s):
|
||||
width = self.width
|
||||
font = self.writer.font
|
||||
n = -1 # Index into string
|
||||
newline = True
|
||||
while True:
|
||||
n += 1
|
||||
if newline:
|
||||
newline = False
|
||||
ls = n # Start of line being processed
|
||||
col = 0 # Column relative to text area
|
||||
if n >= len(s): # End of string
|
||||
if n > ls:
|
||||
self.lines.append(s[ls :])
|
||||
return
|
||||
c = s[n] # Current char
|
||||
if c == '\n':
|
||||
self.lines.append(s[ls : n])
|
||||
newline = True
|
||||
continue # Line fits window
|
||||
col += font.get_ch(c)[2] # width of current char
|
||||
if col > width:
|
||||
if self.clip:
|
||||
p = s[ls :].find('\n') # end of 1st line
|
||||
if p == -1:
|
||||
self.lines.append(s[ls : n]) # clip, discard all to right
|
||||
return
|
||||
self.lines.append(s[ls : n]) # clip, discard to 1st newline
|
||||
n = p # n will move to 1st char after newline
|
||||
elif c == ' ': # Easy word wrap
|
||||
self.lines.append(s[ls : n])
|
||||
else: # Edge splits a word
|
||||
p = s.rfind(' ', ls, n + 1)
|
||||
if p >= 0: # spacechar in line: wrap at space
|
||||
assert (p > 0), 'space char in position 0'
|
||||
self.lines.append(s[ls : p])
|
||||
n = p
|
||||
else: # No spacechar: wrap at end
|
||||
self.lines.append(s[ls : n])
|
||||
n -= 1 # Don't skip current char
|
||||
newline = True
|
||||
|
||||
def _print_lines(self):
|
||||
if len(self.lines) == 0:
|
||||
return
|
||||
|
||||
dev = self.device
|
||||
wri = self.writer
|
||||
col = self.col
|
||||
row = self.row
|
||||
left = col
|
||||
ht = wri.height
|
||||
wri.setcolor(self.fgcolor, self.bgcolor)
|
||||
# Print the first (or last?) lines that fit widget's height
|
||||
#for line in self.lines[-self.nlines : ]:
|
||||
for line in self.lines[self.start : self.start + self.nlines]:
|
||||
Writer.set_textpos(dev, row, col)
|
||||
wri.printstring(line)
|
||||
row += ht
|
||||
col = left
|
||||
wri.setcolor() # Restore defaults
|
||||
|
||||
def show(self):
|
||||
dev = self.device
|
||||
super().show()
|
||||
self._print_lines()
|
||||
|
||||
def append(self, s, ntrim=None, line=None):
|
||||
self._add_lines(s)
|
||||
if ntrim is None: # Default to no. of lines that can fit
|
||||
ntrim = self.nlines
|
||||
if len(self.lines) > ntrim:
|
||||
self.lines = self.lines[-ntrim:]
|
||||
self.goto(line)
|
||||
|
||||
def scroll(self, n): # Relative scrolling
|
||||
value = len(self.lines)
|
||||
if n == 0 or value <= self.nlines: # Nothing to do
|
||||
return False
|
||||
s = self.start
|
||||
self.start = max(0, min(self.start + n, value - self.nlines))
|
||||
if s != self.start:
|
||||
self.show()
|
||||
return True
|
||||
return False
|
||||
|
||||
def value(self):
|
||||
return len(self.lines)
|
||||
|
||||
def clear(self):
|
||||
self.lines = []
|
||||
self.show()
|
||||
|
||||
def goto(self, line=None): # Absolute scrolling
|
||||
if line is None:
|
||||
self.start = max(0, len(self.lines) - self.nlines)
|
||||
else:
|
||||
self.start = max(0, min(line, len(self.lines) - self.nlines))
|
||||
self.show()
|
||||
Reference in New Issue
Block a user