From 5c7d6c96b30e4936a2a4315b09e98a730f14c6db Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Tue, 9 Nov 2021 11:46:59 +0000 Subject: [PATCH] Add FloatAdj to widgets/adjuster.py --- README.md | 10 ++++++++ gui/demos/adjuster.py | 33 ++++++++++++++++--------- gui/widgets/adjuster.py | 55 +++++++++++++++++++++++++++++++++++------ 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b75a597..84816dc 100644 --- a/README.md +++ b/README.md @@ -2348,6 +2348,16 @@ subclass. The callback runs when the widget is instantiated and whenever the value changes. Typically the callback will adjust the text displayed on a linked label. +### A numeric entry device + +The file [widgets/adjuster.py](./gui/widgets/adjuster.py) includes an example +class which combines an `Adjuster` with one or two `Label` instances. The +`Adjuster` changes the displayed value in the `Label` to its left. Its use is +illustrated in [demos/adjuster.py](./gui/demos/adjuster.py). The class can be +used as a template for a user class, which may have a different layout on +screen. It supports arbitrary mapping and number formatting on a per-instance +basis. See code comments for further details. + ###### [Contents](./README.md#0-contents) # 23 Menu class diff --git a/gui/demos/adjuster.py b/gui/demos/adjuster.py index 2c71fd9..f50e030 100644 --- a/gui/demos/adjuster.py +++ b/gui/demos/adjuster.py @@ -9,7 +9,7 @@ from gui.core.ugui import Screen, ssd from gui.widgets.label import Label from gui.widgets.buttons import CloseButton -from gui.widgets.adjuster import Adjuster +from gui.widgets.adjuster import Adjuster, FloatAdj from gui.core.writer import CWriter # Font for CWriter @@ -18,7 +18,6 @@ from gui.core.colors import * class BaseScreen(Screen): - def __init__(self): super().__init__() @@ -26,33 +25,43 @@ class BaseScreen(Screen): col = 2 row = 2 self.lbl1 = Label(wri, row, col, 60, bdcolor=RED) - a = Adjuster(wri, row, self.lbl1.mcol + 2, fgcolor=RED, callback=self.adj1_callback) - Label(wri, row, a.mcol + 2, "Simple") + a = Adjuster(wri, row, self.lbl1.mcol + 2, fgcolor=RED, + callback=self.adj1_callback) + Label(wri, row, a.mcol + 2, "0-1") row = self.lbl1.mrow + 5 self.lbl2 = Label(wri, row, col, 60, bdcolor=RED) - a =Adjuster(wri, row, self.lbl2.mcol + 2, fgcolor=RED, value=0.5, callback=self.adj2_callback) + a = Adjuster(wri, row, self.lbl2.mcol + 2, + fgcolor=RED, value=0.5, + callback=self.adj2_callback) Label(wri, row, a.mcol + 2, "Scale") row = self.lbl2.mrow + 5 self.lbl3 = Label(wri, row, col, 60, bdcolor=YELLOW) - a = Adjuster(wri, row, self.lbl3.mcol + 2, fgcolor=YELLOW, callback=self.adj3_callback) + a = Adjuster(wri, row, self.lbl3.mcol + 2, fgcolor=YELLOW, + callback=self.adj3_callback) Label(wri, row, a.mcol + 2, "Log") + self.fa = FloatAdj(wri, a.mrow + 5, col, color=BLUE, + map_func=lambda x: (x - 0.5) * 20, + fstr="{:6.2f}", text="class") CloseButton(wri) # Quit the application def adj1_callback(self, adj): v = adj.value() # Typically do mapping here - self.lbl1.value(f'{v:4.2f}') + self.lbl1.value(f"{v:4.2f}") def adj2_callback(self, adj): - v = (adj.value() - 0.5) * 10 # Scale and offset - self.lbl2.value(f'{v:4.2f}') + v = (adj.value() - 0.5) * 10 # Scale and offset + self.lbl2.value(f"{v:4.2f}") def adj3_callback(self, adj): - v = 10 ** (3 * adj.value()) # Log 3 decades - self.lbl3.value(f'{v:4.2f}') + v = 10 ** (3 * adj.value()) # Log 3 decades + self.lbl3.value(f"{v:4.2f}") + #def after_open(self): # Demo of programmatic change + #self.fa.value(0.5) def test(): - print('Demo of Adjuster control.') + print("Demo of Adjuster control.") Screen.change(BaseScreen) # A class is passed here, not an instance. + test() diff --git a/gui/widgets/adjuster.py b/gui/widgets/adjuster.py index 97edbc2..aaea3f1 100644 --- a/gui/widgets/adjuster.py +++ b/gui/widgets/adjuster.py @@ -4,24 +4,27 @@ # Copyright (c) 2021 Peter Hinch from gui.core.ugui import LinearIO, display +from gui.widgets.label import Label import math TWOPI = 2 * math.pi # Null function -dolittle = lambda *_ : None +dolittle = lambda *_: None # *********** CONTROL KNOB CLASS *********** + class Adjuster(LinearIO): - def __init__(self, writer, row, col, *, value=0.0, - fgcolor=None, bgcolor=None, color=None, prcolor=None, - callback=dolittle, args=[]): + def __init__(self, writer, row, col, *, + value=0.0, fgcolor=None, bgcolor=None, color=None, + prcolor=None, callback=dolittle, args=[]): height = writer.height # Match a user-linked Label - super().__init__(writer, row, col, height, height, fgcolor, - bgcolor, False, value, True, prcolor) + super().__init__(writer, row, col, height, height, + fgcolor, bgcolor, False, value, + True, prcolor) super()._set_callbacks(callback, args) radius = height / 2 - self.arc = 1.5 * math.pi # Usable angle of control + self.arc = 1.5 * math.pi # Usable angle of control self.radius = radius self.xorigin = col + radius self.yorigin = row + radius @@ -38,7 +41,7 @@ class Adjuster(LinearIO): display.fillcircle(self.xorigin, self.yorigin, radius, self.color) display.circle(self.xorigin, self.yorigin, radius, self.fgcolor) display.circle(self.xorigin, self.yorigin, radius, self.fgcolor) - self._drawpointer(self._value, self.fgcolor) # draw new + self._drawpointer(self._value, self.fgcolor) # draw new def _drawpointer(self, value, color): arc = self.arc @@ -47,3 +50,39 @@ class Adjuster(LinearIO): x_end = int(self.xorigin + length * math.sin(angle)) y_end = int(self.yorigin - length * math.cos(angle)) display.line(int(self.xorigin), int(self.yorigin), x_end, y_end, color) + +# This class combines an Adjuster with one or two labels. Numerous layout +# options exist: users may wish to write their own versions of this example. +# The map_func enables instances to have their own mapping of Adjuster value +# to perform offset, scaling, log mapping etc. +# The object's value is that of the Adjuster, in range 0.0-1.0. The scaled +# value is retrieved with .mapped_value() +class FloatAdj: + def __init__(self, wri, row, col, *, + lbl_width=60, value=0.0, color=None, + fstr="{:4.2f}", map_func=lambda v: v, text="", + callback=dolittle, args=[]): + + self.fstr = fstr + self.map_func = map_func + self.callback = callback + self.args = args + + self.lbl = Label(wri, row, col, lbl_width, bdcolor=color) + self.adj = Adjuster(wri, row, self.lbl.mcol + 2, value=value, + fgcolor=color, callback=self.cb) + l = Label(wri, row, self.adj.mcol + 2, text) if text else self.adj + # Facilitate relative positioning. + self.mcol = l.mcol + self.mrow = self.adj.mrow + + def cb(self, adj): + self.lbl.value(self.fstr.format(self.mapped_value(adj))) + self.callback(self, *self.args) + + # Behave like a Widget. + def value(self, v=None): + return self.adj.value(v) + + def mapped_value(self, adj=None): # Special handling for initial callback + return self.map_func(self.value() if adj is None else adj.value())