{"id":15061521,"url":"https://github.com/bunnysakura/espnanotool-mpy","last_synced_at":"2026-02-01T15:31:40.249Z","repository":{"id":255912139,"uuid":"853858517","full_name":"BunnySakura/EspNanoTool-mpy","owner":"BunnySakura","description":"一个使用ESP32系列芯片开发的小工具，开发语言为MicroPython。| 合宙 ESP32C3-CORE 开发板和 0.96寸 屏幕拓展板 | M5Stack BASIC","archived":false,"fork":false,"pushed_at":"2024-09-10T17:33:07.000Z","size":4861,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-15T04:51:14.896Z","etag":null,"topics":["esp32","esp32c3","gui","luatos","m5stack","m5stack-core","micropython","micropython-esp32","mpy","mpython"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BunnySakura.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-07T18:22:04.000Z","updated_at":"2024-09-11T05:55:20.000Z","dependencies_parsed_at":"2024-09-10T19:03:18.671Z","dependency_job_id":null,"html_url":"https://github.com/BunnySakura/EspNanoTool-mpy","commit_stats":null,"previous_names":["bunnysakura/espnanotool-mpy"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BunnySakura%2FEspNanoTool-mpy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BunnySakura%2FEspNanoTool-mpy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BunnySakura%2FEspNanoTool-mpy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BunnySakura%2FEspNanoTool-mpy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BunnySakura","download_url":"https://codeload.github.com/BunnySakura/EspNanoTool-mpy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239207445,"owners_count":19599963,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["esp32","esp32c3","gui","luatos","m5stack","m5stack-core","micropython","micropython-esp32","mpy","mpython"],"created_at":"2024-09-24T23:20:54.744Z","updated_at":"2025-10-31T14:30:38.194Z","avatar_url":"https://github.com/BunnySakura.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# micropython-micro-gui\n\n![main.jpg](demo_show%2Fmain.jpg)\n\n![wifi-scan.jpg](demo_show%2Fwifi-scan.jpg)\n\n![clock.jpg](demo_show%2Fclock.jpg)\n\nThis is a lightweight, portable, MicroPython GUI library for displays having\ndrivers subclassed from `framebuf`. Written in Python it runs under a standard\nMicroPython firmware build. Options for data input comprise:\n * Two pushbuttons: limited capabilities with some widgets unusable for input.\n * Three pushbuttons with full capability.\n * Five pushbuttons: full capability, less \"modal\" interface.\n * A switch-based navigation joystick: another way to implement five buttons.\n * Via two pushbuttons and a rotary encoder such as\n [this one](https://www.adafruit.com/product/377). An intuitive interface.\n * On ESP32 physical buttons may be replaced with touchpads.\n\nIt is larger and more complex than `nano-gui` owing to the support for input.\nIt enables switching between screens and launching modal windows. Widgets are\na substantial superset of `nano-gui` widgets.\n\n#### [Supported displays](https://github.com/peterhinch/micropython-nano-gui/blob/master/DISPLAYS.md)\n\nIt is compatible with all display drivers for\n[nano-gui](https://github.com/peterhinch/micropython-nano-gui) so is portable\nto a wide range of displays. It is also portable between hosts.\n\n![Image](./images/rp2_test_fixture.JPG)  \nRaspberry Pico with an ILI9341 from eBay.\n\n![Image](./images/ttgo.JPG)  \nTTGO T-Display. A joystick switch and an SIL resistor make a simple inexpensive\nand WiFi-capable system.\n\n![Image](./images/epaper.JPG)  \nmicro_gui now has limited support for ePaper.\n\n# Rationale\n\nTouch GUI's are supported by [micropython-touch](https://github.com/peterhinch/micropython-touch).\nThis GUI provides an alternative for displays without a touch overlay. A\nnon-touch solution avoids the need for calibration and can also save cost. Cheap\nChinese touch displays often marry a good display to a poor touch overlay. It\ncan make sense to use such a screen with micro-gui, ignoring the touch overlay.\nFor touch support it is worth spending money on a good quality device (for\nexample Adafruit).\n\nThe micro-gui input options work well and can yield inexpensive solutions. A\nnetwork-connected board with a 135x240 color display can be built for under £20\n($20?) using the\n[TTGO T-Display](https://www.lilygo.cc/products/lilygo%C2%AE-ttgo-t-display-1-14-inch-lcd-esp32-control-board). The\ntest board shown above has a 320x240 display from eBay with a Pi Pico and has a\ncomponent cost of well below £20.\n\nThe following are similar GUI repos with differing objectives.\n * [nano-gui](https://github.com/peterhinch/micropython-nano-gui) Extremely low\n RAM usage but display-only with no provision for input.\n * [LCD160cr](https://github.com/peterhinch/micropython-lcd160cr-gui) Touch GUI\n for the official display.\n * [RA8875](https://github.com/peterhinch/micropython_ra8875) Touch GUI for\n displays with RA8875 controller. Supports large displays, e.g. from Adafruit.\n * [SSD1963](https://github.com/peterhinch/micropython-tft-gui) Touch GUI for\n displays based on SSD1963 and XPT2046. High performance on large displays due\n to the parallel interface. Specific to STM hosts.\n\n[LVGL](https://lvgl.io/) is a pretty icon-based GUI library. It is written in C\nwith MicroPython bindings; consequently it requires the build system for your\ntarget and a C device driver (unless you can acquire a suitable binary).\n\n# Project status\n\nApril 2024: Add screen replace feature for non-tree navigation.\nSept 2023: Add \"encoder only\" mode suggested by @eudoxos.  \nApril 2023: Add limited ePaper support, grid widget, calendar and epaper demos.\nNow requires firmware \u003e= V1.20.  \nJuly 2022: Add ESP32 touch pad support.  \nJune 2022: Add [QRMap](./README.md#620-qrmap-widget) and\n[BitMap](./README.md#619-bitmap-widget) widgets.  \nMarch 2022: Add [latency control](./README.md#45-class-variable) for hosts with\nSPIRAM.  \nFebruary 2022: Supports use with only three buttons devised by Bart Cerneels.\nSimplified widget import. Existing users should replace the entire `gui` tree.  \n\nCode has been tested on ESP32, ESP32-S2, ESP32-S3, Pi Pico and Pyboard. This is\nunder development so check for updates.\n\n# 0. Contents\n\n1. [Basic concepts](./README.md#1-basic-concepts) Including \"Hello world\" script.  \n 1.1 [Coordinates](./README.md#11-coordinates) The GUI's coordinate system.  \n 1.2 [Screen Window and Widget objects](./README.md#12-Screen-window-and-widget-objects) Basic GUI classes.  \n 1.3 [Fonts](./README.md#13-fonts)  \n 1.4 [Navigation](./README.md#14-navigation) Options for hardware. How the GUI navigates between widgets.  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;1.4.1 [Encoder-only mode](./README.md#141-encoder-only-mode) Using only an encoder for navigation.  \n 1.5 [Hardware definition](./README.md#15-hardware-definition) How to configure your hardware.  \n 1.6 [Quick hardware check](./README.md#16-quick-hardware-check) Testing the hardware config. Please do this first.  \n 1.7 [Installation](./README.md#17-installation) Installing the library.  \n 1.8 [Performance and hardware notes](./README.md#18-performance-and-hardware-notes)  \n 1.9 [Firmware and dependencies](./README.md#19-firmware-and-dependencies)  \n 1.10 [Supported hosts and displays](./README.md#110-supported-hosts-and-displays)  \n 1.11 [Files](./README.md#111-files) Discussion of the files in the library.  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;1.11.1 [Demos](./README.md#1111-demos) Simple demos showing coding techniques.  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;1.11.2 [Test scripts](./README.md#1112-test-scripts) GUI tests, some needing larger displays  \n 1.12 [Floating Point Widgets](./README.md#112-floating-point-widgets) How to input floating point data.  \n2. [Usage](./README.md#2-usage) Application design.  \n 2.1 [Program structure and operation](./README.md#21-program-structure-and-operation) A simple demo of navigation and use.  \n 2.2 [Callbacks](./README.md#22-callbacks)  \n 2.3 [Colors](./README.md#23-colors)  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;2.3.1 [Monochrome displays](./README.md#231-monochrome-displays)  \n3. [The ssd and display objects](./README.md#3-the-ssd-and-display-objects)  \n 3.1 [SSD class](./README.md#31-ssd-class) Instantiation in hardware_setup.  \n 3.2 [Display class](./README.md#32-display-class) Instantiation in hardware_setup.py.  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;3.2.1 [Encoder usage](./README.md#321-encoder-usage)  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;3.2.2 [Encoder only mode](./README.md#322-encoder-only-mode)  \n4. [Screen class](./README.md#4-screen-class) Full screen window.  \n 4.1 [Class methods](./README.md#41-class-methods)  \n 4.2 [Constructor](./README.md#42-constructor)  \n 4.3 [Callback methods](./README.md#43-callback-methods) Methods which run in response to events.  \n 4.4 [Method](./README.md#44-method) Optional interface to asyncio code.  \n 4.5 [Class variable](./README.md#45-class-variable) Control latency caused by garbage collection.  \n 4.6 [Usage](./README.md#46-usage) Accessing data created in a screen.  \n5. [Window class](./README.md#5-window-class)  \n 5.1 [Constructor](./README.md#51-constructor)  \n 5.2 [Class method](./README.md#52-class-method)  \n 5.3 [Popup windows](./README.md#53-popup-windows)  \n6. [Widgets](./README.md#6-widgets) Displayable objects.  \n 6.1 [Label widget](./README.md#61-label-widget) Single line text display.  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;6.1.1 [Grid widget](./README.md#611-grid-widget) A spreadsheet-like array of labels.  \n 6.2 [LED widget](./README.md#62-led-widget) Display Boolean values.  \n 6.3 [Checkbox widget](./README.md#63-checkbox-widget) Enter Boolean values.  \n 6.4 [Button and CloseButton widgets](./README.md#64-button-and-closebutton-widgets) Pushbutton emulation.  \n 6.5 [ButtonList object](./README.md#65-buttonlist-object) Pushbuttons with multiple states.  \n 6.6 [RadioButtons object](./README.md#66-radiobuttons-object) One-of-N pushbuttons.  \n 6.7 [Listbox widget](./README.md#67-listbox-widget)  \n 6.8 [Dropdown widget](./README.md#68-dropdown-widget) Dropdown lists.  \n 6.9 [DialogBox class](./README.md#69-dialogbox-class) Pop-up modal dialog boxes.  \n 6.10 [Textbox widget](./README.md#610-textbox-widget) Scrolling text display.  \n 6.11 [Meter widget](./README.md#611-meter-widget) Display floats on an analog meter, with data driven callbacks.  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;6.11.1 [Region class](./README.md#161-region-class)  \n 6.12 [Slider and HorizSlider widgets](./README.md#612-slider-and-horizslider-widgets) Linear potentiometer float data entry and display  \n 6.13 [Scale widget](./README.md#613-scale-widget) High precision float entry and display.  \n 6.14 [ScaleLog widget](./README.md#614-scalelog-widget) Wide dynamic range float entry and display.  \n 6.15 [Dial widget](./README.md#615-dial-widget) Display multiple vectors.  \n 6.16 [Knob widget](./README.md#616-knob-widget) Rotary potentiometer float entry.  \n 6.17 [Adjuster widget](./README.md#617-adjuster-widget) Space saving way to enter floats.  \n 6.18 [Menu class](./README.md#618-menu-class)  \n 6.19 [BitMap widget](./README.md#619-bitmap-widget) Draw bitmaps from files.  \n 6.20 [QRMap widget](./README.md#620-qrmap-widget) Draw QR codes created by uQR.  \n7. [Graph plotting](./README.md#7-graph-plotting) Widgets for Cartesian and polar graphs.  \n 7.1 [Concepts](./README.md#71-concepts)  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;7.1.1 [Graph classes](./README.md#711-graph-classes)  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;7.1.2 [Curve classes](./README.md#712-curve-classes)  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;7.1.3 [Coordinates](./README.md#713-coordinates)  \n 7.2 [Graph classes](./README.md#72-graph-classes)  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;7.2.1 [Class CartesianGraph](./README.md#721-class-cartesiangraph)  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;7.2.2 [Class PolarGraph](./README.md#722-class-polargraph)  \n 7.3 [Curve classes](./README.md#73-curve-classes)  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;7.3.1 [Class Curve](./README.md#731-class-curve)  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;7.3.2 [Class PolarCurve](./README.md#732-class-polarcurve)  \n 7.4 [Class TSequence](./README.md#74-class-tsequence) Plotting realtime, time sequential data.  \n8. [ESP32 touch pads](./README.md#8-esp32-touch-pads) Replacing buttons with touch pads.  \n9. [Realtime applications](./README.md#9-realtime-applications) Accommodating tasks requiring fast RT performance.  \n10. [ePaper displays](./README.md#10-epaper-displays) Guidance on using ePaper displays.  \n\n[Appendix 1 Application design](./README.md#appendix-1-application-design) Tab order, button layout, encoder interface, use of graphics primitives, more on callbacks.\n[Appendix 2 Freezing bytecode](./README.md#appendix-2-freezing-bytecode) Optional way to save RAM.  \n[Appendix 3 Cross compiling](./README.md#appendix-3-cross-compiling) Another way to save RAM.  \n\n# 1. Basic concepts\n\nInternally `micro-gui` uses `asyncio`. It presents a conventional callback\nbased interface; knowledge of `asyncio` is not required for its use. Display\nrefresh is handled automatically. Widgets are drawn using graphics primitives\nrather than icons. This makes them efficiently scalable and minimises RAM usage\ncompared to icon-based graphics. It also facilitates the provision of extra\nvisual information. For example the color of all or part of a widget may be\nchanged programmatically, for example to highlight an overrange condition.\nThere is limited support for\n[icons](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/WRITER.md#3-icons)\nin pushbuttons via icon fonts, also via the [BitMap widget](./README.md#619-bitmap-widget).\n\nThe following, taken from `gui.demos.simple.py`, is a complete application. It\nshows a message and has \"Yes\" and \"No\" buttons which trigger a callback.\n```python\nimport hardware_setup  # Create a display instance\nfrom gui.core.ugui import Screen, ssd\n\nfrom gui.widgets import Label, Button, CloseButton\n# from gui.core.writer import Writer  # Monochrome display\nfrom gui.core.writer import CWriter\n# Font for CWriter or Writer\nimport gui.fonts.arial10 as arial10\nfrom gui.core.colors import *\n\n\nclass BaseScreen(Screen):\n\n    def __init__(self):\n\n        def my_callback(button, arg):\n            print('Button pressed', arg)\n\n        super().__init__()\n        # wri = Writer(ssd, arial10, verbose=False)  # Monochrome display\n        wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)\n\n        col = 2\n        row = 2\n        Label(wri, row, col, 'Simple Demo')\n        row = 50\n        Button(wri, row, col, text='Yes', callback=my_callback, args=('Yes',))\n        col += 60\n        Button(wri, row, col, text='No', callback=my_callback, args=('No',))\n        CloseButton(wri)  # Quit the application\n\ndef test():\n    print('Simple demo: button presses print to REPL.')\n    Screen.change(BaseScreen)  # A class is passed here, not an instance.\n\ntest()\n```\nNotes:  \n * Monochrome displays use the `Writer` class rather than `CWriter` to\n render fonts, as per the commented-out code above.\n * Hardware is defined by a single small file `hardware_setup.py` which the\n user must edit.\n\n## 1.1 Coordinates\n\nThese are defined as `row` and `col` values where `row==0` and `col==0`\ncorresponds to the top left most pixel. Rows increase downwards and columns\nincrease to the right. The graph plotting widget uses normal mathematical\nconventions within graphs.\n\n###### [Contents](./README.md#0-contents)\n\n## 1.2 Screen Window and Widget objects\n\nA `Screen` is a window which occupies the entire display. A `Screen` can\noverlay another, replacing all its contents. When closed, the `Screen` below is\nre-displayed. This default method of navigation results in a tree structure of\n`Screen` instances where the screen below retains state. An alternative allows\na `Screen` to replace another, allowing `Screen` instances to be navigated in an\narbitrary way. For example a set of `Screen` instances might be navigated in a\ncircular fashion. The penalty is that, to save RAM, state is not retained when a\n`Screen` is replaced\n\nA `Window` is a subclass of `Screen` but is smaller, with size and location\nattributes. It can overlay part of an underlying `Screen` and is typically used\nfor dialog boxes. `Window` objects are modal: a `Window` can overlay a `Screen`\nbut cannot overlay another `Window`.\n\nA `Widget` is an object capable of displaying data. Some are also capable of\ndata input: such a widget is defined as `active`. A `passive` widget can only\ndisplay data. An `active` widget can acquire `focus`. The widget with `focus`\nis able to respond to user input. See [navigation](./README.md#14-navigation).\n`Widget` objects have dimensions defined as `height` and `width`. The space\nrequred by them exceeds these dimensions by two pixels all round. This is\nbecause `micro-gui` displays a surrounding white border to show which object\ncurrently has `focus`. Thus to place a `Widget` at the extreme top left, `row`\nand `col` values should be 2.\n\n###### [Contents](./README.md#0-contents)\n\n## 1.3 Fonts\n\nPython font files are in the `gui/fonts` directory. The easiest way to conserve\nRAM is to freeze them which is highly recommended. In doing so the directory\nstructure must be maintained.\n\nTo create alternatives, Python fonts may be generated from industry standard\nfont files with\n[font_to_py.py](https://github.com/peterhinch/micropython-font-to-py.git). The\n`-x` option for horizontal mapping must be specified. If fixed pitch rendering\nis required `-f` is also required. Supplied examples are:\n\n * `arial10.py` Variable pitch Arial. 10 pixels high.\n * `arial35.py` Arial 35 high.\n * `arial_50.py` Arial 50 high.\n * `courier20.py` Fixed pitch Courier, 20 high.\n * `font6.py` FreeSans 14 high.\n * `font10.py` FreeSans 17 high.\n * `freesans20.py` FreeSans 20 high.\n\nThe directory `gui/fonts/bitmaps` is only required for the `bitmap.py` demo.\n\n###### [Contents](./README.md#0-contents)\n\n## 1.4 Navigation\n\nThe GUI requires from 2 to 5 pushbuttons for control. These are:\n 1. `Next` Move to the next widget.\n 2. `Select` Operate the currently selected widget.\n 3. `Prev` Move to the previous widget.\n 4. `Increase` Move within the widget (i.e. adjust its value).\n 5. `Decrease` Move within the widget.\n\nAn alternative is to replace buttons 4 and 5 with a quadrature encoder knob\nsuch as [this one](https://www.adafruit.com/product/377). That device has a\nswitch which operates when the knob is pressed: this may be wired for the\n`Select` button. This provides the most intuitive operation.\n\nMany widgets such as `Pushbutton` or `Checkbox` objects require only the\n`Select` button to operate: it is possible to design an interface with a subset\nof `micro-gui` widgets which requires only the first two buttons. With three\nbuttons all widgets may be used without restriction.\n\nWidgets such as `Listbox` objects, dropdown lists (`Dropdown`), and those for\nfloating point data entry can use the `Increase` and `Decrease` buttons (or an\nencoder) to select a data item or to adjust the linear value. If three buttons\nare provided, the GUI will enter \"adjust\" mode in response to a double-click\nof `Select`. In this mode `Prev` and `Next` act to decrease and increase the\nwidget's value. A further double-click restores normal navigation. This is\ndiscussed in [Floating Point Widgets](./README.md#112-floating-point-widgets).\n\nThe currently selected `Widget` is identified by a white border: the `focus`\nmoves between widgets via `Next` and `Prev`. Only `active` `Widget` instances\n(those that can accept input) can receive the `focus`.  Widgets are defined as\n`active` or `passive` in the constructor, and this status cannot be changed. In\nsome cases the state can be specified as a constructor arg, but other widgets\nhave a predefined state. An `active` widget can be disabled and re-enabled at\nruntime. A disabled `active` widget is shown \"greyed-out\" and cannot accept the\n`focus` until re-enabled.\n\n### 1.4.1 Encoder only mode\n\nThis uses a rotary encoder with a built-in pushbutton as the sole means of\nnavigation, a mode suggested by @eudoxos. By default, turning the dial moves\nthe currency between widgets; the widget with the focus has a white border.\nWidgets for numeric entry such as sliders and scales may be put into \"adjust\"\nmode with a double click. In that mode turning the dial adjusts the widget.\n[Floating Point Widgets](./README.md#112-floating-point-widgets) can enter\n\"precision\" adjustment mode with a long press of the button. \"Adjust\" and\n\"precision\" modes are cleared with a short button press.\n\nThis mode works well and its use is quite intuitive. Navigation by turning a\ndial makes it particularly useful when a screen has a large number of widgets.\n\n###### [Contents](./README.md#0-contents)\n\n## 1.5 Hardware definition\n\nA file `hardware_setup.py` must exist in the GUI root directory. This defines\nthe connections to the display, the display driver, and pins used for the\npushbuttons. Example files may be found in the `setup_examples` directory.\nFurther examples (without pin definitions) are in this\n[nano-gui directory](https://github.com/peterhinch/micropython-nano-gui/tree/master/setup_examples).\n\nThe following is a typical example for a Raspberry Pi Pico driving an ILI9341\ndisplay:\n\n```python\nfrom machine import Pin, SPI, freq\nimport gc\n\nfrom drivers.ili93xx.ili9341 import ILI9341 as SSD\nfreq(250_000_000)  # RP2 overclock\n# Create and export an SSD instance\npdc = Pin(8, Pin.OUT, value=0)  # Arbitrary pins\nprst = Pin(9, Pin.OUT, value=1)\npcs = Pin(10, Pin.OUT, value=1)\nspi = SPI(0, baudrate=30_000_000)\ngc.collect()  # Precaution before instantiating framebuf\n# Instantiate display and assign to ssd. For args see display drivers doc.\nssd = SSD(spi, pcs, pdc, prst, usd=True)\n# The following import must occur after ssd is instantiated.\nfrom gui.core.ugui import Display, quiet\n# quiet()\n# Define control buttons\nnxt = Pin(19, Pin.IN, Pin.PULL_UP)  # Move to next control\nsel = Pin(16, Pin.IN, Pin.PULL_UP)  # Operate current control\nprev = Pin(18, Pin.IN, Pin.PULL_UP)  # Move to previous control\nincrease = Pin(20, Pin.IN, Pin.PULL_UP)  # Increase control's value\ndecrease = Pin(17, Pin.IN, Pin.PULL_UP)  # Decrease control's value\n# Create a Display instance and assign to display.\ndisplay = Display(ssd, nxt, sel, prev, increase, decrease)\n```\nWhere an encoder replaces the `increase` and `decrease` buttons, only the final\nline needs to be changed to provide an extra arg:\n```python\ndisplay = Display(ssd, nxt, sel, prev, increase, decrease, 4)\n```\nThe final arg specifies the sensitivity of the attached encoder, the higher the\nvalue the more the knob has to be turned for a desired effect. A value of 1\nprovides the highest sensitivity, being the native rate of the encoder. Many\nencoders have mechanical detents: a value of 4 matches the click rate of most\ndevices.\n\nThe commented-out `quiet()` line provides a means of suppressing diagnostic\nmessages.\n\nInstantiation of `SSD` and `Display` classes is detailed in\n[section 3](./README.md#3-the-ssd-and-display-objects).\n\nDisplay drivers are\n[documented here](https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md).\n\n###### [Contents](./README.md#0-contents)\n\n## 1.6 Quick hardware check\n\nThe following may be pasted at the REPL to verify correct connection to the\ndisplay. It also confirms that `hardware_setup.py` is specifying a suitable\ndisplay driver.\n```python\nfrom hardware_setup import ssd  # Create a display instance\nfrom gui.core.colors import *\nssd.fill(0)\nssd.line(0, 0, ssd.width - 1, ssd.height - 1, GREEN)  # Green diagonal corner-to-corner\nssd.rect(0, 0, 15, 15, RED)  # Red square at top left\nssd.rect(ssd.width -15, ssd.height -15, 15, 15, BLUE)  # Blue square at bottom right\nssd.show()\n```\n\n###### [Contents](./README.md#0-contents)\n\n## 1.7 Installation\n\nPlease ensure device firmware is up to date. Clone the repo to the PC with:\n```bash\n$ git clone https://github.com/peterhinch/micropython-micro-gui\n$ cd micropython-micro-gui\n```\nIn the `micropython-micro-gui` directory edit `hardware_setup.py` to match the\nhardware in use.\n\nThe official\n[mpremote](http://docs.micropython.org/en/latest/reference/mpremote.html#mpremote)\ntool is recommended. Install with:\n```bash\n$ pip3 install mpremote\n```\nThere are several options for installation\n 1. Using mpremote to run the GUI demos via the PC without installing.\n 2. Subtractive. Installing the entire GUI, then (optionally) removing unused\n components.\n 3. Additive. Installing a minimal subset and manually adding extra components.\n 4. Using frozen bytecode.\n\n### Testing without installing\n\nThe easy way to start is to use `mpremote` which allows a directory on your PC\nto be mounted on the host. In this way the filesystem on the host is left\nunchanged. This is at some cost in loading speed, especially on ESP32. In the\n`micropython-micro-gui` directory run:\n```bash\n$ mpremote mount .\n```\nThis should provide a REPL. Run the minimal demo:\n```python\n\u003e\u003e\u003e import gui.demos.simple\n```\nIf this runs the hardware is correctly configured and other demos should run.\n\n### Installing a display driver\n\nIt is necessary to install a display driver prior to any GUI installation. On\nnetworked hardware a display driver may be installed as follows (example is for\nST7789):\n```python\n\u003e\u003e\u003e mip.install(\"github:peterhinch/micropython-nano-gui/drivers/st7789\")\n```\nThe last part of the addresss (`st7789`) is the name of the directory holding\ndrivers for the display in use. In cases where the directory holds more than\none driver all will be installed. Unused drivers may be deleted.\n\nInstall using mpremote on the PC as follows:\n```bash\n$ mpremote mip install \"github:peterhinch/micropython-nano-gui/drivers/st7789\"\n```\n### Full installation (subtractive)\n\nThe entire GUI is large. It is possible to install it all from the PC clone by\nissuing:\n```bash\n$ cd micropython-micro-gui\n$ mpremote cp -r gui :\n$ mpremote cp hardware_setup.py :\n```\nThis is rather profligate with Flash storage. There is great scope for\ndiscarding unused fonts, demos and widgets. As an alternative to installing\neverything and pruning, an additive approach may be used where a minimal subset\nis installed with extra fonts and widgets being added as required.\n\n### Minimal installation (additive)\n\nThis installs a subset adequate to run the `simple.py` demo. It comprises:  \n![Image](./images/filesystem.png)  \nNote that `mip` and `mpremote mip` install to `/lib/` which therefore becomes\nthe root of the above tree. The subset is installed with (on the device):\n```python\n\u003e\u003e\u003e mip.install(\"github:peterhinch/micropython-micro-gui\")\n```\nor (on the PC):\n```bash\n$ mpremote mip install \"github:peterhinch/micropython-micro-gui\"\n```\nIn both cases the edited `hardware_setup.py` must be copied from the PC:\n```bash\n$ cd micropython-micro-gui\n$ mpremote cp hardware_setup.py :\n```\nWhen adding components the directory structure must be maintained. For example,\nin the `micropython-micro-gui` directory:\n```bash\n$ mpremote cp gui/fonts/font10.py :/gui/fonts/\n$ mpremote cp gui/widgets/checkbox.py :/gui/widgets/\n```\n\n### Freezing bytecode\n\nThere is scope for speeding loading and saving RAM by using frozen bytecode.\nThe entire `gui` tree may be frozen but the directory structure must be\nmaintained. For reasons that are unclear freezing display drivers may not\nwork. For fexibility, consider keeping `hardware_setup.py` in the filesystem.\nSee [Appendix 2 Freezing bytecode](./README.md#appendix-2-freezing-bytecode).\n\n###### [Contents](./README.md#0-contents)\n\n## 1.8 Performance and hardware notes\n\n#### RAM usage\n\nRunning the `linked_sliders` demo, the code uses about 23,000 bytes with frozen\nbytecode and 55,000 bytes without. To this must be added the size of the frame\nbuffer. This can readily be calculated. For example in the case of the ILI9341\n(a 240x320 pixel unit whose driver uses 4-bit color) the buffer size is  \n`240x320/2 = 38,400` bytes.\n\nA Pico shows ~182000 bytes free with no code running. With `linked_sliders`\nrunning on an ILI9341 display, it shows 120,896 bytes free with frozen\nbytecode and 88,640 bytes free without.\n\nWith multi-pixel displays the size of the frame buffer can prevent the GUI from\ncompiling. If frozen bytecode is impractical, consider cross-compiling. See\n[Appendix 3 Cross compiling](./README.md#appendix-3-cross-compiling).\n\n#### Speed\n\nThe consequence of inadequate speed is that brief button presses can be missed.\nThis is because display update blocks for tens of milliseconds, during which\ntime the pushbuttons are not polled. This can be an issue in displays with a\nlarge number of pixels, multi-byte colors and/or slow SPI clock rates. In high\nresolution cases the device driver has specfic `asyncio` support whereby the\ndriver yields to the scheduler a few times during the refresh.Currently this\nexists on ILI9486, ILI9341 and ST7789 (e.g. TTGO T-Display). By my calculations\nand measurements this should be unnecessary on other drivers, but please report\nany tendency to miss button presses and I will investigate.\n\nThis may be mitigated by two approaches:\n 1. Clocking the SPI bus as fast as possible. This is discussed in\n [the drivers doc](https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md).\n 2. Clocking the host fast (`machine.freq`).\n\n#### Platform notes\n\nOn ESP32 (including the TTGO T-Display) note that pins 36-39 are input-only and\ndo not have pullup support: if these are used for pushbutton input, physical\npullups to 3.3V should be used.\n[See ref](https://randomnerdtutorials.com/esp32-pinout-reference-gpios/).\n\nOn a Pyboard 1.1 with 320x240 ili9341 display it was necessary to use frozen\nbytecode: in this configuration running the `various.py` demo there was 29K of\nfree RAM. Note that, at 37.5KiB, this display is the worst-case in terms of\nRAM usage. A smaller display or a Pyboard D would offer more headroom. Frozen\nbytecode was also necessary on an RP2 running an ILI9486: a 480x320 display\nrequires a 76,800 byte frame buffer.\n\n###### [Contents](./README.md#0-contents)\n\n## 1.9 Firmware and dependencies\n\nFirmware should be V1.17 or later. The source tree includes all dependencies.\nThese are listed to enable users to check for newer versions or to read docs:\n\n * [writer.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/writer.py)\n Provides text rendering of Python font files.\n * [SSD1306 driver](https://github.com/micropython/micropython-lib/tree/master/micropython/drivers/display/ssd1306).\n A copy of the official driver for OLED displays using the SSD1306 chip is\n provided. The link is to the official file.\n * [Synchronisation primitives](https://github.com/peterhinch/micropython-async/tree/master/v3/primitives).\n The link is to my `asyncio` support repo.\n * [PCD8544/Nokia 5110](https://github.com/mcauser/micropython-pcd8544.git).\n Displays based on the Nokia 5110 (PCD8544 chip) require this driver. It is not\n provided in this repo. The link is to its source.\n\n###### [Contents](./README.md#0-contents)\n\n## 1.10 Supported hosts and displays\n\nDevelopment was done using a Raspberry Pi Pico connected to a cheap ILI9341\n320x240 display. I have also tested a TTGO T-Display (an ESP32 host) and a\nPyboard. Code is written with portability as an aim, but MicroPython configs\nvary between platforms and I can't guarantee that every widget will work on\nevery platform. For example, some use the `cmath` module which may be absent on\nsome builds.\n\nSupported displays are as per\n[the nano-gui list](https://github.com/peterhinch/micropython-nano-gui/blob/master/README.md#12-description).\nIn general ePaper and Sharp displays are unlikely to yield good results because\nof slow and visually intrusive refreshing. However there is an exception: the\n[Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm). See\n[10. ePaper displays](./README.md#10-epaper-displays).\n\nDisplay drivers are documented [here](https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md).\n\n###### [Contents](./README.md#0-contents)\n\n## 1.11 Files\n\nDisplay drivers may be found in the `drivers` directory. These are copies of\nthose in `nano-gui`, included for convenience. Note the file\n`drivers/boolpalette.py`, required by all color drivers.\n\nThe system is organised as a Python package with the root being `gui`. Core\nfiles in `gui/core` are:  \n * `colors.py` Constants including colors and shapes.\n * `ugui.py` The start GUI code.\n * `writer.py` Supports the `Writer` and `CWriter` classes.\n\nThe `gui/primitives` directory contains the following files:  \n * `pushbutton.py` Interface to physical pushbuttons and ESP32 touch pads.\n * `delay_ms.py` A software triggerable timer.\n * `encoder.py` Driver for a quadrature encoder. This offers an alternative\n interface - see [Appendix 1](./README.md#appendix-1-application-design).\n\nThe `gui/demos` directory contains a variety of demos and tests described\nbelow.\n\n### 1.11.1 Demos\n\nDemos are run by issuing (for example):\n```python\n\u003e\u003e\u003e import gui.demos.simple\n```\nIf shut down cleanly with the \"close\" button a demo can be re-run with (e.g.):\n```python\ngui.demos.simple.test()\n```\nBefore running a different demo the host should be reset (ctrl-d) to clear RAM.\n\nThese will run on screens of 128x128 pixels or above. The initial ones are\nminimal and aim to demonstrate a single technique.  \n * `simple.py` Minimal demo discussed below. `Button` presses print to REPL.\n * `checkbox.py` A `Checkbox` controlling an `LED`.\n * `slider.py` A `Slider` whose color varies with its value.\n * `slider_label.py` A `Slider` updating a `Label`. Good for trying precision\n mode.\n * `linked_sliders.py` One `Slider` updating two others, and a coding \"wrinkle\"\n required for doing this.\n * `dropdown.py` A dropdown list (with scrolling) updates a `Label`.\n * `listbox.py` A listbox with scrolling.\n * `dialog.py` `DialogBox` demo. Illustrates the screen change mechanism.\n * `screen_change.py` A `Pushbutton` causing a screen change using a re-usable\n \"forward\" button.\n * `screen_replace.py` A more complex (non-tree) screen layout.\n * `primitives.py` Use of graphics primitives.\n * `aclock.py` An analog clock using the `Dial` vector display. Also shows\n screen layout using widget metrics. Has a simple `asyncio` task.\n * `tbox.py` Text boxes and user-controlled scrolling.\n * `tstat.py` A demo of the `Meter` class with data sensitive regions.\n * `menu.py` A multi-level menu.\n * `adjuster.py` Simple demo of the `Adjuster` control.\n * `adjust_vec.py` A pair of `Adjuster`s vary a vector.\n * `bitmap.py` Demo of the `BitMap` widget showing a changing image. (See widget\n    docs).\n * `qrcode.py` Display a QR code. Requires the uQR module.\n * `calendar.py` Demo of grid widget.\n * `epaper.py` Warts-and-all demo for an ePaper display. Currently the only\n supported display is the\n [Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm)\n with Pico or other host.\n\n### 1.11.2 Test scripts\n\nThese more complex demos are run in the same way by issuing (for example):\n```python\n\u003e\u003e\u003e import gui.demos.active\n```\n\nSome of these require larger screens. Required sizes are specified as\n(height x width).  \n * `active.py` Demonstrates `active` controls providing floating point input\n (240x320).\n * `plot.py` Graph plotting (128x200).\n * `screens.py` Listbox, dropdown and dialog boxes (128x240).\n * `various.py` Assorted widgets including the different types of pushbutton\n (240x320).\n * `vtest.py` Clock and compass styles of vector display (240x320).\n * `calendar.py` Demo of grid control (240x320 - but could be reduced).\n\n###### [Contents](./README.md#0-contents)\n\n## 1.12 Floating Point Widgets\n\nSome applications need to adjust a data value with an extremely large dynamic\nrange. This is the ratio of the data value's total range to the smallest\nadjustment that can be made. The mechanism currently implemented enables a\nprecision of 0.05%.\n\nFloating point widgets respond to a brief press of the `increase` or `decrease`\nbuttons by adjusting the value by a small amount. A continued press causes the\nvalue to be repeatedly adjusted, with the amount of the adjustment increasing\nwith time. This enables the entire range of the control to be accessed quickly,\nwhile allowing small changes of 0.5%. This works well. In many cases the level\nof precision will suffice. An encoder provides similar performance.\n\nFine adjustments may be achieved by pressing the `select` button for at least\none second. The GUI will respond by changing the border color from white\n(i.e. has focus) to yellow. In this mode a brief press of `increase` or\n`decrease` or small movement of an encoder will have a reduced effect (0.05%).\nFine mode may be cancelled by pressing `select` or by moving the focus to\nanother control. This also works in three-button mode, with `Next` and `Prev`\nperforming the adjustments.\n\nIn the case of slider and knob controls the precision of fine mode exceeds that\nof the visual appearance of the widget: fine changes can be too small to see.\nOptions are to use the [Scale widget](./README.md#18-scale-widget) or to have a\nlinked `Label` showing the widget's exact value.\n\nThe callback runs whenever the widget's value changes. This causes the callback\nto run repeatedly while the user adjusts the widget. This is required if there\nis a linked `Label` to update.\n\n###### [Contents](./README.md#0-contents)\n\n# 2. Usage\n\n## 2.1 Program structure and operation\n\nThe following is a minimal script (found in `gui.demos.simple.py`) which will\nrun on a minimal system with a small display and two pushbuttons. Commented out\ncode shows changes for monochrome displays.\n\nThe demo provides two `Button` widgets with \"Yes\" and \"No\" legends. It may be\nrun by issuing at the REPL:\n```python\n\u003e\u003e\u003e import gui.demos.simple\n```\n\nNote that the import of `hardware_setup.py` is the first line of code. This is\nbecause the frame buffer is created here, with a need for a substantial block\nof contiguous RAM.\n```python\nimport hardware_setup  # Instantiate display, setup color LUT (if present)\nfrom gui.core.ugui import Screen, ssd\n\nfrom gui.widgets import Label, Button, CloseButton\n# from gui.core.writer import Writer  # Monochrome display\nfrom gui.core.writer import CWriter\n\n# Font for CWriter\nimport gui.fonts.arial10 as arial10\nfrom gui.core.colors import *\n\n\nclass BaseScreen(Screen):\n\n    def __init__(self):\n\n        def my_callback(button, arg):\n            print('Button pressed', arg)\n\n        super().__init__()\n        # wri = Writer(ssd, arial10, verbose=False)\n        wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)\n\n        col = 2\n        row = 2\n        Label(wri, row, col, 'Simple Demo')\n        row = 20\n        Button(wri, row, col, text='Yes', callback=my_callback, args=('Yes',))\n        col += 60\n        Button(wri, row, col, text='No', callback=my_callback, args=('No',))\n        CloseButton(wri)  # Quit the application\n\ndef test():\n    print('Testing micro-gui...')\n    Screen.change(BaseScreen)\n\ntest()\n```\nNote how the `Next` pushbutton moves the focus between the two buttons and the\n\"X\" close button. The focus does not move to the \"Simple Demo\" widget because\nit is not `active`: a `Label` cannot accept user input. Pushing the `Select`\npushbutton while the focus is on a `Pushbutton` causes the callback to run.\n\nApplications start by performing `Screen.change()` to a user-defined `Screen`\nobject. This must be subclassed from the GUI's `Screen` class. Note that\n`Screen.change` accepts a class name, not a class instance.\n\nThe user defined `BaseScreen` class constructor instantiates all widgets to be\ndisplayed and typically associates them with callback functions - which may be\nbound methods. Screens typically have a `CloseButton` widget. This is a special\n`Pushbutton` subclass which displays as an \"X\" at the top right corner of the\nphysical display and closes the current screen, showing the one below. If used\non the bottom level `Screen` (as above) it closes the application.\n\nThe `CWriter` instance `wri` associates a widget with a font. Constructors for\nall widgets have three mandatory positional args. These are a `CWriter`\ninstance followed by `row` and `col`. These args are followed by a number of\noptional keyword args. These have (hopefully) sensible defaults enabling you to\nget started easily. Monochrome displays use the simpler `Writer` class.\n\n###### [Contents](./README.md#0-contents)\n\n## 2.2 Callbacks\n\nThe interface is event driven. Widgets may have optional callbacks which will\nbe executed when a given event occurs. Events occur when a widget's properties\nare changed programmatically, and also (in the case of `active` widgets) in\nresponse to user input.\n\nA callback function receives positional arguments. The first is a reference to\nthe object raising the callback. Subsequent arguments are user defined, and are\nspecified as a tuple or list of items. Callbacks and their argument lists are\noptional: a default null function and empty tuple are provided. Callbacks may\noptionally be written as bound methods. This facilitates communication between\nwidgets.\n\nWhen writing callbacks take care to ensure that the correct number of arguments\nare passed, bearing in mind the first arg described above. An incorrect\nargument count results in puzzling tracebacks which appear to implicate the GUI\ncode. This is because it is the GUI which actually executes the callbacks.\n\nCallbacks should complete quickly. See\n[Appendix 1 Application design](./README.md#appendix-1-application-design) for\ndiscussion of this.\n\n###### [Contents](./README.md#0-contents)\n\n## 2.3 Colors\n\nThe file `gui/core/colors.py` defines a set of color constants which may be\nused with any display driver. This section describes how to change these or\nto create additional colors. Most of the color display drivers define colors\nas 8-bit or larger values. For the larger displays 4-bit drivers are provided\nwith the aim of conserving RAM.\n\nIn the 4-bit case colors are assigned to a lookup table (LUT) with 16 entries.\nThe frame buffer stores 4-bit color values, which are converted to the correct\ncolor depth for the hardware when the display is refreshed. Of the 16 possible\ncolors 13 are assigned in `gui/core/colors.py`, leaving color numbers 12, 13\nand 14 free.\n\nThe following code is portable between displays and creates a user defined\ncolor `PALE_YELLOW`.\n```python\nfrom gui.core.colors import *  # Imports the create_color function\nPALE_YELLOW = create_color(12, 150, 150, 0)  # index, r, g, b\n```\nIf a 4-bit driver is in use, the color `rgb(150, 150, 0)` will be assigned to\n\"spare\" color number 12. Any color number in range `0 \u003c= n \u003c= 15` may be\nused, implying that predefined colors may be reassigned. It is recommended\nthat `BLACK` (0) and `WHITE` (15) are not changed. If an 8-bit or larger driver\nis in use, the color number is ignored and there is no practical restriction on\nthe number of colors that may be created.\n\nIn the above example, regardless of the display driver, the `PALE_YELLOW`\nvariable may be used to refer to the color. An example of custom color\ndefinition may be found in\n[this nano-gui demo](https://github.com/peterhinch/micropython-nano-gui/blob/4ef0e20da27ef7c0b5c34136dcb372200f0e5e66/gui/demos/color15.py#L92).\n\nThere are five default colors which are defined by a `color_map` list. These\nmay be reassigned in user code. For example the following will cause the border\nof any control with the focus to be red:\n```python\nfrom colors import *\ncolor_map[FOCUS] = RED\n```\nThe `color_map` index constants and default colors (defined in `colors.py`)\nare:\n\n| Index     | Color  | Purpose                                   |\n|:----------|:-------|:------------------------------------------|\n| FOCUS     | WHITE  | Border of control with focus              |\n| PRECISION | YELLOW | Border in precision mode                  |\n| FG        | WHITE  | Window foreground default                 |\n| BG        | BLACK  | Background default including screen clear |\n| GREY_OUT  | GREY   | Color to render greyed-out controls       |\n\n###### [Contents](./README.md#0-contents)\n\n### 2.3.1 Monochrome displays\n\nMost widgets work on monochrome displays if color settings are left at default\nvalues. If a color is specified, drivers in this repo will convert it to black\nor white depending on its level of saturation. A low level will produce the\nbackground color, a high level the foreground.\n\nAt the bit level `1` represents the foreground. This is white on an emitting\ndisplay such as an OLED. On a Sharp display it indicates reflection.\n\nThere is an issue regarding ePaper displays discussed\n[here](https://github.com/peterhinch/micropython-nano-gui/blob/master/README.md#312-monochrome-displays).\nThe driver for the [Waveshare pico_epaper_42](https://www.waveshare.com/pico-epaper-4.2.htm)\nrenders colored objects as black on white.\n\n###### [Contents](./README.md#0-contents)\n\n# 3. The ssd and display objects\n\nThe following code, issued as the first executable lines of an application,\ninitialises the display.\n```python\nimport hardware_setup  # Create a display instance\nfrom gui.core.ugui import Screen, ssd, display  # display symbol is seldom needed\n```\nThe `hardware_setup` file creates singleton instances of `SSD` and `Display`\nclasses. These instances are made available via `ugui`. Normal GUI applications\nonly need to import `ssd`. This refererence to the display driver is used to\ninitialise `Writer` objects. Bound variables `ssd.height` and `ssd.width` may\nbe read to determine the dimensions of the display hardware.\n\nThe `display` object is only needed in applications which use graphics\nprimitives to write directly to the screen. See\n[Appendix 1 Application design](./README.md#appendix-1-application-design).\n\n## 3.1 SSD class\n\nThis is instantiated in `hardware_setup.py`. The specific class must match the\ndisplay hardware in use. Display drivers are documented\n[here](https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md).\n\n## 3.2 Display class\n\nThis is instantiated in `hardware_setup.py`. It registers the `SSD` instance\nalong with the `Pin` instances used for input; also whether an encoder is used.\nPins are arbitrary, but should be defined as inputs with pullups. Pushbuttons\nare connected between `Gnd` and the relevant pin.\n\nThe constructor takes the following positional args:  \n 1. `objssd` The `SSD` instance. A reference to the display driver.\n 2. `nxt` A `Pin` instance for the `next` button.\n 3. `sel` A `Pin` instance for the `select` button.\n 4. `prev=None` A `Pin` instance for the `previous` button (if used).\n 5. `incr=None` A `Pin` instance for the `increase` button (if used).\n 6. `decr=None` A `Pin` instance for the `decrease` button (if used).\n 7. `encoder=False` If an encoder is used, an integer must be passed.\n 8. `touch=False` Supply an integer to use ESP32 `TouchPad` instances in place\n of all physical pushbuttons. See [ESP32 touch pads](./README.md#8-esp32-touch-pads).\n\nClass variables:  \n * `verbose=True` Causes a message to be printed indicating whether an encoder\n was specified.\n\n### 3.2.1 Encoder usage\n\nIf an encoder is used, it should be connected to the pins assigned to\n`increase` and `decrease`. If the direction of movement is wrong, these pins\nshould be transposed (physically or in code).\n\nTo specify to the GUI that an encoder is in use an integer should be passed to\nthe `Display` constructor `encoder` arg. Its value represents the division\nratio. A value of 1 defines the native rate of the encoder; if the native rate\nis 32 pulses per revolution, a value of 4 would yield a virtual device with\n8 pulses per rev. A value of 4 matches most encoders with mechanical detents.\n\nIf an encoder is used but the `encoder` arg is `False`, response to the encoder\nwill be erratic.\n\n### 3.2.2 Encoder only mode\n\nThis uses an encoder with an included pushbutton as the sole means of control.\nTo use this mode, constructor args should be:\n 1. `objssd` The `SSD` instance. A reference to the display driver.\n 2. `nxt` A `Pin` instance attached to the encoder X pin.\n 3. `sel` A `Pin` instance attached to the encoder button.\n 4. `prev` A `Pin` instance attached to the encoder Y pin.\n 5. `incr=False`. Must set `False`.\n 6. `decr=None`.\n 7. `encoder` An `int` defining the division ratio as above.\n\n###### [Contents](./README.md#0-contents)\n\n# 4. Screen class\n\nThe `Screen` class presents a full-screen canvas onto which displayable\nobjects are rendered. Before instantiating widgets a `Screen` instance must be\ncreated. This will be current until another is instantiated. When a widget is\ninstantiated it is associated with the current screen.\n\nAll applications require the creation of at least one user screen. This is done\nby subclassing the `Screen` class. Widgets are instantiated in the `Screen`\nconstructor. Widgets may be assigned to bound variable: this facilitates\ncommunication between them.\n\n###### [Contents](./README.md#0-contents)\n\n## 4.1 Class methods\n\nIn normal use only `change` and `back` are required, to move to a new `Screen`\nand to drop back to the previous `Screen` in a tree (or to quit the application\nif there is no predecessor).\n\n * `change(cls, cls_new_screen, mode=Screen.STACK, *, args=[], kwargs={})`  \n Change screen, refreshing the display. Mandatory positional argument: the new\n screen class name. This must be a class subclassed from `Screen`. The class\n will be instantiated and displayed. Optional keyword arguments `args`, `kwargs`\n enable  passing positional and keyword arguments to the constructor of the new,\n user defined, screen. By default the new screen overlays the old. When the new\n `Screen` is closed (via `back`) the old is re-displayed having retained state.\n If `mode=Screen.REPLACE` is passed the old screen instance is deleted. The new\n one retains the parent of the old, so if it is closed that parent is\n re-displayed with its state retained. This enables arbitrary navigation between\n screens (directed graph rather than tree structure). See demo `screen_replace`.\n * `back(cls)` Restore previous screen. If there is no parent, quits the\n application.\n\nThese are uncommon:  \n * `shutdown(cls)` Clear the screen and shut down the GUI. Normally done by a\n `CloseButton` instance.\n * `show(cls, force)`. This causes the screen to be redrawn. If `force` is\n `False` unchanged widgets are not refreshed. If `True`, all visible widgets\n are re-drawn. Explicit calls to this should never be needed.\n\nSee `demos/plot.py` for an example of multi-screen design, or\n`screen_change.py` for a minimal example demostrating the coding technique.\n\n###### [Contents](./README.md#0-contents)\n\n## 4.2 Constructor\n\nThis takes no arguments.\n\n## 4.3 Callback methods\n\nThese are null functions which may be redefined in user subclasses.\n\n * `on_open(self)` Called when a screen is instantiated but prior to display.\n * `after_open(self)` Called after a screen has been displayed.\n * `on_hide(self)` Called when a screen ceases to be current.\n\nSee `demos/plot.py` for examples of usage of `after_open`.\n\n## 4.4 Method\n\n * `reg_task(self, task, on_change=False)` The first arg may be a `Task`\n instance or a coroutine. Returns the passed `task` object.\n\nThis is a convenience method which provides for the automatic cancellation of\ntasks. If a screen runs independent tasks it can opt to register these. If the\nscreen is overlaid by another, tasks registered with `on_change` `True` are\ncancelled. If the screen is closed, all tasks registered to it are cancelled\nregardless of the state of `on_change`. On shudown, any tasks registered to the\nbase screen are cancelled.\n\nFor finer control, applications can ignore this method and handle cancellation\nexplicitly in code.\n\n## 4.5 Class variable\n\n * `do_gc = True` By default a coroutine is launched to periodically perform\n garbage collection (GC). On most platforms this reduces latency by doing GC\n before too much garbage has accumulated. However on platforms with SPIRAM GC\n can take hundreds of ms, causing unacceptable latency. If `do_gc` is `False`\n the application can perform GC at times when fast response to user actions is\n not required. If turned off, the GC task cannot be re-started.\n\n## 4.6 Usage\n\nThe `Screen.change()` classmethod returns immediately. This has implications\nwhere the new, top screen sets up data for use by the underlying screen. One\napproach is for the top screen to populate class variables. These can be\nacccessed by the bottom screen's `after_open` method which will run after the\ntop screen has terminated.\n\nIf a `Screen` throws an exception when instantiated, check that its constructor\ncalls `super().__init__()`.\n\n###### [Contents](./README.md#0-contents)\n\n# 5. Window class\n\nThis is a `Screen` subclass providing for modal windows. As such it has\npositional and dimension information. Usage consists of writing a user class\nsubclassed from `Window`. Example code is in `demos/screens.py`. Code in a\nwindow must not attempt to open another `Window` or `Screen`. Doing so will\nraise a `ValueError`. Modal behaviour means that the only valid screen change\nis a return to the calling screen.\n\n## 5.1 Constructor\n\nThis takes the following positional args:  \n * `row`\n * `col`\n * `height`\n * `width`\n\nFollowed by keyword-only args\n * `draw_border=True`\n * `bgcolor=None` Background color, default black.\n * `fgcolor=None` Foreground color, default white.\n * `writer=None` See Popups below.\n\n## 5.2 Class method\n\n * `value(cls, val=None)` The `val` arg can be any Python type. It allows\n widgets on a `Window` to store information in a way which can be accessed from\n the calling screen. This typically occurs after the window has closed and no\n longer exists as an instance.\n\nAnother approach, demonstrated in `demos/screens.py`, is to pass one or more\ncallbacks to the user window constructor args. These may be called by widgets\nto send data to the calling screen. Note that widgets on the screen below will\nnot be updated until the window has closed.\n\n## 5.3 Popup windows\n\nIn general `Screen` and `Window` instances need at least one `active` widget.\nThere is a special case of a popup window which typically displays status data,\npossibly with a progress meter. A popup has no user controls and is closed by\nuser code. A popup is created by passing a `Writer` (or `CWriter`) to the\nconstructor and is closed by issuing the `close()` static method.\n\n###### [Contents](./README.md#0-contents)\n\n# 6. Widgets\n\n## 6.1 Label widget\n\n```python\nfrom gui.widgets import Label  # File: label.py\n```\n![Image](./images/label.JPG)\n\nVarious styles of `Label`.\n\nThe purpose of a `Label` instance is to display text at a specific screen\nlocation.\n\nText can be static or dynamic. In the case of dynamic text the background is\ncleared to ensure that short strings cleanly replace longer ones.\n\nLabels can be displayed with an optional single pixel border.\n\nColors are handled flexibly. By default the colors used are those of the\n`Writer` instance, however they can be changed dynamically; this might be used\nto warn of overrange or underrange values. The `color15.py` demo illustrates\nthis.\n\nConstructor args:  \n 1. `writer` The `Writer` instance (font and screen) to use.\n 2. `row` Location on screen.\n 3. `col`\n 4. `text` If a string is passed it is displayed: typically used for static\n text. If an integer is passed it is interpreted as the maximum text length\n in pixels; typically obtained from `writer.stringlen('-99.99')`. Nothing is\n dsplayed until `.value()` is called. Intended for dynamic text fields.\n 5. `invert=False` Display in inverted or normal style.\n 6. `fgcolor=None` Color of foreground (the control itself). If `None` the\n `Writer` foreground default is used.\n 7. `bgcolor=BLACK` Background color of object. If `None` the `Writer`\n background default is used.\n 8. `bdcolor=False` Color of border. If `False` no border will be drawn. If\n `None` the `fgcolor` will be used, otherwise a color may be passed. If a color\n is available, a border line will be drawn around the control.\n 9. `justify=Label.LEFT` Options are `Label.RIGHT` and `Label.CENTRE` (note\n British spelling). Justification can only occur if there is sufficient space\n in the `Label` i.e. where an integer is supplied for the `text` arg.\n\nThe constructor displays the string at the required location.\n\nMethod:  \n`value` Redraws the label. This takes the following args:\n * `text=None` The text to display. If `None` displays last value.\n * `invert=False` If true, show inverse text.\n * `fgcolor=None` Foreground color: if `None` the `Writer` default is used.\n * `bgcolor=None` Background color, as per foreground.\n * `bdcolor=None` Border color. As per above except that if `False` is\n passed, no border is displayed. This clears a previously drawn border.  \nReturns the current text string.\n * `justify=None` By default justify using the constructor default. Override\n with `Label.LEFT`, `Label.RIGHT` or `Label.CENTRE`.\n\nIf the `value` method is called with a text string too long for the `Label` the\ntext will be clipped to fit the width. In this case `value()` will return the\ntruncated text.\n\nIf constructing a label would cause it to extend beyond the screen boundary a\nwarning is printed at the console. The label may appear at an unexpected place.\nThe following is a complete \"Hello world\" script.\n```python\nfrom hardware_setup import ssd  # Create a display instance\nfrom gui.core.ugui import Screen\nfrom gui.core.writer import CWriter\nfrom gui.core.colors import *\n\nfrom gui.widgets import Label, CloseButton\nimport gui.fonts.freesans20 as freesans20\n\n\nclass BaseScreen(Screen):\n\n    def __init__(self):\n        super().__init__()\n        wri = CWriter(ssd, freesans20, GREEN, BLACK, verbose=False)\n        Label(wri, 2, 2, 'Hello world!')\n        CloseButton(wri)\n\nScreen.change(BaseScreen)\n```\n\n###### [Contents](./README.md#0-contents)\n\n### 6.1.1 Grid widget\n\n```python\nfrom gui.widgets import Grid  # Files: grid.py, parse2d.py\n```\n![Image](./images/grid.JPG)\n\nThis is a rectangular array of `Label` instances: as such it is a passive\nwidget. Rows are of a fixed height equal to the font height + 4 (i.e. the label\nheight). Column widths are specified in pixels with the column width being the\nspecified width +4 to allow for borders. The dimensions of the widget including\nborders are thus:  \nheight = no. of rows * (font height + 4)  \nwidth = sum(column width + 4)  \nCells may be addressed as a 1 or 2-dimensional array.\n\nConstructor args:  \n 1. `writer` The `Writer` instance (font and screen) to use.\n 2. `row` Location of grid on screen.\n 3. `col`\n 4. `lwidth` If an integer N is passed all labels will have width of N pixels.\n A list or tuple of integers will define the widths of successive columns. If\n the list has fewer entries than there are columns, the last entry will define\n the width of those columns. Thus `[20, 30]` will produce a grid with column 0\n being 20 pixels and all subsequent columns being 30.\n 5. `nrows` Number of rows.\n 6. `ncols` Number of columns.\n 7. `invert=False` Display in inverted or normal style.\n 8. `fgcolor=None` Color of foreground (the control itself). If `None` the\n `Writer` foreground default is used.\n 9. `bgcolor=BLACK` Background color of cells. If `None` the `Writer`\n background default is used.\n 10. `bdcolor=None` Color of border of the widget and its internal grid. If\n `False` no border or grid will be drawn. If `None` the `fgcolor` will be used,\n otherwise a color may be passed.\n 11. `justify=Label.LEFT` Options are `Label.RIGHT` and `Label.CENTRE` (note\n British spelling). Justification can only occur if there is sufficient space\n in the `Label` as defined by `lwidth`.\n\nMethod:  \n * `__getitem__` Returns an iterator enabling `Label` instances to be accessed.\n * `__setitem__` Assign a value to one or more labels. If multiple labels are\n specified and a single text value is passed, all labels will receive that\n value. If an iterator is passed, consecutive labels will receive values from\n the iterator. If the iterator runs out of data, the last value will be\n repeated.\n\nAddressing:  \nThe `Label` instances may be addressed as a 1D array as follows\n```python\ngrid[20] = str(42)\ngrid[20:25] = iter([str(n) for n in range(20, 25)])\n```\nor as a 2D array:\n```python\ngrid[2, 5] = \"A\"  # Row == 2, col == 5\ngrid[0:7, 3] = \"b\"  # Populate col 3 of rows 0..6\ngrid[1:3, 1:3] = (str(n) for n in range(25))  # Produces\n# 0 1\n# 2 3\n```\nColumns are populated from left to right, rows from top to bottom. Unused\niterator values are ignored. If an iterator runs out of data the last value is\nrepeated, thus\n```python\ngrid[1:3, 1:3] = (str(n) for n in range(2))  # Produces\n# 0 1\n# 1 1\n```\nRead access:\n```python\nfor label in grid[2, 0:]:\n    v = label.value()  # Access text of each label in row 2\n```\nExample uses:\n```python\ncolwidth = (20, 30)  # Col 0 width is 20, subsequent columns 30\nself.grid = Grid(wri, row, col, colwidth, rows, cols, justify=Label.CENTRE)\nself.grid[20] = \"\"  # Clear cell 20 by setting its value to \"\"\nself.grid[2, 5] = str(42)  # 2D array syntax\ngrid[1:6, 0] = iter(\"ABCDE\")  # Label row and col headings\ngrid[0, 1:cols] = (str(x + 1) for x in range(cols))\nd = {}  # For indiviual control of cell appearance\nd[\"fgcolor\"] = RED\nd[\"text\"] = str(99)\nself.grid[3, 7] = d  # Specify color as well as text\ndel d[\"fgcolor\"]  # Revert to default\nd[\"invert\"] = True\nself.grid[17] = d\n```\nSee the example [calendar.py](https://github.com/peterhinch/micropython-micro-gui/blob/main/gui/demos/calendar.py).\n\n###### [Contents](./README.md#0-contents)\n\n## 6.2 LED widget\n\n```python\nfrom gui.widgets import LED  # File: led.py\n```\n![Image](./images/led.JPG)\n\nThis is a virtual LED whose color may be altered dynamically. An `LED` may be\ndefined with a color and turned on or off by setting `.value` to a boolean. For\nmore flexibility the `.color` method may be use to set it to any color.\n\nConstructor mandatory positional args:  \n 1. `writer` The `Writer` instance (defines font) to use.\n 2. `row` Location on screen.\n 3. `col`  \n\nKeyword only args:\n\n * `height=30` Height of LED.\n * `fgcolor=None` Color of foreground (the control itself). If `None` the\n `Writer` foreground default is used.\n * `bgcolor=None` Background color of object. If `None` the `Writer` background\n default is used.\n * `bdcolor=False` Color of border. If `False` no border will be drawn. If a\n color is provided, a border line will be drawn around the control.\n shown in the foreground color. If a color is passed, it is used.\n * `color=RED` Color when illuminated (i.e. if `value` is `True`.\n\nMethods:\n 1. `value` arg `val=None` If `True` is passed, lights the `LED` in its current\n color. `False` extinguishes it. `None` has no effect. Returns current value.\n 2. `color` arg `c=None` Change the LED color to `c`. If `c` is `None` the LED\n is turned off (rendered in the background color).\n\nNote that `__call__` is a synonym for `value`. An `LED` instance can be\ncontrolled with `led(True)` or `led(False)`.\n\n###### [Contents](./README.md#0-contents)\n\n## 6.3 Checkbox widget\n\n```python\nfrom gui.widgets import Checkbox  # File: checkbox.py\n```\n![Image](./images/checkbox.JPG)  \nThis provides for Boolean data entry and display. In the `True` state the\ncontrol can show an 'X' or a filled block of any color depending on the\n`fillcolor` constructor arg.\n\nConstructor mandatory positional args:  \n 1. `writer` The `Writer` instance (defines font) to use.\n 2. `row` Location on screen.\n 3. `col`  \n\nOptional keyword only arguments:\n * `height=30` Dimension of the square bounding box. Default 30 pixels.\n * `fillcolor=None` Fill color of checkbox when `True`. If `None` an 'X' will\n be drawn.\n * `fgcolor=None` Color of foreground (the control itself). If `None` the\n `Writer` foreground default is used.\n * `bgcolor=None` Background color of object. If `None` the `Writer` background\n default is used.\n * `bdcolor=False` Color of border. If `False` no border will be drawn. If a\n color is provided, a border line will be drawn around the control.\n * `callback=dolittle` Callback function which will run when the value changes.\n The default is a null function.\n * `args=[]` A list/tuple of arguments for above callback.\n * `value=False` Initial value.\n * `active=True` By default user input is accepted.\n\nMethods:\n * `greyed_out` Optional Boolean argument `val=None`. If `None` returns the\n current 'greyed out' status of the control. Otherwise enables or disables it,\n showing it in its new state.\n * `value` Optional Boolean argument `val`. If the provided value does not\n correspond to the control's current value, updates it; the checkbox is\n re-drawn and the callback executed. Always returns the control's value.\n\n###### [Contents](./README.md#0-contents)\n\n## 6.4 Button and CloseButton widgets\n\n```python\nfrom gui.core.colors import *  # Colors and shapes\nfrom gui.widgets import Button  # File: buttons.py\n```\n![Image](./images/pushbuttons.JPG)\n\nUsing an\n[icon font](https://github.com/peterhinch/micropython-font-to-py/blob/master/icon_fonts/README.md):  \n\n![Image](./images/iconbuttons.jpg)\n\nIn these images `Button` \"a\"  and the \"Forward\" button have the focus. Pressing\nthe physical `select` button will press the virtual `Button`.\n\nThis emulates a pushbutton, with a callback being executed each time the button\nis pressed. Physically this consists of pressing the `select` button when the\n`Button` instance has focus. Buttons may be any one of three shapes: `CIRCLE`,\n`RECTANGLE` or `CLIPPED_RECT`.\n\nConstructor mandatory positional args:  \n 1. `writer` The `Writer` instance (defines font) to use.\n 2. `row` Location on screen.\n 3. `col`  \n\nOptional keyword only arguments:\n * `shape=RECTANGLE` Must be `CIRCLE`, `RECTANGLE` or `CLIPPED_RECT`.\n * `height=20` Height of button or diameter in `CIRCLE` case.\n * `width=50` Width of button. If `text` is supplied and `width` is too low to\n accommodate the text, it will be increased to enable the text to fit. In\n `CIRCLE` case any passed value is ignored.\n * `fgcolor=None` Color of foreground (the control itself). If `None` the\n `Writer` foreground default is used.\n * `bgcolor=None` Background color of object. If `None` the `Writer` background\n default is used.\n * `bdcolor=False` Color of border. If `False` no border will be drawn. If a\n color is provided, a border line will be drawn around the control.\n * `textcolor=None` Text color. Defaults to `fgcolor`.\n * `litcolor=None` If provided the button will display this color for one\n second after being pressed.\n * `text=''` Shown in centre of button. It is possible to show simple\n [icons](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/WRITER.md#3-icons),\n for example media playback symbols.\n * `callback=dolittle` Callback function which runs when button is pressed.\n * `args=()` A list/tuple of arguments for the above callback.\n\nMethod:\n * `greyed_out` Optional Boolean argument `val=None`. If `None` returns the\n current 'greyed out' status of the control. Otherwise enables or disables it,\n showing it in its new state.\n\nClass variable:\n * `lit_time=1000` Period in ms the `litcolor` is displayed.\n\n### CloseButton\n![Image](./images/closebutton.JPG)  \nThis example has focus, as shown by white border.\n\nThis `Button` subclass is a special case of a Button. Its constructor takes a\nsingle arg, being a `Writer` instance. It produces a red \"X\" button at the top\nright hand corner of the current `Screen`. Operating it causes the screen to\nclose, with the screen below being revealed. On the bottom level screen, a\n`CloseButton` will shut down the application.\n\nConstructor mandatory positional arg:  \n * writer\n\nOptional keyword only arguments:  \n * `width=0` By default dimensions are calculated from font size. The button is\n is square. Optionally `width` may be specified.\n * `callback=dolittle` Optional callback, not normally required.\n * `args=()` Args for above.\n * `bgcolor=RED`\n\n###### [Contents](./README.md#0-contents)\n\n## 6.5 ButtonList object\n\n```python\nfrom gui.core.colors import *  # Colors and shapes\nfrom gui.widgets import Button, ButtonList  # File: buttons.py\n```\n\nA `ButtonList` groups a number of buttons together to implement a button which\nchanges state each time it is pressed. For example it might toggle between a\ngreen Start button and a red Stop button. The buttons are defined and added in\nturn to the `ButtonList` object. Typically they will be the same size, shape\nand location but will differ in color and/or text. At any time just one of the\nbuttons will be visible, initially the first to be added to the object.\n\nButtons in a `ButtonList` should not have callbacks. The `ButtonList` has\nits own user supplied callback which runs each time the object is pressed.\nHowever each button can have its own list of `args`. Callback arguments\ncomprise the currently visible button followed by its arguments.\n\nConstructor argument:\n * `callback=dolittle` The callback function. Default does nothing.\n * `new_cb=False` When a button is pressed, determines whether the callback run\n is that of the button visible when pressed, or that which becomes visible after\n the press.\n\nMethods:\n * `add_button` Adds a button to the `ButtonList`. Arguments: as per the\n `Button` constructor.\n Returns the button object.\n * `greyed_out` Optional Boolean argument `val=None`. If `None` returns the\n current 'greyed out' status of the control. Otherwise enables or disables it,\n showing it in its new state.\n * `value` Optional args `button=None`, `new_cb=False`. The `button` arg, if\n provided, should be a button in the set. If supplied and the button is not\n active the currency changes to the supplied button, which is displayed. By\n default the callback of the previous button is run, otherwise the callback of\n the newly displayed button.\n\nAlways returns the active button.\n\nCounter intuitively, running the callback of the previous button is normal\nbehaviour. Consider a `ButtonList` consisting of ON and OFF buttons. If ON is\nvisible this implies that the machine under control is off. Pressing `select`\ncauses the ON callback to run, starting the machine. The new button displayed\nnow reads OFF. There are situations in which the opposite behaviour is required\nsuch as when choosing an option from a list: in this case the callback from the\nnewly visible button might be expected to run.\n\nTypical usage is as follows:\n```python\ndef callback(button, arg):\n    print(arg)\n\ntable = [\n     {'fgcolor' : GREEN, 'shape' : CLIPPED_RECT, 'text' : 'Start', 'args' : ['Live']},\n     {'fgcolor' : RED, 'shape' : CLIPPED_RECT, 'text' : 'Stop', 'args' : ['Die']},\n]\nbl = ButtonList(callback)\nfor t in table:  # Buttons overlay each other at same location\n    bl.add_button(wri, 10, 10, textcolor = BLACK, **t)\n```\n\n###### [Contents](./README.md#0-contents)\n\n## 6.6 RadioButtons object\n\n```python\nfrom gui.core.colors import *  # Colors and shapes\nfrom gui.widgets import Button, RadioButtons  # File: buttons.py\n```\n![Image](./images/radiobuttons.JPG)\n\nThis object groups a set of buttons at different locations. When a button is\npressed, it becomes highlighted and remains so until another button in the set\nis pressed. A callback runs each time the current button is changed.\n\nConstructor positional arguments:\n * `highlight` Color to use for the highlighted button. Mandatory.\n * `callback` Callback when a new button is pressed. Default does nothing.\n * `selected` Index of initial button to be highlighted. Default 0.\n\nMethods:\n * `add_button` Adds a button. Arguments: as per the `Button` constructor.\n Returns the Button instance.\n * `greyed_out` Optional Boolean argument `val=None`. If `None` returns the\n current 'greyed out' status of the control. Otherwise enables or disables it,\n showing it in its new state.\n * `value` Optional argument: a button in the set. If supplied, and the\n button is not currently active, the supplied button receives the focus and its\n callback is run. Always returns the currently active button.\n\nTypical usage:\n```python\ndef callback(button, arg):\n    print(arg)\n\ntable = [\n    {'text' : '1', 'args' : ['1']},\n    {'text' : '2', 'args' : ['2']},\n    {'text' : '3', 'args' : ['3']},\n    {'text' : '4', 'args' : ['4']},\n]\ncol = 0\nrb = RadioButtons(BLUE, callback) # color of selected button\nfor t in table:\n    rb.add_button(wri, 10, col, textcolor = WHITE,\n                  fgcolor = LIGHTBLUE, height = 40, **t)\n    col += 60 # Horizontal row of buttons\n```\n\n###### [Contents](./README.md#0-contents)\n\n## 6.7 Listbox widget\n\n```python\nfrom gui.widgets import Listbox  # File: listbox.py\n```\n![Image](./images/listbox.JPG)\n\nA `listbox` with the second item highlighted. Pressing the physical `select`\nbutton will cause the callback to run.\n\nA `Listbox` is an active widget. By default its height is determined by the\nnumber of entries in it and the font in use. It may be reduced by specifying\n`dlines` in which case scrolling will occur. When the widget has focus the\ncurrently selected element may be changed using `increase` and `decrease`\nbuttons or by turning the encoder. On pressing `select` a callback runs.\n\nConstructor mandatory positional args:  \n 1. `writer` The `Writer` instance (defines font) to use.\n 2. `row` Location on screen.\n 3. `col`  \n\nMandatory keyword only argument:\n * `elements` A list or tuple of strings to display. Must have at least one\n entry. An alternative format is described below which enables each item in the\n list to have a separate callback.\n\nOptional keyword only arguments:\n * `dlines=None` By default the height of the control is determined by the\n number of elements. If an integer \u003c number of elements is passed the list\n will show that number of lines; its height will correspond. Scrolling will\n occur to ensure that the current element is always visible. To indicate when\n scrolling is possible, one or two vertical bars will appear to the right of\n the list.\n * `width=None` Control width in pixels. By default this is calculated to\n accommodate all elements. If a `width` is specified, and some elements are too\n long to fit, they will be clipped. This is a visual effect only and does not\n affect the value of that element.\n * `value=0` Index of currently selected list item. If necessary the list will\n scroll to ensure the item is visible.\n * `fgcolor=None` Color of foreground (the control itself). If `None` the\n `Writer` foreground default is used.\n * `bgcolor=None` Background color of object. If `None` the `Writer` background\n default is used.\n * `bdcolor=False` Color of border. If `False` no border will be drawn. If a\n color is provided, a border line will be drawn around the control.\n * `fontcolor=None` Text color. Defaults to system text color.\n * `select_color=DARKBLUE` Background color for selected item in list.\n * `callback=dolittle` Callback function which runs when `select` is pressed.\n * `args=[]` A list/tuple of arguments for above callback.\n * `also=0` Options are `Listbox.ON_MOVE` or `Listbox.ON_LEAVE`. By default the\n callback runs only when the `select` button is pressed. The `ON_LEAVE` value\n causes it also to run when the focus moves from the control if the currently\n selected element has changed. The `ON_MOVE` arg causes the callback to run\n every time the highlighted element is changed.\n\nMethods:\n * `greyed_out` Optional Boolean argument `val=None`. If `None` returns the\n current 'greyed out' status of the control. Otherwise enables or disables it,\n showing it in its new state.\n * `value` Argument `val=None`. If a provided argument is a valid index for the\n list, that entry becomes current and the callback is executed. Always returns\n the index of the currently active entry.\n * `textvalue` Argument `text=None`. If a string argument is provided and is in\n the control's list, that item becomes current. Normally returns the current\n string. If a provided arg did not match any list item, the control's state is\n not changed and `None` is returned.\n\nThe callback's first argument is the listbox instance followed by any args\nspecified to the constructor. The currently selected item may be retrieved by\nmeans of the instance's `value` or `textvalue` methods.\n\n#### Alternative approach\n\nBy default the `Listbox` runs a common callback regardless of the item chosen.\nThis can be changed by specifying `elements` such that each element comprises a\n3-list or 3-tuple with the following contents:  \n 1. String to display.\n 2. Callback.\n 3. Tuple of args (may be `()`).\n\nIn this case constructor args `callback` and `args` must not be supplied. Args\nreceived by the callback functions comprise the `Listbox` instance followed by\nany supplied args. The following is a complete example (minus initial `import`\nstatements).\n\n```python\nclass BaseScreen(Screen):\n    def __init__(self):\n        def cb(lb, s):\n            print('Callback', s)\n\n        def cb_radon(lb, s):\n            print('Radioactive', s)\n\n        super().__init__()\n        wri = CWriter(ssd, freesans20, GREEN, BLACK, verbose=False)\n        els = (('Hydrogen', cb, ('H2',)),\n               ('Helium', cb, ('He',)),\n               ('Neon', cb, ('Ne',)),\n               ('Xenon', cb, ('Xe',)),\n               ('Radon', cb_radon, ('Ra',)))\n        Listbox(wri, 2, 2, elements = els, bdcolor=RED)\n        CloseButton(wri)\n\nScreen.change(BaseScreen)\n```\n\n###### [Contents](./README.md#0-contents)\n\n## 6.8 Dropdown widget\n\n```python\nfrom gui.widgets import Dropdown  # File: dropdown.py\n```\n\n![Image](./images/dd_closed.JPG)\n\nClosed dropdown list.\n\n![Image](./images/dd_open.JPG)\n\nOpen dropdown list. When closed, hidden items below are refreshed.\n\nA dropdown list. The list, when active, is drawn over the control. The height\nof the control is determined by the height of the font in use. By default the\nheight of the list is determined by the number of entries in it and the font in\nuse. It may be reduced by specifying `dlines` in which case scrolling will\noccur. The dropdown should be placed high enough on the screen to ensure that\nthe list can be displayed.\n\nConstructor mandatory positional args:  \n 1. `writer` The `Writer` instance (defines font) to use.\n 2. `row` Location on screen.\n 3. `col`  \n\nMandatory keyword only argument:\n * `elements` A list or tuple of strings to display. Must have at least one\n entry. See below for an alternative way to use the `Dropdown` which enables\n each item on the dropdown list to have a separate callback.\n\nOptional keyword only arguments:\n * `dlines=None` By default the height of the dropdown list is determined by\n the number of elements. If an integer \u003c number of elements is passed the list\n will show that number of lines; its height will correspond. Scrolling will\n occur to ensure that the current element is always visible. To indicate when\n scrolling is possible, one or two vertical bars will appear to the right of\n the list.\n * `width=None` Control width in pixels. By default this is calculated to\n accommodate all elements.\n * `value=0` Index of currently selected list item.\n * `fgcolor=None` Color of foreground (the control itself). If `None` the\n `Writer` foreground default is used.\n * `bgcolor=None` Background color of object. If `None` the `Writer` background\n default is used.\n * `bdcolor=False` Color of border. If `False` no border will be drawn. If a\n color is provided, a border line will be drawn around the control.\n * `fontcolor=None` Text color. Defaults to foreground color.\n * `select_color=DARKBLUE` Background color for selected item in list.\n * `callback=dolittle` Callback function which runs when a list entry is picked.\n * `args=[]` A list/tuple of arguments for above callback.\n\nMethods:\n * `greyed_out` Optional Boolean argument `val=None`. If `None` returns the\n current 'greyed out' status of the control. Otherwise enables or disables it,\n showing it in its new state.\n * `value` Argument `val=None`. If a provided arg is a valid index into the\n list, that entry becomes current and the callback is executed. Always returns\n the index of the currently active entry.\n * `textvalue` Argument `text=None`. If a string argument is provided and is in\n the control's list, that item becomes current. Normally returns the current\n string. If a provided arg did not match any list item, the control's state is\n not changed and `None` is returned.\n\nIf `select` is pressed when the `Dropdown` has focus, the list is displayed.\nThe `increase` and `decrease` buttons move the list currency. If `select` is\npressed after changing the currency the callback is triggered, the list is\nclosed and the control will display the newly selected entry. If `next` or\n`prev` are pressed while the list is open, focus will move to the next widget.\nIn this event the list will close and no selection change will be recognised:\nthe control will show the element which was visible at the start and the\ncallback will not run. Moving the focus is a means of cancelling any changes.\n\nThe callback's first argument is the dropdown instance followed by any args\nspecified to the constructor. The currently selected item may be retrieved by\nmeans of the instance's `value` or `textvalue` methods.\n\n#### Alternative approach\n\nBy default the `Dropdown` runs a single callback regardless of the element\nchosen. This can be changed by specifying `elements` such that each element\ncomprises a 3-list or 3-tuple with the following contents:  \n 1. String to display.\n 2. Callback.\n 3. Tuple of args (may be `()`).\n\nIn this case constructor args `callback` and `args` must not be supplied. Args\nreceived by the callback functions comprise the `Dropdown` instance followed by\nany supplied args. The following is a complete example (minus initial import\nstatements):\n```python\nclass BaseScreen(Screen):\n    def __init__(self):\n        def cb(dd, arg):\n            print('Gas', arg)\n\n        def cb_radon(dd, arg):\n            print('Radioactive', arg)\n\n        super().__init__()\n        wri = CWriter(ssd, freesans20, GREEN, BLACK, verbose=False)\n        els = (('hydrogen', cb, ('H2',)),\n               ('helium', cb, ('He',)),\n               ('neon', cb, ('Ne',)),\n               ('xenon', cb, ('Xe',)),\n               ('radon', cb_radon, ('Ra',)))\n        Dropdown(wri, 2, 2, elements = els,\n                bdcolor = RED, fgcolor=RED, fontcolor = YELLOW)\n        CloseButton(wri)\n\n\nScreen.change(BaseScreen)\n```\n###### [Contents](./README.md#0-contents)\n\n## 6.9 DialogBox class\n\n```python\nfrom gui.widgets import DialogBox  # File: dialog.py\n```\n![Image](./images/dialog.JPG)\n\nAn active dialog box. Auto generated dialogs contain only `pushbutton`\ninstances, but user created dialogs may contain any widget.\n\nThis implements a modal dialog box based on a horizontal row of pushbuttons.\nAny button press will close the dialog. The caller can determine which button\nwas pressed. The size of the buttons and the width of the dialog box are\ncalculated from the strings assigned to the buttons. This ensures that buttons\nare evenly spaced and identically sized. Typically used for simple queries such\nas \"yes/no/cancel\".\n\nConstructor positional args:  \n 1. `writer` The `Writer` instance (defines font) to use.\n 2. `row=20` Location on screen.\n 3. `col=20`  \n\nMandatory keyword only arg:  \n * `elements` A list or tuple of 2-tuples. Each defines the text and color of\n a pushbutton, e.g. `(('Yes', RED), ('No', GREEN))`.\n\nOptional keyword only args:  \n * `label=None` Text for an optional label displayed in the centre of the\n dialog box.\n * `bgcolor=DARKGREEN` Background color of window.\n * `buttonwidth=25` Minimum width of buttons. In general button dimensions are\n calculated from the size of the strings in `elements`.\n * `closebutton=True` If set, a `close` button will be displayed at the top RH\n corner of the dialog box.\n * `callback=dolittle`\n * `args=[]`\n\nClassmethod (inherited from `Screen`):  \n * `value(cls, val=None)` The `val` arg can be any Python type.\n\nThe `DialogBox` is a `Screen` subclass. Pressing any button closes the dialog\nand sets the `Screen` value to the text of the button pressed or \"Close\" in the\ncase of the `close` button. The outcome can therefore be tested by running\n`Screen.value()` or by implementing the callback. The latter receives the\n`DialogBox` instance as a first arg, followed by any args supplied to the\nconstructor.\n\nNote that dialog boxes can also be constructed manually, enabling more flexible\ndesigns. For example these might have widgets other than pushbuttons. The\napproach is to write a user subclass of `Window`. Example code may be found\nin `gui/demos/screens.py`.\n\n###### [Contents](./README.md#0-contents)\n\n## 6.10 Textbox widget\n\n```python\nfrom gui.widgets import Textbox  # File: textbox.py\n```\n![Image](./images/textbox.JPG)\n\nDisplays multiple lines of text in a field of fixed dimensions. Text may be\nclipped to the width of the control or may be word-wrapped. If the number of\nlines of text exceeds the height available, scrolling will occur. Access to\ntext that has scrolled out of view may be achieved by calling a method. If the\nwidget is instantiated as `active` scrolling may be performed using the\n`increase` and `decrease` buttons. The widget supports fixed and variable pitch\nfonts.\n\nConstructor mandatory positional arguments:\n 1. `writer` The `Writer` instance (font and screen) to use.\n 2. `row` Location on screen.\n 3. `col`\n 4. `width` Width of the object in pixels.\n 5. `nlines` Number of lines of text to display. The object's height is\n determined from the height of the font:  \n `height in pixels = nlines*font_height`  \n As per all widgets the border is drawn two pixels beyond the control's\n boundary.\n\nKeyword only arguments:\n * `fgcolor=None` Color of foreground (the control itself). If `None` the\n `Writer` foreground default is used.\n * `bgcolor=None` Background color of object. If `None` the `Writer` background\n default is used.\n * `bdcolor=False` Color of border. If `False` no border will be drawn. If a\n color is provided, a border line will be drawn around the control.\n * `clip=True` By default lines too long to display are right clipped. If\n `False` is passed, word-wrap is attempted. If the line contains no spaces\n it will be wrapped at the right edge of the window.\n * `active=False` If `True` scrolling may be performed via the `increase` and\n `decrease` buttons.\n\nMethods:\n * `append` Args `s, ntrim=None, line=None` Append the string `s` to the\n display and scroll up as required to show it. By default only the number of\n lines which will fit on screen are retained. If an integer `ntrim=N` is\n passed, only the last N lines are retained; `ntrim` may be greater than can be\n shown in the control, hidden lines being accessed by scrolling.  \n If an integer (typically 0) is passed in `line` the display will scroll to\n show that line.\n * `scroll` Arg `n` Number of lines to scroll. A negative number scrolls up. If\n scrolling would achieve nothing because there are no extra lines to display,\n nothing will happen. Returns `True` if scrolling occurred, otherwise `False`.\n * `value` No args. Returns the number of lines of text stored in the widget.\n * `clear` No args. Clears all lines from the widget and refreshes the display.\n * `goto` Arg `line=None` Fast scroll to a line. By default shows the end of\n the text. 0 shows the start.\n\nFast updates:  \nRendering text to the screen is relatively slow. To send a large amount of text\nthe fastest way is to perform a single `append`. Text may contain newline\n(`'\\n'`) characters as required. In that way rendering occurs once only.\n\n`ntrim`__\nIf text is regularly appended to a `Textbox` its buffer grows, using RAM. The\nvalue of `ntrim` sets a limit to the number of lines which are retained, with\nthe oldest (topmost) being discarded as required.\n\n###### [Contents](./README.md#0-contents)\n\n## 6.11 Meter widget\n\nThis `passive` widget displays a single floating point value on a vertical\nlinear scale. Optionally it can support data dependent callbacks.\n```python\nfrom gui.widgets import Meter  # File: meter.py\n```\n![Image](./images/meter.JPG)\nThe two styles of `meter`, both showing a value of 0.65. This `passive` widget\nprovides a vertical linear meter display of values scaled between 0.0 and 1.0.\nIn these examples each meter simply displays a data value.\n\n![Image](./images/tstat.JPG)  \nThis example has two data sensitive regions, a control region with hysteresis\nand an alarm region. Callbacks can run in response to specific changes in the\n`Meter`'s value emulating data-dependent behaviour including alarms and\ncontrols (like thermostats) having hysteresis.\n\nThe class supports one or more `Region` instances. Visually these appear as\ncolored bands on the scale. If the meter's value enters, leaves or crosses one\nof these bands a callback is triggered. This receives an arg indicating the\nnature of the change which caused the trigger. For example an alarm might be\ntriggered when the value, initially below the region, enters it or crosses it.\nThe alarm might be cleared on exit or if crossed from above. Hysteresis as used\nin thermostats is simple to implement. Examples of these techniques may be\nfound in `gui.demos.tstat.py`.\n\nRegions may be modified, added or removed programmatically.\n\nConstructor mandatory positional args:  \n 1. `writer` The `Writer` instance (defines font) to use.\n 2. `row` Location on screen.\n 3. `col`  \n\nKeyword only args:  \n * `height=50` Height of meter.\n * `width=10` Width.\n * `fgcolor=None` Color of foreground (the control itself). If `None` the\n `Writer` foreground default is used.\n * `bgcolor=BLACK` Background color of meter. If `None` the `Writer` background\n is used.\n * `bdcolor=False` Color of border. If `False` no border will be drawn. If a\n color is provided, a border line will be drawn around the control.\n * `ptcolor=None` Color of meter pointer or bar. Default is foreground color.\n * `divisions=5` No. of graduations to show.\n * `label=None` A text string will cause a `Label` to be drawn below the\n meter. An integer will create a `Label` of that width for later use.\n * `style=Meter.LINE` The pointer is a horizontal line. `Meter.BAR` causes a\n vertical bar to be displayed. Much easier to read on monochrome displays.\n * `legends=None` If a tuple of strings is passed, `Label` instances will be\n displayed to  the right hand side of the meter, starting at the bottom. E.G.\n `('0.0', '0.5', '1.0')`\n * `value=0` Initial value.\n\nMethods:\n 1. `value` Args: `n=None, color=None`.\n    * `n` should be a float in range 0 to 1.0. Causes the meter to be updated.\n    Out of range values are constrained. If `None` is passed the meter is not\n    updated.\n    * `color` Updates the color of the bar or line if a value is also passed.\n    `None` causes no change.  \n Returns the current value.  \n 2. `text` Updates the label if present (otherwise throws a `ValueError`). Args:\n    * `text=None` The text to display. If `None` displays last value.\n    * `invert=False` If true, show inverse text.\n    * `fgcolor=None` Foreground color: if `None` the `Writer` default is used.\n    * `bgcolor=None` Background color, as per foreground.\n    * `bdcolor=None` Border color. As per above except that if `False` is\n    passed, no border is displayed. This clears a previously drawn border.  \n 3. `del_region` Arg: a `Region` instance. Deletes the region. No callback will\n run.\n\n### Legends\n\nDepending on the font in use for legends additional space may be required above\nand below the `Meter` to display the top and bottom legends.\n\n### Example of use of Regions\n\n```python\n# Instantiate Meter\nts = Meter(wri, row, sl.mcol + 5, ptcolor=YELLOW, height=100, width=15,\n           style=Meter.BAR, legends=('0.0', '0.5', '1.0'))\n# Instantiate two Regions and associate with the Meter instance.\nreg = Region(ts, 0.4, 0.55, MAGENTA, ts_cb)\nal = Region(ts, 0.9, 1.0, RED, al_cb)\n```\nThe callback `ts_cb` will run in response to data values between 0.4 and 0.55:\nif the value enters that range having been outside it, if it leaves the range,\nor if successive values are either side of the range. The `al_cb` callback\nbehaves similarly for data values between 0.9 and 1.0.\n\n###### [Contents](./README.md#0-contents)\n\n### 6.11.1 Region class\n\n```python\nfrom gui.widgets import Region  # File: region.py\n```\nInstantiating a `Region` associates it with a supporting widget (currently only\na `Meter`). Constructor positional args are as follows:\n\n * `tstat` The parent instance.\n * `vlo` Low value (0 \u003c= `vlo` \u003c= 1.0).\n * `vhi` High value (`vlo` \u003c `vhi` \u003c= 1.0).\n * `color` For visible band.\n * `callback` This receives two args, `reg` being the `Region` instance and\n`reason`, an integer indicating why the callback occurred (see below).\n * `args=()` Optional additional tuple of positional args for the callback.\n\nMethod:\n * `adjust` Args: `vlo`, `vhi`. Change the range of the `Region`. Constraints\n are as per the above constructor args.\n\nClass variables (constants).\n\nThese define the reasons why a callback occurred. A change in the `Tstat` value\nor an adjustment of the `Region` values can trigger a callback. The value might\nchange such that it enters or exits the region. Alternatively it might change\nfrom being below the region to above it: this is described as a transit. The\nfollowing cover all possible options.\n\n * `EX_WB_IA` Exit region. Was below before it entered. Is now above.\n * `EX_WB_IB` Exit, was below, is below.\n * `EX_WA_IA` Exit, was above, is above.\n * `EX_WA_IB` Exit, was above, is below.\n * `T_IA` Transit, is above (was below by definition of a transit).\n * `T_IB` Transit, is below.\n * `EN_WA` Entry, was above.\n * `EN_WB` Entry, was below.\n\nThe following, taken from `gui.demos.tstat.py` is an example of a thermostat\ncallback with hysteresis:\n```python\n    def ts_cb(self, reg, reason):\n        # Turn on if T drops below low threshold when it had been above high threshold. Or\n        # in the case of a low going drop so fast it never registered as being within bounds\n        if reason == reg.EX_WA_IB or reason == reg.T_IB:\n            self.led.value(True)\n        elif reason == reg.EX_WB_IA or reason == reg.T_IA:\n            self.led.value(False)\n```\nValues for these constants enable them to be combined with the bitwise `or`\noperator if you prefer that coding style:\n```python\nif reason \u0026 (reg.EX_WA_IB | reg.T_IB):  # Leaving region heading down\n```\nOn instantiation of a `Region` callbacks do not run. The desirability of this\nis application dependent. If the user `Screen` is provided with an `after_open`\nmethod, this can be used to assign a value to the `Tstat` to cause region\ncallbacks to run as appropriate.\n\n###### [Contents](./README.md#0-contents)\n\n## 6.12 Slider and HorizSlider widgets\n\n```python\nfrom gui.widgets import Slider, HorizSlider  # File: sliders.py\n```\n![Image](./images/sliders.JPG)\n\nDifferent styles of slider.\n\nThese emulate linear potentiometers in order to display or control floating\npoint values. A description of the user interface in the `active` case may be\nfound in [Floating Point Widgets](./README.md#112-floating-point-widgets).\n\nVertical `Slider` and horizontal `HorizSlider` variants are available. These\nare constructed and used similarly. The short forms (v) or (h) are used below\nto identify these variants.\n\nConstructor mandatory positional args:  \n 1. `writer` The `Writer` instance (defines font) to use.\n 2. `row` Location on screen.\n 3. `col`  \n\nOptional keyword only arguments:\n * `height` Dimension of the bounding box. Default 100 pixels (v), 20 (h).\n * `width` Dimension of the bounding box. Default 20 pixels (v), 100 (h).\n * `divisions=10` Number of graduations on the scale.\n * `legends=None` A tuple of strings to display near the slider. These will be\n distributed evenly along its length, starting at the bottom (v) or left (h).\n * `fgcolor=None` Color of foreground (the control itself). If `None` the\n `Writer` foreground default is used.\n * `bgcolor=None` Background color of object. If `None` the `Writer` background\n default is used.\n * `fontcolor=None` Text color. Defaults to foreground color.\n * `bdcolor=False` Color of border. If `False` no border will be drawn. If a\n color is provided, a border line will be drawn around the control.\n * `slotcolor=None` Color for the slot: this is a thin rectangular region in\n the centre of the control along which the slider moves. Defaults to the\n background color.\n * `prcolor=None` If `active`, in precision mode the white focus border changes\n to yellow to for a visual indication. An alternative color can be provided.\n `WHITE` will defeat this change.\n * `callback=dolittle` Callback function which runs whenever the control's\n value changes. If the control is `active` it also runs on instantiation. This\n enables dynamic color changes. Default is a null function.\n * `args=[]` A list/tuple of arguments for above callback.\n * `value=0.0` The initial value: slider will be at the bottom (v), left (h).\n * `active=True` Determines whether the control can accept user input.\n * `min_delta=0.01` Minimim value increment\n * `max_delta=0.1` Maximum value increment (long button presses)  \n\nMethods:\n * `greyed_out` Optional Boolean argument `val=None`. If `None` returns the\n current 'greyed out' status of the control. Otherwise enables or disables it,\n showing it in its new state.\n * `value=None` Optional float argument. If supplied the slider moves to show\n the new value and the callback is triggered. The method constrains the range\n to 0.0 to 1.0. The method always returns the control's value.\n * `color` Mandatory arg `color` The control is rendered in the selected\n color. This supports dynamic color changes.\n\nIf instantiated as `active`, the floating point widget behaves as per\n[section 1.12](./README.md#112-floating-point-widgets). When the widget has\nfocus, `increase` and `decrease` buttons adjust the value. Brief presses cause\nsmall changes, longer presses cause accelerating change. A long press of\n`select` invokes high precision mode.\n\n### Callback\n\nThe callback receives an initial arg being the widget instance followed by any\nuser supplied args. The callback can be a bound method, typically of a `Screen`\nsubclass. The callback runs when the widget is instantiated and whenever the\nvalue changes. This enables dynamic color change. See `gui/demos/active.py`.\n\n### Legends\n\nDepending on the font in use for legends additional space may be required\naround sliders to display all legends.\n\n###### [Contents](./README.md#0-contents)\n\n## 6.13 Scale widget\n\n```python\nfrom gui.widgets import Scale  # File: scale.py\n```\n![Image](./images/scale.JPG)\n\nThis displays floating point data having a wide dynamic range, and optionally\nprovides for user input of such values. It is modelled on old radios where a\nlarge scale scrolls past a small window having a fixed pointer. This enables a\nscale with (say) 200 graduations (ticks) to readily be visible on a small\ndisplay, with sufficient resolution to enable the user to interpolate between\nticks. Default settings enable estimation of a value to within about +-0.1%.\n\nThe `Scale` may be `active` or `passive`. A description of the user interface\nin the `active` case may be found in\n[Floating Point Widgets](./README.md#112-floating-point-widgets).\n\nThe scale handles floats in range `-1.0 \u003c= V \u003c= 1.0`, however data values may\nbe scaled to match any given range.\n\nLegends for the scale are created dynamically as it scrolls past the window.\nThe user may control this by means of a callback. Example code may be found\n[in nano-gui](https://github.com/peterhinch/micropython-nano-gui/blob/master/gui/demos/scale.py)\nwhich has a `Scale` whose value range is 88.0 to 108.0. A callback ensures that\nthe display legends match the user variable. A further callback can enable the\nscale's color to change over its length or in response to other circumstances.\n\nConstructor mandatory positional args:  \n 1. `writer` The `Writer` instance (defines font) to use.\n 2. `row` Location on screen.\n 3. `col`  \n\nOptional keyword only arguments:\n * `ticks=200` Number of \"tick\" divisions on scale. Must be divisible by 2.\n * `value=0.0` Initial value.\n * `height=0` Default is a minimum height based on the font height.\n * `width=100`\n * `fgcolor=None` Color of foreground (the control itself). If `None` the\n `Writer` foreground default is used.\n * `bgcolor=None` Background color of object. If `None` the `Writer` background\n default is used.\n * `bdcolor=None` Color of border, default `fgcolor`. If `False` no border will\n be drawn. If a  color is provided, a border line will be drawn around the\n control.\n * `prcolor=None` If `active`, in precision mode the white focus border changes\n to yellow to for a visual indication. An alternative color can be provided.\n `WHITE` will defeat this change.\n * `pointercolor=None` Color of pointer. Defaults to `.fgcolor`.\n * `fontcolor=None` Color of legends. Default `fgcolor`.\n * `legendcb=None` Callback for populating scale legends (see below).\n * `tickcb=None` Callback for setting tick colors (see below).\n * `callback=dolittle` Callback function which runs when the user moves the\n scale or the value is changed programmatically. If the control is `active` it\n also runs on instantiation. Default is a null function.\n * `args=[]` A list/tuple of arguments for above callback.\n * `active=False` By default the widget is passive. By setting `active=True`\n the widget can acquire focus; its value can then be adjusted with the\n `increase` and `decrease` buttons.\n\nMethods:\n * `greyed_out` Optional Boolean argument `val=None`. If `None` returns the\n current 'greyed out' status of the control. Otherwise enables or disables it,\n showing it in its new state.\n * `value=None` Set or get the current value. Always returns the current value.\n A passed `float` is constrained to the range -1.0 \u003c= V \u003c= 1.0 and becomes the\n `Scale`'s current value. The `Scale` is updated. Passing `None` enables\n reading the current value, but see note below on precision.\n\nFor example code see `gui/demos/active.py`.\n\n### Control algorithm\n\nIf instantiated as `active`, the floating point widget behaves as","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbunnysakura%2Fespnanotool-mpy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbunnysakura%2Fespnanotool-mpy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbunnysakura%2Fespnanotool-mpy/lists"}