Skip to content

Commit

Permalink
dock offsets
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Nov 20, 2024
1 parent 068e439 commit 60f2b2c
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 30 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).


## Unreleased

### Added

- Added Styles.has_any_rules

### Fixed

- Fixed offset applied to docked widgets

## [0.86.3] - 2024-11-19

### Changed
Expand Down
39 changes: 27 additions & 12 deletions src/textual/_arrange.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@ def arrange(

get_dock = attrgetter("styles.is_docked")
get_split = attrgetter("styles.is_split")
get_display = attrgetter("styles.display")

styles = widget.styles

# Widgets which will be displayed
display_widgets = [child for child in children if child.styles.display != "none"]
display_widgets = [child for child in children if get_display(child) != "none"]

# Widgets organized into layers
layers = _build_layers(display_widgets)
Expand Down Expand Up @@ -91,9 +92,7 @@ def arrange(
if layout_widgets:
# Arrange layout widgets (i.e. not docked)
layout_placements = widget.layout.arrange(
widget,
layout_widgets,
dock_region.size,
widget, layout_widgets, dock_region.size
)
scroll_spacing = scroll_spacing.grow_maximum(dock_spacing)
placement_offset = dock_region.offset
Expand Down Expand Up @@ -171,16 +170,32 @@ def _arrange_dock_widgets(
(widget_width, widget_height), size
)
dock_region = dock_region.shrink(margin).translate(align_offset)
append_placement(
_WidgetPlacement(
dock_region.translate(region_offset),
null_offset,
null_spacing,
dock_widget,
top_z,
True,
styles = dock_widget.styles
offset = (
styles.offset.resolve(
size,
viewport,
)
if styles.has_rule("offset")
else NULL_OFFSET
)
placement = _WidgetPlacement(
dock_region.translate(region_offset),
offset,
null_spacing,
dock_widget,
top_z,
True,
)
if (
styles.has_any_rules("constrain_x", "constrain_y")
or dock_widget.absolute_offset is not None
):
placement = placement.process_offset(
viewport.region if placement.overlay else size.region
)
append_placement(placement)

dock_spacing = Spacing(top, right, bottom, left)
return (placements, dock_spacing)

Expand Down
12 changes: 6 additions & 6 deletions src/textual/_compositor.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,12 +713,12 @@ def add_widget(
elif visible:
# Add the widget to the map

if widget.absolute_offset is not None:
margin = styles.margin
region = region.at_offset(widget.absolute_offset + margin.top_left)
region = region.translate(
styles.offset.resolve(region.grow(margin).size, size)
)
# if widget.absolute_offset is not None:
# margin = styles.margin
# region = region.at_offset(widget.absolute_offset + margin.top_left)
# region = region.translate(
# styles.offset.resolve(region.grow(margin).size, size)
# )
# has_rule = styles.has_rule
# if has_rule("constrain_x") or has_rule("constrain_y"):
# region = region.constrain(
Expand Down
13 changes: 13 additions & 0 deletions src/textual/css/styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,19 @@ def has_rule(self, rule_name: str) -> bool:
rule_name
)

def has_any_rules(self, *rule_names: str) -> bool:
"""Check if any of the supplied rules have been set.
Args:
rule_names: Number of rules.
Returns:
`True` if any of the supplied rules have been set, `False` if none have.
"""
inline_has_rule = self._inline_styles.has_rule
base_has_rule = self._base_styles.has_rule
return any(inline_has_rule(name) or base_has_rule(name) for name in rule_names)

def set_rule(self, rule_name: str, value: object | None) -> bool:
self._updates += 1
return self._inline_styles.set_rule(rule_name, value)
Expand Down
21 changes: 18 additions & 3 deletions src/textual/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,25 @@ def get_bounds(cls, placements: Iterable[WidgetPlacement]) -> Region:
)
return bounding_region

def apply_constrain(self, constrain_region: Region) -> WidgetPlacement:
styles = self.widget.styles
def process_offset(self, constrain_region: Region) -> WidgetPlacement:
"""Apply any absolute offset or constrain rules to the placement.
region = self.region.translate(self.offset).constrain(
Args:
constrain_region: The container region when applying constrain rules.
Returns:
Processes placement, may be the same instance.
"""
widget = self.widget
styles = widget.styles
region = self.region
margin = self.margin
if widget.absolute_offset is not None:
region = region.at_offset(widget.absolute_offset + margin.top_left)
region.translate(
styles.offset.resolve(region.grow(margin).size, constrain_region.size)
)
region = region.translate(self.offset).constrain(
styles.constrain_x,
styles.constrain_y,
self.margin,
Expand Down
11 changes: 7 additions & 4 deletions src/textual/layouts/horizontal.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def arrange(
children, box_models, margins
):
styles = widget.styles
has_rule = styles.has_rule

overlay = styles.overlay == "screen"
offset = (
styles.offset.resolve(
Expand Down Expand Up @@ -109,9 +109,12 @@ def arrange(
overlay,
)

if has_rule("constrain_x") or has_rule("constrain_y"):
placement = placement.apply_constrain(
viewport.region if placement.overlay else size.region
if (
styles.has_any_rules("constrain_x", "constrain_y")
or widget.absolute_offset is not None
):
placement = placement.process_offset(
viewport.region if overlay else size.region
)
add_placement(placement)
if not overlay:
Expand Down
11 changes: 7 additions & 4 deletions src/textual/layouts/vertical.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ def arrange(
children, box_models, margins
):
styles = widget.styles
has_rule = styles.has_rule
overlay = styles.overlay == "screen"
next_y = y + content_height
offset = (
Expand All @@ -112,10 +111,14 @@ def arrange(
False,
overlay,
)
if has_rule("constrain_x") or has_rule("constrain_y"):
placement = placement.apply_constrain(
viewport.region if placement.overlay else size.region
if (
styles.has_any_rules("constrain_x", "constrain_y")
or widget.absolute_offset is not None
):
placement = placement.process_offset(
viewport.region if overlay else size.region
)

add_placement(placement)
if not overlay:
y = next_y + margin
Expand Down
2 changes: 1 addition & 1 deletion src/textual/widgets/_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ class Select(Generic[SelectType], Vertical, can_focus=True):
& > SelectOverlay {
width: 1fr;
# display: none;
display: none;
height: auto;
max-height: 12;
overlay: screen;
Expand Down
24 changes: 24 additions & 0 deletions tests/snapshot_tests/test_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -2588,3 +2588,27 @@ def on_mount(self) -> None:
label.styles.color = variables["text-primary"]

assert snap_compare(ThemeVariablesApp())


def test_dock_offset(snap_compare):
"""Regression test for https://github.com/Textualize/textual/issues/5261
You should see 10 labels, 0 thru 9, in a diagonal line starting at the top left.
"""

class OffsetBugApp(App):
CSS = """
.label {
dock: top;
color: $text-success;
background: $success-muted;
}
"""

def compose(self) -> ComposeResult:
# I'd expect this to draw a diagonal line of labels, but it places them all at the top left.
for i in range(10):
label = Label(str(i), classes="label")
label.styles.offset = (i, i)
yield label

assert snap_compare(OffsetBugApp())

0 comments on commit 60f2b2c

Please sign in to comment.