Skip to content

Commit ebd09ba

Browse files
committed
add greedy
1 parent e84600c commit ebd09ba

File tree

9 files changed

+53
-25
lines changed

9 files changed

+53
-25
lines changed

src/textual/_arrange.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ def _build_layers(widgets: Iterable[Widget]) -> Mapping[str, Sequence[Widget]]:
3232

3333

3434
def arrange(
35-
widget: Widget, children: Sequence[Widget], size: Size, viewport: Size
35+
widget: Widget,
36+
children: Sequence[Widget],
37+
size: Size,
38+
viewport: Size,
39+
optimal: bool = False,
3640
) -> DockArrangeResult:
3741
"""Arrange widgets by applying docks and calling layouts
3842
@@ -56,7 +60,6 @@ def arrange(
5660

5761
# Widgets which will be displayed
5862
display_widgets = [child for child in children if get_display(child) != "none"]
59-
6063
# Widgets organized into layers
6164
layers = _build_layers(display_widgets)
6265

@@ -80,7 +83,7 @@ def arrange(
8083
# Arrange docked widgets
8184
if dock_widgets:
8285
_dock_placements, dock_spacing = _arrange_dock_widgets(
83-
dock_widgets, dock_region, viewport
86+
dock_widgets, dock_region, viewport, greedy=not optimal
8487
)
8588
placements.extend(_dock_placements)
8689
dock_region = dock_region.shrink(dock_spacing)
@@ -92,7 +95,7 @@ def arrange(
9295
if layout_widgets:
9396
# Arrange layout widgets (i.e. not docked)
9497
layout_placements = widget.layout.arrange(
95-
widget, layout_widgets, dock_region.size
98+
widget, layout_widgets, dock_region.size, greedy=not optimal
9699
)
97100
scroll_spacing = scroll_spacing.grow_maximum(dock_spacing)
98101
placement_offset = dock_region.offset
@@ -118,11 +121,12 @@ def arrange(
118121

119122
placements.extend(layout_placements)
120123

124+
widget.log(placements)
121125
return DockArrangeResult(placements, set(display_widgets), scroll_spacing)
122126

123127

124128
def _arrange_dock_widgets(
125-
dock_widgets: Sequence[Widget], region: Region, viewport: Size
129+
dock_widgets: Sequence[Widget], region: Region, viewport: Size, greedy: bool = True
126130
) -> tuple[list[WidgetPlacement], Spacing]:
127131
"""Arrange widgets which are *docked*.
128132
@@ -150,7 +154,7 @@ def _arrange_dock_widgets(
150154
edge = dock_widget.styles.dock
151155

152156
box_model = dock_widget._get_box_model(
153-
size, viewport, Fraction(size.width), Fraction(size.height)
157+
size, viewport, Fraction(size.width), Fraction(size.height), greedy=greedy
154158
)
155159
widget_width_fraction, widget_height_fraction, margin = box_model
156160
widget_width = int(widget_width_fraction) + margin.width

src/textual/_resolve.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ def resolve_box_models(
222222
viewport_size: Size,
223223
margin: Size,
224224
resolve_dimension: Literal["width", "height"] = "width",
225+
greedy: bool = True,
225226
) -> list[BoxModel]:
226227
"""Resolve box models for a list of dimensions
227228
@@ -263,6 +264,7 @@ def resolve_box_models(
263264
if (_height := fraction_height - margin_height) < 0
264265
else _height
265266
),
267+
greedy=greedy,
266268
)
267269
)
268270
for (_dimension, widget, (margin_width, margin_height)) in zip(
@@ -334,10 +336,7 @@ def resolve_box_models(
334336
box_models = [
335337
box_model
336338
or widget._get_box_model(
337-
size,
338-
viewport_size,
339-
width_fraction,
340-
height_fraction,
339+
size, viewport_size, width_fraction, height_fraction, greedy=greedy
341340
)
342341
for widget, box_model in zip(widgets, box_models)
343342
]

src/textual/css/scalar.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ def resolve(
283283

284284
if unit == Unit.PERCENT:
285285
unit = percent_unit
286+
# elif unit == Unit.AUTO:
287+
# unit = Unit.FRACTION
286288
try:
287289
dimension = RESOLVE_MAP[unit](
288290
value, size, viewport, fraction_unit or _FRACTION_ONE

src/textual/layout.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,11 @@ def __repr__(self) -> str:
210210

211211
@abstractmethod
212212
def arrange(
213-
self, parent: Widget, children: list[Widget], size: Size
213+
self,
214+
parent: Widget,
215+
children: list[Widget],
216+
size: Size,
217+
greedy: bool = True,
214218
) -> ArrangeResult:
215219
"""Generate a layout map that defines where on the screen the widgets will be drawn.
216220
@@ -237,7 +241,8 @@ def get_content_width(self, widget: Widget, container: Size, viewport: Size) ->
237241
width = 0
238242
else:
239243
arrangement = widget._arrange(
240-
Size(0 if widget.shrink else container.width, 0)
244+
Size(0 if widget.shrink else container.width, 0),
245+
optimal=True,
241246
)
242247
width = arrangement.total_region.right
243248
return width

src/textual/layouts/grid.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def __init__(self) -> None:
3131
"""If self.shrink is `True`, auto-detect and limit the width."""
3232

3333
def arrange(
34-
self, parent: Widget, children: list[Widget], size: Size
34+
self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True
3535
) -> ArrangeResult:
3636
parent.pre_layout(self)
3737
styles = parent.styles
@@ -309,6 +309,7 @@ def apply_height_limits(widget: Widget, height: int) -> int:
309309
Fraction(cell_size.width),
310310
Fraction(cell_size.height),
311311
constrain_width=True,
312+
greedy=greedy,
312313
)
313314

