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