mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #774 from Textualize/dont-render-zero-opacity-text
Minimise rendering when text-opacity/opacity is zero
This commit is contained in:
@@ -84,7 +84,7 @@ Widgets are self-contained components responsible for generating the output for
|
|||||||
|
|
||||||
Widgets can be as simple as a piece of text, a button, or a fully-fledge component like a text editor or file browser (which may contain widgets of their own).
|
Widgets can be as simple as a piece of text, a button, or a fully-fledge component like a text editor or file browser (which may contain widgets of their own).
|
||||||
|
|
||||||
### Composing
|
### Composing
|
||||||
|
|
||||||
To add widgets to your app implement a [`compose()`][textual.app.App.compose] method which should return a iterable of Widget instances. A list would work, but it is convenient to yield widgets, making the method a *generator*.
|
To add widgets to your app implement a [`compose()`][textual.app.App.compose] method which should return a iterable of Widget instances. A list would work, but it is convenient to yield widgets, making the method a *generator*.
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ While composing is the preferred way of adding widgets when your app starts it i
|
|||||||
|
|
||||||
Here's an app which adds the welcome widget in response to any key press:
|
Here's an app which adds the welcome widget in response to any key press:
|
||||||
|
|
||||||
```python title="widgets02.py"
|
```python title="widgets02.py"
|
||||||
--8<-- "docs/examples/app/widgets02.py"
|
--8<-- "docs/examples/app/widgets02.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ An app will run until you call [App.exit()][textual.app.App.exit] which will exi
|
|||||||
|
|
||||||
The exit method will also accept an optional positional value to be returned by `run()`. The following example uses this to return the `id` (identifier) of a clicked button.
|
The exit method will also accept an optional positional value to be returned by `run()`. The following example uses this to return the `id` (identifier) of a clicked button.
|
||||||
|
|
||||||
```python title="question01.py"
|
```python title="question01.py"
|
||||||
--8<-- "docs/examples/app/question01.py"
|
--8<-- "docs/examples/app/question01.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ The following example sets the `css_path` attribute on the app:
|
|||||||
|
|
||||||
If the path is relative (as it is above) then it is taken as relative to where the app is defined. Hence this example references `"question01.css"` in the same directory as the Python code. Here is that CSS file:
|
If the path is relative (as it is above) then it is taken as relative to where the app is defined. Hence this example references `"question01.css"` in the same directory as the Python code. Here is that CSS file:
|
||||||
|
|
||||||
```sass title="question02.css"
|
```sass title="question02.css"
|
||||||
--8<-- "docs/examples/app/question02.css"
|
--8<-- "docs/examples/app/question02.css"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
Screen {
|
Screen {
|
||||||
|
layout: center;
|
||||||
background: darkslategrey;
|
background: darkslategrey;
|
||||||
}
|
}
|
||||||
|
|
||||||
.box1 {
|
#box1 {
|
||||||
background: darkmagenta;
|
background: darkmagenta;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
padding: 4 8;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.widgets import Static
|
from textual.binding import Binding
|
||||||
|
from textual.widgets import Static, Footer
|
||||||
|
|
||||||
|
|
||||||
class JustABox(App):
|
class JustABox(App):
|
||||||
|
BINDINGS = [
|
||||||
|
Binding(key="t", action="text_fade_out", description="text-opacity fade out"),
|
||||||
|
Binding(key="o", action="widget_fade_out", description="opacity fade out"),
|
||||||
|
]
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Static("Hello, world!", classes="box1")
|
yield Static("Hello, world!", id="box1")
|
||||||
|
yield Footer()
|
||||||
|
|
||||||
|
def action_text_fade_out(self) -> None:
|
||||||
|
box = self.query_one("#box1")
|
||||||
|
self.animator.animate(box.styles, "text_opacity", value=0.0, duration=1)
|
||||||
|
|
||||||
|
def action_widget_fade_out(self) -> None:
|
||||||
|
box = self.query_one("#box1")
|
||||||
|
self.animator.animate(box.styles, "opacity", value=0.0, duration=1)
|
||||||
|
|
||||||
|
|
||||||
app = JustABox(watch_css=True, css_path="../darren/just_a_box.css")
|
app = JustABox(watch_css=True, css_path="../darren/just_a_box.css")
|
||||||
|
|||||||
@@ -604,19 +604,28 @@ class Compositor:
|
|||||||
# up to this point.
|
# up to this point.
|
||||||
_rich_traceback_guard = True
|
_rich_traceback_guard = True
|
||||||
|
|
||||||
|
def is_visible(widget: Widget) -> bool:
|
||||||
|
"""Return True if the widget is (literally) visible by examining various
|
||||||
|
properties which affect whether it can be seen or not."""
|
||||||
|
return (
|
||||||
|
widget.visible
|
||||||
|
and not widget.is_transparent
|
||||||
|
and widget.styles.opacity > 0
|
||||||
|
)
|
||||||
|
|
||||||
if self.map:
|
if self.map:
|
||||||
if crop:
|
if crop:
|
||||||
overlaps = crop.overlaps
|
overlaps = crop.overlaps
|
||||||
mapped_regions = [
|
mapped_regions = [
|
||||||
(widget, region, order, clip)
|
(widget, region, order, clip)
|
||||||
for widget, (region, order, clip, *_) in self.map.items()
|
for widget, (region, order, clip, *_) in self.map.items()
|
||||||
if widget.visible and not widget.is_transparent and overlaps(crop)
|
if is_visible(widget) and overlaps(crop)
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
mapped_regions = [
|
mapped_regions = [
|
||||||
(widget, region, order, clip)
|
(widget, region, order, clip)
|
||||||
for widget, (region, order, clip, *_) in self.map.items()
|
for widget, (region, order, clip, *_) in self.map.items()
|
||||||
if widget.visible and not widget.is_transparent
|
if is_visible(widget)
|
||||||
]
|
]
|
||||||
|
|
||||||
widget_regions = sorted(mapped_regions, key=itemgetter(2), reverse=True)
|
widget_regions = sorted(mapped_regions, key=itemgetter(2), reverse=True)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import functools
|
import functools
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
|
from rich.cells import cell_len
|
||||||
from rich.color import Color
|
from rich.color import Color
|
||||||
from rich.console import ConsoleOptions, Console, RenderResult, RenderableType
|
from rich.console import ConsoleOptions, Console, RenderResult, RenderableType
|
||||||
from rich.segment import Segment
|
from rich.segment import Segment
|
||||||
@@ -59,19 +60,25 @@ class TextOpacity:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
_Segment = Segment
|
_Segment = Segment
|
||||||
for segment in segments:
|
_from_color = Style.from_color
|
||||||
text, style, control = segment
|
if opacity == 0:
|
||||||
if not style:
|
for text, style, control in segments:
|
||||||
yield segment
|
invisible_style = _from_color(bgcolor=style.bgcolor)
|
||||||
continue
|
yield _Segment(cell_len(text) * " ", invisible_style)
|
||||||
|
else:
|
||||||
|
for segment in segments:
|
||||||
|
text, style, control = segment
|
||||||
|
if not style:
|
||||||
|
yield segment
|
||||||
|
continue
|
||||||
|
|
||||||
color = style.color
|
color = style.color
|
||||||
bgcolor = style.bgcolor
|
bgcolor = style.bgcolor
|
||||||
if color and color.triplet and bgcolor and bgcolor.triplet:
|
if color and color.triplet and bgcolor and bgcolor.triplet:
|
||||||
color_style = _get_blended_style_cached(bgcolor, color, opacity)
|
color_style = _get_blended_style_cached(bgcolor, color, opacity)
|
||||||
yield _Segment(text, style + color_style)
|
yield _Segment(text, style + color_style)
|
||||||
else:
|
else:
|
||||||
yield segment
|
yield segment
|
||||||
|
|
||||||
def __rich_console__(
|
def __rich_console__(
|
||||||
self, console: Console, options: ConsoleOptions
|
self, console: Console, options: ConsoleOptions
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ def test_simple_text_opacity(text):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_value_zero_sets_foreground_color_to_background_color(text):
|
def test_value_zero_doesnt_render_the_text(text):
|
||||||
foreground = background = "0;255;0"
|
|
||||||
assert render(TextOpacity(text, opacity=0)) == (
|
assert render(TextOpacity(text, opacity=0)) == (
|
||||||
f"\x1b[38;2;{foreground};48;2;{background}mHello, world!{STOP}"
|
f"\x1b[48;2;0;255;0m {STOP}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user