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/17] Add scaffolding for the Placeholder widget.
---
src/textual/widgets/_placeholder.py | 218 ++++++++++++++++++++++------
1 file changed, 177 insertions(+), 41 deletions(-)
diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py
index 7ce714a241..3562a7d735 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",
+]
+
+
+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.
+ 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.
-@rich.repr.auto(angular=False)
-class Placeholder(Widget, 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.
+ css: shows the css rules that apply to the placeholder.
+ text: shows some Lorem Ipsum text on the placeholder."""
- has_focus: Reactive[bool] = Reactive(False)
- mouse_over: Reactive[bool] = Reactive(False)
+ 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
-
- 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
-
- 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,
+ """Create a Placeholder widget.
+
+ 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 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,
+ )
+ )
+
+ 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.
+ """
+ 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,
+ )
+ )
+
+ def _update_position_variant(self) -> None:
+ """Update the placeholder with the "position" variant.
+
+ 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",
+ )
)
- async def on_focus(self, event: events.Focus) -> None:
- self.has_focus = True
+ 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()
- async def on_blur(self, event: events.Blur) -> None:
- self.has_focus = False
+ 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()
- async def on_enter(self, event: events.Enter) -> None:
- self.mouse_over = True
+ 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()
- async def on_leave(self, event: events.Leave) -> None:
- self.mouse_over = False
+ 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/17] 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 3562a7d735..e645e8b04f 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -13,8 +13,8 @@
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 @@ def _update_state_variant(self) -> None:
)
)
- 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/17] 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 e645e8b04f..16809d09d0 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -83,6 +83,7 @@ def __init__(
"""
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 @@ def _update_default_variant(self) -> None:
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 @@ def _update_size_variant(self) -> None:
"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/17] 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 16809d09d0..1b34780df2 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -3,9 +3,6 @@
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 @@ def __init__(
self,
variant: PlaceholderVariant = "default",
*,
+ label: str | None = None,
name: str | None = None,
id: str | None = None,
classes: str | None = None,
@@ -75,6 +72,8 @@ def __init__(
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 @@ def __init__(
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 @@ def watch_variant(self, old_variant: str, variant: str) -> None:
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 @@ def _update_state_variant(self) -> None:
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 @@ def _update_size_variant(self) -> None:
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 @@ def _update_text_variant(self) -> None:
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/17] 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 1b34780df2..48a484b0e8 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 @@ def update_on_variant_change(self, variant: str) -> None:
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 @@ def _update_size_variant(self) -> None:
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/17] 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 48a484b0e8..73c044ee39 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -3,15 +3,16 @@
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 @@ def __init__(
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 @@ def update_on_variant_change(self, variant: str) -> None:
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 @@ def on_resize(self, event: events.Resize) -> None:
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/17] 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 73c044ee39..b011b8ee26 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/17] 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 b011b8ee26..cbe0b50047 100644
--- a/src/textual/widgets/_placeholder.py
+++ b/src/textual/widgets/_placeholder.py
@@ -3,17 +3,21 @@
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 @@ 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:
+ 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 @@ def on_resize(self, event: events.Resize) -> None:
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 51ad5531c1..0e86e084a7 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
'''