mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Invisible widgets now dont render
This commit is contained in:
49
examples/dev_sandbox.css
Normal file
49
examples/dev_sandbox.css
Normal file
@@ -0,0 +1,49 @@
|
||||
/* CSS file for dev_sandbox.py */
|
||||
|
||||
App > View {
|
||||
docks: side=left/1;
|
||||
text: on #20639b;
|
||||
}
|
||||
|
||||
Widget:hover {
|
||||
outline: heavy;
|
||||
text: bold !important;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
text: #09312e on #3caea3;
|
||||
dock: side;
|
||||
width: 30;
|
||||
offset-x: -100%;
|
||||
transition: offset 500ms in_out_cubic;
|
||||
border-right: outer #09312e;
|
||||
}
|
||||
|
||||
#sidebar.-active {
|
||||
offset-x: 0;
|
||||
}
|
||||
|
||||
#header {
|
||||
text: white on #173f5f;
|
||||
height: 3;
|
||||
border: hkey;
|
||||
}
|
||||
|
||||
#header.-visible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#content {
|
||||
text: white on #20639b;
|
||||
border-bottom: hkey #0f2b41;
|
||||
offset-y: -3;
|
||||
}
|
||||
|
||||
#content.-content-visible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#footer {
|
||||
text: #3a3009 on #f6d55c;
|
||||
height: 3;
|
||||
}
|
||||
32
examples/dev_sandbox.py
Normal file
32
examples/dev_sandbox.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from rich.console import RenderableType
|
||||
from rich.panel import Panel
|
||||
|
||||
from textual.app import App
|
||||
from textual.widget import Widget
|
||||
|
||||
|
||||
class PanelWidget(Widget):
|
||||
def render(self) -> RenderableType:
|
||||
return Panel("hello world!", title="Title")
|
||||
|
||||
|
||||
class BasicApp(App):
|
||||
"""Sandbox application used for testing/development by Textual developers"""
|
||||
|
||||
def on_load(self):
|
||||
"""Bind keys here."""
|
||||
self.bind("tab", "toggle_class('#sidebar', '-active')")
|
||||
self.bind("a", "toggle_class('#header', '-visible')")
|
||||
self.bind("c", "toggle_class('#content', '-content-visible')")
|
||||
|
||||
def on_mount(self):
|
||||
"""Build layout here."""
|
||||
self.mount(
|
||||
header=Widget(),
|
||||
content=PanelWidget(),
|
||||
footer=Widget(),
|
||||
sidebar=Widget(),
|
||||
)
|
||||
|
||||
|
||||
BasicApp.run(css_file="test_app.css", watch_css=True, log="textual.log")
|
||||
@@ -9,7 +9,7 @@ from rich.style import Style
|
||||
from rich.tree import Tree
|
||||
|
||||
from .css._error_tools import friendly_list
|
||||
from .css.constants import VALID_DISPLAY
|
||||
from .css.constants import VALID_DISPLAY, VALID_VISIBILITY
|
||||
from .css.errors import StyleValueError
|
||||
from .css.styles import Styles
|
||||
from .message_pump import MessagePump
|
||||
@@ -160,6 +160,22 @@ class DOMNode(MessagePump):
|
||||
f"expected {friendly_list(VALID_DISPLAY)})",
|
||||
)
|
||||
|
||||
@property
|
||||
def visible(self) -> bool:
|
||||
return self.styles.visibility != "hidden"
|
||||
|
||||
@visible.setter
|
||||
def visible(self, new_value: bool) -> None:
|
||||
if isinstance(new_value, bool):
|
||||
self.styles.visibility = "visible" if new_value else "hidden"
|
||||
elif new_value in VALID_VISIBILITY:
|
||||
self.styles.visibility = new_value
|
||||
else:
|
||||
raise StyleValueError(
|
||||
f"invalid value for visibility (received {new_value!r}, "
|
||||
f"expected {friendly_list(VALID_VISIBILITY)})"
|
||||
)
|
||||
|
||||
@property
|
||||
def z(self) -> tuple[int, ...]:
|
||||
"""Get the z index tuple for this node.
|
||||
|
||||
@@ -199,6 +199,7 @@ class Layout(ABC):
|
||||
raise NoWidget(f"No widget under screen coordinate ({x}, {y})")
|
||||
|
||||
def get_style_at(self, x: int, y: int) -> Style:
|
||||
"""Get the Style at the given cell or Style.null()"""
|
||||
try:
|
||||
widget, region = self.get_widget_at(x, y)
|
||||
except NoWidget:
|
||||
@@ -217,6 +218,18 @@ class Layout(ABC):
|
||||
return Style.null()
|
||||
|
||||
def get_widget_region(self, widget: Widget) -> Region:
|
||||
"""Get the Region of a Widget contained in this Layout.
|
||||
|
||||
Args:
|
||||
widget: The Widget in this layout you wish to know the Region of.
|
||||
|
||||
Raises:
|
||||
KeyError: If the Widget is not contained in this Layout.
|
||||
|
||||
Returns:
|
||||
Region: The Region of the Widget.
|
||||
|
||||
"""
|
||||
try:
|
||||
region, *_ = self.map[widget]
|
||||
except KeyError:
|
||||
@@ -270,7 +283,7 @@ class Layout(ABC):
|
||||
|
||||
for widget, region, _order, clip in widget_regions:
|
||||
|
||||
if not widget.is_visual:
|
||||
if not (widget.is_visual and widget.visible):
|
||||
continue
|
||||
|
||||
lines = widget._get_lines()
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import chain
|
||||
from typing import Callable, Iterable, ClassVar, TYPE_CHECKING
|
||||
from typing import Callable, Iterable, TYPE_CHECKING
|
||||
|
||||
from rich.console import RenderableType
|
||||
import rich.repr
|
||||
from rich.console import RenderableType
|
||||
from rich.style import Style
|
||||
|
||||
from . import events
|
||||
from . import errors
|
||||
from . import log
|
||||
from . import events
|
||||
from . import messages
|
||||
from .geometry import Size, Offset, Region
|
||||
from .layout import Layout, NoWidget, WidgetPlacement
|
||||
from .layouts.factory import get_layout
|
||||
from .geometry import Size, Offset, Region
|
||||
from .reactive import Reactive, watch
|
||||
|
||||
from .widget import Widget, Widget
|
||||
|
||||
from .widget import Widget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .app import App
|
||||
pass
|
||||
|
||||
|
||||
class LayoutProperty:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from logging import PercentStyle, getLogger
|
||||
from logging import getLogger
|
||||
from typing import (
|
||||
Any,
|
||||
Awaitable,
|
||||
@@ -11,35 +11,29 @@ from typing import (
|
||||
NamedTuple,
|
||||
cast,
|
||||
)
|
||||
import rich.repr
|
||||
from rich import box
|
||||
from rich.align import Align
|
||||
from rich.console import Console, RenderableType, ConsoleOptions
|
||||
from rich.measure import Measurement
|
||||
from rich.panel import Panel
|
||||
from rich.padding import Padding
|
||||
from rich.pretty import Pretty
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style, StyleType
|
||||
from rich.styled import Styled
|
||||
from rich.text import Text, TextType
|
||||
|
||||
from . import events
|
||||
import rich.repr
|
||||
from rich.align import Align
|
||||
from rich.console import Console, RenderableType
|
||||
from rich.padding import Padding
|
||||
from rich.style import Style
|
||||
from rich.styled import Styled
|
||||
from rich.text import Text
|
||||
|
||||
from . import errors
|
||||
from . import events
|
||||
from ._animator import BoundAnimator
|
||||
from ._border import Border, BORDER_STYLES
|
||||
from ._border import Border
|
||||
from ._callback import invoke
|
||||
from .blank import Blank
|
||||
from .dom import DOMNode
|
||||
from ._context import active_app
|
||||
from .geometry import Size, Spacing, SpacingDimensions
|
||||
from ._types import Lines
|
||||
from .dom import DOMNode
|
||||
from .geometry import Size, Spacing
|
||||
from .message import Message
|
||||
from .messages import Layout, Update
|
||||
from .reactive import Reactive, watch
|
||||
from ._types import Lines
|
||||
from .reactive import watch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .app import App
|
||||
from .view import View
|
||||
|
||||
log = getLogger("rich")
|
||||
@@ -163,34 +157,29 @@ class Widget(DOMNode):
|
||||
styles = self.styles
|
||||
parent_text_style = self.parent.text_style
|
||||
|
||||
if styles.visibility == "hidden":
|
||||
renderable = Blank(parent_text_style)
|
||||
else:
|
||||
text_style = styles.text
|
||||
renderable_text_style = parent_text_style + text_style
|
||||
if renderable_text_style:
|
||||
renderable = Styled(renderable, renderable_text_style)
|
||||
text_style = styles.text
|
||||
renderable_text_style = parent_text_style + text_style
|
||||
if renderable_text_style:
|
||||
renderable = Styled(renderable, renderable_text_style)
|
||||
|
||||
if styles.has_padding:
|
||||
renderable = Padding(
|
||||
renderable, styles.padding, style=renderable_text_style
|
||||
)
|
||||
if styles.has_padding:
|
||||
renderable = Padding(
|
||||
renderable, styles.padding, style=renderable_text_style
|
||||
)
|
||||
|
||||
if styles.has_border:
|
||||
renderable = Border(
|
||||
renderable, styles.border, style=renderable_text_style
|
||||
)
|
||||
if styles.has_border:
|
||||
renderable = Border(renderable, styles.border, style=renderable_text_style)
|
||||
|
||||
if styles.has_margin:
|
||||
renderable = Padding(renderable, styles.margin, style=parent_text_style)
|
||||
if styles.has_margin:
|
||||
renderable = Padding(renderable, styles.margin, style=parent_text_style)
|
||||
|
||||
if styles.has_outline:
|
||||
renderable = Border(
|
||||
renderable,
|
||||
styles.outline,
|
||||
outline=True,
|
||||
style=renderable_text_style,
|
||||
)
|
||||
if styles.has_outline:
|
||||
renderable = Border(
|
||||
renderable,
|
||||
styles.outline,
|
||||
outline=True,
|
||||
style=renderable_text_style,
|
||||
)
|
||||
|
||||
return renderable
|
||||
|
||||
|
||||
28
tests/test_widget.py
Normal file
28
tests/test_widget.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import pytest
|
||||
|
||||
from textual.css.errors import StyleValueError
|
||||
from textual.widget import Widget
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"set_val, get_val, style_str", [
|
||||
[True, True, "visible"],
|
||||
[False, False, "hidden"],
|
||||
["hidden", False, "hidden"],
|
||||
["visible", True, "visible"],
|
||||
])
|
||||
def test_widget_set_visible_true(set_val, get_val, style_str):
|
||||
widget = Widget()
|
||||
widget.visible = set_val
|
||||
|
||||
assert widget.visible is get_val
|
||||
assert widget.styles.visibility == style_str
|
||||
|
||||
|
||||
def test_widget_set_visible_invalid_string():
|
||||
widget = Widget()
|
||||
|
||||
with pytest.raises(StyleValueError):
|
||||
widget.visible = "nope! no widget for me!"
|
||||
|
||||
assert widget.visible
|
||||
Reference in New Issue
Block a user