Textual (python) – how to add click event in simple Text object?

Question:

I’m trying to get it so I can add links in text rendered by Textual.

My text may have multiple links, for example:

Hello [@click=hello]World[/] there, how are you?
This is a test of [@click=more] more info[/] being clickable as well.

In this simple sample I made, clicking on the word "World" should hopefully change the background color to red, but it doesn’t work.

NOTE: I also bound the "b" key to do pretty much the same thing, so I could see it work
It should change the background color, and the subtitle of the app.

import os
import sys
from rich.console import RenderableType
from rich.panel import Panel
from rich.text import Text
from textual.app import App
from textual.widgets import Header, Footer, ScrollView
from textual.widgets import Placeholder

class MyApp(App):

    async def on_load(self) -> None:
        await self.bind("b", "color('blue')")

    async def on_mount(self) -> None:
        await self.view.dock(Header(), size=5, edge="top")
        await self.view.dock(Footer(), edge="bottom")
        await self.view.dock(ScrollView(Panel("Hello [@click=hello]World[/] more info here")), edge="top")

    async def action_color(self, color:str) -> None:
        self.app.sub_title = "KEYBOARD"
        self.background = f"on {color}"

    async def action_hello(self) -> None:
        self.app.sub_title = "CLICKED"
        self.background = "on red"

MyApp.run(title="Test click", log="textual.log")

I asked this same question in the textual discussions and originally rich discussions, but haven’t been able to see how to make this work from the feedback I received there, which was helpful for sure, but I’m missing something here, so thanks for any input.

Asked By: Brad Parks

||

Answers:

It seems like @click=action doesn’t work in textual (at least I couldn’t make it work at all).
After digging up in rich documentation, I stepped upon Text class. That one, has on method, that can create click callback.

It supports __add__, so You can concat multiple Text(s) together with + operator.

Second piece of the puzzle was to find out what to set as a click callback. Again I looked in the source code and found _action_targets in app.py. That contains {"app", "view"} set.

Putting all together, You can create link(s) using Text with on(click="app.callback()"), which will call action_callback method of MyApp Class (instance of textual App). Then creating final panel text by concating other Text(s) and link(s) together.

Here is working example, turning background to red clicking on Hello or green clicking on World.

from rich.panel import Panel
from rich.text import Text
from textual.app import App
from textual.widgets import Header, Footer, ScrollView


class MyApp(App):

    async def on_load(self) -> None:
        await self.bind("b", "color('blue')")

    async def on_mount(self) -> None:
        await self.view.dock(Header(), size=5, edge="top")
        await self.view.dock(Footer(), edge="bottom")
        link1 = Text("Hello").on(click="app.hello()")
        link2 = Text("World").on(click="app.world()")
        panel_text = link1 + " " + link2 + Text(" more info here")
        await self.view.dock(ScrollView(Panel(panel_text)), edge="top")

    async def action_color(self, color: str) -> None:
        self.app.sub_title = "KEYBOARD"
        self.background = f"on {color}"

    async def action_hello(self) -> None:
        self.app.sub_title = "CLICKED Hello"
        self.background = "on red"

    async def action_world(self) -> None:
        self.app.sub_title = "CLICKED World"
        self.background = "on green"


MyApp.run(title="Test click", log="textual.log")

I know it’s not ideal solution, but it’s closest I could get to what You want.

Answered By: Domarm

You can also make a text with different properties with Text.assemble():

async def on_mount(self) -> None:
    await self.view.dock(Header(), size=5, edge="top")
    await self.view.dock(Footer(), edge="bottom")
    panel_text = Text.assemble(
        "Hello ",
        Text("World","dark_blue u").on({"@click" : "app.hello()"}),
        " more ",
        Text("info","dark_blue u").on({"@click" : "app.color('blue')"}),
         " here")
    await self.view.dock(ScrollView(Panel(panel_text)), edge="top")
Answered By: Dron003
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.