11import curses
2+ from abc import ABC
23from curses import wrapper , use_default_colors , curs_set
3- from typing import List
4- from pymenu .misc import Item , Keyboard
4+ from typing import List , Callable
5+ from pymenu .misc import Item , Keyboard , FunctionalItem
56
67
7- class SelectorMenu :
8+ class __BaseMenu ( ABC ) :
89
9- def __init__ (self , options : List [ str ] , title : str = '' , default_index : int = 0 , indicator : str = "->" ):
10+ def __init__ (self , options : list , title : str = '' , default_index : int = 0 , indicator : str = "->" ):
1011 if len (options ) == 0 :
1112 raise Exception ('must contains 1 element' )
1213 if 0 <= default_index >= len (options ):
1314 raise ValueError ('default_index should be less than the length of options' )
14-
15- self . _options = options
15+ if not len ( indicator ):
16+ raise Exception
1617 self ._title = title
18+ self ._options = options
1719 self ._index = default_index
1820 self ._indicator = indicator
1921 self ._scroll_top = 0
2022
21- def get_selected (self ) -> Item :
22- return Item (name = self ._options [self ._index ], index = self ._index )
23-
24- def get_option_lines (self ):
25- lines = []
26- for index , option in enumerate (self ._options ):
27- if index == self ._index :
28- prefix = self ._indicator
29- else :
30- prefix = len (self ._indicator ) * ' '
31- lines .append (f'{ prefix } { option } ' )
32- return lines
23+ self ._config = {
24+ Keyboard .UP : self .go_up ,
25+ Keyboard .DOWN : self .go_down ,
26+ }
27+ self ._returnable_config = {
28+ Keyboard .ENTER : self .get_selected
29+ }
30+ self ._titles = self ._get_titles ()
3331
34- def get_lines (self ):
35- title_lines = self ._title .split ('\n ' )
36- current_line_y = self ._index + len (title_lines ) + 1
37- lines = title_lines + self .get_option_lines ()
38- return lines , current_line_y
32+ # region Ideal
3933
4034 def go_up (self ):
4135 self ._index = (self ._index - 1 ) % len (self ._options )
4236
4337 def go_down (self ):
4438 self ._index = (self ._index + 1 ) % len (self ._options )
4539
40+ def input (self ):
41+ return wrapper (self .run_loop )
42+
43+ def _get_titles (self ):
44+ return self ._options
45+
46+ def get_selected (self ):
47+ raise NotImplementedError
48+
49+ def run_loop (self , screen ):
50+ use_default_colors ()
51+ curs_set (0 )
52+ while True :
53+ self .draw (screen )
54+ key = screen .getch ()
55+ for keys in self ._config .keys ():
56+ if key in keys :
57+ self ._config [keys ]()
58+ break
59+ for keys in self ._returnable_config .keys ():
60+ if key in keys :
61+ return self ._returnable_config [keys ]()
62+
63+ # endregion
64+
4665 def draw (self , screen ) -> None :
4766 screen .clear ()
4867
4968 max_y , max_x = screen .getmaxyx ()
5069
51- lines , current_line = self . get_lines ()
70+ y = 0
5271
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
57-
58- lines_to_draw = lines [self ._scroll_top :self ._scroll_top + max_y ]
72+ for line in self ._title .split ('\n ' ):
73+ screen .addnstr (y , 0 , line , max_x )
74+ y += 1
5975
60- for y , line in enumerate (lines_to_draw ):
61- screen .addnstr (y , 0 , line , max_x - 2 )
76+ for local_y , line in enumerate (self ._title ):
77+ if local_y == self ._index :
78+ line = f'{ self ._indicator } { line } '
79+ else :
80+ line = f'{ " " * len (self ._indicator )} { line } '
81+ screen .addnstr (y , 0 , line , max_x )
6282 y += 1
6383
6484 screen .refresh ()
6585
66- def run_loop (self , screen ):
67- config = {
68- Keyboard .UP : self .go_up ,
69- Keyboard .DOWN : self .go_down ,
70- }
71- returnable_config = {
72- Keyboard .ENTER : self .get_selected ,
73- }
74- while True :
75- self .draw (screen )
76- key = screen .getch ()
77- for keys in config .keys ():
78- if key in keys :
79- config [keys ]()
80- break
81- for keys in returnable_config .keys ():
82- if key in keys :
83- return returnable_config [keys ]()
8486
85- def __start (self , screen ):
86- use_default_colors ()
87- curs_set (0 )
88- return self .run_loop (screen )
87+ class SelectorMenu (__BaseMenu ):
8988
90- def input (self ):
91- return wrapper ( self . __start )
89+ def __init__ (self , options : List [ str ], title : str = '' , default_index : int = 0 , indicator : str = "->" ):
90+ super (). __init__ ( options , title , default_index , indicator )
9291
92+ def get_selected (self ) -> Item :
93+ return Item (name = self ._options [self ._index ], index = self ._index )
9394
94- class MultiSelectorMenu (SelectorMenu ):
95+
96+ class MultiSelectorMenu (__BaseMenu ):
9597
9698 def __init__ (self , options : List [str ], count : int , title : str = '' , default_index : int = 0 , indicator : str = "->" ):
9799 if 1 <= count > len (options ):
98100 raise Exception
99101 self .count = count
100102 self .selected : list [int ] = []
101103 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 ,
104+ self ._config .update ({
107105 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 ]()
106+ })
122107
123108 def select (self ):
124109 if self ._index in self .selected :
@@ -138,14 +123,14 @@ def draw(self, screen) -> None:
138123
139124 max_y , max_x = screen .getmaxyx ()
140125
141- lines , current_line = self .get_lines ( )
126+ current_line = len ( self ._title . split ( ' \n ' ) )
142127
143128 if current_line <= self ._scroll_top :
144129 self ._scroll_top = 0
145130 elif current_line - self ._scroll_top > max_y :
146131 self ._scroll_top = current_line - max_y
147132
148- lines_to_draw = lines [self ._scroll_top :self ._scroll_top + max_y ]
133+ lines_to_draw = self . _titles [self ._scroll_top :self ._scroll_top + max_y ]
149134
150135 for y , line in enumerate (lines_to_draw ):
151136 if y - len (self ._title .split ('\n ' )) in self .selected :
@@ -154,4 +139,16 @@ def draw(self, screen) -> None:
154139 screen .addnstr (y , 0 , line , max_x - 2 )
155140 y += 1
156141
157- screen .refresh ()
142+ screen .refresh ()
143+
144+
145+ class FunctionalMenu (__BaseMenu ):
146+
147+ def __init__ (self , options : List [FunctionalItem ], title : str = '' , default_index : int = 0 , indicator : str = "->" ):
148+ super ().__init__ (options , title , default_index , indicator )
149+
150+ def get_selected (self ) -> Callable :
151+ return self ._options [self ._index ].func
152+
153+ def _get_titles (self ):
154+ return [option .name for option in self ._options ]
0 commit comments