314315
if self.stretch_height and len(children) > 1:

src/textual/layouts/horizontal.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class HorizontalLayout(Layout):
2020
name = "horizontal"
2121

2222
def arrange(
23-
self, parent: Widget, children: list[Widget], size: Size
23+
self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True
2424
) -> ArrangeResult:
2525
parent.pre_layout(self)
2626
placements: list[WidgetPlacement] = []
@@ -57,6 +57,7 @@ def arrange(
5757
viewport,
5858
resolve_margin,
5959
resolve_dimension="width",
60+
greedy=greedy,
6061
)
6162

6263
margins = [

src/textual/layouts/vertical.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class VerticalLayout(Layout):
1818
name = "vertical"
1919

2020
def arrange(
21-
self, parent: Widget, children: list[Widget], size: Size
21+
self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True
2222
) -> ArrangeResult:
2323
parent.pre_layout(self)
2424
placements: list[WidgetPlacement] = []
@@ -57,6 +57,7 @@ def arrange(
5757
parent.app.size,
5858
resolve_margin,
5959
resolve_dimension="height",
60+
greedy=greedy,
6061
)
6162

6263
margins = [

src/textual/screen.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ def get_maximize_widgets(maximized: Widget) -> list[Widget]:
513513
),
514514
size,
515515
self.screen.size,
516+
False,
516517
)
517518

518519
return arrangement

