Skip to content

Commit

Permalink
Merge pull request #156 from willmcgugan/panel
Browse files Browse the repository at this point in the history
panel title
  • Loading branch information
willmcgugan authored Jul 12, 2020
2 parents 17bcbf5 + dd40166 commit f84d5de
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 32 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.3.0] - 2020-07-12

### Added

- Added title and title_align options to Panel
- Added pad and width parameters to Align
- Added end parameter to Rule
- Added Text.pad and Text.align methods
- Added leading parameter to Table

## [3.2.0] - 2020-07-10

### Added
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "rich"
homepage = "https://github.com/willmcgugan/rich"
documentation = "https://rich.readthedocs.io/en/latest/"
version = "3.2.0"
version = "3.3.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
authors = ["Will McGugan <willmcgugan@gmail.com>"]
license = "MIT"
Expand Down
59 changes: 46 additions & 13 deletions rich/align.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import TYPE_CHECKING
from typing import Iterable, TYPE_CHECKING

from typing_extensions import Literal
from .constrain import Constrain
from .jupyter import JupyterMixin
from .measure import Measurement
from .segment import Segment
Expand All @@ -20,13 +21,21 @@ class Align(JupyterMixin):
renderable (RenderableType): A console renderable.
align (AlignValues): One of "left", "center", or "right""
style (StyleType, optional): An optional style to apply to the renderable.
pad (bool, optional): Pad the right with spaces. Defaults to True.
width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None.
Raises:
ValueError: if ``align`` is not one of the expected values.
"""

def __init__(
self, renderable: "RenderableType", align: AlignValues, style: StyleType = None
self,
renderable: "RenderableType",
align: AlignValues,
style: StyleType = None,
*,
pad: bool = True,
width: int = None,
) -> None:
if align not in ("left", "center", "right"):
raise ValueError(
Expand All @@ -35,35 +44,58 @@ def __init__(
self.renderable = renderable
self.align = align
self.style = style
self.pad = pad
self.width = width

@classmethod
def left(cls, renderable: "RenderableType", style: StyleType = None) -> "Align":
def left(
cls,
renderable: "RenderableType",
style: StyleType = None,
*,
pad: bool = True,
width: int = None,
) -> "Align":
"""Align a renderable to the left."""
return cls(renderable, "left", style=style)
return cls(renderable, "left", style=style, pad=pad, width=width)

@classmethod
def center(cls, renderable: "RenderableType", style: StyleType = None) -> "Align":
def center(
cls,
renderable: "RenderableType",
style: StyleType = None,
*,
pad: bool = True,
width: int = None,
) -> "Align":
"""Align a renderable to the center."""
return cls(renderable, "center", style=style)
return cls(renderable, "center", style=style, pad=pad, width=width)

@classmethod
def right(cls, renderable: "RenderableType", style: StyleType = None) -> "Align":
def right(
cls,
renderable: "RenderableType",
style: StyleType = None,
*,
pad: bool = True,
width: int = None,
) -> "Align":
"""Align a renderable to the right."""
return cls(renderable, "right", style=style)
return cls(renderable, "right", style=style, pad=pad, width=width)

def __rich_console__(
self, console: "Console", options: "ConsoleOptions"
) -> "RenderResult":

align = self.align
rendered = console.render(self.renderable, options)
rendered = console.render(Constrain(self.renderable, width=self.width), options)
lines = list(Segment.split_lines(rendered))
width, height = Segment.get_shape(lines)
lines = Segment.set_shape(lines, width, height)
new_line = Segment.line()
excess_space = options.max_width - width

def generate_segments():
def generate_segments() -> Iterable[Segment]:
if excess_space <= 0:
# Exact fit
for line in lines:
Expand All @@ -72,17 +104,18 @@ def generate_segments():

elif align == "left":
# Pad on the right
pad = Segment(" " * excess_space)
pad = Segment(" " * excess_space) if self.pad else None
for line in lines:
yield from line
yield pad
if pad:
yield pad
yield new_line

elif align == "center":
# Pad left and right
left = excess_space // 2
pad = Segment(" " * left)
pad_right = Segment(" " * (excess_space - left))
pad_right = Segment(" " * (excess_space - left)) if self.pad else None
for line in lines:
if left:
yield pad
Expand Down
7 changes: 6 additions & 1 deletion rich/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def get_top(self, widths: Iterable[int]) -> str:
def get_row(
self,
widths: Iterable[int],
level: Literal["head", "row", "foot"] = "row",
level: Literal["head", "row", "foot", "mid"] = "row",
edge: bool = True,
) -> str:
"""Get the top of a simple box.
Expand All @@ -102,6 +102,11 @@ def get_row(
horizontal = self.row_horizontal
cross = self.row_cross
right = self.row_right
elif level == "mid":
left = self.mid_left
horizontal = " "
cross = self.mid_vertical
right = self.mid_right
elif level == "foot":
left = self.foot_row_left
horizontal = self.foot_row_horizontal
Expand Down
14 changes: 8 additions & 6 deletions rich/constrain.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from typing import Optional
from typing import Optional, TYPE_CHECKING

from .console import Console, ConsoleOptions, RenderableType, RenderResult
from .jupyter import JupyterMixin
from .measure import Measurement

if TYPE_CHECKING:
from .console import Console, ConsoleOptions, RenderableType, RenderResult


class Constrain(JupyterMixin):
"""Constrain the width of a renderable to a given number of characters.
Expand All @@ -13,20 +15,20 @@ class Constrain(JupyterMixin):
width (int, optional): The maximum width (in characters) to render. Defaults to 80.
"""

def __init__(self, renderable: RenderableType, width: Optional[int] = 80) -> None:
def __init__(self, renderable: "RenderableType", width: Optional[int] = 80) -> None:
self.renderable = renderable
self.width = width

def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
self, console: "Console", options: "ConsoleOptions"
) -> "RenderResult":
if self.width is None:
yield self.renderable
else:
child_options = options.update(width=min(self.width, options.max_width))
yield from console.render(self.renderable, child_options)

