{"id":13802163,"url":"https://github.com/peterhinch/micropython-touch","last_synced_at":"2025-04-30T15:31:26.031Z","repository":{"id":228036476,"uuid":"772990897","full_name":"peterhinch/micropython-touch","owner":"peterhinch","description":"A GUI for touch panel displays.","archived":false,"fork":false,"pushed_at":"2024-09-17T11:05:53.000Z","size":2775,"stargazers_count":23,"open_issues_count":3,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-09-17T13:51:11.690Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/peterhinch.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-03-16T12:47:32.000Z","updated_at":"2024-09-17T11:05:56.000Z","dependencies_parsed_at":"2024-04-27T11:43:22.103Z","dependency_job_id":"1570e678-532b-43d6-8275-1366216d1a68","html_url":"https://github.com/peterhinch/micropython-touch","commit_stats":null,"previous_names":["peterhinch/micropython-touch"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython-touch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython-touch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython-touch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython-touch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/peterhinch","download_url":"https://codeload.github.com/peterhinch/micropython-touch/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224215504,"owners_count":17274798,"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":[],"created_at":"2024-08-04T00:01:37.830Z","updated_at":"2025-04-30T15:31:25.996Z","avatar_url":"https://github.com/peterhinch.png","language":"Python","readme":"# micropython-touch\n\nThis is a lightweight, portable, MicroPython GUI library for displays having\na touch interface and with drivers subclassed from\n[framebuf](https://docs.micropython.org/en/latest/library/framebuf.html).\nWritten in Python it runs under a standard MicroPython firmware build. Input is\nby touch. The design is intended to support a range of touch controllers.\nInitially the TSC2007, XPT2046 and FT6206 are supported.\n\nIt is larger and more complex than\n[nano-gui](https://github.com/peterhinch/micropython-nano-gui) owing to its\nsupport for input. The GUI enables switching between screens and launching modal\nwindows. Widgets are a substantial superset of `nano-gui` widgets. It is\ncompatible with all `nano-gui` display drivers so is portable to a wide range of\ndisplays. Support for e-paper is theoretically possible if any exist with touch\ncontrollers. The GUI is also portable between hosts.\n\nIt was developed from [micro-gui](https://github.com/peterhinch/micropython-micro-gui/tree/main)\nwith the aim of supporting a variety of touch controllers. Touch drivers share\na common API via a common abstract base class, the aim being to simplify the\ndesign of touch controller drivers.\n\n![Image](./images/rp2_test_fixture.JPG)  \nRaspberry Pico with an ILI9341 from eBay (XPT2046 touch controller).\n\n![Image](./images/rp2_tsc2007.JPG)  \nRaspberry Pico with Adafruit 3.2\" display and TSC2007 touch controller.\n\n![Image](./images/chess.JPG)  \nChess game.\n\n![Image](./images/keyboard.JPG)  \nKeyboard demo.\n\n![Image](./images/calendar.JPG)  \nCalendar demo - a perpetual calendar.\n\n# Documents\n\n[Supported displays](https://github.com/peterhinch/micropython-nano-gui/blob/master/DISPLAYS.md).  \n[Setup guide](./SETUP.md) Quick start guide. Calibration and approaches to application development.  \n[Touchpad drivers](./TOUCHPAD.md) Details of supported drivers and technical notes.  \nCommercial links to hardware that produced excellent results:  \n[TSC2007 breakout](http://www.adafruit.com/products/5423) Interface to displays\nthat bring out analog touch signals, such as  \n[Adafruit 3.2\" touchscreen](https://www.adafruit.com/product/1743)  \nWaveshare make touch displays where the Raspberry Pico plugs in e.g.\n[Waveshare Pico res touch](https://www.waveshare.com/wiki/Pico-ResTouch-LCD-2.8).  \n\n# Alternative GUIs for MicroPython\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 * [micro-gui](https://github.com/peterhinch/micropython-micro-gui/tree/main) A\n similar GUI with input provided by various configurations of pushbuttons and/or\n an encoder. Ideal for displays lacking touch overlays.\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 * [LCD160cr](https://github.com/peterhinch/micropython-lcd160cr-gui) Touch GUI\n for the official display. Alas the hardware is hewn from ingots of the purest,\n weapons grade unobtanium.\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\nDec 2024: hardware_setup.py now renamed touch_setup.py (existing users please note).  \nOct 2024: Refresh locking can now be handled by device driver.  \nSept 2024: Dropdown and Listbox widgets support dynamically variable lists of elements.  \nMay 2024: Add support for round displays with CST816S touch controller.  \nApril 2024: Touch ABC simplified and bugs fixed. Demos updated to take advantage\nof larger displays.  \nMarch 2024: Port from micro-gui.\n\n# Before you start\n\nPlease read [the quick start guide](./SETUP.md) which advises on hardware selection\nand configuration.\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 [Widget control](./README.md#14-widget-control) Operation of variable controls.  \n 1.5 [Hardware definition](./README.md#15-hardware-definition) How to configure your hardware.  \n 1.6 [Quick start](./README.md#16-quick-start) Also a guide to hardware choice, calibration, application development.   \n 1.7 [Files](./README.md#17-files) Discussion of the files in the library.  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;1.7.1 [Demos](./README.md#171-demos) Simple demos showing coding techniques.  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;1.7.2 [Test scripts](./README.md#172-test-scripts) GUI tests, some needing larger displays  \n 1.8 [Floating Point Widgets](./README.md#18-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 touch_setup.  \n 3.2 [Display class](./README.md#32-display-class) Instantiation in touch_setup.py.  \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 [Retrieving data](./README.md#46-retrieving-data) 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 \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;6.7.1 [Dynamic changes](./README.md#671-dynamic-changes) Alter listbox contents at runtime.  \n 6.8 [Dropdown widget](./README.md#68-dropdown-widget) Dropdown lists.  \n \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;6.8.1 [Dynamic changes](./README.md#681-dynamic-changes) Alter dropdown contents at runtime.  \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#6111-region-class) Convert a Meter to a thermostat type object.  \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 [Menu class](./README.md#617-menu-class)  \n 6.18 [BitMap widget](./README.md#618-bitmap-widget) Draw bitmaps from files.  \n 6.19 [QRMap widget](./README.md#619-qrmap-widget) Draw QR codes created by uQR.  \n 6.20 [Pad widget](./README.md#620-pad-widget) Invisible region sensitive to touch.\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. [Realtime applications](./README.md#8-realtime-applications) Accommodating tasks requiring fast RT performance: refresh control.  \n\n[Appendix 1 Application design](./README.md#appendix-1-application-design) Useful hints.  \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[Appendix 4 GUI Design notes](./README.md#appendix-4-gui-design-notes) The reason for continuous refresh.  \n[Appendix 5 Bus sharing](./README.md#appendix-5-bus-sharing) Using the SD card on Waveshare boards.    \n\n# 1. Basic concepts\n\nInternally `micropython-touch` uses `asyncio`. The interface is callback-based;\nknowledge of `asyncio` is not required for its use. Display refresh is handled\nautomatically. Widgets are drawn using graphics primitives rather than icons.\nThis makes them efficiently scalable and minimises RAM usage compared to\nicon-based graphics. It also facilitates the provision of extra visual\ninformation. For example the color of all or part of a widget may be changed\nprogrammatically, for example to highlight an overrange condition. There is\nlimited 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 touch_setup  # Create a display instance linked to touch controller\nfrom gui.core.tgui 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 `touch_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, having 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 respond to touch. `Widget` objects have\ndimensions defined by bound variables `height` and `width`.\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\n###### [Contents](./README.md#0-contents)\n\n## 1.4 Widget control\n\nSome widgets support the entry of floating point values, for example slider\ncontrols. These operate as follows. A touch near one end of the control causes\nthe value to increase, touching near the other end causes it to decrease. The\nrate of change depends on the distance between the touch and the widget centre.\nThis enables rapid change, but also slow and extremely precise adjustment.\n\n###### [Contents](./README.md#0-contents)\n\n## 1.5 Hardware definition\n\nA file `touch_setup.py` must exist in the GUI root directory. This defines\nthe connections to the display and the display driver. It also defines the touch\ndriver and the pins used for its interface. The doc referenced in the next\nsection describes the creation of a `touch_setup.py` in detail. Example files\nmay be found in the `setup_examples` directory. Further examples (without touch\ncontroller 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 with TSC2007 touch controller:\n```python\nfrom machine import Pin, SoftI2C, SPI, freq\nimport gc\nfrom drivers.ili93xx.ili9341 import ILI9341 as SSD\n\nfreq(250_000_000)  # RP2 overclock\n# Create and export an SSD instance\nprst = Pin(8, Pin.OUT, value=1)\npdc = Pin(9, Pin.OUT, value=0)  # Arbitrary pins\npcs = Pin(10, Pin.OUT, value=1)\nspi = SPI(0, sck=Pin(6), mosi=Pin(7), miso=Pin(4), baudrate=30_000_000)\ngc.collect()  # Precaution before instantiating framebuf\nssd = SSD(spi, pcs, pdc, prst, height=240, width=320, usd=True)  # 240x320 default\nfrom gui.core.tgui import Display\n\n# Touch configuration\nfrom touch.tsc2007 import TSC2007\ni2c = SoftI2C(scl=Pin(27), sda=Pin(26), freq=100_000)\ntpad = TSC2007(i2c)\n# The following line of code is the outcome of calibration.\ntpad.init(240, 320, 241, 292, 3866, 3887, True, True, False)\ndisplay = Display(ssd, tpad)\n```\n###### [Contents](./README.md#0-contents)\n\n## 1.6 Quick start\n\nPlease ensure device firmware is up to date. [SETUP.md](./SETUP.md) describes\nhow to configure, calibrate and test a touchscreen so that the demos below may\nbe run. It includes ideas on application development.\n\n###### [Contents](./README.md#0-contents)\n\n## 1.7 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 * `tgui.py` The main GUI code.\n * `writer.py` Supports the `Writer` and `CWriter` classes.  \n\nTouch support is in the `touch` directory:\n* `touch.py` Common abstract base class.\n* `tsc2007.py` Driver for TSC2007 controller.\n* `xpt2046.py` Driver for XPT2046 controller.\n* `ft6206.py` FT6206 capacitive screen controller.\nOther drivers will be added.\n\nThe `gui/demos` directory contains a variety of demos and tests described\nbelow.\n\n### 1.7.1 Demos\n\nDemos are run from the GUI root directory 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\nIt is possible to run the demos without installing. Copy the directory tree to\nthe PC with\n```bash\n$ git clone https://github.com/peterhinch/micropython-touch\n```\n Ensure your `touch setup.py` is in the GUI root and the hardware is connected.\n Then issue (e.g.)\n```bash\n$ mpremote mount . exec \"import gui.demos.simple\"\n```\n\nThe initial demos are minimal 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`.\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 `Button` 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, also the `Pad` widget.\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 * `bitmap.py` Demo of the `BitMap` widget showing a changing image. (See widget\n    docs for instructions for running this).\n * `qrcode.py` Display a QR code. Requires the uQR module: copy tree\n `optional/py/uQR.py` to the root directory.\n\n### 1.7.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 * `round.py` Polar plot demo primarily for round touchscreens (240x240). Also\n demonstrates a sequential screen arrangement rather than a tree structure.\n * `screens.py` Listbox, dropdown and dialog boxes (128x240).\n * `various.py` Assorted widgets including the different types of `Button`\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 * `keyboard.py` Full alphanumeric keyboard using `Grid` (240x320). Illustrates\n use of `Pad` to make a `Grid` respond to touch.\n * `mqtt.py` (240x320) Demo of sending and receiving MQTT messages. Requires\n some setup, see [./optional/mqtt/MQTT_DEMO.md](./optional/mqtt/MQTT_DEMO.md).\n * `listbox_var.py` Listbox with dynamically variable elements.\n * `dropdown_var.py` Dropdown with dynamically variable elements.\n * `dropdown_var_tuple.py ` Dropdown with dynamically variable tuple elements.\n * `refresh_lock.py` Specialised demo of an application which controls refresh\n behaviour. See [Realtime applications](./README.md#8-realtime-applications).\n * `chess_game.py` Does what it says on the tin (240x320). See [guide](./optional/chess/CHESS.md).\n\n###### [Contents](./README.md#0-contents)\n\n## 1.8 Floating Point Widgets\n\nThe following widgets provide floating point input:\n* `Knob` Rotary control.\n* `Slider` (also `HorizSlider`) Linear controls modelled on audio mixing desks.\n* `Scale` A horizontal linear control that displays data to high precision.\n* `ScaleLog` Logarithmic linear control for values with high dynamic range.\n\nFormer iterations of touch GUIs used dragging. This provided rather coarse\nadjustment, even when the hardware interfaces were fast. The above controls use\na different algorithm where a touch causes the control's value to change at a\nrate depending on the location of the touch. The closer the touch is to the  \ncentreline of the control, the slower the rate of change. This allows very\nprecise changes to be made by adjusting the location and duration of a touch.\n\"Closer\" is on the horizontal axis for horizontal widgets, otherwise vertical.\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. Commented out code shows changes\nfor 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 `touch_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 touch_setup  # Instantiate display, setup color LUT (if present)\nfrom gui.core.tgui import Screen, ssd\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 touch-gui...')\n    Screen.change(BaseScreen)\n\ntest()\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`Button` 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 three default colors which are defined by a `color_map` list. These\nmay be reassigned in user code. The `color_map` index constants and default\ncolors (defined in `colors.py`) are:\n\n| Index     | Color  | Purpose                                   |\n|:----------|:-------|:------------------------------------------|\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. At the bit level `1` represents\nthe foreground. This is white on an emitting display such as an OLED. I am not\naware of any non-emitting displays (e.g. ePaper) with touch controllers.\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 touch_setup  # Create a display instance\nfrom gui.core.tgui import Screen, ssd, display  # display symbol is seldom needed\n```\nThe `touch_setup` file creates singleton instances of `SSD` and `Display`\nclasses. These instances are made available via `tgui`. Normal GUI applications\nonly need to import `ssd`. This reference 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 `touch_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 `touch_setup.py`. It registers the `SSD` instance\nalong with the `touch` subclass instance used for input.\n\nThe constructor takes the following positional args:  \n 1. `objssd` The `SSD` instance. A reference to the display driver.\n 2. `objtouch=None` Touch controller instance. `None` allows the display to be\n tested prior to implementing the touch interface.\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 a 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). A means of returning data is provided by the `value`\nclassmethod.\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 * `value(cls, val=None)` This is a convenience method for accessing data from a\n `Screen` after closure. See [section 4.6](./README.md#46-retrieving-data).\n\nThese are uncommon:  \n * `shutdown(cls)` Clear the screen and shut down the GUI. Normally done by a\n `CloseButton` on the initial `Screen`: the button issues `Screen.back()`.\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 demonstrating 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`, `demos/primitives.py` for examples of `after_open`; this\nmethod is particularly useful for drawing onto a screen and for\n[displaying images](https://github.com/peterhinch/micropython-nano-gui/blob/master/IMAGE_DISPLAY.md).\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. If `do_gc` is `False` the application\n can control garbage collection. The GC task cannot be re-started if disabled.\n\n## 4.6 Retrieving data\n\nWhere widgets on a `Screen` generate data and the `Screen` is then closed, there\nare a number of ways to ensure that the data remains accessible. These include\n* Shared global variables.\n* Class variables.\n* Passing callbacks to `Screen.change()`. This enables a `Screen` to update\ncontrols on an underlying `Screen`. See the `screens` demo for an example.\n\nThe `value` classmethod is provided to standardise and simplify the use of class\nvariables. Assume a user screen `MyScreenClass`. Widgets on the `MyScreenClass`\ninstance call `MyScreenClass.value(arg)`. The `arg` can be any Python object -\na `dict` might be used if there are multiple data widgets.\n\nData may be retrieved after the screen is closed with:\n```python\ndata = MyScreenClass.value()\n```\nSee the `dialog` demo for an example.\n\nWhere the underlying `Screen` has controls which need to be updated with the\nreturned data, the widgets should be re-populated in the `after_open` method.\nThis runs after the underlying `Screen` is re-displayed.\n\n#### Note\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)` This is inherited from `Screen` and provides a\nstandardised way to access data created in a `Window`. See\n[section 4.6](./README.md#46-retrieving-data).\n\n## 5.3 Popup windows\n\nThere is a special case of a popup window with no touch sensitive controls.\nThis typically displays status data, possibly with a progress meter. Such a\npopup is closed by user code. A popup is created by passing a `Writer` (or\n`CWriter`) to the constructor and is closed by issuing the `Window.close()`\nstatic 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 touch_setup import ssd  # Create a display instance\nfrom gui.core.tgui 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\nA `Grid` overlaid by a `Pad` enables the construction of touchable grids with\ndiffering characteristics. See the calendar, keyboard and chess demos.\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\nMethods:  \n * `__call__(row, col=None)` Returns the `Label` instance at a single location.\n If no `col` is provided 1D addressing is assumed.\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:  \nIt is important to note that array index notation always returns an iterator,\neven if only a single element is required. One way to access a single element is\n```python\nit = grid[0 , 0]\nlabel = next(it)  # Label at row == 0, col == 0\n```\nhowever function call syntax is more intuitive:\n```python\nlabel = grid(0, 0)\n```\nAccessing labels in a single row, by column:\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 examples [calendar.py](https://github.com/peterhinch/micropython-touch/blob/main/gui/demos/calendar.py),\n[keyboard.py](https://github.com/peterhinch/micropython-touch/blob/master/gui/demos/keyboard.py)\nand [chess_game.py](https://github.com/peterhinch/micropython-touch/blob/master/optional/chess/chess_game.py).\nIn these samples a `Pad` overlays the `Grid` to produce a grid that responds to touch.\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 used 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\nThis emulates a pushbutton, with a callback being executed each time the button\nis pressed. Buttons may be any one of three shapes: `CIRCLE`, `RECTANGLE` or\n`CLIPPED_RECT`. By default the callback is triggered on release of the touch.\nTriggering on press carries a hazard if a button causes a screen change: this\nresults if the new screen has an active widget at the same location, when that\nwidget would inadvertently be triggered.\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 * `onrelease=True` If set the callback runs when the button is released.\n * `lp_callback=None` Callback for a long press (runs on press).\n * `lp_args=()` Args for above.\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 variables:\n * `lit_time=1000` Period in ms the `litcolor` is displayed.\n * `long_press_time=1000` Touch duration in ms to trigger long press callack.\n\n### CloseButton\n![Image](./images/closebutton.JPG)  \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 square. Optionally `width` may be specified.\n * `callback=dolittle` Optional callback, not normally required.\n * `args=()` Args for above.\n * `bgcolor=RED`\n\nClass variable:\n * `width=30` Sets a default minimum button width. Larger buttons are easier to\n touch accurately. This may be set to zero: the actual width is the largest of\n the class variable, the width passed to the constructor, and that calculated\n from the font size.\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 the new button is\n displayed. By default the callback of the previous button is run, otherwise the\n callback of 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 the button\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. Touching an entry will cause the\ncallback 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. A short vertical line is visible\nin the top right if scrolling down is possible, likewise in the bottom right if\nthe contents may be scrolled up. A long touch on the top or bottom entry\ninitiates scrolling.\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\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 * `update` No args. See [Dynamic changes](./README.md#671-dynamic-changes).\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### 6.7.1 Dynamic changes\n\nThe contents of a listbox may be changed at runtime. To achieve this, elements\nmust be defined as a list rather than a tuple. After the application has\nmodified the list, it should call the `.update` method to refresh the control.\nThe demo script `listbox_var.py` illustrates this.\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\nWhen the dropdown is touched the list is displayed. If an entry in the list is\ntouched the callback is triggered, the list is closed and the control displays\nthe newly selected entry. If the list contains more entries than can be shown,\nscrolling may be used. A short vertical line is visible in the top right if\nscrolling down is possible, likewise in the bottom right if the contents may be\nscrolled up. A long touch on the top or bottom entry initiates scrolling.\n\nThe callback's first argument is the dropdown instance followed by any args\nspecified to the constructor. The current item may be retrieved by means of the\ninstance'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### 6.8.1 Dynamic changes\n\nThe contents of a Dropdown may be changed at runtime. To achieve this, elements\nmust be defined as a list rather than a tuple. After the application has\nmodified the list, it should call the `.update` method to refresh the control.\nChanges should be made when the dropdown list is not visible; the consequence of\n`.update` will immediately be visible only if the currently visible item is\ndeleted. The demo scripts `dropdown_var.py` and `dropdown_var_tuple.py`\nillustrate this.\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 `Button`\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 `Button`, 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 `Button`s. 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 by touching near\nthe top or bottom of the control. The rate of scrolling depends on the distance\nbetween the touch and the centreline of the control. The widget supports fixed\nand variable pitch fonts.\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 by touch.\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`append` arg `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\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 * `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\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).\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.\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 * `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 responds to touch.\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### 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.\n\n### Callback legendcb\n\nThe display window contains 20 ticks comprising two divisions; by default a\ndivision covers a range of 0.1. A division has a legend at the start and end\nwhose text is defined by the `legendcb` callback. If no user callback is\nsupplied, legends will be of the form `0.3`, `0.4` etc. User code may override\nthese to cope with cases where a user variable is mapped onto the control's\nrange. The callback takes a single `float` arg which is the value of the tick\n(in range -1.0 \u003c= v \u003c= 1.0). It must return a text string. An example from\n[ths nano-gui demo](https://github.com/peterhinch/micropython-nano-gui/blob/master/gui/demos/scale.py)\nshows FM radio frequencies:\n```python\ndef legendcb(f):\n    return '{:2.0f}'.format(88 + ((f + 1) / 2) * (108 - 88))\n```\nThe above arithmetic aims to show the logic. It can (obviously) be simplified.\n\n### Callback tickcb\n\nThis callback enables the tick color to be changed dynamically. For example a\nscale might change from green to orange, then to red as it nears the extremes.\nThe callback takes two args, being the value of the tick (in range\n-1.0 \u003c= v \u003c= 1.0) and the default color. It must return a color. This example\nis taken from the `scale.py` demo:\n```python\ndef tickcb(f, c):\n    if f \u003e 0.8:\n        return RED\n    if f \u003c -0.8:\n        return BLUE\n    return c\n```\n\n### Increasing the ticks value\n\nThis increases the precision of the display.\n\nIt does this by lengthening the scale while keeping the window the same size,\nwith 20 ticks displayed. If the scale becomes 10x longer, the value diference\nbetween consecutive large ticks and legends is divided by 10. This means that\nthe `tickcb` callback must return a string having an additional significant\ndigit. If this is not done, consecutive legends will have the same value.\n\n### Precision\n\nFor performance reasons the control stores values as integers. This means that\nif you set `value` and subsequently retrieve it, there may be some loss of\nprecision. Each visible division on the control represents 10 integer units.\n\n###### [Contents](./README.md#0-contents)\n\n## 6.14 ScaleLog widget\n\n```python\nfrom gui.widgets import ScaleLog  # File: scale_log.py\n```\n![Image](./images/log_scale.JPG)\n\nThis displays floating point values with extremely wide dynamic range and\noptionally enables their input. The dynamic range is handled by means of a base\n10 logarithmic scale. In other respects the concept is that of the `Scale`\nclass.\n\nThe control is modelled on old radios where a large scale scrolls past a small\nwindow having a fixed pointer. The use of a logarithmic scale enables the\nvalue to span a range of multiple orders of magnitude.\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). Owing to the\nlogarithmic nature of the widget, the changes discussed in that reference are\nmultiplicative rather than additive. Thus a long touch will multiply the widget's\nvalue by a progressively larger factor, enabling many decades to be traversed\nquickly.\n\nLegends for the scale are created dynamically as it scrolls past the window,\nwith one legend for each decade. The user may control this by means of a\ncallback, for example to display units, e.g. `10MHz`. A further callback\nenables the scale's color to change over its length or in response to other\ncircumstances.\n\nThe scale displays floats in range `1.0 \u003c= V \u003c= 10**decades` where `decades` is\na constructor arg. The user may readily scale these. For example a control with\na range of 1-10,000 controls a user value from 1e-6 to 1e-2 while displaying\nticks labelled 1μs, 10μs, 100μs, 1ms and 10ms.\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 arguments (all optional):\n * `decades=5` Defines the control's maximum value (i.e. `10**decades`).\n * `value=1.0` Initial value for control. Will be constrained to\n `1.0 \u003c= value \u003c= 10**decades` if outside this range.\n * `height=0` Default is a minimum height based on the font height.\n * `width=160`\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 * `pointercolor=None` Color of pointer. Defaults to `.fgcolor`.\n * `fontcolor=None` Color of legends. Default `WHITE`.\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. The callback's\n arguments are the `ScaleLog` instance, followed by any user supplied args.\n * `active=False` Determines whether the widget accepts user input.\n\nMethods:\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= 10**decades` and\n becomes the control's current value. The `ScaleLog` is updated. Always returns\n the control's current value.\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\nFor example code see `gui/demos/active.py`.\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.\n\n### Callback legendcb\n\nThe start of each decade is marked by a long \"tick\" with a user-definable text\nlabel. By default it will display a number corresponding to the value at that\ntick (of form `10**n` where `n` is an integer), but this can be overridden to\ndisplay values such as \"10MHz\". The following is a simple example from the\n`scale_ctrl_test` demo:\n```python\ndef legendcb(f):\n    if f \u003c 999:\n        return '{:\u003c1.0f}'.format(f)\n    return '{:\u003c1.0f}K'.format(f/1000)\n```\n\n### Callback tickcb\n\nThis callback enables the tick color to be changed dynamically. For example a\nscale might change from green to orange, then to red as it nears the extremes.\nThe callback takes two args, being the value of the tick (of form `10**n` where\n`n` is an integer) and the default color. It must return a color. This example\nis taken from the `scale_ctrl_test` demo:\n```python\ndef tickcb(f, c):\n    if f \u003e 30000:\n        return RED\n    if f \u003c 10:\n        return BLUE\n    return c\n```\n\n###### [Contents](./README.md#0-contents)\n\n## 6.15 Dial widget\n\n```python\nfrom gui.widgets import Dial, Pointer  # File: dial.py\n```\n![Image](./images/dial.JPG)  ![Image](./images/dial1.JPG)\n\nA `Dial` is a passive widget. It presents a circular display capable of\ndisplaying an arbitrary number of vectors; each vector is represented by a\n`Pointer` instance. The format of the display may be chosen to resemble an\nanalog clock or a compass. In the `CLOCK` case a pointer resembles a clock's\nhand extending from the centre towards the periphery. In the `COMPASS` case\npointers are chevrons extending equally either side of the circle centre.\n\nIn both cases the length, angle and color of each `Pointer` may be changed\ndynamically. A `Dial` can include an optional `Label` at the bottom which may\nbe used to display any required text.\n\nIn use, a `Dial` is instantiated. Then one or more `Pointer` objects are\ninstantiated and assigned to it. The `Pointer.value` method enables the `Dial`\nto be updated affecting the length, angle and color of the `Pointer`.\nPointer values are complex numbers.\n\n### Dial class\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=100` Height and width of dial.\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 * `ticks=4` No. of gradutions 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=Dial.CLOCK` Pointers are drawn from the centre of the circle as per\n the hands of a clock. `Dial.COMPASS` causes pointers to be drawn as arrows\n centred on the control's centre. Arrow tail chevrons are suppressed for very\n short pointers.\n * `pip=None` Draws a central dot. A color may be passed, otherwise the\n foreground color will be used. If `False` is passed, no pip will be drawn. The\n pip is suppressed if the shortest pointer would be hard to see.\n\nMethod:\n\n 1. `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\nWhen a `Pointer` is instantiated it is assigned to the `Dial` by the `Pointer`\nconstructor.\n\n### Pointer class\n\nConstructor arg:\n 1. `dial` The `Dial` instance on which it is to be dsplayed.\n\nMethods:\n 1. `value` Args:  \n    * `v=None` The value is a complex number. A magnitude exceeding unity is\n    reduced (preserving phase) to constrain the `Pointer` within the unit\n    circle.\n    * `color=None` By default the pointer is rendered in the foreground color\n    of the parent `Dial`. Otherwise the passed color is used.  \n    Returns the current value.\n\nTypical usage:\n```python\nfrom touch_setup import ssd  # Create a display instance\nimport asyncio\nimport cmath\nfrom gui.core.tgui import Screen\nfrom gui.core.writer import CWriter\nfrom gui.core.colors import *\n\nfrom gui.widgets import Dial, Pointer, CloseButton\nimport gui.fonts.freesans20 as freesans20\n\nasync def run(dial):\n    hrs = Pointer(dial)\n    mins = Pointer(dial)\n    hrs.value(0 + 0.7j, RED)\n    mins.value(0 + 0.9j, YELLOW)\n    dm = cmath.exp(-1j * cmath.pi / 30)  # Rotate by 1 minute\n    dh = cmath.exp(-1j * cmath.pi / 1800)  # Rotate hours by 1 minute\n    # Twiddle the hands: see vtest.py for an actual clock\n    while True:\n        await asyncio.sleep_ms(200)\n        mins.value(mins.value() * dm, RED)\n        hrs.value(hrs.value() * dh, YELLOW)\n\nclass BaseScreen(Screen):\n\n    def __init__(self):\n        super().__init__()\n        wri = CWriter(ssd, freesans20, GREEN, BLACK, verbose=False)\n        dial = Dial(wri, 5, 5, ticks = 12, bdcolor=None)\n        self.reg_task(run(dial))\n        CloseButton(wri)\n\nScreen.change(BaseScreen)\n```\n\n###### [Contents](./README.md#0-contents)\n\n## 6.16 Knob widget\n\n```python\nfrom gui.widgets import Knob  # File: knob.py\n```\n![Image](./images/knob.JPG)\n\nRightmost example has no border and 270° travel. Others have 360°.\n\nThis emulates a rotary control capable of being rotated through a predefined\narc in order to display or set a floating point variable. A `Knob` may be\n`active` or `passive`. A description of the user interface in the `active` case\nmay be found in [Floating Point Widgets](./README.md#112-floating-point-widgets).\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=70` Dimension of the square bounding box.\n * `arc=TWOPI` Movement available. Default 2*PI radians (360 degrees). May be\n reduced, e.g. to provide a 270° range of movement.\n * `ticks=9` Number of graduations around the dial.\n * `value=0.0` Initial value. By default the knob will be at its most\n counter-clockwise position.\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 * `color=None` Fill color for the control knob. Default: no fill.\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 runs when the user moves the knob or\n the value is changed programmatically.\n * `args=[]` A list/tuple of arguments for above callback.\n * `active=True` Enable user input via the `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` Optional argument `val`. If set, adjusts the pointer to\n correspond to the new value. The move callback will run. The method constrains\n the range to 0.0 to 1.0. Always returns the control's value.\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.\n\n###### [Contents](./README.md#0-contents)\n\n## 6.17 Menu class\n\n```python\nfrom gui.widgets import Menu  # File: menu.py\n```\n![Image](./images/menu.JPG)  \n\nThe `Menu` class enables the creation of single or multiple level menus. The\ntop level of the menu comprises a row of `Button` instances at the top of the\nphysical screen. Each button can either call a callback or instantiate a\ndropdown menu comprising the second menu level.\n\nEach item on a dropdown menu can invoke either a callback or a lower level\nmenu.\n\nConstructor mandatory positional arg:  \n 1. `writer` The `Writer` instance (defines font) to use.\n\nKeyword only args:  \n * `height=25` Height of top level menu buttons.\n * `bgcolor=None` Background color of buttons and dropdown.\n * `fgcolor=None` Foreground color.\n * `textcolor=None` Text color.\n * `select_color=DARKBLUE` Background color of selected item on dropdown menu.\n * `args` This should be a tuple containing a tuple of args for each entry in\n the top level menu. Each tuple should be of one of two forms:\n  1. `(text, cb, (args,))` A single-level entry: the top level `Button` with\n  text `text` runs the callback `cb` with positional args defined by the\n  supplied tuple (which may be `()`). The callback receives an initial arg\n  being the `Button` instance.\n  2. `(text, (element0, element1,...))` In this instance the top level `Button`\n  triggers a dropdown menu defined by data in the `elements` tuple.\n\nEach element in the `elements` tuple is a tuple defining a menu item. This can\ntake two forms, each of which has the text for the menu item as the first\nvalue:\n 1. `(text, cb, (args,))` The element triggers callback `cb` with positional\n args defined by the supplied tuple (which may be `()`). The callback receives\n an initial arg being the `Listbox` instance which corresponds to the parent\n dropdown menu.\n 2. `(text, (elements,))` This element triggers a submenu with a recursive\n instance of `elements`.\n\nThe following (from `gui/demos/menui.py`) is complete apart from initial import\nstatements. It illustrates a 3-level menu.\n```python\nclass BaseScreen(Screen):\n\n    def __init__(self):\n        def cb(button, n):\n            print('Help callback', n)\n\n        def cb_sm(lb, n):\n            print('Submenu callback', lb.value(), lb.textvalue(), n)\n\n        super().__init__()\n        metals2 = (('Gold', cb_sm, (10,)),\n                   ('Silver', cb_sm, (11,)),\n                   ('Iron', cb_sm, (12,)),\n                   ('Zinc', cb_sm, (13,)),\n                   ('Copper', cb_sm, (14,)))  # Level 3\n\n        gases = (('Helium', cb_sm, (0,)),\n                 ('Neon', cb_sm, (1,)),\n                 ('Argon', cb_sm, (2,)),\n                 ('Krypton', cb_sm, (3,)),\n                 ('Xenon', cb_sm, (4,)),\n                 ('Radon', cb_sm, (5,)))  # Level 2\n\n        metals = (('Lithium', cb_sm, (6,)),\n                  ('Sodium', cb_sm, (7,)),\n                  ('Potassium', cb_sm, (8,)),\n                  ('Rubidium', cb_sm, (9,)),\n                  ('More', metals2))  # Level 2\n\n        mnu = (('Gas', gases),\n               ('Metal', metals),\n               ('Help', cb, (2,)))  # Top level 1\n\n        wri = CWriter(ssd, font, GREEN, BLACK, verbose=False)\n        Menu(wri, bgcolor=BLUE, textcolor=WHITE, args = mnu)\n        CloseButton(wri)\n\nScreen.change(BaseScreen)\n```\nThe code\n```python\n        mnu = (('Gas', gases),\n               ('Metal',metals),\n               ('Help', cb, (2,)))\n```\ndefines the top level, with the first two entries invoking submenus and the\nthird running a callback `cb` with 2 as an arg.\n\nThis produces a second level menu with one entry ('More') invoking a third\nlevel (`metals2`):\n```python\n        metals = (('Lithium', cb_sm, (6,)),\n                  ('Sodium', cb_sm, (7,)),\n                  ('Potassium', cb_sm, (8,)),\n                  ('Rubidium', cb_sm, (9,)),\n                  ('More', metals2))\n```\nThe other entries all run `cb_sm` with a different arg. They could each run a\ndifferent callback if the application required it.\n\n###### [Contents](./README.md#0-contents)\n\n## 6.18 BitMap Widget\n\n```python\nfrom gui.widgets import BitMap  # File: bitmap.py\n```\n![Image](./images/bitmap.JPG)  \n\nThis renders a monochrome bitmap stored in a file to a rectangular region. The\nbitmap file format is C source code generated by the Linux `bitmap` editor. The\nbitmap may be rendered in any color. Data and colors can be changed at run time.\nThe widget is intended for larger bitmaps and is designed to minimise RAM usage\nat cost of performance. For fast updates of smaller bitmaps consider using an\n[icon font](https://github.com/peterhinch/micropython-font-to-py/tree/master/icon_fonts).\n\nConstructor mandatory positional args:  \n 1. `writer` A `Writer` instance.\n 2. `row` Location on screen.\n 3. `col`\n 4. `height` Image height in pixels. Dimensions must exactly match the image file.\n 5. `width` Image width in pixels.\n\nKeyword only args:  \n * `fgcolor=None` Foreground (1) color of image.\n * `bgcolor=None` Background (0) color.\n * `bdcolor=RED` Border color.\n\nMethods:__\n * `value` mandatory arg `fn` path to an image file. Causes the `BitMap` image\n to be updated from the file. Files should be stored on the root directory of\n the host. Blocks for a period depending on filesystem performance.\n * `color` args `fgcolor=None`, `bgcolor=None`. Causes the image colors to be\n changed. The file will be re-read and the image updated.\n\nBecause of the use of file storage when an update occurs there will be a brief\n\"dead time\" when the GUI is unresponsive. This is not noticeable if the image\nis displayed when a screen initialises, or if it changes in response to a user\naction. Use in animations is questionable.\n\nSee `gui/demos/bitmap.py` for a usage example. For this demo the directory tree\n`optional/bitmaps/` and contents should be copied to the device. If running via\n`mpremote mount .` the demo will run, but will be slow to update.\n\n###### [Contents](./README.md#0-contents)\n\n## 6.19 QRMap Widget\n\n```python\nfrom gui.widgets import QRMap  # File: qrcode.py\n```\n![Image](./images/qrcode.JPG)  \n\nThis renders QR codes generated using the [uQR](https://github.com/JASchilz/uQR)\napplication. Images may be scaled to render them at larger sizes. Please see\nthe notes below on performance and RAM usage. The widget requires the following\nfile: `optional/py/uQR.py` to exist on the target.\n\nConstructor positional args:  \n 1. `writer` A `Writer` instance.\n 2. `row` Location on screen.\n 3. `col`\n 4. `version=4` Defines the size of the image: see below.\n 5. `scale=1`\n\nKeyword only args:  \n * `bdcolor=RED` Border color.\n * `buf=None` Allows use of a pre-allocated image buffer.\n\nMethods:__\n * `value` mandatory arg `text` a string for display as a QR code. This method\n can throw a `ValueError` if the string cannot be accommodated in the chosen\n code size (i.e. `version`).\n * `__call__` Synonym for `value`.\n\nStatic Method:__\n * `make_buffer` args `version`, `scale`. Returns a buffer big enough to hold\n the QR code bitmap. Use of this is optional: it is a solution if memory errors\n are encountered when instantiating a `QRMap`.\n\nNote on image sizes. The size of a QR code bitmap depends on the `version` and\n`scale` parameters according to this formula:  \n`edge_length_in_pixels = (4 * version + 17) * scale`  \nTo this must be added a mandatory 4 pixel border around every edge. So the\nheight and width occupied on screen is:  \n`dimension = (4 * version + 25) * scale`  \n\nPerformance  \nThe uQR `get_matrix()` method blocks: in my testing for about 750ms. A `QRMap`\nbuffers the scaled matrix and renders it using bit blitting. Blocking by\n`QRMap` methods is minimal; refreshing a screen with the same contents is fast.\n\nThe `uQR` library is large, and compiling it uses a substantial amount of RAM.\nIf memory errors are encountered try cross-compiling or the use of frozen byte\ncode.\n\nSee `gui/demos/qrcode.py` for a usage example.\n\n###### [Contents](./README.md#0-contents)\n\n## 6.20 Pad widget\n\nThis rectangular active widget is invisible. It can be used to enable passive\nwidgets or objects drawn with display primitives to respond to touch.\n\nConstructor mandatory positional arguments:\n1. `writer` A `Writer` instance.\n2. `row` Location on screen.\n3. `col`\n\nOptional keyword only arguments:\n * `height=20`\n * `width=50`\n * `onrelease=True` If True the callback will occur when the pad is released\n otherwise it will occur when pressed.\n * `callback=None` Callback function - response to touch or release.\n * `args=[]` Args for above.\n * `lp_callback=None` Callback for a long press.\n * `lp_args=[]` Args for above.\n\nMethod:\n * `greyed_out=None` Optional boolean argument or `None`. If `None` returns the\n current 'greyed out' status of the control. Otherwise enables or disables it;\n this determines whether the control responds to touch - there is no visible\n effect.\n\n Bound variables (read access only):\n  * `rr` Coordinates of last touch in pixels relative to `Pad` location.\n  * `rc` These may be accessed by callbacks.\n\nClass variable:\n* `long_press_time = 1000` Press duration (ms) for a long press to be registered.\n\nThe demo `primitives.py` illustrates this widget.\n\n###### [Contents](./README.md#0-contents)\n\n# 7. Graph Plotting\n\n```python\nfrom gui.widgets.graph import PolarGraph, PolarCurve, CartesianGraph, Curve, TSequence\n```\n![Image](./images/polar.png)  ![Image](./images/cartesian.png)\n\n![Image](./images/lissajous.png)  ![Image](./images/bernoulli.png)\n\n![Image](./images/sine.png) Realtime time sequence simulation.\n\nFor example code see `gui/demos/plot.py`.\n\n## 7.1 Concepts\n\nData for Cartesian graphs constitutes a sequence of x, y pairs, for polar\ngraphs it is a sequence of complex `z` values. The module supports three\ncommon cases:  \n 1. The dataset to be plotted is complete at the outset.\n 2. Arbitrary data arrives gradually and needs to be plotted as it arrives.\n 3. One or more `y` values arrive gradually. The `X` axis represents time. This\n is a simplifying case of 2.\n\n### 7.1.1 Graph classes\n\nA user program first instantiates a graph object (`PolarGraph` or\n`CartesianGraph`). This creates an empty graph image upon which one or more\ncurves may be plotted. Graphs are passive widgets so do not respond to touch.\n\n### 7.1.2 Curve classes\n\nThe user program then instantiates one or more curves (`Curve` or\n`PolarCurve`) as appropriate to the graph. Curves may be assigned colors to\ndistinguish them.\n\nA curve is plotted by means of a user defined `populate` generator. This\nassigns points to the curve in the order in which they are to be plotted. The\ncurve will be displayed on the graph as a sequence of straight line segments\nbetween successive points.\n\nWhere it is required to plot realtime data as it arrives, this is achieved\nvia calls to the curve's `point` method. If a prior point exists it causes a\nline to be drawn connecting the point to the last one drawn.\n\n### 7.1.3 Coordinates\n\n`PolarGraph` and `CartesianGraph` objects are subclassed from `Widget` and are\npositioned accordingly by `row` and `col` with a 2-pixel outside border. The\ncoordinate system within a graph conforms to normal mathematical conventions.\n\nScaling is provided on Cartesian curves enabling user defined ranges for x and\ny values. Points lying outside of the defined range will produce lines which\nare clipped at the graph boundary.\n\nPoints on polar curves are defined as Python `complex` types and should lie\nwithin the unit circle. Points which are out of range may be plotted beyond the\nunit circle but will be clipped to the rectangular graph boundary.\n\n###### [Contents](./README.md#0-contents)\n\n## 7.2 Graph classes\n\n### 7.2.1 Class CartesianGraph\n\nConstructor.  \nMandatory positional arguments:  \n 1. `writer` A `CWriter` instance.\n 2. `row` Position of the graph in screen coordinates.\n 3. `col`\n\nKeyword only arguments (all optional):  \n * `height=90` Dimension of the bounding box.\n * `width=110` Dimension of the bounding box.\n * `fgcolor=None` Color of the axis lines. Defaults to `Writer` foreground\n color.\n * `bgcolor=None` Background color of graph. Defaults to `Writer` background.\n * `bdcolor=None` Border color. If `False` no border is displayed. If `None` a\n border is shown in the foreground color. If a color is passed, it is used.\n * `gridcolor=None` Color of grid. Default: Writer foreground color.\n * `xdivs=10` Number of divisions (grid lines) on x axis.\n * `ydivs=10` Number of divisions on y axis.\n * `xorigin=5` Location of origin in terms of grid divisions.\n * `yorigin=5` As `xorigin`. The default of 5, 5 with 10 grid lines on each\n axis puts the origin at the centre of the graph. Settings of 0, 0 would be\n used to plot positive values only.\n\nMethod:  \n * `show` No args. Redraws the empty graph. Used when plotting time sequences.\n\n### 7.2.2 Class PolarGraph\n\nConstructor.  \nMandatory positional arguments:  \n 1. `writer` A `CWriter` instance.\n 2. `row` Position of the graph in screen coordinates.\n 3. `col`\n\nKeyword only arguments (all optional):  \n * `height=90` Dimension of the square bounding box.\n * `fgcolor=None` Color of the axis lines. Defaults to `Writer` foreground\n color.\n * `bgcolor=None` Background color of graph. Defaults to `Writer` background.\n * `bdcolor=None` Border color. If `False` no border is displayed. If `None` a\n border is shown in the `Writer` foreground color. If a color is passed, it is\n used.\n * `gridcolor=None` Color of grid. Default: Writer foreground color.\n * `adivs=3` Number of angle divisions per quadrant.\n * `rdivs=4` Number radius divisions.\n\nMethod:  \n * `show` No args. Redraws the empty graph.\n\n###### [Contents](./README.md#0-contents)\n\n## 7.3 Curve classes\n\n### 7.3.1 Class Curve\n\nThe Cartesian curve constructor takes the following positional arguments:\n\nMandatory arguments:\n 1. `graph` The `CartesianGraph` instance.\n 2. `color` If `None` is passed, the `graph` foreground color is used.\n\nOptional arguments:  \n 3. `populate=None` A generator to populate the curve. See below.   \n 4. `origin=(0,0)` 2-tuple containing x and y values for the origin. Provides\n for an optional shift of the data's origin.  \n 5. `excursion=(1,1)` 2-tuple containing scaling values for x and y.  \n\nMethods:\n * `point` Arguments x, y. Defaults `None`. Adds a point to the curve. If a\n prior point exists a line will be drawn between it and the current point. If a\n point is out of range or if either arg is `None` no line will be drawn.\n Passing no args enables discontinuous curves to be plotted. This method is\n normally used for real time plotting.\n\nThe `populate` generator may take zero or more positional arguments. It should\nrepeatedly yield `x, y` values before returning. Where a curve is discontinuous\n`None, None` may be yielded: this causes the line to stop. It is resumed when\nthe next valid `x, y` pair is yielded.\n\nIf `populate` is not provided the curve may be plotted by successive calls to\nthe `point` method. This may be of use where data points are acquired in real\ntime, and realtime plotting is required. See class `RTRect` in\n`gui/demos/plot.py`.\n\n#### Scaling\n\nBy default, with symmetrical axes, x and y values are assumed to lie between -1\nand +1.\n\nTo plot x values from 1000 to 4000 we would set the `origin` x value to 1000\nand the `excursion` x value to 3000. The `excursion` values scale the plotted\nvalues to fit the corresponding axis.\n\n### 7.3.2 Class PolarCurve\n\nThe constructor takes the following positional arguments:\n\nMandatory arguments:\n 1. `graph` The `PolarGraph` instance.\n 2. `color`\n\nOptional arguments:  \n 3. `populate=None` A generator to populate the curve. See below.   \n\nMethods:\n * `point` Argument `z=None`. Normally a `complex`. Adds a point\n to the curve. If a prior point exists a line will be drawn between it and the\n current point. If the arg is `None` no line  will be drawn. Passing no args\n enables discontinuous curves to be plotted. Lines are clipped at the square\n region bounded by (-1, -1) to (+1, +1).\n\nThe `populate` generator may take zero or more positional arguments. It should\nyield a complex `z` value for each point before returning. Where a curve is\ndiscontinuous a value of `None` may be yielded: this causes plotting to stop.\nIt is resumed when the next valid `z` point is yielded.\n\nIf `populate` is not provided the curve may be plotted by successive calls to\nthe `point` method. This may be of use where data points are acquired in real\ntime, and realtime plotting is required. See class `RTPolar` in\n`gui/demos/plot.py`.\n\n#### Scaling\n\nComplex points should lie within the unit circle to be drawn within the grid.\n\n###### [Contents](./README.md#0-contents)\n\n## 7.4 Class TSequence\n\nA common task is the acquisition and plotting of real time data against time,\nsuch as hourly temperature and air pressure readings. This class facilitates\nthis. Time is on the x-axis with the most recent data on the right. Older\npoints are plotted to the left until they reach the left hand edge when they\nare discarded. This is akin to old fashioned pen plotters where the pen was at\nthe rightmost edge (corresponding to time now) with old values scrolling to the\nleft with the time axis in the conventional direction.\n\nThe user instantiates a graph with the X origin at the right hand side and then\ninstantiates one or more `TSequence` objects. As each set of data arrives it is\nappended to its `TSequence` using the `add` method. See the example below.\n\nThe constructor takes the following args:\n\nMandatory arguments:\n 1. `graph` The `CartesianGraph` instance.\n 2. `color`\n 3. `size` Integer. The number of time samples to be plotted. See below.\n\nOptional arguments:  \n 4. `yorigin=0` These args provide scaling of Y axis values as per the `Curve`\n class.\n 5 `yexc=1`\n\nMethod:\n 1. `add` Arg `v` the value to be plotted. This should lie between -1 and +1\n unless scaling is applied.\n\nNote that there is little point in setting the `size` argument to a value\ngreater than the number of X-axis pixels on the graph. It will work but RAM\nand execution time will be wasted: the constructor instantiates an array of\nfloats of this size.\n\nEach time a data set arrives the graph should be cleared and a data value\nis added to each `TSequence` instance. The following (slightly simplified) is\ntaken from `gui/demos/plot.py` and simulates the slow arrival of sinusoidal\nvalues.\n\n```python\nclass TSeq(Screen):\n    def __init__(self):\n        super().__init__()\n        self.g = CartesianGraph(wri, 2, 2, xorigin = 10, fgcolor=GREEN,\n                                gridcolor=LIGHTGREEN, bdcolor=False)\n\n    def after_open(self):  # After graph has been drawn\n        self.reg_task(self.run(self.g), True)  # Cancel on screen change\n\n    async def run(self, g):\n        await asyncio.sleep_ms(0)\n        tsy = TSequence(g, YELLOW, 50)\n        tsr = TSequence(g, RED, 50)\n        t = 0\n        while True:\n            g.show()  # Redraw the empty graph\n            tsy.add(0.9*math.sin(t/10))\n            tsr.add(0.4*math.cos(t/10))  # Plot the new curves\n            await asyncio.sleep_ms(400)\n            t += 1\n```\n###### [Contents](./README.md#0-contents)\n\n# 8. Realtime applications\n\nThese notes assume an application based on `asyncio` that needs to handle events\noccurring in real time. There are two ways in which the GUI might affect real\ntime performance:\n* By imposing latency on the scheduling of tasks.\n* By making demands on processing power such that a critical task is starved of\nexecution.\n\nThe GUI uses `asyncio` internally and runs a number of tasks. Most of these are\nsimple and undemanding, the one exception being refresh. This has to copy the\ncontents of the frame buffer to the hardware, and runs continuously. The way\nthis works depends on the display type. On small displays with relatively few\npixels it is a blocking, synchronous method. On bigger screens such a method\nwould block for many tens of ms causing latency which would affect the\nresponsiveness of the user interface. The drivers for such screens have an\nasynchronous `do_refresh` method: this divides the refresh into a small number\nof segments, each of which blocks for a short period, preserving responsiveness.\n\nIn the great majority of applications this works well. For demanding cases a\nuser-accessible `Lock` is provided to enable refresh to be paused. This is\n`Screen.rfsh_lock`. Further, the behaviour of this `Lock` can be modified. By\ndefault the refresh task will hold the `Lock` for the entire duration of a\nrefresh. Alternatively the `Lock` can be held for the duration of the update of\none segment. In testing on a Pico with ILI9341 the `Lock` duration was reduced\nfrom 95ms to 11.3ms. If an application has a task which needs to be scheduled at\na high rate, this corresponds to an increase from 10Hz to 88Hz.\n\nIf an application acquires the lock, accesses to the touch controller will also\nbe paused. In systems with a shared SPI bus this guarantees that the application\nhas exclusive access to the bus. While the lock is held the application may use\nthe bus as required.\n\nThe mechanism for controlling lock behaviour is a method of the `ssd` instance:\n* `short_lock(v=None)` If `True` is passed, the `Lock` will be held briefly,\n`False` will cause it to be held for the entire refresh, `None` makes no change.\nThe method returns the current state. Note that only the larger display drivers\nsupport this method.\n\nThe following (pseudocode, simplified) illustrates this mechanism:\n```python\nclass Screen:\n    rfsh_lock = Lock()  # Refresh pauses until lock is acquired\n\n    @classmethod\n    async def auto_refresh(cls):\n        while True:\n            if display_supports_segmented_refresh and short_lock_is_enabled:\n                # At intervals yield and release the lock\n                await ssd.do_refresh(split, cls.rfsh_lock)\n            else:  # Lock for the entire refresh\n                await asyncio.sleep_ms(0)  # Let user code respond to event\n                async with cls.rfsh_lock:\n                    if display_supports_segmented_refresh:\n                        # Yield at intervals (retaining lock)\n                        await ssd.do_refresh(split)  # Segmented refresh\n                    else:\n                        ssd.show()  # Blocking synchronous refresh on small screen.\n```\nUser code can wait on the lock and, once acquired, run asynchronous code which\ncannot be interrupted by a refresh. This is normally done with an asynchronous\ncontext manager:\n```python\nasync with Screen.rfsh_lock:\n    # do something that can't be interrupted with a refresh\n```\nThe demo `refresh_lock.py` illustrates this mechanism, allowing refresh to be\nstarted and stopped. The demo also allows the `short_lock` method to be tested,\nwith a display of the scheduling rate of a minimal locked task. In a practical\napplication this rate is dependant on various factors. A number of debugging\naids exist to assist in measuring and optimising this. See\n[this doc](https://github.com/peterhinch/micropython-async/blob/master/v3/README.md).\n\nThe [micro-gui audio demo](https://github.com/peterhinch/micropython-micro-gui/blob/main/gui/demos/audio.py)\nprovides an example, where the `play_song` task gives priority to maintaining\nthe audio buffer. It does this by holding the lock for several iterations of\nbuffer filling before releasing the lock to allow a single refresh.\n\nSee [Appendix 4 GUI Design notes](./README.md#appendix-4-gui-design-notes) for\nthe reason for continuous refresh.  \n\n###### [Contents](./README.md#0-contents)\n\n# Appendix 1 Application design\n\n## Screen layout\n\nWidgets are positioned using absolute `row` and `col` coordinates. These may\noptionally be calculated using the metrics of other widgets. This facilitates\nrelative positioning which can make layouts easier to modify. Such layouts can\nalso automatically adapt to changes of fonts. To simplify this, all widgets\nhave the following bound variables, which should be considered read-only:\n\n * `height` As specified. Does not include border.\n * `width` Ditto.\n * `mrow` Maximum absolute row occupied by the widget (including border).\n * `mcol` Maximum absolute col occupied by the widget (including border).\n\nA further aid to metrics is the `Writer` method `.stringlen(s)`. This takes a\nstring as its arg and returns its length in pixels when rendered using the font\nof that `Writer` instance.\n\nThe `mrow` and `mcol` values enable other widgets to be positioned relative to\nthe one previously instantiated. In the cases of sliders, `Dial` and `Meter`\nwidgets these take account of space occupied by legends or labels.\n\nThe `aclock.py` and `linked_sliders.py` demos provide simple examples of this\napproach.\n\n## Use of graphics primitives\n\nSee demo [primitives.py](./gui/demos/primitives.py).\n\nThese notes are for those wishing to draw directly to the `Screen` instance.\nThis is done by providing the user `Screen` class with an `after_open()` method\nwhich is written to issue the display driver calls.\n\nThe following code instantiates two classes:\n```python\nimport touch_setup  # Create a display instance\nfrom gui.core.tgui import Screen, ssd, display\n```\nThe `ssd` object is an instance of the object defined in the display driver. It\nis a requirement that this is a subclass of `framebuf.FrameBuffer`. Hence `ssd`\nsupports all the graphics primitives provided by `FrameBuffer`. These may be\nused to draw on the `Screen`.\n\nThe `display` object has methods with the same names and args as those of\n`ssd`. These support greying out. So you can write (for example)\n```python\ndisplay.rect(10, 10, 50, 50, RED)\n```\nTo render in the correct colors it is wise ensure that greying out is disabled\nprior to calling `display` methods. This is done with\n```python\ndisplay.usegrey(False)\n```\nThere is little point in issuing `display.rect` as it confers no advantage over\n`ssd.rect`. However the `Display` class adds methods not currently available in\n`framebuf`. These are listed below.\n\n * `circle(self, x0, y0, r, color)` Calls `framebuf.ellipse`.\n * `fillcircle(self, x0, y0, r, color)` Calls `framebuf.ellipse`.\n * `clip_rect(self, x, y, w, h, color)` Rectangle with clipped corners.\n * `fill_clip_rect(self, x, y, w, h, color)`\n * `print_left(self, writer, x, y, txt, fgcolor=None, bgcolor=None, invert=False)`\n * `print_centred(self, writer, x, y, text, fgcolor=None, bgcolor=None, invert=False)`\n\nHopefully these are self explanatory. The `Display` methods use the `framebuf`\nconvention of `x, y` coordinates rather than the `row, col` system used by\nthe GUI interface.\n\nThe `primitives.py` demo provides a simple example.\n\n## Callbacks\n\nCallback functions should execute quickly, otherwise screen refresh will not\noccur until the callback is complete. Where a time consuming task is to be\ntriggered by a callback an `asyncio` task should be launched. In the following\nsample an `LED` widget is to be cycled through various colors in response to\na callback.\n```python\ndef callback(self, button, val):\n    self.reg_task(self.flash_led(), on_change=True)\n\nasync def flash_led(self):  # Will be cancelled if the screen ceases to be current\n    self.led.color(RED)\n    self.led.value(True)  # Turn on LED\n    await asyncio.sleep_ms(500)\n    self.led.color(YELLOW)\n    await asyncio.sleep_ms(500)\n    self.led.color(GREEN)\n    await asyncio.sleep_ms(500)\n    self.led.value(False)  # Turn it off. Task is complete.\n```\nThe `callback()` executes fast, with `flash_led()` running as a background task.\nThe use of [reg_task](./README.md#44-method)\nis because `flash_led()` is a method of the `Screen` object accessing bound\nobjects. The method ensures that the task is cancelled if the user closes or\noverlays the current screen. For more information on `asyncio`, see the\n[official docs](https://docs.micropython.org/en/latest/library/asyncio.html)\nand [tutorial](https://github.com/peterhinch/micropython-async/blob/master/v3/docs/TUTORIAL.md).\n\n###### [Contents](./README.md#0-contents)\n\n## Appendix 2 Freezing bytecode\n\nThis achieves a major saving of RAM. The correct way to do this is via a\n[manifest file](http://docs.micropython.org/en/latest/reference/manifest.html).\nThe first step is to clone MicroPython and prove that you can build and deploy\nfirmware to the chosen platform. Build instructions vary between ports and can\nbe found in the MicroPython source tree in `ports/\u003cport\u003e/README.md`.\n\nThe following is an example of how the entire\nGUI with fonts, demos and all widgets can be frozen on RP2.\n\nBuild script:\n```bash\ncd /mnt/qnap2/data/Projects/MicroPython/micropython/ports/rp2\nMANIFEST='/mnt/qnap2/Scripts/manifests/rp2_manifest.py'\n\nmake submodules\nmake clean\nif make -j 8 BOARD=PICO FROZEN_MANIFEST=$MANIFEST\nthen\n    echo Firmware is in build-PICO/firmware.uf2\nelse\n    echo Build failure\nfi\ncd -\n```\nManifest file contents (first line ensures that the default files are frozen):\n```python\ninclude(\"$(MPY_DIR)/ports/rp2/boards/manifest.py\")\nfreeze('/mnt/qnap2/Scripts/modules/rp2_modules')\n```\nThe directory `/mnt/qnap2/Scripts/modules/rp2_modules` contains only a symlink\nto the `gui` directory of the `micropython-touch` source tree. The freezing\nprocess follows symlinks and respects directory structures.\n\nIt is usually best to keep `touch_setup.py` unfrozen for ease of making\nchanges. I also keep the display driver and `boolpalette.py` in the filesystem\nas I have experienced problems freezing display drivers - but feel free to\nexperiment.\n\n## Appendix 3 Cross compiling\n\nThis addresses the case where a memory error occurs on import. There are better\nsavings with frozen bytecode, but cross compiling the main program module saves\nthe compiler from having to compile a large module on the target hardware. The\ncross compiler is documented [here](https://github.com/micropython/micropython/blob/master/mpy-cross/README.md).\n\nChange to the directory `gui/core` and issue:\n```bash\n$ /path/to/micropython/mpy-cross/mpy-cross tgui.py\n```\nThis creates a file `tgui.mpy`. It is necessary to move, delete or rename\n`tgui.py` as MicroPython loads a `.py` file in preference to `.mpy`.\n\nIf \"incorrect mpy version\" errors occur, the cross compiler should be\nrecompiled.\n\n###### [Contents](./README.md#0-contents)\n\n## Appendix 4 GUI Design notes\n\nA user (Toni Röyhy) raised the question of why refresh operates as a continuous\nbackground task, even when nothing has changed on screen. The concern was that\nit may result in needless power consumption. The following reasons apply:\n* It enables applications to draw on the screen using FrameBuffer primitives\nwithout the need to notify the GUI to perform a refresh.\n* There is a mechanism for stopping refresh in those rare occasions when it is\nnecessary.\n* Stopping refresh has no measurable effect on power consumption. This is\nbecause `asyncio` continues to schedule tasks even if refresh is paused. Overall\nCPU activity remains high. The following script may be used to confirm this.\n\n```py\nimport touch_setup  # Create a display instance\nfrom gui.core.tgui import Screen, ssd\n\nfrom gui.widgets import Label, Button, CloseButton, LED\nfrom gui.core.writer import CWriter\nimport gui.fonts.arial10 as arial10\nfrom gui.core.colors import *\nimport asyncio\n\nasync def stop_rfsh():\n    await Screen.rfsh_lock.acquire()\n\ndef cby(_):\n    asyncio.create_task(stop_rfsh())\n\ndef cbn(_):\n    Screen.rfsh_lock.release()  # Allow refresh\n\nclass BaseScreen(Screen):\n    def __init__(self):\n\n        super().__init__()\n        wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)\n        col = 2\n        row = 2\n        Label(wri, row, col, \"Refresh test\")\n        self.led = LED(wri, row, 80)\n        row = 50\n        Button(wri, row, col, text=\"Stop\", callback=cby)\n        col += 60\n        Button(wri, row, col, text=\"Start\", callback=cbn)\n        self.reg_task(self.flash())\n        CloseButton(wri)  # Quit\n\n    async def flash(self):  # Proof of stopped refresh\n        while True:\n            self.led.value(not self.led.value())\n            await asyncio.sleep_ms(300)\n\ndef test():\n    print(\"Refresh test.\")\n    Screen.change(BaseScreen)\n\ntest()\n```\n###### [Contents](./README.md#0-contents)\n\n## Appendix 5 Bus sharing\n\nBoards from Waveshare use the same SPI bus to access the display controller, the\ntouch controller, and an optional SD card. If an SD card is fitted, it is\npossible to mount this in `boot.py`: doing this enables the filesystem on the\nSD card to be managed at the Bash prompt using `mpremote`. There is a \"gotcha\"\nhere. For this to work reliably, the `CS\\` pins of the display controller and\nthe touch controller must be set high, otherwise bus contention on the `miso`\nline can occur. The following is an example of a `boot.py` for the 2.8\" Pico\nRes touch.\n```py\nfrom machine import SPI, Pin\nfrom sdcard import SDCard\nimport os\nBAUDRATE = 3_000_000  # Much higher rates seem OK, but may depend on card.\n# Initialise all CS\\ pins\ncst = Pin(16, Pin.OUT, value=1)  # Touch XPT2046\ncsd = Pin(9, Pin.OUT, value=1)  # Display ST7789\ncss = Pin(22, Pin.OUT, value=1)  # SD card\nspi = SPI(1, BAUDRATE, sck=Pin(10), mosi=Pin(11), miso=Pin(12))\nsd = SDCard(spi, css, BAUDRATE)\nvfs = os.VfsFat(sd)\nos.mount(vfs, \"/sd\")\n```\nAn application which is to access the SD card must ensure that the GUI is\nprevented from accessing the SPI bus for the duration of SD card access. This\nmay be done with an asynchronous context manager. When the context manager\nterminates, refresh and touch sensitivity will re-start.\n```py\nasync def read_data():\n    async with Screen.rfsh_lock:\n        # set up the SPI bus baudrate for the SD card\n        # read the data\n    await asyncio.sleep_ms(0)  # Allow refresh and touch to proceed\n    # Do anything else you need\n```\nSee section 8 for further background. Tested by @bianc104 in\n[iss 15](https://github.com/peterhinch/micropython-touch/issues/15#issuecomment-2397988225)\n\n###### [Contents](./README.md#0-contents)\n","funding_links":[],"categories":["Libraries"],"sub_categories":["Display"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterhinch%2Fmicropython-touch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpeterhinch%2Fmicropython-touch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterhinch%2Fmicropython-touch/lists"}