Skip to content

Commit d12a112

Browse files
committed
ADD: MultiSelectorMenu
Was written on YouTube stream: https://www.youtube.com/watch?v=7eHcjkM-mTs
1 parent 4d6590f commit d12a112

3 files changed

Lines changed: 97 additions & 23 deletions

File tree

pymenu/menu.py

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import curses
12
from curses import wrapper, use_default_colors, curs_set
23
from typing import List
34
from pymenu.misc import Item, Keyboard
@@ -7,40 +8,40 @@ class SelectorMenu:
78

89
def __init__(self, options: List[str], title: str = '', default_index: int = 0, indicator: str = "->"):
910
if len(options) == 0:
10-
raise ValueError('must contains 1 element')
11+
raise Exception('must contains 1 element')
1112
if 0 <= default_index >= len(options):
1213
raise ValueError('default_index should be less than the length of options')
1314

14-
self.__options = options
15-
self.__title = title
16-
self.__index = default_index
17-
self.__indicator = indicator
18-
self.__scroll_top = 0
15+
self._options = options
16+
self._title = title
17+
self._index = default_index
18+
self._indicator = indicator
19+
self._scroll_top = 0
1920

2021
def get_selected(self) -> Item:
21-
return Item(name=self.__options[self.__index], index=self.__index)
22+
return Item(name=self._options[self._index], index=self._index)
2223

2324
def get_option_lines(self):
2425
lines = []
25-
for index, option in enumerate(self.__options):
26-
if index == self.__index:
27-
prefix = self.__indicator
26+
for index, option in enumerate(self._options):
27+
if index == self._index:
28+
prefix = self._indicator
2829
else:
29-
prefix = len(self.__indicator) * ' '
30+
prefix = len(self._indicator) * ' '
3031
lines.append(f'{prefix} {option}')
3132
return lines
3233

3334
def get_lines(self):
34-
title_lines = self.__title.split('\n')
35-
current_line_y = self.__index + len(title_lines) + 1
35+
title_lines = self._title.split('\n')
36+
current_line_y = self._index + len(title_lines) + 1
3637
lines = title_lines + self.get_option_lines()
3738
return lines, current_line_y
3839

3940
def go_up(self):
40-
self.__index = (self.__index - 1) % len(self.__options)
41+
self._index = (self._index - 1) % len(self._options)
4142

4243
def go_down(self):
43-
self.__index = (self.__index + 1) % len(self.__options)
44+
self._index = (self._index + 1) % len(self._options)
4445

4546
def draw(self, screen) -> None:
4647
screen.clear()
@@ -49,12 +50,12 @@ def draw(self, screen) -> None:
4950

5051
lines, current_line = self.get_lines()
5152

52-
if current_line <= self.__scroll_top:
53-
self.__scroll_top = 0
54-
elif current_line - self.__scroll_top > max_y:
55-
self.__scroll_top = current_line - max_y
53+
if current_line <= self._scroll_top:
54+
self._scroll_top = 0
55+
elif current_line - self._scroll_top > max_y:
56+
self._scroll_top = current_line - max_y
5657

57-
lines_to_draw = lines[self.__scroll_top:self.__scroll_top + max_y]
58+
lines_to_draw = lines[self._scroll_top:self._scroll_top + max_y]
5859

5960
for y, line in enumerate(lines_to_draw):
6061
screen.addnstr(y, 0, line, max_x - 2)
@@ -88,3 +89,69 @@ def __start(self, screen):
8889

8990
def input(self):
9091
return wrapper(self.__start)
92+
93+
94+
class MultiSelectorMenu(SelectorMenu):
95+
96+
def __init__(self, options: List[str], count: int, title: str = '', default_index: int = 0, indicator: str = "->"):
97+
if 1 <= count > len(options):
98+
raise Exception
99+
self.count = count
100+
self.selected: list[int] = []
101+
super().__init__(options, title, default_index, indicator)
102+
103+
def run_loop(self, screen):
104+
config = {
105+
Keyboard.UP: self.go_up,
106+
Keyboard.DOWN: self.go_down,
107+
Keyboard.SELECT: self.select
108+
}
109+
returnable_config = {
110+
Keyboard.ENTER: self.get_selected,
111+
}
112+
while True:
113+
self.draw(screen)
114+
key = screen.getch()
115+
for keys in config.keys():
116+
if key in keys:
117+
config[keys]()
118+
break
119+
for keys in returnable_config.keys():
120+
if key in keys:
121+
return returnable_config[keys]()
122+
123+
def select(self):
124+
if self._index in self.selected:
125+
self.selected.remove(self._index)
126+
return
127+
if len(self.selected) < self.count:
128+
self.selected.append(self._index)
129+
130+
def get_selected(self) -> list[Item]:
131+
self.selected.sort()
132+
return [Item(index=i, name=self._options[i]) for i in self.selected]
133+
134+
def draw(self, screen) -> None:
135+
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
136+
137+
screen.clear()
138+
139+
max_y, max_x = screen.getmaxyx()
140+
141+
lines, current_line = self.get_lines()
142+
143+
if current_line <= self._scroll_top:
144+
self._scroll_top = 0
145+
elif current_line - self._scroll_top > max_y:
146+
self._scroll_top = current_line - max_y
147+
148+
lines_to_draw = lines[self._scroll_top:self._scroll_top + max_y]
149+
150+
for y, line in enumerate(lines_to_draw):
151+
if y - len(self._title.split('\n')) in self.selected:
152+
screen.addnstr(y, 0, line, max_x - 2, curses.color_pair(1))
153+
continue
154+
screen.addnstr(y, 0, line, max_x - 2)
155+
y += 1
156+
157+
screen.refresh()

pymenu/misc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ class Keyboard(ABC):
1212
UP: Final = (KEY_UP, ord('w'))
1313
DOWN: Final = (KEY_DOWN, ord('s'))
1414
ENTER: Final = (KEY_ENTER, ord('\n'))
15-
SELECT: Final = (ord(' '), )
15+
SELECT: Final = (ord(' '),)

run.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
from pymenu.menu import SelectorMenu
1+
from pymenu.menu import SelectorMenu, MultiSelectorMenu
22

33

44
def main() -> None:
5-
ans = SelectorMenu(['С++', 'Python', 'Pascal'], title='Выбери ЯП мечты').input()
5+
# MultiSelector
6+
menu = MultiSelectorMenu(['С++', 'Python', 'Pascal'], count=2, title='Выбери ЯП мечты')
7+
ans = menu.input()
8+
print(ans)
9+
10+
# Selector
11+
menu = SelectorMenu(['С++', 'Python', 'Pascal'], title='Выбери ЯП мечты')
12+
ans = menu.input()
613
print(ans)
714

815

0 commit comments

Comments
 (0)