OpenSpace / openspace /utils /display.py
darkfire514's picture
Upload 160 files
399b80c verified
from typing import Optional, List
from enum import Enum
import re
class Colors:
RESET = "\033[0m"
BOLD = "\033[1m"
DIM = "\033[2m"
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
MAGENTA = "\033[95m"
CYAN = "\033[96m"
WHITE = "\033[97m"
GRAY = "\033[90m"
GREEN_SOFT = '\033[38;5;78m'
BLUE_SOFT = '\033[38;5;39m'
CYAN_SOFT = '\033[38;5;51m'
YELLOW_SOFT = '\033[38;5;222m'
RED_SOFT = '\033[38;5;204m'
MAGENTA_SOFT = '\033[38;5;141m'
GRAY_SOFT = '\033[38;5;246m'
class BoxStyle(Enum):
ROUNDED = "rounded" # Rounded corner box โ•ญโ”€โ•ฎโ•ฐโ•ฏ
SQUARE = "square" # Square corner box โ”Œโ”€โ”โ””โ”˜
DOUBLE = "double" # Double line box โ•”โ•โ•—โ•šโ•
SIMPLE = "simple" # Simple box ===
BOX_CHARS = {
BoxStyle.ROUNDED: {
'tl': 'โ•ญ', 'tr': 'โ•ฎ', 'bl': 'โ•ฐ', 'br': 'โ•ฏ',
'h': 'โ”€', 'v': 'โ”‚'
},
BoxStyle.SQUARE: {
'tl': 'โ”Œ', 'tr': 'โ”', 'bl': 'โ””', 'br': 'โ”˜',
'h': 'โ”€', 'v': 'โ”‚'
},
BoxStyle.DOUBLE: {
'tl': 'โ•”', 'tr': 'โ•—', 'bl': 'โ•š', 'br': 'โ•',
'h': 'โ•', 'v': 'โ•‘'
},
}
def strip_ansi(text: str) -> str:
"""
Strip ANSI color codes from text
Args:
text: Text with potential ANSI codes
Returns:
Clean text without ANSI codes
"""
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
return ansi_escape.sub('', text)
def colorize(text: str, color: str = '', bold: bool = False) -> str:
try:
color_map = {
'r': Colors.RESET,
'b': Colors.BOLD,
'd': Colors.DIM,
'g': Colors.GREEN_SOFT,
'bl': Colors.BLUE_SOFT,
'c': Colors.CYAN_SOFT,
'y': Colors.YELLOW_SOFT,
'rd': Colors.RED_SOFT,
'm': Colors.MAGENTA_SOFT,
'gr': Colors.GRAY_SOFT,
}
prefix = Colors.BOLD if bold else ''
code = color_map.get(color, color)
return f"{prefix}{code}{text}{Colors.RESET}"
except:
return text
class Box:
def __init__(self,
width: int = 68,
style: BoxStyle = BoxStyle.ROUNDED,
color: str = 'bl',
padding: int = 2):
self.width = width
self.style = style
self.color = color
self.padding = padding
self.chars = BOX_CHARS.get(style, BOX_CHARS[BoxStyle.ROUNDED])
def top_line(self, indent: int = 2) -> str:
indent_str = " " * indent
if self.style == BoxStyle.SIMPLE:
return colorize(indent_str + "=" * self.width, self.color)
return colorize(
indent_str + self.chars['tl'] + self.chars['h'] * self.width + self.chars['tr'],
self.color
)
def bottom_line(self, indent: int = 2) -> str:
indent_str = " " * indent
if self.style == BoxStyle.SIMPLE:
return colorize(indent_str + "=" * self.width, self.color)
return colorize(
indent_str + self.chars['bl'] + self.chars['h'] * self.width + self.chars['br'],
self.color
)
def separator_line(self, indent: int = 2) -> str:
indent_str = " " * indent
if self.style == BoxStyle.SIMPLE:
return colorize(indent_str + "-" * self.width, self.color)
return colorize(indent_str + " " + self.chars['h'] * self.width, self.color)
def empty_line(self, indent: int = 2) -> str:
indent_str = " " * indent
if self.style == BoxStyle.SIMPLE:
return ""
return colorize(
indent_str + self.chars['v'] + " " * self.width + self.chars['v'],
self.color
)
def text_line(self, text: str, align: str = 'left', indent: int = 2, text_color: str = '') -> str:
indent_str = " " * indent
content_width = self.width - 2 * self.padding
# Strip ANSI codes to get actual display length
clean_text = strip_ansi(text)
text_len = len(clean_text)
# Use original text (may contain colors) or apply new color
display_text = colorize(text, text_color) if text_color else text
if align == 'center':
left_pad = (content_width - text_len) // 2
right_pad = content_width - text_len - left_pad
content = " " * left_pad + display_text + " " * right_pad
elif align == 'right':
left_pad = content_width - text_len
content = " " * left_pad + display_text
else: # left
right_pad = content_width - text_len
content = display_text + " " * right_pad
if self.style == BoxStyle.SIMPLE:
return indent_str + " " * self.padding + content
padding_str = " " * self.padding
return colorize(indent_str + self.chars['v'], self.color) + \
padding_str + content + padding_str + \
colorize(self.chars['v'], self.color)
def build(self,
title: Optional[str] = None,
lines: List[str] = None,
footer: Optional[str] = None,
indent: int = 2) -> str:
result = []
result.append(self.top_line(indent))
if title:
result.append(self.empty_line(indent))
result.append(self.text_line(title, align='center', indent=indent, text_color='c'))
result.append(self.empty_line(indent))
if lines:
for line in lines:
result.append(self.text_line(line, indent=indent))
if footer:
result.append(self.empty_line(indent))
result.append(self.text_line(footer, align='center', indent=indent, text_color='gr'))
result.append(self.bottom_line(indent))
return "\n".join(result)
def print_box(title: Optional[str] = None,
lines: List[str] = None,
footer: Optional[str] = None,
width: int = 68,
style: BoxStyle = BoxStyle.ROUNDED,
color: str = 'bl',
indent: int = 2):
box = Box(width=width, style=style, color=color)
print(box.build(title=title, lines=lines, footer=footer, indent=indent))
def print_banner(title: str,
subtitle: Optional[str] = None,
width: int = 66,
style: BoxStyle = BoxStyle.ROUNDED,
color: str = 'bl',
indent: int = 2):
box = Box(width=width, style=style, color=color)
print()
print(box.top_line(indent))
print(box.empty_line(indent))
print(box.text_line(title, align='center', indent=indent, text_color='c'))
if subtitle:
print(box.text_line(subtitle, align='center', indent=indent, text_color='gr'))
print(box.empty_line(indent))
print(box.bottom_line(indent))
print()
def print_section(title: str,
content: List[str],
color: str = 'c',
indent: int = 2):
indent_str = " " * indent
print(f"\n{indent_str}{colorize('- ' + title, color, bold=True)}")
for line in content:
print(f"{indent_str} {line}")
def print_separator(width: int = 68, color: str = 'bl', indent: int = 2):
indent_str = " " * indent
print(colorize(indent_str + "โ”€" * width, color))