Merge branch 'css' of github.com:willmcgugan/textual into view-from-css

This commit is contained in:
Darren Burns 2022-01-21 14:23:16 +00:00
commit 489f2c48d2
6 changed files with 167 additions and 27 deletions

49
examples/dev_sandbox.css Normal file
View 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
View 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")

View file

@ -10,7 +10,7 @@ from rich.tree import Tree
from ._node_list import NodeList
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
@ -158,6 +158,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.

View file

@ -199,6 +199,15 @@ 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()
Args:
x (int): X position within the Layout
y (int): Y position within the Layout
Returns:
Style: The Style at the cell (x, y) within the Layout
"""
try:
widget, region = self.get_widget_at(x, y)
except NoWidget:
@ -217,6 +226,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 (Widget): The Widget in this layout you wish to know the Region of.
Raises:
NoWidget: 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 +291,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()

View file

@ -27,7 +27,6 @@ from ._border import Border
from ._callback import invoke
from ._context import active_app
from ._types import Lines
from .blank import Blank
from .dom import DOMNode
from .geometry import Size, Spacing
from .message import Message
@ -158,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
View 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