diff --git a/cpl.json b/cpl.json index e0f0670b..6816cfba 100644 --- a/cpl.json +++ b/cpl.json @@ -16,12 +16,12 @@ "LicenseName": "MIT", "LicenseDescription": "MIT, see LICENSE for more details.", "Dependencies": [ - "discord.py", "mysql-connector", "SQLAlchemy", "termcolor", "pyfiglet", - "tabulate" + "tabulate", + "pynput" ], "PythonVersion": ">=3.8" }, diff --git a/src/cpl/console/console.py b/src/cpl/console/console.py index ee429d82..d9f88598 100644 --- a/src/cpl/console/console.py +++ b/src/cpl/console/console.py @@ -3,6 +3,8 @@ from collections import Callable from typing import Union, Optional import pyfiglet +from pynput import keyboard +from pynput.keyboard import Key from tabulate import tabulate from termcolor import colored @@ -24,6 +26,15 @@ class Console: _hold_back = False _hold_back_calls: list[ConsoleCall] = [] + _select_menu_items: list[str] = [] + _is_first_select_menu_output = True + _selected_menu_item_index: int = 0 + _selected_menu_item_char: str = '' + _selected_menu_option_foreground_color: ForegroundColorEnum = ForegroundColorEnum.default + _selected_menu_option_background_color: BackgroundColorEnum = BackgroundColorEnum.default + _selected_menu_cursor_foreground_color: ForegroundColorEnum = ForegroundColorEnum.default + _selected_menu_cursor_background_color: BackgroundColorEnum = BackgroundColorEnum.default + """ Properties """ @@ -99,6 +110,41 @@ class Console: args.append(colored(*colored_args)) print(*args, end=end) + @classmethod + def _show_select_menu(cls): + if not cls._is_first_select_menu_output: + for _ in range(0, len(cls._select_menu_items) + 1): + print('\b', end="\r") + else: + cls._is_first_select_menu_output = False + + for i in range(0, len(cls._select_menu_items)): + Console.set_foreground_color(cls._selected_menu_cursor_foreground_color) + Console.set_background_color(cls._selected_menu_cursor_background_color) + Console.write_line(f'{cls._selected_menu_item_char if cls._selected_menu_item_index == i else " "} ') + Console.set_foreground_color(cls._selected_menu_option_foreground_color) + Console.set_background_color(cls._selected_menu_option_background_color) + Console.write(f'{cls._select_menu_items[i]}') + + Console.write_line() + + @classmethod + def _select_menu_key_press(cls, key: Key): + if key == Key.down: + if cls._selected_menu_item_index == len(cls._select_menu_items) - 1: + return + cls._selected_menu_item_index += 1 + cls._show_select_menu() + + elif key == Key.up: + if cls._selected_menu_item_index == 0: + return + cls._selected_menu_item_index -= 1 + cls._show_select_menu() + + elif key == Key.enter: + return False + """ Useful public methods """ @@ -132,7 +178,7 @@ class Console: cls._hold_back_calls.append(ConsoleCall(cls.close)) return - Console.reset() + Console.color_reset() Console.write('\n\n\nPress any key to continue...') Console.read_line() exit() @@ -181,7 +227,7 @@ class Console: return input() @classmethod - def reset(cls): + def color_reset(cls): cls._background_color = BackgroundColorEnum.default cls._foreground_color = ForegroundColorEnum.default @@ -199,6 +245,72 @@ class Console: Console.write_line(table) Console.write('\n') + @classmethod + def select(cls, char: str, message: str, options: list[str], + header_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default, + header_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default, + option_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default, + option_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default, + cursor_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default, + cursor_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default + ) -> str: + + cls._selected_menu_item_char = char + cls._select_menu_items = options + + if option_foreground_color is not None: + cls._selected_menu_option_foreground_color = option_foreground_color + if option_background_color is not None: + cls._selected_menu_option_background_color = option_background_color + + if cursor_foreground_color is not None: + cls._selected_menu_cursor_foreground_color = cursor_foreground_color + if cursor_background_color is not None: + cls._selected_menu_cursor_background_color = cursor_background_color + + Console.set_foreground_color(header_foreground_color) + Console.set_background_color(header_background_color) + Console.write_line(message) + cls._show_select_menu() + + with keyboard.Listener( + on_press=cls._select_menu_key_press, suppress=True) as listener: + listener.join() + + Console.color_reset() + return cls._select_menu_items[cls._selected_menu_item_index] + + @classmethod + def spinner(cls, message: str, call: Callable, *args, text_foreground_color: Union[str, ForegroundColorEnum] = None, + spinner_foreground_color: Union[str, ForegroundColorEnum] = None, + text_background_color: Union[str, BackgroundColorEnum] = None, + spinner_background_color: Union[str, BackgroundColorEnum] = None) -> any: + if cls._hold_back: + cls._hold_back_calls.append(ConsoleCall(cls.spinner, message, call, *args)) + return + + if text_foreground_color is not None: + cls.set_foreground_color(text_foreground_color) + + if text_background_color is not None: + cls.set_background_color(text_background_color) + + cls.write_line(message) + cls.set_hold_back(True) + spinner = SpinnerThread(len(message), spinner_foreground_color, spinner_background_color) + spinner.start() + return_value = call(*args) + spinner.stop_spinning() + cls.set_hold_back(False) + + cls.set_foreground_color(ForegroundColorEnum.default) + cls.set_background_color(BackgroundColorEnum.default) + + for call in cls._hold_back_calls: + call.function(*call.args) + + return return_value + @classmethod def write(cls, *args): if cls._disabled: @@ -250,34 +362,3 @@ class Console: if not cls._is_first_write: cls._output('', end='') cls._output(string, x, y, end='') - - @classmethod - def spinner(cls, message: str, call: Callable, *args, text_foreground_color: Union[str, ForegroundColorEnum] = None, - spinner_foreground_color: Union[str, ForegroundColorEnum] = None, - text_background_color: Union[str, BackgroundColorEnum] = None, - spinner_background_color: Union[str, BackgroundColorEnum] = None) -> any: - if cls._hold_back: - cls._hold_back_calls.append(ConsoleCall(cls.spinner, message, call, *args)) - return - - if text_foreground_color is not None: - cls.set_foreground_color(text_foreground_color) - - if text_background_color is not None: - cls.set_background_color(text_background_color) - - cls.write_line(message) - cls.set_hold_back(True) - spinner = SpinnerThread(len(message), spinner_foreground_color, spinner_background_color) - spinner.start() - return_value = call(*args) - spinner.stop_spinning() - cls.set_hold_back(False) - - cls.set_foreground_color(ForegroundColorEnum.default) - cls.set_background_color(BackgroundColorEnum.default) - - for call in cls._hold_back_calls: - call.function(*call.args) - - return return_value diff --git a/src/tests/custom/console/main.py b/src/tests/custom/console/main.py new file mode 100644 index 00000000..d429b44a --- /dev/null +++ b/src/tests/custom/console/main.py @@ -0,0 +1,27 @@ +import time +from cpl.console import Console, ForegroundColorEnum + + +def test_spinner(): + time.sleep(3) + + +if __name__ == '__main__': + Console.write_line('Hello World') + # Console.spinner('Test:', test_spinner, spinner_foreground_color=ForegroundColorEnum.cyan, + # text_foreground_color='green') + opts = [ + 'Option 1', + 'Option 2', + 'Option 3', + 'Option 4' + ] + selected = Console.select( + '>', + 'Select item:', + opts, + header_foreground_color=ForegroundColorEnum.blue, + option_foreground_color=ForegroundColorEnum.green, + cursor_foreground_color=ForegroundColorEnum.red + ) + Console.write_line(f'You selected: {selected}')