codekingpro/portable-devtools
114k
1from typing import TYPE_CHECKING, Optional2 3from .align import AlignMethod4from .box import ROUNDED, Box5from .cells import cell_len6from .jupyter import JupyterMixin7from .measure import Measurement, measure_renderables8from .padding import Padding, PaddingDimensions9from .segment import Segment10from .style import Style, StyleType11from .text import Text, TextType12 13if TYPE_CHECKING:14 from .console import Console, ConsoleOptions, RenderableType, RenderResult15 16 17class Panel(JupyterMixin):18 """A console renderable that draws a border around its contents.19 20 Example:21 >>> console.print(Panel("Hello, World!"))22 23 Args:24 renderable (RenderableType): A console renderable object.25 box (Box, optional): A Box instance that defines the look of the border (see :ref:`appendix_box`.26 Defaults to box.ROUNDED.27 safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.28 expand (bool, optional): If True the panel will stretch to fill the console29 width, otherwise it will be sized to fit the contents. Defaults to True.30 style (str, optional): The style of the panel (border and contents). Defaults to "none".31 border_style (str, optional): The style of the border. Defaults to "none".32 width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect.33 height (Optional[int], optional): Optional height of panel. Defaults to None to auto-detect.34 padding (Optional[PaddingDimensions]): Optional padding around renderable. Defaults to 0.35 highlight (bool, optional): Enable automatic highlighting of panel title (if str). Defaults to False.36 """37 38 def __init__(39 self,40 renderable: "RenderableType",41 box: Box = ROUNDED,42 *,43 title: Optional[TextType] = None,44 title_align: AlignMethod = "center",45 subtitle: Optional[TextType] = None,46 subtitle_align: AlignMethod = "center",47 safe_box: Optional[bool] = None,48 expand: bool = True,49 style: StyleType = "none",50 border_style: StyleType = "none",51 width: Optional[int] = None,52 height: Optional[int] = None,53 padding: PaddingDimensions = (0, 1),54 highlight: bool = False,55 ) -> None:56 self.renderable = renderable57 self.box = box58 self.title = title59 self.title_align: AlignMethod = title_align60 self.subtitle = subtitle61 self.subtitle_align = subtitle_align62 self.safe_box = safe_box63 self.expand = expand64 self.style = style65 self.border_style = border_style66 self.width = width67 self.height = height68 self.padding = padding69 self.highlight = highlight70 71 @classmethod72 def fit(73 cls,74 renderable: "RenderableType",75 box: Box = ROUNDED,76 *,77 title: Optional[TextType] = None,78 title_align: AlignMethod = "center",79 subtitle: Optional[TextType] = None,80 subtitle_align: AlignMethod = "center",81 safe_box: Optional[bool] = None,82 style: StyleType = "none",83 border_style: StyleType = "none",84 width: Optional[int] = None,85 height: Optional[int] = None,86 padding: PaddingDimensions = (0, 1),87 highlight: bool = False,88 ) -> "Panel":89 """An alternative constructor that sets expand=False."""90 return cls(91 renderable,92 box,93 title=title,94 title_align=title_align,95 subtitle=subtitle,96 subtitle_align=subtitle_align,97 safe_box=safe_box,98 style=style,99 border_style=border_style,100 width=width,101 height=height,102 padding=padding,103 highlight=highlight,104 expand=False,105 )106 107 @property108 def _title(self) -> Optional[Text]:109 if self.title:110 title_text = (111 Text.from_markup(self.title)112 if isinstance(self.title, str)113 else self.title.copy()114 )115 title_text.end = ""116 title_text.plain = title_text.plain.replace("\n", " ")117 title_text.no_wrap = True118 title_text.expand_tabs()119 title_text.pad(1)120 return title_text121 return None122 123 @property124 def _subtitle(self) -> Optional[Text]:125 if self.subtitle:126 subtitle_text = (127 Text.from_markup(self.subtitle)128 if isinstance(self.subtitle, str)129 else self.subtitle.copy()130 )131 subtitle_text.end = ""132 subtitle_text.plain = subtitle_text.plain.replace("\n", " ")133 subtitle_text.no_wrap = True134 subtitle_text.expand_tabs()135 subtitle_text.pad(1)136 return subtitle_text137 return None138 139 def __rich_console__(140 self, console: "Console", options: "ConsoleOptions"141 ) -> "RenderResult":142 _padding = Padding.unpack(self.padding)143 renderable = (144 Padding(self.renderable, _padding) if any(_padding) else self.renderable145 )146 style = console.get_style(self.style)147 border_style = style + console.get_style(self.border_style)148 width = (149 options.max_width150 if self.width is None151 else min(options.max_width, self.width)152 )153 154 safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box155 box = self.box.substitute(options, safe=safe_box)156 157 def align_text(158 text: Text, width: int, align: str, character: str, style: Style159 ) -> Text:160 """Gets new aligned text.161 162 Args:163 text (Text): Title or subtitle text.164 width (int): Desired width.165 align (str): Alignment.166 character (str): Character for alignment.167 style (Style): Border style168 169 Returns:170 Text: New text instance171 """172 text = text.copy()173 text.truncate(width)174 excess_space = width - cell_len(text.plain)175 if excess_space:176 if align == "left":177 return Text.assemble(178 text,179 (character * excess_space, style),180 no_wrap=True,181 end="",182 )183 elif align == "center":184 left = excess_space // 2185 return Text.assemble(186 (character * left, style),187 text,188 (character * (excess_space - left), style),189 no_wrap=True,190 end="",191 )192 else:193 return Text.assemble(194 (character * excess_space, style),195 text,196 no_wrap=True,197 end="",198 )199 return text200 201 title_text = self._title202 if title_text is not None:203 title_text.stylize_before(border_style)204 205 child_width = (206 width - 2207 if self.expand208 else console.measure(209 renderable, options=options.update_width(width - 2)210 ).maximum211 )212 child_height = self.height or options.height or None213 if child_height:214 child_height -= 2215 if title_text is not None:216 child_width = min(217 options.max_width - 2, max(child_width, title_text.cell_len + 2)218 )219 220 width = child_width + 2221 child_options = options.update(222 width=child_width, height=child_height, highlight=self.highlight223 )224 lines = console.render_lines(renderable, child_options, style=style)225 226 line_start = Segment(box.mid_left, border_style)227 line_end = Segment(f"{box.mid_right}", border_style)228 new_line = Segment.line()229 if title_text is None or width <= 4:230 yield Segment(box.get_top([width - 2]), border_style)231 else:232 title_text = align_text(233 title_text,234 width - 4,235 self.title_align,236 box.top,237 border_style,238 )239 yield Segment(box.top_left + box.top, border_style)240 yield from console.render(title_text, child_options.update_width(width - 4))241 yield Segment(box.top + box.top_right, border_style)242 243 yield new_line244 for line in lines:245 yield line_start246 yield from line247 yield line_end248 yield new_line249 250 subtitle_text = self._subtitle251 if subtitle_text is not None:252 subtitle_text.stylize_before(border_style)253 254 if subtitle_text is None or width <= 4:255 yield Segment(box.get_bottom([width - 2]), border_style)256 else:257 subtitle_text = align_text(258 subtitle_text,259 width - 4,260 self.subtitle_align,261 box.bottom,262 border_style,263 )264 yield Segment(box.bottom_left + box.bottom, border_style)265 yield from console.render(266 subtitle_text, child_options.update_width(width - 4)267 )268 yield Segment(box.bottom + box.bottom_right, border_style)269 270 yield new_line271 272 def __rich_measure__(273 self, console: "Console", options: "ConsoleOptions"274 ) -> "Measurement":275 _title = self._title276 _, right, _, left = Padding.unpack(self.padding)277 padding = left + right278 renderables = [self.renderable, _title] if _title else [self.renderable]279 280 if self.width is None:281 width = (282 measure_renderables(283 console,284 options.update_width(options.max_width - padding - 2),285 renderables,286 ).maximum287 + padding288 + 2289 )290 else:291 width = self.width292 return Measurement(width, width)293 294 295if __name__ == "__main__": # pragma: no cover296 from .console import Console297 298 c = Console()299 300 from .box import DOUBLE, ROUNDED301 from .padding import Padding302 303 p = Panel(304 "Hello, World!",305 title="rich.Panel",306 style="white on blue",307 box=DOUBLE,308 padding=1,309 )310 311 c.print()312 c.print(p)313 