| from typing import TYPE_CHECKING, Optional |
| |
| from .align import AlignMethod |
| from .box import ROUNDED, Box |
| from .jupyter import JupyterMixin |
| from .measure import Measurement, measure_renderables |
| from .padding import Padding, PaddingDimensions |
| from .segment import Segment |
| from .style import StyleType |
| from .text import Text, TextType |
| |
| if TYPE_CHECKING: |
| from .console import Console, ConsoleOptions, RenderableType, RenderResult |
| |
| |
| class Panel(JupyterMixin): |
| """A console renderable that draws a border around its contents. |
| |
| Example: |
| >>> console.print(Panel("Hello, World!")) |
| |
| Args: |
| renderable (RenderableType): A console renderable object. |
| box (Box, optional): A Box instance that defines the look of the border (see :ref:`appendix_box`. |
| Defaults to box.ROUNDED. |
| safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. |
| expand (bool, optional): If True the panel will stretch to fill the console |
| width, otherwise it will be sized to fit the contents. Defaults to True. |
| style (str, optional): The style of the panel (border and contents). Defaults to "none". |
| border_style (str, optional): The style of the border. Defaults to "none". |
| width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect. |
| height (Optional[int], optional): Optional height of panel. Defaults to None to auto-detect. |
| padding (Optional[PaddingDimensions]): Optional padding around renderable. Defaults to 0. |
| highlight (bool, optional): Enable automatic highlighting of panel title (if str). Defaults to False. |
| """ |
| |
| def __init__( |
| self, |
| renderable: "RenderableType", |
| box: Box = ROUNDED, |
| *, |
| title: Optional[TextType] = None, |
| title_align: AlignMethod = "center", |
| subtitle: Optional[TextType] = None, |
| subtitle_align: AlignMethod = "center", |
| safe_box: Optional[bool] = None, |
| expand: bool = True, |
| style: StyleType = "none", |
| border_style: StyleType = "none", |
| width: Optional[int] = None, |
| height: Optional[int] = None, |
| padding: PaddingDimensions = (0, 1), |
| highlight: bool = False, |
| ) -> None: |
| self.renderable = renderable |
| self.box = box |
| self.title = title |
| self.title_align: AlignMethod = title_align |
| self.subtitle = subtitle |
| self.subtitle_align = subtitle_align |
| self.safe_box = safe_box |
| self.expand = expand |
| self.style = style |
| self.border_style = border_style |
| self.width = width |
| self.height = height |
| self.padding = padding |
| self.highlight = highlight |
| |
| @classmethod |
| def fit( |
| cls, |
| renderable: "RenderableType", |
| box: Box = ROUNDED, |
| *, |
| title: Optional[TextType] = None, |
| title_align: AlignMethod = "center", |
| subtitle: Optional[TextType] = None, |
| subtitle_align: AlignMethod = "center", |
| safe_box: Optional[bool] = None, |
| style: StyleType = "none", |
| border_style: StyleType = "none", |
| width: Optional[int] = None, |
| padding: PaddingDimensions = (0, 1), |
| ) -> "Panel": |
| """An alternative constructor that sets expand=False.""" |
| return cls( |
| renderable, |
| box, |
| title=title, |
| title_align=title_align, |
| subtitle=subtitle, |
| subtitle_align=subtitle_align, |
| safe_box=safe_box, |
| style=style, |
| border_style=border_style, |
| width=width, |
| padding=padding, |
| expand=False, |
| ) |
| |
| @property |
| def _title(self) -> Optional[Text]: |
| if self.title: |
| title_text = ( |
| Text.from_markup(self.title) |
| if isinstance(self.title, str) |
| else self.title.copy() |
| ) |
| title_text.end = "" |
| title_text.plain = title_text.plain.replace("\n", " ") |
| title_text.no_wrap = True |
| title_text.expand_tabs() |
| title_text.pad(1) |
| return title_text |
| return None |
| |
| @property |
| def _subtitle(self) -> Optional[Text]: |
| if self.subtitle: |
| subtitle_text = ( |
| Text.from_markup(self.subtitle) |
| if isinstance(self.subtitle, str) |
| else self.subtitle.copy() |
| ) |
| subtitle_text.end = "" |
| subtitle_text.plain = subtitle_text.plain.replace("\n", " ") |
| subtitle_text.no_wrap = True |
| subtitle_text.expand_tabs() |
| subtitle_text.pad(1) |
| return subtitle_text |
| return None |
| |
| def __rich_console__( |
| self, console: "Console", options: "ConsoleOptions" |
| ) -> "RenderResult": |
| _padding = Padding.unpack(self.padding) |
| renderable = ( |
| Padding(self.renderable, _padding) if any(_padding) else self.renderable |
| ) |
| style = console.get_style(self.style) |
| border_style = style + console.get_style(self.border_style) |
| width = ( |
| options.max_width |
| if self.width is None |
| else min(options.max_width, self.width) |
| ) |
| |
| safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box |
| box = self.box.substitute(options, safe=safe_box) |
| |
| title_text = self._title |
| if title_text is not None: |
| title_text.style = border_style |
| |
| child_width = ( |
| width - 2 |
| if self.expand |
| else console.measure( |
| renderable, options=options.update_width(width - 2) |
| ).maximum |
| ) |
| child_height = self.height or options.height or None |
| if child_height: |
| child_height -= 2 |
| if title_text is not None: |
| child_width = min( |
| options.max_width - 2, max(child_width, title_text.cell_len + 2) |
| ) |
| |
| width = child_width + 2 |
| child_options = options.update( |
| width=child_width, height=child_height, highlight=self.highlight |
| ) |
| lines = console.render_lines(renderable, child_options, style=style) |
| |
| line_start = Segment(box.mid_left, border_style) |
| line_end = Segment(f"{box.mid_right}", border_style) |
| new_line = Segment.line() |
| if title_text is None or width <= 4: |
| yield Segment(box.get_top([width - 2]), border_style) |
| else: |
| title_text.align(self.title_align, width - 4, character=box.top) |
| yield Segment(box.top_left + box.top, border_style) |
| yield from console.render(title_text, child_options.update_width(width - 4)) |
| yield Segment(box.top + box.top_right, border_style) |
| |
| yield new_line |
| for line in lines: |
| yield line_start |
| yield from line |
| yield line_end |
| yield new_line |
| |
| subtitle_text = self._subtitle |
| if subtitle_text is not None: |
| subtitle_text.style = border_style |
| |
| if subtitle_text is None or width <= 4: |
| yield Segment(box.get_bottom([width - 2]), border_style) |
| else: |
| subtitle_text.align(self.subtitle_align, width - 4, character=box.bottom) |
| yield Segment(box.bottom_left + box.bottom, border_style) |
| yield from console.render( |
| subtitle_text, child_options.update_width(width - 4) |
| ) |
| yield Segment(box.bottom + box.bottom_right, border_style) |
| |
| yield new_line |
| |
| def __rich_measure__( |
| self, console: "Console", options: "ConsoleOptions" |
| ) -> "Measurement": |
| _title = self._title |
| _, right, _, left = Padding.unpack(self.padding) |
| padding = left + right |
| renderables = [self.renderable, _title] if _title else [self.renderable] |
| |
| if self.width is None: |
| width = ( |
| measure_renderables( |
| console, |
| options.update_width(options.max_width - padding - 2), |
| renderables, |
| ).maximum |
| + padding |
| + 2 |
| ) |
| else: |
| width = self.width |
| return Measurement(width, width) |
| |
| |
| if __name__ == "__main__": # pragma: no cover |
| from .console import Console |
| |
| c = Console() |
| |
| from .box import DOUBLE, ROUNDED |
| from .padding import Padding |
| |
| p = Panel( |
| "Hello, World!", |
| title="rich.Panel", |
| style="white on blue", |
| box=DOUBLE, |
| padding=1, |
| ) |
| |
| c.print() |
| c.print(p) |