{"id":13500220,"url":"https://github.com/peterhinch/micropython-nano-gui","last_synced_at":"2025-05-15T14:04:28.827Z","repository":{"id":46708464,"uuid":"146632615","full_name":"peterhinch/micropython-nano-gui","owner":"peterhinch","description":"A lightweight MicroPython GUI library for display drivers based on framebuf class","archived":false,"fork":false,"pushed_at":"2025-04-07T09:38:42.000Z","size":1964,"stargazers_count":564,"open_issues_count":3,"forks_count":94,"subscribers_count":23,"default_branch":"master","last_synced_at":"2025-04-15T03:54:05.885Z","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,"zenodo":null}},"created_at":"2018-08-29T17:07:07.000Z","updated_at":"2025-04-12T07:19:16.000Z","dependencies_parsed_at":"2024-01-29T19:30:26.605Z","dependency_job_id":"3e9d03fc-046c-48e5-9368-d8e941f6b727","html_url":"https://github.com/peterhinch/micropython-nano-gui","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython-nano-gui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython-nano-gui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython-nano-gui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython-nano-gui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/peterhinch","download_url":"https://codeload.github.com/peterhinch/micropython-nano-gui/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254355334,"owners_count":22057354,"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-07-31T22:00:53.654Z","updated_at":"2025-05-15T14:04:28.805Z","avatar_url":"https://github.com/peterhinch.png","language":"Python","funding_links":[],"categories":["Python","Libraries"],"sub_categories":["Display"],"readme":"# MicroPython nano-gui\n\nA lightweight and minimal MicroPython GUI library for display drivers based on\nthe `FrameBuffer` class. It is portable between a range of MicroPython hosts\nand display devices. Various display technologies are supported, including\nsmall color and monochrome OLED's, color TFT's, ePaper and Sharp units.\n\nThe `nano-gui` library is display-only. A library supporting user input is\n[micro-gui](https://github.com/peterhinch/micropython-micro-gui); this comes at\na cost of a substantially greater RAM requirement. It supports all displays\navailable to nano-gui.\n\nThese images, most from OLED displays, fail to reproduce the quality of these\ndisplays. OLEDs are visually impressive displays with bright colors, wide\nviewing angle and extreme contrast. For some reason I find them hard to\nphotograph.  \n![Image](images/clock.png) The aclock.py demo.  \n\n![Image](images/fonts.png) Label objects in two fonts.  \n\n![Image](images/meters.png) One of the demos running on an Adafruit 1.27 inch\nOLED. The colors change dynamically with low values showing green, intermediate\nyellow and high red.  \n\n![Image](images/alevel.png) The alevel.py demo. The Pyboard was mounted\nvertically: the length and angle of the vector arrow varies as the\nPyboard is moved.\n\nThere is an optional [graph plotting module](./FPLOT.md) for basic\nCartesian and polar plots, also real time plotting including time series.\n\n![Image](images/sine.png) A sample image from the plot module.\n\nThese images from a TFT display illustrate the new widgets.  \n![Image](images/scale.JPG) The Scale widget. Capable of precision display of\nfloats as the notionally very long scale moves behind its small window.\n\n![Image](images/textbox1.JPG) The Textbox widget for scrolling text with word\nwrap or clipping.\n\n![Image](images/seismo.JPG) A mockup of a seismograph screen on an ePaper\ndisplay.\n\n![Image](images/round.JPG) Circular display using gc9a01 controller.\n\n# Contents\n\n 1. [Introduction](./README.md#1-introduction)  \n  1.1 [Change log](./README.md#11-change-log)  \n  1.2 [Description](./README.md#12-description)  \n  1.3 [Quick start](./README.md#13-quick-start) Run without actually installing it.  \n  \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;1.3.1 [Quick install](./README.md#131-quick-install)  \n  1.4 [A performance boost](./README.md#14-a-performance-boost)  \n 2. [Files and Dependencies](./README.md#2-files-and-dependencies)  \n  2.1 [Files](./README.md#21-files)  \n  \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;2.1.1 [Core files](./README.md#211-core-files)  \n  \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;2.1.2 [Demo Scripts](./README.md#212-demo-scripts)  \n  \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;2.1.3 [Fonts](./README.md#213-fonts)  \n  \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;2.1.4 [Hardware setup examples](./README.md#214-hardware-setup-examples)  \n  2.2 [Dependencies](./README.md#21-dependencies)  \n  2.3 [Verifying hardware configuration](./README.md#23-verifying-hardware-configuration)  \n 3. [The nanogui module](./README.md#3-the-nanogui-module)  \n  3.1 [Application Initialisation](./README.md#31-application-initialisation) Initial setup and refresh method.  \n  \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;3.1.1 [User defined colors](./README.md#311-user-defined-colors)  \n  \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;3.1.2 [Monochrome displays](./README.md#312-monochrome-displays) A slight \"gotcha\" with ePaper.  \n  \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;3.1.3 [Display update mechanism](./README.md#313-display-update-mechanism) How updates are managed.  \n  \u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;3.1.4 [ePaper displays](./README.md#314-epaper-displays) New developments in ePaper.  \n  3.2 [Label class](./README.md#32-label-class) Dynamic text at any screen location.  \n  3.3 [Meter class](./README.md#33-meter-class) A vertical panel meter.  \n  3.4 [LED class](./README.md#34-led-class) Virtual LED of any color.  \n  3.5 [Dial and Pointer classes](./README.md#35-dial-and-pointer-classes) Clock\n  or compass style display of one or more pointers.  \n  3.6 [Scale class](./README.md#36-scale-class) Linear display with wide dynamic range.  \n  3.7 [Class Textbox](./README.md#37-class-textbox) Scrolling text display.  \n 4. [ESP8266](./README.md#4-esp8266) This can work. Contains information on\n minimising the RAM and flash footprints of the GUI.  \n[Appendix 1 Freezing bytecode](./README.md#appendix-1-freezing-bytecode) Optional way to save RAM.  \n[Appendix 2 Round displays](./README.md#appendix-2-round-displays) Alternative hardware check script.\n\n#### [Supported displays](./DISPLAYS.md)\n\n#### [Device driver document.](./DRIVERS.md)\n\n#### [Graph plotting module.](./FPLOT.md)\n\n#### [The extras directory.](./extras/README.md)\n\nThe `extras` directory contains further widgets back-ported from\n[micro-gui](https://github.com/peterhinch/micropython-micro-gui) plus further\ndemos and information. The aim is to avoid this document becoming over long and\ndaunting to new users.\n\n# 1. Introduction\n\nThis library provides a limited set of GUI objects (widgets) for displays whose\ndisplay driver is subclassed from the `FrameBuffer` class. The drivers can have\nminimal code as graphics primitives are supplied by the `FrameBuffer` class.\n\nCompatible and tested displays are detailed [here](./DISPLAYS.md). The\n[device driver doc](./DRIVERS.md) provides guidance on selecting the right\ndriver for your display, platform and application.\n\nThe GUI is cross-platform. The device driver doc explains how to configure it\nfor a given display and MicroPython host by adapting a single small file. The\nGUI supports multiple displays attached to a single target, but bear in mind\nthe RAM requirements for multiple frame buffers. The GUI has been tested on\nPyboard 1.1, Pyboard D, Raspberry Pi Pico and on the ESP32 reference board\nwithout SPIRAM. Running on ESP8266 is possible but frozen bytecode must be used\nowing to its restricted RAM - see\n[Appendix 1 Freezing bytecode](./README.md#appendix-1-freezing-bytecode).\n\nIt uses synchronous code but is compatible with `asyncio`. Some demo programs\nillustrate this. Code is standard MicroPython, but some device drivers use the\n`native` and `viper` decorators.\n\nThe GUI is display-only and lacks provision for user input. Authors of\napplications requiring touch should consider the touch GUI's for the following\ndisplays:\n * [Official lcd160cr](https://github.com/peterhinch/micropython-lcd160cr-gui)\n * [RA8875 large displays](https://github.com/peterhinch/micropython_ra8875)\n * [SSD1963 large displays](https://github.com/peterhinch/micropython-tft-gui)\n\nFor historical reasons and to ensure consistency, code and documentation for\nmy GUI's employ the American spelling of `color`.\n\n## 1.1 Change log\n\n22 May 2024 Support circular displays with gc9a01 controller.  \n15 Mar 2023 Driver update to 4.2 inch Waveshare ePpaper display.  \n12 Feb 2023 Add support for sh1106 driver. Fix color compatibility of SSD1306.  \n5 Sep 2022 Add support for additional Pico displays.  \n8 Aug 2022 Typo and grammar fixes from @bfiics.  \n10 May 2022 Support Waveshare Pi Pico displays.  \n7 Sep 2021 Code reduction and faster color text display. Color use now requires\nfirmware V1.17 or later.  \n26 Aug 2021 Support [PR7682](https://github.com/micropython/micropython/pull/7682)\nfor fast text rendering.  \n25 Apr 2021 Support TTGO T-Display.  \n26 Mar 2021 Add ST7789. Alter asyncio support on ili9341.  \n\n## 1.2 Description\n\nWidgets are intended for the display of data from physical devices such as\nsensors. They are drawn using graphics primitives rather than icons to minimise\nRAM usage. It also enables them to be efficiently rendered at arbitrary scale by\nhosts with restricted processing power. The approach also enables widgets to\nmaximise information in ways that are difficult with icons, in particular using\ndynamic color changes in conjunction with moving elements.\n\nCopying the contents of the frame buffer to the display is relatively slow. The\ntime depends on the size of the frame buffer and the interface speed, but the\nlatency may be too high for applications such as games. For example the time to\nupdate a 128x128x8 color ssd1351 display on a Pyboard 1.0 is 41ms.\n\nDrivers based on `FrameBuffer` must allocate contiguous RAM for the buffer. To\navoid 'out of memory' errors it is best to instantiate the display before\nimporting other modules. The example `color_setup` files illustrate this.\n\n## 1.3 Quick start\n\nAn easy way to start is to use `mpremote` which allows a directory on your PC\nto be mounted on the host. In this way the filesystem on the host is left\nunchanged. This is at some cost in loading speed, especially on ESP32. If\nadopting this approach, you will need to edit the `color_setup.py` file on\nyour PC to match your hardware. Install `mpremote` with:\n```bash\n$ pip3 install mpremote\n```\nClone the repo to your PC with:\n```bash\n$ git clone https://github.com/peterhinch/micropython-nano-gui\n$ cd micropython-nano-gui\n```\nAs supplied, `color_setup.py` assumes a Pyboard (1.x or D) connected to an\nAdafruit 1.27\" OLED as specified in that file. If that doesn't correspond to\nyour hardware, it should be edited to suit. See example files in the\n`setup_examples` directory.\n```bash\n$ mpremote mount .\n```\nThis should provide a REPL. Run a demo:\n```python\n\u003e\u003e\u003e import gui.demos.aclock\n```\nThe directory `setup_examples` has examples of files to match various displays\nand targets. If one of these matches your hardware, it may be copied to the\nroot as `color_setup.py`.\n\nNote that the `gui.demos.aclock.py` demo comprises 38 lines of actual code.\nThis stuff is easier than you might think.\n\n### 1.3.1 Quick install\n\nOn networked hardware this is done with `mip` which is included in recent\nfirmware. On non-networked hardware this is done using the official\n[mpremote utility](http://docs.micropython.org/en/latest/reference/mpremote.html)\nwhich should be installed on the PC as described above.\n\n#### Networked hardware\n\nThe easy approach is to copy the entire GUI to your hardware using `mip`\n```python\n\u003e\u003e\u003e import mip\n\u003e\u003e\u003e mip.install(\"github:peterhinch/micropython-nano-gui\")\n```\nSubstantial pruning can be done to eliminate unused fonts, widgets and demos.\nThe appropriate driver for the display hardware is installed as follows\n(example is for ST7789):\n```python\n\u003e\u003e\u003e mip.install(\"github:peterhinch/micropython-nano-gui/drivers/st7789\")\n```\nThe last part of the addresss (`st7789`) is the name of the directory holding\ndrivers for the display in use.\n\nAfter editing `color_setup.py` as discussed above it should be copied to the\ntarget hardware with:\n```bash\n$ mpremote cp color_setup.py :\n```\n#### Non networked hardware\n\nInstallation is as per networked hardware except that `mip` on the target is\nreplaced by `mpremote mip` on the PC:\n```bash\n$ mpremote mip install \"github:peterhinch/micropython-nano-gui\"\n$ mpremote mip install \"github:peterhinch/micropython-nano-gui/drivers/st7789\"\n```\n\n## 1.4 A performance boost\n\nUse of color displays now requires firmware V1.17 or later which offered a\nperformance boost. If upgrading `nano-gui` from an installation which pre-dated\nV1.17 the display driver and GUI core files should be updated and the new file\n`drivers/boolpalette.py` must exist.\n\n###### [Contents](./README.md#contents)\n\n# 2. Files and Dependencies\n\nOn monochrome displays firmware should be V1.13 or later. On the Pi Pico\nfirmware should be V1.15 or later. For color displays it should be V1.17 or\nlater.\n\nInstallation comprises copying the `gui` and `drivers` directories, with their\ncontents, plus a hardware configuration file, to the target. The directory\nstructure on the target must match that in the repo. This consumes about 300KiB\nof flash.\n\nFilesystem space may be conserved by copying only the required driver from\n`drivers`, but the directory path to that file must be retained. For example,\nfor SSD1351 displays only the following are actually required:  \n`drivers/ssd1351/ssd1351.py`, `drivers/ssd1351/__init__.py`.\n\nThe small `color_setup.py` file contains all hardware definitions (for color or\nmonochrome displays). This is the only file which will require editing to match\nthe display and its wiring. For information on how to do this, see\n[the drivers document](./DRIVERS.md#1-introduction).\n\n## 2.1 Files\n\n### 2.1.1 Core files\n\nThe root directory contains an example setup file `color_setup.py` for a color\nOLED display. Other examples may be found in the `setup_examples` directory.\nThese are templates for adaptation: only one file is copied to the target. On\nthe target the file should be named `color_setup.py` and put in the root of the\nfilesystem.\n\nThe chosen template will need to be edited to match the display in use, the\nMicroPython target and the electrical connections between display and target.\nElectrical connections are detailed in the template source.\n * `color_setup.py` Hardware setup for the display. As written supports an\n SSD1351 display connected to a Pyboard.\n\nThe `gui/core` directory contains the GUI core and its principal dependencies:\n\n * `nanogui.py` The library.\n * `writer.py` Module for rendering Python fonts.\n * `fplot.py` The graph plotting module.\n * `colors.py` Color constants.\n\n###### [Contents](./README.md#contents)\n\n### 2.1.2 Demo scripts\n\nThe `gui/demos` directory contains test/demo scripts.\n\nDemos for small displays:\n * `mono_test.py` Tests/demos using the official SSD1306 or SH1106 driver for\n monochrome 128*64 OLED displays.\n * `color96.py` Tests/demos for the Adafruit 0.96 inch color OLED.\n\nDemos for larger displays.\n * `color15.py` Demonstrates a variety of widgets. Cross platform.\n * `aclock.py` Analog clock demo. Cross platform.\n * `alevel.py` Spirit level using Pyboard accelerometer.\n * `fpt.py` Plot demo. Cross platform.\n * `scale.py` A demo of the `Scale` widget. Cross platform. Uses `asyncio`.\n * `asnano_sync.py` Two Pyboard specific demos using the GUI with `asyncio`.\n * `asnano.py` Could readily be adapted for other targets.\n * `tbox.py` Demo `Textbox` class. Cross-platform.\n * `round.py` Demo for 240*240 circular displays.\n\nDemos for ePaper displays:\n * `epd_async.py` Demo of asynchronous code on an eInk display. Needs a large display.\n * `epd29_sync.py` Demo for Adafruit 2.9\" eInk display: emulates a seismograph.\n * `epd29_async.py` Asynchronous demo for Adafruit 2.9\" eInk display.\n * `epd29_lowpower.py` Micropower demo for Adafruit 2.9\" eInk display. This doc\n [Micropower use](./DRIVERS.md#715-micropower-use) should be read before\n attempting to run this.\n\nDemos for Sharp displays:\n * `sharptest.py` Basic functionality check.\n * `clocktest.py` Digital and analog clock demo.\n * `clock_batt.py` Low power demo of battery operated clock.\n\nUsage with `asyncio` is discussed [here](./ASYNC.md). In summary the GUI works\nwell with `asyncio` but the blocking which occurs during transfer of the\nframebuffer to the display may affect more demanding applications. Some display\ndrivers have an additional asynchronous refresh method. This may optionally be\nused to mitigate the resultant latency.\n\n###### [Contents](./README.md#contents)\n\n### 2.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: the [ESP8266](./README.md#4-esp8266) provides an\nillustration.\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### 2.1.4 Hardware setup examples\n\nThe `setup_examples` directory contains example setup files for various hardware.\nThese are templates which may be adapted to suit the hardware in use, then\ncopied to the hardware root as `color_setup.py`. Example files:\n\n * `ssd1306_pyb.py` Setup file for monochrome displays using the official\n driver. Supports hard or soft SPI or I2C connections.\n * `ssd1106_spi_pico.py` Setup file for monochrome displays.\n Supports hard or soft SPI or I2C connections.\n * `ssd1351_esp32.py` As written supports an ESP32 connected to a 128x128 SSD1351\n display. After editing to match the display and wiring, it should be copied to\n the target as `/pyboard/color_setup.py`.\n * `ssd1351_esp8266.py` Similar for [ESP8266](./README.md#4-esp8266). Usage is\n somewhat experimental.\n * `st7735r_pyb.py` Assumes a Pyboard with an\n [Adafruit 1.8 inch TFT display](https://www.adafruit.com/product/358).\n * `st7735r144_pyb.py` For a Pyboard with an\n [Adafruit 1.44 inch TFT display](https://www.adafruit.com/product/2088).\n * `ili9341_esp32.py` A 240*320 ILI9341 display on ESP32.\n * `st7789_pico.py` Example with SSD7789 driver and Pi Pico host.\n * `st7789_ttgo.py` Setup for the TTGO T-Display device.\n * `waveshare_pyb.py` 176*274 ePaper display on Pyboard.\n * `epd29_pyb_sync.py` Adafruit 2.9 inch ePaper display for synchronous code.\n * `epd29_pyb_async.py` Adafruit 2.9 inch ePaper display: `asyncio` applications.\n\n## 2.2 Dependencies\n\nThe source tree now includes all dependencies. These are listed to enable users\nto check for newer versions:\n\n * [writer.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/writer.py)\n Provides text rendering of Python font files.\n\nA copy of the official driver for OLED displays using the SSD1306 chip is\nprovided. The official file is here:\n * [SSD1306 driver](https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/display/ssd1306/ssd1306.py).\n\nA copy of the unofficial driver for OLED displays using the SH1106 chip is\nprovided. The unofficial file is here:\n * [SH1106 driver](https://github.com/robert-hh/SH1106).\n\nDisplays based on the Nokia 5110 (PCD8544 chip) require this driver. It is not\nin this repo but may be found here:\n * [PCD8544/Nokia 5110](https://github.com/mcauser/micropython-pcd8544.git)\n\n###### [Contents](./README.md#contents)\n\n## 2.3 Verifying hardware configuration\n\nThis script performs a basic check that the `color_setup.py` file matches the\nhardware, that (on color units) all three primary colors can be displayed and\nthat pixels up to the edges of the display can be accessed. It is highly\nrecommended that this be run on any new installation.\n```python\nfrom color_setup import ssd  # Create a display instance\nfrom gui.core.colors import RED, BLUE, GREEN\nfrom gui.core.nanogui import refresh\nrefresh(ssd, True)  # Initialise and clear display.\n# Uncomment for ePaper displays\n# ssd.wait_until_ready()\nssd.fill(0)\nssd.line(0, 0, ssd.width - 1, ssd.height - 1, GREEN)  # Green diagonal corner-to-corner\nssd.rect(0, 0, 15, 15, RED)  # Red square at top left\nssd.rect(ssd.width -15, ssd.height -15, 15, 15, BLUE)  # Blue square at bottom right\nrefresh(ssd)\n```\nFor round displays please see\n[Appendix 2 Round displays](./README.md#appendix-2-round-displays) for a\nsuitable hardware check script.\n\n###### [Contents](./README.md#contents)\n\n# 3. The nanogui module\n\nThe GUI supports a variety of widgets, some of which include text elements. The\ncoordinates of a widget are those of its top left corner. If a border is\nspecified, this is drawn outside of the limits of the widgets with a margin of\n2 pixels. If the widget is placed at `[row, col]` the top left hand corner of\nthe border is at `[row-2, col-2]`.\n\nWhen a widget is drawn or updated (typically with its `value` method) it is not\nimmediately displayed. To update the display `nanogui.refresh` is called: this\nenables multiple updates to the `FrameBuffer` contents before once copying the\nbuffer to the display. Postponement enhances performance providing a visually\ninstant update.\n\nText components of widgets are rendered using the `Writer` (monochrome) or\n`CWriter` (colour) classes.\n\n## 3.1 Application Initialisation\n\nThe GUI is initialised by issuing:\n```python\nfrom color_setup import ssd\n```\nThis defines the hardware as described in [the drivers document](./DRIVERS.md#1-introduction).\n\nA typical application then imports `nanogui` modules and clears the display:\n```python\nfrom gui.core.nanogui import refresh\nfrom gui.widgets.label import Label  # Import any widgets you plan to use\nfrom gui.widgets.dial import Dial, Pointer\nrefresh(ssd, True)  # Initialise and clear display.\n```\n\nInitialisation of color text display follows. For each font a `CWriter` instance\nis created:\n```python\nfrom gui.core.writer import CWriter  # Renders color text\nfrom gui.fonts import arial10  # A Python Font\nfrom gui.core.colors import *  # Standard color constants\n\nCWriter.set_textpos(ssd, 0, 0)  # In case previous tests have altered it\n # Instantiate any CWriters to be used (one for each font)\nwri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)  # Colors are defaults\n# wri = Writer(ssd, arial10, verbose=False)  # Monochrome display uses Writer\nwri.set_clip(True, True, False)\n```\n\nInitialisation of monochrome text display follows. For each font a `Writer` instance\nis created:\n```python\nfrom gui.core.writer import Writer  # Renders color text\nfrom gui.fonts import arial10\n\nWriter.set_textpos(ssd, 0, 0)  # In case previous tests have altered it\n# Instantiate any Writers to be used (one for each font)\nwri = Writer(ssd, arial10, verbose=False)  # Monochrome display uses Writer\nwri.set_clip(True, True, False)\n```\n\nCalling `nanogui.refresh` on startup sets up and clears the display. The method\nwill subsequently be called whenever a refresh is required. It takes two args:\n 1. `device` The display instance (the GUI supports multiple displays).\n 2. `clear=False` If set `True` the display will be blanked; it is also\n blanked when a device is refreshed for the first time.\n\n### 3.1.1 User defined colors\n\nThe file `gui/core/colors.py` defines standard color constants which may be\nused with any display driver. This section describes how to change these or\nto create additional colors.\n\nMost of the color display drivers define colors as 8-bit or larger values.\nIn such cases colors may be created and assigned to variables as follows:\n```python\nfrom color_setup import SSD\nPALE_YELLOW = SSD.rgb(150, 150, 0)\n```\nThe GUI also provides drivers with 4-bit color to minimise RAM use. Colors are\nassigned to a lookup table having 16 entries. The frame buffer stores 4-bit\ncolor values, which are converted to the correct color depth for the hardware\nwhen the display is refreshed.\n\nOf the possible 16 colors 13 are assigned in `gui/core/colors.py`, leaving\ncolor numbers 12, 13 and 14 free. Any color can be assigned as follows:\n```python\nfrom gui.core.colors import *  # Imports the create_color function\nPALE_YELLOW = create_color(12, 150, 150, 0)\n```\nThis creates a color `rgb(150, 150, 0)` assigns it to \"spare\" color number 12\nthen sets `PALE_YELLOW` to 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 code is to be ported\nbetween 4-bit and other drivers, use `create_color()` for all custom colors:\nit will produce appropriate behaviour. See the `vari_fields` function in the\ndemo `color15.py` for an example.\n\n### 3.1.2 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. Consequently demos written for\ncolor displays will work on monochrome units.\n\nOn a monochrome OLED display the background is black and the foreground is\nwhite. This contrasts with ePaper units where the foreground is black on a\nwhite background. The display drivers perform this inversion so that user\ncode renders as expected on color, mono OLED or ePaper units.\n\nAt the bit level `1` represents the foreground. This is white on an emitting\ndisplay such as an OLED. On a Sharp display it indicates reflection. On an\nePaper display it represents black. Given that `1` is the foreground color,\nexplicitly specifying `BLACK` on an ePaper will produce `0` as black has (very)\nlow saturation. In this context the resultant physically white background\ncolor may come as a surprise.\n\nIn general the solution is to leave color settings at default.\n\n### 3.1.3 Display update mechanism\n\nA typical application comprises various widgets displaying user data. When a\nwidget's `value` method is called, the framebuffer's contents are updated to\nreflect the widget's current state. The framebuffer is transferred to the\nphysical hardware when `refresh(device)` is called. This allows multiple\nwidgets to be refreshed at the same time. It also minimises processor overhead:\n`.value` is generally fast, while `refresh` is slow because of the time taken\nto transfer an entire buffer over SPI.\n\n### 3.1.4 ePaper displays\n\nOn ePaper displays `refresh` is both slow and visually intrusive, with the\ndisplay flashing repeatedly. This made them unsatisfactory for displaying\nrapidly changing information. There is a new breed of ePaper display supporting\neffective partial updates notably\n[the Waveshare Pico paper 4.2](https://www.waveshare.com/pico-epaper-4.2.htm).\nThis can be used in such roles and is discussed  in\n[EPD Asynchronous support](./DRIVERS.md#6-epd-asynchronous-support).\n\n###### [Contents](./README.md#contents)\n\n## 3.2 Label class\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 displayed until `.value()` is called. Intended for dynamic text fields.\n 5. `invert=False` Display in inverted or normal style.\n 6. `fgcolor=None` Optionally overrides the `Writer` colors.\n 7. `bgcolor=None`\n 8. `bdcolor=False` If `False` no border is displayed. If `None` a border is\n shown in the `Writer` foreground color. If a color is passed, it is used.\n 9. `align=ALIGN_LEFT` By default text in labels is left aligned. Options are\n `ALIGN_RIGHT` and `ALIGN_CENTER`. These options can only take effect if a\n large enough field width is passed to `text`.\n\nThe constructor displays the string at the required location.\n\nMethods:\n 1. `value` Redraws the label. This takes the following args:\n    * `text=None` The text to display. If `None` displays the 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    * `align=None` Use alignment specified in constructor unless one of the\n    module constants is passed.  \n Returns the current text string.  \n 2. `show` No args. (Re)draws the label. Primarily for internal use by GUI.\n\nModule Constants:\n * `ALIGN_LEFT=0`\n * `ALIGN_RIGHT=1`\n * `ALIGN_CENTER=2`\n\nIf populating 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 color_setup import ssd  # Create a display instance\nfrom gui.core.nanogui import refresh\nfrom gui.core.writer import CWriter\nfrom gui.core.colors import *\n\nfrom gui.widgets.label import Label\nimport gui.fonts.freesans20 as freesans20\n\nrefresh(ssd)  # Initialise and clear display.\nCWriter.set_textpos(ssd, 0, 0)  # In case previous tests have altered it\nwri = CWriter(ssd, freesans20, GREEN, BLACK, verbose=False)\nwri.set_clip(True, True, False)\n\n# End of boilerplate code. This is our application:\nLabel(wri, 2, 2, 'Hello world!')\nrefresh(ssd)\n```\n\n###### [Contents](./README.md#contents)\n\n## 3.3 Meter class\n\nThis provides a vertical linear meter display of values scaled between 0.0 and\n1.0.\n\nConstructor positional args:\n\n 1. `writer` The `Writer` instance (font and screen) to use.\n 2. `row` Location on screen.\n 3. `col`\n\nKeyword only args:\n\n 4. `height=50` Height of meter.\n 5. `width=10` Width.\n 6. `fgcolor=None` Foreground color: if `None` the `Writer` default is used.\n 7. `bgcolor=None` Background color, as per foreground.\n 8. `ptcolor=None` Color of meter pointer or bar. Default is foreground color.\n 9. `bdcolor=False` If `False` no border is displayed. If `None` a border is\n shown in the `Writer` foreground color. If a color is passed, it is used.\n 10. `divisions=5` No. of graduations to show.\n 11. `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 12. `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 13. `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 14. `value=None` Initial value. If `None` the meter will not be drawn until\n its `value()` method is called.\n\nMethods:\n 1. `value` Args: `n=None, color=None`.\n    * `n` should be a float in range 0 to 1.0. Causes the meter to be updated.\n    Out of range values are constrained. If `None` is passed the meter is not\n    updated.\n    * `color` Updates the color of the bar or line if a value is also passed.\n    `None` causes no change.\n Returns the current value.\n 2. `text` Updates the label if present (otherwise throws a `ValueError`). Args:\n    * `text=None` The text to display. If `None` displays the 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. `show` No args. (Re)draws the meter. Primarily for internal use by GUI.\n\n###### [Contents](./README.md#contents)\n\n## 3.4 LED class\n\nThis is a virtual LED whose color may be altered dynamically.\n\nConstructor positional args:\n 1. `writer` The `Writer` instance (font and screen) to use.\n 2. `row` Location on screen.\n 3. `col`\n\nKeyword only args:\n\n 4. `height=12` Height of LED.\n 5. `fgcolor=None` Foreground color: if `None` the `Writer` default is used.\n 6. `bgcolor=None` Background color, as per foreground.\n 7. `bdcolor=False` If `False` no border is displayed. If `None` a border is\n shown in the `Writer` foreground color. If a color is passed, it is used.\n 8. `label=None`  A text string will cause a `Label` to be drawn below the\n LED. An integer will create a `Label` of that width for later use.\n\nMethods:\n 1. `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 2. `text` Updates the label if present (otherwise throws a `ValueError`). Args:\n    * `text=None` The text to display. If `None` displays the 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. `show` No args. (Re)draws the LED. Primarily for internal use by GUI.\n\n###### [Contents](./README.md#contents)\n\n## 3.5 Dial and Pointer classes\n\nA `Dial` is a circular display capable of displaying a number of vectors; each\nvector is represented by a `Pointer` instance. The format of the display may be\nchosen to resemble an analog clock or a compass. In the `CLOCK` case a pointer\nresembles a clock's hand extending from the centre towards the periphery. In\nthe `COMPASS` case pointers are chevrons extending equally either side of the\ncircle 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 positional args:\n 1. `writer` The `Writer` instance (font and screen) to use.\n 2. `row` Location on screen.\n 3. `col`\n\nKeyword only args:\n\n 4. `height=50` Height and width of dial.\n 5. `fgcolor=None` Foreground color: if `None` the `Writer` default is used.\n 6. `bgcolor=None` Background color, as per foreground.\n 7. `bdcolor=False` If `False` no border is displayed. If `None` a border is\n shown in the `Writer` foreground color. If a color is passed, it is used.\n 8. `ticks=4` No. of gradutions to show.\n 9. `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 10. `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 11. `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\nWhen a `Pointer` is instantiated it is assigned to the `Dial` by the `Pointer`\nconstructor.\n\nBound variable:\n\n 1. `label` The `Label` instance if one was created.\n\n### Pointer class\n\nConstructor arg:\n 1. `dial` The `Dial` instance on which it is to be displayed.\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 2. `show` No args. (Re)draws the control. Primarily for internal use by GUI.\n\nTypical usage (`ssd` is the device and `wri` is the current `Writer`):\n```python\ndef clock(ssd, wri):\n    # Border in Writer foreground color:\n    dial = Dial(wri, 5, 5, ticks = 12, bdcolor=None)\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 aclock.py for an actual clock\n    for _ in range(80):\n        utime.sleep_ms(200)\n        mins.value(mins.value() * dm, RED)\n        hrs.value(hrs.value() * dh, YELLOW)\n        refresh(ssd)\n```\n\n###### [Contents](./README.md#contents)\n\n## 3.6 Scale class\n\nThis displays floating point data having a wide dynamic range. It is modelled\non old radios where a large scale scrolls past a small window having a fixed\npointer. This enables a scale with (say) 200 graduations (ticks) to readily be\nvisible on a small display, with sufficient resolution to enable the user to\ninterpolate between ticks. Default settings enable estimation of a value to\nwithin about +-0.1%.\n\nLegends for the scale are created dynamically as it scrolls past the window.\nThe user may control this by means of a callback. The example `lscale.py`\nillustrates a variable with range 88.0 to 108.0, the callback ensuring that the\ndisplay legends match the user variable. A further callback enables the scale's\ncolor to change over its length or in response to other circumstances.\n\nThe scale displays floats in range -1.0 \u003c= V \u003c= 1.0.\n\nConstructor positional args:  \n 1. `writer` The `Writer` instance (font and screen) to use.\n 2. `row` Location on screen.\n 3. `col`  \n\nKeyword only arguments (all optional):\n * `ticks=200` Number of \"tick\" divisions on scale. Must be divisible by 2.\n * `legendcb=None` Callback for populating scale legends (see below).\n * `tickcb=None` Callback for setting tick colors (see below).\n * `height=0` Pass 0 for a minimum height based on the font height.\n * `width=200`\n * `bdcolor=None` Border color. If `None`, `fgcolor` will be used.\n * `fgcolor=None` Foreground color. Defaults to system color.\n * `bgcolor=None` Background color defaults to system background.\n * `pointercolor=None` Color of pointer. Defaults to `.fgcolor`.\n * `fontcolor=None` Color of legends. Default `fgcolor`.\n\nMethod:\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\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 the\n`lscale.py` demo shows 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#contents)\n\n## 3.7 Class Textbox\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. The\nwidget supports fixed and variable pitch fonts.\n```python\nfrom gui.widgets.textbox import Textbox\n```\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 most widgets the border is drawn two pixels beyond the control's\n boundary.\n\nKeyword only arguments:\n * `bdcolor=None` Border color. If `None`, `fgcolor` will be used.\n * `fgcolor=None` Color of border. Defaults to system color.\n * `bgcolor=None` Background color of object. Defaults to system background.\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\nMethods:\n * `append` Args `s, ntrim=None, line=None` Append the string `s` to the\n display and scroll up as required to show it. By default only the number of\n lines which will fit on screen are retained. If an integer `ntrim=N` is\n passed, only the last N lines are retained; `ntrim` may be greater than can be\n shown in the control, hidden lines being accessed by scrolling.  \n If an integer (typically 0) is passed in `line` the display will scroll to\n show that line.\n * `scroll` Arg `n` Number of lines to scroll. A negative number scrolls up. If\n scrolling would achieve nothing because there are no extra lines to display,\n nothing will happen. Returns `True` if scrolling occurred, otherwise `False`.\n * `value` No args. Returns the number of lines of text stored in the widget.\n * `clear` No args. Clears all lines from the widget and refreshes the display.\n * `goto` Arg `line=None` Fast scroll to a line. By default shows the end of\n the text. 0 shows the start.\n\nFast updates:  \nRendering text to the screen is relatively slow. To send a large amount of text\nthe fastest way is to perform a single `append`. Text may contain newline\n(`'\\n'`) characters as required. In that way rendering occurs once only.\n\n`ntrim`__\nIf text is regularly appended to a `Textbox` its buffer grows, using RAM. The\nvalue of `ntrim` sets a limit to the number of lines which are retained, with\nthe oldest (topmost) being discarded as required.\n\n###### [Contents](./README.md#contents)\n\n# 4. ESP8266\n\nSome personal observations on successful use with an ESP8266.\n\nI chose an [Adafruit 128x128 OLED display](https://www.adafruit.com/product/1431)\nto represent the biggest display I thought the ESP8266 might support. I\nreasoned that, if this can be made to work, smaller or monochrome displays\nwould present no problem.\n\nThe ESP8266 is a minimal platform with typically 36.6KiB of free RAM. The\nframebuffer for a 128*128 OLED requires 16KiB of contiguous RAM (the display\nhardware uses 16 bit color but my driver uses an 8 bit buffer to conserve RAM).\nThe 4-bit driver halves this size.\n\nA further issue is that, by default, ESP8266 firmware does not support complex\nnumbers. This rules out the plot module and the `Dial` widget. It is possible\nto turn on complex support in the build, but I haven't tried this.\n\nI set out to run the `scale.py` and `textbox.py` demos as these use `asyncio`\nto create dynamic content, and the widgets themselves are relatively complex.\n\nI froze a subset of the `drivers` and the `gui` directories. A subset minimises\nthe size of the firmware build and eliminates modules which won't compile due\nto the complex number issue. The directory structure in my frozen modules\ndirectory matched that of the source. This was the structure of my frozen\ndirectory before I added the 4 bit driver:  \n![Image](images/esp8266_tree.JPG)\n\nI erased the flash, built and installed the new firmware. Finally I copied\n`setup_examples/esp8266_setup.py` to `/pyboard/color_setup.py`. This could have\nbeen frozen but I wanted to be able to change pins if required.\n\nBoth demos worked perfectly.\n\nI modified the demos to regularly report free RAM. `scale.py` reported 10480\nbytes, `tbox.py` reported 10512 bytes, sometimes more, as the demo progressed.\nWith the 4 bit driver `scale.py` reported 18112 bytes. In conclusion I think\nthat applications of moderate complexity should be feasible.\n\n###### [Contents](./README.md#contents)\n\n## Appendix 1 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 GUI with fonts, demos and all\nwidgets 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-micro-gui` source tree. The freezing\nprocess follows symlinks and respects directory structures.\n\nIt is usually best to keep `hardware_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###### [Contents](./README.md#contents)\n\n## Appendix 2 Round displays\n\nThe normal test script is unsuitable as the rectangles are off-screen. Please\npaste this at the REPL to verify hardware and display orientation:\n```Python\nfrom color_setup import ssd  # Create a display instance\nfrom gui.core.colors import RED, BLUE, GREEN\nfrom gui.core.nanogui import refresh, circle\nrefresh(ssd, True)  # Initialise and clear display.\nssd.fill(0)\nw = ssd.width\nssd.line(0, 0, w - 1, w - 1, GREEN)  # Green diagonal corner-to-corner\noffs = round(0.29289 * w / 2)\nssd.rect(offs, offs, 15, 15, RED)  # Red square at top left\nssd.rect(w - offs - 15, w - offs - 15, 15, 15, BLUE)  # Blue square at bottom right\ncircle(ssd, 119, 119, 119, GREEN)\nrefresh(ssd)\n```\n###### [Contents](./README.md#contents)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterhinch%2Fmicropython-nano-gui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpeterhinch%2Fmicropython-nano-gui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterhinch%2Fmicropython-nano-gui/lists"}