|
|
|
from typing import cast, List, Optional, Tuple, TYPE_CHECKING, Union
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from .console import (
|
|
|
|
Console,
|
|
|
|
ConsoleOptions,
|
|
|
|
RenderableType,
|
|
|
|
RenderResult,
|
|
|
|
)
|
|
|
|
from .jupyter import JupyterMixin
|
|
|
|
from .measure import Measurement
|
|
|
|
from .style import Style
|
|
|
|
from .segment import Segment
|
|
|
|
|
|
|
|
|
|
|
|
PaddingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]
|
|
|
|
|
|
|
|
|
|
|
|
class Padding(JupyterMixin):
|
|
|
|
"""Draw space around content.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> print(Padding("Hello", (2, 4), style="on blue"))
|
|
|
|
|
|
|
|
Args:
|
|
|
|
renderable (RenderableType): String or other renderable.
|
|
|
|
pad (Union[int, Tuple[int]]): Padding for top, right, bottom, and left borders.
|
|
|
|
May be specified with 1, 2, or 4 integers (CSS style).
|
|
|
|
style (Union[str, Style], optional): Style for padding characters. Defaults to "none".
|
|
|
|
expand (bool, optional): Expand padding to fit available width. Defaults to True.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
renderable: "RenderableType",
|
|
|
|
pad: "PaddingDimensions" = (0, 0, 0, 0),
|
|
|
|
*,
|
|
|
|
style: Union[str, Style] = "none",
|
|
|
|
expand: bool = True,
|
|
|
|
):
|
|
|
|
self.renderable = renderable
|
|
|
|
self.top, self.right, self.bottom, self.left = self.unpack(pad)
|
|
|
|
self.style = style
|
|
|
|
self.expand = expand
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def indent(cls, renderable: "RenderableType", level: int) -> "Padding":
|
|
|
|
"""Make padding instance to render an indent.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
renderable (RenderableType): String or other renderable.
|
|
|
|
level (int): Number of characters to indent.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Padding: A Padding instance.
|
|
|
|
"""
|
|
|
|
|
|
|
|
return Padding(renderable, pad=(0, 0, 0, level), expand=False)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def unpack(pad: "PaddingDimensions") -> Tuple[int, int, int, int]:
|
|
|
|
"""Unpack padding specified in CSS style."""
|
|
|
|
if isinstance(pad, int):
|
|
|
|
return (pad, pad, pad, pad)
|
|
|
|
if len(pad) == 1:
|
|
|
|
_pad = pad[0]
|
|
|
|
return (_pad, _pad, _pad, _pad)
|
|
|
|
if len(pad) == 2:
|
|
|
|
pad_top, pad_right = cast(Tuple[int, int], pad)
|
|
|
|
return (pad_top, pad_right, pad_top, pad_right)
|
|
|
|
if len(pad) == 4:
|
|
|
|
top, right, bottom, left = cast(Tuple[int, int, int, int], pad)
|
|
|
|
return (top, right, bottom, left)
|
|
|
|
raise ValueError(f"1, 2 or 4 integers required for padding; {len(pad)} given")
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"Padding({self.renderable!r}, ({self.top},{self.right},{self.bottom},{self.left}))"
|
|
|
|
|
|
|
|
def __rich_console__(
|
|
|
|
self, console: "Console", options: "ConsoleOptions"
|
|
|
|
) -> "RenderResult":
|
|
|
|
style = console.get_style(self.style)
|
|
|
|
if self.expand:
|
|
|
|
width = options.max_width
|
|
|
|
else:
|
|
|
|
width = min(
|
|
|
|
Measurement.get(console, options, self.renderable).maximum
|
|
|
|
+ self.left
|
|
|
|
+ self.right,
|
|
|
|
options.max_width,
|
|
|
|
)
|
|
|
|
render_options = options.update_width(width - self.left - self.right)
|
|
|
|
if render_options.height is not None:
|
|
|
|
render_options = render_options.update_height(
|
|
|
|
height=render_options.height - self.top - self.bottom
|
|
|
|
)
|
|
|
|
lines = console.render_lines(
|
|
|
|
self.renderable, render_options, style=style, pad=True
|
|
|
|
)
|
|
|
|
_Segment = Segment
|
|
|
|
|
|
|
|
left = _Segment(" " * self.left, style) if self.left else None
|
|
|
|
right = (
|
|
|
|
[_Segment(f'{" " * self.right}', style), _Segment.line()]
|
|
|
|
if self.right
|
|
|
|
else [_Segment.line()]
|
|
|
|
)
|
|
|
|
blank_line: Optional[List[Segment]] = None
|
|
|
|
if self.top:
|
|
|
|
blank_line = [_Segment(f'{" " * width}\n', style)]
|
|
|
|
yield from blank_line * self.top
|
|
|
|
if left:
|
|
|
|
for line in lines:
|
|
|
|
yield left
|
|
|
|
yield from line
|
|
|
|
yield from right
|
|
|
|
else:
|
|
|
|
for line in lines:
|
|
|
|
yield from line
|
|
|
|
yield from right
|
|
|
|
if self.bottom:
|
|
|
|
blank_line = blank_line or [_Segment(f'{" " * width}\n', style)]
|
|
|
|
yield from blank_line * self.bottom
|
|
|
|
|
|
|
|
def __rich_measure__(
|
|
|
|
self, console: "Console", options: "ConsoleOptions"
|
|
|
|
) -> "Measurement":
|
|
|
|
max_width = options.max_width
|
|
|
|
extra_width = self.left + self.right
|
|
|
|
if max_width - extra_width < 1:
|
|
|
|
return Measurement(max_width, max_width)
|
|
|
|
measure_min, measure_max = Measurement.get(console, options, self.renderable)
|
|
|
|
measurement = Measurement(measure_min + extra_width, measure_max + extra_width)
|
|
|
|
measurement = measurement.with_maximum(max_width)
|
|
|
|
return measurement
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
|
|
from rich import print
|
|
|
|
|
|
|
|
print(Padding("Hello, World", (2, 4), style="on blue"))
|