fixes for layout

This commit is contained in:
Will McGugan
2022-05-19 17:48:39 +01:00
parent b42d33c48d
commit 875d67a91d
9 changed files with 111 additions and 47 deletions

View File

@@ -69,7 +69,7 @@ App > Screen {
color: $text-background;
background: $background;
layout: vertical;
overflow-y: scroll;
overflow-y: scroll;
}
@@ -114,11 +114,11 @@ TweetHeader {
}
TweetBody {
width: 100%;
width: 100w;
background: $panel;
color: $text-panel;
height: auto;
padding: 0 1 0 0;
padding: 0 1 0 0;
}

25
sandbox/nest.css Normal file
View File

@@ -0,0 +1,25 @@
Vertical {
background: blue;
}
#container {
width:50%;
height: auto;
align-horizontal: center;
padding: 1;
border: heavy white;
background: white 50%;
overflow-y: auto
}
TextWidget {
/* width: 50%; */
height: auto;
padding: 2;
background: green 30%;
border: yellow;
box-sizing: border-box;
}

38
sandbox/nest.py Normal file
View File

@@ -0,0 +1,38 @@
from textual.app import App, ComposeResult
from textual.widget import Widget
from textual import layout
from rich.text import Text
lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
TEXT = Text.from_markup(lorem)
class TextWidget(Widget):
def render(self, style):
return TEXT
class AutoApp(App):
def on_mount(self) -> None:
self.bind("t", "tree")
def compose(self) -> ComposeResult:
yield layout.Vertical(
Widget(
TextWidget(classes="test"),
id="container",
),
)
def action_tree(self):
self.log(self.screen.tree)
app = AutoApp(css_path="nest.css")
if __name__ == "__main__":
app.run()

View File

@@ -166,7 +166,7 @@ class Border:
if has_top:
lines.pop(0)
if has_bottom:
if has_bottom and lines:
lines.pop(-1)
divide = Segment.divide

View File

