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.
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.