Привет, Хабр! В этой статье я расскажу про npyscreen — библиотеке для создания текстовых интерфейсов для терминальных и консольных приложений.

Установка
Пакет доступен для скачивания через PyPI.
sudo pip3 install npyscreen
Типы объектов
Npyscreen использует 3 основных типа объектов:
- Application objects — обеспечивают запуск и завершение приложения, создание форм, обработку событий.
В основном используются NPSAppManaged и StandardApp(с поддержкой событий). - Form objects — область экрана, которая содержит виджеты.
Основные формы:
- FormBaseNew — пустая форма.
- Form — форма с кнопокой «ok».
- ActionForm — форма с двумя кнопками: «ok» и «cancel».
- FormWithMenus — форма, поддерживающая работу с меню.
- Widget Objects — различные элементы, расположенные на формах.
Некоторые виджеты:
- Textfield, PasswordEntry, MultiLineEdit, FilenameCombo* — формы для ввода данных.
- DateCombo, ComboBox, FilenameCombo — выпадающие списки.
- MultiSelect, MultiSelect, BufferPager — виджеты с возможностью выбора.
- Slider, TitleSlider — слайдеры.
Больше информации можно найти на официальном сайте с документацией.
Напишем Hello World
Формы удобно создавать, наследуя их от встроенных классов. Таким образом, можно переопределить встроенные методы для расширения функционала приложения.
#!/usr/bin/env python3 import npyscreen class App(npyscreen.StandardApp): def onStart(self): self.addForm("MAIN", MainForm, name="Hello Habr!") class MainForm(npyscreen.ActionForm): # Конструктор def create(self): # Добавляем виджет TitleText на форму self.title = self.add(npyscreen.TitleText, name="TitleText", value="Hello World!") # переопределенный метод, срабатывающий при нажатии на кнопку «ok» def on_ok(self): self.parentApp.setNextForm(None) # переопределенный метод, срабатывающий при нажатии на кнопку «cancel» def on_cancel(self): self.title.value = "Hello World!" MyApp = App() MyApp.run()

Расположение элементов
По умолчанию виджеты занимают максимально возможное пространство.
Чтобы задать точные координаты, нужно задать параметры:
- relx, rely — позиция виджета относительно начала координат формы.
- width, height, max_width, max_height — ограничения размеров виджета.
#!/usr/bin/env python3 import npyscreen class App(npyscreen.StandardApp): def onStart(self): self.addForm("MAIN", MainForm, name="Hello Habr!") class MainForm(npyscreen.FormBaseNew): def create(self): # Узнаем используемое формой пространство y, x = self.useable_space() self.add(npyscreen.TitleDateCombo, name="Date:", max_width=x // 2) self.add(npyscreen.TitleMultiSelect, relx=x // 2 + 1, rely=2, value=[1, 2], name="Pick Several", values=["Option1", "Option2", "Option3"], scroll_exit=True) # Можно использовать отицательные координаты self.add(npyscreen.TitleFilename, name="Filename:", rely=-5) MyApp = App() MyApp.run()

Боксы и пользовательские цвета
Сделать обертку в виде бокса просто — нужно создать класс, наследованный от BoxTitle и переопределить атрибут _contained_widget, положив туда виджет, который будет находиться внутри.
В npyscreen доступно несколько встроенных цветовых тем. При желании можно добавить свои. Установить их можно с помощью метода setTheme.
С настройкой цвета текста все немного сложнее. Мне пришлось расширить функционал библиотеки, чтобы это работало.
#!/usr/bin/env python3 from src import npyscreen import random class App(npyscreen.StandardApp): def onStart(self): # Устанавливаем тему. По умолчанию используется DefaultTheme npyscreen.setTheme(npyscreen.Themes.ColorfulTheme) self.addForm("MAIN", MainForm, name="Hello Habr!") class InputBox(npyscreen.BoxTitle): # MultiLineEdit теперь будет окружен боксом _contained_widget = npyscreen.MultiLineEdit class MainForm(npyscreen.FormBaseNew): def create(self): y, x = self.useable_space() obj = self.add(npyscreen.BoxTitle, name="BoxTitle", custom_highlighting=True, values=["first line", "second line"], rely=y // 4, max_width=x // 2 - 5, max_height=y // 2) self.add(InputBox, name="Boxed MultiLineEdit", footer="footer", relx=x // 2, rely=2) color1 = self.theme_manager.findPair(self, 'GOOD') color2 = self.theme_manager.findPair(self, 'WARNING') color3 = self.theme_manager.findPair(self, 'NO_EDIT') color_list = [color1, color2, color3] first_line_colors = [random.choice(color_list) for i in range(len("first line"))] second_line_colors = [random.choice(color_list) for i in range(len("second"))] # Заполняем строки кастомными цветами obj.entry_widget.highlighting_arr_color_data = [first_line_colors, second_line_colors] MyApp = App() MyApp.run()

События и обработчики
Класс StandardApp в npyscreen поддерживает очередь событий.
В качестве обработки нажатий используется метод add_handlers.
#!/usr/bin/env python3 import npyscreen import curses class App(npyscreen.StandardApp): def onStart(self): self.addForm("MAIN", MainForm, name="Hello Habr!") class InputBox1(npyscreen.BoxTitle): _contained_widget = npyscreen.MultiLineEdit def when_value_edited(self): self.parent.parentApp.queue_event(npyscreen.Event("event_value_edited")) class InputBox2(npyscreen.BoxTitle): _contained_widget = npyscreen.MultiLineEdit class MainForm(npyscreen.FormBaseNew): def create(self): self.add_event_hander("event_value_edited", self.event_value_edited) new_handlers = { # Устанавливаем ctrl+Q для выхода "^Q": self.exit_func, # Устанавливаем alt+enter для очистки inputbox curses.ascii.alt(curses.ascii.NL): self.inputbox_clear } self.add_handlers(new_handlers) y, x = self.useable_space() self.InputBox1 = self.add(InputBox1, name="Editable", max_height=y // 2) self.InputBox2 = self.add(InputBox2, footer="No editable", editable=False) def event_value_edited(self, event): self.InputBox2.value = self.InputBox1.value self.InputBox2.display() def inputbox_clear(self, _input): self.InputBox1.value = self.InputBox2.value = "" self.InputBox1.display() self.InputBox2.display() def exit_func(self, _input): exit(0) MyApp = App() MyApp.run()

Ссылки:
Официальная документация
Оригинальные исходные коды
Обновленный мною репозиторий (основной гитхаб, кажется, умер)
Телеграм клиент на npyscreen (который на первом скриншоте)
