pytermgui.widgets.boxes
Convenience objects for Container corner & border styles.
They can be used as:
from pytermgui import Container, boxes
boxes.DOUBLE_TOP.set_chars_of(Container)
c = Container() # this will now use the style chosen
Boxes are also settable as a property of pytermgui.widgets.Container
, and can
be referenced & defined in markup file definitions. For more info, check out
pytermgui.file_loaders
.
View Source
0""" 1Convenience objects for Container corner & border styles. 2 3They can be used as: 4```python3 5from pytermgui import Container, boxes 6 7boxes.DOUBLE_TOP.set_chars_of(Container) 8c = Container() # this will now use the style chosen 9``` 10 11Boxes are also settable as a property of `pytermgui.widgets.Container`, and can 12be referenced & defined in markup file definitions. For more info, check out 13`pytermgui.file_loaders`. 14""" 15 16from __future__ import annotations 17 18from typing import Tuple 19 20from .base import WidgetType 21from ..regex import real_length 22 23 24class Box: 25 """Class for defining border & corner styles 26 27 `lines` should be `list[str]` of length 3, such as: 28 29 ```python3 30 lines = [ 31 ".---.", 32 "| x |", 33 "`---`", 34 ] 35 ``` 36 37 The length of individual lines is arbitrary, only limitation is 38 that the top & bottom border characters should occur most often in 39 their respective lines. 40 41 You can set corners to be of any length, their end is calculated by 42 finding the index of the most often occuring character, which is assumed 43 to be the border character. 44 45 Top & bottom borders are currently limited in length to 1, but sides 46 operate similarly to corners. They are separated by finding the index 47 of the fill char from the start or end. The content char is "x" by 48 default, however it can be set to anything else by giving the "content_char" 49 construction parameter. 50 51 As such, this: 52 53 ```python3 54 boxes.Box( 55 [ 56 "corner1 ________________ corner2", 57 "xleft ################ rightxx", 58 "corner3 ---------------- corner4", 59 ], 60 content_char="#", 61 ) 62 ``` 63 64 Will result in: 65 66 ```python3 67 Box( 68 borders=['xleft ', '_', ' rightxx', '-'], 69 corners=['corner1 ', ' corner2', ' corner4', 'corner3 '] 70 ) 71 ``` 72 """ 73 74 CharType = Tuple[str, str, str, str] 75 76 def __init__(self, lines: list[str], content_char: str = "x"): 77 """Set instance attributes""" 78 79 super().__init__() 80 self.content_char = content_char 81 82 top, _, bottom = lines 83 top_left, top_right = self._get_corners(top) 84 bottom_left, bottom_right = self._get_corners(bottom) 85 86 self.borders = list(self._get_borders(lines)) 87 self.corners = [ 88 top_left, 89 top_right, 90 bottom_right, 91 bottom_left, 92 ] 93 94 def __repr__(self) -> str: 95 """Return string of self""" 96 97 return self.debug() 98 99 @staticmethod 100 def _find_mode_char(line: str) -> str: 101 """Find most often consecutively occuring character in string""" 102 103 instances = 0 104 current_char = "" 105 106 results: list[tuple[str, int]] = [] 107 for char in line: 108 if current_char == char: 109 instances += 1 110 else: 111 if len(current_char) > 0: 112 results.append((current_char, instances)) 113 114 instances = 1 115 current_char = char 116 117 results.append((current_char, instances)) 118 119 results.sort(key=lambda item: item[1]) 120 if len(results) == 0: 121 print(line, instances, current_char) 122 123 return results[-1][0] 124 125 def _get_corners(self, line: str) -> tuple[str, str]: 126 """Get corners from a line""" 127 128 mode_char = self._find_mode_char(line) 129 left = line[: line.index(mode_char)] 130 right = line[real_length(line) - (line[::-1].index(mode_char)) :] 131 132 return left, right 133 134 def _get_borders(self, lines: list[str]) -> tuple[str, str, str, str]: 135 """Get borders from all lines""" 136 137 top, middle, bottom = lines 138 middle_reversed = middle[::-1] 139 140 top_border = self._find_mode_char(top) 141 left_border = middle[: middle.index(self.content_char)] 142 143 right_border = middle[ 144 real_length(middle) - middle_reversed.index(self.content_char) : 145 ] 146 bottom_border = self._find_mode_char(bottom) 147 148 # return top_border, left_border, right_border, bottom_border 149 return left_border, top_border, right_border, bottom_border 150 151 def set_chars_of(self, cls_or_obj: WidgetType) -> WidgetType: 152 """Set border & corner chars of cls_or_obj to self values""" 153 154 # We cannot import any widgets into here due to cyclic imports, 155 # so we have to "hack" around it. 156 if not hasattr(cls_or_obj, "set_char"): 157 raise NotImplementedError( 158 f"Object of type {cls_or_obj} does not support `set_char`" 159 ) 160 161 cls_or_obj.set_char("border", self.borders) 162 cls_or_obj.set_char("corner", self.corners) 163 164 return cls_or_obj 165 166 def debug(self) -> str: 167 """Return identifiable information about object""" 168 169 return f"Box(borders={self.borders}, corners={self.corners})" 170 171 172BASIC = Box( 173 [ 174 "-----", 175 "| x |", 176 "-----", 177 ] 178) 179 180HEAVY = Box( 181 [ 182 "┏━━━┓", 183 "┃ x ┃", 184 "┗━━━┛", 185 ] 186) 187EMPTY = Box( 188 [ 189 "", 190 "x", 191 "", 192 ] 193) 194EMPTY_VERTICAL = Box( 195 [ 196 "─────", 197 " x ", 198 "─────", 199 ] 200) 201EMPTY_HORIZONTAL = Box( 202 [ 203 "│ │", 204 "│ x │", 205 "│ │", 206 ] 207) 208ROUNDED = Box( 209 [ 210 "╭───╮", 211 "│ x │", 212 "╰───╯", 213 ] 214) 215 216SINGLE = Box( 217 [ 218 "┌───┐", 219 "│ x │", 220 "└───┘", 221 ] 222) 223 224SINGLE_VERTICAL = Box( 225 [ 226 "╔───╗", 227 "║ x ║", 228 "╚───╝", 229 ] 230) 231SINGLE_HORIZONTAL = Box( 232 [ 233 "╔═══╗", 234 "│ x │", 235 "╚═══╝", 236 ] 237) 238DOUBLE_HORIZONTAL = Box( 239 [ 240 "╭═══╮", 241 "│ x │", 242 "╰═══╯", 243 ] 244) 245DOUBLE_VERTICAL = Box( 246 [ 247 "╭───╮", 248 "║ x ║", 249 "╰───╯", 250 ] 251) 252DOUBLE_SIDES = Box( 253 [ 254 "╭═══╮", 255 "║ x ║", 256 "╰═══╯", 257 ] 258) 259DOUBLE = Box( 260 [ 261 "╔═══╗", 262 "║ x ║", 263 "╚═══╝", 264 ] 265) 266DOUBLE_TOP = Box( 267 [ 268 "╭═══╮", 269 "│ x │", 270 "╰───╯", 271 ] 272) 273DOUBLE_BOTTOM = Box( 274 [ 275 "╭───╮", 276 "│ x │", 277 "╰═══╯", 278 ] 279)
View Source
25class Box: 26 """Class for defining border & corner styles 27 28 `lines` should be `list[str]` of length 3, such as: 29 30 ```python3 31 lines = [ 32 ".---.", 33 "| x |", 34 "`---`", 35 ] 36 ``` 37 38 The length of individual lines is arbitrary, only limitation is 39 that the top & bottom border characters should occur most often in 40 their respective lines. 41 42 You can set corners to be of any length, their end is calculated by 43 finding the index of the most often occuring character, which is assumed 44 to be the border character. 45 46 Top & bottom borders are currently limited in length to 1, but sides 47 operate similarly to corners. They are separated by finding the index 48 of the fill char from the start or end. The content char is "x" by 49 default, however it can be set to anything else by giving the "content_char" 50 construction parameter. 51 52 As such, this: 53 54 ```python3 55 boxes.Box( 56 [ 57 "corner1 ________________ corner2", 58 "xleft ################ rightxx", 59 "corner3 ---------------- corner4", 60 ], 61 content_char="#", 62 ) 63 ``` 64 65 Will result in: 66 67 ```python3 68 Box( 69 borders=['xleft ', '_', ' rightxx', '-'], 70 corners=['corner1 ', ' corner2', ' corner4', 'corner3 '] 71 ) 72 ``` 73 """ 74 75 CharType = Tuple[str, str, str, str] 76 77 def __init__(self, lines: list[str], content_char: str = "x"): 78 """Set instance attributes""" 79 80 super().__init__() 81 self.content_char = content_char 82 83 top, _, bottom = lines 84 top_left, top_right = self._get_corners(top) 85 bottom_left, bottom_right = self._get_corners(bottom) 86 87 self.borders = list(self._get_borders(lines)) 88 self.corners = [ 89 top_left, 90 top_right, 91 bottom_right, 92 bottom_left, 93 ] 94 95 def __repr__(self) -> str: 96 """Return string of self""" 97 98 return self.debug() 99 100 @staticmethod 101 def _find_mode_char(line: str) -> str: 102 """Find most often consecutively occuring character in string""" 103 104 instances = 0 105 current_char = "" 106 107 results: list[tuple[str, int]] = [] 108 for char in line: 109 if current_char == char: 110 instances += 1 111 else: 112 if len(current_char) > 0: 113 results.append((current_char, instances)) 114 115 instances = 1 116 current_char = char 117 118 results.append((current_char, instances)) 119 120 results.sort(key=lambda item: item[1]) 121 if len(results) == 0: 122 print(line, instances, current_char) 123 124 return results[-1][0] 125 126 def _get_corners(self, line: str) -> tuple[str, str]: 127 """Get corners from a line""" 128 129 mode_char = self._find_mode_char(line) 130 left = line[: line.index(mode_char)] 131 right = line[real_length(line) - (line[::-1].index(mode_char)) :] 132 133 return left, right 134 135 def _get_borders(self, lines: list[str]) -> tuple[str, str, str, str]: 136 """Get borders from all lines""" 137 138 top, middle, bottom = lines 139 middle_reversed = middle[::-1] 140 141 top_border = self._find_mode_char(top) 142 left_border = middle[: middle.index(self.content_char)] 143 144 right_border = middle[ 145 real_length(middle) - middle_reversed.index(self.content_char) : 146 ] 147 bottom_border = self._find_mode_char(bottom) 148 149 # return top_border, left_border, right_border, bottom_border 150 return left_border, top_border, right_border, bottom_border 151 152 def set_chars_of(self, cls_or_obj: WidgetType) -> WidgetType: 153 """Set border & corner chars of cls_or_obj to self values""" 154 155 # We cannot import any widgets into here due to cyclic imports, 156 # so we have to "hack" around it. 157 if not hasattr(cls_or_obj, "set_char"): 158 raise NotImplementedError( 159 f"Object of type {cls_or_obj} does not support `set_char`" 160 ) 161 162 cls_or_obj.set_char("border", self.borders) 163 cls_or_obj.set_char("corner", self.corners) 164 165 return cls_or_obj 166 167 def debug(self) -> str: 168 """Return identifiable information about object""" 169 170 return f"Box(borders={self.borders}, corners={self.corners})"
Class for defining border & corner styles
lines
should be list[str]
of length 3, such as:
lines = [
".---.",
"| x |",
"`---`",
]
The length of individual lines is arbitrary, only limitation is that the top & bottom border characters should occur most often in their respective lines.
You can set corners to be of any length, their end is calculated by finding the index of the most often occuring character, which is assumed to be the border character.
Top & bottom borders are currently limited in length to 1, but sides operate similarly to corners. They are separated by finding the index of the fill char from the start or end. The content char is "x" by default, however it can be set to anything else by giving the "content_char" construction parameter.
As such, this:
boxes.Box(
[
"corner1 ________________ corner2",
"xleft ################ rightxx",
"corner3 ---------------- corner4",
],
content_char="#",
)
Will result in:
Box(
borders=['xleft ', '_', ' rightxx', '-'],
corners=['corner1 ', ' corner2', ' corner4', 'corner3 ']
)
View Source
77 def __init__(self, lines: list[str], content_char: str = "x"): 78 """Set instance attributes""" 79 80 super().__init__() 81 self.content_char = content_char 82 83 top, _, bottom = lines 84 top_left, top_right = self._get_corners(top) 85 bottom_left, bottom_right = self._get_corners(bottom) 86 87 self.borders = list(self._get_borders(lines)) 88 self.corners = [ 89 top_left, 90 top_right, 91 bottom_right, 92 bottom_left, 93 ]
Set instance attributes
View Source
152 def set_chars_of(self, cls_or_obj: WidgetType) -> WidgetType: 153 """Set border & corner chars of cls_or_obj to self values""" 154 155 # We cannot import any widgets into here due to cyclic imports, 156 # so we have to "hack" around it. 157 if not hasattr(cls_or_obj, "set_char"): 158 raise NotImplementedError( 159 f"Object of type {cls_or_obj} does not support `set_char`" 160 ) 161 162 cls_or_obj.set_char("border", self.borders) 163 cls_or_obj.set_char("corner", self.corners) 164 165 return cls_or_obj
Set border & corner chars of cls_or_obj to self values