# sliders.py Extension to ugui providing linear "potentiometer" widgets. # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2021 Peter Hinch from micropython import const from gui.core.ugui import LinearIO, display from gui.core.colors import * # Null function dolittle = lambda *_ : None # *********** SLIDER CLASSES *********** # A slider's text items lie outside its bounding box. _SLIDE_DEPTH = const(6) # Must be divisible by 2 _TICK_VISIBLE = const(3) # No. of tick pixels visible either side of slider _HALF_SLOT_WIDTH = const(2) # Width of slot /2 class Slider(LinearIO): def __init__(self, writer, row, col, *, height=100, width=20, divisions=10, legends=None, fgcolor=None, bgcolor=None, fontcolor=None, bdcolor=None, slotcolor=None, prcolor=None, callback=dolittle, args=[], value=0.0, active=True): width &= 0xfe # ensure divisible by 2 super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, value, active, prcolor) super()._set_callbacks(callback, args) self.divisions = divisions self.legends = legends if legends is not None: # Adjust column metric ml = max((writer.stringlen(l) for l in legends)) self.mcol += ml + 2 # Strings are rendered 2 pixels right of border self.fontcolor = self.fgcolor if fontcolor is None else fontcolor self.slotcolor = self.bgcolor if slotcolor is None else slotcolor # Define slider self.slide_x0 = col + _TICK_VISIBLE # Draw slider shorter than ticks self.slide_w = width - 2 * _TICK_VISIBLE # y coord of top left hand corner of slide when value == 0 self.slide_y0 = row + height - _SLIDE_DEPTH - 1 # Slot coordinates centre = col + width // 2 self.slot_x0 = centre - _HALF_SLOT_WIDTH self.slot_y0 = row + _SLIDE_DEPTH // 2 self.slot_h = height - _SLIDE_DEPTH - 1 self.draw = True # Ensure a redraw on next refresh # Run callback (e.g. to set dynamic colors) self.callback(self, *self.args) def show(self): # Blank slot, ticks and slider if super().show(False): # Honour bgcolor x = self.col y = self.slot_y0 # Length of travel of slider slot_len = self.slot_h # Dimensions of slot slot_w = 2 * _HALF_SLOT_WIDTH if self.divisions > 0: dy = slot_len / (self.divisions) # Tick marks xs = x + 1 xe = x + self.width -1 for tick in range(self.divisions + 1): ypos = int(y + dy * tick) display.line(xs, ypos, xe, ypos, self.fgcolor) # Blank and redraw slot display.fill_rect(self.slot_x0, self.slot_y0, slot_w, slot_len, self.slotcolor) display.rect(self.slot_x0, self.slot_y0, slot_w, slot_len, self.fgcolor) txtcolor = GREY if self.greyed_out() else self.fontcolor # Dynamic processing of legends supports greying-out if self.legends is not None: if len(self.legends) <= 1: dy = 0 else: dy = slot_len / (len(self.legends) -1) yl = y + slot_len # Start at bottom wri = self.writer fhdelta = wri.height / 2 for legend in self.legends: display.print_left(wri, x + self.width + 4, int(yl - fhdelta), legend, txtcolor, self.bgcolor) yl -= dy slide_y = round(self.slide_y0 - self._value * slot_len) display.fill_rect(self.slide_x0, slide_y, self.slide_w, _SLIDE_DEPTH, self.fgcolor) self.drawn = True def color(self, color): if color != self.fgcolor: self.fgcolor = color self.draw = True class HorizSlider(LinearIO): def __init__(self, writer, row, col, *, height=20, width=100, divisions=10, legends=None, fgcolor=None, bgcolor=None, fontcolor=None, bdcolor=None, slotcolor=None, prcolor=None, callback=dolittle, args=[], value=0.0, active=True): height &= 0xfe # ensure divisible by 2 super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, value, active, prcolor) super()._set_callbacks(callback, args) self.divisions = divisions self.legends = legends self.fontcolor = self.fgcolor if fontcolor is None else fontcolor self.slotcolor = self.bgcolor if slotcolor is None else slotcolor # Define slider self.slide_y0 = row + _TICK_VISIBLE # Draw slider shorter than ticks self.slide_h = height - 2 * _TICK_VISIBLE # Slot coordinates self.slot_x0 = col + _SLIDE_DEPTH // 2 self.slot_w = width - _SLIDE_DEPTH - 1 centre = row + height // 2 self.slot_y0 = centre - _HALF_SLOT_WIDTH self.draw = True # Ensure a redraw on next refresh # Run callback (e.g. to set dynamic colors) self.callback(self, *self.args) def show(self): # Blank slot, ticks and slider if super().show(False): # Honour bgcolor x = self.slot_x0 y = self.row slot_len = self.slot_w # Slot dimensions slot_h = 2 * _HALF_SLOT_WIDTH if self.divisions > 0: dx = slot_len / (self.divisions) # Tick marks ys = y + 1 ye = y + self.height - 1 for tick in range(self.divisions + 1): xpos = int(x + dx * tick) display.line(xpos, ys, xpos, ye, self.fgcolor) # Blank and redraw slot display.fill_rect(self.slot_x0, self.slot_y0, slot_len, slot_h, self.slotcolor) display.rect(self.slot_x0, self.slot_y0, slot_len, slot_h, self.fgcolor) txtcolor = GREY if self.greyed_out() else self.fontcolor if self.legends is not None: if len(self.legends) <= 1: dx = 0 else: dx = slot_len / (len(self.legends) -1) xl = x wri = self.writer for legend in self.legends: offset = wri.stringlen(legend) / 2 display.print_left(wri, int(xl - offset), y - wri.height - 4, legend, txtcolor, self.bgcolor) xl += dx self.slide_x = round(self.col + self._value * slot_len) display.fill_rect(self.slide_x, self.slide_y0, _SLIDE_DEPTH, self.slide_h, self.fgcolor) self.drawn = True def color(self, color): if color != self.fgcolor: self.fgcolor = color self.draw = True