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
|
from __future__ import annotations
import typing
import urwid
if typing.TYPE_CHECKING:
from collections.abc import Callable, Hashable, Iterable
class MenuButton(urwid.Button):
def __init__(
self,
caption: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
callback: Callable[[MenuButton], typing.Any],
) -> None:
super().__init__("", on_press=callback)
self._w = urwid.AttrMap(
urwid.SelectableIcon([" \N{BULLET} ", caption], 2),
None,
"selected",
)
class SubMenu(urwid.WidgetWrap[MenuButton]):
def __init__(
self,
caption: str | tuple[Hashable, str],
choices: Iterable[urwid.Widget],
) -> None:
super().__init__(MenuButton([caption, "\N{HORIZONTAL ELLIPSIS}"], self.open_menu))
line = urwid.Divider("\N{LOWER ONE QUARTER BLOCK}")
listbox = urwid.ListBox(
urwid.SimpleFocusListWalker(
[
urwid.AttrMap(urwid.Text(["\n ", caption]), "heading"),
urwid.AttrMap(line, "line"),
urwid.Divider(),
*choices,
urwid.Divider(),
]
)
)
self.menu = urwid.AttrMap(listbox, "options")
def open_menu(self, button: MenuButton) -> None:
top.open_box(self.menu)
class Choice(urwid.WidgetWrap[MenuButton]):
def __init__(
self,
caption: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],
) -> None:
super().__init__(MenuButton(caption, self.item_chosen))
self.caption = caption
def item_chosen(self, button: MenuButton) -> None:
response = urwid.Text([" You chose ", self.caption, "\n"])
done = MenuButton("Ok", exit_program)
response_box = urwid.Filler(urwid.Pile([response, done]))
top.open_box(urwid.AttrMap(response_box, "options"))
def exit_program(key):
raise urwid.ExitMainLoop()
menu_top = SubMenu(
"Main Menu",
[
SubMenu(
"Applications",
[
SubMenu(
"Accessories",
[
Choice("Text Editor"),
Choice("Terminal"),
],
)
],
),
SubMenu(
"System",
[
SubMenu("Preferences", [Choice("Appearance")]),
Choice("Lock Screen"),
],
),
],
)
palette = [
(None, "light gray", "black"),
("heading", "black", "light gray"),
("line", "black", "light gray"),
("options", "dark gray", "black"),
("focus heading", "white", "dark red"),
("focus line", "black", "dark red"),
("focus options", "black", "light gray"),
("selected", "white", "dark blue"),
]
focus_map = {"heading": "focus heading", "options": "focus options", "line": "focus line"}
class HorizontalBoxes(urwid.Columns):
def __init__(self) -> None:
super().__init__([], dividechars=1)
def open_box(self, box: urwid.Widget) -> None:
if self.contents:
del self.contents[self.focus_position + 1 :]
self.contents.append(
(
urwid.AttrMap(box, "options", focus_map),
self.options(urwid.GIVEN, 24),
)
)
self.focus_position = len(self.contents) - 1
top = HorizontalBoxes()
top.open_box(menu_top.menu)
urwid.MainLoop(urwid.Filler(top, "middle", 10), palette).run()
|