def __rich_measure__(self, console: Console, max_width: int) -> Measurement:
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
if self.width is None:
return Measurement.get(console, self.renderable, max_width)
else:
Expand Down
35 changes: 31 additions & 4 deletions rich/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from .box import get_safe_box, Box, SQUARE, ROUNDED

from .align import AlignValues
from .console import (
Console,
ConsoleOptions,
Expand All @@ -12,7 +13,7 @@
from .jupyter import JupyterMixin
from .padding import Padding, PaddingDimensions
from .style import Style, StyleType
from .text import Text
from .text import Text, TextType
from .segment import Segment


Expand Down Expand Up @@ -40,6 +41,8 @@ def __init__(
renderable: RenderableType,
box: Box = ROUNDED,
*,
title: TextType = None,
title_align: AlignValues = "center",
safe_box: Optional[bool] = None,
expand: bool = True,
style: StyleType = "none",
Expand All @@ -49,6 +52,8 @@ def __init__(
) -> None:
self.renderable = renderable
self.box = box
self.title = title
self.title_align = title_align
self.safe_box = safe_box
self.expand = expand
self.style = style
Expand All @@ -62,6 +67,8 @@ def fit(
renderable: RenderableType,
box: Box = ROUNDED,
*,
title: TextType = None,
title_align: AlignValues = "center",
safe_box: Optional[bool] = None,
style: StyleType = "none",
border_style: StyleType = "none",
Expand All @@ -72,6 +79,8 @@ def fit(
return cls(
renderable,
box,
title=title,
title_align=title_align,
safe_box=safe_box,
style=style,
border_style=border_style,
Expand Down Expand Up @@ -108,7 +117,24 @@ def __rich_console__(
line_start = Segment(box.mid_left, border_style)
line_end = Segment(f"{box.mid_right}", border_style)
new_line = Segment.line()
yield Segment(box.get_top([width - 2]), border_style)
if self.title is None:
yield Segment(box.get_top([width - 2]), border_style)
else:
title_text = (
Text.from_markup(self.title)
if isinstance(self.title, str)
else self.title.copy()
)
title_text.style = border_style
title_text.end = ""
title_text.plain = title_text.plain.replace("\n", " ")
title_text = title_text.tabs_to_spaces()
title_text.pad(1)
title_text.align(self.title_align, width - 4, character=box.top)
yield Segment(box.top_left + box.top, border_style)
yield title_text
yield Segment(box.top + box.top_right, border_style)

yield new_line
for line in lines:
yield line_start
Expand All @@ -129,7 +155,7 @@ def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
c = Console()

from .padding import Padding
from .box import ROUNDED
from .box import ROUNDED, DOUBLE

p = Panel(
Panel.fit(
Expand All @@ -138,7 +164,8 @@ def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
safe_box=True,
style="on red",
),
border_style="on blue",
title="[b]Hello, World",
box=DOUBLE,
)

print(p)
Expand Down
8 changes: 6 additions & 2 deletions rich/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@


class Rule(JupyterMixin):
"""A console renderable to draw a horizontal rule (line).
r"""A console renderable to draw a horizontal rule (line).
Args:
title (Union[str, Text], optional): Text to render in the rule. Defaults to "".
character (str, optional): Character used to draw the line. Defaults to "─".
style (StyleType, optional): Style of Rule. Defaults to "rule.line".
end (str, optional): Character at end of Rule. defaults to "\\n"
"""

def __init__(
Expand All @@ -21,12 +23,14 @@ def __init__(
*,
character: str = None,
style: Union[str, Style] = "rule.line",
end: str = "\n",
) -> None:
if character and cell_len(character) != 1:
raise ValueError("'character' argument must have a cell width of 1")
self.title = title
self.character = character
self.style = style
self.end = end

def __repr__(self) -> str:
return f"Rule({self.title!r}, {self.character!r})"
Expand All @@ -51,7 +55,7 @@ def __rich_console__(

title_text.plain = title_text.plain.replace("\n", " ")
title_text = title_text.tabs_to_spaces()
rule_text = Text()
rule_text = Text(end=self.end)
center = (width - cell_len(title_text.plain)) // 2
rule_text.append(character * (center - 1) + " ", self.style)
rule_text.append(title_text)
Expand Down
Loading

0 comments on commit f84d5de

Please sign in to comment.