Merge branch 'css' of github.com:willmcgugan/textual into view-from-css
This commit is contained in:
commit
489f2c48d2
6 changed files with 167 additions and 27 deletions
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")
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
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
|
||||
Loading…
Reference in a new issue