Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added scroll_to_widget #483

Merged
merged 11 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sandbox/uber.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ App.-show-focus *:focus {
}

.list-item {
height: 10;
height: 6;
color: #12a0;
background: #ffffff00;
}
26 changes: 13 additions & 13 deletions src/textual/_compositor.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,17 @@ class ReflowResult(NamedTuple):
resized: set[Widget] # Widgets that have been resized


class RenderRegion(NamedTuple):
class MapGeometry(NamedTuple):
"""Defines the absolute location of a Widget."""

region: Region # The region occupied by the widget
order: tuple[int, ...] # A tuple of ints defining the painting order
clip: Region # A region to clip the widget by (if a Widget is within a container)
virtual_size: Size # The virtual size (scrollable region) of a widget if it is a container
container_size: Size # The container size (area no occupied by scrollbars)
container_size: Size # The container size (area not occupied by scrollbars)


RenderRegionMap: TypeAlias = "dict[Widget, RenderRegion]"
CompositorMap: TypeAlias = "dict[Widget, MapGeometry]"


@rich.repr.auto
Expand Down Expand Up @@ -97,7 +97,7 @@ class Compositor:

def __init__(self) -> None:
# A mapping of Widget on to its "render location" (absolute position / depth)
self.map: RenderRegionMap = {}
self.map: CompositorMap = {}

# All widgets considered in the arrangement
# Note this may be a superset of self.map.keys() as some widgets may be invisible for various reasons
Expand Down Expand Up @@ -167,7 +167,7 @@ def reflow(self, parent: Widget, size: Size) -> ReflowResult:
resized=resized_widgets,
)

def _arrange_root(self, root: Widget) -> tuple[RenderRegionMap, set[Widget]]:
def _arrange_root(self, root: Widget) -> tuple[CompositorMap, set[Widget]]:
"""Arrange a widgets children based on its layout attribute.

Args:
Expand All @@ -180,7 +180,7 @@ def _arrange_root(self, root: Widget) -> tuple[RenderRegionMap, set[Widget]]:

ORIGIN = Offset(0, 0)
size = root.size
map: RenderRegionMap = {}
map: CompositorMap = {}
widgets: set[Widget] = set()
get_order = attrgetter("order")

Expand Down Expand Up @@ -249,7 +249,7 @@ def add_widget(
for chrome_widget, chrome_region in widget._arrange_scrollbars(
container_size
):
map[chrome_widget] = RenderRegion(
map[chrome_widget] = MapGeometry(
chrome_region + container_region.origin + layout_offset,
order,
clip,
Expand All @@ -258,7 +258,7 @@ def add_widget(
)

# Add the container widget, which will render a background
map[widget] = RenderRegion(
map[widget] = MapGeometry(
region + layout_offset,
order,
clip,
Expand All @@ -268,7 +268,7 @@ def add_widget(

else:
# Add the widget to the map
map[widget] = RenderRegion(
map[widget] = MapGeometry(
region + layout_offset, order, clip, region.size, container_size
)

Expand Down Expand Up @@ -338,8 +338,8 @@ def get_style_at(self, x: int, y: int) -> Style:
return segment.style or Style.null()
return Style.null()

def get_widget_region(self, widget: Widget) -> Region:
"""Get the Region of a Widget contained in this Layout.
def find_widget(self, widget: Widget) -> MapGeometry:
"""Get information regarding the relative position of a widget in the Compositor.

Args:
widget (Widget): The Widget in this layout you wish to know the Region of.
Expand All @@ -348,11 +348,11 @@ def get_widget_region(self, widget: Widget) -> Region:
NoWidget: If the Widget is not contained in this Layout.

Returns:
Region: The Region of the Widget.
MapGeometry: Widget's composition information.

"""
try:
region, *_ = self.map[widget]
region = self.map[widget]
except KeyError:
raise errors.NoWidget("Widget is not in layout")
else:
Expand Down
8 changes: 7 additions & 1 deletion src/textual/css/styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,13 @@ def gutter(self) -> Spacing:
Returns:
Spacing: Space around widget.
"""
spacing = Spacing() + self.padding + self.border.spacing
spacing = self.padding + self.border.spacing
return spacing

@property
def content_gutter(self) -> Spacing:
"""The spacing that surrounds the content area of the widget."""
spacing = self.padding + self.border.spacing + self.margin
return spacing

@abstractmethod
Expand Down
4 changes: 2 additions & 2 deletions src/textual/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,9 @@ class Blur(Event, bubble=False):
pass


class DescendantFocus(Event, bubble=True):
class DescendantFocus(Event, verbosity=2, bubble=True):
pass


class DescendantBlur(Event, bubble=True):
class DescendantBlur(Event, verbosity=2, bubble=True):
pass
24 changes: 21 additions & 3 deletions src/textual/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,24 @@ def origin(self) -> Offset:
"""Get the start point of the region."""
return Offset(self.x, self.y)

@property
def bottom_left(self) -> Offset:
"""Bottom left offset of the region."""
x, y, _width, height = self
return Offset(x, y + height)

@property
def top_right(self) -> Offset:
"""Top right offset of the region."""
x, y, width, _height = self
return Offset(x + width, y)

@property
def bottom_right(self) -> Offset:
"""Bottom right of the region."""
x, y, width, height = self
return Offset(x + width, y + height)

@property
def size(self) -> Size:
"""Get the size of the region."""
Expand All @@ -274,17 +292,17 @@ def corners(self) -> tuple[int, int, int, int]:

@property
def x_range(self) -> range:
"""A range object for X coordinates"""
"""A range object for X coordinates."""
return range(self.x, self.x + self.width)

@property
def y_range(self) -> range:
"""A range object for Y coordinates"""
"""A range object for Y coordinates."""
return range(self.y, self.y + self.height)

@property
def reset_origin(self) -> Region:
"""An region of the same size at the origin."""
"""An region of the same size at (0, 0)."""
_, _, width, height = self
return Region(0, 0, width, height)

Expand Down
10 changes: 5 additions & 5 deletions src/textual/screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from . import events, messages, errors

from .geometry import Offset, Region
from ._compositor import Compositor
from ._compositor import Compositor, MapGeometry
from .reactive import Reactive
from .widget import Widget

Expand Down Expand Up @@ -76,7 +76,7 @@ def get_style_at(self, x: int, y: int) -> Style:
"""
return self._compositor.get_style_at(x, y)

def get_widget_region(self, widget: Widget) -> Region:
def find_widget(self, widget: Widget) -> MapGeometry:
"""Get the screen region of a Widget.

Args:
Expand All @@ -85,7 +85,7 @@ def get_widget_region(self, widget: Widget) -> Region:
Returns:
Region: Region relative to screen.
"""
return self._compositor.get_widget_region(widget)
return self._compositor.find_widget(widget)

def on_idle(self, event: events.Idle) -> None:
# Check for any widgets marked as 'dirty' (needs a repaint)
Expand Down Expand Up @@ -156,7 +156,7 @@ async def _on_mouse_move(self, event: events.MouseMove) -> None:
try:
if self.app.mouse_captured:
widget = self.app.mouse_captured
region = self.get_widget_region(widget)
region = self.find_widget(widget).region
else:
widget, region = self.get_widget_at(event.x, event.y)
except errors.NoWidget:
Expand Down Expand Up @@ -195,7 +195,7 @@ async def forward_event(self, event: events.Event) -> None:
try:
if self.app.mouse_captured:
widget = self.app.mouse_captured
region = self.get_widget_region(widget)
region = self.find_widget(widget).region
else:
widget, region = self.get_widget_at(event.x, event.y)
except errors.NoWidget:
Expand Down
Loading