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; color: $text-background;
background: $background; background: $background;
layout: vertical; layout: vertical;
overflow-y: scroll; overflow-y: scroll;
} }
@@ -114,11 +114,11 @@ TweetHeader {
} }
TweetBody { TweetBody {
width: 100%; width: 100w;
background: $panel; background: $panel;
color: $text-panel; color: $text-panel;
height: auto; 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: if has_top:
lines.pop(0) lines.pop(0)
if has_bottom: if has_bottom and lines:
lines.pop(-1) lines.pop(-1)
divide = Segment.divide divide = Segment.divide

View File

@@ -60,7 +60,6 @@ class Layout(ABC):
""" """
width: int | None = None width: int | None = None
for child in widget.displayed_children: for child in widget.displayed_children:
assert isinstance(child, Widget)
if not child.is_container: if not child.is_container:
child_width = child.get_content_width(container, viewport) child_width = child.get_content_width(container, viewport)
width = child_width if width is None else max(width, child_width) 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 width, height = container
is_content_box = styles.box_sizing == "content-box" is_content_box = styles.box_sizing == "content-box"
is_border_box = styles.box_sizing == "border-box" is_border_box = styles.box_sizing == "border-box"
gutter = styles.padding + styles.border.spacing gutter = styles.gutter
margin = styles.margin margin = styles.margin
is_auto_width = styles.width and styles.width.is_auto 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: if styles.width is None:
width = container.width - margin.width width = container.width - margin.width
elif is_auto_width: elif is_auto_width:
# When width is auto, we want enough space to always fit the content # When width is auto, we want enough space to always fit the content
width = get_content_width( width = get_content_width(sizing_container - styles.margin.totals, viewport)
(container - gutter.totals if is_border_box else container)
- styles.margin.totals,
viewport,
)
else: else:
width = styles.width.resolve_dimension(container, viewport) width = styles.width.resolve_dimension(container, viewport)
if styles.min_width is not None: 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) width = max(width, min_width)
if styles.max_width is not None: 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) width = min(width, max_width)
if styles.height is None: if styles.height is None:
height = container.height - margin.height height = container.height - margin.height
elif styles.height.is_auto: elif styles.height.is_auto:
height = get_content_height( height = get_content_height(content_container, viewport, width - gutter.width)
container - gutter.totals if is_border_box else container,
viewport,
(width - gutter.width) if is_border_box else width,
)
if is_border_box: if is_border_box:
height += gutter.height height += gutter.height
else: else:
height = styles.height.resolve_dimension(container, viewport) height = styles.height.resolve_dimension(content_container, viewport)
if styles.min_height is not None: 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) height = max(height, min_height)
if styles.max_height is not None: 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) height = min(height, max_height)
if is_border_box and is_auto_width: if is_border_box and is_auto_width:
@@ -86,7 +84,7 @@ def get_box_model(
if is_content_box: if is_content_box:
width += gutter.width width += gutter.width
height += gutter.height # height += gutter.height
model = BoxModel(Size(width, height), margin) model = BoxModel(Size(width, height), margin)
return model return model

View File

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

View File

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

View File

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