src/textual/widget.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
from textual.css.match import match
6767
from textual.css.parse import parse_selectors
6868
from textual.css.query import NoMatches, WrongType
69-
from textual.css.scalar import ScalarOffset
69+
from textual.css.scalar import Scalar, ScalarOffset
7070
from textual.dom import DOMNode, NoScreen
7171
from textual.geometry import (
7272
NULL_REGION,
@@ -327,6 +327,8 @@ class Widget(DOMNode):
327327
"""Rich renderable may expand beyond optimal size."""
328328
shrink: Reactive[bool] = Reactive(True)
329329
"""Rich renderable may shrink below optimal size."""
330+
greedy: Reactive[bool] = Reactive(True)
331+
"""Fraction widths will consume as much space as possible."""
330332
auto_links: Reactive[bool] = Reactive(True)
331333
"""Widget will highlight links automatically."""
332334
disabled: Reactive[bool] = Reactive(False)
@@ -1185,7 +1187,7 @@ def render_str(self, text_content: str | Content) -> Content:
11851187
return text_content
11861188
return Content.from_markup(text_content)
11871189

1188-
def _arrange(self, size: Size) -> DockArrangeResult:
1190+
def _arrange(self, size: Size, optimal: bool = False) -> DockArrangeResult:
11891191
"""Arrange children.
11901192
11911193
Args:
@@ -1194,13 +1196,13 @@ def _arrange(self, size: Size) -> DockArrangeResult:
11941196
Returns:
11951197
Widget locations.
11961198
"""
1197-
cache_key = (size, self._nodes._updates)
1199+
cache_key = (size, self._nodes._updates, optimal)
11981200
cached_result = self._arrangement_cache.get(cache_key)
11991201
if cached_result is not None:
12001202
return cached_result
12011203

12021204
arrangement = self._arrangement_cache[cache_key] = arrange(
1203-
self, self._nodes, size, self.screen.size
1205+
self, self._nodes, size, self.screen.size, optimal=optimal
12041206
)
12051207

12061208
return arrangement
@@ -1541,6 +1543,7 @@ def _get_box_model(
15411543
width_fraction: Fraction,
15421544
height_fraction: Fraction,
15431545
constrain_width: bool = False,
1546+
greedy: bool = True,
15441547
) -> BoxModel:
15451548
"""Process the box model for this widget.
15461549
@@ -1555,14 +1558,17 @@ def _get_box_model(
15551558
The size and margin for this widget.
15561559
"""
15571560
styles = self.styles
1558-
_content_width, _content_height = container
1559-
content_width = Fraction(_content_width)
1560-
content_height = Fraction(_content_height)
1561+
# _content_width, _content_height = container
1562+
# content_width = Fraction(_content_width)
1563+
# content_height = Fraction(_content_height)
15611564
is_border_box = styles.box_sizing == "border-box"
15621565
gutter = styles.gutter # Padding plus border
15631566
margin = styles.margin
15641567

1565-
is_auto_width = styles.width and styles.width.is_auto
1568+
styles_width = styles.width
1569+
if not greedy and styles_width is not None and styles_width.is_fraction:
1570+
styles_width = Scalar.parse("auto")
1571+
is_auto_width = styles_width and styles_width.is_auto
15661572
is_auto_height = styles.height and styles.height.is_auto
15671573

15681574
# Container minus padding and border
@@ -1573,7 +1579,7 @@ def _get_box_model(
15731579
)
15741580
min_width, max_width, min_height, max_height = extrema
15751581

1576-
if styles.width is None:
1582+
if styles_width is None:
15771583
# No width specified, fill available space
15781584
content_width = Fraction(content_container.width - margin.width)
15791585
elif is_auto_width:
@@ -1592,7 +1598,6 @@ def _get_box_model(
15921598
content_width = Fraction(content_container.width)
15931599
else:
15941600
# An explicit width
1595-
styles_width = styles.width
15961601
content_width = styles_width.resolve(
15971602
container - margin.totals, viewport, width_fraction
15981603
)
@@ -2193,6 +2198,8 @@ def _has_relative_children_width(self) -> bool:
21932198
if not self.is_container:
21942199
return False
21952200
for child in self.children:
2201+
if not child.greedy:
2202+
continue
21962203
styles = child.styles
21972204
if styles.display == "none":
21982205
continue
@@ -3326,6 +3333,8 @@ def scroll_to_widget(
33263333
scrolled = False
33273334

33283335
if not region.size:
3336+
if on_complete is not None:
3337+
self.call_after_refresh(on_complete)
33293338
return False
33303339

33313340
while isinstance(widget.parent, Widget) and widget is not self:
@@ -3418,6 +3427,8 @@ def scroll_to_region(
34183427
window = window.shrink(spacing)
34193428

34203429
if window in region and not (top or center):
3430+
if on_complete is not None:
3431+
self.call_after_refresh(on_complete)
34213432
return Offset()
34223433

34233434
def clamp_delta(delta: Offset) -> Offset:
@@ -3471,6 +3482,9 @@ def clamp_delta(delta: Offset) -> Offset:
34713482
level=level,
34723483
immediate=immediate,
34733484
)
3485+
else:
3486+
if on_complete is not None:
3487+
self.call_after_refresh(on_complete)
34743488
return delta
34753489

34763490
def scroll_visible(

0 commit comments

Comments
 (0)