Simple MicroPython library to create nested and multifunctional menu with callbacks and custom menu items.
This is a fork of Paweł Ługowski repo
This fork has the following enhancement.
Two properties were added to Menu, fg_col and bg_col which can store foreground color and background color of the menu.
Those 2 properties default to white and black so they make this modification compatible with previous version providing that the display driver has implemented the rgb(r, g, b) method that converts r, g, b values to whichever format the display is using.
This option helps walking through the menu with just one button
- when rollover is True and up() or down() reaches one end of the menu, it jumps to the other end
- when rollover is False and up() or down() reaches one end of the menu, it stops
This argument defaults to True
Just copy the umenu.py
module into your MicroPython board, or just add into root dir, or just in a /lib
folder.
It's strongly reccomended to froze it into a MicroPython binary image - just place the file inside the ports/<board>/modules
folder when building MicroPython from source, then flash to the board as usual.
To build simple menu, you should initialize display first (can be any driver which supports framebuf
, I use ssd1306).
Then create menu object, specify how many items you want to display on one screen and size of one element.
In my example I knnow that menu looks nice when you use 5 lines and 10px height each or 4 lines and 12px height.
import ssd1306
from machine import Pin, I2C
from umenu import *
i2c = I2C(1, scl=Pin(4), sda=Pin(5))
display = ssd1306.SSD1306_I2C(128, 64, i2c)
menu = Menu(display, 5, 10)
menu.add_screen(MenuScreen('Main Menu'))
menu.draw()
Example above will draw empty menu with title Main Menu
on top.
Library allows you to add nested screens, as also implement your own screens with logic.
To walk through menu items you have to trigger methods from Menu
class.
Menu.move(direction: -1|1)
with go to next or previous item.Menu.click()
select current item and execute callable, or go into SubMenu.Menu.reset()
reset current menu state and go to very beginning.Menu.draw()
redraw menu with current state.
This package already contains some basic Menu Items objects which can be used to build your menu
Creates new sub-menu with list of items
Arguments (common for all MenuItems):
name
- to define name visible on screendecorator
- decorator is a text or symbol aligned to right side of screen, can be also callable which return proper string. Default:>
visible
- determine if current section should be visible (read more in Visibility section)
Dummy Item, shows only specified text, with no action.
Arguments:
See SubMenuItem, default decorator here is empty.
Item on menu which is able to trigger any callback specified in argument. After callback, parent screen is returned, but can be disabled by setting return_parent to False.
Specific Arguments:
callback
- callable to trigger on click on item (more in section Callback)return_parent
- to determine if parent should be returned or not
Selected List, here you can define list which will be displayed after click, and on select that element will be passed to callback
Specific Arguments:
items
- list of items, can be also list of dicts {'value': 'xxx', 'name': 'Fance name'}, wherename
will be displayed on screen andvalue
passed to callbackcallback
- callable called after selecting specific positionselected
- define which element should be selected (index or dict key)
Widget to adjust values, by incrementing or decrementing by specified amount.
Specific Arguments:
value_reader
- callable to read current value as start to adjustmin_v
- minimum value for rangemax_v
- maximum value for rangestep
- step to increment / decrementcallback
- callback called on every change of value, value will be passed as last argument
Abstract class to override by custom logic, see example below. Also you can check ValueItem
implementation
which extends CustomItem.
Item to handle toggles, like on/off actions. You can specify state, and callback which will be called to change state.
ToggleItem
is an extension for CallbackItem
Specific Arguments:
state_callback
- callback to check current statechange_callback
- callback to toggle current state (True/False)
Implementation of CallbackItem
with prompt screen before calling custom function. Can be used when we need confirmation for specific action.
If user select "no" option, callback won't be triggered.
Specific Arguments:
question
- can be None, then question "Are you sure?" will be visibleanswers
- tuple foryes
andno
, it'll simply override default tuple ('yes', 'no')
menu.set_screen(MenuScreen('Main Menu')
.add(SubMenuItem('WiFi')
.add(ToggleItem('Activate', wifi.get_status, wifi.activate)))
.add(SubMenuItem('Lights')
.add(ToggleItem('Headlight', (config.get_status, 1), (config.toggle, 1)))
.add(ToggleItem('Backlight', (config.get_status, 2), (config.toggle, 2)))
.add(SubMenuItem('Main Info')
.add(InfoItem('Status:', 'ok'))
.add(InfoItem('Temp:', '45.1')))
)
menu.draw()
In all MenuItems callbacks can be single callable if no parameters should be passed, or tuple where wirst element is
callable, and second is a single arg or tuple with *args
. For example:
CallbackItem('Print it!', (print, 'hello there'))
# will print: hello there > like print('hello there')
CallbackItem('Print it!', (print, (1, 2, 3)))
# will print: 1 2 3 > like print(*args) where *args are taken from tuple
Every item can be hidden separately by setting named argument visible
to False or
by passing callable to check conditions if element should be vissible. Callable should return True or False.
To create your own menu logic, you can extend abstract class CustomItem class and implement at least draw()
and
select()
function.
draw()
is called once you click on specifiv CustomItem position, so basically it can do anything you want, what more
that object has included display, so you can simply draw anything on OLED using driver's methods.
Example usage of CustomItem, to draw some status page:
class DrawCustomScreen(CustomItem):
def __init__(self, name):
super().__init__(name)
def select(self):
return self.parent # this is needed to go back to previous view when SET button is pushed
def draw(self):
self.display.fill(0)
self.display.rect(0, 0, self.display.width, self.display.height, 1)
self.display.text('SHOW SOME TEXT', 0, 10, 1)
self.display.hline(0, 32, self.display.width, 1)
self.display.show()
menu.add_screen(MenuScreen('Main Menu')
.add(DrawCustomScreen('Text in frame'))
)
See examples/rotary_encoder_menu.py
.
Copyright (C) 2021, Paweł Ługowski Copyright (C) 2023, fdufnews for color support
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.