Как сделать так, чтобы кнопки библиотеки Python urwid выглядели красиво?

Внешний вид кнопки по умолчанию в urwid очень функционален, но, на мой взгляд, не очень красив. Также может раздражать, когда несколько кнопок расположены рядом друг с другом.

Как я могу добиться, чтобы кнопка отображалась с рамкой и центрированным текстом и меняла свой цвет, когда она получала фокус?

введите описание изображения здесь


person AFoeee    schedule 10.09.2018    source источник


Ответы (2)


Вы можете рисовать с помощью Urwid все, что можно нарисовать в терминале, с чистым текстом Unicode, с цветами переднего плана и фона, назначенными для каждого символа.

Учитывая это, невозможно нарисовать точно так же, как в вашем макете, потому что для рисования границ нужно использовать символы рисования блока Unicode, которые заняли бы больше места.

Я начал писать пример, но, к сожалению, сейчас нет времени его полировать.

Я делюсь здесь в его незаконченном состоянии (работает, но внешний вид выбора глючит) в надежде, что вы найдете его полезным и, возможно, послужит достаточно хорошей отправной точкой для вас, чтобы поиграть с ним.

Скриншоты:

введите здесь описание изображения

введите здесь описание изображения

Код:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function, absolute_import, division
import urwid


PALETTE = [
    ('normal', '', ''),
    ('bold', 'bold', ''),
    ('blue', 'bold', 'dark blue'),
    ('highlight', 'black', 'dark blue'),
]


def show_or_exit(key):
    if key in ('q', 'Q', 'esc'):
        raise urwid.ExitMainLoop()


class CustomButton(urwid.Button):
    button_left = urwid.Text('[')
    button_right = urwid.Text(']')


def custom_button(*args, **kwargs):
    b = CustomButton(*args, **kwargs)
    b = urwid.AttrMap(b, '', 'highlight')
    b = urwid.Padding(b, left=4, right=4)
    return b


class BoxButton(urwid.WidgetWrap):
    _border_char = u'─'
    def __init__(self, label, on_press=None, user_data=None):
        padding_size = 2
        border = self._border_char * (len(label) + padding_size * 2)
        cursor_position = len(border) + padding_size

        self.top = u'┌' + border + u'┐\n'
        self.middle = u'│  ' + label + u'  │\n'
        self.bottom = u'└' + border + u'┘'

        # self.widget = urwid.Text([self.top, self.middle, self.bottom])
        self.widget = urwid.Pile([
            urwid.Text(self.top[:-1]),
            urwid.Text(self.middle[:-1]),
            urwid.Text(self.bottom),
        ])

        self.widget = urwid.AttrMap(self.widget, '', 'highlight')

        # self.widget = urwid.Padding(self.widget, 'center')
        # self.widget = urwid.Filler(self.widget)

        # here is a lil hack: use a hidden button for evt handling
        self._hidden_btn = urwid.Button('hidden %s' % label, on_press, user_data)

        super(BoxButton, self).__init__(self.widget)

    def selectable(self):
        return True

    def keypress(self, *args, **kw):
        return self._hidden_btn.keypress(*args, **kw)

    def mouse_event(self, *args, **kw):
        return self._hidden_btn.mouse_event(*args, **kw)


if __name__ == '__main__':
    header = urwid.Text('Header')
    footer = urwid.Text('Footer')
    onclick = lambda w: footer.set_text('clicked: %r' % w)
    widget = urwid.Pile([
        header,
        urwid.Text('Simple custom buttons:'),
        urwid.Columns([
            custom_button('OK', on_press=onclick),
            custom_button('Cancel', on_press=onclick),
        ]),
        urwid.Text('Box bordered buttons:'),
        urwid.Columns([
            urwid.Padding(BoxButton('OK', on_press=onclick), left=4, right=4),
            BoxButton('Cancel', on_press=onclick),
        ]),
        footer,
    ])
    widget = urwid.Filler(widget, 'top')
    loop = urwid.MainLoop(widget, PALETTE, unhandled_input=show_or_exit)
    loop.run()
person Elias Dorneles    schedule 10.09.2018
comment
Это работает, но где задокументированы button_left и button_right? - person cYrus; 29.11.2020
comment
Я предполагаю, что они не задокументированы, но вы можете увидеть это в коде здесь: ="nofollow noreferrer">github.com/urwid/urwid/blob/ - person Elias Dorneles; 30.11.2020
comment
Спасибо, мой вопрос был больше похож на то, можем ли мы на них положиться? Но я думаю, это знают только разработчики. :) - person cYrus; 30.11.2020
comment
Я бы не беспокоился об этом, потому что: 1) это общедоступные атрибуты, поэтому их нужно расширять 2) использующий его код тривиален, поэтому при необходимости его легко воспроизвести - person Elias Dorneles; 30.11.2020

Основываясь на превосходном ответе Элиаса, я взял его класс BoxButton и немного упростил его, используя LineBox вместо того, чтобы рисовать границу вручную:

class BoxButton(urwid.WidgetWrap):
    """ Taken from https://stackoverflow.com/a/65871001/778272
    """
    def __init__(self, label, on_click):
        label_widget = urwid.Text(label, align='center')
        self.widget = urwid.LineBox(label_widget)
        self.hidden_button = urwid.Button('hidden button', on_click)
        super(BoxButton, self).__init__(self.widget)

    def selectable(self):
        return True

    def keypress(self, *args, **kwargs):
        return self.hidden_button.keypress(*args, **kwargs)

    def mouse_event(self, *args, **kwargs):
        return self.hidden_button.mouse_event(*args, **kwargs)

Контейнер должен установить ширину кнопки. Метка кнопки будет автоматически центрирована. Пример использования столбцов для отображения двух кнопок подряд:

urwid.Columns([
    (14, BoxButton('Deploy', on_deploy_clicked)),
    (1, urwid.Text(' ')),  # quick hack to add gap
    (14, BoxButton('Restart', on_restart_clicked)),
])

введите здесь описание изображения

BoxButton намеренно не украшает себя. Чтобы украсить его:

urwid.AttrMap(BoxButton('Deploy', on_clicked), 'normal', 'highlight')
person Lucio Paiva    schedule 24.01.2021