This program displays the string Hello World in the top left corner of the screen and will run until interrupted with CTRL+C (^C).
1 2 3 4 5 6 | import urwid
txt = urwid.Text(u"Hello World")
fill = urwid.Filler(txt, 'top')
loop = urwid.MainLoop(fill)
loop.run()
|
This program initially displays the string Hello World, then it displays each key pressed, exiting when the user presses Q.
1 2 3 4 5 6 7 8 9 10 11 | import urwid
def show_or_exit(key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
txt.set_text(repr(key))
txt = urwid.Text(u"Hello World")
fill = urwid.Filler(txt, 'top')
loop = urwid.MainLoop(fill, unhandled_input=show_or_exit)
loop.run()
|
This program displays the string Hello World in the center of the screen. It uses different attributes for the text, the space on either side of the text and the space above and below the text. It waits for a keypress before exiting.
The screenshots above show how these widgets react to being resized.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import urwid
def exit_on_q(key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
palette = [
('banner', 'black', 'light gray'),
('streak', 'black', 'dark red'),
('bg', 'black', 'dark blue'),]
txt = urwid.Text(('banner', u" Hello World "), align='center')
map1 = urwid.AttrMap(txt, 'streak')
fill = urwid.Filler(map1)
map2 = urwid.AttrMap(fill, 'bg')
loop = urwid.MainLoop(map2, palette, unhandled_input=exit_on_q)
loop.run()
|
Display attributes are defined as part of a palette. Valid foreground, background and setting values are documented in Foreground and Background Settings A palette is a list of tuples containing:
- Name of the display attribute, typically a string
- Foreground color and settings for 16-color (normal) mode
- Background color for normal mode
- Settings for monochrome mode (optional)
- Foreground color and settings for 88 and 256-color modes (optional, see next example)
- Background color for 88 and 256-color modes (optional)
A Text widget is created containing the string " Hello World " with display attribute 'banner'. The attributes of text in a Text widget is set by using a (attribute, text) tuple instead of a simple text string. Display attributes will flow with the text, and multiple display attributes may be specified by combining tuples into a list. This format is called Text Markup.
An AttrMap widget is created to wrap the text widget with display attribute 'streak'. AttrMap widgets allow you to map any display attribute to any other display attribute, but by default they will set the display attribute of everything that does not already have a display attribute. In this case the text has an attribute, so only the areas around the text used for alignment will be have the new attribute.
A second AttrMap widget is created to wrap the Filler widget with attribute 'bg'.
When this program is run you can now clearly see the separation of the text, the alignment around the text, and the filler above and below the text.
See also
This program displays the string Hello World in the center of the screen. It uses a number of 256-color-mode colors to decorate the text, and will work in any terminal that supports 256-color mode. It will exit when Q is pressed.
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 | import urwid
def exit_on_q(key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
palette = [
('banner', '', '', '', '#ffa', '#60d'),
('streak', '', '', '', 'g50', '#60a'),
('inside', '', '', '', 'g38', '#808'),
('outside', '', '', '', 'g27', '#a06'),
('bg', '', '', '', 'g7', '#d06'),]
placeholder = urwid.SolidFill()
loop = urwid.MainLoop(placeholder, palette, unhandled_input=exit_on_q)
loop.screen.set_terminal_properties(colors=256)
loop.widget = urwid.AttrMap(placeholder, 'bg')
loop.widget.original_widget = urwid.Filler(urwid.Pile([]))
div = urwid.Divider()
outside = urwid.AttrMap(div, 'outside')
inside = urwid.AttrMap(div, 'inside')
txt = urwid.Text(('banner', u" Hello World "), align='center')
streak = urwid.AttrMap(txt, 'streak')
pile = loop.widget.base_widget # .base_widget skips the decorations
for item in [outside, inside, streak, inside, outside]:
pile.contents.append((item, pile.options()))
loop.run()
|
This palette only defines values for the high color foregroundand backgrounds, because only the high colors will be used. A real application should define values for all the modes in their palette. Valid foreground, background and setting values are documented in Foreground and Background Settings.
This example also demonstrates how you can build the widgets to display in a top-down order instead of the usual bottom-up order. In some places we need to use a placeholder widget because we must provide a widget before the correct one has been created.
This program asks for your name then responds Nice to meet you, (your name).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import urwid
def exit_on_q(key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
class QuestionBox(urwid.Filler):
def keypress(self, size, key):
if key != 'enter':
return super(QuestionBox, self).keypress(size, key)
self.original_widget = urwid.Text(
u"Nice to meet you,\n%s.\n\nPress Q to exit." %
edit.edit_text)
edit = urwid.Edit(u"What is your name?\n")
fill = QuestionBox(edit)
loop = urwid.MainLoop(fill, unhandled_input=exit_on_q)
loop.run()
|
The Edit widget is based on the Text widget but it accepts keyboard input for entering text, making corrections and moving the cursor around with the HOME, END and arrow keys.
Here we are customizing the Filler decoration widget that is holding our Edit widget by subclassing it and defining a new keypress() method. Customizing decoration or container widgets to handle input this way is a common pattern in Urwid applications. This pattern is easier to maintain and extend than handling all special input in an unhandled_input function.
This program asks for your name and responds Nice to meet you, (your name) while you type your name. Press DOWN then SPACE or ENTER to exit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import urwid
palette = [('I say', 'default,bold', 'default', 'bold'),]
ask = urwid.Edit(('I say', u"What is your name?\n"))
reply = urwid.Text(u"")
button = urwid.Button(u'Exit')
div = urwid.Divider()
pile = urwid.Pile([ask, div, reply, div, button])
top = urwid.Filler(pile, valign='top')
def on_ask_change(edit, new_edit_text):
reply.set_text(('I say', u"Nice to meet you, %s" % new_edit_text))
def on_exit_clicked(button):
raise urwid.ExitMainLoop()
urwid.connect_signal(ask, 'change', on_ask_change)
urwid.connect_signal(button, 'click', on_exit_clicked)
urwid.MainLoop(top, palette).run()
|
This program asks for your name and responds Nice to meet you, (your name). It then asks again, and again. Old values may be changed and the responses will be updated when you press ENTER. ENTER on a blank line exits.
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 | import urwid
def question():
return urwid.Pile([urwid.Edit(('I say', u"What is your name?\n"))])
def answer(name):
return urwid.Text(('I say', u"Nice to meet you, " + name + "\n"))
class ConversationListBox(urwid.ListBox):
def __init__(self):
body = urwid.SimpleFocusListWalker([question()])
super(ConversationListBox, self).__init__(body)
def keypress(self, size, key):
key = super(ConversationListBox, self).keypress(size, key)
if key != 'enter':
return key
name = self.focus[0].edit_text
if not name:
raise urwid.ExitMainLoop()
# replace or add response
self.focus.contents[1:] = [(answer(name), self.focus.options())]
pos = self.focus_position
# add a new question
self.body.insert(pos + 1, question())
self.focus_position = pos + 1
palette = [('I say', 'default,bold', 'default'),]
urwid.MainLoop(ConversationListBox(), palette).run()
|
ListBox widgets let you scroll through a number of flow widgets vertically. It handles UP, DOWN, PAGE UP and PAGE DOWN keystrokes and changing the focus for you. ListBox Contents are managed by a “list walker”, one of the list walkers that is easiest to use is SimpleFocusListWalker.
SimpleFocusListWalker is like a normal python list of widgets, but any time you insert or remove widgets the focus position is updated automatically.
Here we are customizing our ListBox‘s keypress handling by overriding it in a subclass.
We can use the same sort of code to build a simple adventure game. Instead of menus we have “places” and instead of submenus and parent menus we just have “exits”. This example scrolls previous places off the top of the screen, allowing you to scroll back to view but not interact with previous places.
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 | import urwid
class ActionButton(urwid.Button):
def __init__(self, caption, callback):
super(ActionButton, self).__init__("")
urwid.connect_signal(self, 'click', callback)
self._w = urwid.AttrMap(urwid.SelectableIcon(caption, 1),
None, focus_map='reversed')
class Place(urwid.WidgetWrap):
def __init__(self, name, choices):
super(Place, self).__init__(
ActionButton([u" > go to ", name], self.enter_place))
self.heading = urwid.Text([u"\nLocation: ", name, "\n"])
self.choices = choices
# create links back to ourself
for child in choices:
getattr(child, 'choices', []).insert(0, self)
def enter_place(self, button):
game.update_place(self)
class Thing(urwid.WidgetWrap):
def __init__(self, name):
super(Thing, self).__init__(
ActionButton([u" * take ", name], self.take_thing))
self.name = name
def take_thing(self, button):
self._w = urwid.Text(u" - %s (taken)" % self.name)
game.take_thing(self)
def exit_program(button):
raise urwid.ExitMainLoop()
map_top = Place(u'porch', [
Place(u'kitchen', [
Place(u'refrigerator', []),
Place(u'cupboard', [
Thing(u'jug'),
]),
]),
Place(u'garden', [
Place(u'tree', [
Thing(u'lemon'),
Thing(u'bird'),
]),
]),
Place(u'street', [
Place(u'store', [
Thing(u'sugar'),
]),
Place(u'lake', [
Place(u'beach', []),
]),
]),
])
class AdventureGame(object):
def __init__(self):
self.log = urwid.SimpleFocusListWalker([])
self.top = urwid.ListBox(self.log)
self.inventory = set()
self.update_place(map_top)
def update_place(self, place):
if self.log: # disable interaction with previous place
self.log[-1] = urwid.WidgetDisable(self.log[-1])
self.log.append(urwid.Pile([place.heading] + place.choices))
self.top.focus_position = len(self.log) - 1
self.place = place
def take_thing(self, thing):
self.inventory.add(thing.name)
if self.inventory >= set([u'sugar', u'lemon', u'jug']):
response = urwid.Text(u'You can make lemonade!\n')
done = ActionButton(u' - Joy', exit_program)
self.log[:] = [response, done]
else:
self.update_place(self.place)
game = AdventureGame()
urwid.MainLoop(game.top, palette=[('reversed', 'standout', '')]).run()
|
This example starts to show some separation between the application logic and the widgets that have been created. The AdventureGame class is responsible for all the changes that happen through the game and manages the topmost widget, but isn’t a widget itself. This is a good pattern to follow as your application grows larger.