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