Compare commits
10 Commits
f17dee09e8
...
main
Author | SHA1 | Date | |
---|---|---|---|
5c07857c79 | |||
a546c0ffa4 | |||
4d85721b8a | |||
e2870e2ced | |||
d75dabd174 | |||
a76c484116 | |||
2ce33db8e2 | |||
3bb67eb610 | |||
af98c125df | |||
e8a27f2455 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
__pycache__
|
||||||
|
.env
|
137
Menu.py
137
Menu.py
@ -1,9 +1,55 @@
|
|||||||
import os;
|
import os;
|
||||||
import utils
|
import re
|
||||||
|
import colorama
|
||||||
|
|
||||||
|
def render(pos: int, min: int, max: int, render_distance: int, elements: list):
|
||||||
|
start = pos - render_distance;
|
||||||
|
if (start < min):
|
||||||
|
start = min;
|
||||||
|
stop = pos + render_distance + 1;
|
||||||
|
if (stop > max):
|
||||||
|
stop = max;
|
||||||
|
i = 0;
|
||||||
|
y = pos - start;
|
||||||
|
if (y < min):
|
||||||
|
y = pos;
|
||||||
|
if (stop - start != render_distance * 2 + 1):
|
||||||
|
if (pos - start != render_distance):
|
||||||
|
stop = stop + render_distance - start - pos
|
||||||
|
# elif (stop + render_distance > max):
|
||||||
|
# start = start - (stop + render_distance - max)
|
||||||
|
y = pos - start;
|
||||||
|
if (y < min):
|
||||||
|
y = pos;
|
||||||
|
return (start, stop, y);
|
||||||
|
|
||||||
|
def preview_formater(title: str, body: str, size_x: int, size_y: int):
|
||||||
|
out: str = "";
|
||||||
|
out += f"┌── {title} " + "─" * (size_x - 6 - printable_len(title)) + "┐\n"
|
||||||
|
lines = body.split("\n");
|
||||||
|
for line in lines[:size_y - 2]:
|
||||||
|
out += f"│ {line[: size_x - 3]}" + " " * (size_x - 3 - printable_len(line)) + "│\n"
|
||||||
|
out += "└" + "─" * (size_x - 2) + "┘"
|
||||||
|
return (out);
|
||||||
|
|
||||||
|
strip_ANSI_pat = re.compile(r"""
|
||||||
|
\x1b # literal ESC
|
||||||
|
\[ # literal [
|
||||||
|
[;\d]* # zero or more digits or semicolons
|
||||||
|
[A-Za-z] # a letter
|
||||||
|
""", re.VERBOSE).sub
|
||||||
|
|
||||||
|
def printable_len(string: str):
|
||||||
|
mid = strip_ANSI_pat("", string)
|
||||||
|
out = 0;
|
||||||
|
for char in mid:
|
||||||
|
if (char.isprintable()):
|
||||||
|
out += 1;
|
||||||
|
return (out);
|
||||||
|
|
||||||
class Menu():
|
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):
|
def __init__(self, options: list, title: str = None, cursor: str = colorama.Back.WHITE + colorama.Fore.BLACK, 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.options = options;
|
||||||
self.title = title;
|
self.title = title;
|
||||||
self.cursor = cursor;
|
self.cursor = cursor;
|
||||||
@ -19,6 +65,8 @@ class Menu():
|
|||||||
self.skip_empty_option = skip_empty_option;
|
self.skip_empty_option = skip_empty_option;
|
||||||
self.size = len(options)
|
self.size = len(options)
|
||||||
self.preview_ratio = preview_ratio;
|
self.preview_ratio = preview_ratio;
|
||||||
|
self._nb_printed_lines = 0;
|
||||||
|
self.cursor_size = printable_len(cursor)
|
||||||
|
|
||||||
def _up(self):
|
def _up(self):
|
||||||
if (self.circular == False):
|
if (self.circular == False):
|
||||||
@ -27,9 +75,9 @@ class Menu():
|
|||||||
else:
|
else:
|
||||||
self.cursor_pos = (self.cursor_pos - 1) % self.size;
|
self.cursor_pos = (self.cursor_pos - 1) % self.size;
|
||||||
if (self.skip_empty_option == True):
|
if (self.skip_empty_option == True):
|
||||||
if (self.options[self.cursor_pos] == None
|
if (printable_len(self.options[self.cursor_pos]) == 0):
|
||||||
or self.options[self.cursor_pos] == ""):
|
|
||||||
self._up()
|
self._up()
|
||||||
|
self.cursor_pos_x = 0;
|
||||||
|
|
||||||
def _down(self):
|
def _down(self):
|
||||||
if (self.circular == False):
|
if (self.circular == False):
|
||||||
@ -38,9 +86,17 @@ class Menu():
|
|||||||
else:
|
else:
|
||||||
self.cursor_pos = (self.cursor_pos + 1) % self.size;
|
self.cursor_pos = (self.cursor_pos + 1) % self.size;
|
||||||
if (self.skip_empty_option == True):
|
if (self.skip_empty_option == True):
|
||||||
if (self.options[self.cursor_pos] == None
|
if (printable_len(self.options[self.cursor_pos]) == 0):
|
||||||
or self.options[self.cursor_pos] == ""):
|
|
||||||
self._down()
|
self._down()
|
||||||
|
self.cursor_pos_x = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
while (True):
|
while (True):
|
||||||
@ -50,20 +106,26 @@ class Menu():
|
|||||||
self._up()
|
self._up()
|
||||||
elif (key == "down"):
|
elif (key == "down"):
|
||||||
self._down()
|
self._down()
|
||||||
|
if (key == "left"):
|
||||||
|
self._left()
|
||||||
|
elif (key == "right"):
|
||||||
|
self._right()
|
||||||
elif (key == "enter"):
|
elif (key == "enter"):
|
||||||
|
self._clear();
|
||||||
return (self.cursor_pos)
|
return (self.cursor_pos)
|
||||||
|
|
||||||
def _display_menu(self):
|
def _display_menu(self):
|
||||||
|
menu: str = "";
|
||||||
self._clear();
|
self._clear();
|
||||||
size = os.get_terminal_size();
|
size = os.get_terminal_size();
|
||||||
size_x = size.columns;
|
size_x = size.columns;
|
||||||
size_y = size.lines - 2;
|
size_y = size.lines;
|
||||||
preview = self._preview();
|
preview_size = int(self.preview_ratio * size_y)
|
||||||
content_size, preview_size = utils.get_sizes(self.size,
|
content_size = size_y - preview_size - 2
|
||||||
preview.count("\n") + 1,
|
preview = self._preview(size_x, preview_size)
|
||||||
size_y,
|
if (self.title != None):
|
||||||
self.preview_ratio)
|
menu += self.title[:size_x] + "\n";
|
||||||
min_y, max_y, display_pos = utils.render(self.cursor_pos,
|
min_y, max_y, display_pos = render(self.cursor_pos,
|
||||||
0,
|
0,
|
||||||
self.size,
|
self.size,
|
||||||
content_size // 2,
|
content_size // 2,
|
||||||
@ -71,25 +133,45 @@ class Menu():
|
|||||||
for i, option in enumerate(self.options[min_y:max_y]):
|
for i, option in enumerate(self.options[min_y:max_y]):
|
||||||
element = option
|
element = option
|
||||||
if (i == display_pos):
|
if (i == display_pos):
|
||||||
line = self.cursor + element
|
min_x, max_x, display_pos_x = render(self.cursor_pos_x,
|
||||||
|
0,
|
||||||
|
size_x,
|
||||||
|
size_x // 2,
|
||||||
|
option)
|
||||||
|
line = self.cursor + element[self.cursor_pos_x:size_x - self.cursor_size + self.cursor_pos_x]
|
||||||
else:
|
else:
|
||||||
line = " " * len(self.cursor) + element
|
line = " " * self.cursor_size + element[:size_x - self.cursor_size]
|
||||||
print(line)
|
line = line + colorama.Style.RESET_ALL + "\n"
|
||||||
self._preview()
|
menu = menu + line;
|
||||||
|
menu = menu[:-1]
|
||||||
|
self._display_screen(menu + "\n" + preview)
|
||||||
|
|
||||||
def _preview(self):
|
def _preview(self, size_x: int, size_y: int):
|
||||||
if (len(self.preview_args) < self.cursor_pos):
|
if (len(self.preview_args) < self.cursor_pos):
|
||||||
return
|
return ("")
|
||||||
title = ""
|
if (self.preview_body_function == None):
|
||||||
if (self.preview_title_function != None):
|
return ("");
|
||||||
|
if (self.preview_title_function == None):
|
||||||
|
title = "preview"
|
||||||
|
else:
|
||||||
title = self.preview_title_function(self.preview_args[self.cursor_pos])
|
title = self.preview_title_function(self.preview_args[self.cursor_pos])
|
||||||
elif (self.preview_body_function != None):
|
|
||||||
body = self.preview_body_function(self.preview_args[self.cursor_pos])
|
body = self.preview_body_function(self.preview_args[self.cursor_pos])
|
||||||
return (title + "\n" + body);
|
if (body == None or title == None):
|
||||||
|
return ("")
|
||||||
|
|
||||||
|
return (preview_formater(title, body, size_x, size_y));
|
||||||
|
|
||||||
|
|
||||||
def _clear(self):
|
def _clear(self):
|
||||||
os.system("clear")
|
UP = "\x1B[3A"
|
||||||
|
CLR = "\x1B[0K"
|
||||||
|
for _ 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
|
||||||
|
|
||||||
def _get_input(self) -> str:
|
def _get_input(self) -> str:
|
||||||
flag_have_getch = False
|
flag_have_getch = False
|
||||||
@ -132,7 +214,6 @@ class Menu():
|
|||||||
print('\nErr:\tcan\'t get input \nFix:\tpip install getch')
|
print('\nErr:\tcan\'t get input \nFix:\tpip install getch')
|
||||||
exit()
|
exit()
|
||||||
first_char = getch.getch()
|
first_char = getch.getch()
|
||||||
print (first_char)
|
|
||||||
if first_char == '\x1b': #arrow keys
|
if first_char == '\x1b': #arrow keys
|
||||||
a=getch.getch()
|
a=getch.getch()
|
||||||
b=getch.getch()
|
b=getch.getch()
|
||||||
@ -141,9 +222,3 @@ class Menu():
|
|||||||
return 'enter'
|
return 'enter'
|
||||||
if ord(first_char) == 32:
|
if ord(first_char) == 32:
|
||||||
return 'space'
|
return 'space'
|
||||||
|
|
||||||
def preview(lst):
|
|
||||||
return ("\n".join(lst))
|
|
||||||
|
|
||||||
menu = Menu("fdddddddddddddddddddddddddddddddddddddddddddddddddddddddda", preview_body_function=preview);
|
|
||||||
menu.show()
|
|
||||||
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
colorama==0.4.6
|
||||||
|
getch==1.0
|
31
utils.py
31
utils.py
@ -1,31 +0,0 @@
|
|||||||
def render(pos: int, min: int, max: int, render_distance: int, elements: list):
|
|
||||||
start = pos - render_distance;
|
|
||||||
if (start < min):
|
|
||||||
start = min;
|
|
||||||
stop = pos + render_distance + 1;
|
|
||||||
if (stop > max):
|
|
||||||
stop = max;
|
|
||||||
i = 0;
|
|
||||||
y = pos - start;
|
|
||||||
if (y < min):
|
|
||||||
y = pos;
|
|
||||||
if (stop - start != render_distance * 2 + 1):
|
|
||||||
if (pos - start != render_distance):
|
|
||||||
stop = stop + render_distance - start - pos
|
|
||||||
# elif (stop + render_distance > max):
|
|
||||||
# start = start - (stop + render_distance - max)
|
|
||||||
y = pos - start;
|
|
||||||
if (y < min):
|
|
||||||
y = pos;
|
|
||||||
return (start, stop, y);
|
|
||||||
|
|
||||||
def get_sizes(size1: int, size2: int, total_size: int, aim_ratio: float):
|
|
||||||
if (size1 + size2 < total_size):
|
|
||||||
return (size1, size2);
|
|
||||||
if (size1 > total_size * aim_ratio):
|
|
||||||
size1 = total_size * aim_ratio;
|
|
||||||
if (size2 > total_size - size1):
|
|
||||||
size2 = total_size - size1;
|
|
||||||
size1 = int(size1);
|
|
||||||
size2 = int(size2)
|
|
||||||
return (size1, size2);
|
|
Reference in New Issue
Block a user