1""" 
    2Layout dimensions are used to give the minimum, maximum and preferred 
    3dimensions for containers and controls. 
    4""" 
    5 
    6from __future__ import annotations 
    7 
    8from typing import TYPE_CHECKING, Any, Callable, Union 
    9 
    10__all__ = [ 
    11    "Dimension", 
    12    "D", 
    13    "sum_layout_dimensions", 
    14    "max_layout_dimensions", 
    15    "AnyDimension", 
    16    "to_dimension", 
    17    "is_dimension", 
    18] 
    19 
    20if TYPE_CHECKING: 
    21    from typing_extensions import TypeGuard 
    22 
    23 
    24class Dimension: 
    25    """ 
    26    Specified dimension (width/height) of a user control or window. 
    27 
    28    The layout engine tries to honor the preferred size. If that is not 
    29    possible, because the terminal is larger or smaller, it tries to keep in 
    30    between min and max. 
    31 
    32    :param min: Minimum size. 
    33    :param max: Maximum size. 
    34    :param weight: For a VSplit/HSplit, the actual size will be determined 
    35                   by taking the proportion of weights from all the children. 
    36                   E.g. When there are two children, one with a weight of 1, 
    37                   and the other with a weight of 2, the second will always be 
    38                   twice as big as the first, if the min/max values allow it. 
    39    :param preferred: Preferred size. 
    40    """ 
    41 
    42    def __init__( 
    43        self, 
    44        min: int | None = None, 
    45        max: int | None = None, 
    46        weight: int | None = None, 
    47        preferred: int | None = None, 
    48    ) -> None: 
    49        if weight is not None: 
    50            assert weight >= 0  # Also cannot be a float. 
    51 
    52        assert min is None or min >= 0 
    53        assert max is None or max >= 0 
    54        assert preferred is None or preferred >= 0 
    55 
    56        self.min_specified = min is not None 
    57        self.max_specified = max is not None 
    58        self.preferred_specified = preferred is not None 
    59        self.weight_specified = weight is not None 
    60 
    61        if min is None: 
    62            min = 0  # Smallest possible value. 
    63        if max is None:  # 0-values are allowed, so use "is None" 
    64            max = 1000**10  # Something huge. 
    65        if preferred is None: 
    66            preferred = min 
    67        if weight is None: 
    68            weight = 1 
    69 
    70        self.min = min 
    71        self.max = max 
    72        self.preferred = preferred 
    73        self.weight = weight 
    74 
    75        # Don't allow situations where max < min. (This would be a bug.) 
    76        if max < min: 
    77            raise ValueError("Invalid Dimension: max < min.") 
    78 
    79        # Make sure that the 'preferred' size is always in the min..max range. 
    80        if self.preferred < self.min: 
    81            self.preferred = self.min 
    82 
    83        if self.preferred > self.max: 
    84            self.preferred = self.max 
    85 
    86    @classmethod 
    87    def exact(cls, amount: int) -> Dimension: 
    88        """ 
    89        Return a :class:`.Dimension` with an exact size. (min, max and 
    90        preferred set to ``amount``). 
    91        """ 
    92        return cls(min=amount, max=amount, preferred=amount) 
    93 
    94    @classmethod 
    95    def zero(cls) -> Dimension: 
    96        """ 
    97        Create a dimension that represents a zero size. (Used for 'invisible' 
    98        controls.) 
    99        """ 
    100        return cls.exact(amount=0) 
    101 
    102    def __repr__(self) -> str: 
    103        fields = [] 
    104        if self.min_specified: 
    105            fields.append(f"min={self.min!r}") 
    106        if self.max_specified: 
    107            fields.append(f"max={self.max!r}") 
    108        if self.preferred_specified: 
    109            fields.append(f"preferred={self.preferred!r}") 
    110        if self.weight_specified: 
    111            fields.append(f"weight={self.weight!r}") 
    112 
    113        return "Dimension({})".format(", ".join(fields)) 
    114 
    115 
    116def sum_layout_dimensions(dimensions: list[Dimension]) -> Dimension: 
    117    """ 
    118    Sum a list of :class:`.Dimension` instances. 
    119    """ 
    120    min = sum(d.min for d in dimensions) 
    121    max = sum(d.max for d in dimensions) 
    122    preferred = sum(d.preferred for d in dimensions) 
    123 
    124    return Dimension(min=min, max=max, preferred=preferred) 
    125 
    126 
    127def max_layout_dimensions(dimensions: list[Dimension]) -> Dimension: 
    128    """ 
    129    Take the maximum of a list of :class:`.Dimension` instances. 
    130    Used when we have a HSplit/VSplit, and we want to get the best width/height.) 
    131    """ 
    132    if not len(dimensions): 
    133        return Dimension.zero() 
    134 
    135    # If all dimensions are size zero. Return zero. 
    136    # (This is important for HSplit/VSplit, to report the right values to their 
    137    # parent when all children are invisible.) 
    138    if all(d.preferred == 0 and d.max == 0 for d in dimensions): 
    139        return Dimension.zero() 
    140 
    141    # Ignore empty dimensions. (They should not reduce the size of others.) 
    142    dimensions = [d for d in dimensions if d.preferred != 0 and d.max != 0] 
    143 
    144    if dimensions: 
    145        # Take the highest minimum dimension. 
    146        min_ = max(d.min for d in dimensions) 
    147 
    148        # For the maximum, we would prefer not to go larger than then smallest 
    149        # 'max' value, unless other dimensions have a bigger preferred value. 
    150        # This seems to work best: 
    151        #  - We don't want that a widget with a small height in a VSplit would 
    152        #    shrink other widgets in the split. 
    153        # If it doesn't work well enough, then it's up to the UI designer to 
    154        # explicitly pass dimensions. 
    155        max_ = min(d.max for d in dimensions) 
    156        max_ = max(max_, max(d.preferred for d in dimensions)) 
    157 
    158        # Make sure that min>=max. In some scenarios, when certain min..max 
    159        # ranges don't have any overlap, we can end up in such an impossible 
    160        # situation. In that case, give priority to the max value. 
    161        # E.g. taking (1..5) and (8..9) would return (8..5). Instead take (8..8). 
    162        if min_ > max_: 
    163            max_ = min_ 
    164 
    165        preferred = max(d.preferred for d in dimensions) 
    166 
    167        return Dimension(min=min_, max=max_, preferred=preferred) 
    168    else: 
    169        return Dimension() 
    170 
    171 
    172# Anything that can be converted to a dimension. 
    173AnyDimension = Union[ 
    174    None,  # None is a valid dimension that will fit anything. 
    175    int, 
    176    Dimension, 
    177    # Callable[[], 'AnyDimension']  # Recursive definition not supported by mypy. 
    178    Callable[[], Any], 
    179] 
    180 
    181 
    182def to_dimension(value: AnyDimension) -> Dimension: 
    183    """ 
    184    Turn the given object into a `Dimension` object. 
    185    """ 
    186    if value is None: 
    187        return Dimension() 
    188    if isinstance(value, int): 
    189        return Dimension.exact(value) 
    190    if isinstance(value, Dimension): 
    191        return value 
    192    if callable(value): 
    193        return to_dimension(value()) 
    194 
    195    raise ValueError("Not an integer or Dimension object.") 
    196 
    197 
    198def is_dimension(value: object) -> TypeGuard[AnyDimension]: 
    199    """ 
    200    Test whether the given value could be a valid dimension. 
    201    (For usage in an assertion. It's not guaranteed in case of a callable.) 
    202    """ 
    203    if value is None: 
    204        return True 
    205    if callable(value): 
    206        return True  # Assume it's a callable that doesn't take arguments. 
    207    if isinstance(value, (int, Dimension)): 
    208        return True 
    209    return False 
    210 
    211 
    212# Common alias. 
    213D = Dimension 
    214 
    215# For backward-compatibility. 
    216LayoutDimension = Dimension