Search code examples
pythonlistboxconsole-applicationurwidtui

How to indicate that a urwid listbox has more items than displayed at the moment?


Is there a way to show a user that a urwid listbox has additional items above / below the dispalyed section?

I'm thinking of something like a scrollbar that gives an idea of the number of entries.

listbox with vertical scrollbar.

Or a separate bar at the top / bottom of the list box.

transformation of listbox while scrolling down.

If this behavior can not be implemented, what approaches are there to achieve this notification?

During my research, I found this question, which tried to achieve eventually the same. The given answer seems to check if all elements are visible. Unfortunately, this loses its functionality if some elements are hidden at any time because the terminal is not resized.


Solution

  • I've implemented a list box that applies the second visualization concept (bars at the top and bottom) by default.

    It is called additional_urwid_widgets.IndicativeListBox and can be installed via pip.

    Demonstration of list box


    For a stand alone example, which illustrates the functionality of the widget, see here.

    For more (and simpler) examples, see here.

    For a more detailed explanation of the parameters and options, see the corresponding github wiki entry.



    Some Examples

    Minimal

    #! /usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    from additional_urwid_widgets import IndicativeListBox    # installed via pip
    import urwid                                              # installed via pip
    
    # Color schemes that specify the appearance off focus and on focus.
    PALETTE = [("reveal_focus", "black", "light cyan", "standout")]
    
    # The list box is filled with buttons.
    body = [urwid.Button(letter) for letter in "abcdefghijklmnopqrstuvwxyz"]
    
    # Wrap the list items into an 'urwid.AttrMap', so that they have an other appearance when focused.
    # Instead of an simple list-like object you can/should create a 'urwid.ListWalker'.
    attr_body = [urwid.AttrMap(entry, None, "reveal_focus") for entry in body]
    
    ilb = IndicativeListBox(attr_body)
    
    loop = urwid.MainLoop(ilb,
                          PALETTE)
    loop.run()
    

    visual output of example 'Minimal'


    Display items above/below

    #! /usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    from additional_urwid_widgets import IndicativeListBox    # installed via pip
    import urwid                                              # installed via pip
    
    # Color schemes that specify the appearance off focus and on focus.
    PALETTE = [("reveal_focus", "black", "light cyan", "standout")]
    
    # The list box is filled with buttons.
    body = [urwid.Button(letter) for letter in "abcdefghijklmnopqrstuvwxyz"]
    
    # Wrap the list items into an 'urwid.AttrMap', so that they have an other appearance when focused.
    # Instead of an simple list-like object you can/should create a 'urwid.ListWalker'.
    attr_body = [urwid.AttrMap(entry, None, "reveal_focus") for entry in body]
    
    ilb = IndicativeListBox(attr_body,
                            topBar_endCovered_prop=("{} above ...", None, None),
                            bottomBar_endCovered_prop=("{} below ...", None, None))
    
    loop = urwid.MainLoop(ilb,
                          PALETTE)
    loop.run()
    

    Visual output of example 'Display items above/below'


    In contex with other widgets (also styled)

    In this example, ctrl must be additionally pressed so that the list box responds to the input.
    This allows the widget to be used in vertical containers (such as urwid.Pile).

    #! /usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    from additional_urwid_widgets import IndicativeListBox, MODIFIER_KEY    # installed via pip
    import urwid                                                            # installed via pip
    
    # Color schemes that specify the appearance off focus and on focus.
    PALETTE = [("reveal_focus",              "black",            "light cyan",   "standout"),
               ("ilb_barActive_focus",       "dark cyan",        "light gray"),
               ("ilb_barActive_offFocus",    "light gray",       "dark gray"),
               ("ilb_barInactive_focus",     "light cyan",       "dark gray"),
               ("ilb_barInactive_offFocus",  "black",            "dark gray"),
               ("ilb_highlight_offFocus",    "black",            "dark cyan")]
    
    # The list box is filled with buttons.
    body = [urwid.Button(letter) for letter in "abcdefghijklmnopqrstuvwxyz"]
    
    # Wrap the list items into an 'urwid.AttrMap', so that they have an other appearance when focused.
    # Instead of an simple list-like object you can/should create a 'urwid.ListWalker'.
    attr_body = [urwid.AttrMap(entry, None, "reveal_focus") for entry in body]
    
    ilb = ilb = IndicativeListBox(attr_body,
                                  modifier_key=MODIFIER_KEY.CTRL,
                                  return_unused_navigation_input=False,
                                  topBar_endCovered_prop=("ᐃ", "ilb_barActive_focus", "ilb_barActive_offFocus"),
                                  topBar_endExposed_prop=("───", "ilb_barInactive_focus", "ilb_barInactive_offFocus"), 
                                  bottomBar_endCovered_prop=("ᐁ", "ilb_barActive_focus", "ilb_barActive_offFocus"), 
                                  bottomBar_endExposed_prop=("───", "ilb_barInactive_focus", "ilb_barInactive_offFocus"),
                                  highlight_offFocus="ilb_highlight_offFocus")
    
    pile = urwid.Pile([urwid.Text("The listbox responds only if 'ctrl' is pressed."),
                       urwid.Divider(" "),
                       urwid.Button("a button"),
                       urwid.BoxAdapter(ilb, 6),         # Wrap flow widget in box adapter
                       urwid.Button("another button")])
    
    
    loop = urwid.MainLoop(urwid.Filler(pile, "top"),
                          PALETTE)
    loop.run()
    

    Visual output of example 'In contex with other widgets (also styled)'