-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmenu.py
253 lines (222 loc) · 8.92 KB
/
menu.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# Version: 1.0.5
"""
> Cloudzik <
Interactive-Python-Menu
1.0.5
16.11.2023
https://github.com/Cloudzik1337/Interactive-Python-Menu
Changelog:
1.0.5
This version strongly focuses on optimization and code readability.
1.0.4 pylint rating was 7.5/10 now in 1.0.5 its 9.79/10
------------------------------------------------------------------
Your code has been rated at 9.78/10 (previous run: 7.48/10, +2.30)
------------------------------------------------------------------
"""
# Import necessary libraries
import os
import keyboard
from pystyle import Center
# Class definition for the menu colors
class _Colors:
"""Menu colors"""
@staticmethod
def _color_code(code):
"""Static method to format color codes"""
return f'\033[{code}m'
ENDC: str = _color_code(0)
BOLD: str = _color_code(1)
UNDERLINE: str = _color_code(4)
BLACK: str = _color_code(30)
RED: str = _color_code(31)
GREEN: str = _color_code(32)
YELLOW: str = _color_code(33)
BLUE: str = _color_code(34)
MAGENTA: str = _color_code(35)
CYAN: str = _color_code(36)
WHITE: str = _color_code(37)
REDBG: str = _color_code(41)
GREENBG: str = _color_code(42)
YELLOWBG: str = _color_code(43)
BLUEBG: str = _color_code(44)
MAGENTABG: str = _color_code(45)
CYANBG: str = _color_code(46)
WHITEBG: str = _color_code(47)
GREY: str = _color_code(90)
REDGREY: str = _color_code(91)
GREENGREY: str = _color_code(92)
YELLOWGREY: str = _color_code(93)
BLUEGREY: str = _color_code(94)
MAGENTAGREY: str = _color_code(95)
CYANGREY: str = _color_code(96)
WHITEGREY: str = _color_code(97)
GREYBG: str = _color_code(100)
REDGREYBG: str = _color_code(101)
GREENGREYBG: str = _color_code(102)
YELLOWGREYBG: str = _color_code(103)
BLUEGREYBG: str = _color_code(104)
MAGENTAGREYBG: str = _color_code(105)
CYANGREYBG: str = _color_code(106)
WHITEGREYBG: str = _color_code(107)
# Class definition for the menu styles
class _Styles:
"""Menu styles"""
DEFAULT: int = 1
SELECTED: int = 2
ARROW: int = 3
CENTERED: int = 11
CENTEREDSELECTED: int = 22
ARROWCENTERED: int = 33
# Create instances of the classes
Colors = _Colors()
Styles = _Styles()
# Class definition for the menu system
class Menu:
"""Menu system"""
def __init__(self,
options: list = None,
color: str = Colors.CYAN,
style: int = Styles.DEFAULT,
pretext:str = None):# Use ANSI escape code for color
"""
:options: list of menu options format: ["Option 1", "Option 2", "Option 3"]
:color: ANSI escape code for color format: Colors.CYAN
:style: menu style format: Styles.DEFAULT or Styles.SELECTED
or Styles.CENTERED or Styles.CENTEREDSELECTED
:pretext: text to display before the menu otherwise it will earesed
To get the selected option, use menu.selected or menu.selected_index
Example:
import menu
options = ["Option 1", "Option 2", "Option 3"]
my_menu = menu.Menu(options=options, style=menu.Styles.SELECTED)
User_choice = my_menu.launch(response="String") # can be "String" or "Index"
# There are two ways to get the selected option
# 1. Get the index of the selected option
print(my_menu.selected_index)
# 2. Get the string of the selected option
print(my_menu.selected)
# also you can get the index or string by using the variable User_choice
print(User_choice) # change response to "Index" or "String" to get the index or string
"""
self.pretext = str(pretext)
self.style = style
self.options = options
self.color = color
self.index = 0
if options is not None:
self.index_max = len(options)
self.selected = None
self.json = {}
self.selected_index = None
self.last_known_index = 1
def launch(self, response:str = "String"):
"""Launch the menu
:response: Let user decide if menu should return index or string"""
return self._create_menu(response)
def _create_menu(self, response:str = "String"):
# Create a dictionary mapping index to menu options
for index, option in enumerate(self.options):
self.json[index] = option
# Set up hotkeys for navigation
keyboard.add_hotkey('up', self._up, suppress=True)
keyboard.add_hotkey('down', self._down, suppress=True)
keyboard.add_hotkey('enter', self._enter, suppress=True)
keyboard.add_hotkey('right', self._enter, suppress=True)
# Display the menu and wait for user input
self._display()
# Unhook all hotkeys after menu display
keyboard.unhook_all()
if response == "String":
return self.selected
return self.selected_index
def _up(self):
# Move the selection index up
self.index = (self.index - 1) % self.index_max
def _down(self):
# Move the selection index down
self.index = (self.index + 1) % self.index_max
def _enter(self):
# Set the selected option based on the current index
self.selected = self.index
def _style_parse_non_center(self):
""" Parse the style and display the menu
1 = default,
2 = > option < style
3 = ↳ option style
11 = 1 but with a centered title
22 = 2 but with a centered title
33 = 3 but with a centered title
"""
# This if statement chain determines the style of the menu
if self.style == 1:
for i in range(self.index_max):
if i == self.index:
print(self.color + self.json[i] + Colors.ENDC)
else:
print(self.json[i])
elif self.style == 2:
for i in range(self.index_max):
if i == self.index:
print(self.color + "> " + self.json[i] + " <" + Colors.ENDC)
else:
print(self.json[i])
elif self.style == 3:
for i in range(self.index_max):
if i == self.index:
print(self.color + "↳ " + self.json[i] + Colors.ENDC)
else:
print(self.json[i])
def _style_parse_center(self):
""" Parse the style and display the menu"""
equaling_space = " "
if self.style == 11:
for i in range(self.index_max):
if i == self.index:
sep = equaling_space
print(Center.XCenter(self.color + sep +self.json[i] + Colors.ENDC))
else:
print(Center.XCenter(self.json[i]))
if self.style == 22:
for i in range(self.index_max):
if i == self.index:
#blank space for centering
sep = equaling_space + "> "
print(Center.XCenter(self.color + sep + self.json[i] + " <" + Colors.ENDC))
else:
print(Center.XCenter(self.json[i]))
if self.style == 33:
for i in range(self.index_max):
if i == self.index:
sep = equaling_space + "↳ "
print(Center.XCenter(self.color + sep + self.json[i] + Colors.ENDC))
else:
print(Center.XCenter(self.json[i]))
def _display(self):
# Initialize index and selected values
self.index = 0
self.selected = None
# Display the menu options with highlighting for the selected option
while self.selected is None:
if self.last_known_index != self.index:
# Update the screen with the current menu state
self.last_known_index = self.index
self.cls()
if self.pretext is not None:
if self.style in [11, 22, 33]:
print(Center.XCenter(self.pretext))
self._style_parse_center()
else:
print(self.pretext)
self._style_parse_non_center()
# Set the selected value to the corresponding menu option
self.selected = self.json[self.selected]
self.selected_index = self.index
def show_example(self):
"""Show an example of each menu style"""
for example_style in [1, 2, 3, 11, 22, 33]:
options = ["Option 1", "Option 2", "Option 3"]
Menu(options=options, style=example_style).launch()
@staticmethod
def cls():
"""Clear the console screen"""
os.system('cls' if os.name == 'nt' else 'printf "\033c"')