BlueFox
4ad714fa83
The new loop method allows for more advanced usages (e.g. when you want to use the cpu core for more than just the stupid while True, listen for btn presses procedure but also invoke other commands from time to time - then you can implement your 'own' run procedure!)
133 lines
7.6 KiB
Python
133 lines
7.6 KiB
Python
"""
|
|
lcdMenu - A micropython library, which supports vertical and horizontal scrolling through menu items on both 2x16 and 4x20 LCDs
|
|
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/>.
|
|
"""
|
|
|
|
from time import sleep
|
|
|
|
|
|
class lcdMenu:
|
|
# 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
|
|
# 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
|
|
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
|
|
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
|
|
|
|
|
|
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
|
|
print(self.current_selection)
|
|
|
|
|
|
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
|
|
|
|
|
|
def execute_selection(self):
|
|
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)
|
|
|
|
|
|
# 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()
|
|
|
|
|
|
def run(self):
|
|
# show the selection first
|
|
self.show_selection()
|
|
|
|
# then listen on button presses in a loop...
|
|
while True:
|
|
self.loop()
|