from typing import TYPE_CHECKING , Optional
from . align import AlignMethod
from . box import ROUNDED , Box
from . cells import cell_len
from . jupyter import JupyterMixin
from . measure import Measurement , measure_renderables
from . padding import Padding , PaddingDimensions
from . segment import Segment
from . style import Style , 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 ,
height : Optional [ int ] = None ,
padding : PaddingDimensions = ( 0 , 1 ) ,
highlight : bool = False ,
) - > " 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 ,
height = height ,
padding = padding ,
highlight = highlight ,
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 )
def align_text (
text : Text , width : int , align : str , character : str , style : Style
) - > Text :
""" Gets new aligned text.
Args :
text ( Text ) : Title or subtitle text .
width ( int ) : Desired width .
align ( str ) : Alignment .
character ( str ) : Character for alignment .
style ( Style ) : Border style
Returns :
Text : New text instance
"""
text = text . copy ( )
text . truncate ( width )
excess_space = width - cell_len ( text . plain )
if excess_space :
if align == " left " :
return Text . assemble (
text ,
( character * excess_space , style ) ,
no_wrap = True ,
end = " " ,
)
elif align == " center " :
left = excess_space / / 2
return Text . assemble (
( character * left , style ) ,
text ,
( character * ( excess_space - left ) , style ) ,
no_wrap = True ,
end = " " ,
)
else :
return Text . assemble (
( character * excess_space , style ) ,
text ,
no_wrap = True ,
end = " " ,
)
return text
title_text = self . _title
if title_text is not None :
title_text . stylize_before ( 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_text (
title_text ,
width - 4 ,
self . title_align ,
box . top ,
border_style ,
)
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 . stylize_before ( border_style )
if subtitle_text is None or width < = 4 :
yield Segment ( box . get_bottom ( [ width - 2 ] ) , border_style )
else :
subtitle_text = align_text (
subtitle_text ,
width - 4 ,
self . subtitle_align ,
box . bottom ,
border_style ,
)
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 )