@@ -60,7 +60,6 @@ class Layout(ABC):
"""
width: int | None = None
for child in widget.displayed_children:
assert isinstance(child, Widget)
if not child.is_container:
child_width = child.get_content_width(container, viewport)
width = child_width if width is None else max(width, child_width)

View File

@@ -35,50 +35,48 @@ def get_box_model(
width, height = container
is_content_box = styles.box_sizing == "content-box"
is_border_box = styles.box_sizing == "border-box"
gutter = styles.padding + styles.border.spacing
gutter = styles.gutter
margin = styles.margin
is_auto_width = styles.width and styles.width.is_auto
is_auto_height = styles.height and styles.height.is_auto
# Container minus padding and border
content_container = container - gutter.totals
# The container including the content
sizing_container = content_container if is_border_box else container
if styles.width is None:
width = container.width - margin.width
elif is_auto_width:
# When width is auto, we want enough space to always fit the content
width = get_content_width(
(container - gutter.totals if is_border_box else container)
- styles.margin.totals,
viewport,
)
width = get_content_width(sizing_container - styles.margin.totals, viewport)
else:
width = styles.width.resolve_dimension(container, viewport)
if styles.min_width is not None:
min_width = styles.min_width.resolve_dimension(container, viewport)
min_width = styles.min_width.resolve_dimension(content_container, viewport)
width = max(width, min_width)
if styles.max_width is not None:
max_width = styles.max_width.resolve_dimension(container, viewport)
max_width = styles.max_width.resolve_dimension(content_container, viewport)
width = min(width, max_width)
if styles.height is None:
height = container.height - margin.height
elif styles.height.is_auto:
height = get_content_height(
container - gutter.totals if is_border_box else container,
viewport,
(width - gutter.width) if is_border_box else width,
)
height = get_content_height(content_container, viewport, width - gutter.width)
if is_border_box:
height += gutter.height
else:
height = styles.height.resolve_dimension(container, viewport)
height = styles.height.resolve_dimension(content_container, viewport)
if styles.min_height is not None:
min_height = styles.min_height.resolve_dimension(container, viewport)
min_height = styles.min_height.resolve_dimension(content_container, viewport)
height = max(height, min_height)
if styles.max_height is not None:
max_height = styles.max_height.resolve_dimension(container, viewport)
max_height = styles.max_height.resolve_dimension(content_container, viewport)
height = min(height, max_height)
if is_border_box and is_auto_width:
@@ -86,7 +84,7 @@ def get_box_model(
if is_content_box:
width += gutter.width
height += gutter.height
# height += gutter.height
model = BoxModel(Size(width, height), margin)
return model

View File

@@ -26,9 +26,13 @@ DockEdge = Literal["top", "right", "bottom", "left"]
@dataclass
class DockOptions:
size: int | None = None
fraction: int = 1
fraction: int | None = 1
min_size: int = 1
def __post_init__(self) -> None:
if self.size is None and self.fraction is None:
self.fraction = 1
class Dock(NamedTuple):
edge: Edge
@@ -71,6 +75,7 @@ class DockLayout(Layout):
styles = widget.styles
has_rule = styles.has_rule
# TODO: This was written pre resolve_dimension, we should update this to use available units
return (
DockOptions(
styles.width.cells if has_rule("width") else None,

View File

@@ -19,7 +19,6 @@ class VerticalLayout(Layout):
placements: list[WidgetPlacement] = []
add_placement = placements.append
max_width = max_height = 0
parent_size = parent.size
box_models = [
@@ -43,9 +42,8 @@ class VerticalLayout(Layout):
region = Region(offset_x, y, content_width, content_height)
add_placement(WidgetPlacement(region, widget, 0))
y += region.height + margin
max_height = y
total_region = Region(0, 0, max_width, max_height)
total_region = Region(0, 0, size.width, y)
add_placement(WidgetPlacement(total_region, None, 0))
return placements, set(displayed_children)

View File

@@ -66,13 +66,12 @@ class RenderCache(NamedTuple):
@rich.repr.auto
class Widget(DOMNode):
CSS = """
"""
can_focus: bool = False
can_focus_children: bool = True
CSS = """
"""
def __init__(
self,
*children: Widget,
@@ -169,14 +168,18 @@ class Widget(DOMNode):
self.get_content_width,
self.get_content_height,
)
print("--")
print(self)
print("container", container)
print("model", box_model)
return box_model
def get_content_width(self, container: Size, viewport: Size) -> int:
"""Gets the width of the content area.
Args:
container_size (Size): Size of the container (immediate parent) widget.
viewport_size (Size): Size of the viewport.
container (Size): Size of the container (immediate parent) widget.
viewport (Size): Size of the viewport.
Returns:
int: The optimal width of the content.
@@ -194,32 +197,34 @@ class Widget(DOMNode):
width = measurement.maximum
return width
def get_content_height(
self, container_size: Size, viewport_size: Size, width: int
) -> int:
def get_content_height(self, container: Size, viewport: Size, width: int) -> int:
"""Gets the height (number of lines) in the content area.
Args:
container_size (Size): Size of the container (immediate parent) widget.
viewport_size (Size): Size of the viewport.
container (Size): Size of the container (immediate parent) widget.
viewport (Size): Size of the viewport.
width (int): Width of renderable.
Returns:
int: The height of the content.
"""
if self.is_container:
assert self.layout is not None
height = self.layout.get_content_height(
self, container_size, viewport_size, width
self,
container,
viewport,
width,
)
print(self, "auto_height container", "width=", width, "height=", height)
else:
renderable = self.render(self.styles.rich_style)
options = self.console.options.update_width(width).update(highlight=False)
segments = self.console.render(renderable, options)
# Cheaper than counting the lines returned from render_lines!
height = sum(text.count("\n") for text, _, _ in segments)
print(self, "auto_height renderable", height)
return height
async def watch_scroll_x(self, new_value: float) -> None:
@@ -690,13 +695,9 @@ class Widget(DOMNode):
return self._animate
@property
def layout(self) -> Layout | None:
return self.styles.layout or (
# If we have children we _should_ return a layout, otherwise they won't be displayed:
self._default_layout
if self.children
else None
)
def layout(self) -> Layout:
"""Get the layout object if set in styles, or a default layout."""
return self.styles.layout or self._default_layout
@property
def is_container(self) -> bool: