pytermgui.window_manager.layouts
Layouts for the WindowManager.
1"""Layouts for the WindowManager.""" 2 3from __future__ import annotations 4 5from dataclasses import dataclass 6from typing import Callable 7 8from ..terminal import Terminal, get_terminal 9from ..widgets import Widget 10 11 12class Dimension: 13 """The base class for layout dimensions. 14 15 Each dimension has a `value` property. This returns an integer, 16 and is essentially the *meaning* of the object. 17 """ 18 19 _value: int 20 21 @property 22 def value(self) -> int: 23 """Returns the value of the object. 24 25 Override this for custom behaviour.""" 26 27 return self._value 28 29 @value.setter 30 def value(self, new: int) -> None: 31 """Sets a new value.""" 32 33 self._value = new 34 35 def __repr__(self) -> str: 36 """Returns `{typename}(value={value})`. 37 38 We use this over the dataclasses one as that used `_value`, and it's 39 a bit ugly. 40 """ 41 42 return f"{type(self).__name__}(value={self.value})" 43 44 45@dataclass(repr=False, frozen=True) 46class Static(Dimension): 47 """A static dimension. 48 49 This dimension is immutable, and the Layout will always leave it unchanged. 50 """ 51 52 _value: int = 0 53 54 55@dataclass(repr=False) 56class Relative(Dimension): 57 """A relative dimension. 58 59 This dimension has a scale attribute and bound method. Every time the `value` 60 is queried, `int(self.bound() * self.scale)` is returned. 61 62 When instantiated through `Layout.add_slot`, `bound` will default to either 63 the terminal's width or height, depending on which attribute it is applied to. 64 """ 65 66 _value = 0 67 scale: float 68 bound: Callable[[], int] 69 70 @property 71 def value(self) -> int: 72 """Calculates the new value for the dimension.""" 73 74 return int(self.bound() * self.scale) 75 76 @value.setter 77 def value(self, new: int) -> None: 78 """Disallows setting the value. 79 80 We can't inherit and then override a set-get property with a get one, so this 81 kind of patches that issue up. 82 """ 83 84 raise TypeError 85 86 def __repr__(self) -> str: 87 scale = self.scale 88 bound = self.bound 89 90 original = super().__repr__() 91 return original[:-1] + f", {scale=}, {bound=}" + original[-1] 92 93 94@dataclass 95class Auto(Dimension): 96 """An automatically calculated dimension. 97 98 The value of this dimension is overwritten on `Layout.apply`. 99 100 Generally, the way calculations are done is by looking at the available 101 size of the layout by subtracting the sum of all the non-auto dimensions 102 from the terminal's width or height, and dividing it by the number of 103 Auto-type dimensions in the current context. 104 105 An additional offset is applied to the first dimension (left-most or top-most) 106 of the context when the division has a remainder. 107 """ 108 109 _value = 0 110 111 def __repr__(self) -> str: 112 return f"{type(self).__name__}(value={self.value})" 113 114 115@dataclass 116class Slot: 117 """A slot within a layout. 118 119 A slot has a name, width & height, as well as some content. It's `apply` method 120 can be called to apply the slot's position & dimensions to its content. 121 """ 122 123 name: str 124 width: Dimension 125 height: Dimension 126 127 content: Widget | None = None 128 129 _restore_data: tuple[int, int, tuple[int, int]] | None = None 130 131 def apply(self, position: tuple[int, int]) -> None: 132 """Applies the given position & dimension to the content. 133 134 Args: 135 position: The position that this object resides in. Set as its content's `pos`. 136 """ 137 138 if self.content is None or self.width is None or self.height is None: 139 return 140 141 if self._restore_data is None: 142 self._restore_data = ( 143 self.content.width, 144 self.content.height, 145 self.content.pos, 146 ) 147 148 self.content.height = self.height.value 149 self.content.width = self.width.value 150 self.content.pos = position 151 152 def detach_content(self) -> None: 153 """Detaches content & restores its original state.""" 154 155 content = self.content 156 if content is None: 157 raise AttributeError(f"No content to detach in {self!r}.") 158 159 assert self._restore_data is not None 160 161 content.width, content.height, content.pos = self._restore_data 162 163 self.content = None 164 self._restore_data = None 165 166 167ROW_BREAK = Slot("Row Break", Static(0), Static(0)) 168"""When encountered in `Layout.build_rows`, a new row will be started at the next element.""" 169 170 171class Layout: 172 """Defines a layout of Widgets, used by WindowManager. 173 174 Internally, it keeps track of a list of `Slot`. This list is then turned into a list 175 of rows, all containing slots. This is done either when the current row has run out 176 of the terminal's width, or `ROW_BREAK` is encountered. 177 """ 178 179 name: str 180 181 def __init__(self, name: str = "Layout") -> None: 182 self.name = name 183 self.slots: list[Slot] = [] 184 185 @property 186 def terminal(self) -> Terminal: 187 """Returns the current global terminal instance.""" 188 189 return get_terminal() 190 191 def _to_rows(self) -> list[list[Slot]]: 192 """Breaks `self.slots` into a list of list of slots. 193 194 The terminal's remaining width is kept track of, and when a slot doesn't have enough 195 space left it is pushed to a new row. Additionally, `ROW_BREAK` will force a new 196 row to be created, starting with the next slot. 197 """ 198 199 rows: list[list[Slot]] = [] 200 available = self.terminal.width 201 202 row: list[Slot] = [] 203 for slot in self.slots: 204 if available <= 0 or slot is ROW_BREAK: 205 rows.append(row) 206 207 row = [] 208 available = self.terminal.width - slot.width.value 209 210 if slot is ROW_BREAK: 211 continue 212 213 available -= slot.width.value 214 row.append(slot) 215 216 if len(row) > 0: 217 rows.append(row) 218 219 return rows 220 221 def build_rows(self) -> list[list[Slot]]: 222 """Builds a list of slot rows, breaking them & applying automatic dimensions. 223 224 Returns: 225 A list[list[Slot]], aka. a list of slot-rows. 226 """ 227 228 def _get_height(row: list[Slot]) -> int: 229 defined = list(filter(lambda slot: not isinstance(slot.height, Auto), row)) 230 231 if len(defined) > 0: 232 return max(slot.height.value for slot in defined) 233 234 return 0 235 236 def _calculate_widths(row: list[Slot]) -> tuple[int, int]: 237 defined: list[Slot] = list( 238 filter(lambda slt: not isinstance(slt.width, Auto), row) 239 ) 240 undefined = list(filter(lambda slt: slt not in defined, row)) 241 242 available = self.terminal.width - sum(slot.width.value for slot in defined) 243 244 return divmod(available, len(undefined) or 1) 245 246 rows = self._to_rows() 247 heights = [_get_height(row) for row in rows] 248 249 occupied = sum(heights) 250 auto_height, extra_height = divmod( 251 self.terminal.height - occupied, heights.count(0) or 1 252 ) 253 254 for row, height in zip(rows, heights): 255 height = height or auto_height 256 257 auto_width, extra_width = _calculate_widths(row) 258 for slot in row: 259 width = auto_width if isinstance(slot.width, Auto) else slot.width.value 260 261 if isinstance(slot.height, Auto): 262 slot.height.value = height + extra_height 263 extra_height = 0 264 265 if isinstance(slot.width, Auto): 266 slot.width.value = width + extra_width 267 extra_width = 0 268 269 return rows 270 271 def add_slot( 272 self, 273 name: str = "Slot", 274 *, 275 slot: Slot | None = None, 276 width: Dimension | int | float | None = None, 277 height: Dimension | int | float | None = None, 278 index: int = -1, 279 ) -> Slot: 280 """Adds a new slot to the layout. 281 282 Args: 283 name: The name of the slot. Used for display purposes. 284 slot: An already instantiated `Slot` instance. If this is given, 285 the additional width & height arguments will be ignored. 286 width: The width for the new slot. See below for special types. 287 height: The height for the new slot. See below for special types. 288 index: The index to add the new slot to. 289 290 Returns: 291 The just-added slot. 292 293 When defining dimensions, either width or height, some special value 294 types can be given: 295 - `Dimension`: Passed directly to the new slot. 296 - `None`: An `Auto` dimension is created with no value. 297 - `int`: A `Static` dimension is created with the given value. 298 - `float`: A `Relative` dimension is created with the given value as its 299 scale. Its `bound` attribute will default to the relevant part of the 300 terminal's size. 301 """ 302 303 if slot is None: 304 if width is None: 305 width = Auto() 306 307 elif isinstance(width, int): 308 width = Static(width) 309 310 elif isinstance(width, float): 311 width = Relative(width, bound=lambda: self.terminal.width) 312 313 if height is None: 314 height = Auto() 315 316 elif isinstance(height, int): 317 height = Static(height) 318 319 elif isinstance(height, float): 320 height = Relative(height, bound=lambda: self.terminal.height) 321 322 slot = Slot(name, width=width, height=height) 323 324 if index == -1: 325 self.slots.append(slot) 326 return slot 327 328 self.slots.insert(index, slot) 329 330 return slot 331 332 def add_break(self, *, index: int = -1) -> None: 333 """Adds `ROW_BREAK` to the given index. 334 335 This special slot is ignored for all intents and purposes, other than when 336 breaking the slots into rows. In that context, when encountered, the current 337 row is deemed completed, and the next slot will go into a new row list. 338 """ 339 340 self.add_slot(slot=ROW_BREAK, index=index) 341 342 def assign(self, widget: Widget, *, index: int = -1, apply: bool = True) -> None: 343 """Assigns a widget to the slot at the specified index. 344 345 Args: 346 widget: The widget to assign. 347 index: The target slot's index. 348 apply: If set, `apply` will be called once the widget has been assigned. 349 """ 350 351 slots = [slot for slot in self.slots if slot is not ROW_BREAK] 352 if index > len(slots) - 1: 353 return 354 355 slot = slots[index] 356 357 slot.content = widget 358 359 if apply: 360 self.apply() 361 362 def apply(self) -> None: 363 """Applies the layout to each slot.""" 364 365 position = list(self.terminal.origin) 366 for row in self.build_rows(): 367 position[0] = 1 368 369 for slot in row: 370 slot.apply((position[0], position[1])) 371 372 position[0] += slot.width.value 373 374 position[1] += max(slot.height.value for slot in row) 375 376 def __getattr__(self, attr: str) -> Slot: 377 """Gets a slot by its (slugified) name.""" 378 379 def _snakeify(name: str) -> str: 380 return name.lower().replace(" ", "_") 381 382 for slot in self.slots: 383 if _snakeify(slot.name) == attr: 384 return slot 385 386 raise AttributeError(f"Slot with name {attr!r} could not be found.")
13class Dimension: 14 """The base class for layout dimensions. 15 16 Each dimension has a `value` property. This returns an integer, 17 and is essentially the *meaning* of the object. 18 """ 19 20 _value: int 21 22 @property 23 def value(self) -> int: 24 """Returns the value of the object. 25 26 Override this for custom behaviour.""" 27 28 return self._value 29 30 @value.setter 31 def value(self, new: int) -> None: 32 """Sets a new value.""" 33 34 self._value = new 35 36 def __repr__(self) -> str: 37 """Returns `{typename}(value={value})`. 38 39 We use this over the dataclasses one as that used `_value`, and it's 40 a bit ugly. 41 """ 42 43 return f"{type(self).__name__}(value={self.value})"
The base class for layout dimensions.
Each dimension has a value
property. This returns an integer,
and is essentially the meaning of the object.
46@dataclass(repr=False, frozen=True) 47class Static(Dimension): 48 """A static dimension. 49 50 This dimension is immutable, and the Layout will always leave it unchanged. 51 """ 52 53 _value: int = 0
A static dimension.
This dimension is immutable, and the Layout will always leave it unchanged.
56@dataclass(repr=False) 57class Relative(Dimension): 58 """A relative dimension. 59 60 This dimension has a scale attribute and bound method. Every time the `value` 61 is queried, `int(self.bound() * self.scale)` is returned. 62 63 When instantiated through `Layout.add_slot`, `bound` will default to either 64 the terminal's width or height, depending on which attribute it is applied to. 65 """ 66 67 _value = 0 68 scale: float 69 bound: Callable[[], int] 70 71 @property 72 def value(self) -> int: 73 """Calculates the new value for the dimension.""" 74 75 return int(self.bound() * self.scale) 76 77 @value.setter 78 def value(self, new: int) -> None: 79 """Disallows setting the value. 80 81 We can't inherit and then override a set-get property with a get one, so this 82 kind of patches that issue up. 83 """ 84 85 raise TypeError 86 87 def __repr__(self) -> str: 88 scale = self.scale 89 bound = self.bound 90 91 original = super().__repr__() 92 return original[:-1] + f", {scale=}, {bound=}" + original[-1]
A relative dimension.
This dimension has a scale attribute and bound method. Every time the value
is queried, int(self.bound() * self.scale)
is returned.
When instantiated through Layout.add_slot
, bound
will default to either
the terminal's width or height, depending on which attribute it is applied to.
95@dataclass 96class Auto(Dimension): 97 """An automatically calculated dimension. 98 99 The value of this dimension is overwritten on `Layout.apply`. 100 101 Generally, the way calculations are done is by looking at the available 102 size of the layout by subtracting the sum of all the non-auto dimensions 103 from the terminal's width or height, and dividing it by the number of 104 Auto-type dimensions in the current context. 105 106 An additional offset is applied to the first dimension (left-most or top-most) 107 of the context when the division has a remainder. 108 """ 109 110 _value = 0 111 112 def __repr__(self) -> str: 113 return f"{type(self).__name__}(value={self.value})"
An automatically calculated dimension.
The value of this dimension is overwritten on Layout.apply
.
Generally, the way calculations are done is by looking at the available size of the layout by subtracting the sum of all the non-auto dimensions from the terminal's width or height, and dividing it by the number of Auto-type dimensions in the current context.
An additional offset is applied to the first dimension (left-most or top-most) of the context when the division has a remainder.
116@dataclass 117class Slot: 118 """A slot within a layout. 119 120 A slot has a name, width & height, as well as some content. It's `apply` method 121 can be called to apply the slot's position & dimensions to its content. 122 """ 123 124 name: str 125 width: Dimension 126 height: Dimension 127 128 content: Widget | None = None 129 130 _restore_data: tuple[int, int, tuple[int, int]] | None = None 131 132 def apply(self, position: tuple[int, int]) -> None: 133 """Applies the given position & dimension to the content. 134 135 Args: 136 position: The position that this object resides in. Set as its content's `pos`. 137 """ 138 139 if self.content is None or self.width is None or self.height is None: 140 return 141 142 if self._restore_data is None: 143 self._restore_data = ( 144 self.content.width, 145 self.content.height, 146 self.content.pos, 147 ) 148 149 self.content.height = self.height.value 150 self.content.width = self.width.value 151 self.content.pos = position 152 153 def detach_content(self) -> None: 154 """Detaches content & restores its original state.""" 155 156 content = self.content 157 if content is None: 158 raise AttributeError(f"No content to detach in {self!r}.") 159 160 assert self._restore_data is not None 161 162 content.width, content.height, content.pos = self._restore_data 163 164 self.content = None 165 self._restore_data = None
A slot within a layout.
A slot has a name, width & height, as well as some content. It's apply
method
can be called to apply the slot's position & dimensions to its content.
132 def apply(self, position: tuple[int, int]) -> None: 133 """Applies the given position & dimension to the content. 134 135 Args: 136 position: The position that this object resides in. Set as its content's `pos`. 137 """ 138 139 if self.content is None or self.width is None or self.height is None: 140 return 141 142 if self._restore_data is None: 143 self._restore_data = ( 144 self.content.width, 145 self.content.height, 146 self.content.pos, 147 ) 148 149 self.content.height = self.height.value 150 self.content.width = self.width.value 151 self.content.pos = position
Applies the given position & dimension to the content.
Args
- position: The position that this object resides in. Set as its content's
pos
.
153 def detach_content(self) -> None: 154 """Detaches content & restores its original state.""" 155 156 content = self.content 157 if content is None: 158 raise AttributeError(f"No content to detach in {self!r}.") 159 160 assert self._restore_data is not None 161 162 content.width, content.height, content.pos = self._restore_data 163 164 self.content = None 165 self._restore_data = None
Detaches content & restores its original state.
When encountered in Layout.build_rows
, a new row will be started at the next element.
172class Layout: 173 """Defines a layout of Widgets, used by WindowManager. 174 175 Internally, it keeps track of a list of `Slot`. This list is then turned into a list 176 of rows, all containing slots. This is done either when the current row has run out 177 of the terminal's width, or `ROW_BREAK` is encountered. 178 """ 179 180 name: str 181 182 def __init__(self, name: str = "Layout") -> None: 183 self.name = name 184 self.slots: list[Slot] = [] 185 186 @property 187 def terminal(self) -> Terminal: 188 """Returns the current global terminal instance.""" 189 190 return get_terminal() 191 192 def _to_rows(self) -> list[list[Slot]]: 193 """Breaks `self.slots` into a list of list of slots. 194 195 The terminal's remaining width is kept track of, and when a slot doesn't have enough 196 space left it is pushed to a new row. Additionally, `ROW_BREAK` will force a new 197 row to be created, starting with the next slot. 198 """ 199 200 rows: list[list[Slot]] = [] 201 available = self.terminal.width 202 203 row: list[Slot] = [] 204 for slot in self.slots: 205 if available <= 0 or slot is ROW_BREAK: 206 rows.append(row) 207 208 row = [] 209 available = self.terminal.width - slot.width.value 210 211 if slot is ROW_BREAK: 212 continue 213 214 available -= slot.width.value 215 row.append(slot) 216 217 if len(row) > 0: 218 rows.append(row) 219 220 return rows 221 222 def build_rows(self) -> list[list[Slot]]: 223 """Builds a list of slot rows, breaking them & applying automatic dimensions. 224 225 Returns: 226 A list[list[Slot]], aka. a list of slot-rows. 227 """ 228 229 def _get_height(row: list[Slot]) -> int: 230 defined = list(filter(lambda slot: not isinstance(slot.height, Auto), row)) 231 232 if len(defined) > 0: 233 return max(slot.height.value for slot in defined) 234 235 return 0 236 237 def _calculate_widths(row: list[Slot]) -> tuple[int, int]: 238 defined: list[Slot] = list( 239 filter(lambda slt: not isinstance(slt.width, Auto), row) 240 ) 241 undefined = list(filter(lambda slt: slt not in defined, row)) 242 243 available = self.terminal.width - sum(slot.width.value for slot in defined) 244 245 return divmod(available, len(undefined) or 1) 246 247 rows = self._to_rows() 248 heights = [_get_height(row) for row in rows] 249 250 occupied = sum(heights) 251 auto_height, extra_height = divmod( 252 self.terminal.height - occupied, heights.count(0) or 1 253 ) 254 255 for row, height in zip(rows, heights): 256 height = height or auto_height 257 258 auto_width, extra_width = _calculate_widths(row) 259 for slot in row: 260 width = auto_width if isinstance(slot.width, Auto) else slot.width.value 261 262 if isinstance(slot.height, Auto): 263 slot.height.value = height + extra_height 264 extra_height = 0 265 266 if isinstance(slot.width, Auto): 267 slot.width.value = width + extra_width 268 extra_width = 0 269 270 return rows 271 272 def add_slot( 273 self, 274 name: str = "Slot", 275 *, 276 slot: Slot | None = None, 277 width: Dimension | int | float | None = None, 278 height: Dimension | int | float | None = None, 279 index: int = -1, 280 ) -> Slot: 281 """Adds a new slot to the layout. 282 283 Args: 284 name: The name of the slot. Used for display purposes. 285 slot: An already instantiated `Slot` instance. If this is given, 286 the additional width & height arguments will be ignored. 287 width: The width for the new slot. See below for special types. 288 height: The height for the new slot. See below for special types. 289 index: The index to add the new slot to. 290 291 Returns: 292 The just-added slot. 293 294 When defining dimensions, either width or height, some special value 295 types can be given: 296 - `Dimension`: Passed directly to the new slot. 297 - `None`: An `Auto` dimension is created with no value. 298 - `int`: A `Static` dimension is created with the given value. 299 - `float`: A `Relative` dimension is created with the given value as its 300 scale. Its `bound` attribute will default to the relevant part of the 301 terminal's size. 302 """ 303 304 if slot is None: 305 if width is None: 306 width = Auto() 307 308 elif isinstance(width, int): 309 width = Static(width) 310 311 elif isinstance(width, float): 312 width = Relative(width, bound=lambda: self.terminal.width) 313 314 if height is None: 315 height = Auto() 316 317 elif isinstance(height, int): 318 height = Static(height) 319 320 elif isinstance(height, float): 321 height = Relative(height, bound=lambda: self.terminal.height) 322 323 slot = Slot(name, width=width, height=height) 324 325 if index == -1: 326 self.slots.append(slot) 327 return slot 328 329 self.slots.insert(index, slot) 330 331 return slot 332 333 def add_break(self, *, index: int = -1) -> None: 334 """Adds `ROW_BREAK` to the given index. 335 336 This special slot is ignored for all intents and purposes, other than when 337 breaking the slots into rows. In that context, when encountered, the current 338 row is deemed completed, and the next slot will go into a new row list. 339 """ 340 341 self.add_slot(slot=ROW_BREAK, index=index) 342 343 def assign(self, widget: Widget, *, index: int = -1, apply: bool = True) -> None: 344 """Assigns a widget to the slot at the specified index. 345 346 Args: 347 widget: The widget to assign. 348 index: The target slot's index. 349 apply: If set, `apply` will be called once the widget has been assigned. 350 """ 351 352 slots = [slot for slot in self.slots if slot is not ROW_BREAK] 353 if index > len(slots) - 1: 354 return 355 356 slot = slots[index] 357 358 slot.content = widget 359 360 if apply: 361 self.apply() 362 363 def apply(self) -> None: 364 """Applies the layout to each slot.""" 365 366 position = list(self.terminal.origin) 367 for row in self.build_rows(): 368 position[0] = 1 369 370 for slot in row: 371 slot.apply((position[0], position[1])) 372 373 position[0] += slot.width.value 374 375 position[1] += max(slot.height.value for slot in row) 376 377 def __getattr__(self, attr: str) -> Slot: 378 """Gets a slot by its (slugified) name.""" 379 380 def _snakeify(name: str) -> str: 381 return name.lower().replace(" ", "_") 382 383 for slot in self.slots: 384 if _snakeify(slot.name) == attr: 385 return slot 386 387 raise AttributeError(f"Slot with name {attr!r} could not be found.")
Defines a layout of Widgets, used by WindowManager.
Internally, it keeps track of a list of Slot
. This list is then turned into a list
of rows, all containing slots. This is done either when the current row has run out
of the terminal's width, or ROW_BREAK
is encountered.
222 def build_rows(self) -> list[list[Slot]]: 223 """Builds a list of slot rows, breaking them & applying automatic dimensions. 224 225 Returns: 226 A list[list[Slot]], aka. a list of slot-rows. 227 """ 228 229 def _get_height(row: list[Slot]) -> int: 230 defined = list(filter(lambda slot: not isinstance(slot.height, Auto), row)) 231 232 if len(defined) > 0: 233 return max(slot.height.value for slot in defined) 234 235 return 0 236 237 def _calculate_widths(row: list[Slot]) -> tuple[int, int]: 238 defined: list[Slot] = list( 239 filter(lambda slt: not isinstance(slt.width, Auto), row) 240 ) 241 undefined = list(filter(lambda slt: slt not in defined, row)) 242 243 available = self.terminal.width - sum(slot.width.value for slot in defined) 244 245 return divmod(available, len(undefined) or 1) 246 247 rows = self._to_rows() 248 heights = [_get_height(row) for row in rows] 249 250 occupied = sum(heights) 251 auto_height, extra_height = divmod( 252 self.terminal.height - occupied, heights.count(0) or 1 253 ) 254 255 for row, height in zip(rows, heights): 256 height = height or auto_height 257 258 auto_width, extra_width = _calculate_widths(row) 259 for slot in row: 260 width = auto_width if isinstance(slot.width, Auto) else slot.width.value 261 262 if isinstance(slot.height, Auto): 263 slot.height.value = height + extra_height 264 extra_height = 0 265 266 if isinstance(slot.width, Auto): 267 slot.width.value = width + extra_width 268 extra_width = 0 269 270 return rows
Builds a list of slot rows, breaking them & applying automatic dimensions.
Returns
A list[list[Slot]], aka. a list of slot-rows.
272 def add_slot( 273 self, 274 name: str = "Slot", 275 *, 276 slot: Slot | None = None, 277 width: Dimension | int | float | None = None, 278 height: Dimension | int | float | None = None, 279 index: int = -1, 280 ) -> Slot: 281 """Adds a new slot to the layout. 282 283 Args: 284 name: The name of the slot. Used for display purposes. 285 slot: An already instantiated `Slot` instance. If this is given, 286 the additional width & height arguments will be ignored. 287 width: The width for the new slot. See below for special types. 288 height: The height for the new slot. See below for special types. 289 index: The index to add the new slot to. 290 291 Returns: 292 The just-added slot. 293 294 When defining dimensions, either width or height, some special value 295 types can be given: 296 - `Dimension`: Passed directly to the new slot. 297 - `None`: An `Auto` dimension is created with no value. 298 - `int`: A `Static` dimension is created with the given value. 299 - `float`: A `Relative` dimension is created with the given value as its 300 scale. Its `bound` attribute will default to the relevant part of the 301 terminal's size. 302 """ 303 304 if slot is None: 305 if width is None: 306 width = Auto() 307 308 elif isinstance(width, int): 309 width = Static(width) 310 311 elif isinstance(width, float): 312 width = Relative(width, bound=lambda: self.terminal.width) 313 314 if height is None: 315 height = Auto() 316 317 elif isinstance(height, int): 318 height = Static(height) 319 320 elif isinstance(height, float): 321 height = Relative(height, bound=lambda: self.terminal.height) 322 323 slot = Slot(name, width=width, height=height) 324 325 if index == -1: 326 self.slots.append(slot) 327 return slot 328 329 self.slots.insert(index, slot) 330 331 return slot
Adds a new slot to the layout.
Args
- name: The name of the slot. Used for display purposes.
- slot: An already instantiated
Slot
instance. If this is given, the additional width & height arguments will be ignored. - width: The width for the new slot. See below for special types.
- height: The height for the new slot. See below for special types.
- index: The index to add the new slot to.
Returns
The just-added slot.
When defining dimensions, either width or height, some special value types can be given:
Dimension
: Passed directly to the new slot.None
: AnAuto
dimension is created with no value.int
: AStatic
dimension is created with the given value.float
: ARelative
dimension is created with the given value as its scale. Itsbound
attribute will default to the relevant part of the terminal's size.
333 def add_break(self, *, index: int = -1) -> None: 334 """Adds `ROW_BREAK` to the given index. 335 336 This special slot is ignored for all intents and purposes, other than when 337 breaking the slots into rows. In that context, when encountered, the current 338 row is deemed completed, and the next slot will go into a new row list. 339 """ 340 341 self.add_slot(slot=ROW_BREAK, index=index)
Adds ROW_BREAK
to the given index.
This special slot is ignored for all intents and purposes, other than when breaking the slots into rows. In that context, when encountered, the current row is deemed completed, and the next slot will go into a new row list.
343 def assign(self, widget: Widget, *, index: int = -1, apply: bool = True) -> None: 344 """Assigns a widget to the slot at the specified index. 345 346 Args: 347 widget: The widget to assign. 348 index: The target slot's index. 349 apply: If set, `apply` will be called once the widget has been assigned. 350 """ 351 352 slots = [slot for slot in self.slots if slot is not ROW_BREAK] 353 if index > len(slots) - 1: 354 return 355 356 slot = slots[index] 357 358 slot.content = widget 359 360 if apply: 361 self.apply()
Assigns a widget to the slot at the specified index.
Args
- widget: The widget to assign.
- index: The target slot's index.
- apply: If set,
apply
will be called once the widget has been assigned.
363 def apply(self) -> None: 364 """Applies the layout to each slot.""" 365 366 position = list(self.terminal.origin) 367 for row in self.build_rows(): 368 position[0] = 1 369 370 for slot in row: 371 slot.apply((position[0], position[1])) 372 373 position[0] += slot.width.value 374 375 position[1] += max(slot.height.value for slot in row)
Applies the layout to each slot.