Skip to content

Commit f1500f8

Browse files
authored
Merge branch 'main' into fix-1309
2 parents 2a4050c + dd8a956 commit f1500f8

File tree

17 files changed

+1165
-72
lines changed

17 files changed

+1165
-72
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1212

1313
- Added "inherited bindings" -- BINDINGS classvar will be merged with base classes, unless inherit_bindings is set to False
1414
- Added `Tree` widget which replaces `TreeControl`.
15+
- Added widget `Placeholder` https://github.com/Textualize/textual/issues/1200.
1516

1617
### Changed
1718

1819
- Rebuilt `DirectoryTree` with new `Tree` control.
1920
- Empty containers with a dimension set to `"auto"` will now collapse instead of filling up the available space.
21+
- Container widgets now have default height of `1fr`.
22+
- The default `width` of a `Label` is now `auto`.
2023

2124
### Fixed
2225

2326
- Type selectors can now contain numbers https://github.com/Textualize/textual/issues/1253
27+
- Fixed visibility not affecting children https://github.com/Textualize/textual/issues/1313
2428

2529
## [0.5.0] - 2022-11-20
2630

docs/api/placeholder.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
::: textual.widgets.Placeholder

docs/examples/guide/dom4.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
/* The top level dialog (a Container) */
33
#dialog {
4+
height: 100%;
45
margin: 4 8;
56
background: $panel;
67
color: $text;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Placeholder {
2+
height: 100%;
3+
}
4+
5+
#top {
6+
height: 50%;
7+
width: 100%;
8+
layout: grid;
9+
grid-size: 2 2;
10+
}
11+
12+
#left {
13+
row-span: 2;
14+
}
15+
16+
#bot {
17+
height: 50%;
18+
width: 100%;
19+
layout: grid;
20+
grid-size: 8 8;
21+
}
22+
23+
#c1 {
24+
row-span: 4;
25+
column-span: 8;
26+
}
27+
28+
#col1, #col2, #col3 {
29+
width: 1fr;
30+
}
31+
32+
#p1 {
33+
row-span: 4;
34+
column-span: 4;
35+
}
36+
37+
#p2 {
38+
row-span: 2;
39+
column-span: 4;
40+
}
41+
42+
#p3 {
43+
row-span: 2;
44+
column-span: 2;
45+
}
46+
47+
#p4 {
48+
row-span: 1;
49+
column-span: 2;
50+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from textual.app import App, ComposeResult
2+
from textual.containers import Container, Horizontal, Vertical
3+
from textual.widgets import Placeholder
4+
5+
6+
class PlaceholderApp(App):
7+
8+
CSS_PATH = "placeholder.css"
9+
10+
def compose(self) -> ComposeResult:
11+
yield Vertical(
12+
Container(
13+
Placeholder("This is a custom label for p1.", id="p1"),
14+
Placeholder("Placeholder p2 here!", id="p2"),
15+
Placeholder(id="p3"),
16+
Placeholder(id="p4"),
17+
Placeholder(id="p5"),
18+
Placeholder(),
19+
Horizontal(
20+
Placeholder(variant="size", id="col1"),
21+
Placeholder(variant="text", id="col2"),
22+
Placeholder(variant="size", id="col3"),
23+
id="c1",
24+
),
25+
id="bot"
26+
),
27+
Container(
28+
Placeholder(variant="text", id="left"),
29+
Placeholder(variant="size", id="topright"),
30+
Placeholder(variant="text", id="botright"),
31+
id="top",
32+
),
33+
id="content",
34+
)
35+
36+
37+
if __name__ == "__main__":
38+
app = PlaceholderApp()
39+
app.run()

docs/widgets/placeholder.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Placeholder
2+
3+
4+
A widget that is meant to have no complex functionality.
5+
Use the placeholder widget when studying the layout of your app before having to develop your custom widgets.
6+
7+
The placeholder widget has variants that display different bits of useful information.
8+
Clicking a placeholder will cycle through its variants.
9+
10+
- [ ] Focusable
11+
- [ ] Container
12+
13+
## Example
14+
15+
The example below shows each placeholder variant.
16+
17+
=== "Output"
18+
19+
```{.textual path="docs/examples/widgets/placeholder.py"}
20+
```
21+
22+
=== "placeholder.py"
23+
24+
```python
25+
--8<-- "docs/examples/widgets/placeholder.py"
26+
```
27+
28+
=== "placeholder.css"
29+
30+
```css
31+
--8<-- "docs/examples/widgets/placeholder.css"
32+
```
33+
34+
## Reactive Attributes
35+
36+
| Name | Type | Default | Description |
37+
| ---------- | ------ | ----------- | -------------------------------------------------- |
38+
| `variant` | `str` | `"default"` | Styling variant. One of `default`, `size`, `text`. |
39+
40+
41+
## Messages
42+
43+
This widget sends no messages.
44+
45+
## See Also
46+
47+
* [Placeholder](../api/placeholder.md) code reference

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ nav:
9898
- "widgets/index.md"
9999
- "widgets/input.md"
100100
- "widgets/label.md"
101+
- "widgets/placeholder.md"
101102
- "widgets/static.md"
102103
- "widgets/tree.md"
103104
- API:

src/textual/_arrange.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ def arrange(
4141

4242
placements: list[WidgetPlacement] = []
4343
add_placement = placements.append
44-
region = size.region
4544

4645
_WidgetPlacement = WidgetPlacement
4746
top_z = TOP_Z
@@ -50,7 +49,9 @@ def arrange(
5049
get_dock = attrgetter("styles.dock")
5150
styles = widget.styles
5251

52+
layer_region = size.region
5353
for widgets in dock_layers.values():
54+
region = layer_region
5455

5556
layout_widgets, dock_widgets = partition(get_dock, widgets)
5657

src/textual/_compositor.py

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ def add_widget(
347347
order: tuple[tuple[int, ...], ...],
348348
layer_order: int,
349349
clip: Region,
350+
visible: bool,
350351
) -> None:
351352
"""Called recursively to place a widget and its children in the map.
352353
@@ -356,7 +357,12 @@ def add_widget(
356357
order (tuple[int, ...]): A tuple of ints to define the order.
357358
clip (Region): The clipping region (i.e. the viewport which contains it).
358359
"""
359-
widgets.add(widget)
360+
visibility = widget.styles.get_rule("visibility")
361+
if visibility is not None:
362+
visible = visibility == "visible"
363+
364+
if visible:
365+
widgets.add(widget)
360366
styles_offset = widget.styles.offset
361367
layout_offset = (
362368
styles_offset.resolve(region.size, clip.size)
@@ -420,32 +426,34 @@ def add_widget(
420426
widget_order,
421427
layer_order,
422428
sub_clip,
429+
visible,
423430
)
424431
layer_order -= 1
425432

426-
# Add any scrollbars
427-
for chrome_widget, chrome_region in widget._arrange_scrollbars(
428-
container_region
429-
):
430-
map[chrome_widget] = MapGeometry(
431-
chrome_region + layout_offset,
433+
if visible:
434+
# Add any scrollbars
435+
for chrome_widget, chrome_region in widget._arrange_scrollbars(
436+
container_region
437+
):
438+
map[chrome_widget] = MapGeometry(
439+
chrome_region + layout_offset,
440+
order,
441+
clip,
442+
container_size,
443+
container_size,
444+
chrome_region,
445+
)
446+
447+
map[widget] = MapGeometry(
448+
region + layout_offset,
432449
order,
433450
clip,
451+
total_region.size,
434452
container_size,
435-
container_size,
436-
chrome_region,
453+
virtual_region,
437454
)
438455

439-
map[widget] = MapGeometry(
440-
region + layout_offset,
441-
order,
442-
clip,
443-
total_region.size,
444-
container_size,
445-
virtual_region,
446-
)
447-
448-
else:
456+
elif visible:
449457
# Add the widget to the map
450458
map[widget] = MapGeometry(
451459
region + layout_offset,
@@ -457,7 +465,15 @@ def add_widget(
457465
)
458466

459467
# Add top level (root) widget
460-
add_widget(root, size.region, size.region, ((0,),), layer_order, size.region)
468+
add_widget(
469+
root,
470+
size.region,
471+
size.region,
472+
((0,),),
473+
layer_order,
474+
size.region,
475+
True,
476+
)
461477
return map, widgets
462478

463479
@property
@@ -630,11 +646,6 @@ def _get_renders(
630646
if not self.map:
631647
return
632648

633-
def is_visible(widget: Widget) -> bool:
634-
"""Return True if the widget is (literally) visible by examining various
635-
properties which affect whether it can be seen or not."""
636-
return widget.visible and widget.styles.opacity > 0
637-
638649
_Region = Region
639650

640651
visible_widgets = self.visible_widgets
@@ -644,13 +655,13 @@ def is_visible(widget: Widget) -> bool:
644655
widget_regions = [
645656
(widget, region, clip)
646657
for widget, (region, clip) in visible_widgets.items()
647-
if crop_overlaps(clip) and is_visible(widget)
658+
if crop_overlaps(clip) and widget.styles.opacity > 0
648659
]
649660
else:
650661
widget_regions = [
651662
(widget, region, clip)
652663
for widget, (region, clip) in visible_widgets.items()
653-
if is_visible(widget)
664+
if widget.styles.opacity > 0
654665
]
655666

656667
intersection = _Region.intersection

src/textual/containers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class Container(Widget):
66

77
DEFAULT_CSS = """
88
Container {
9+
height: 1fr;
910
layout: vertical;
1011
overflow: auto;
1112
}
@@ -17,6 +18,7 @@ class Vertical(Widget):
1718

1819
DEFAULT_CSS = """
1920
Vertical {
21+
height: 1fr;
2022
layout: vertical;
2123
overflow-y: auto;
2224
}
@@ -28,6 +30,7 @@ class Horizontal(Widget):
2830

2931
DEFAULT_CSS = """
3032
Horizontal {
33+
height: 1fr;
3134
layout: horizontal;
3235
overflow-x: hidden;
3336
}
@@ -39,6 +42,7 @@ class Grid(Widget):
3942

4043
DEFAULT_CSS = """
4144
Grid {
45+
height: 1fr;
4246
layout: grid;
4347
}
4448
"""
@@ -49,6 +53,7 @@ class Content(Widget, can_focus=True, can_focus_children=False):
4953

5054
DEFAULT_CSS = """
5155
Vertical {
56+
height: 1fr;
5257
layout: vertical;
5358
overflow-y: auto;
5459
}

0 commit comments

Comments
 (0)