box model calculations

This commit is contained in:
Will McGugan
2022-04-20 17:00:52 +01:00
parent 3869eb7463
commit 2c85bd97bd
5 changed files with 36 additions and 21 deletions

View File

@@ -8,16 +8,18 @@ from .css.styles import StylesBase
class BoxModel(NamedTuple):
content: Size
margin: Spacing
"""The result of `get_box_model`."""
size: Size # Content + padding + border
margin: Spacing # Additional margin
def get_box_model(
styles: StylesBase,
container_size: Size,
parent_size: Size,
get_auto_width: Callable[[Size, Size], int],
get_auto_height: Callable[[Size, Size], int],
get_auto_width: Callable[[Size, Size, Spacing], int],
get_auto_height: Callable[[Size, Size, Spacing], int],
) -> BoxModel:
"""Resolve the box model for this Styles.
@@ -35,19 +37,18 @@ def get_box_model(
has_rule = styles.has_rule
width, height = container_size
extra = Size(0, 0)
gutter = Spacing(0, 0, 0, 0)
if styles.box_sizing == "content-box":
if has_rule("padding"):
extra += styles.padding.totals
extra += styles.border.spacing.totals
gutter += styles.padding
gutter += styles.border.spacing
else: # border-box
extra -= styles.border.spacing.totals
gutter -= styles.border.spacing
if has_rule("width"):
if styles.width.is_auto:
# extra_width = styles.padding.width + styles.border.spacing.width
width = get_auto_width(container_size, parent_size)
width = get_auto_width(container_size, parent_size, gutter)
else:
width = styles.width.resolve_dimension(container_size, parent_size)
else:
@@ -63,8 +64,7 @@ def get_box_model(
if has_rule("height"):
if styles.height.is_auto:
extra_height = styles.padding.height + styles.border.spacing.height
height = get_auto_height(container_size, parent_size) + extra_height
height = get_auto_height(container_size, parent_size, gutter)
else:
height = styles.height.resolve_dimension(container_size, parent_size)
else:
@@ -78,7 +78,7 @@ def get_box_model(
max_height = styles.max_height.resolve_dimension(container_size, parent_size)
height = min(width, max_height)
size = Size(width, height) + extra
size = Size(width, height) + gutter.totals
margin = styles.margin if has_rule("margin") else Spacing(0, 0, 0, 0)
return BoxModel(size, margin)

View File

@@ -653,5 +653,14 @@ class Spacing(NamedTuple):
)
return NotImplemented
def __sub__(self, other: object) -> Spacing:
if isinstance(other, tuple):
top1, right1, bottom1, left1 = self
top2, right2, bottom2, left2 = other
return Spacing(
top1 - top2, right1 - right2, bottom1 - bottom2, left1 - left2
)
return NotImplemented
NULL_OFFSET = Offset(0, 0)

View File

@@ -39,7 +39,7 @@ class HorizontalLayout(Layout):
x = box_models[0].margin.left
for widget, box_model, margin in zip(parent.children, box_models, margins):
content_width, content_height = box_model.content
content_width, content_height = box_model.size
offset_y = widget.styles.align_height(content_height, parent_size.height)
region = Region(x, offset_y, content_width, content_height)
max_height = max(max_height, content_height)

View File

@@ -40,7 +40,7 @@ class VerticalLayout(Layout):
y = box_models[0].margin.top
for widget, box_model, margin in zip(parent.children, box_models, margins):
content_width, content_height = box_model.content
content_width, content_height = box_model.size
offset_x = widget.styles.align_width(content_width, parent_size.width)
region = Region(offset_x, y, content_width, content_height)
max_height = max(max_height, content_height)

View File

@@ -107,7 +107,7 @@ class Widget(DOMNode):
show_vertical_scrollbar = Reactive(False, layout=True)
show_horizontal_scrollbar = Reactive(False, layout=True)
def get_box_model(self, container_size, parent_size) -> BoxModel:
def get_box_model(self, container_size: Size, parent_size: Size) -> BoxModel:
box_model = get_box_model(
self.styles,
container_size,
@@ -118,14 +118,18 @@ class Widget(DOMNode):
self.log(self, self.styles.padding, self.styles.border.spacing)
return box_model
def get_content_width(self, container_size: Size, parent_size: Size) -> int:
def get_content_width(
self, container_size: Size, parent_size: Size, gutter: Spacing
) -> int:
console = self.app.console
renderable = self.render()
measurement = Measurement.get(console, console.options, renderable)
return measurement.maximum
def get_content_height(self, container_size: Size, parent_size: Size) -> int:
return container_size.height
def get_content_height(
self, container_size: Size, parent_size: Size, gutter: Spacing
) -> int:
return container_size.height - gutter.height
async def watch_scroll_x(self, new_value: float) -> None:
self.horizontal_scrollbar.position = int(new_value)
@@ -260,7 +264,7 @@ class Widget(DOMNode):
self.scroll_target_x = x
if x != self.scroll_x:
self.animate(
"scroll_x", self.scroll_target_x, speed=80, easing="lineary"
"scroll_x", self.scroll_target_x, speed=80, easing="out_cubic"
)
scrolled_x = True
if y is not None:
@@ -537,7 +541,9 @@ class Widget(DOMNode):
"""Render all lines."""
width, height = self.size
renderable = self.render_styled()
options = self.console.options.update_dimensions(width, height)
options = self.console.options.update_dimensions(width, height).update(
highlight=False
)
lines = self.console.render_lines(renderable, options)
self._render_cache = RenderCache(self.size, lines)
self._dirty_regions.clear()