# nanogui.py Displayable objects based on the Writer and CWriter classes # V0.41 Peter Hinch 16th Nov 2020 # Move cmath dependency to widgets/dial # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2018-2021 Peter Hinch # Base class for a displayable object. Subclasses must implement .show() and .value() # Has position, colors and border definition. # border: False no border None use bgcolor, int: treat as color from gui.core.colors import * # Populate color LUT before use. from gui.core.writer import Writer import framebuf import gc def circle(dev, x0, y0, r, color, _=1): # Draw circle x, y, r = int(x0), int(y0), int(r) dev.ellipse(x, y, r, r, color) def fillcircle(dev, x0, y0, r, color): # Draw filled circle x, y, r = int(x0), int(y0), int(r) dev.ellipse(x, y, r, r, color, True) # If a (framebuf based) device is passed to refresh, the screen is cleared. # None causes pending widgets to be drawn and the result to be copied to hardware. # The pend mechanism enables a displayable object to postpone its renedering # until it is complete: efficient for e.g. Dial which may have multiple Pointers def refresh(device, clear=False): if not isinstance(device, framebuf.FrameBuffer): raise ValueError('Device must be derived from FrameBuffer.') if device not in DObject.devices: DObject.devices[device] = set() device.fill(0) else: if clear: DObject.devices[device].clear() # Clear the pending set device.fill(0) else: for obj in DObject.devices[device]: obj.show() DObject.devices[device].clear() device.show() # Displayable object: effectively an ABC for all GUI objects. class DObject(): devices = {} # Index device instance, value is a set of pending objects @classmethod def _set_pend(cls, obj): cls.devices[obj.device].add(obj) def __init__(self, writer, row, col, height, width, fgcolor, bgcolor, bdcolor): writer.set_clip(True, True, False) # Disable scrolling text self.writer = writer device = writer.device self.device = device # The following assumes that the widget is mal-positioned, not oversize. if row < 0: row = 0 self.warning() elif row + height >= device.height: row = device.height - height - 1 self.warning() if col < 0: col = 0 self.warning() elif col + width >= device.width: col = device.width - width - 1 self.warning() self.row = row self.col = col self.width = width self.height = height self._value = None # Type depends on context but None means don't display. # Current colors if fgcolor is None: fgcolor = writer.fgcolor if bgcolor is None: bgcolor = writer.bgcolor if bdcolor is None: bdcolor = fgcolor self.fgcolor = fgcolor self.bgcolor = bgcolor # bdcolor is False if no border is to be drawn self.bdcolor = bdcolor # Default colors allow restoration after dynamic change self.def_fgcolor = fgcolor self.def_bgcolor = bgcolor self.def_bdcolor = bdcolor # has_border is True if a border was drawn self.has_border = False def warning(self): print('Warning: attempt to create {} outside screen dimensions.'.format(self.__class__.__name__)) # Blank working area # Draw a border if .bdcolor specifies a color. If False, erase an existing border def show(self): wri = self.writer dev = self.device dev.fill_rect(self.col, self.row, self.width, self.height, self.bgcolor) if isinstance(self.bdcolor, bool): # No border if self.has_border: # Border exists: erase it dev.rect(self.col - 2, self.row - 2, self.width + 4, self.height + 4, self.bgcolor) self.has_border = False elif self.bdcolor: # Border is required dev.rect(self.col - 2, self.row - 2, self.width + 4, self.height + 4, self.bdcolor) self.has_border = True def value(self, v=None): if v is not None: self._value = v return self._value def text(self, text=None, invert=False, fgcolor=None, bgcolor=None, bdcolor=None): if hasattr(self, 'label'): self.label.value(text, invert, fgcolor, bgcolor, bdcolor) else: raise ValueError('Attempt to update nonexistent label.')