Spaces:
Sleeping
Sleeping
first-space
/
first-space-venv
/lib
/python3.12
/site-packages
/prompt_toolkit
/layout
/dimension.py
""" | |
Layout dimensions are used to give the minimum, maximum and preferred | |
dimensions for containers and controls. | |
""" | |
from __future__ import annotations | |
from typing import TYPE_CHECKING, Any, Callable, Union | |
__all__ = [ | |
"Dimension", | |
"D", | |
"sum_layout_dimensions", | |
"max_layout_dimensions", | |
"AnyDimension", | |
"to_dimension", | |
"is_dimension", | |
] | |
if TYPE_CHECKING: | |
from typing_extensions import TypeGuard | |
class Dimension: | |
""" | |
Specified dimension (width/height) of a user control or window. | |
The layout engine tries to honor the preferred size. If that is not | |
possible, because the terminal is larger or smaller, it tries to keep in | |
between min and max. | |
:param min: Minimum size. | |
:param max: Maximum size. | |
:param weight: For a VSplit/HSplit, the actual size will be determined | |
by taking the proportion of weights from all the children. | |
E.g. When there are two children, one with a weight of 1, | |
and the other with a weight of 2, the second will always be | |
twice as big as the first, if the min/max values allow it. | |
:param preferred: Preferred size. | |
""" | |
def __init__( | |
self, | |
min: int | None = None, | |
max: int | None = None, | |
weight: int | None = None, | |
preferred: int | None = None, | |
) -> None: | |
if weight is not None: | |
assert weight >= 0 # Also cannot be a float. | |
assert min is None or min >= 0 | |
assert max is None or max >= 0 | |
assert preferred is None or preferred >= 0 | |
self.min_specified = min is not None | |
self.max_specified = max is not None | |
self.preferred_specified = preferred is not None | |
self.weight_specified = weight is not None | |
if min is None: | |
min = 0 # Smallest possible value. | |
if max is None: # 0-values are allowed, so use "is None" | |
max = 1000**10 # Something huge. | |
if preferred is None: | |
preferred = min | |
if weight is None: | |
weight = 1 | |
self.min = min | |
self.max = max | |
self.preferred = preferred | |
self.weight = weight | |
# Don't allow situations where max < min. (This would be a bug.) | |
if max < min: | |
raise ValueError("Invalid Dimension: max < min.") | |
# Make sure that the 'preferred' size is always in the min..max range. | |
if self.preferred < self.min: | |
self.preferred = self.min | |
if self.preferred > self.max: | |
self.preferred = self.max | |
def exact(cls, amount: int) -> Dimension: | |
""" | |
Return a :class:`.Dimension` with an exact size. (min, max and | |
preferred set to ``amount``). | |
""" | |
return cls(min=amount, max=amount, preferred=amount) | |
def zero(cls) -> Dimension: | |
""" | |
Create a dimension that represents a zero size. (Used for 'invisible' | |
controls.) | |
""" | |
return cls.exact(amount=0) | |
def is_zero(self) -> bool: | |
"True if this `Dimension` represents a zero size." | |
return self.preferred == 0 or self.max == 0 | |
def __repr__(self) -> str: | |
fields = [] | |
if self.min_specified: | |
fields.append(f"min={self.min!r}") | |
if self.max_specified: | |
fields.append(f"max={self.max!r}") | |
if self.preferred_specified: | |
fields.append(f"preferred={self.preferred!r}") | |
if self.weight_specified: | |
fields.append(f"weight={self.weight!r}") | |
return "Dimension({})".format(", ".join(fields)) | |
def sum_layout_dimensions(dimensions: list[Dimension]) -> Dimension: | |
""" | |
Sum a list of :class:`.Dimension` instances. | |
""" | |
min = sum(d.min for d in dimensions) | |
max = sum(d.max for d in dimensions) | |
preferred = sum(d.preferred for d in dimensions) | |
return Dimension(min=min, max=max, preferred=preferred) | |
def max_layout_dimensions(dimensions: list[Dimension]) -> Dimension: | |
""" | |
Take the maximum of a list of :class:`.Dimension` instances. | |
Used when we have a HSplit/VSplit, and we want to get the best width/height.) | |
""" | |
if not len(dimensions): | |
return Dimension.zero() | |
# If all dimensions are size zero. Return zero. | |
# (This is important for HSplit/VSplit, to report the right values to their | |
# parent when all children are invisible.) | |
if all(d.is_zero() for d in dimensions): | |
return dimensions[0] | |
# Ignore empty dimensions. (They should not reduce the size of others.) | |
dimensions = [d for d in dimensions if not d.is_zero()] | |
if dimensions: | |
# Take the highest minimum dimension. | |
min_ = max(d.min for d in dimensions) | |
# For the maximum, we would prefer not to go larger than then smallest | |
# 'max' value, unless other dimensions have a bigger preferred value. | |
# This seems to work best: | |
# - We don't want that a widget with a small height in a VSplit would | |
# shrink other widgets in the split. | |
# If it doesn't work well enough, then it's up to the UI designer to | |
# explicitly pass dimensions. | |
max_ = min(d.max for d in dimensions) | |
max_ = max(max_, max(d.preferred for d in dimensions)) | |
# Make sure that min>=max. In some scenarios, when certain min..max | |
# ranges don't have any overlap, we can end up in such an impossible | |
# situation. In that case, give priority to the max value. | |
# E.g. taking (1..5) and (8..9) would return (8..5). Instead take (8..8). | |
if min_ > max_: | |
max_ = min_ | |
preferred = max(d.preferred for d in dimensions) | |
return Dimension(min=min_, max=max_, preferred=preferred) | |
else: | |
return Dimension() | |
# Anything that can be converted to a dimension. | |
AnyDimension = Union[ | |
None, # None is a valid dimension that will fit anything. | |
int, | |
Dimension, | |
# Callable[[], 'AnyDimension'] # Recursive definition not supported by mypy. | |
Callable[[], Any], | |
] | |
def to_dimension(value: AnyDimension) -> Dimension: | |
""" | |
Turn the given object into a `Dimension` object. | |
""" | |
if value is None: | |
return Dimension() | |
if isinstance(value, int): | |
return Dimension.exact(value) | |
if isinstance(value, Dimension): | |
return value | |
if callable(value): | |
return to_dimension(value()) | |
raise ValueError("Not an integer or Dimension object.") | |
def is_dimension(value: object) -> TypeGuard[AnyDimension]: | |
""" | |
Test whether the given value could be a valid dimension. | |
(For usage in an assertion. It's not guaranteed in case of a callable.) | |
""" | |
if value is None: | |
return True | |
if callable(value): | |
return True # Assume it's a callable that doesn't take arguments. | |
if isinstance(value, (int, Dimension)): | |
return True | |
return False | |
# Common alias. | |
D = Dimension | |
# For backward-compatibility. | |
LayoutDimension = Dimension | |