Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rich/panel.py: 77%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from typing import TYPE_CHECKING, Optional
3from .align import AlignMethod
4from .box import ROUNDED, Box
5from .cells import cell_len
6from .jupyter import JupyterMixin
7from .measure import Measurement, measure_renderables
8from .padding import Padding, PaddingDimensions
9from .segment import Segment
10from .style import Style, StyleType
11from .text import Text, TextType
13if TYPE_CHECKING:
14 from .console import Console, ConsoleOptions, RenderableType, RenderResult
17class Panel(JupyterMixin):
18 """A console renderable that draws a border around its contents.
20 Example:
21 >>> console.print(Panel("Hello, World!"))
23 Args:
24 renderable (RenderableType): A console renderable object.
25 box (Box): A Box instance that defines the look of the border (see :ref:`appendix_box`. Defaults to box.ROUNDED.
26 title (Optional[TextType], optional): Optional title displayed in panel header. Defaults to None.
27 title_align (AlignMethod, optional): Alignment of title. Defaults to "center".
28 subtitle (Optional[TextType], optional): Optional subtitle displayed in panel footer. Defaults to None.
29 subtitle_align (AlignMethod, optional): Alignment of subtitle. Defaults to "center".
30 safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
31 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.
32 style (str, optional): The style of the panel (border and contents). Defaults to "none".
33 border_style (str, optional): The style of the border. Defaults to "none".
34 width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect.
35 height (Optional[int], optional): Optional height of panel. Defaults to None to auto-detect.
36 padding (Optional[PaddingDimensions]): Optional padding around renderable. Defaults to 0.
37 highlight (bool, optional): Enable automatic highlighting of panel title (if str). Defaults to False.
38 """
40 def __init__(
41 self,
42 renderable: "RenderableType",
43 box: Box = ROUNDED,
44 *,
45 title: Optional[TextType] = None,
46 title_align: AlignMethod = "center",
47 subtitle: Optional[TextType] = None,
48 subtitle_align: AlignMethod = "center",
49 safe_box: Optional[bool] = None,
50 expand: bool = True,
51 style: StyleType = "none",
52 border_style: StyleType = "none",
53 width: Optional[int] = None,
54 height: Optional[int] = None,
55 padding: PaddingDimensions = (0, 1),
56 highlight: bool = False,
57 ) -> None:
58 self.renderable = renderable
59 self.box = box
60 self.title = title
61 self.title_align: AlignMethod = title_align
62 self.subtitle = subtitle
63 self.subtitle_align = subtitle_align
64 self.safe_box = safe_box
65 self.expand = expand
66 self.style = style
67 self.border_style = border_style
68 self.width = width
69 self.height = height
70 self.padding = padding
71 self.highlight = highlight
73 @classmethod
74 def fit(
75 cls,
76 renderable: "RenderableType",
77 box: Box = ROUNDED,
78 *,
79 title: Optional[TextType] = None,
80 title_align: AlignMethod = "center",
81 subtitle: Optional[TextType] = None,
82 subtitle_align: AlignMethod = "center",
83 safe_box: Optional[bool] = None,
84 style: StyleType = "none",
85 border_style: StyleType = "none",
86 width: Optional[int] = None,
87 height: Optional[int] = None,
88 padding: PaddingDimensions = (0, 1),
89 highlight: bool = False,
90 ) -> "Panel":
91 """An alternative constructor that sets expand=False."""
92 return cls(
93 renderable,
94 box,
95 title=title,
96 title_align=title_align,
97 subtitle=subtitle,
98 subtitle_align=subtitle_align,
99 safe_box=safe_box,
100 style=style,
101 border_style=border_style,
102 width=width,
103 height=height,
104 padding=padding,
105 highlight=highlight,
106 expand=False,
107 )
109 @property
110 def _title(self) -> Optional[Text]:
111 if self.title:
112 title_text = (
113 Text.from_markup(self.title)
114 if isinstance(self.title, str)
115 else self.title.copy()
116 )
117 title_text.end = ""
118 title_text.plain = title_text.plain.replace("\n", " ")
119 title_text.no_wrap = True
120 title_text.expand_tabs()
121 title_text.pad(1)
122 return title_text
123 return None
125 @property
126 def _subtitle(self) -> Optional[Text]:
127 if self.subtitle:
128 subtitle_text = (
129 Text.from_markup(self.subtitle)
130 if isinstance(self.subtitle, str)
131 else self.subtitle.copy()
132 )
133 subtitle_text.end = ""
134 subtitle_text.plain = subtitle_text.plain.replace("\n", " ")
135 subtitle_text.no_wrap = True
136 subtitle_text.expand_tabs()
137 subtitle_text.pad(1)
138 return subtitle_text
139 return None
141 def __rich_console__(
142 self, console: "Console", options: "ConsoleOptions"
143 ) -> "RenderResult":
144 _padding = Padding.unpack(self.padding)
145 renderable = (
146 Padding(self.renderable, _padding) if any(_padding) else self.renderable
147 )
148 style = console.get_style(self.style)
149 partial_border_style = console.get_style(self.border_style)
150 border_style = style + partial_border_style
151 width = (
152 options.max_width
153 if self.width is None
154 else min(options.max_width, self.width)
155 )
157 safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box
158 box = self.box.substitute(options, safe=safe_box)
160 def align_text(
161 text: Text, width: int, align: str, character: str, style: Style
162 ) -> Text:
163 """Gets new aligned text.
165 Args:
166 text (Text): Title or subtitle text.
167 width (int): Desired width.
168 align (str): Alignment.
169 character (str): Character for alignment.
170 style (Style): Border style
172 Returns:
173 Text: New text instance
174 """
175 text = text.copy()
176 text.truncate(width)
177 excess_space = width - cell_len(text.plain)
178 if text.style:
179 text.stylize(console.get_style(text.style))
181 if excess_space:
182 if align == "left":
183 return Text.assemble(
184 text,
185 (character * excess_space, style),
186 no_wrap=True,
187 end="",
188 )
189 elif align == "center":
190 left = excess_space // 2
191 return Text.assemble(
192 (character * left, style),
193 text,
194 (character * (excess_space - left), style),
195 no_wrap=True,
196 end="",
197 )
198 else:
199 return Text.assemble(
200 (character * excess_space, style),
201 text,
202 no_wrap=True,
203 end="",
204 )
205 return text
207 title_text = self._title
208 if title_text is not None:
209 title_text.stylize_before(partial_border_style)
211 child_width = (
212 width - 2
213 if self.expand
214 else console.measure(
215 renderable, options=options.update_width(width - 2)
216 ).maximum
217 )
218 child_height = self.height or options.height or None
219 if child_height:
220 child_height -= 2
221 if title_text is not None:
222 child_width = min(
223 options.max_width - 2, max(child_width, title_text.cell_len + 2)
224 )
226 width = child_width + 2
227 child_options = options.update(
228 width=child_width, height=child_height, highlight=self.highlight
229 )
230 lines = console.render_lines(renderable, child_options, style=style)
232 line_start = Segment(box.mid_left, border_style)
233 line_end = Segment(f"{box.mid_right}", border_style)
234 new_line = Segment.line()
235 if title_text is None or width <= 4:
236 yield Segment(box.get_top([width - 2]), border_style)
237 else:
238 title_text = align_text(
239 title_text,
240 width - 4,
241 self.title_align,
242 box.top,
243 border_style,
244 )
245 yield Segment(box.top_left + box.top, border_style)
246 yield from console.render(title_text, child_options.update_width(width - 4))
247 yield Segment(box.top + box.top_right, border_style)
249 yield new_line
250 for line in lines:
251 yield line_start
252 yield from line
253 yield line_end
254 yield new_line
256 subtitle_text = self._subtitle
257 if subtitle_text is not None:
258 subtitle_text.stylize_before(partial_border_style)
260 if subtitle_text is None or width <= 4:
261 yield Segment(box.get_bottom([width - 2]), border_style)
262 else:
263 subtitle_text = align_text(
264 subtitle_text,
265 width - 4,
266 self.subtitle_align,
267 box.bottom,
268 border_style,
269 )
270 yield Segment(box.bottom_left + box.bottom, border_style)
271 yield from console.render(
272 subtitle_text, child_options.update_width(width - 4)
273 )
274 yield Segment(box.bottom + box.bottom_right, border_style)
276 yield new_line
278 def __rich_measure__(
279 self, console: "Console", options: "ConsoleOptions"
280 ) -> "Measurement":
281 _title = self._title
282 _, right, _, left = Padding.unpack(self.padding)
283 padding = left + right
284 renderables = [self.renderable, _title] if _title else [self.renderable]
286 if self.width is None:
287 width = (
288 measure_renderables(
289 console,
290 options.update_width(options.max_width - padding - 2),
291 renderables,
292 ).maximum
293 + padding
294 + 2
295 )
296 else:
297 width = self.width
298 return Measurement(width, width)
301if __name__ == "__main__": # pragma: no cover
302 from .console import Console
304 c = Console()
306 from .box import DOUBLE, ROUNDED
307 from .padding import Padding
309 p = Panel(
310 "Hello, World!",
311 title="rich.Panel",
312 style="white on blue",
313 box=DOUBLE,
314 padding=1,
315 )
317 c.print()
318 c.print(p)