diff --git a/README.md b/README.md index ef1325c..b64b1f4 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,5 @@ -# PyMenu-Framework -A Python (3.3+) based framework for creating simple and intuitive text-based menu interfaces. Includes support for text entry and dialog boxes with multiple options within them. +# PyMenu-Framework-Version2 ---- +A complete rewrite of the original code with a few unnecessary features removed and a few new features added such as the ability to add inputs to a menu and cycle between them with the up and down arrow keys. -![Image of PyMenu](https://github.com/Nytra/PyMenu-Framework/blob/master/images/main_menu.PNG) - -PyMenu is capable of displaying many different buttons on the screen with each one linked to a specific function. - ---- - -![Image of a Dialog Box](https://github.com/Nytra/PyMenu-Framework/blob/master/images/dialog.png) - -PyMenu can display dialog boxes which contain text. The text will be automatically word wrapped to fit on the screen, no matter the screen dimensions. - ---- - -![Image of Dialog Box Options](https://github.com/Nytra/PyMenu-Framework/blob/master/images/dialog_choices.png) - -Dialog boxes are capable of containing many different options. - ---- - -![Image of Dynamic Resizing](https://github.com/Nytra/PyMenu-Framework/blob/master/images/resize.png) - -The framework will automatically detect when the terminal dimensions have changed and it will resize the interface accordingly. - ---- - -##How To Use The PyMenu Framework - ---- - -![Image of PyMenu Code](https://github.com/Nytra/PyMenu-Framework/blob/master/images/demo.png) - -PyMenu is designed to be easily implemented alongside existing code. The possibilities are practically endless. - ---- - -![Image of Dialog Code](https://github.com/Nytra/PyMenu-Framework/blob/master/images/demo3.png?raw=true) - -This is an example of how a dialog box may be created with PyMenu. - -Further example code can be found within the file called demo2.py. This is the file that I personally use for all of my testing so it is always up to date, but often very messy. - ---- - -##Limitations - ---- - -PyMenu currently does not support scrolling of any kind. So any text that exceeds the maximum overlay height will spill out onto the background. I am working on this. +![Example of Inputs](https://github.com/Nytra/PyMenu-Framework/blob/pymenu-version2/images/Login_Menu_Input.png?raw=true) diff --git a/colorama_utils.py b/colorama_utils.py new file mode 100644 index 0000000..805db0f --- /dev/null +++ b/colorama_utils.py @@ -0,0 +1,2 @@ +def draw(x, y, text): + print("\x1b[{};{}H".format(y, x) + text, end="") # Move the cursor to the given coordinates and then print the text \ No newline at end of file diff --git a/demo.py b/demo.py deleted file mode 100644 index 2c8ce15..0000000 --- a/demo.py +++ /dev/null @@ -1,52 +0,0 @@ -import pymenu - - - -# Now we will define some functions that our menu buttons will be linked to. -def hello(m): - - # Here we are calling the method msg() which takes a string constant as its only argument. - # It will display this string at the top of the menu overlay and will automatically wrap the text if it's too long. - m.msg("Hello, world!") - -# This next function will create a new menu within the main menu -# (Allows you to have buttons that are linked to different menu screens) -def new_menu(): - - # Here we create a new menu object with different attributes. - newMenu = pymenu.Menu("Second Menu", "This is a separate menu", "This is some different footer text") - - # This button will be linked to the hello() function that we created earlier. - newMenu.add("First Button", lambda: hello(newMenu)) - - # We should be able to return to the previous menu - newMenu.add("Back", newMenu.quit) - - # And now to display the new menu. - newMenu.start() - - # Finally, we should redraw the previous menu after the new menu has closed. - myMenu.redraw() - - - -# Create a new menu object. Menu(Title, Description, Footer) -myMenu = pymenu.Menu("Main Menu", "This is a short description of the menu.", "This is some footer text.") - -# We should display the name of our program in the bottom right. If not specified, PyMenu Vx.xx will be displayed. -# This only needs to be done once, because the program title is a global variable that is automatically applied to any -# new menus that are created. -myMenu.set_program_title("PyMenu Demonstration") - -# Add a new button to the menu. add(Text, Function Pointer) -myMenu.add("First Button", lambda: hello(myMenu)) -# It is important that you do not include parentheses when typing the function name. - -# We can create a button which links to another menu -myMenu.add("Next Menu", new_menu) - -# We should be able to close the menu (This will end the program in this case) -myMenu.add("Exit", myMenu.quit) - -# Finally, in order to display the menu, we call the start() method. -myMenu.start() \ No newline at end of file diff --git a/demo2.py b/demo2.py deleted file mode 100644 index edac6bc..0000000 --- a/demo2.py +++ /dev/null @@ -1,260 +0,0 @@ -from pymenu import * - -if __name__ == "__main__": # A simple menu demonstration will run whenever this program is directly executed - - try: - import winsound - sound = True - except: - sound = False - - def test_function(): - m.msg(" ".join("Hello, world!" for x in range(6))) - - def dummy(): - pass - - def inc_pitch(val): - global pitch, bm - pitch += val - #bm.write([bm.msg_x, bm.msg_y],"Pitch: %d" % pitch) - bm.msg("Pitch: %d" % pitch) - - def dec_pitch(val): - global pitch, bm - if pitch <= val: - pass - else: - pitch -= val - #bm.draw_overlay() - #bm.write([bm.msg_x, bm.msg_y], "Pitch: %d" % pitch) - bm.msg("Pitch: %d" % pitch) - - def beep(): - global pitch, duration - if sound: - winsound.Beep(pitch, duration) - else: - #bm.write([bm.msg_x, bm.msg_y], "Sound is disabled on this platform.") - bm.msg("Sound is disabled on this platform.") - - def beep_menu(): - global pitch, duration, bm - bm = Menu("Beeping", "Use The Options To Control The Pitch Of The Beep") - bm.overlay_bg = global_overlay_bg - bm.add("Beep", beep) - bm.add("Increase Pitch", lambda: inc_pitch(100)) - bm.add("Decrease Pitch", lambda: dec_pitch(100)) - bm.add("Back", bm.quit) - bm.start() - m.redraw() - - def sys_config(): - - def change_outbg(): - c = Menu("Change Outer Background", "Select A Colour") - - def set(colour): - global global_outer_bg - Menu.outer_bg = colour - global_outer_bg = colour - c.redraw() - - c.overlay_bg = global_overlay_bg - c.add("Red", lambda: set(Back.RED)) - c.add("Blue", lambda: set(Back.BLUE)) - c.add("Yellow", lambda: set(Back.YELLOW)) - c.add("Green", lambda: set(Back.GREEN)) - c.add("Magenta", lambda: set(Back.MAGENTA)) - c.add("White", lambda: set(Back.WHITE)) - c.add("Black", lambda: set(Back.BLACK)) - c.add("Back", c.quit) - c.start() - sc.redraw() - - def change_overbg(): - c = Menu("Change Outer Background", "Select A Colour") - - def set(colour): - global global_overlay_bg - Menu.overlay_bg = colour - global_overlay_bg = colour - c.redraw() - - c.overlay_bg = global_overlay_bg - c.add("Red", lambda: set(Back.RED)) - c.add("Blue", lambda: set(Back.BLUE)) - c.add("Yellow", lambda: set(Back.YELLOW)) - c.add("Green", lambda: set(Back.GREEN)) - c.add("Magenta", lambda: set(Back.MAGENTA)) - c.add("White", lambda: set(Back.WHITE)) - c.add("Black", lambda: set(Back.BLACK)) - c.add("Back", c.quit) - c.start() - sc.redraw() - - sc = Menu("Colour Settings", "Give Your Menu A New Coat Of Paint!") - sc.overlay_bg = global_overlay_bg - sc.add("Outer Background", change_outbg) - sc.add("Overlay Background", change_overbg) - sc.add("Back", sc.quit) - sc.start() - m.redraw() - - - def dialog_demo(): - pm = Menu("Dialog Boxes", "Examples of Dialog Boxes") - - def alice(): - tm = Menu("Alice in Wonderland", "Alice's Adventures in Wonderland") - tm.set_dialog_msg( - "There was nothing so very remarkable in that; nor did Alice think it so very much out of the way to hear the Rabbit say to itself \"Oh dear! Oh dear! I shall be too late!\" (when she thought it over afterwards it occurred to her that she ought to have wondered at this, but at the time it all seemed quite natural); but, when the Rabbit actually took a watch out of its waistcoat-pocket, and looked at it, and then hurried on, Alice started to her feet, for it flashed across her mind that she had never before seen a rabbit with either a waistcoat-pocket, or a watch to take out of it, and burning with curiosity, she ran across the field after it, and was just in time to see it pop down a large rabbit-hole under the hedge.") - tm.add("OK", tm.quit) - #tm.add("Cancel", self.quit) - tm.start() - pm.redraw() - - def error_msg(): - tm = Menu("ERROR", "CODE: A322", "") - tm.set_dialog_msg( - "An error occurred while trying to set power efficacy for Generator 7 in Zone 6. Recommend manual intervention." - ) - tm.add("OK", tm.quit) - #tm.add("Cancel", self.quit) - tm.start() - pm.redraw() - - def short_msg(): - tm = Menu("Small Dialog Box", ":)", "") - tm.set_dialog_msg( - "Blah" - ) - tm.add("OK", tm.quit) - tm.add("\"Blah\" to you, too.", tm.quit) - #tm.add("Cancel", self.quit) - tm.start() - pm.redraw() - - def choice(): - tm = Menu("Multiple Choice", "Make A Decision", "") - tm.set_dialog_msg( - "Are you sure you want to proceed?" - ) - tm.add("OK", tm.quit) - tm.add("Cancel", tm.quit) - tm.start() - pm.redraw() - - def colours(): - - def set_colour(colour): - Menu.outer_bg = colour - Menu.outer_bg = colour - tm.quit() - - tm = Menu("Colour Choice", "Make A Decision", "") - tm.set_dialog_msg( - "Choose a colour from the selection below." - ) - tm.add("Green", lambda: set_colour(Back.GREEN)) - tm.add("Magenta", lambda: set_colour(Back.MAGENTA)) - tm.add("White", lambda: set_colour(Back.WHITE)) - tm.add("Blue", lambda: set_colour(Back.BLUE)) - tm.add("Cancel", tm.quit) - tm.start() - pm.redraw() - - def employee_info(): - tm = Menu("Employee Info", "Sam Scott's Information", "") - tm.set_dialog_msg( - r""" - Name: Sam Scott\n - Date Of Birth: 04/03/1999\n - Job Title: Project Manager\n - Pay Per Hour: £24\n - Phone Number: 07534841819\n - Home Address: 142 Vicarage Road""" - - ) - tm.add("OK", tm.quit) - tm.start() - pm.redraw() - - #tm = Menu("Colour Choice", "Make A Decision", "") - pm.add("Alice in Wonderland", alice) - pm.add("Error Message", error_msg) - pm.add("Small Dialog Box", short_msg) - pm.add("Confirmation Dialog Box", choice) - pm.add("Colour Selection", colours) - pm.add("Employee Info", employee_info) - pm.add("Back", pm.quit) - pm.start() - m.redraw() - - def text_test(): - tm = Menu("Text Editor", "Edit", "[ESC - exit] [F1 - save] [F2 - load]") - #Menu.overlay_bg = global_overlay_bg - tm.set_text_box() - tm.start() - m.redraw() - - def text_entry(): - - def greet(name): - gm = Menu("Welcome!", "Welcome!", "") - gm.set_dialog_msg("Welcome, " + name + "!") - gm.add("OK", gm.quit) - gm.start() - m.redraw() - - name = "" - while not name: - tm = Menu("Text Entry", "Employee Information", "") - # Menu.overlay_bg = global_overlay_bg - tm.set_text_box("Please enter your name: ") - tm.add("OK", tm.quit) - tm.start() - name = tm.get_entry() - - greet(name) - - #print(name) - #input() - m.redraw() - - def quit_prompt(): - - def quit(): - tm.quit() - m.quit() - - tm = Menu("Exit", "", "") - #Menu.overlay_bg = global_overlay_bg - tm.set_dialog_msg("Are you sure you want to quit?") - tm.add("Yes", quit) - tm.add("No", tm.quit) - tm.start() - m.redraw() - - #title = input("Menu title: ") - - pitch = 400 - duration = 600 - global_outer_bg = Back.BLUE - global_overlay_bg = Back.WHITE - - m = Menu("Main Menu", "Choose An Option") - m.set_program_title(Menu.prog_title + " Demonstration") - Menu.overlay_bg = global_overlay_bg - m.add("Hello World", target=test_function) - m.add("Dialog Boxes", target=dialog_demo) - m.add("Text Editor", target=text_test) - m.add("Text Entry", target=text_entry) - m.add("Beeping", target= beep_menu) - m.add("Colour Settings", target=sys_config) - m.add("Exit", target= quit_prompt ) - try: - m.start() - except Exception as e: - print(e) - input() \ No newline at end of file diff --git a/demo3.py b/demo3.py deleted file mode 100644 index 543b884..0000000 --- a/demo3.py +++ /dev/null @@ -1,16 +0,0 @@ -import pymenu - -# This is an example of how a dialog box is created using PyMenu. - -m = pymenu.Menu("Dialog Box Demo", "This is a dialog box", "This is some footer text") -m.set_program_title("Dialog Box Demo") - -# Here we must specify what text will appear in the dialog box. This step is essential. -m.set_dialog_msg("Are you sure you want to continue?") - -# We should add some buttons to give the user some choice. -m.add("Yes", m.quit) -m.add("No", m.quit) - -# And now we display the interface. -m.start() \ No newline at end of file diff --git a/images/Login_Menu_Input.png b/images/Login_Menu_Input.png new file mode 100644 index 0000000..80764e8 Binary files /dev/null and b/images/Login_Menu_Input.png differ diff --git a/menu.py b/menu.py new file mode 100644 index 0000000..e91b517 --- /dev/null +++ b/menu.py @@ -0,0 +1,391 @@ +from colorama import * +from colorama_utils import * +import msvcrt, ctypes +init() + +class Menu: + + instance_num = 0 + menu_being_displayed = 0 # the id of the menu which is currently being displayed + session = None + + program_title = "Morriston Manufacturing and Logistics" + debug_message = "" + + outer_bg = Back.BLUE + outer_fg = Fore.WHITE + outer_style = Style.BRIGHT + + overlay_bg = Back.WHITE + overlay_fg = Fore.BLACK + overlay_style = Style.NORMAL + + selected_bg = Back.BLUE + selected_fg = Fore.WHITE + selected_style = Style.BRIGHT + + debug_bg = Back.BLACK + debug_fg = Fore.RED + debug_style = Style.NORMAL + + input_bg = overlay_bg + input_fg = Fore.BLUE + input_style = overlay_style + + shadow_bg = Back.BLACK + + accent_fg = overlay_fg + accent_bg = overlay_bg + accent_style = overlay_style + accent_alt_style = Style.BRIGHT + + #X_MAX = 119 + #Y_MAX = 30 + X_MAX = 80 + Y_MAX = 25 + + def __init__(self, title, description="", footer="Use the arrow keys to highlight an option and then press enter to select it."): + self.uid = Menu.instance_num + Menu.instance_num += 1 + + self.title = title + self.desc = description + self.footer = footer + + self.buttons = [] + self.inputs = [] + self.button_index = 0 # stores the index of the button that is currently selected + self.input_index = 0 # stores the index of the input that is currently selected + + self.has_been_killed = False + + self.is_prompt = False + self.prompt_msg = "" + + self.kb_chars = "abcdefghijklmnopqrstuvwxyz,./?!\"\'£;:$%^&*()[]{}@#~/\\<>|-_=+¬`¦1234567890 " + + self.option_width = Menu.X_MAX // 3 + self.option_height = 1 + self.title_x = (Menu.X_MAX // 2) - (len(self.title) // 2) + self.title_y = 1#(Menu.Y_MAX // 10) + + self.cursor_x = 0 + self.cursor_y = 0 + + self.update_dimensions() + + def add_button(self, text, target): + # Target is a function pointer + self.buttons.append([text, target]) + + def add_input(self, text): + self.inputs.append([text, ""]) # the second element in the list will store keyboard input + + def get_inputs(self): + data = [] + for i in self.inputs: + data.append(i[1]) + return data + + def clear_inputs(self): + for inp in self.inputs: + inp[1] = "" + + def set_desc(self, text): + self.desc = text + + def set_footer(self, text): + self.footer = text + + def set_program_title(self, text): + Menu.program_title = text + + def update_dimensions(self): + + if self.is_prompt: + wrapped = self.word_wrapped_text(self.prompt_msg) + self.overlay_height = len(wrapped) + 4 + (len(self.inputs) * 2) + self.overlay_width = int(Menu.X_MAX * 0.8) # len(wrapped[0]) + 4 + else: + self.overlay_width = int(Menu.X_MAX * 0.8) + self.overlay_height = Menu.Y_MAX // 2 + + self.overlay_top = (Menu.Y_MAX // 2) - (self.overlay_height // 2) # top y coord + self.overlay_left = (Menu.X_MAX // 2) - (self.overlay_width // 2) # leftmost x coord + + self.desc_x = self.overlay_left + 1 + self.desc_y = self.overlay_top + + self.msg_x = self.overlay_left + 1 + self.msg_y = self.overlay_top + 1 + + self.input_x = (Menu.X_MAX // 2) - (self.overlay_width // 4) + self.input_y = (self.overlay_top + (self.overlay_height // 2)) - (len(self.inputs) // 2) + + def draw_menu(self): + + if self.has_been_killed: + return + + Menu.menu_being_displayed = self.uid + + self.update_dimensions() + + # ===== Draw the background + for y in range(1, Menu.Y_MAX + 1): + draw(1, y, Menu.outer_bg + " " * Menu.X_MAX) + + # ===== Draw the footer text + draw(1, Menu.Y_MAX, Menu.outer_bg + Menu.outer_fg + Menu.outer_style + self.footer) + + self.draw_menu_title() + self.draw_program_title() + self.draw_overlay() + self.draw_buttons() + self.draw_inputs() + self.draw_debug_message() + + def draw_debug_message(self): + if Menu.debug_message: + draw(1, Menu.Y_MAX - 1, Menu.debug_bg + Menu.debug_fg + Menu.debug_style + Menu.debug_message) + draw(1, Menu.Y_MAX - 2, Menu.debug_bg + Menu.debug_fg + Menu.debug_style + "button_index: " + str(self.button_index)) + draw(1, Menu.Y_MAX - 3, Menu.debug_bg + Menu.debug_fg + Menu.debug_style + "input_index: " + str(self.input_index)) + draw(1, Menu.Y_MAX - 4, Menu.debug_bg + Menu.debug_fg + Menu.debug_style + "menu_id: " + str(self.uid)) + + def draw_program_title(self): + draw(Menu.X_MAX // 2 - len(Menu.program_title) // 2, 1, + Menu.outer_bg + Menu.outer_fg + Menu.outer_style + Menu.program_title) + + def draw_menu_title(self): + # ===== Draw the menu title at the top + title_x = (Menu.X_MAX // 2) - (len(self.title) // 2) + title_y = (Menu.Y_MAX // 10) + draw(1, title_y - 1, Menu.outer_bg + Menu.outer_fg + Menu.outer_style + "-" * Menu.X_MAX) + draw(title_x, title_y, Menu.outer_bg + Menu.outer_fg + Menu.outer_style + self.title) + draw(1, title_y + 1, Menu.outer_bg + Menu.outer_fg + Menu.outer_style + "-" * Menu.X_MAX) + + # ===== Draw session username + username = Menu.session.get_login_details()["username"] + if username: + draw(1, title_y, Menu.outer_bg + Menu.outer_fg + Menu.outer_style + "Username: " + username) + + def draw_overlay(self): + self.update_dimensions() + + # ===== Draw the menu overlay + for y in range(self.overlay_top, self.overlay_top + self.overlay_height): + draw(self.overlay_left, y, Menu.overlay_bg + " " * self.overlay_width) + # ===== Draw the menu overlay shadow + for y in range(self.overlay_top + 1, self.overlay_top + self.overlay_height): + draw(self.overlay_left + self.overlay_width, y, Menu.shadow_bg + " ") + #for x in range(self.overlay_left + 1, self.overlay_left + self.overlay_width + 1): + #draw(x, self.overlay_top + self.overlay_height, Back.BLACK + " ") + draw(self.overlay_left + 1, self.overlay_top + self.overlay_height, Menu.shadow_bg + " " * self.overlay_width) + + # Draw fancy menu stuff + for x in range(self.overlay_left + 1, self.overlay_left + self.overlay_width): + draw(x, self.overlay_top + self.overlay_height - 1, Menu.accent_bg + Menu.accent_fg + Menu.accent_style + "─") + draw(x, self.overlay_top, Menu.accent_bg + Menu.accent_fg + Menu.accent_alt_style + "─") + for y in range(self.overlay_top + 1, self.overlay_top + self.overlay_height - 1): + draw(self.overlay_left + self.overlay_width - 1, y, Menu.accent_bg + Menu.accent_fg + Menu.accent_style + "│") + draw(self.overlay_left, y, Menu.accent_bg + Menu.accent_fg + Menu.accent_alt_style + "│") + draw(self.overlay_left + self.overlay_width - 1, self.overlay_top + self.overlay_height - 1, + Menu.accent_bg + Menu.accent_fg + Menu.accent_style + "┘") + draw(self.overlay_left + self.overlay_width - 1, self.overlay_top, Menu.accent_bg + Menu.accent_fg + Menu.accent_style + "┐") + draw(self.overlay_left, self.overlay_top + self.overlay_height - 1, Menu.accent_bg + Menu.accent_fg + Menu.accent_alt_style + "└") + draw(self.overlay_left, self.overlay_top, Menu.accent_bg + Menu.accent_fg + Menu.accent_alt_style + "┌") + + # Draw the description at the top of the overlay box + if len(self.desc) > self.overlay_width: + half = len(self.desc.split()) // 2 + first_line = " ".join(word for word in self.desc.split()[:half]) + second_line = " ".join(word for word in self.desc.split()[half:]) + draw(self.desc_x, self.desc_y, Menu.overlay_bg + Menu.overlay_fg + Menu.overlay_style + first_line) + draw(self.desc_x, self.desc_y, Menu.overlay_bg + Menu.overlay_fg + Menu.overlay_style + second_line) + else: + draw(self.desc_x, self.desc_y, Menu.overlay_bg + Menu.overlay_fg + Menu.overlay_style + self.desc) + + if self.is_prompt: + wrapped = self.word_wrapped_text(self.prompt_msg) + for i, line in enumerate(wrapped): + draw(self.msg_x, self.msg_y + i, + Menu.overlay_bg + Menu.overlay_fg + Menu.overlay_style + line) + + + def draw_buttons(self): # Redraw only the menu buttons + self.update_dimensions() + + options_drawn = 0 + for index, option in enumerate(self.buttons): + text = self.buttons[index][0] + + if self.is_prompt: + option_x = self.overlay_left + ((self.overlay_width // (len(self.buttons) + 1)) * (index + 1)) - ( + len(text) // 2) # (Menu.X_MAX // 2) - (len(text) // 2) # In the middle + option_y = self.overlay_top + self.overlay_height - 2 + else: + option_x = (Menu.X_MAX // 2) - (len(text) // 2) # In the middle + option_y = (Menu.Y_MAX // 2 - (len(self.buttons) // 2) + options_drawn) + + box_x = (Menu.X_MAX // 2) - (self.overlay_width // 2) + options_drawn += 1 + + if index == self.button_index: + if not self.is_prompt: + draw(box_x + 1, option_y, + Menu.selected_bg + Menu.selected_fg + Menu.selected_style + "(" + str( + index + 1) + ")") + draw(option_x, option_y, Menu.selected_bg + Menu.selected_fg + Menu.selected_style + text) + else: + if not self.is_prompt: + draw(box_x + 1, option_y, + Menu.overlay_bg + Menu.overlay_fg + Menu.overlay_style + "(" + str(index + 1) + ")") + draw(option_x, option_y, Menu.overlay_bg + Menu.overlay_fg + Menu.overlay_style + text) + + def draw_inputs(self): + self.update_dimensions() + + inputs_drawn = 0 + for index, inp in enumerate(self.inputs): + text = self.inputs[index][0] + + inputs_drawn += 1 + + if index == self.input_index: + draw(self.input_x, self.input_y + inputs_drawn - 1, Menu.selected_bg + Menu.selected_fg + Menu.selected_style + text) + else: + draw(self.input_x, self.input_y + inputs_drawn - 1, Menu.overlay_bg + Menu.overlay_fg + Menu.overlay_style + text) + if "password" not in inp[0].lower(): + draw(self.input_x + len(text) + 1, self.input_y + inputs_drawn - 1, Menu.input_bg + Menu.input_fg + Menu.input_style + inp[1]) + else: + draw(self.input_x + len(text) + 1, self.input_y + inputs_drawn - 1, + Menu.input_bg + Menu.input_fg + Menu.input_style + "*"*len(inp[1])) + def move_down_button(self): + if len(self.buttons) > 1: + if self.button_index >= len(self.buttons) - 1: + self.button_index = 0 + else: + self.button_index += 1 + + def move_up_button(self): + if len(self.buttons) > 1: + if self.button_index <= 0: + self.button_index = len(self.buttons) - 1 + else: + self.button_index -= 1 + + def move_down_input(self): + if len(self.inputs) > 1: + if self.input_index >= len(self.inputs) - 1: + self.input_index = 0 + else: + self.input_index += 1 + + def move_up_input(self): + if len(self.inputs) > 1: + if self.input_index <= 0: + self.input_index = len(self.inputs) - 1 + else: + self.input_index -= 1 + + def start(self): + self.draw_menu() + + while not self.has_been_killed: + if self.uid != Menu.menu_being_displayed: + self.draw_menu() + Menu.menu_being_displayed = self.uid + + if msvcrt.kbhit(): + key = ord(msvcrt.getch()) + + if key == 13: # enter key + f = self.buttons[self.button_index][1] + f() # execute the function stored by the button + + elif key == 0: # function key + key = ord(msvcrt.getch()) + if key == 59: # F1 + self.draw_menu() + continue + + if not self.is_prompt: + if key == 224: # arrow key + key = ord(msvcrt.getch()) + if key == 80: # down + self.move_down_button() + self.draw_buttons() + elif key == 72: # up + self.move_up_button() + self.draw_buttons() + elif key == 9: # tab + self.move_down_button() + self.draw_buttons() + else: + if key == 224: # arrow key + key = ord(msvcrt.getch()) + if key == 75: # left + self.move_up_button() + self.draw_buttons() + elif key == 77: # right + self.move_down_button() + self.draw_buttons() + if len(self.inputs) > 0: + if key == 80: # down or tab + self.move_down_input() + self.draw_inputs() + elif key == 72: # up + self.move_up_input() + self.draw_inputs() + elif str(chr(key)).lower() in self.kb_chars and len(self.inputs) > 0: + if self.input_x + len(self.inputs[self.input_index][1]) + len(self.inputs[self.input_index][0]) + 1 < self.overlay_left + self.overlay_width - 2: + char = str(chr(key)) + self.inputs[self.input_index][1] += char + self.draw_inputs() + elif key == 8: # backspace + if len(self.inputs[self.input_index][1]) > 0: + self.inputs[self.input_index][1] = self.inputs[self.input_index][1][:-1] + + x = (Menu.X_MAX // 2) - (self.overlay_width // 4) + len(self.inputs[self.input_index][1]) + len(self.inputs[self.input_index][0]) + 1 + y = self.input_y + self.input_index + draw(x, y, Menu.overlay_bg + " ") + + self.draw_inputs() + elif key == 9: # tab + self.move_down_input() + self.draw_inputs() + self.draw_debug_message() + + def quit(self): + self.has_been_killed = True + + def add_text(self, text): + self.is_prompt = True + self.prompt_msg += text + r"\n" + + def set_prompt(self, msg): + self.is_prompt = True + self.prompt_msg = msg + + def word_wrapped_text(self, text): + wrapped_lines = [] + line = "" + n_words = len(text.split(" ")) + + if len(text.split(r"\n")) > 1: + for words in text.split(r"\n"): + wrapped_lines.append(words) + else: + for index, word in enumerate(text.split()): + if len(line + word + " ") >= self.overlay_width - 2: + if line[-1] == " ": + line = line[:-1] + wrapped_lines.append(line) + line = word + " " + elif index + 1 == n_words: + line += word + wrapped_lines.append(line) + else: + line += word + " " + + return wrapped_lines \ No newline at end of file diff --git a/pymenu.py b/pymenu.py deleted file mode 100644 index 394ead8..0000000 --- a/pymenu.py +++ /dev/null @@ -1,506 +0,0 @@ -from colorama import init, Fore, Back, Style -import msvcrt, os - -# A Python Framework for the Creation of Text-Based Menu Interfaces -# Created and developed by Sam Scott, aged 17 years and 7 months -# 13-10-2016 - -# GitHub Repo: https://github.com/Nytra/PyMenu-Framework -# Here you'll be able to find all of the latest updates as soon as they're available - -init(autoreset=True) - -class Menu: - - prog_title = "PyMenu V0.94a" - - overlay_bg = Back.WHITE - overlay_fg = Fore.BLACK - overlay_style = Style.NORMAL - - outer_bg = Back.BLUE - outer_fg = Fore.WHITE - outer_style = Style.BRIGHT - - def __init__(self, title="", desc="", footer_text="Use the arrow keys to highlight an option and then press enter to select it."): - self.options = [] - - self.msg_style = Style.NORMAL - - self.shadow_bg = Back.BLACK - self.shadow_fg = Fore.WHITE - self.shadow_style = Style.BRIGHT - - self.option_style = Style.NORMAL - - self.selected_bg = Back.BLUE - self.selected_fg = Fore.WHITE - self.selected_style = Style.BRIGHT - - self.title_fg = Fore.WHITE - self.title_style = Style.BRIGHT - - self.footer_fg = Fore.WHITE - self.footer_style = Style.BRIGHT - - self.desc_style = Style.NORMAL - self.desc_fg = Fore.BLUE - - self.accent_fg = Fore.WHITE - self.accent_style = Style.BRIGHT - - self.title = title # The menu title - - self.footer_text = footer_text - - self.selected = 0 # The index of the option that is currently selected - self.desc = desc # A simple description of the menu - self.alive = True - - self.ok_dialog = False - self.text_box = False - self.dialog_msg = "" - self.buffer = "" # keyboard buffer - - self.get_kb_input = False - - self.lines = [] # used for the text editor - - self.update_dimensions() - - self.cursor_x = self.msg_x - self.cursor_y = self.msg_y - - def set_program_title(self, name): - Menu.prog_title = name - - def update_dimensions(self): - - self.outer_bg = Menu.outer_bg - self.outer_fg = Menu.outer_fg - self.outer_style = Menu.outer_style - self.overlay_bg = Menu.overlay_bg - self.overlay_fg = Menu.overlay_fg - self.overlay_style = Menu.overlay_style - - # Get the latest terminal dimensions - - #self.X_MAX, self.Y_MAX = shutil.get_terminal_size((80, 20)).columns, shutil.get_terminal_size((80, 20)).lines + 1 - self.X_MAX, self.Y_MAX = os.get_terminal_size().columns, os.get_terminal_size().lines + 1 - - #os.system('mode con: cols=%d lines=%d' % (self.X_MAX, self.Y_MAX)) - - self.option_width = self.X_MAX // 3 - self.option_height = 1 - - if self.ok_dialog: - wrapped = self.word_wrapped_text(self.dialog_msg) - self.overlay_height = len(wrapped) + 4 - self.overlay_width = self.X_MAX // 2 # len(wrapped[0]) + 4 - # for line in wrapped: - # if len(line) > self.overlay_width - 2: - # self.overlay_width = len(line) + 2 - elif self.text_box and not self.ok_dialog: - self.overlay_width = self.X_MAX - 8 - self.overlay_height = self.Y_MAX - 10 - else: - self.overlay_width = self.X_MAX // 2 - self.overlay_height = self.Y_MAX // 2 - - self.overlay_top = (self.Y_MAX // 2) - (self.overlay_height // 2) # top y coord - self.overlay_bottom = self.overlay_top + self.overlay_height # bottom y coord - - self.overlay_left = (self.X_MAX // 2) - (self.overlay_width // 2) # leftmost x coord - #self.overlay_right = self.X_MAX - (self.overlay_width // 2) # rightmost x coord - self.overlay_right = self.overlay_left + self.overlay_width - - self.title_x = (self.X_MAX // 2) - (len(self.title) // 2) - self.title_y = (self.Y_MAX // 10) - - self.desc_x = self.overlay_left + 1#self.overlay_right - (self.overlay_width // 2) - (len(self.desc) // 2)#(self.X_MAX // 2) - (len(self.desc) // 2) - self.desc_y = self.overlay_top #// 2 + self.title_y // 2 - - self.msg_x = self.overlay_left + 1 - self.msg_y = self.overlay_top + 1 - - #if self.text_box and self.ok_dialog: - #self.msg_x = self.overlay_left + (self.overlay_width // 4) - #self.msg_y = self.overlay_top + 2 - - #self.buffer = [] - #for y in range(self.overlay_top + 1, self.overlay_bottom - 2): - #self.buffer.append([0]*self.overlay_width-2) - - def add(self, text, target): - - # Target is a function pointer - self.options.append([text,target]) - - def put(self, coords, text): - - # Generate the ANSI code for placing text at position x,y - x,y = coords - pos = lambda x,y: '\x1b[%d;%dH' % (y,x) - - # Display the text at position x,y - print(pos(x,y) + text, end = "") - - def redraw(self): # redraw the whole menu - - if not self.alive: - return - - self.update_dimensions() - - for y in range(1, self.Y_MAX): - #for x in range(1, self.X_MAX): - self.put([1,y], self.outer_bg + " " * (self.X_MAX - 1)) - - self.draw_overlay() - self.draw_buttons() - - def draw_overlay(self): # redraw only the menu overlay and skip the outer background - self.update_dimensions() - - # Draw the menu title at the top - self.put([self.title_x, self.title_y], self.outer_bg + self.title_fg + self.title_style + self.title) - - # Draw the white overlay background - for y in range(self.overlay_top, self.overlay_bottom): - #for x in range(self.overlay_left, self.overlay_right): - self.put([self.overlay_left, y], self.overlay_bg + " " * self.overlay_width) - - # Draw the overlay shadow - for y in range(self.overlay_top + 1, self.overlay_bottom): - self.put([self.overlay_right, y], self.shadow_bg + " ") - for x in range(self.overlay_left + 1, self.overlay_right + 1): - self.put([x, self.overlay_bottom], self.shadow_bg + " ") - - # Draw the footer text - self.put([1, self.Y_MAX-1], self.outer_bg + self.footer_fg + self.footer_style + self.footer_text) - - # Draw the program title in the top left - self.put([self.X_MAX - len(self.prog_title), self.Y_MAX - 1], self.outer_bg + self.title_fg + self.title_style + Menu.prog_title) - - # Draw accenting - #for x in range(1, self.X_MAX): - #self.put([x, 2], self.outer_bg + self.accent_fg + self.accent_style + "═") - #for x in range(1, self.X_MAX): - #self.put([x, self.title_y + 2], self.outer_bg + self.accent_fg + self.accent_style + "═") - for x in range(self.overlay_left + 1, self.overlay_right): - self.put([x, self.overlay_bottom - 1], self.overlay_bg + Fore.BLACK + Style.NORMAL + "─") - self.put([x, self.overlay_top], self.overlay_bg + Fore.BLACK + Style.BRIGHT + "─") - for y in range(self.overlay_top + 1, self.overlay_bottom - 1): - self.put([self.overlay_right - 1, y], self.overlay_bg + Fore.BLACK + Style.NORMAL + "│") - self.put([self.overlay_left, y], self.overlay_bg + Fore.BLACK + Style.BRIGHT + "│") - self.put([self.overlay_right - 1, self.overlay_bottom - 1], self.overlay_bg + Fore.BLACK + Style.NORMAL + "┘") - self.put([self.overlay_right - 1, self.overlay_top], self.overlay_bg + Fore.BLACK + Style.NORMAL + "┐") - self.put([self.overlay_left, self.overlay_bottom - 1], self.overlay_bg + Fore.BLACK + Style.BRIGHT + "└") - self.put([self.overlay_left, self.overlay_top], self.overlay_bg + Fore.BLACK + Style.BRIGHT + "┌") - - # Draw the description at the top of the overlay box - if len(self.desc) > self.overlay_width: - half = len(self.desc.split()) // 2 - first_line = " ".join(word for word in self.desc.split()[:half]) - second_line = " ".join(word for word in self.desc.split()[half:]) - self.put([self.desc_x, self.desc_y], self.overlay_bg + self.desc_fg + self.desc_style + first_line) - self.put([self.desc_x, self.desc_y], self.overlay_bg + self.desc_fg + self.desc_style + second_line) - else: - self.put([self.desc_x, self.desc_y], self.overlay_bg + self.desc_fg + self.desc_style + self.desc) - - if self.dialog_msg: - - wrapped = self.word_wrapped_text(self.dialog_msg) - for i, line in enumerate(wrapped): - self.put([self.msg_x, self.msg_y + i], - self.overlay_bg + self.overlay_fg + Style.NORMAL + line) - - # Draw the footer text - self.put([1, self.Y_MAX - 1], self.outer_bg + self.footer_fg + self.footer_style + self.footer_text) - - # Draw the buttons just in case something needs to be changed - self.draw_buttons() - - - def draw_buttons(self): # Redraw only the menu buttons - self.update_dimensions() - - options_drawn = 0 - for index, option in enumerate(self.options): - text = self.options[index][0] - - if self.ok_dialog or self.text_box: - option_x = self.overlay_left + ((self.overlay_width // (len(self.options) + 1)) * (index + 1)) - ( - len(text) // 2) # (self.X_MAX // 2) - (len(text) // 2) # In the middle - option_y = self.overlay_bottom - 2 - elif self.text_box: - pass - else: - option_x = (self.X_MAX // 2) - (len(text) // 2) # In the middle - option_y = (self.Y_MAX // 2 - (len(self.options) // 2) + options_drawn) - - box_x = (self.X_MAX // 2) - (self.overlay_width // 2) - options_drawn += 1 - - if not self.text_box or self.ok_dialog: - if index == self.selected: - if not self.ok_dialog: - self.put([box_x + 1, option_y], self.selected_bg + self.selected_fg + self.selected_style + "(" + str(index + 1) + ")") - self.put([option_x, option_y], self.selected_bg + self.selected_fg + self.selected_style + text) - else: - if not self.ok_dialog: - self.put([box_x + 1, option_y], self.overlay_bg + self.overlay_fg + "(" + str(index + 1) + ")") - self.put([option_x, option_y], self.overlay_bg + self.overlay_fg + self.option_style + text) - - def move_down(self): - if len(self.options) > 1: - if self.selected >= len(self.options) - 1: - self.selected = 0 - else: - self.selected += 1 - - def move_up(self): - if len(self.options) > 1: - if self.selected <= 0: - self.selected = len(self.options) - 1 - else: - self.selected -= 1 - - def key_buffer(self, char): - #self.buffer[self.cursor_y - self.overlay_top + 1][self.cursor_x - self.overlay_left + 1] = char - self.buffer += char - #self.msg(self.buffer) - - #x = self.msg_x - #y = self.msg_y - - #for c in self.buffer: - - c = char - - if self.cursor_x > self.overlay_right - 2: - if self.cursor_y != self.overlay_bottom - 2: - self.cursor_x = self.msg_x - self.cursor_y += 1 - else: - self.cursor_x -= 1 # THIS IS HACKY I DONT LIKE IT - - #if self.cursor_y >= self.overlay_bottom - 1: - #self.cursor_y = self.overlay_bottom - 2 - - self.put([self.cursor_x, self.cursor_y], self.overlay_bg + self.overlay_fg + c) - self.cursor_x += 1 - - def backspace(self): - self.cursor_x -= 1 - if self.cursor_x <= self.overlay_left: - - if self.cursor_y == self.msg_y: - self.cursor_x = self.msg_x - else: - self.cursor_x = self.overlay_right - 2 - self.cursor_y -= 1 - - if self.cursor_y <= self.overlay_top: - self.cursor_y = self.overlay_top + 1 - - self.put([self.cursor_x, self.cursor_y], self.overlay_bg + " ") - self.put([self.cursor_x, self.cursor_y], self.overlay_bg + "") - - def get_entry(self): - return self.buffer - - def start(self): - self.redraw() - while self.alive: - - if [self.X_MAX, self.Y_MAX] != [os.get_terminal_size().columns, os.get_terminal_size().lines + 1]: #[shutil.get_terminal_size((80, 20)).columns, shutil.get_terminal_size((80, 20)).lines + 1]: - self.redraw() - - self.cursor_x = self.msg_x - self.cursor_y = self.msg_y - - if msvcrt.kbhit(): - key = ord(msvcrt.getch()) - - # BEGIN TEXT ENTRY CODE - - if self.get_kb_input: - if key == 8: # Backspace - self.buffer = self.buffer[:-1] # Remove a character from the buffer - #self.msg(self.buffer) - self.backspace() - if str(chr(key)).lower() in "abcdefghijklmnopqrstuvwxyz,./?!\"\'£;:$%^&*()[]{}@#~/\\<>|-_=+¬`¦1234567890 ": - char = str(chr(key)) - self.key_buffer(char) - if key == 156: # 156 = £ - self.key_buffer("£") - if key == 27: - self.quit() - if key == 13: # enter, go to next line - if self.ok_dialog: - f = self.options[self.selected][1] - f() - #self.move_cursor_down() - else: - if self.cursor_y != self.overlay_bottom - 2: - self.cursor_y += 1 - self.cursor_x = self.msg_x - if self.cursor_y >= self.overlay_bottom - 1: - self.cursor_y = self.overlay_bottom - 2 - self.move_cursor_to([self.cursor_x, self.cursor_y]) - if key == 0: # function key - key = ord(msvcrt.getch()) - if key == 59: # F1 - pass # save file - - # END TEXT ENTRY CODE - - if key == 13 and not self.text_box: # enter key - # self.put([1, 1], "") - f = self.options[self.selected][1] - f() - - if not self.ok_dialog: - if key == 224: # arrow key - key = ord(msvcrt.getch()) - if key == 80: # down - self.move_down() - self.draw_buttons() - elif key == 72: # up - self.move_up() - self.draw_buttons() - else: - if key == 224: # arrow key - key = ord(msvcrt.getch()) - if key == 75: # left - self.move_up() - self.draw_buttons() - elif key == 77: # right - self.move_down() - self.draw_buttons() - - def quit(self): - self.alive = False - - def set_dialog_msg(self, msg): - #self.update_dimensions() - #self.cursor_y = self.msg_y + 1 - self.ok_dialog = True - self.dialog_msg = msg - - def set_outer_bg(self, c): - Menu.outer_bg = c - self.redraw() - - def set_overlay_bg(self, c): - Menu.overlay_bg = c - self.redraw() - - def set_title(self, title): - self.title = title - self.redraw() - - def set_desc(self, desc): - self.desc = desc - self.redraw() - - def set_footer(self, text): - self.footer_text = text - self.redraw() - - def word_wrapped_text(self, text): - wrapped_lines = [] - line = "" - #lines_done = 0 - words = len(text.split()) - for index, word in enumerate(text.split()): - #word = word.strip() - if r"\n" in word: - line += word.split(r"\n")[0] + " " - wrapped_lines.append(line) - line = word.split(r"\n")[1] + " " - else: - if len(line + word + " ") >= self.overlay_width - 2: - wrapped_lines.append(line) - line = word + " " - #lines_done += 1 - elif index + 1 == words: - line += word + " " - wrapped_lines.append(line) - else: - line += word + " " - return wrapped_lines - - def set_text_box(self, message=""): - self.text_box = True - self.get_kb_input = True - - if message: - self.set_desc(message) - self.set_dialog_msg("") - self.update_dimensions() - self.cursor_x = self.msg_x - self.cursor_y = self.msg_y - self.move_cursor_to([self.cursor_x, self.cursor_y]) - #self.overlay_height += 4 - else: - self.update_dimensions() - self.cursor_x = self.msg_x - self.cursor_y = self.msg_y - - def move_cursor_up(self, n=1): - code = lambda n: '\x1b[%dA' % n - print(code(n), end="") - - def move_cursor_down(self, n=1): - code = lambda n: '\x1b[%dB' % n - print(code(n), end="") - - def move_cursor_back(self, n=1): - code = lambda n: '\x1b[%dD' % n - print(code(n), end = "") - - def move_cursor_right(self, n=1): - code = lambda n: '\x1b[%dC' % n - print(code(n), end="") - - def move_cursor_to(self, coords): - x,y = coords - pos = lambda x, y: '\x1b[%d;%dH' % (y, x) - print(pos(x,y), end="") - - def msg(self, text): - x,y = self.msg_x, self.msg_y - - if len(text) > self.overlay_width - 2: - - wrapped_text = self.word_wrapped_text(text) - if len(wrapped_text) > 1: - for i, line in enumerate(wrapped_text): - for j in range(self.overlay_left + 1, self.overlay_right - 1): - self.put([j, y + i], self.overlay_bg + " ") - self.put([x, y + i], self.overlay_bg + self.overlay_fg + self.msg_style + line) - else: - clean = False - for char in text: - if y > self.msg_y and not clean: - clean = True - for j in range(self.overlay_left + 1, self.overlay_right - 1): - self.put([j, y], self.overlay_bg + " ") - if x > self.overlay_width - 1: - y += 1 - clean = False - x = self.msg_x - for j in range(self.overlay_left + 1, self.overlay_right - 1): - self.put([j, y], self.overlay_bg + " ") - self.put([x, y], self.overlay_bg + self.overlay_fg + self.msg_style + char) - x += 1 - - #wrapped_text = self.word_wrapped_text(text) - - else: - for j in range(self.overlay_left + 1, self.overlay_right - 1): - self.put([j, y], self.overlay_bg + " ") - - self.put([x, y], self.overlay_bg + self.overlay_fg + self.msg_style + text) \ No newline at end of file