2023-05-27 06:40:09 -04:00
|
|
|
import os;
|
|
|
|
import utils
|
|
|
|
|
|
|
|
class Menu():
|
|
|
|
|
|
|
|
def __init__(self, options: list, title: str = None, cursor: str = "> ", cursor_pos: int = 0, preview_body_function = None, preview_title_function = None, preview_args: list = None, preview_ratio: float = 0.5, circular: bool = True, skip_empty_option: bool = False, quit_button: bool = False):
|
|
|
|
self.options = options;
|
|
|
|
self.title = title;
|
|
|
|
self.cursor = cursor;
|
|
|
|
self.cursor_pos = cursor_pos;
|
|
|
|
self.cursor_pos_x = 0;
|
|
|
|
self.preview_body_function = preview_body_function;
|
|
|
|
self.preview_title_function = preview_title_function;
|
|
|
|
if (preview_args == None):
|
|
|
|
self.preview_args = options;
|
|
|
|
else:
|
|
|
|
self.preview_args = preview_args;
|
|
|
|
self.circular = circular;
|
|
|
|
self.skip_empty_option = skip_empty_option;
|
|
|
|
self.size = len(options)
|
|
|
|
self.preview_ratio = preview_ratio;
|
2023-05-27 10:03:10 -04:00
|
|
|
self._nb_printed_lines = 0;
|
2023-05-27 06:40:09 -04:00
|
|
|
|
|
|
|
def _up(self):
|
|
|
|
if (self.circular == False):
|
|
|
|
if (self.cursor_pos != 0):
|
|
|
|
self.cursor_pos = self.cursor_pos - 1
|
|
|
|
else:
|
|
|
|
self.cursor_pos = (self.cursor_pos - 1) % self.size;
|
|
|
|
if (self.skip_empty_option == True):
|
|
|
|
if (self.options[self.cursor_pos] == None
|
|
|
|
or self.options[self.cursor_pos] == ""):
|
|
|
|
self._up()
|
2023-05-27 09:22:47 -04:00
|
|
|
self.cursor_pos_x = 0;
|
2023-05-27 06:40:09 -04:00
|
|
|
|
|
|
|
def _down(self):
|
|
|
|
if (self.circular == False):
|
|
|
|
if (self.cursor_pos != self.size - 1):
|
|
|
|
self.cursor_pos = self.cursor_pos + 1
|
|
|
|
else:
|
|
|
|
self.cursor_pos = (self.cursor_pos + 1) % self.size;
|
|
|
|
if (self.skip_empty_option == True):
|
|
|
|
if (self.options[self.cursor_pos] == None
|
|
|
|
or self.options[self.cursor_pos] == ""):
|
|
|
|
self._down()
|
2023-05-27 09:22:47 -04:00
|
|
|
self.cursor_pos_x = 0;
|
2023-05-27 06:40:09 -04:00
|
|
|
|
2023-05-27 07:21:24 -04:00
|
|
|
def _left(self):
|
|
|
|
if (self.cursor_pos_x > 0):
|
|
|
|
self.cursor_pos_x = self.cursor_pos_x - 1;
|
|
|
|
|
|
|
|
def _right(self):
|
|
|
|
self.cursor_pos_x = self.cursor_pos_x + 1;
|
|
|
|
|
|
|
|
|
2023-05-27 06:40:09 -04:00
|
|
|
def show(self):
|
|
|
|
while (True):
|
|
|
|
self._display_menu();
|
|
|
|
key = self._get_input()
|
|
|
|
if (key == "up"):
|
|
|
|
self._up()
|
|
|
|
elif (key == "down"):
|
|
|
|
self._down()
|
2023-05-27 07:21:24 -04:00
|
|
|
if (key == "left"):
|
|
|
|
self._left()
|
|
|
|
elif (key == "right"):
|
|
|
|
self._right()
|
2023-05-27 06:40:09 -04:00
|
|
|
elif (key == "enter"):
|
2023-05-27 10:03:10 -04:00
|
|
|
self._clear();
|
2023-05-27 06:40:09 -04:00
|
|
|
return (self.cursor_pos)
|
|
|
|
|
|
|
|
def _display_menu(self):
|
2023-05-27 10:03:10 -04:00
|
|
|
menu: str = "";
|
2023-05-27 06:40:09 -04:00
|
|
|
self._clear();
|
|
|
|
size = os.get_terminal_size();
|
|
|
|
size_x = size.columns;
|
2023-05-27 10:44:31 -04:00
|
|
|
size_y = size.lines;
|
|
|
|
preview_size = int(self.preview_ratio * size_y);
|
|
|
|
content_size = size_y - preview_size
|
|
|
|
preview = self._preview(size_x, preview_size)
|
2023-05-27 06:40:09 -04:00
|
|
|
min_y, max_y, display_pos = utils.render(self.cursor_pos,
|
|
|
|
0,
|
|
|
|
self.size,
|
|
|
|
content_size // 2,
|
|
|
|
self.options)
|
|
|
|
for i, option in enumerate(self.options[min_y:max_y]):
|
|
|
|
element = option
|
|
|
|
if (i == display_pos):
|
2023-05-27 07:21:24 -04:00
|
|
|
min_x, max_x, display_pos_x = utils.render(self.cursor_pos_x,
|
|
|
|
0,
|
|
|
|
len(element),
|
|
|
|
len(element) // 2,
|
|
|
|
option)
|
|
|
|
line = self.cursor + element[min_x:max_x]
|
2023-05-27 06:40:09 -04:00
|
|
|
else:
|
|
|
|
line = " " * len(self.cursor) + element
|
2023-05-27 10:03:10 -04:00
|
|
|
line = line + "\n"
|
|
|
|
menu = menu + line;
|
|
|
|
self._display_screen(menu + preview)
|
2023-05-27 06:40:09 -04:00
|
|
|
|
2023-05-27 10:44:31 -04:00
|
|
|
def _preview(self, size_x: int, size_y: int):
|
2023-05-27 06:40:09 -04:00
|
|
|
if (len(self.preview_args) < self.cursor_pos):
|
2023-05-27 10:44:31 -04:00
|
|
|
return ("")
|
|
|
|
if (self.preview_body_function == None):
|
|
|
|
return ("");
|
|
|
|
if (self.preview_title_function == None):
|
|
|
|
title = "preview"
|
|
|
|
else:
|
2023-05-27 06:40:09 -04:00
|
|
|
title = self.preview_title_function(self.preview_args[self.cursor_pos])
|
2023-05-27 10:44:31 -04:00
|
|
|
body = self.preview_body_function(self.preview_args[self.cursor_pos])
|
|
|
|
|
|
|
|
return (utils.preview_formater(title, body, size_x, size_y));
|
2023-05-27 06:40:09 -04:00
|
|
|
|
|
|
|
|
|
|
|
def _clear(self):
|
2023-05-27 10:03:10 -04:00
|
|
|
UP = "\x1B[3A"
|
|
|
|
CLR = "\x1B[0K"
|
|
|
|
for i in range(self._nb_printed_lines):
|
|
|
|
print(CLR)
|
|
|
|
print(UP)
|
|
|
|
|
|
|
|
def _display_screen(self, screen: str):
|
|
|
|
print(screen)
|
|
|
|
self._nb_printed_lines = screen.count("\n") + 1
|
2023-05-27 06:40:09 -04:00
|
|
|
|
|
|
|
def _get_input(self) -> str:
|
|
|
|
flag_have_getch = False
|
|
|
|
flag_have_msvcrt = False
|
|
|
|
try :
|
|
|
|
import getch
|
|
|
|
flag_have_getch = True
|
|
|
|
first_char = getch.getch()
|
|
|
|
if first_char == '\x1b': #arrow keys
|
|
|
|
a=getch.getch()
|
|
|
|
b=getch.getch()
|
|
|
|
return {'[A': 'up', '[B': 'down', '[C': 'right', '[D': 'left' }[a+b]
|
|
|
|
if ord(first_char) == 10:
|
|
|
|
return 'enter'
|
|
|
|
if ord(first_char) == 32:
|
|
|
|
return 'space'
|
|
|
|
else:
|
|
|
|
return first_char #normal keys like abcd 1234
|
|
|
|
except :
|
|
|
|
pass
|
|
|
|
|
|
|
|
try:
|
|
|
|
import msvcrt
|
|
|
|
flag_have_msvcrt = True
|
|
|
|
key = msvcrt.getch() # get keypress
|
|
|
|
if key == b'\x1b': # Esc key to exit
|
|
|
|
return 'esc'
|
|
|
|
elif key == b'\r': # Enter key to select
|
|
|
|
return 'enter'
|
|
|
|
elif key == b'\x48': # Up or Down arrow
|
|
|
|
return 'up'
|
|
|
|
elif key == b'\x50': # Up or Down arrow
|
|
|
|
return 'down'
|
|
|
|
else:
|
|
|
|
return key.decode('utf-8')
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if flag_have_getch == False and flag_have_msvcrt == False:
|
|
|
|
print('\nErr:\tcan\'t get input \nFix:\tpip install getch')
|
|
|
|
exit()
|
|
|
|
first_char = getch.getch()
|
|
|
|
print (first_char)
|
|
|
|
if first_char == '\x1b': #arrow keys
|
|
|
|
a=getch.getch()
|
|
|
|
b=getch.getch()
|
|
|
|
return {'[A': 'up', '[B': 'down', '[C': 'right', '[D': 'left' }[a+b]
|
|
|
|
if ord(first_char) == 10:
|
|
|
|
return 'enter'
|
|
|
|
if ord(first_char) == 32:
|
|
|
|
return 'space'
|
|
|
|
|
|
|
|
def preview(lst):
|
2023-05-27 07:21:24 -04:00
|
|
|
return ("je suis la")
|
2023-05-27 06:40:09 -04:00
|
|
|
|
|
|
|
menu = Menu("fdddddddddddddddddddddddddddddddddddddddddddddddddddddddda", preview_body_function=preview);
|
|
|
|
menu.show()
|