box model

This commit is contained in:
Will McGugan
2022-03-05 10:42:52 +00:00
parent ef99069cf4
commit 380254a0aa
7 changed files with 72 additions and 46 deletions

View File

@@ -6,7 +6,7 @@
.list-item {
height: 8;
margin: 2;
margin: 1 2;
}
#child1 {

View File

@@ -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)

View File

@@ -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,

View File

@@ -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:

View File

@@ -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

View File

@@ -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."""

View File

@@ -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)