mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
box model
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
.list-item {
|
||||
height: 8;
|
||||
margin: 2;
|
||||
margin: 1 2;
|
||||
}
|
||||
|
||||
#child1 {
|
||||
|
||||
@@ -208,9 +208,7 @@ class Compositor:
|
||||
widget, region.size, scroll
|
||||
)
|
||||
widgets.update(arranged_widgets)
|
||||
placements = [placement.apply_margin() for placement in placements]
|
||||
placements.sort(key=attrgetter("order"))
|
||||
log("---", placements)
|
||||
placements = sorted(placements, key=attrgetter("order"))
|
||||
|
||||
for sub_region, sub_widget, z in placements:
|
||||
total_region = total_region.union(sub_region)
|
||||
|
||||
@@ -189,14 +189,15 @@ class Edges(NamedTuple):
|
||||
if left[0]:
|
||||
yield "left", left
|
||||
|
||||
def spacing(self) -> tuple[int, int, int, int]:
|
||||
@property
|
||||
def spacing(self) -> Spacing:
|
||||
"""Get spacing created by borders.
|
||||
|
||||
Returns:
|
||||
tuple[int, int, int, int]: Spacing for top, right, bottom, and left.
|
||||
"""
|
||||
top, right, bottom, left = self
|
||||
return (
|
||||
return Spacing(
|
||||
1 if top[0] else 0,
|
||||
1 if right[0] else 0,
|
||||
1 if bottom[0] else 0,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum, unique
|
||||
from functools import lru_cache
|
||||
import re
|
||||
from typing import Iterable, NamedTuple, TYPE_CHECKING
|
||||
|
||||
@@ -130,6 +131,7 @@ class Scalar(NamedTuple):
|
||||
scalar = cls(float(value), SYMBOL_UNIT[unit_name or ""], percent_unit)
|
||||
return scalar
|
||||
|
||||
@lru_cache(maxsize=4096)
|
||||
def resolve_dimension(
|
||||
self, size: tuple[int, int], viewport: tuple[int, int]
|
||||
) -> int:
|
||||
|
||||
@@ -324,49 +324,60 @@ class StylesBase(ABC):
|
||||
terminal_size (Size): The size of the terminal.
|
||||
|
||||
Returns:
|
||||
tuple[Size, Spacing]: A tuple with the size of the renderable area, and the space around it.
|
||||
tuple[Size, Spacing]: A tuple with the size of the content area and margin.
|
||||
"""
|
||||
width, height = container_size
|
||||
has_rule = self.has_rule
|
||||
if styles.width:
|
||||
width = styles.width.resolve_dimension(container_size, parent_size)
|
||||
width, height = container_size
|
||||
|
||||
if styles.min_width:
|
||||
min_width = styles.min_width.resolve_dimension(container_size, parent_size)
|
||||
if has_rule("width"):
|
||||
width = self.width.resolve_dimension(container_size, parent_size)
|
||||
else:
|
||||
width = max(0, width - self.margin.width)
|
||||
|
||||
if self.min_width:
|
||||
min_width = self.min_width.resolve_dimension(container_size, parent_size)
|
||||
width = max(width, min_width)
|
||||
|
||||
if styles.max_width:
|
||||
max_width = styles.max_width.resolve_dimension(container_size, parent_size)
|
||||
if self.max_width:
|
||||
max_width = self.max_width.resolve_dimension(container_size, parent_size)
|
||||
width = min(width, max_width)
|
||||
|
||||
if styles.height:
|
||||
height = styles.height.resolve_dimension(container_size, parent_size)
|
||||
if has_rule("height"):
|
||||
height = self.height.resolve_dimension(container_size, parent_size)
|
||||
else:
|
||||
height = max(0, height - self.margin.height)
|
||||
|
||||
if styles.min_height:
|
||||
min_height = styles.min_height.resolve_dimension(
|
||||
container_size, parent_size
|
||||
)
|
||||
if self.min_height:
|
||||
min_height = self.min_height.resolve_dimension(container_size, parent_size)
|
||||
height = max(height, min_height)
|
||||
|
||||
if styles.max_height:
|
||||
max_height = styles.max_height.resolve_dimension(
|
||||
container_size, parent_size
|
||||
)
|
||||
if self.max_height:
|
||||
max_height = self.max_height.resolve_dimension(container_size, parent_size)
|
||||
height = min(width, max_height)
|
||||
|
||||
# TODO: box sizing
|
||||
|
||||
size = Size(width, height)
|
||||
margin = Spacing(0, 0, 0, 0)
|
||||
|
||||
spacing = Spacing(0, 0, 0, 0)
|
||||
if has_rule("padding"):
|
||||
spacing += styles.padding
|
||||
if has_rule("border"):
|
||||
spacing += styles.border.spacing
|
||||
if has_rule("margin"):
|
||||
spacing += styles.margin
|
||||
if self.box_sizing == "content-box":
|
||||
|
||||
return size, spacing
|
||||
if has_rule("padding"):
|
||||
size += self.padding
|
||||
if has_rule("border"):
|
||||
size += self.border.spacing.totals
|
||||
if has_rule("margin"):
|
||||
margin = self.margin
|
||||
|
||||
else: # border-box
|
||||
if has_rule("padding"):
|
||||
size -= self.padding
|
||||
if has_rule("border"):
|
||||
size -= self.border.spacing.totals
|
||||
if has_rule("margin"):
|
||||
margin = self.margin
|
||||
|
||||
return size, margin
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
|
||||
@@ -111,6 +111,11 @@ class Size(NamedTuple):
|
||||
"""A Size is Falsy if it has area 0."""
|
||||
return self.width * self.height != 0
|
||||
|
||||
@property
|
||||
def clamped(self) -> Size:
|
||||
width, height = self
|
||||
return Size(max(0, width), max(0, height))
|
||||
|
||||
@property
|
||||
def area(self) -> int:
|
||||
"""Get the area of the size.
|
||||
@@ -127,17 +132,22 @@ class Size(NamedTuple):
|
||||
return Region(0, 0, width, height)
|
||||
|
||||
def __add__(self, other: object) -> Size:
|
||||
if isinstance(other, Spacing):
|
||||
width, height = self
|
||||
other_width, other_height = other.totals
|
||||
return Size(width + other_width, height + other_height)
|
||||
|
||||
if isinstance(other, tuple):
|
||||
width, height = self
|
||||
width2, height2 = other
|
||||
return Size(width + width2, height + height2)
|
||||
return Size(max(0, width + width2), max(0, height + height2))
|
||||
return NotImplemented
|
||||
|
||||
def __sub__(self, other: object) -> Size:
|
||||
if isinstance(other, tuple):
|
||||
width, height = self
|
||||
width2, height2 = other
|
||||
return Size(width - width2, height - height2)
|
||||
return Size(max(0, width - width2), max(0, height - height2))
|
||||
return NotImplemented
|
||||
|
||||
def contains(self, x: int, y: int) -> bool:
|
||||
@@ -515,6 +525,11 @@ class Spacing(NamedTuple):
|
||||
"""Bottom right space."""
|
||||
return (self.right, self.bottom)
|
||||
|
||||
@property
|
||||
def totals(self) -> tuple[int, int]:
|
||||
top, right, bottom, left = self
|
||||
return (left + right, top + bottom)
|
||||
|
||||
@property
|
||||
def css(self) -> str:
|
||||
"""Gets a string containing the spacing in CSS format."""
|
||||
|
||||
@@ -23,24 +23,23 @@ class VerticalLayout(Layout):
|
||||
placements: list[WidgetPlacement] = []
|
||||
add_placement = placements.append
|
||||
|
||||
x = y = 0
|
||||
y = max_width = max_height = 0
|
||||
parent_size = parent.size
|
||||
|
||||
for widget in parent.children:
|
||||
styles = widget.styles
|
||||
render_width, render_height = parent.size
|
||||
|
||||
render_size, spacing = styles.get_box_model(size, parent_size)
|
||||
(content_width, content_height), margin = widget.styles.get_box_model(
|
||||
size, parent_size
|
||||
)
|
||||
|
||||
# TODO:
|
||||
|
||||
if styles.has_rule("width"):
|
||||
render_width = int(styles.width.resolve_dimension(size, parent_size))
|
||||
if styles.has_rule("height"):
|
||||
render_height = int(styles.height.resolve_dimension(size, parent_size))
|
||||
region = Region(x, y, render_width, render_height)
|
||||
region = Region(margin.left, y + margin.top, content_width, content_height)
|
||||
max_width = max(max_width, content_width + margin.width)
|
||||
add_placement(WidgetPlacement(region, widget, 0))
|
||||
y += render_height
|
||||
y += region.y_max
|
||||
max_height = y + margin.bottom
|
||||
|
||||
total_region = Region(0, 0, max_width, max_height)
|
||||
add_placement(WidgetPlacement(total_region, None, 0))
|
||||
|
||||
for placement in placements:
|
||||
log(placement)
|
||||
|
||||
Reference in New Issue
Block a user