How to implement partial text blackout effect #6007
-
Hi there! I’m trying to achieve a visual effect similar to text redaction—some words or sentences appear fully “blacked out,” where both the foreground and background colors are the same (e.g. black on black), making the content unreadable at first glance. Ideally, each hidden text segment should be independently revealed on hover or click by changing its color or background to restore visibility. What would be some simple or idiomatic ways to implement this using Textual’s styling or widget system? I’d love to hear any tips or best practices for achieving this kind of interactive masking. Thanks! |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
The 'redacted' visual effect is the easy part. Below is a quick example using content markup. But revealing the portion of text when hovered or clicked is a lot more complicated! from textual.app import App, ComposeResult
from textual.widgets import Static
class RedactedApp(App):
def compose(self) -> ComposeResult:
yield Static(
"Hello [on $foreground]world[/] foo [on $foreground]bar[/]",
)
if __name__ == "__main__":
app = RedactedApp()
app.run() |
Beta Was this translation helpful? Give feedback.
-
Here's a quick example of how you might reveal the redacted text when hovered. The The important part is the metadata ( from rich.text import Text
from textual import events
from textual.app import App, ComposeResult, RenderResult
from textual.reactive import reactive
from textual.widget import Widget
class RedactedText(Widget):
COMPONENT_CLASSES = {"redacted"}
DEFAULT_CSS = """
RedactedText {
width: auto;
height: auto;
.redacted {
color: $foreground;
background: $foreground;
}
}
"""
reveal_range: reactive[tuple[int, int] | None] = reactive(None)
def render(self) -> RenderResult:
redacted_style = self.get_component_rich_style("redacted")
redacted_text = Text("Hello world foo bar")
redact_ranges = [(6, 11), (16, 19)]
for redact_range in redact_ranges:
start, end = redact_range
# Add meta info - see the mouse handler below
redacted_text.apply_meta({"redacted": redact_range}, start, end)
if self.reveal_range != redact_range:
# Add a redacted style to portions of the text
redacted_text.stylize(redacted_style, start, end)
return redacted_text
def on_mouse_move(self, event: events.MouseMove) -> None:
meta = event.style.meta
if "redacted" in meta:
self.reveal_range = meta["redacted"]
else:
self.reveal_range = None
def on_leave(self) -> None:
self.reveal_range = None
class RedactedApp(App):
CSS = """
Screen {
align: center middle;
}
"""
def compose(self) -> ComposeResult:
yield RedactedText()
if __name__ == "__main__":
app = RedactedApp()
app.run() |
Beta Was this translation helpful? Give feedback.
Here's a quick example of how you might reveal the redacted text when hovered.
The
RedactedText
widget has areveal_range
reactive attribute which is updated when some redacted text is hovered. This will prompt a "smart refresh" to update which ranges have the 'redacted' styling.The important part is the metadata (
meta
) applied to portions of the text. The mouse event handler checks theevent.style.meta
and updates thereveal_range
reactive attribute accordingly.