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
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 ._node_list import NodeList
from .css._error_tools import friendly_list 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.errors import StyleValueError
from .css.styles import Styles from .css.styles import Styles
from .message_pump import MessagePump from .message_pump import MessagePump
@@ -158,6 +158,22 @@ class DOMNode(MessagePump):
f"expected {friendly_list(VALID_DISPLAY)})", 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 @property
def z(self) -> tuple[int, ...]: def z(self) -> tuple[int, ...]:
"""Get the z index tuple for this node. """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})") raise NoWidget(f"No widget under screen coordinate ({x}, {y})")
def get_style_at(self, x: int, y: int) -> Style: 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: try:
widget, region = self.get_widget_at(x, y) widget, region = self.get_widget_at(x, y)
except NoWidget: except NoWidget:
@@ -217,6 +226,18 @@ class Layout(ABC):
return Style.null() return Style.null()
def get_widget_region(self, widget: Widget) -> Region: 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: try:
region, *_ = self.map[widget] region, *_ = self.map[widget]
except KeyError: except KeyError:
@@ -270,7 +291,7 @@ class Layout(ABC):
for widget, region, _order, clip in widget_regions: for widget, region, _order, clip in widget_regions:
if not widget.is_visual: if not (widget.is_visual and widget.visible):
continue continue
lines = widget._get_lines() lines = widget._get_lines()

View File

@@ -27,7 +27,6 @@ from ._border import Border
from ._callback import invoke from ._callback import invoke
from ._context import active_app from ._context import active_app
from ._types import Lines from ._types import Lines
from .blank import Blank
from .dom import DOMNode from .dom import DOMNode
from .geometry import Size, Spacing from .geometry import Size, Spacing
from .message import Message from .message import Message
@@ -158,34 +157,29 @@ class Widget(DOMNode):
styles = self.styles styles = self.styles
parent_text_style = self.parent.text_style parent_text_style = self.parent.text_style
if styles.visibility == "hidden": text_style = styles.text
renderable = Blank(parent_text_style) renderable_text_style = parent_text_style + text_style
else: if renderable_text_style:
text_style = styles.text renderable = Styled(renderable, renderable_text_style)
renderable_text_style = parent_text_style + text_style
if renderable_text_style:
renderable = Styled(renderable, renderable_text_style)
if styles.has_padding: if styles.has_padding:
renderable = Padding( renderable = Padding(
renderable, styles.padding, style=renderable_text_style renderable, styles.padding, style=renderable_text_style
) )
if styles.has_border: if styles.has_border:
renderable = Border( renderable = Border(renderable, styles.border, style=renderable_text_style)
renderable, styles.border, style=renderable_text_style
)
if styles.has_margin: if styles.has_margin:
renderable = Padding(renderable, styles.margin, style=parent_text_style) renderable = Padding(renderable, styles.margin, style=parent_text_style)
if styles.has_outline: if styles.has_outline:
renderable = Border( renderable = Border(
renderable, renderable,
styles.outline, styles.outline,
outline=True, outline=True,
style=renderable_text_style, style=renderable_text_style,
) )
return renderable 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