From 76a3894730510ca21611c1ae473193c940069d7b Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 27 May 2023 12:40:09 +0200 Subject: [PATCH] init --- Menu.py | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ utils.py | 23 +++++++++ 2 files changed, 172 insertions(+) create mode 100644 Menu.py create mode 100644 utils.py diff --git a/Menu.py b/Menu.py new file mode 100644 index 0000000..97f714c --- /dev/null +++ b/Menu.py @@ -0,0 +1,149 @@ +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; + + 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() + + 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() + + def show(self): + while (True): + self._display_menu(); + key = self._get_input() + if (key == "up"): + self._up() + elif (key == "down"): + self._down() + elif (key == "enter"): + return (self.cursor_pos) + + def _display_menu(self): + self._clear(); + size = os.get_terminal_size(); + size_x = size.columns; + size_y = size.lines - 2; + preview = self._preview(); + content_size, preview_size = utils.get_sizes(self.size, + preview.count("\n") + 1, + size_y, + self.preview_ratio) + 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): + line = self.cursor + element + else: + line = " " * len(self.cursor) + element + print(line) + self._preview() + + def _preview(self): + if (len(self.preview_args) < self.cursor_pos): + return + title = "" + if (self.preview_title_function != None): + 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]) + return (title + "\n" + body); + + + def _clear(self): + os.system("clear") + + 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): + return ("\n".join(lst)) + +menu = Menu("fdddddddddddddddddddddddddddddddddddddddddddddddddddddddda", preview_body_function=preview); +menu.show() diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..450643e --- /dev/null +++ b/utils.py @@ -0,0 +1,23 @@ +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 ; + 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);