How does urwid handle keypress hierarchy?

326 Views Asked by At

I'm building a UI for a text adventure game. I want my topmost widget to handle keypresses for simple shortcuts. 'Enter' to open an input field for instance. But if the input field ist the current focus widget, I want the 'enter' keypress to emit a connected signal.

Is there a way to pass the keypress to the current focus widget, without the container handling it first? The only solution I can think of is the following:

class UserInput(urwid.Edit):
    
    signals = ['submit']
    
    def __init__(self):
        super().__init__(u">> ", wrap='clip')
    
    def keypress(self, size, key):
        if key == 'enter':
            self._emit('submit')
        else:
            super().keypress(size, key)

class Container(urwid.Frame):
    
    def keypress(self, size, key):
        # pass keys with double binding if focus widget is input field
        if isinstance(self.focus, urwid.Edit):
            pass
        else:
            # Move Player
            if key in ['w', 'a', 's', 'd', 'q', 'e']:
                self.handle_userInput(key)
            # Append input field to bottom of ListWalker
            elif key in [' ', 'enter']:
                self.textBox.show_inputField()
            ...
    
        # Open Menu in side panel
        if key == 'esc':
            self.switch_infoTab('menu')
        # pass unhandled keys to base class method
        else:
            super().keypress(size, key)

I think the type check at the beginning of Container.keypress is clunky. A hint to a better solution would be much apprechiated.

1

There are 1 best solutions below

0
On

If understand Urwid correctly this should indeed be possible! Think of what the arrow keys do for navigation in nested hierarchies of widgets.

Example: Let's say there is a Columns widget that contains two Edit widgets, and the first Edit has the focus. When you press right, the cursor will step through the string in the Edit widget until you reach the end. Then the focus will shift to the second Edit widget.

How does this work? The keypress method has a return value. If it returns None it means the widget handled the key press. If the returns the key value back, it means that it did not handle key press. The Edit widget returns None when it is moving the cursor inside the widget. Once the end is reached, it starts return key instead. The Columns widget tries to call keyprees on its focused widget. If it returns None then it is done. If it returns a key, then the Columns widget shifts the focus to the next child instead.

So in your case, you can call keypress on the currently focused widget in your Container widget. If it returns key back and the key is 'enter' then you can launch a new input field.

I learned this from the following page: http://urwid.org/manual/widgets.html (see the "then keyboard input will be handled this way:" part).