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;
|
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
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:
|
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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user