From 4b5fd43423a327e4cd6d477a66bebc9588fd1488 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?=
<5621605+RodrigoGiraoSerrao@users.noreply.github.com>
Date: Thu, 17 Nov 2022 16:40:09 +0000
Subject: [PATCH 01/29] Add scaffolding for the Placeholder widget.
---
src/textual/widgets/_placeholder.py | 214 +++++++++++++++++++++++-----
1 file changed, 175 insertions(+), 39 deletions(-)
diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py
index 7ce714a24..3562a7d73 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -1,63 +1,199 @@
from __future__ import annotations
-from rich import box
+from itertools import cycle
+from typing import Literal
+
+from rich import box, repr
from rich.align import Align
-from rich.console import RenderableType
from rich.panel import Panel
from rich.pretty import Pretty
-import rich.repr
-from rich.style import Style
from .. import events
-from ..reactive import Reactive
-from ..widget import Widget
+from ..css._error_tools import friendly_list
+from ..reactive import reactive
+from ..widgets import Static
+
+PlaceholderVariant = Literal["default", "state", "position", "css", "text"]
+_VALID_PLACEHOLDER_VARIANTS_ORDERED = ["default", "state", "position", "css", "text"]
+_VALID_PLACEHOLDER_VARIANTS = set(_VALID_PLACEHOLDER_VARIANTS_ORDERED)
+_PLACEHOLDER_BACKGROUND_COLORS = [
+ "#881177",
+ "#aa3355",
+ "#cc6666",
+ "#ee9944",
+ "#eedd00",
+ "#99dd55",
+ "#44dd88",
+ "#22ccbb",
+ "#00bbcc",
+ "#0099cc",
+ "#3366bb",
+ "#663399",
+]
-@rich.repr.auto(angular=False)
-class Placeholder(Widget, can_focus=True):
+class InvalidPlaceholderVariant(Exception):
+ pass
- has_focus: Reactive[bool] = Reactive(False)
- mouse_over: Reactive[bool] = Reactive(False)
+
+@repr.auto(angular=False)
+class Placeholder(Static, can_focus=True):
+ """A simple placeholder widget to use before you build your custom widgets.
+
+ This placeholder has a couple of variants that show different data.
+ Clicking the placeholder cycles through the available variants, but a placeholder
+ can also be initialised in a specific variant.
+
+ The variants available are:
+ default: shows a placeholder with a solid color.
+ state: shows the placeholder mouse over and focus state.
+ position: shows the size and position of the placeholder.
+ css: shows the css rules that apply to the placeholder.
+ text: shows some Lorem Ipsum text on the placeholder."""
+
+ DEFAULT_CSS = """
+ Placeholder {
+ content-align: center middle;
+ }
+ """
+ # Consecutive placeholders get assigned consecutive colors.
+ COLORS = cycle(_PLACEHOLDER_BACKGROUND_COLORS)
+
+ variant = reactive("default")
def __init__(
- # parent class constructor signature:
self,
- *children: Widget,
+ variant: PlaceholderVariant = "default",
+ *,
name: str | None = None,
id: str | None = None,
classes: str | None = None,
- # ...and now for our own class specific params:
- title: str | None = None,
) -> None:
- super().__init__(*children, name=name, id=id, classes=classes)
- self.title = title
+ """Create a Placeholder widget.
- def __rich_repr__(self) -> rich.repr.Result:
- yield from super().__rich_repr__()
- yield "has_focus", self.has_focus, False
- yield "mouse_over", self.mouse_over, False
+ Args:
+ variant (PlaceholderVariant, optional): The variant of the placeholder.
+ Defaults to "default".
+ name (str | None, optional): The name of the placeholder. Defaults to None.
+ id (str | None, optional): The ID of the placeholder in the DOM.
+ Defaults to None.
+ classes (str | None, optional): A space separated string with the CSS classes
+ of the placeholder, if any. Defaults to None.
+ """
+ super().__init__(name=name, id=id, classes=classes)
+ self.color = next(Placeholder.COLORS)
+ self.variant = self.validate_variant(variant)
+ # Set a cycle through the variants with the correct starting point.
+ self.variants_cycle = cycle(_VALID_PLACEHOLDER_VARIANTS_ORDERED)
+ while next(self.variants_cycle) != self.variant:
+ pass
- def render(self) -> RenderableType:
- # Apply colours only inside render_styled
- # Pass the full RICH style object into `render` - not the `Styles`
- return Panel(
- Align.center(
- Pretty(self, no_wrap=True, overflow="ellipsis"),
- vertical="middle",
- ),
- title=self.title or self.__class__.__name__,
- border_style="green" if self.mouse_over else "blue",
- box=box.HEAVY if self.has_focus else box.ROUNDED,
+ def on_click(self) -> None:
+ """Clicking on the placeholder cycles through the placeholder variants."""
+ self.cycle_variant()
+
+ def cycle_variant(self) -> None:
+ """Get the next variant in the cycle."""
+ self.variant = next(self.variants_cycle)
+
+ def watch_variant(self, old_variant: str, variant: str) -> None:
+ self.remove_class(f"-{old_variant}")
+ self.add_class(f"-{variant}")
+ self.update_on_variant_change(variant)
+
+ def update_on_variant_change(self, variant: str) -> None:
+ """Calls the appropriate method to update the render of the placeholder."""
+ update_variant_method = getattr(self, f"_update_{variant}_variant", None)
+ assert update_variant_method is not None
+ try:
+ update_variant_method()
+ except TypeError as te: # triggered if update_variant_method is None
+ raise InvalidPlaceholderVariant(
+ "Valid placeholder variants are "
+ + f"{friendly_list(_VALID_PLACEHOLDER_VARIANTS)}"
+ ) from te
+
+ def _update_default_variant(self) -> None:
+ """Update the placeholder with the "default" variant.
+
+ This variant prints a panel with a solid color.
+ """
+ self.update(
+ Panel(
+ Align.center("Placeholder"),
+ style=f"on {self.color}",
+ border_style=self.color,
+ )
)
- async def on_focus(self, event: events.Focus) -> None:
- self.has_focus = True
+ def _update_state_variant(self) -> None:
+ """Update the placeholder with the "state" variant.
- async def on_blur(self, event: events.Blur) -> None:
- self.has_focus = False
+ This variant pretty prints the placeholder, together with information about
+ whether the placeholder has focus and/or the mouse over it.
+ """
+ data = {"has_focus": self.has_focus, "mouse_over": self.mouse_over}
+ self.update(
+ Panel(
+ Align.center(
+ Pretty(data),
+ vertical="middle",
+ ),
+ title="Placeholder",
+ border_style="green" if self.mouse_over else "blue",
+ box=box.HEAVY if self.has_focus else box.ROUNDED,
+ )
+ )
- async def on_enter(self, event: events.Enter) -> None:
- self.mouse_over = True
+ def _update_position_variant(self) -> None:
+ """Update the placeholder with the "position" variant.
- async def on_leave(self, event: events.Leave) -> None:
- self.mouse_over = False
+ This variant shows the position and the size of the widget.
+ """
+ width, height = self.size
+ position_data = {
+ "width": width,
+ "height": height,
+ }
+ self.update(Panel(Align.center(Pretty(position_data)), title="Placeholder"))
+
+ def _update_css_variant(self) -> None:
+ """Update the placeholder with the "css" variant.
+
+ This variant shows all the CSS rules that are applied to this placeholder."""
+ self.update(Panel(Pretty(self.styles), title="Placeholder"))
+
+ def _update_text_variant(self) -> None:
+ """Update the placeholder with the "text" variant.
+
+ This variant shows some Lorem Ipsum text."""
+ self.update(
+ Panel(
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam feugiat ac elit sit amet accumsan. Suspendisse bibendum nec libero quis gravida. Phasellus id eleifend ligula. Nullam imperdiet sem tellus, sed vehicula nisl faucibus sit amet. Praesent iaculis tempor ultricies. Sed lacinia, tellus id rutrum lacinia, sapien sapien congue mauris, sit amet pellentesque quam quam vel nisl. Curabitur vulputate erat pellentesque mauris posuere, non dictum risus mattis.",
+ title="Placeholder",
+ )
+ )
+
+ def on_resize(self, event: events.Resize) -> None:
+ """Update the placeholder render if the current variant needs it."""
+ if self.variant == "position":
+ self._update_position_variant()
+
+ def watch_has_focus(self, has_focus: bool) -> None:
+ """Update the placeholder render if the current variant needs it."""
+ if self.variant == "state":
+ self._update_state_variant()
+
+ def watch_mouse_over(self, mouse_over: bool) -> None:
+ """Update the placeholder render if the current variant needs it."""
+ if self.variant == "state":
+ self._update_state_variant()
+
+ def validate_variant(self, variant: PlaceholderVariant) -> str:
+ """Validate the variant to which the placeholder was set."""
+ if variant not in _VALID_PLACEHOLDER_VARIANTS:
+ raise InvalidPlaceholderVariant(
+ "Valid placeholder variants are "
+ + f"{friendly_list(_VALID_PLACEHOLDER_VARIANTS)}"
+ )
+ return variant
From 67947d5806bb3181eba349f0da3fd35e0542d1be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?=
<5621605+RodrigoGiraoSerrao@users.noreply.github.com>
Date: Thu, 17 Nov 2022 16:49:13 +0000
Subject: [PATCH 02/29] Fix documentation about the variant 'size'.
---
src/textual/widgets/_placeholder.py | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py
index 3562a7d73..e645e8b04 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -13,8 +13,8 @@ from ..css._error_tools import friendly_list
from ..reactive import reactive
from ..widgets import Static
-PlaceholderVariant = Literal["default", "state", "position", "css", "text"]
-_VALID_PLACEHOLDER_VARIANTS_ORDERED = ["default", "state", "position", "css", "text"]
+PlaceholderVariant = Literal["default", "state", "size", "css", "text"]
+_VALID_PLACEHOLDER_VARIANTS_ORDERED = ["default", "state", "size", "css", "text"]
_VALID_PLACEHOLDER_VARIANTS = set(_VALID_PLACEHOLDER_VARIANTS_ORDERED)
_PLACEHOLDER_BACKGROUND_COLORS = [
"#881177",
@@ -47,9 +47,10 @@ class Placeholder(Static, can_focus=True):
The variants available are:
default: shows a placeholder with a solid color.
state: shows the placeholder mouse over and focus state.
- position: shows the size and position of the placeholder.
+ size: shows the size of the placeholder.
css: shows the css rules that apply to the placeholder.
- text: shows some Lorem Ipsum text on the placeholder."""
+ text: shows some Lorem Ipsum text on the placeholder.
+ """
DEFAULT_CSS = """
Placeholder {
@@ -145,10 +146,10 @@ class Placeholder(Static, can_focus=True):
)
)
- def _update_position_variant(self) -> None:
- """Update the placeholder with the "position" variant.
+ def _update_size_variant(self) -> None:
+ """Update the placeholder with the "size" variant.
- This variant shows the position and the size of the widget.
+ This variant shows the the size of the widget.
"""
width, height = self.size
position_data = {
From 83c8a9b55f4ad9a141f23ebe9b2348ba12b06d52 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?=
<5621605+RodrigoGiraoSerrao@users.noreply.github.com>
Date: Thu, 17 Nov 2022 16:56:36 +0000
Subject: [PATCH 03/29] Style placeholder variants more uniformly.
---
src/textual/widgets/_placeholder.py | 22 ++++++++++++----------
1 file changed, 12 insertions(+), 10 deletions(-)
diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py
index e645e8b04..16809d09d 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -83,6 +83,7 @@ class Placeholder(Static, can_focus=True):
"""
super().__init__(name=name, id=id, classes=classes)
self.color = next(Placeholder.COLORS)
+ self.styles.background = f"{self.color} 50%"
self.variant = self.validate_variant(variant)
# Set a cycle through the variants with the correct starting point.
self.variants_cycle = cycle(_VALID_PLACEHOLDER_VARIANTS_ORDERED)
@@ -119,13 +120,7 @@ class Placeholder(Static, can_focus=True):
This variant prints a panel with a solid color.
"""
- self.update(
- Panel(
- Align.center("Placeholder"),
- style=f"on {self.color}",
- border_style=self.color,
- )
- )
+ self.update(Panel("", title="Placeholder"))
def _update_state_variant(self) -> None:
"""Update the placeholder with the "state" variant.
@@ -156,18 +151,25 @@ class Placeholder(Static, can_focus=True):
"width": width,
"height": height,
}
- self.update(Panel(Align.center(Pretty(position_data)), title="Placeholder"))
+ self.update(
+ Panel(
+ Align.center(Pretty(position_data), vertical="middle"),
+ title="Placeholder",
+ )
+ )
def _update_css_variant(self) -> None:
"""Update the placeholder with the "css" variant.
- This variant shows all the CSS rules that are applied to this placeholder."""
+ This variant shows all the CSS rules that are applied to this placeholder.
+ """
self.update(Panel(Pretty(self.styles), title="Placeholder"))
def _update_text_variant(self) -> None:
"""Update the placeholder with the "text" variant.
- This variant shows some Lorem Ipsum text."""
+ This variant shows some Lorem Ipsum text.
+ """
self.update(
Panel(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam feugiat ac elit sit amet accumsan. Suspendisse bibendum nec libero quis gravida. Phasellus id eleifend ligula. Nullam imperdiet sem tellus, sed vehicula nisl faucibus sit amet. Praesent iaculis tempor ultricies. Sed lacinia, tellus id rutrum lacinia, sapien sapien congue mauris, sit amet pellentesque quam quam vel nisl. Curabitur vulputate erat pellentesque mauris posuere, non dictum risus mattis.",
From 5dfa9c4845b1de8be3b2ed3e0455a6feb9a6c41d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?=
<5621605+RodrigoGiraoSerrao@users.noreply.github.com>
Date: Fri, 18 Nov 2022 09:55:27 +0000
Subject: [PATCH 04/29] Change placeholder styles.
---
src/textual/widgets/_placeholder.py | 52 +++++++++--------------------
1 file changed, 16 insertions(+), 36 deletions(-)
diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py
index 16809d09d..1b34780df 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -3,9 +3,6 @@ from __future__ import annotations
from itertools import cycle
from typing import Literal
-from rich import box, repr
-from rich.align import Align
-from rich.panel import Panel
from rich.pretty import Pretty
from .. import events
@@ -36,7 +33,6 @@ class InvalidPlaceholderVariant(Exception):
pass
-@repr.auto(angular=False)
class Placeholder(Static, can_focus=True):
"""A simple placeholder widget to use before you build your custom widgets.
@@ -66,6 +62,7 @@ class Placeholder(Static, can_focus=True):
self,
variant: PlaceholderVariant = "default",
*,
+ label: str | None = None,
name: str | None = None,
id: str | None = None,
classes: str | None = None,
@@ -75,6 +72,8 @@ class Placeholder(Static, can_focus=True):
Args:
variant (PlaceholderVariant, optional): The variant of the placeholder.
Defaults to "default".
+ label (str | None, optional): The label to identify the placeholder.
+ If no label is present, uses the placeholder ID instead. Defaults to None.
name (str | None, optional): The name of the placeholder. Defaults to None.
id (str | None, optional): The ID of the placeholder in the DOM.
Defaults to None.
@@ -82,8 +81,9 @@ class Placeholder(Static, can_focus=True):
of the placeholder, if any. Defaults to None.
"""
super().__init__(name=name, id=id, classes=classes)
+ self._placeholder_label = label if label else f"#{id}" if id else "Placeholder"
self.color = next(Placeholder.COLORS)
- self.styles.background = f"{self.color} 50%"
+ self.styles.background = f"{self.color} 70%"
self.variant = self.validate_variant(variant)
# Set a cycle through the variants with the correct starting point.
self.variants_cycle = cycle(_VALID_PLACEHOLDER_VARIANTS_ORDERED)
@@ -106,21 +106,19 @@ class Placeholder(Static, can_focus=True):
def update_on_variant_change(self, variant: str) -> None:
"""Calls the appropriate method to update the render of the placeholder."""
update_variant_method = getattr(self, f"_update_{variant}_variant", None)
- assert update_variant_method is not None
- try:
- update_variant_method()
- except TypeError as te: # triggered if update_variant_method is None
+ if update_variant_method is None:
raise InvalidPlaceholderVariant(
"Valid placeholder variants are "
+ f"{friendly_list(_VALID_PLACEHOLDER_VARIANTS)}"
- ) from te
+ )
+ update_variant_method()
def _update_default_variant(self) -> None:
"""Update the placeholder with the "default" variant.
This variant prints a panel with a solid color.
"""
- self.update(Panel("", title="Placeholder"))
+ self.update(self._placeholder_label)
def _update_state_variant(self) -> None:
"""Update the placeholder with the "state" variant.
@@ -129,17 +127,7 @@ class Placeholder(Static, can_focus=True):
whether the placeholder has focus and/or the mouse over it.
"""
data = {"has_focus": self.has_focus, "mouse_over": self.mouse_over}
- self.update(
- Panel(
- Align.center(
- Pretty(data),
- vertical="middle",
- ),
- title="Placeholder",
- border_style="green" if self.mouse_over else "blue",
- box=box.HEAVY if self.has_focus else box.ROUNDED,
- )
- )
+ self.update(Pretty(data))
def _update_size_variant(self) -> None:
"""Update the placeholder with the "size" variant.
@@ -147,23 +135,18 @@ class Placeholder(Static, can_focus=True):
This variant shows the the size of the widget.
"""
width, height = self.size
- position_data = {
+ size_data = {
"width": width,
"height": height,
}
- self.update(
- Panel(
- Align.center(Pretty(position_data), vertical="middle"),
- title="Placeholder",
- )
- )
+ self.update(Pretty(size_data))
def _update_css_variant(self) -> None:
"""Update the placeholder with the "css" variant.
This variant shows all the CSS rules that are applied to this placeholder.
"""
- self.update(Panel(Pretty(self.styles), title="Placeholder"))
+ self.update(self.styles.css)
def _update_text_variant(self) -> None:
"""Update the placeholder with the "text" variant.
@@ -171,16 +154,13 @@ class Placeholder(Static, can_focus=True):
This variant shows some Lorem Ipsum text.
"""
self.update(
- Panel(
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam feugiat ac elit sit amet accumsan. Suspendisse bibendum nec libero quis gravida. Phasellus id eleifend ligula. Nullam imperdiet sem tellus, sed vehicula nisl faucibus sit amet. Praesent iaculis tempor ultricies. Sed lacinia, tellus id rutrum lacinia, sapien sapien congue mauris, sit amet pellentesque quam quam vel nisl. Curabitur vulputate erat pellentesque mauris posuere, non dictum risus mattis.",
- title="Placeholder",
- )
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam feugiat ac elit sit amet accumsan. Suspendisse bibendum nec libero quis gravida. Phasellus id eleifend ligula. Nullam imperdiet sem tellus, sed vehicula nisl faucibus sit amet. Praesent iaculis tempor ultricies. Sed lacinia, tellus id rutrum lacinia, sapien sapien congue mauris, sit amet pellentesque quam quam vel nisl. Curabitur vulputate erat pellentesque mauris posuere, non dictum risus mattis."
)
def on_resize(self, event: events.Resize) -> None:
"""Update the placeholder render if the current variant needs it."""
- if self.variant == "position":
- self._update_position_variant()
+ if self.variant == "size":
+ self._update_size_variant()
def watch_has_focus(self, has_focus: bool) -> None:
"""Update the placeholder render if the current variant needs it."""
From 392a95d0a9a4f4e94880589ca058f11549044bec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?=
<5621605+RodrigoGiraoSerrao@users.noreply.github.com>
Date: Fri, 18 Nov 2022 09:58:45 +0000
Subject: [PATCH 05/29] Simplify docstrings.
---
src/textual/widgets/_placeholder.py | 34 ++++++++---------------------
1 file changed, 9 insertions(+), 25 deletions(-)
diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py
index 1b34780df..48a484b0e 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -44,7 +44,7 @@ class Placeholder(Static, can_focus=True):
default: shows a placeholder with a solid color.
state: shows the placeholder mouse over and focus state.
size: shows the size of the placeholder.
- css: shows the css rules that apply to the placeholder.
+ css: shows the CSS rules that apply to the placeholder.
text: shows some Lorem Ipsum text on the placeholder.
"""
@@ -114,26 +114,16 @@ class Placeholder(Static, can_focus=True):
update_variant_method()
def _update_default_variant(self) -> None:
- """Update the placeholder with the "default" variant.
-
- This variant prints a panel with a solid color.
- """
+ """Update the placeholder with its label."""
self.update(self._placeholder_label)
def _update_state_variant(self) -> None:
- """Update the placeholder with the "state" variant.
-
- This variant pretty prints the placeholder, together with information about
- whether the placeholder has focus and/or the mouse over it.
- """
+ """Update the placeholder with its focus and mouse over status."""
data = {"has_focus": self.has_focus, "mouse_over": self.mouse_over}
self.update(Pretty(data))
def _update_size_variant(self) -> None:
- """Update the placeholder with the "size" variant.
-
- This variant shows the the size of the widget.
- """
+ """Update the placeholder with the size of the placeholder."""
width, height = self.size
size_data = {
"width": width,
@@ -142,33 +132,27 @@ class Placeholder(Static, can_focus=True):
self.update(Pretty(size_data))
def _update_css_variant(self) -> None:
- """Update the placeholder with the "css" variant.
-
- This variant shows all the CSS rules that are applied to this placeholder.
- """
+ """Update the placeholder with the CSS rules applied to this placeholder."""
self.update(self.styles.css)
def _update_text_variant(self) -> None:
- """Update the placeholder with the "text" variant.
-
- This variant shows some Lorem Ipsum text.
- """
+ """Update the placeholder with some Lorem Ipsum text."""
self.update(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam feugiat ac elit sit amet accumsan. Suspendisse bibendum nec libero quis gravida. Phasellus id eleifend ligula. Nullam imperdiet sem tellus, sed vehicula nisl faucibus sit amet. Praesent iaculis tempor ultricies. Sed lacinia, tellus id rutrum lacinia, sapien sapien congue mauris, sit amet pellentesque quam quam vel nisl. Curabitur vulputate erat pellentesque mauris posuere, non dictum risus mattis."
)
def on_resize(self, event: events.Resize) -> None:
- """Update the placeholder render if the current variant needs it."""
+ """Update the placeholder "size" variant with the new placeholder size."""
if self.variant == "size":
self._update_size_variant()
def watch_has_focus(self, has_focus: bool) -> None:
- """Update the placeholder render if the current variant needs it."""
+ """Update the placeholder "state" variant with the new focus state."""
if self.variant == "state":
self._update_state_variant()
def watch_mouse_over(self, mouse_over: bool) -> None:
- """Update the placeholder render if the current variant needs it."""
+ """Update the placeholder "state" variant with the new mouse over state."""
if self.variant == "state":
self._update_state_variant()
From 8d94e32afddcce49c737a0bdf5d74034b117ea18 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?=
<5621605+RodrigoGiraoSerrao@users.noreply.github.com>
Date: Fri, 18 Nov 2022 14:33:38 +0000
Subject: [PATCH 06/29] Remove variants css and state.
---
src/textual/widgets/_placeholder.py | 76 +++++++++++++----------------
1 file changed, 34 insertions(+), 42 deletions(-)
diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py
index 48a484b0e..73c044ee3 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -3,15 +3,16 @@ from __future__ import annotations
from itertools import cycle
from typing import Literal
-from rich.pretty import Pretty
+from rich.text import Text
from .. import events
+from ..app import ComposeResult
from ..css._error_tools import friendly_list
from ..reactive import reactive
from ..widgets import Static
-PlaceholderVariant = Literal["default", "state", "size", "css", "text"]
-_VALID_PLACEHOLDER_VARIANTS_ORDERED = ["default", "state", "size", "css", "text"]
+PlaceholderVariant = Literal["default", "size", "text"]
+_VALID_PLACEHOLDER_VARIANTS_ORDERED = ["default", "size", "text"]
_VALID_PLACEHOLDER_VARIANTS = set(_VALID_PLACEHOLDER_VARIANTS_ORDERED)
_PLACEHOLDER_BACKGROUND_COLORS = [
"#881177",
@@ -33,6 +34,10 @@ class InvalidPlaceholderVariant(Exception):
pass
+class _PlaceholderLabel(Static):
+ pass
+
+
class Placeholder(Static, can_focus=True):
"""A simple placeholder widget to use before you build your custom widgets.
@@ -41,15 +46,22 @@ class Placeholder(Static, can_focus=True):
can also be initialised in a specific variant.
The variants available are:
- default: shows a placeholder with a solid color.
- state: shows the placeholder mouse over and focus state.
+ default: shows an identifier label or the ID of the placeholder.
size: shows the size of the placeholder.
- css: shows the CSS rules that apply to the placeholder.
text: shows some Lorem Ipsum text on the placeholder.
"""
DEFAULT_CSS = """
Placeholder {
+ align: center middle;
+ overflow-y: auto;
+ }
+
+ Placeholder.-text {
+ padding: 1;
+ }
+
+ Placeholder > _PlaceholderLabel {
content-align: center middle;
}
"""
@@ -81,31 +93,34 @@ class Placeholder(Static, can_focus=True):
of the placeholder, if any. Defaults to None.
"""
super().__init__(name=name, id=id, classes=classes)
- self._placeholder_label = label if label else f"#{id}" if id else "Placeholder"
- self.color = next(Placeholder.COLORS)
- self.styles.background = f"{self.color} 70%"
+ self._placeholder_text = label if label else f"#{id}" if id else "Placeholder"
+ self._placeholder_label = _PlaceholderLabel()
+ self.styles.background = f"{next(Placeholder.COLORS)} 70%"
self.variant = self.validate_variant(variant)
# Set a cycle through the variants with the correct starting point.
- self.variants_cycle = cycle(_VALID_PLACEHOLDER_VARIANTS_ORDERED)
- while next(self.variants_cycle) != self.variant:
+ self._variants_cycle = cycle(_VALID_PLACEHOLDER_VARIANTS_ORDERED)
+ while next(self._variants_cycle) != self.variant:
pass
+ def compose(self) -> ComposeResult:
+ yield self._placeholder_label
+
def on_click(self) -> None:
- """Clicking on the placeholder cycles through the placeholder variants."""
+ """Click handler to cycle through the placeholder variants."""
self.cycle_variant()
def cycle_variant(self) -> None:
"""Get the next variant in the cycle."""
- self.variant = next(self.variants_cycle)
+ self.variant = next(self._variants_cycle)
def watch_variant(self, old_variant: str, variant: str) -> None:
self.remove_class(f"-{old_variant}")
self.add_class(f"-{variant}")
- self.update_on_variant_change(variant)
+ self.call_variant_update()
- def update_on_variant_change(self, variant: str) -> None:
+ def call_variant_update(self) -> None:
"""Calls the appropriate method to update the render of the placeholder."""
- update_variant_method = getattr(self, f"_update_{variant}_variant", None)
+ update_variant_method = getattr(self, f"_update_{self.variant}_variant", None)
if update_variant_method is None:
raise InvalidPlaceholderVariant(
"Valid placeholder variants are "
@@ -115,29 +130,16 @@ class Placeholder(Static, can_focus=True):
def _update_default_variant(self) -> None:
"""Update the placeholder with its label."""
- self.update(self._placeholder_label)
-
- def _update_state_variant(self) -> None:
- """Update the placeholder with its focus and mouse over status."""
- data = {"has_focus": self.has_focus, "mouse_over": self.mouse_over}
- self.update(Pretty(data))
+ self._placeholder_label.update(self._placeholder_text)
def _update_size_variant(self) -> None:
"""Update the placeholder with the size of the placeholder."""
width, height = self.size
- size_data = {
- "width": width,
- "height": height,
- }
- self.update(Pretty(size_data))
-
- def _update_css_variant(self) -> None:
- """Update the placeholder with the CSS rules applied to this placeholder."""
- self.update(self.styles.css)
+ self._placeholder_label.update(f"[b]{width} x {height}[/b]")
def _update_text_variant(self) -> None:
"""Update the placeholder with some Lorem Ipsum text."""
- self.update(
+ self._placeholder_label.update(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam feugiat ac elit sit amet accumsan. Suspendisse bibendum nec libero quis gravida. Phasellus id eleifend ligula. Nullam imperdiet sem tellus, sed vehicula nisl faucibus sit amet. Praesent iaculis tempor ultricies. Sed lacinia, tellus id rutrum lacinia, sapien sapien congue mauris, sit amet pellentesque quam quam vel nisl. Curabitur vulputate erat pellentesque mauris posuere, non dictum risus mattis."
)
@@ -146,16 +148,6 @@ class Placeholder(Static, can_focus=True):
if self.variant == "size":
self._update_size_variant()
- def watch_has_focus(self, has_focus: bool) -> None:
- """Update the placeholder "state" variant with the new focus state."""
- if self.variant == "state":
- self._update_state_variant()
-
- def watch_mouse_over(self, mouse_over: bool) -> None:
- """Update the placeholder "state" variant with the new mouse over state."""
- if self.variant == "state":
- self._update_state_variant()
-
def validate_variant(self, variant: PlaceholderVariant) -> str:
"""Validate the variant to which the placeholder was set."""
if variant not in _VALID_PLACEHOLDER_VARIANTS:
From 21630e07fc9451b20680636eb0e4231244b238d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?=
<5621605+RodrigoGiraoSerrao@users.noreply.github.com>
Date: Fri, 18 Nov 2022 14:51:13 +0000
Subject: [PATCH 07/29] Make Placeholder non-focusable.
---
src/textual/widgets/_placeholder.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py
index 73c044ee3..b011b8ee2 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -38,7 +38,7 @@ class _PlaceholderLabel(Static):
pass
-class Placeholder(Static, can_focus=True):
+class Placeholder(Static):
"""A simple placeholder widget to use before you build your custom widgets.
This placeholder has a couple of variants that show different data.
From a87c9ca916bfa8a61fce736cd6a1eba18d6b402c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?=
<5621605+RodrigoGiraoSerrao@users.noreply.github.com>
Date: Fri, 18 Nov 2022 15:14:56 +0000
Subject: [PATCH 08/29] Add tests for placeholder widget.
---
src/textual/widgets/_placeholder.py | 31 ++--
.../__snapshots__/test_snapshots.ambr | 170 ++++++++++++++++++
tests/snapshot_tests/test_snapshots.py | 5 +
tests/test_placeholder.py | 15 ++
4 files changed, 207 insertions(+), 14 deletions(-)
create mode 100644 tests/test_placeholder.py
diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py
index b011b8ee2..cbe0b5004 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -3,17 +3,21 @@ from __future__ import annotations
from itertools import cycle
from typing import Literal
-from rich.text import Text
-
from .. import events
from ..app import ComposeResult
from ..css._error_tools import friendly_list
-from ..reactive import reactive
+from ..reactive import Reactive, reactive
from ..widgets import Static
PlaceholderVariant = Literal["default", "size", "text"]
-_VALID_PLACEHOLDER_VARIANTS_ORDERED = ["default", "size", "text"]
-_VALID_PLACEHOLDER_VARIANTS = set(_VALID_PLACEHOLDER_VARIANTS_ORDERED)
+_VALID_PLACEHOLDER_VARIANTS_ORDERED: list[PlaceholderVariant] = [
+ "default",
+ "size",
+ "text",
+]
+_VALID_PLACEHOLDER_VARIANTS: set[PlaceholderVariant] = set(
+ _VALID_PLACEHOLDER_VARIANTS_ORDERED
+)
_PLACEHOLDER_BACKGROUND_COLORS = [
"#881177",
"#aa3355",
@@ -68,7 +72,7 @@ class Placeholder(Static):
# Consecutive placeholders get assigned consecutive colors.
COLORS = cycle(_PLACEHOLDER_BACKGROUND_COLORS)
- variant = reactive("default")
+ variant: Reactive[PlaceholderVariant] = reactive("default")
def __init__(
self,
@@ -113,19 +117,18 @@ class Placeholder(Static):
"""Get the next variant in the cycle."""
self.variant = next(self._variants_cycle)
- def watch_variant(self, old_variant: str, variant: str) -> None:
+ def watch_variant(
+ self, old_variant: PlaceholderVariant, variant: PlaceholderVariant
+ ) -> None:
+ self.validate_variant(variant)
self.remove_class(f"-{old_variant}")
self.add_class(f"-{variant}")
self.call_variant_update()
def call_variant_update(self) -> None:
"""Calls the appropriate method to update the render of the placeholder."""
- update_variant_method = getattr(self, f"_update_{self.variant}_variant", None)
- if update_variant_method is None:
- raise InvalidPlaceholderVariant(
- "Valid placeholder variants are "
- + f"{friendly_list(_VALID_PLACEHOLDER_VARIANTS)}"
- )
+ update_variant_method = getattr(self, f"_update_{self.variant}_variant")
+ assert update_variant_method is not None
update_variant_method()
def _update_default_variant(self) -> None:
@@ -148,7 +151,7 @@ class Placeholder(Static):
if self.variant == "size":
self._update_size_variant()
- def validate_variant(self, variant: PlaceholderVariant) -> str:
+ def validate_variant(self, variant: PlaceholderVariant) -> PlaceholderVariant:
"""Validate the variant to which the placeholder was set."""
if variant not in _VALID_PLACEHOLDER_VARIANTS:
raise InvalidPlaceholderVariant(
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index 51ad5531c..0e86e084a 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -6322,6 +6322,176 @@
'''
# ---
+# name: test_placeholder_render
+ '''
+
+
+ '''
+# ---
# name: test_textlog_max_lines
'''
From 2e6e43e9b8040206bae8c29f8a453b441752749f Mon Sep 17 00:00:00 2001
From: Dave Pearson
Date: Mon, 5 Dec 2022 11:28:12 +0000
Subject: [PATCH 24/29] Fix copy/paste-o in DEFAULT_CSS for Label
---
src/textual/widgets/_label.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/textual/widgets/_label.py b/src/textual/widgets/_label.py
index 732fc61c9..344c37013 100644
--- a/src/textual/widgets/_label.py
+++ b/src/textual/widgets/_label.py
@@ -7,7 +7,7 @@ class Label(Static):
"""A simple label widget for displaying text-oriented renderables."""
DEFAULT_CSS = """
- Static {
+ Label {
width: auto;
height: auto;
}
From f427c55e591dd8de7ebbc9fae3f01167bc1d007c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?=
<5621605+RodrigoGiraoSerrao@users.noreply.github.com>
Date: Mon, 5 Dec 2022 13:47:07 +0000
Subject: [PATCH 25/29] Remove Placeholder dependency on Static.
---
src/textual/widgets/_placeholder.py | 107 +++++++++++++++++-----------
1 file changed, 65 insertions(+), 42 deletions(-)
diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py
index 2fbbc3a30..33ad25e0c 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -3,10 +3,11 @@ from __future__ import annotations
from itertools import cycle
from .. import events
-from ..app import ComposeResult
+from ..containers import Container
from ..css._error_tools import friendly_list
from ..reactive import Reactive, reactive
-from ..widgets import Static
+from ..widget import Widget, RenderResult
+from ..widgets import Label
from .._typing import Literal
PlaceholderVariant = Literal["default", "size", "text"]
@@ -32,17 +33,23 @@ _PLACEHOLDER_BACKGROUND_COLORS = [
"#3366bb",
"#663399",
]
+_LOREM_IPSUM_PLACEHOLDER_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam feugiat ac elit sit amet accumsan. Suspendisse bibendum nec libero quis gravida. Phasellus id eleifend ligula. Nullam imperdiet sem tellus, sed vehicula nisl faucibus sit amet. Praesent iaculis tempor ultricies. Sed lacinia, tellus id rutrum lacinia, sapien sapien congue mauris, sit amet pellentesque quam quam vel nisl. Curabitur vulputate erat pellentesque mauris posuere, non dictum risus mattis."
class InvalidPlaceholderVariant(Exception):
pass
-class _PlaceholderLabel(Static):
- pass
+class _PlaceholderLabel(Widget):
+ def __init__(self, content, classes) -> None:
+ super().__init__(classes=classes)
+ self._content = content
+
+ def render(self) -> RenderResult:
+ return self._content
-class Placeholder(Static):
+class Placeholder(Container):
"""A simple placeholder widget to use before you build your custom widgets.
This placeholder has a couple of variants that show different data.
@@ -58,19 +65,38 @@ class Placeholder(Static):
DEFAULT_CSS = """
Placeholder {
align: center middle;
- overflow-y: auto;
}
Placeholder.-text {
padding: 1;
}
+ _PlaceholderLabel {
+ height: auto;
+ }
+
Placeholder > _PlaceholderLabel {
content-align: center middle;
}
+
+ Placeholder.-default > _PlaceholderLabel.-size,
+ Placeholder.-default > _PlaceholderLabel.-text,
+ Placeholder.-size > _PlaceholderLabel.-default,
+ Placeholder.-size > _PlaceholderLabel.-text,
+ Placeholder.-text > _PlaceholderLabel.-default,
+ Placeholder.-text > _PlaceholderLabel.-size {
+ display: none;
+ }
+
+ Placeholder.-default > _PlaceholderLabel.-default,
+ Placeholder.-size > _PlaceholderLabel.-size,
+ Placeholder.-text > _PlaceholderLabel.-text {
+ display: block;
+ }
"""
# Consecutive placeholders get assigned consecutive colors.
_COLORS = cycle(_PLACEHOLDER_BACKGROUND_COLORS)
+ _SIZE_RENDER_TEMPLATE = "[b]{} x {}[/b]"
variant: Reactive[PlaceholderVariant] = reactive("default")
@@ -101,23 +127,36 @@ class Placeholder(Static):
classes (str | None, optional): A space separated string with the CSS classes
of the placeholder, if any. Defaults to None.
"""
- super().__init__(name=name, id=id, classes=classes)
- self._placeholder_text = label if label else f"#{id}" if id else "Placeholder"
- self._placeholder_label = _PlaceholderLabel()
+ # Create and cache labels for all the variants.
+ self._default_label = _PlaceholderLabel(
+ label if label else f"#{id}" if id else "Placeholder",
+ "-default",
+ )
+ self._size_label = _PlaceholderLabel(
+ "",
+ "-size",
+ )
+ self._text_label = _PlaceholderLabel(
+ _LOREM_IPSUM_PLACEHOLDER_TEXT,
+ "-text",
+ )
+ super().__init__(
+ self._default_label,
+ self._size_label,
+ self._text_label,
+ name=name,
+ id=id,
+ classes=classes,
+ )
+
self.styles.background = f"{next(Placeholder._COLORS)} 70%"
+
self.variant = self.validate_variant(variant)
# Set a cycle through the variants with the correct starting point.
self._variants_cycle = cycle(_VALID_PLACEHOLDER_VARIANTS_ORDERED)
while next(self._variants_cycle) != self.variant:
pass
- def compose(self) -> ComposeResult:
- yield self._placeholder_label
-
- def on_click(self) -> None:
- """Click handler to cycle through the placeholder variants."""
- self.cycle_variant()
-
def cycle_variant(self) -> None:
"""Get the next variant in the cycle."""
self.variant = next(self._variants_cycle)
@@ -127,32 +166,6 @@ class Placeholder(Static):
) -> None:
self.remove_class(f"-{old_variant}")
self.add_class(f"-{variant}")
- self.call_variant_update()
-
- def call_variant_update(self) -> None:
- """Calls the appropriate method to update the render of the placeholder."""
- update_variant_method = getattr(self, f"_update_{self.variant}_variant")
- update_variant_method()
-
- def _update_default_variant(self) -> None:
- """Update the placeholder with its label."""
- self._placeholder_label.update(self._placeholder_text)
-
- def _update_size_variant(self) -> None:
- """Update the placeholder with the size of the placeholder."""
- width, height = self.size
- self._placeholder_label.update(f"[b]{width} x {height}[/b]")
-
- def _update_text_variant(self) -> None:
- """Update the placeholder with some Lorem Ipsum text."""
- self._placeholder_label.update(
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam feugiat ac elit sit amet accumsan. Suspendisse bibendum nec libero quis gravida. Phasellus id eleifend ligula. Nullam imperdiet sem tellus, sed vehicula nisl faucibus sit amet. Praesent iaculis tempor ultricies. Sed lacinia, tellus id rutrum lacinia, sapien sapien congue mauris, sit amet pellentesque quam quam vel nisl. Curabitur vulputate erat pellentesque mauris posuere, non dictum risus mattis."
- )
-
- def on_resize(self, event: events.Resize) -> None:
- """Update the placeholder "size" variant with the new placeholder size."""
- if self.variant == "size":
- self._update_size_variant()
def validate_variant(self, variant: PlaceholderVariant) -> PlaceholderVariant:
"""Validate the variant to which the placeholder was set."""
@@ -162,3 +175,13 @@ class Placeholder(Static):
+ f"{friendly_list(_VALID_PLACEHOLDER_VARIANTS)}"
)
return variant
+
+ def on_click(self) -> None:
+ """Click handler to cycle through the placeholder variants."""
+ self.cycle_variant()
+
+ def on_resize(self, event: events.Resize) -> None:
+ """Update the placeholder "size" variant with the new placeholder size."""
+ self._size_label._content = self._SIZE_RENDER_TEMPLATE.format(*self.size)
+ if self.variant == "size":
+ self._size_label.refresh(layout=True)
From 08bf1bcbe97ecad37b66d6af86cbdede11ee526e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?=
<5621605+RodrigoGiraoSerrao@users.noreply.github.com>
Date: Mon, 5 Dec 2022 15:10:30 +0000
Subject: [PATCH 26/29] Update CHANGELOG.
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 52b77e80e..61144b427 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added "inherited bindings" -- BINDINGS classvar will be merged with base classes, unless inherit_bindings is set to False
- Added `Tree` widget which replaces `TreeControl`.
+- Added widget `Placeholder` https://github.com/Textualize/textual/issues/1200.
### Changed
@@ -46,7 +47,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Support lazy-instantiated Screens (callables in App.SCREENS) https://github.com/Textualize/textual/pull/1185
- Display of keys in footer has more sensible defaults https://github.com/Textualize/textual/pull/1213
- Add App.get_key_display, allowing custom key_display App-wide https://github.com/Textualize/textual/pull/1213
-- Added widget `Placeholder`
### Changed
From 23c2c3edd3472af028cd7b557136d5304ccfb1bf Mon Sep 17 00:00:00 2001
From: Will McGugan
Date: Wed, 7 Dec 2022 14:41:30 +0000
Subject: [PATCH 27/29] fix for visiblity
---
CHANGELOG.md | 1 +
src/textual/_compositor.py | 65 +++++---
.../__snapshots__/test_snapshots.ambr | 157 ++++++++++++++++++
tests/snapshot_tests/snapshot_apps/vis.html | 34 ++++
.../snapshot_apps/visibility.py | 48 ++++++
tests/snapshot_tests/test_snapshots.py | 4 +
6 files changed, 282 insertions(+), 27 deletions(-)
create mode 100644 tests/snapshot_tests/snapshot_apps/vis.html
create mode 100644 tests/snapshot_tests/snapshot_apps/visibility.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89140e36d..013ff9916 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- Type selectors can now contain numbers https://github.com/Textualize/textual/issues/1253
+- Fixed visibility not affecting children https://github.com/Textualize/textual/issues/1313
## [0.5.0] - 2022-11-20
diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py
index ab9d69eca..b59710218 100644
--- a/src/textual/_compositor.py
+++ b/src/textual/_compositor.py
@@ -347,6 +347,7 @@ class Compositor:
order: tuple[tuple[int, ...], ...],
layer_order: int,
clip: Region,
+ visible: bool,
) -> None:
"""Called recursively to place a widget and its children in the map.
@@ -356,7 +357,12 @@ class Compositor:
order (tuple[int, ...]): A tuple of ints to define the order.
clip (Region): The clipping region (i.e. the viewport which contains it).
"""
- widgets.add(widget)
+ visibility = widget.styles.get_rule("visibility")
+ if visibility is not None:
+ visible = visibility == "visible"
+
+ if visible:
+ widgets.add(widget)
styles_offset = widget.styles.offset
layout_offset = (
styles_offset.resolve(region.size, clip.size)
@@ -420,32 +426,34 @@ class Compositor:
widget_order,
layer_order,
sub_clip,
+ visible,
)
layer_order -= 1
- # Add any scrollbars
- for chrome_widget, chrome_region in widget._arrange_scrollbars(
- container_region
- ):
- map[chrome_widget] = MapGeometry(
- chrome_region + layout_offset,
+ if visible:
+ # Add any scrollbars
+ for chrome_widget, chrome_region in widget._arrange_scrollbars(
+ container_region
+ ):
+ map[chrome_widget] = MapGeometry(
+ chrome_region + layout_offset,
+ order,
+ clip,
+ container_size,
+ container_size,
+ chrome_region,
+ )
+
+ map[widget] = MapGeometry(
+ region + layout_offset,
order,
clip,
+ total_region.size,
container_size,
- container_size,
- chrome_region,
+ virtual_region,
)
- map[widget] = MapGeometry(
- region + layout_offset,
- order,
- clip,
- total_region.size,
- container_size,
- virtual_region,
- )
-
- else:
+ elif visible:
# Add the widget to the map
map[widget] = MapGeometry(
region + layout_offset,
@@ -457,7 +465,15 @@ class Compositor:
)
# Add top level (root) widget
- add_widget(root, size.region, size.region, ((0,),), layer_order, size.region)
+ add_widget(
+ root,
+ size.region,
+ size.region,
+ ((0,),),
+ layer_order,
+ size.region,
+ True,
+ )
return map, widgets
@property
@@ -630,11 +646,6 @@ class Compositor:
if not self.map:
return
- 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 widget.styles.opacity > 0
-
_Region = Region
visible_widgets = self.visible_widgets
@@ -644,13 +655,13 @@ class Compositor:
widget_regions = [
(widget, region, clip)
for widget, (region, clip) in visible_widgets.items()
- if crop_overlaps(clip) and is_visible(widget)
+ if crop_overlaps(clip) and widget.styles.opacity > 0
]
else:
widget_regions = [
(widget, region, clip)
for widget, (region, clip) in visible_widgets.items()
- if is_visible(widget)
+ if widget.styles.opacity > 0
]
intersection = _Region.intersection
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index c2ae2ab5f..53db5e9ec 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -7422,3 +7422,160 @@
'''
# ---
+# name: test_visibility
+ '''
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Visibility
+
+
+
+
+
+
+
+
+
+ ┌──────────────────────────────────────┐
+ │bar│
+ ┌────────────────────────────────────┐│┌────────────────────────────────────┐│
+ │float│││float││
+ └────────────────────────────────────┘│└────────────────────────────────────┘│
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ ││
+ └──────────────────────────────────────┘
+
+
+
+
+ '''
+# ---
diff --git a/tests/snapshot_tests/snapshot_apps/vis.html b/tests/snapshot_tests/snapshot_apps/vis.html
new file mode 100644
index 000000000..4e4ac19e1
--- /dev/null
+++ b/tests/snapshot_tests/snapshot_apps/vis.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/snapshot_tests/snapshot_apps/visibility.py b/tests/snapshot_tests/snapshot_apps/visibility.py
new file mode 100644
index 000000000..a5ccccb7e
--- /dev/null
+++ b/tests/snapshot_tests/snapshot_apps/visibility.py
@@ -0,0 +1,48 @@
+from textual.app import App
+from textual.containers import Vertical
+from textual.widgets import Static
+
+
+class Visibility(App):
+ """Check that visibility: hidden also makes children invisible;"""
+
+ CSS = """
+ Screen {
+ layout: horizontal;
+ }
+ Vertical {
+ width: 1fr;
+ border: solid red;
+ }
+
+ #container1 {
+ visibility: hidden;
+ }
+
+ .float {
+ border: solid blue;
+ }
+
+ /* Make a child of a hidden widget visible again */
+ #container1 .float {
+ visibility: visible;
+ }
+ """
+
+ def compose(self):
+
+ yield Vertical(
+ Static("foo"),
+ Static("float", classes="float"),
+ id="container1",
+ )
+ yield Vertical(
+ Static("bar"),
+ Static("float", classes="float"),
+ id="container2",
+ )
+
+
+if __name__ == "__main__":
+ app = Visibility()
+ app.run()
diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py
index 338d3fb5d..7efcd3b2a 100644
--- a/tests/snapshot_tests/test_snapshots.py
+++ b/tests/snapshot_tests/test_snapshots.py
@@ -100,6 +100,10 @@ def test_fr_units(snap_compare):
assert snap_compare("snapshot_apps/fr_units.py")
+def test_visibility(snap_compare):
+ assert snap_compare("snapshot_apps/visibility.py")
+
+
def test_tree_example(snap_compare):
assert snap_compare(WIDGET_EXAMPLES_DIR / "tree.py")
From a25a60efbf752c5ee78aef1052e7bafa0443bd65 Mon Sep 17 00:00:00 2001
From: Will McGugan
Date: Wed, 7 Dec 2022 15:01:50 +0000
Subject: [PATCH 28/29] remove html test
---
tests/snapshot_tests/snapshot_apps/vis.html | 34 ---------------------
1 file changed, 34 deletions(-)
delete mode 100644 tests/snapshot_tests/snapshot_apps/vis.html
diff --git a/tests/snapshot_tests/snapshot_apps/vis.html b/tests/snapshot_tests/snapshot_apps/vis.html
deleted file mode 100644
index 4e4ac19e1..000000000
--- a/tests/snapshot_tests/snapshot_apps/vis.html
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
From dd8a9562d08374732f52a955ab319af1f8bf4810 Mon Sep 17 00:00:00 2001
From: Will McGugan
Date: Wed, 7 Dec 2022 16:02:51 +0000
Subject: [PATCH 29/29] fix for container height change
---
docs/examples/guide/dom4.css | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/examples/guide/dom4.css b/docs/examples/guide/dom4.css
index d9169ee17..8ac843abb 100644
--- a/docs/examples/guide/dom4.css
+++ b/docs/examples/guide/dom4.css
@@ -1,6 +1,7 @@
/* The top level dialog (a Container) */
#dialog {
+ height: 100%;
margin: 4 8;
background: $panel;
color: $text;