Skip to content

Commit 145829f

Browse files
committed
Provide a vertical menu widget
VertMenu is a full screen vertical menu widget that supports efficient item updates and has a callback for menu movement.
1 parent 4432d62 commit 145829f

File tree

4 files changed

+598
-0
lines changed

4 files changed

+598
-0
lines changed

src/prompt_toolkit/widgets/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
SystemToolbar,
3333
ValidationToolbar,
3434
)
35+
from .vertmenu import VertMenu
3536

3637
__all__ = [
3738
# Base.
@@ -59,4 +60,6 @@
5960
# Menus.
6061
"MenuContainer",
6162
"MenuItem",
63+
# Vertical menu.
64+
"VertMenu",
6265
]
+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""Vertical menu widget"""
2+
3+
from typing import Callable, Iterable, Optional, Tuple
4+
5+
from prompt_toolkit.application import get_app
6+
from prompt_toolkit.key_binding import KeyBindings
7+
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
8+
from prompt_toolkit.layout.containers import Container, Window
9+
10+
from .vertmenuuicontrol import Item, VertMenuUIControl
11+
12+
E = KeyPressEvent
13+
14+
15+
class VertMenu:
16+
def __init__(
17+
self,
18+
items: Iterable[Item],
19+
selected_item: Optional[Item] = None,
20+
selected_handler: Optional[
21+
Callable[[Optional[Item], Optional[int]], None]
22+
] = None,
23+
accept_handler: Optional[Callable[[Item], None]] = None,
24+
focusable: bool = True,
25+
max_width: Optional[int] = None,
26+
):
27+
self.accept_handler = accept_handler
28+
self.control = VertMenuUIControl(
29+
items,
30+
focusable=focusable,
31+
key_bindings=self._init_key_bindings(),
32+
selected_handler=selected_handler,
33+
)
34+
self.max_width = max_width
35+
self.window = Window(
36+
self.control, width=self.preferred_width, style=self.get_style
37+
)
38+
self.focus_window: Container = self.window
39+
if selected_item is not None:
40+
self.control.selected_item = selected_item
41+
42+
def _init_key_bindings(self) -> KeyBindings:
43+
kb = KeyBindings()
44+
45+
@kb.add("c-home")
46+
@kb.add("escape", "home")
47+
@kb.add("c-pageup")
48+
def _first(event: E) -> None:
49+
self.control.go_first()
50+
51+
@kb.add("c-end")
52+
@kb.add("escape", "end")
53+
@kb.add("c-pagedown")
54+
def _last(event: E) -> None:
55+
self.control.go_last()
56+
57+
@kb.add("up")
58+
def _up(event: E) -> None:
59+
self.control.go_relative(-1)
60+
61+
@kb.add("down")
62+
def _down(event: E) -> None:
63+
self.control.go_relative(1)
64+
65+
@kb.add("pageup")
66+
def _pageup(event: E) -> None:
67+
w = self.window
68+
if w.render_info:
69+
self.control.go_relative(-len(w.render_info.displayed_lines))
70+
71+
@kb.add("pagedown")
72+
def _pagedown(event: E) -> None:
73+
w = self.window
74+
if w.render_info:
75+
self.control.go_relative(len(w.render_info.displayed_lines))
76+
77+
@kb.add(" ")
78+
@kb.add("enter")
79+
def _enter(event: E) -> None:
80+
self.handle_accept()
81+
82+
return kb
83+
84+
def get_style(self) -> str:
85+
if get_app().layout.has_focus(self.focus_window):
86+
return "class:vertmenu.focused"
87+
else:
88+
return "class:vertmenu.unfocused"
89+
90+
def handle_selected(self) -> None:
91+
self.control.handle_selected()
92+
93+
def handle_accept(self) -> None:
94+
if self.accept_handler is not None and self.control.selected_item is not None:
95+
self.accept_handler(self.control.selected_item)
96+
97+
def preferred_width(self) -> int:
98+
width = self.control.preferred_width(0)
99+
assert width
100+
if self.max_width is not None:
101+
return min(width, self.max_width)
102+
return width
103+
104+
@property
105+
def items(self) -> Tuple[Item, ...]:
106+
return self.control.items
107+
108+
@items.setter
109+
def items(self, items: Iterable[Item]) -> None:
110+
self.control.items = tuple(items)
111+
112+
@property
113+
def selected(self) -> Optional[int]:
114+
return self.control.selected
115+
116+
@selected.setter
117+
def selected(self, selected: int) -> None:
118+
self.control.selected = selected
119+
120+
@property
121+
def selected_item(self) -> Optional[Item]:
122+
return self.control.selected_item
123+
124+
@selected_item.setter
125+
def selected_item(self, item: Item) -> None:
126+
self.control.selected_item = item
127+
128+
@property
129+
def selected_handler(self) -> Optional[Callable[[Optional[Item], int], None]]:
130+
return self.control.selected_handler
131+
132+
@selected_handler.setter
133+
def selected_handler(
134+
self,
135+
selected_handler: Optional[Callable[[Optional[Item], Optional[int]], None]],
136+
) -> None:
137+
self.control.selected_handler = selected_handler
138+
139+
def __pt_container__(self) -> Container:
140+
return self.window
141+
142+
143+
__all__ = [
144+
"VertMenu",
145+
"Item",
146+
]

0 commit comments

Comments
 (0)