2024-10-31 17:23:28 +00:00
"""
2024-10-31 17:24:46 +00:00
lcdMenu - A micropython library , which supports vertical and horizontal scrolling through menu items on both 2 x16 and 4 x20 LCDs
2024-10-31 17:23:28 +00:00
Copyright ( C ) 2024 Benjamin Burkhardt < bluefox @privacynerd.de >
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 .
This program is distributed in the hope that it will be useful , but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for more details .
You should have received a copy of the GNU General Public License along with this program . If not , see < https : / / www . gnu . org / licenses / > .
"""
2024-11-01 16:30:41 +00:00
from time import sleep
2024-10-31 21:03:48 +00:00
2024-10-31 17:24:46 +00:00
class lcdMenu :
2024-10-31 20:11:12 +00:00
# lcd: I2C_LCD - an object of the I2C_LCD class (https://git.privacynerd.de/BlueFox/micropython-libraries/src/branch/main/PCF8574T)
# buttons: dict - a dictionary with machine.Pin objects as items with following keys
# - "prev_btn" - OPTIONAL - when pressed, select the previous menu item
# - "next_btn" - REQUIRED - when pressed, select the next menu item
# - "ok_btn" - REQUIRED - when pressed, call the callback function of the menu item
# menu_items: list - a list (-> maintains order!) containing tuples with the following format: (<ENTRY_NAME>,<CALLBACK FUNCTION>)
# scroll_direction: bool - if true, the scrolling direction is horizontal, if false, vertical
# cycle: bool - if true, start again with the first menu entry after the last one (and show the last before the first)
# hide_menu_name: bool - OPTIONAL - if true, hide the menu's name (won't work in combination with a vertical scrolling direction)
# name: str - OPTIONAL - a string with the name of the menu (can be hidden under certain circumstances)
# default_selection: int - OPTIONAL - the index of the item selected by default (starting with 0) - DON'T USE NEGATIVE INDEXES
2024-11-01 16:30:41 +00:00
# debounce_time: float - OPTIONAL - the debounce time used by the library to debounce button presses
def __init__ ( self , lcd , buttons : dict , menu_items : list , scroll_direction : bool , cycle : bool , hide_menu_name : bool = False , name : str = " CHOOSE " , default_selection : int = 0 , debounce_time : float = 0.15 ) :
# save the argument variables
2024-10-31 20:11:12 +00:00
self . lcd = lcd
if " prev_btn " in buttons . keys ( ) :
self . prev_btn = buttons [ " prev_btn " ]
else :
self . prev_btn = None
self . next_btn = buttons [ " next_btn " ]
self . ok_btn = buttons [ " ok_btn " ]
self . menu_items = menu_items
self . scroll_direction = scroll_direction
self . cycle = cycle
self . hide_menu_name = hide_menu_name
self . name = name
self . current_selection = default_selection
2024-11-01 16:30:41 +00:00
self . debounce_time = debounce_time
# variables that are very unlikely the user want's to set them (but can be set via <lcdMenuObject>.<attribute_name>, of course)
self . start_execution_msg = " Selected... " # the string displayed when an menu item is selected
self . end_execution_msg = " Closing... " # the string displayed when the callback function of a selected menu item returns self.end_execution_wait = 1 # the time (in seconds) to wait after the callback function of a selected menu item returns
self . start_execution_wait = 0.5 # the time (in seconds) to wait before the callback function of a selected menu item is called
self . end_execution_wait = 1 # the time (in seconds) to wait after the callback function of a selected menu item returns
self . fill_char = ' - ' # the character used to fill up space (used only on 4x20 displays); MUST BE 1 character long
2024-10-31 20:11:12 +00:00
def show_selection ( self ) :
# if you scrolling vertically, I found no elegant way to hide the name (there just need's to be something up there!)
if self . scroll_direction and self . hide_menu_name :
raise TypeError ( " Hiding the menu name whilst having the scroll direction set to horizontal! " )
# TODO: the real process
if self . scroll_direction : # if horizontal scrolling is activated
pass
else : # if vertical scrolling is activated
pass
2024-11-01 16:30:41 +00:00
print ( self . current_selection )
2024-10-31 20:11:12 +00:00
2024-10-31 21:03:48 +00:00
def previous_selection ( self ) :
self . current_selection - = 1
if self . current_selection < 0 : # after the last element:
if self . cycle : # if cycling is enabled, set it to the index of the last element
self . current_selection = len ( self . menu_items ) - 1
else : # else, go to first element
self . current_selection = 0
def next_selection ( self ) :
self . current_selection + = 1
if self . current_selection > = len ( self . menu_items ) : # after the last element:
if self . cycle : # if cycling is enabled, go to first element
self . current_selection = 0
else : # else, set it to the index of the last element
self . current_selection = len ( self . menu_items ) - 1
2024-10-31 20:11:12 +00:00
def execute_selection ( self ) :
2024-11-01 16:30:41 +00:00
selection = self . menu_items [ self . current_selection ]
lw = self . lcd . num_columns
# if the program executed had no display (so the user notices something happens!)
self . lcd . move_to ( 0 , 0 )
if self . lcd . num_lines == 4 :
self . lcd . putstr ( f " { self . fill_char * lw } { ' ' * lw * 2 } { self . fill_char * lw } " ) # fill the first and last line with 'fill_char's
self . lcd . move_to ( 0 , 1 ) # move to the second line for the starting message below (takes two lines)
self . lcd . putstr ( f " [ { selection [ 0 ] [ 0 : lw ] . center ( lw - 2 ) } ] { self . start_execution_msg [ 0 : lw ] . center ( lw ) } " )
sleep ( self . start_execution_wait ) # wait some time before execution (so that the text can be read)
# run the program
return_value = selection [ 1 ] ( )
# show a exit when there's no specific return value
if not return_value : # if the return value is None / nothing was returned
while self . ok_btn . value ( ) == 1 : time . sleep ( self . debounce_time ) # wait till ok_btn release (e.g. if the "program" is a simple send action)
self . lcd . move_to ( 0 , 0 )
if self . lcd . num_lines == 4 :
self . lcd . putstr ( f " { self . fill_char * lw } { ' ' * lw * 2 } { self . fill_char * lw } " ) # fill the first and last line with 'fill_char's
self . lcd . move_to ( 0 , 1 ) # move to the second line for the starting message below (takes two lines)
self . lcd . putstr ( f " [ { selection [ 0 ] [ 0 : lw ] . center ( lw - 2 ) } ] { self . end_execution_msg [ 0 : lw ] . center ( lw ) } " )
sleep ( self . end_execution_wait )
2024-10-31 20:11:12 +00:00
2024-11-01 16:30:41 +00:00
# listen for button presses (this method should be called in an endless loop, see method run)
def loop ( self ) :
if self . prev_btn :
if self . prev_btn . value ( ) == 1 :
self . previous_selection ( )
self . show_selection ( )
while self . prev_btn . value ( ) == 1 : sleep ( self . debounce_time ) # wait till release
if self . next_btn . value ( ) == 1 :
self . next_selection ( )
self . show_selection ( )
while self . next_btn . value ( ) == 1 : sleep ( self . debounce_time ) # wait till release
if self . ok_btn . value ( ) == 1 :
while self . ok_btn . value ( ) == 1 : sleep ( self . debounce_time ) # wait till release
self . execute_selection ( )
2024-10-31 20:11:12 +00:00
def run ( self ) :
2024-11-01 16:30:41 +00:00
# show the selection first
self . show_selection ( )
# then listen on button presses in a loop...
while True :
self . loop ( )