using urwid
, I'm trying to separate the highlight/walk and cursor functionality of a Pile
widget. How can I use up/down
to change which widget is highlighted, while keeping the cursor in a different widget?
2 Answers
The default focus
behavior couples the cursor with attribute (highlighting) behavior. The example below shows one way to decouple these, where a list of SelectableIcons
retains the highlight feature, while the cursor is moved to a separate Edit
widget. It does this via:
- overriding the
keypress
method to update the focus where the cursor is not - wrapping each
SelectableIcon
inAttrMap
that change theirattribute
based on theirPile's
focus_position
- after changing the SelectableIcon attributes, the focus (cursor) is set back to the
Edit
widget viafocus_part='body'
self._w = ...
is called to update all widgets on screen
There may be more concise ways of doing this, but this should be rather flexible.
import urwid
def main():
my_widget = MyWidget()
palette = [('unselected', 'default', 'default'),
('selected', 'standout', 'default', 'bold')]
urwid.MainLoop(my_widget, palette=palette).run()
class MyWidget(urwid.WidgetWrap):
def __init__(self):
n = 10
labels = ['selection {}'.format(j) for j in range(n)]
self.header = urwid.Pile([urwid.AttrMap(urwid.SelectableIcon(label), 'unselected', focus_map='selected') for label in labels])
self.edit_widgets = [urwid.Edit('', label + ' edit_text') for label in labels]
self.body = urwid.Filler(self.edit_widgets[0])
super().__init__(urwid.Frame(header=self.header, body=self.body, focus_part='body'))
self.update_focus(new_focus_position=0)
def update_focus(self, new_focus_position=None):
self.header.focus_item.set_attr_map({None: 'unselected'})
try:
self.header.focus_position = new_focus_position
self.body = urwid.Filler(self.edit_widgets[new_focus_position])
except IndexError:
pass
self.header.focus_item.set_attr_map({None: 'selected'})
self._w = urwid.Frame(header=self.header, body=self.body, focus_part='body')
def keypress(self, size, key):
if key == 'up':
self.update_focus(new_focus_position=self.header.focus_position - 1)
if key == 'down':
self.update_focus(new_focus_position=self.header.focus_position + 1)
if key in {'Q', 'q'}:
raise urwid.ExitMainLoop()
super().keypress(size, key)
main()

- 10,618
- 8
- 35
- 58
-
niiice, you did it!! that's a pretty cool trick, thanks for sharing! =) – Elias Dorneles Mar 27 '18 at 21:13
-
Honestly i'm not that experienced (built some games + a simple drum machine), but your approach seems pretty reasonable, yeah! It may feel a bit hackish, but as I see it, what determines if it's a good design is your needs. I mean, a good approach is what fits best your requirements (and future ones you anticipate). :) – Elias Dorneles Mar 27 '18 at 21:38
-
Urwid is more of a library than a framework (even though it comes w/ a bunch of widgets + default loop), so in the end it's up to you to decide what is best to do with it. So don't worry too much and have fun! :) – Elias Dorneles Mar 27 '18 at 21:40
If you really need this, it probably makes sense to write your own widgets -- maybe based on some classes extending urwid.Text and urwid.Button
There is no real "highlight" feature in the widgets that come with urwid, there is only a "focus" feature, and it doesn't seem to be easy to decouple the focus highlight from the focus behavior.
You probably want to implement your own widgets with some sort of secondary highlighting.

- 22,556
- 11
- 85
- 107
-
1FWIW, I ended up wrapping a `SelectableIcon` in an `AttrMap`, and changing the AttrMap based on keypresses - it's a reasonable solution. I'll post it if I can condense the code into an example. Thanks for your input – anon01 Mar 26 '18 at 22:04
-
cool, sounds like a decent approach, +1! yeah, it would be cool if you could share, I'd love to see the example code :) – Elias Dorneles Mar 27 '18 at 09:46