Textualize / textual

The lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.
https://textual.textualize.io/
MIT License
24.99k stars 764 forks source link

Dynamic actions sharing the same keybinding #4755

Closed xavierog closed 1 month ago

xavierog commented 1 month ago

Dynamic bindings/actions do not allow changing the description of a "toggle" action (e.g. dark mode / light mode, expand / collapse, lock / unlock) without also changing its key.

MRE

./DarkLight.py successfully implements a toggle action using two actual actions with different keys (d: Light mode, e: Dark mode) SAME_BINDINGS= ./DarkLight.py does the same but declares the two actual actions with the same key (d). Result: only the "quit" action is retained.

Code

#!/usr/bin/env python3
from os import environ
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.widgets import Header, Footer

class DarkLight(App):
    if 'SAME_BINDINGS' in environ:
        BINDINGS = [
            ('d', 'light', 'Light mode'),
            ('d', 'dark', 'Dark  mode'),
            ('q', 'quit', 'Quit'),
        ]
    else:
        BINDINGS = [
            ('d', 'light', 'Light mode'),
            ('e', 'dark', 'Dark  mode'),
            ('q', 'quit', 'Quit'),
        ]

    def action_light(self):
        self.dark = False
        self.refresh_bindings()

    def action_dark(self):
        self.dark = True
        self.refresh_bindings()

    def check_action(self, action, _parameters):
        if action == 'light':
            return self.dark
        if action == 'dark':
            return not self.dark
        return True

    def compose(self) -> ComposeResult:
        yield Header()
        yield Footer()

if __name__ == "__main__":
    app = DarkLight()
    app.run()

Textual Diagnostics

Versions

Name Value
Textual 0.72.0
Rich 13.7.1

Python

Name Value
Version 3.12.4
Implementation CPython
Compiler GCC 13.2.0
Executable /path/to/venv/bin/python3

Operating System

Name Value
System Linux
Release 6.9.9-amd64
Version #1 SMP PREEMPT_DYNAMIC Debian 6.9.9-1 (2024-07-13)

Terminal

Name Value
Terminal Application tmux (3.4)
TERM tmux-256color
COLORTERM truecolor
FORCE_COLOR Not set
NO_COLOR Not set

Rich Console options

Name Value
size width=104, height=31
legacy_windows False
min_width 1
max_width 104
is_terminal False
encoding utf-8
max_height 31
justify None
overflow None
no_wrap False
highlight None
markup None
height None
github-actions[bot] commented 1 month ago

Thank you for your issue. Give us a little time to review it.

PS. You might want to check the FAQ if you haven't done so already.

This is an automated reply, generated by FAQtory

merriam commented 1 month ago

I think this the description of your bug, but let me know if I missed it.

  1. Run the program
  2. Press 'd' for dark mode, then 'e' for the light mode.
  3. The screen is now dark mode, but the footer says 'e Dark' meaning the footer did not update
  4. Move the mouse to trigger the footer to update.

So, the issue is that request to update the footer somehow gets stuck in a queue waiting for any other event to pop it out?

TomJGooding commented 1 month ago

@merriam Not quite, though if the footer isn't updating for you in the first example, you might try updating your version of Textual.

This boils down to a fairly common question about how to change the description of a binding. For example, where the d key toggles dark mode, update the description to display 'Light Mode' when in dark mode and 'Dark' when in light mode.

There's not currently a way of updating the binding description AFAIK. You can't bind a key to multiple actions so trying to use check_actions as a workaround isn't going to work.

MeadBarrel commented 1 month ago

The issue is that you cannot change an action's description depending on the current state. For instance you can have "toggle dark mode" but not "enable dark mode" and "disable dark mode" on the same key, switching between the two depending on whether dark mode is currently active.

willmcgugan commented 1 month ago

I can see why this happens. The code makes the erroneous assumption that there is one binding per key.

The fix is non-trivial, but required for such dynamic bindings!

github-actions[bot] commented 1 month ago

Don't forget to star the repository!

Follow @textualizeio for Textual updates.