mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
fixes for layout
This commit is contained in:
@@ -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
25
sandbox/nest.css
Normal 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
38
sandbox/nest.py
Normal 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()
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user