hspylib-clitt 0.9.118__py3-none-any.whl → 0.9.120__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hspylib-clitt might be problematic. Click here for more details.
- build/lib/build/lib/build/lib/clitt/__classpath__.py +28 -0
- build/lib/build/lib/build/lib/clitt/__init__.py +13 -0
- build/lib/build/lib/build/lib/clitt/__main__.py +139 -0
- build/lib/build/lib/build/lib/clitt/addons/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/addons/appman/__init__.py +13 -0
- build/lib/build/lib/build/lib/clitt/addons/appman/appman.py +305 -0
- build/lib/build/lib/build/lib/clitt/addons/appman/appman_enums.py +39 -0
- build/lib/build/lib/build/lib/clitt/addons/appman/templates/__init__.py +11 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/__init__.py +14 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widget.py +70 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widget_entry.py +54 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widgets/__init__.py +14 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widgets/widget_free.py +110 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widgets/widget_punch.py +246 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widgets/widget_send_msg.py +272 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widgets/widget_time_calc.py +146 -0
- build/lib/build/lib/build/lib/clitt/addons/widman/widman.py +123 -0
- build/lib/build/lib/build/lib/clitt/core/__init__.py +15 -0
- build/lib/build/lib/build/lib/clitt/core/exception/__init__.py +11 -0
- build/lib/build/lib/build/lib/clitt/core/exception/exceptions.py +19 -0
- build/lib/build/lib/build/lib/clitt/core/icons/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/icons/emojis/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/icons/emojis/emojis.py +41 -0
- build/lib/build/lib/build/lib/clitt/core/icons/emojis/face_smiling.py +40 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/__init__.py +18 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/app_icons.py +55 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/awesome.py +76 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/dashboard_icons.py +93 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/form_icons.py +69 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/game_icons.py +59 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/nav_icons.py +42 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/trickplay_icons.py +39 -0
- build/lib/build/lib/build/lib/clitt/core/icons/font_awesome/widget_icons.py +37 -0
- build/lib/build/lib/build/lib/clitt/core/preferences.py +87 -0
- build/lib/build/lib/build/lib/clitt/core/term/__init__.py +14 -0
- build/lib/build/lib/build/lib/clitt/core/term/commons.py +106 -0
- build/lib/build/lib/build/lib/clitt/core/term/cursor.py +174 -0
- build/lib/build/lib/build/lib/clitt/core/term/screen.py +106 -0
- build/lib/build/lib/build/lib/clitt/core/term/terminal.py +202 -0
- build/lib/build/lib/build/lib/clitt/core/tui/__init__.py +20 -0
- build/lib/build/lib/build/lib/clitt/core/tui/line_input/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/tui/line_input/keyboard_input.py +229 -0
- build/lib/build/lib/build/lib/clitt/core/tui/line_input/line_input.py +31 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mchoose/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mchoose/mchoose.py +43 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mchoose/menu_choose.py +192 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mdashboard/__init__.py +14 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mdashboard/dashboard_builder.py +54 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mdashboard/dashboard_item.py +31 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mdashboard/mdashboard.py +26 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mdashboard/menu_dashboard.py +148 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/__init__.py +16 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu.py +111 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu_action.py +47 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu_factory.py +117 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu_item.py +196 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu_ui.py +98 -0
- build/lib/build/lib/build/lib/clitt/core/tui/menu/tui_menu_view.py +57 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/__init__.py +19 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/access_type.py +26 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/field_builder.py +117 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/form_builder.py +72 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/form_field.py +180 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/input_type.py +30 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/input_validator.py +98 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/menu_input.py +292 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/minput.py +44 -0
- build/lib/build/lib/build/lib/clitt/core/tui/minput/minput_utils.py +156 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mselect/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mselect/menu_select.py +170 -0
- build/lib/build/lib/build/lib/clitt/core/tui/mselect/mselect.py +36 -0
- build/lib/build/lib/build/lib/clitt/core/tui/table/__init__.py +12 -0
- build/lib/build/lib/build/lib/clitt/core/tui/table/table_enums.py +47 -0
- build/lib/build/lib/build/lib/clitt/core/tui/table/table_renderer.py +339 -0
- build/lib/build/lib/build/lib/clitt/core/tui/tui_application.py +52 -0
- build/lib/build/lib/build/lib/clitt/core/tui/tui_component.py +154 -0
- build/lib/build/lib/build/lib/clitt/core/tui/tui_preferences.py +103 -0
- build/lib/build/lib/build/lib/clitt/utils/__init__.py +11 -0
- build/lib/build/lib/build/lib/clitt/utils/git_utils.py +66 -0
- build/lib/build/lib/clitt/__classpath__.py +28 -0
- build/lib/build/lib/clitt/__init__.py +13 -0
- build/lib/build/lib/clitt/__main__.py +139 -0
- build/lib/build/lib/clitt/addons/__init__.py +12 -0
- build/lib/build/lib/clitt/addons/appman/__init__.py +13 -0
- build/lib/build/lib/clitt/addons/appman/appman.py +305 -0
- build/lib/build/lib/clitt/addons/appman/appman_enums.py +39 -0
- build/lib/build/lib/clitt/addons/appman/templates/__init__.py +11 -0
- build/lib/build/lib/clitt/addons/widman/__init__.py +14 -0
- build/lib/build/lib/clitt/addons/widman/widget.py +70 -0
- build/lib/build/lib/clitt/addons/widman/widget_entry.py +54 -0
- build/lib/build/lib/clitt/addons/widman/widgets/__init__.py +14 -0
- build/lib/build/lib/clitt/addons/widman/widgets/widget_free.py +110 -0
- build/lib/build/lib/clitt/addons/widman/widgets/widget_punch.py +246 -0
- build/lib/build/lib/clitt/addons/widman/widgets/widget_send_msg.py +272 -0
- build/lib/build/lib/clitt/addons/widman/widgets/widget_time_calc.py +146 -0
- build/lib/build/lib/clitt/addons/widman/widman.py +123 -0
- build/lib/build/lib/clitt/core/__init__.py +15 -0
- build/lib/build/lib/clitt/core/exception/__init__.py +11 -0
- build/lib/build/lib/clitt/core/exception/exceptions.py +19 -0
- build/lib/build/lib/clitt/core/icons/__init__.py +12 -0
- build/lib/build/lib/clitt/core/icons/emojis/__init__.py +12 -0
- build/lib/build/lib/clitt/core/icons/emojis/emojis.py +41 -0
- build/lib/build/lib/clitt/core/icons/emojis/face_smiling.py +40 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/__init__.py +18 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/app_icons.py +55 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/awesome.py +76 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/dashboard_icons.py +93 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/form_icons.py +69 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/game_icons.py +59 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/nav_icons.py +42 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/trickplay_icons.py +39 -0
- build/lib/build/lib/clitt/core/icons/font_awesome/widget_icons.py +37 -0
- build/lib/build/lib/clitt/core/preferences.py +87 -0
- build/lib/build/lib/clitt/core/term/__init__.py +14 -0
- build/lib/build/lib/clitt/core/term/commons.py +106 -0
- build/lib/build/lib/clitt/core/term/cursor.py +174 -0
- build/lib/build/lib/clitt/core/term/screen.py +106 -0
- build/lib/build/lib/clitt/core/term/terminal.py +202 -0
- build/lib/build/lib/clitt/core/tui/__init__.py +20 -0
- build/lib/build/lib/clitt/core/tui/line_input/__init__.py +12 -0
- build/lib/build/lib/clitt/core/tui/line_input/keyboard_input.py +229 -0
- build/lib/build/lib/clitt/core/tui/line_input/line_input.py +31 -0
- build/lib/build/lib/clitt/core/tui/mchoose/__init__.py +12 -0
- build/lib/build/lib/clitt/core/tui/mchoose/mchoose.py +43 -0
- build/lib/build/lib/clitt/core/tui/mchoose/menu_choose.py +192 -0
- build/lib/build/lib/clitt/core/tui/mdashboard/__init__.py +14 -0
- build/lib/build/lib/clitt/core/tui/mdashboard/dashboard_builder.py +54 -0
- build/lib/build/lib/clitt/core/tui/mdashboard/dashboard_item.py +31 -0
- build/lib/build/lib/clitt/core/tui/mdashboard/mdashboard.py +26 -0
- build/lib/build/lib/clitt/core/tui/mdashboard/menu_dashboard.py +148 -0
- build/lib/build/lib/clitt/core/tui/menu/__init__.py +16 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu.py +111 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu_action.py +47 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu_factory.py +117 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu_item.py +196 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu_ui.py +98 -0
- build/lib/build/lib/clitt/core/tui/menu/tui_menu_view.py +57 -0
- build/lib/build/lib/clitt/core/tui/minput/__init__.py +19 -0
- build/lib/build/lib/clitt/core/tui/minput/access_type.py +26 -0
- build/lib/build/lib/clitt/core/tui/minput/field_builder.py +117 -0
- build/lib/build/lib/clitt/core/tui/minput/form_builder.py +72 -0
- build/lib/build/lib/clitt/core/tui/minput/form_field.py +180 -0
- build/lib/build/lib/clitt/core/tui/minput/input_type.py +30 -0
- build/lib/build/lib/clitt/core/tui/minput/input_validator.py +98 -0
- build/lib/build/lib/clitt/core/tui/minput/menu_input.py +292 -0
- build/lib/build/lib/clitt/core/tui/minput/minput.py +44 -0
- build/lib/build/lib/clitt/core/tui/minput/minput_utils.py +156 -0
- build/lib/build/lib/clitt/core/tui/mselect/__init__.py +12 -0
- build/lib/build/lib/clitt/core/tui/mselect/menu_select.py +170 -0
- build/lib/build/lib/clitt/core/tui/mselect/mselect.py +36 -0
- build/lib/build/lib/clitt/core/tui/table/__init__.py +12 -0
- build/lib/build/lib/clitt/core/tui/table/table_enums.py +47 -0
- build/lib/build/lib/clitt/core/tui/table/table_renderer.py +339 -0
- build/lib/build/lib/clitt/core/tui/tui_application.py +52 -0
- build/lib/build/lib/clitt/core/tui/tui_component.py +154 -0
- build/lib/build/lib/clitt/core/tui/tui_preferences.py +103 -0
- build/lib/build/lib/clitt/utils/__init__.py +11 -0
- build/lib/build/lib/clitt/utils/git_utils.py +66 -0
- build/lib/clitt/__classpath__.py +28 -0
- build/lib/clitt/__init__.py +13 -0
- build/lib/clitt/__main__.py +139 -0
- build/lib/clitt/addons/__init__.py +12 -0
- build/lib/clitt/addons/appman/__init__.py +13 -0
- build/lib/clitt/addons/appman/appman.py +305 -0
- build/lib/clitt/addons/appman/appman_enums.py +39 -0
- build/lib/clitt/addons/appman/templates/__init__.py +11 -0
- build/lib/clitt/addons/widman/__init__.py +14 -0
- build/lib/clitt/addons/widman/widget.py +70 -0
- build/lib/clitt/addons/widman/widget_entry.py +54 -0
- build/lib/clitt/addons/widman/widgets/__init__.py +14 -0
- build/lib/clitt/addons/widman/widgets/widget_free.py +110 -0
- build/lib/clitt/addons/widman/widgets/widget_punch.py +246 -0
- build/lib/clitt/addons/widman/widgets/widget_send_msg.py +272 -0
- build/lib/clitt/addons/widman/widgets/widget_time_calc.py +146 -0
- build/lib/clitt/addons/widman/widman.py +123 -0
- build/lib/clitt/core/__init__.py +15 -0
- build/lib/clitt/core/exception/__init__.py +11 -0
- build/lib/clitt/core/exception/exceptions.py +19 -0
- build/lib/clitt/core/icons/__init__.py +12 -0
- build/lib/clitt/core/icons/emojis/__init__.py +12 -0
- build/lib/clitt/core/icons/emojis/emojis.py +41 -0
- build/lib/clitt/core/icons/emojis/face_smiling.py +40 -0
- build/lib/clitt/core/icons/font_awesome/__init__.py +18 -0
- build/lib/clitt/core/icons/font_awesome/app_icons.py +55 -0
- build/lib/clitt/core/icons/font_awesome/awesome.py +76 -0
- build/lib/clitt/core/icons/font_awesome/dashboard_icons.py +93 -0
- build/lib/clitt/core/icons/font_awesome/form_icons.py +69 -0
- build/lib/clitt/core/icons/font_awesome/game_icons.py +59 -0
- build/lib/clitt/core/icons/font_awesome/nav_icons.py +42 -0
- build/lib/clitt/core/icons/font_awesome/trickplay_icons.py +39 -0
- build/lib/clitt/core/icons/font_awesome/widget_icons.py +37 -0
- build/lib/clitt/core/preferences.py +87 -0
- build/lib/clitt/core/term/__init__.py +14 -0
- build/lib/clitt/core/term/commons.py +106 -0
- build/lib/clitt/core/term/cursor.py +174 -0
- build/lib/clitt/core/term/screen.py +106 -0
- build/lib/clitt/core/term/terminal.py +202 -0
- build/lib/clitt/core/tui/__init__.py +20 -0
- build/lib/clitt/core/tui/line_input/__init__.py +12 -0
- build/lib/clitt/core/tui/line_input/keyboard_input.py +229 -0
- build/lib/clitt/core/tui/line_input/line_input.py +31 -0
- build/lib/clitt/core/tui/mchoose/__init__.py +12 -0
- build/lib/clitt/core/tui/mchoose/mchoose.py +43 -0
- build/lib/clitt/core/tui/mchoose/menu_choose.py +192 -0
- build/lib/clitt/core/tui/mdashboard/__init__.py +14 -0
- build/lib/clitt/core/tui/mdashboard/dashboard_builder.py +54 -0
- build/lib/clitt/core/tui/mdashboard/dashboard_item.py +31 -0
- build/lib/clitt/core/tui/mdashboard/mdashboard.py +26 -0
- build/lib/clitt/core/tui/mdashboard/menu_dashboard.py +148 -0
- build/lib/clitt/core/tui/menu/__init__.py +16 -0
- build/lib/clitt/core/tui/menu/tui_menu.py +111 -0
- build/lib/clitt/core/tui/menu/tui_menu_action.py +47 -0
- build/lib/clitt/core/tui/menu/tui_menu_factory.py +117 -0
- build/lib/clitt/core/tui/menu/tui_menu_item.py +196 -0
- build/lib/clitt/core/tui/menu/tui_menu_ui.py +98 -0
- build/lib/clitt/core/tui/menu/tui_menu_view.py +57 -0
- build/lib/clitt/core/tui/minput/__init__.py +19 -0
- build/lib/clitt/core/tui/minput/access_type.py +26 -0
- build/lib/clitt/core/tui/minput/field_builder.py +117 -0
- build/lib/clitt/core/tui/minput/form_builder.py +72 -0
- build/lib/clitt/core/tui/minput/form_field.py +180 -0
- build/lib/clitt/core/tui/minput/input_type.py +30 -0
- build/lib/clitt/core/tui/minput/input_validator.py +98 -0
- build/lib/clitt/core/tui/minput/menu_input.py +292 -0
- build/lib/clitt/core/tui/minput/minput.py +44 -0
- build/lib/clitt/core/tui/minput/minput_utils.py +156 -0
- build/lib/clitt/core/tui/mselect/__init__.py +12 -0
- build/lib/clitt/core/tui/mselect/menu_select.py +170 -0
- build/lib/clitt/core/tui/mselect/mselect.py +36 -0
- build/lib/clitt/core/tui/table/__init__.py +12 -0
- build/lib/clitt/core/tui/table/table_enums.py +47 -0
- build/lib/clitt/core/tui/table/table_renderer.py +339 -0
- build/lib/clitt/core/tui/tui_application.py +52 -0
- build/lib/clitt/core/tui/tui_component.py +154 -0
- build/lib/clitt/core/tui/tui_preferences.py +103 -0
- build/lib/clitt/utils/__init__.py +11 -0
- build/lib/clitt/utils/git_utils.py +66 -0
- clitt/.version +1 -1
- clitt/__init__.py +3 -3
- clitt/addons/__init__.py +3 -3
- clitt/addons/appman/__init__.py +3 -3
- clitt/addons/appman/templates/__init__.py +3 -3
- clitt/addons/widman/__init__.py +3 -3
- clitt/addons/widman/widgets/__init__.py +3 -3
- clitt/core/__init__.py +3 -3
- clitt/core/exception/__init__.py +3 -3
- clitt/core/icons/__init__.py +3 -3
- clitt/core/icons/emojis/__init__.py +3 -3
- clitt/core/icons/font_awesome/__init__.py +3 -3
- clitt/core/term/__init__.py +3 -3
- clitt/core/term/commons.py +11 -12
- clitt/core/tui/__init__.py +3 -3
- clitt/core/tui/line_input/__init__.py +3 -3
- clitt/core/tui/line_input/keyboard_input.py +22 -22
- clitt/core/tui/mchoose/__init__.py +3 -3
- clitt/core/tui/mdashboard/__init__.py +3 -3
- clitt/core/tui/menu/__init__.py +3 -3
- clitt/core/tui/minput/__init__.py +3 -3
- clitt/core/tui/mselect/__init__.py +3 -3
- clitt/core/tui/table/__init__.py +3 -3
- clitt/utils/__init__.py +3 -3
- {hspylib_clitt-0.9.118.dist-info → hspylib_clitt-0.9.120.dist-info}/METADATA +2 -2
- hspylib_clitt-0.9.120.dist-info/RECORD +334 -0
- {hspylib_clitt-0.9.118.dist-info → hspylib_clitt-0.9.120.dist-info}/top_level.txt +1 -0
- hspylib_clitt-0.9.118.dist-info/RECORD +0 -97
- {hspylib_clitt-0.9.118.dist-info → hspylib_clitt-0.9.120.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@project: HsPyLib
|
|
6
|
+
@package: clitt.core.tui.mdashboard
|
|
7
|
+
@file: menu_dashboard.py
|
|
8
|
+
@created: Tue, 4 May 2021
|
|
9
|
+
@author: <B>H</B>ugo <B>S</B>aporetti <B>J</B>unior"
|
|
10
|
+
@site: https://github.com/yorevs/hspylib
|
|
11
|
+
@license: MIT - Please refer to <https://opensource.org/licenses/MIT>
|
|
12
|
+
|
|
13
|
+
Copyright·(c)·2024,·HSPyLib
|
|
14
|
+
"""
|
|
15
|
+
from clitt.core.icons.font_awesome.nav_icons import NavIcons
|
|
16
|
+
from clitt.core.term.commons import Direction, Portion
|
|
17
|
+
from clitt.core.tui.mdashboard.dashboard_builder import DashboardBuilder
|
|
18
|
+
from clitt.core.tui.mdashboard.dashboard_item import DashboardItem
|
|
19
|
+
from clitt.core.tui.tui_component import TUIComponent
|
|
20
|
+
from hspylib.core.preconditions import check_state
|
|
21
|
+
from hspylib.modules.cli.keyboard import Keyboard
|
|
22
|
+
from typing import List, Optional, TypeAlias
|
|
23
|
+
|
|
24
|
+
DashboardMatrix: TypeAlias = List[List[str]]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MenuDashBoard(TUIComponent):
|
|
28
|
+
"""A dashboard is a type of graphical user interface which provides at-a-glance views."""
|
|
29
|
+
|
|
30
|
+
# fmt: off
|
|
31
|
+
ICN = "x" # x mars the spot if the icon.
|
|
32
|
+
|
|
33
|
+
# Selected cell template.
|
|
34
|
+
CELL_TPL = [
|
|
35
|
+
[" ", " ", " ", " ", " ", " ", " "],
|
|
36
|
+
[" ", " ", " ", ICN, " ", " ", " "],
|
|
37
|
+
[" ", " ", " ", " ", " ", " ", " "],
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# Unselected cell template.
|
|
41
|
+
SEL_CELL_TPL = [
|
|
42
|
+
[" ", "╭", " ", " ", " ", "╮", " "],
|
|
43
|
+
[" ", " ", " ", ICN, " ", " ", " "],
|
|
44
|
+
[" ", "╰", " ", " ", " ", "╯", " "],
|
|
45
|
+
]
|
|
46
|
+
# fmt: on
|
|
47
|
+
|
|
48
|
+
NAV_ICONS = NavIcons.compose(NavIcons.LEFT, NavIcons.DOWN, NavIcons.UP, NavIcons.RIGHT)
|
|
49
|
+
|
|
50
|
+
COLUMN_OFFSET = 10
|
|
51
|
+
|
|
52
|
+
MIN_COLUMNS = 2
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def builder(cls) -> DashboardBuilder:
|
|
56
|
+
return DashboardBuilder()
|
|
57
|
+
|
|
58
|
+
def __init__(self, title: str, items: List[DashboardItem]):
|
|
59
|
+
super().__init__(title)
|
|
60
|
+
self._items = items
|
|
61
|
+
self._tab_index = 0
|
|
62
|
+
check_state(
|
|
63
|
+
len(self.CELL_TPL) == len(self.SEL_CELL_TPL) and len(self.CELL_TPL[0]) == len(self.SEL_CELL_TPL[0]),
|
|
64
|
+
"Invalid CELL definitions. Selected and Unselected matrices should have the same lengths.",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def execute(self) -> Optional[DashboardItem]:
|
|
68
|
+
if (len(self._items)) == 0:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
self._prepare_render()
|
|
72
|
+
keypress = self._loop()
|
|
73
|
+
selected = self._items[self._tab_index] if keypress == Keyboard.VK_ENTER else None
|
|
74
|
+
|
|
75
|
+
if selected and selected.on_trigger:
|
|
76
|
+
selected.on_trigger()
|
|
77
|
+
|
|
78
|
+
return selected
|
|
79
|
+
|
|
80
|
+
def render(self) -> None:
|
|
81
|
+
self.cursor.restore()
|
|
82
|
+
self.writeln(f"{self.prefs.title_color.placeholder}{self.title}%EOL%%NC%")
|
|
83
|
+
|
|
84
|
+
for idx, item in enumerate(self._items):
|
|
85
|
+
self._draw_item(idx, item, MenuDashBoard.CELL_TPL if self._tab_index != idx else MenuDashBoard.SEL_CELL_TPL)
|
|
86
|
+
|
|
87
|
+
self.cursor.erase(Portion.LINE)
|
|
88
|
+
self.cursor.move_to(column=1)
|
|
89
|
+
self.draw_navbar(self.navbar())
|
|
90
|
+
self._re_render = False
|
|
91
|
+
|
|
92
|
+
def navbar(self, **kwargs) -> str:
|
|
93
|
+
return (
|
|
94
|
+
f"{NavIcons.POINTER} %GREEN%{self._items[self._tab_index].tooltip}%NC%"
|
|
95
|
+
f"%EOL%{self.prefs.navbar_color.placeholder}%EOL%"
|
|
96
|
+
f"[Enter] Select Navigate {self.NAV_ICONS} Next [Tab] [Esc] Quit %NC%"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def handle_keypress(self) -> Keyboard:
|
|
100
|
+
length = len(self._items)
|
|
101
|
+
if keypress := Keyboard.wait_keystroke():
|
|
102
|
+
match keypress:
|
|
103
|
+
case _ as key if key in [Keyboard.VK_ESC, Keyboard.VK_ENTER]:
|
|
104
|
+
self._done = True
|
|
105
|
+
case Keyboard.VK_UP:
|
|
106
|
+
self._tab_index = max(0, self._tab_index - self._items_per_line())
|
|
107
|
+
case Keyboard.VK_DOWN:
|
|
108
|
+
self._tab_index = min(length - 1, self._tab_index + self._items_per_line())
|
|
109
|
+
case _ as key if key in [Keyboard.VK_LEFT, Keyboard.VK_SHIFT_TAB]:
|
|
110
|
+
self._tab_index = max(0, self._tab_index - 1)
|
|
111
|
+
case _ as key if key in [Keyboard.VK_RIGHT, Keyboard.VK_TAB]:
|
|
112
|
+
self._tab_index = min(length - 1, self._tab_index + 1)
|
|
113
|
+
|
|
114
|
+
self._re_render = True
|
|
115
|
+
|
|
116
|
+
return keypress
|
|
117
|
+
|
|
118
|
+
def _draw_item(self, item_idx: int, item: DashboardItem, cell_template: DashboardMatrix) -> None:
|
|
119
|
+
"""Print the specified dashboard item at the given index.
|
|
120
|
+
:param item_idx: the item index.
|
|
121
|
+
:param item: the dashboard item.
|
|
122
|
+
:param cell_template: the template of the dashboard cell (selected or unselected).
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
num_cols, num_rows = len(cell_template[0]), len(cell_template)
|
|
126
|
+
|
|
127
|
+
for row in range(0, num_rows):
|
|
128
|
+
for col in range(0, num_cols):
|
|
129
|
+
cur_cell = cell_template[row][col]
|
|
130
|
+
# Print current cell
|
|
131
|
+
self.write(f"{item.icon if cur_cell == self.ICN else cur_cell}")
|
|
132
|
+
# Move to the next the next row
|
|
133
|
+
self.cursor.move(1, Direction.DOWN)
|
|
134
|
+
self.cursor.move(num_cols, Direction.LEFT)
|
|
135
|
+
|
|
136
|
+
if item_idx > 0 and (item_idx + 1) % self._items_per_line() == 0:
|
|
137
|
+
# Break the line
|
|
138
|
+
self.cursor.move(1, Direction.DOWN)
|
|
139
|
+
self.cursor.move(num_cols * self._items_per_line(), Direction.LEFT)
|
|
140
|
+
elif item_idx + 1 < len(self._items):
|
|
141
|
+
# Continue on the same line
|
|
142
|
+
self.cursor.move(num_rows, Direction.UP)
|
|
143
|
+
self.cursor.move(num_cols, Direction.RIGHT)
|
|
144
|
+
|
|
145
|
+
def _items_per_line(self) -> int:
|
|
146
|
+
"""TODO"""
|
|
147
|
+
screen_columns = self.screen.columns - self.COLUMN_OFFSET
|
|
148
|
+
return max(self.MIN_COLUMNS, min(self.prefs.items_per_line, screen_columns))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# _*_ coding: utf-8 _*_
|
|
2
|
+
#
|
|
3
|
+
# hspylib-clitt v0.9.120
|
|
4
|
+
#
|
|
5
|
+
# Package: main.build.lib.build.lib.clitt.core.tui.menu
|
|
6
|
+
"""Package initialization."""
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'tui_menu',
|
|
10
|
+
'tui_menu_action',
|
|
11
|
+
'tui_menu_factory',
|
|
12
|
+
'tui_menu_item',
|
|
13
|
+
'tui_menu_ui',
|
|
14
|
+
'tui_menu_view'
|
|
15
|
+
]
|
|
16
|
+
__version__ = '0.9.120'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@project: HsPyLib
|
|
6
|
+
@package: clitt.core.tui.menu
|
|
7
|
+
@file: tui_menu.py
|
|
8
|
+
@created: Tue, 4 May 2021
|
|
9
|
+
@author: <B>H</B>ugo <B>S</B>aporetti <B>J</B>unior"
|
|
10
|
+
@site: https://github.com/yorevs/hspylib
|
|
11
|
+
@license: MIT - Please refer to <https://opensource.org/licenses/MIT>
|
|
12
|
+
|
|
13
|
+
Copyright·(c)·2024,·HSPyLib
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from abc import ABC
|
|
17
|
+
from clitt.core.icons.font_awesome.nav_icons import NavIcons
|
|
18
|
+
from clitt.core.tui.minput.input_validator import InputValidator
|
|
19
|
+
from clitt.core.tui.minput.menu_input import MenuInput
|
|
20
|
+
from clitt.core.tui.minput.minput import minput
|
|
21
|
+
from clitt.core.tui.tui_component import TUIComponent
|
|
22
|
+
from hspylib.core.namespace import Namespace
|
|
23
|
+
from hspylib.modules.cli.keyboard import Keyboard
|
|
24
|
+
from typing import Callable, Optional, TypeAlias
|
|
25
|
+
|
|
26
|
+
OnTrigger_Cb: TypeAlias = Callable[["TUIMenu"], Optional["TUIMenu"]]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TUIMenu(TUIComponent, ABC):
|
|
30
|
+
"""Provide a base class for terminal UI menus."""
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def prompt(
|
|
34
|
+
label: str, dest: str = None, min_length: int = 1, max_length: int = 32, validator: InputValidator = None
|
|
35
|
+
) -> Optional[Namespace]:
|
|
36
|
+
# fmt: off
|
|
37
|
+
form_fields = (
|
|
38
|
+
MenuInput.builder()
|
|
39
|
+
.field()
|
|
40
|
+
.label(label)
|
|
41
|
+
.dest(dest or label)
|
|
42
|
+
.validator(validator or InputValidator.words())
|
|
43
|
+
.min_max_length(min_length, max_length)
|
|
44
|
+
.build()
|
|
45
|
+
.build()
|
|
46
|
+
)
|
|
47
|
+
# fmt: on
|
|
48
|
+
|
|
49
|
+
return minput(form_fields)
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
parent: Optional["TUIMenu"] = None,
|
|
54
|
+
title: str = "",
|
|
55
|
+
tooltip: str = "",
|
|
56
|
+
default_on_trigger_cb: OnTrigger_Cb = None,
|
|
57
|
+
):
|
|
58
|
+
super().__init__(title)
|
|
59
|
+
self._tooltip = tooltip
|
|
60
|
+
self._parent = parent
|
|
61
|
+
self._on_trigger = default_on_trigger_cb or self._default_trigger_cb
|
|
62
|
+
|
|
63
|
+
def __str__(self) -> str:
|
|
64
|
+
return self._title
|
|
65
|
+
|
|
66
|
+
def __repr__(self) -> str:
|
|
67
|
+
return str(self)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def parent(self) -> Optional["TUIMenu"]:
|
|
71
|
+
return self._parent
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def tooltip(self) -> str:
|
|
75
|
+
return self._tooltip
|
|
76
|
+
|
|
77
|
+
def handle_keypress(self) -> Keyboard:
|
|
78
|
+
if keypress := Keyboard.wait_keystroke():
|
|
79
|
+
match keypress:
|
|
80
|
+
case Keyboard.VK_ENTER | Keyboard.VK_ESC:
|
|
81
|
+
self._done = True
|
|
82
|
+
|
|
83
|
+
return keypress
|
|
84
|
+
|
|
85
|
+
def navbar(self, **kwargs) -> str:
|
|
86
|
+
return (
|
|
87
|
+
f"%EOL%{self.breadcrumb()}%ED0%"
|
|
88
|
+
f"%EOL%{self.prefs.navbar_color.placeholder}%EOL%"
|
|
89
|
+
f"[Enter] Back [Esc] Quit %EL0%%EOL%%EOL%%NC%"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def breadcrumb(self) -> str:
|
|
93
|
+
"""Provide a breadcrumb of menus for navigation purposes."""
|
|
94
|
+
return (
|
|
95
|
+
f"{self.prefs.breadcrumb_color.placeholder}"
|
|
96
|
+
f" {NavIcons.BREADCRUMB} {self.title} %NC%"
|
|
97
|
+
f"{self.prefs.tooltip_color.placeholder}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def wait_keystroke(self, wait_message: str = "%YELLOW%%EOL%Press any key to continue%EOL%%NC%") -> None:
|
|
101
|
+
"""Wait for a keypress (blocking).
|
|
102
|
+
:param wait_message: the message to present to the user.
|
|
103
|
+
"""
|
|
104
|
+
self.writeln(wait_message)
|
|
105
|
+
Keyboard.wait_keystroke()
|
|
106
|
+
|
|
107
|
+
def _default_trigger_cb(self, source: Optional["TUIMenu"]) -> Optional["TUIMenu"]:
|
|
108
|
+
"""Provide a default trigger callback when a menu is activated. Provided a source menu, returns it's parent.
|
|
109
|
+
:param source: the source which invoked this menu.
|
|
110
|
+
"""
|
|
111
|
+
return self.parent
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@project: HsPyLib
|
|
6
|
+
@package: clitt.core.tui.menu
|
|
7
|
+
@file: tui_menu_action.py
|
|
8
|
+
@created: Tue, 4 May 2021
|
|
9
|
+
@author: <B>H</B>ugo <B>S</B>aporetti <B>J</B>unior"
|
|
10
|
+
@site: https://github.com/yorevs/hspylib
|
|
11
|
+
@license: MIT - Please refer to <https://opensource.org/licenses/MIT>
|
|
12
|
+
|
|
13
|
+
Copyright·(c)·2024,·HSPyLib
|
|
14
|
+
"""
|
|
15
|
+
from clitt.core.tui.menu.tui_menu import OnTrigger_Cb, TUIMenu
|
|
16
|
+
from clitt.core.tui.menu.tui_menu_ui import TUIMenuUi
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TUIMenuAction(TUIMenu):
|
|
21
|
+
"""Represent a menu item action. Each action trigger a callback function."""
|
|
22
|
+
|
|
23
|
+
def execute(self) -> Optional[TUIMenu]:
|
|
24
|
+
self.render()
|
|
25
|
+
ret_menu = self._on_trigger(self._parent)
|
|
26
|
+
if not ret_menu:
|
|
27
|
+
self.wait_keystroke()
|
|
28
|
+
return ret_menu if ret_menu else self._default_trigger_cb(self)
|
|
29
|
+
|
|
30
|
+
def render(self) -> None:
|
|
31
|
+
self.screen.cursor.restore()
|
|
32
|
+
TUIMenuUi.render_app_title()
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
parent: TUIMenu,
|
|
37
|
+
title: Optional[str] = None,
|
|
38
|
+
tooltip: Optional[str] = None,
|
|
39
|
+
on_trigger: OnTrigger_Cb = None,
|
|
40
|
+
):
|
|
41
|
+
super().__init__(parent, title, tooltip)
|
|
42
|
+
self._parent = parent
|
|
43
|
+
self._on_trigger: OnTrigger_Cb = on_trigger or super()._default_trigger_cb
|
|
44
|
+
|
|
45
|
+
def on_trigger(self, cb_on_trigger: OnTrigger_Cb) -> None:
|
|
46
|
+
"""Setter for the on_trigger callback. It is called when a menu item is triggered."""
|
|
47
|
+
self._on_trigger = cb_on_trigger
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@project: HsPyLib
|
|
6
|
+
@package: clitt.core.tui.menu
|
|
7
|
+
@file: tui_menu_factory.py
|
|
8
|
+
@created: Tue, 4 May 2021
|
|
9
|
+
@author: <B>H</B>ugo <B>S</B>aporetti <B>J</B>unior"
|
|
10
|
+
@site: https://github.com/yorevs/hspylib
|
|
11
|
+
@license: MIT - Please refer to <https://opensource.org/licenses/MIT>
|
|
12
|
+
|
|
13
|
+
Copyright·(c)·2024,·HSPyLib
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from clitt.core.tui.menu.tui_menu import TUIMenu
|
|
17
|
+
from clitt.core.tui.menu.tui_menu_action import OnTrigger_Cb, TUIMenuAction
|
|
18
|
+
from clitt.core.tui.menu.tui_menu_item import TUIMenuItem
|
|
19
|
+
from clitt.core.tui.menu.tui_menu_view import TUIMenuView
|
|
20
|
+
from hspylib.core.preconditions import check_not_none
|
|
21
|
+
from typing import List, TypeAlias, Union
|
|
22
|
+
|
|
23
|
+
MenuBuilder: TypeAlias = Union[
|
|
24
|
+
"TUIMenuFactory.TUIMenuBuilder", "TUIMenuFactory.TUIMenuItemBuilder", "TUIMenuFactory.TUIMenuViewBuilder"
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TUIMenuFactory:
|
|
29
|
+
"""TODO"""
|
|
30
|
+
|
|
31
|
+
_main_menu = None
|
|
32
|
+
|
|
33
|
+
class TUIMenuBuilder:
|
|
34
|
+
"""TODO"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, parent: "TUIMenuFactory", main_menu: TUIMenuItem):
|
|
37
|
+
check_not_none((parent, main_menu))
|
|
38
|
+
self._parent = parent
|
|
39
|
+
self._main_menu: TUIMenuItem = main_menu
|
|
40
|
+
|
|
41
|
+
def with_item(self, title: str, tooltip: str | None = None) -> "TUIMenuFactory.TUIMenuItemBuilder":
|
|
42
|
+
return TUIMenuFactory.TUIMenuItemBuilder(self, self._main_menu, title, tooltip)
|
|
43
|
+
|
|
44
|
+
def with_action(self, title: str, tooltip: str | None = None) -> "TUIMenuFactory.TUIMenuActionBuilder":
|
|
45
|
+
return TUIMenuFactory.TUIMenuActionBuilder(self, self._main_menu, title, tooltip)
|
|
46
|
+
|
|
47
|
+
def with_view(self, title: str, tooltip: str | None = None) -> "TUIMenuFactory.TUIMenuViewBuilder":
|
|
48
|
+
return TUIMenuFactory.TUIMenuViewBuilder(self, self._main_menu, title, tooltip)
|
|
49
|
+
|
|
50
|
+
def then(self) -> "TUIMenuFactory":
|
|
51
|
+
return self._parent
|
|
52
|
+
|
|
53
|
+
class TUIMenuItemBuilder:
|
|
54
|
+
"""TODO"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self, parent: MenuBuilder, parent_item: TUIMenuItem, title: str = None, tooltip: str | None = None
|
|
58
|
+
):
|
|
59
|
+
check_not_none((parent, parent_item))
|
|
60
|
+
self._parent = parent
|
|
61
|
+
self._menu_item = TUIMenuItem(parent_item, title, tooltip)
|
|
62
|
+
self._items: List[TUIMenu] = []
|
|
63
|
+
parent_item.add_items(self._menu_item)
|
|
64
|
+
|
|
65
|
+
def with_item(self, title: str, tooltip: str | None = None) -> "TUIMenuFactory.TUIMenuItemBuilder":
|
|
66
|
+
return TUIMenuFactory.TUIMenuItemBuilder(self, self._menu_item, title, tooltip)
|
|
67
|
+
|
|
68
|
+
def with_action(self, title: str, tooltip: str | None = None) -> "TUIMenuFactory.TUIMenuActionBuilder":
|
|
69
|
+
return TUIMenuFactory.TUIMenuActionBuilder(self, self._menu_item, title, tooltip)
|
|
70
|
+
|
|
71
|
+
def with_view(self, title: str, tooltip: str | None = None) -> "TUIMenuFactory.TUIMenuViewBuilder":
|
|
72
|
+
return TUIMenuFactory.TUIMenuViewBuilder(self, self._menu_item, title, tooltip)
|
|
73
|
+
|
|
74
|
+
def then(self) -> MenuBuilder:
|
|
75
|
+
return self._parent
|
|
76
|
+
|
|
77
|
+
class TUIMenuViewBuilder:
|
|
78
|
+
"""TODO"""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self, parent: MenuBuilder, parent_item: TUIMenuItem, title: str = None, tooltip: str | None = None
|
|
82
|
+
):
|
|
83
|
+
check_not_none((parent, parent_item))
|
|
84
|
+
self._parent = parent
|
|
85
|
+
self._menu_view = TUIMenuView(parent_item, title, tooltip)
|
|
86
|
+
parent_item.add_items(self._menu_view)
|
|
87
|
+
|
|
88
|
+
def on_render(self, on_render: str | OnTrigger_Cb) -> "TUIMenuFactory.TUIMenuItemBuilder":
|
|
89
|
+
self._menu_view.on_render(on_render)
|
|
90
|
+
return self._parent
|
|
91
|
+
|
|
92
|
+
class TUIMenuActionBuilder:
|
|
93
|
+
"""TODO"""
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self, parent: MenuBuilder, parent_item: TUIMenuItem, title: str = None, tooltip: str | None = None
|
|
97
|
+
):
|
|
98
|
+
check_not_none((parent, parent_item))
|
|
99
|
+
self._parent = parent
|
|
100
|
+
self._menu_action = TUIMenuAction(parent_item, title, tooltip)
|
|
101
|
+
self._parent_item = parent_item
|
|
102
|
+
parent_item.add_items(self._menu_action)
|
|
103
|
+
|
|
104
|
+
def on_trigger(self, action: OnTrigger_Cb) -> "TUIMenuFactory.TUIMenuItemBuilder":
|
|
105
|
+
check_not_none(action)
|
|
106
|
+
self._menu_action.on_trigger(action)
|
|
107
|
+
return self._parent
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def create_main_menu(cls, title: str = None, tooltip: str | None = None) -> "TUIMenuBuilder":
|
|
111
|
+
cls._main_menu = TUIMenuItem(title=title, tooltip=tooltip)
|
|
112
|
+
factory = TUIMenuFactory()
|
|
113
|
+
|
|
114
|
+
return cls.TUIMenuBuilder(factory, cls._main_menu)
|
|
115
|
+
|
|
116
|
+
def build(self) -> TUIMenuItem:
|
|
117
|
+
return self._main_menu
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@project: HsPyLib
|
|
6
|
+
@package: clitt.core.tui.menu
|
|
7
|
+
@file: tui_menu_item.py
|
|
8
|
+
@created: Tue, 4 May 2021
|
|
9
|
+
@author: <B>H</B>ugo <B>S</B>aporetti <B>J</B>unior"
|
|
10
|
+
@site: https://github.com/yorevs/hspylib
|
|
11
|
+
@license: MIT - Please refer to <https://opensource.org/licenses/MIT>
|
|
12
|
+
|
|
13
|
+
Copyright·(c)·2024,·HSPyLib
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from clitt.core.icons.font_awesome.nav_icons import NavIcons
|
|
17
|
+
from clitt.core.term.commons import Direction, Portion
|
|
18
|
+
from clitt.core.tui.menu.tui_menu import TUIMenu
|
|
19
|
+
from clitt.core.tui.menu.tui_menu_ui import TUIMenuUi
|
|
20
|
+
from functools import cached_property
|
|
21
|
+
from hspylib.core.tools.dict_tools import get_or_default
|
|
22
|
+
from hspylib.modules.cli.keyboard import Keyboard
|
|
23
|
+
from typing import List, Optional
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TUIMenuItem(TUIMenu):
|
|
27
|
+
"""Represent a menu item with submenus. Each sub-item must belong to the TUIMenu class and can be an instance of
|
|
28
|
+
TUIMenuItem, TUIMenuAction or TUIMenuView.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
ROW_OFFSET = 5
|
|
32
|
+
|
|
33
|
+
NAV_ICONS = NavIcons.compose(NavIcons.UP, NavIcons.DOWN)
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
parent: Optional[TUIMenu] = None,
|
|
38
|
+
title: str = "Sub Menu",
|
|
39
|
+
tooltip: str = None,
|
|
40
|
+
items: List[TUIMenu] = None,
|
|
41
|
+
):
|
|
42
|
+
super().__init__(parent, title, tooltip or f"Access the '{title}' menu")
|
|
43
|
+
self._show_from: int = 0
|
|
44
|
+
self._show_to: int = self.screen.lines - self.ROW_OFFSET
|
|
45
|
+
self._diff_index: int = self._show_to - self._show_from
|
|
46
|
+
self._sel_index: int = 0
|
|
47
|
+
self._items: List[TUIMenu] = items or []
|
|
48
|
+
self._max_line_length = max(len(str(menu)) for menu in self._items) if self._items else len(str(self))
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def items(self) -> List[TUIMenu]:
|
|
52
|
+
return self._items
|
|
53
|
+
|
|
54
|
+
def add_items(self, *items: TUIMenu) -> None:
|
|
55
|
+
"""Add submenu items to the menu."""
|
|
56
|
+
list(map(self._items.append, items))
|
|
57
|
+
|
|
58
|
+
def execute(self) -> Optional[TUIMenu]:
|
|
59
|
+
# Wait for user interaction
|
|
60
|
+
while not self._done:
|
|
61
|
+
if not self._items:
|
|
62
|
+
return self._on_trigger(self._parent)
|
|
63
|
+
|
|
64
|
+
# Menu Renderization
|
|
65
|
+
if self._re_render:
|
|
66
|
+
self.render()
|
|
67
|
+
|
|
68
|
+
# Navigation input
|
|
69
|
+
if self.handle_keypress() == Keyboard.VK_ENTER:
|
|
70
|
+
return self._on_trigger(self._parent)
|
|
71
|
+
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
def render(self) -> None:
|
|
75
|
+
length = len(self._items)
|
|
76
|
+
self.cursor.restore()
|
|
77
|
+
TUIMenuUi.render_app_title()
|
|
78
|
+
|
|
79
|
+
if length > 0:
|
|
80
|
+
for idx in range(self._show_from, self._show_to):
|
|
81
|
+
if idx >= length:
|
|
82
|
+
break # When the number of items is lower than the max_rows, skip the other lines
|
|
83
|
+
option_line = str(self._items[idx])
|
|
84
|
+
self.cursor.erase(Portion.LINE)
|
|
85
|
+
# Print the selector if the index is currently selected
|
|
86
|
+
selector = self.draw_selector(is_selected=(idx == self._sel_index), has_bg_color=False)
|
|
87
|
+
# fmt: off
|
|
88
|
+
line_fmt = (
|
|
89
|
+
" {:>" + f"{len(str(length))}" + "} "
|
|
90
|
+
+ "{:>" + f"{len(selector)}" + "} "
|
|
91
|
+
+ "{:<" + f"{self._max_line_length}" + "} "
|
|
92
|
+
)
|
|
93
|
+
# fmt: on
|
|
94
|
+
self.draw_line(line_fmt, idx + 1, selector, option_line)
|
|
95
|
+
|
|
96
|
+
self.draw_navbar(self.navbar(to=length))
|
|
97
|
+
self._re_render = False
|
|
98
|
+
|
|
99
|
+
def navbar(self, **kwargs) -> str:
|
|
100
|
+
menu = get_or_default(self.items, self._sel_index, None)
|
|
101
|
+
tooltip = menu.tooltip if menu else None
|
|
102
|
+
return (
|
|
103
|
+
f"%EOL%%GREEN%{self.breadcrumb()} "
|
|
104
|
+
f"{tooltip + ' ' if tooltip else ''}%ED0%%NC%"
|
|
105
|
+
f"%EOL%{self.prefs.navbar_color.placeholder}%EOL%"
|
|
106
|
+
f"[Enter] Select Navigate [{self.NAV_ICONS}] "
|
|
107
|
+
f"[Esc] Quit [1..{kwargs['to']}] Goto: %NC%%EL0%%EOL%%EOL%"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def handle_keypress(self) -> Keyboard:
|
|
111
|
+
"""Handle a keyboard press."""
|
|
112
|
+
if keypress := Keyboard.wait_keystroke():
|
|
113
|
+
match keypress:
|
|
114
|
+
case Keyboard.VK_ESC:
|
|
115
|
+
self._done = True
|
|
116
|
+
case Keyboard.VK_UP:
|
|
117
|
+
self._handle_key_up()
|
|
118
|
+
case Keyboard.VK_DOWN:
|
|
119
|
+
self._handle_key_down()
|
|
120
|
+
case Keyboard.VK_TAB:
|
|
121
|
+
self._handle_tab()
|
|
122
|
+
case Keyboard.VK_SHIFT_TAB:
|
|
123
|
+
self._handle_shift_tab()
|
|
124
|
+
case _ as key if key in self._digits:
|
|
125
|
+
self._handle_digit(keypress)
|
|
126
|
+
self._re_render = True
|
|
127
|
+
|
|
128
|
+
return keypress
|
|
129
|
+
|
|
130
|
+
def _handle_key_up(self) -> None:
|
|
131
|
+
"""Handle a key up (arrow up) press."""
|
|
132
|
+
if self._sel_index == self._show_from and self._show_from > 0:
|
|
133
|
+
self._show_from -= 1
|
|
134
|
+
self._show_to -= 1
|
|
135
|
+
if self._sel_index - 1 >= 0:
|
|
136
|
+
self._sel_index -= 1
|
|
137
|
+
self._re_render = True
|
|
138
|
+
|
|
139
|
+
def _handle_key_down(self) -> None:
|
|
140
|
+
"""Handle a key down (arrow down) press."""
|
|
141
|
+
length = len(self.items)
|
|
142
|
+
if self._sel_index + 1 == self._show_to and self._show_to < length:
|
|
143
|
+
self._show_from += 1
|
|
144
|
+
self._show_to += 1
|
|
145
|
+
if self._sel_index + 1 < length:
|
|
146
|
+
self._sel_index += 1
|
|
147
|
+
self._re_render = True
|
|
148
|
+
|
|
149
|
+
def _handle_tab(self) -> None:
|
|
150
|
+
"""Handle a tab keypress."""
|
|
151
|
+
length = len(self.items)
|
|
152
|
+
page_index = min(self._show_to + self._diff_index, length)
|
|
153
|
+
self._show_to = max(page_index, self._diff_index)
|
|
154
|
+
self._show_from = self._show_to - self._diff_index
|
|
155
|
+
self._sel_index = self._show_from
|
|
156
|
+
self._re_render = True
|
|
157
|
+
|
|
158
|
+
def _handle_shift_tab(self) -> None:
|
|
159
|
+
"""Handle a shift tab keypress."""
|
|
160
|
+
page_index = max(self._show_from - self._diff_index, 0)
|
|
161
|
+
self._show_from = min(page_index, self._diff_index)
|
|
162
|
+
self._show_to = self._show_from + self._diff_index
|
|
163
|
+
self._sel_index = self._show_from
|
|
164
|
+
self._re_render = True
|
|
165
|
+
|
|
166
|
+
def _handle_digit(self, digit: Keyboard) -> None:
|
|
167
|
+
"""Handle a digit keypress.
|
|
168
|
+
:param digit: the digit pressed.
|
|
169
|
+
"""
|
|
170
|
+
length = len(self._items)
|
|
171
|
+
typed_index = digit.value
|
|
172
|
+
self.write(digit.value) # echo the digit typed
|
|
173
|
+
index_len = 1
|
|
174
|
+
while len(typed_index) < len(str(length)):
|
|
175
|
+
keystroke = Keyboard.wait_keystroke()
|
|
176
|
+
if not keystroke or not keystroke.isdigit():
|
|
177
|
+
typed_index = None if keystroke != Keyboard.VK_ENTER else typed_index
|
|
178
|
+
break
|
|
179
|
+
typed_index = f"{typed_index}{keystroke.value if keystroke else ''}"
|
|
180
|
+
self.write(f"{keystroke.value if keystroke else ''}")
|
|
181
|
+
index_len += 1
|
|
182
|
+
# Erase the index typed by the user
|
|
183
|
+
self.cursor.move(index_len, Direction.LEFT)
|
|
184
|
+
self.cursor.erase(Direction.RIGHT)
|
|
185
|
+
if typed_index and 1 <= int(typed_index) <= length:
|
|
186
|
+
self._show_to = max(int(typed_index), self._diff_index)
|
|
187
|
+
self._show_from = self._show_to - self._diff_index
|
|
188
|
+
self._sel_index = int(typed_index) - 1
|
|
189
|
+
self._re_render = True
|
|
190
|
+
|
|
191
|
+
def _default_trigger_cb(self, source: TUIMenu) -> Optional["TUIMenu"]:
|
|
192
|
+
return get_or_default(self._items, self._sel_index, self._parent)
|
|
193
|
+
|
|
194
|
+
@cached_property
|
|
195
|
+
def _digits(self) -> List[Keyboard]:
|
|
196
|
+
return Keyboard.digits()
|