2

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.

oguz ismail
  • 1
  • 16
  • 47
  • 69
Brad Parks
  • 66,836
  • 64
  • 257
  • 336

2 Answers2

2

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.

Domarm
  • 2,360
  • 1
  • 5
  • 17
  • Thanks ! I will check it out, but did you read this [post I originally did on rich discussions](https://github.com/Textualize/rich/discussions/2021#discussioncomment-2288880) where they imply it can be done? I will try this out... much appreciated! – Brad Parks Mar 16 '22 at 20:29
  • You're welcome. I tried suggested syntax `"Hello [@click=hello]World[/]"` as well as `"Hello [@click=app.hello]World[/]"` and `"Hello [@click=app.hello()]World[/]"` without desired result. The thing is, that even `action` method in App class was not called, so nothing could happened. I believe either syntax is wrong or there is something between Rich and textual, that doesn't work properly. – Domarm Mar 16 '22 at 20:37
  • Yeah I found the same but am not strong in python, so I thought maybe I was missing it somehow. One of the maintainers of rich/textual implied it could be done in [that discussion I linked](https://github.com/Textualize/rich/discussions/2021#discussioncomment-2288880), so that's what got me wondering! Thanks again – Brad Parks Mar 16 '22 at 20:40
1

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")
BrokenBenchmark
  • 18,126
  • 7
  • 21
  • 33
Dron003
  • 11
  • 1