mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
gemoetry split
This commit is contained in:
@@ -20,17 +20,20 @@ class BasicApp(App):
|
|||||||
Widget(id="uber2-child2"),
|
Widget(id="uber2-child2"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
uber1 = Widget(
|
||||||
|
Placeholder(id="child1", classes={"list-item"}),
|
||||||
|
Placeholder(id="child2", classes={"list-item"}),
|
||||||
|
Placeholder(id="child3", classes={"list-item"}),
|
||||||
|
Placeholder(classes={"list-item"}),
|
||||||
|
Placeholder(classes={"list-item"}),
|
||||||
|
Placeholder(classes={"list-item"}),
|
||||||
|
Placeholder(classes={"list-item"}),
|
||||||
|
# Placeholder(id="child3", classes={"list-item"}),
|
||||||
|
)
|
||||||
|
uber1.show_vertical_scrollbar = True
|
||||||
|
|
||||||
self.mount(
|
self.mount(
|
||||||
uber1=Widget(
|
uber1=uber1
|
||||||
Placeholder(id="child1", classes={"list-item"}),
|
|
||||||
Placeholder(id="child2", classes={"list-item"}),
|
|
||||||
Placeholder(id="child3", classes={"list-item"}),
|
|
||||||
Placeholder(classes={"list-item"}),
|
|
||||||
Placeholder(classes={"list-item"}),
|
|
||||||
Placeholder(classes={"list-item"}),
|
|
||||||
Placeholder(classes={"list-item"}),
|
|
||||||
# Placeholder(id="child3", classes={"list-item"}),
|
|
||||||
),
|
|
||||||
# uber2=uber2,
|
# uber2=uber2,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from rich.style import Style
|
|||||||
from . import errors, log
|
from . import errors, log
|
||||||
from .geometry import Region, Offset, Size
|
from .geometry import Region, Offset, Size
|
||||||
|
|
||||||
|
from ._arrange import arrange
|
||||||
from ._loop import loop_last
|
from ._loop import loop_last
|
||||||
from ._types import Lines
|
from ._types import Lines
|
||||||
from .widget import Widget
|
from .widget import Widget
|
||||||
@@ -88,15 +89,20 @@ class Compositor:
|
|||||||
# All widgets considered in the arrangement
|
# All widgets considered in the arrangement
|
||||||
# Not this may be a supperset of self.map.keys() as some widgets may be invisible for various reasons
|
# Not this may be a supperset of self.map.keys() as some widgets may be invisible for various reasons
|
||||||
self.widgets: set[Widget] = set()
|
self.widgets: set[Widget] = set()
|
||||||
|
|
||||||
|
# The top level widget
|
||||||
self.root: Widget | None = None
|
self.root: Widget | None = None
|
||||||
|
|
||||||
# Dimensions of the arrangement
|
# Dimensions of the arrangement
|
||||||
self.size = Size(0, 0)
|
self.size = Size(0, 0)
|
||||||
|
|
||||||
|
# A mapping of Widget on to region, and clip region
|
||||||
|
# The clip region can be considered the window through which a widget is viewed
|
||||||
self.regions: dict[Widget, tuple[Region, Region]] = {}
|
self.regions: dict[Widget, tuple[Region, Region]] = {}
|
||||||
|
|
||||||
|
# The points in each line where the line bisects the left and right edges of the widget
|
||||||
self._cuts: list[list[int]] | None = None
|
self._cuts: list[list[int]] | None = None
|
||||||
self._require_update: bool = True
|
self._require_update: bool = True
|
||||||
self.background = ""
|
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
yield "size", self.size
|
yield "size", self.size
|
||||||
@@ -202,9 +208,15 @@ class Compositor:
|
|||||||
total_region = region.size.region
|
total_region = region.size.region
|
||||||
sub_clip = clip.intersection(region)
|
sub_clip = clip.intersection(region)
|
||||||
|
|
||||||
placements, arranged_widgets = widget.layout.arrange(
|
# for chrome_widget, chrome_region in widget.arrange_chrome(region.size):
|
||||||
widget, region.size, scroll
|
# map[chrome_widget] = RenderRegion(
|
||||||
)
|
# chrome_region + layout_offset,
|
||||||
|
# order,
|
||||||
|
# clip,
|
||||||
|
# total_region.size,
|
||||||
|
# )
|
||||||
|
|
||||||
|
placements, arranged_widgets = arrange(widget, region.size, scroll)
|
||||||
|
|
||||||
widgets.update(arranged_widgets)
|
widgets.update(arranged_widgets)
|
||||||
placements = sorted(placements, key=attrgetter("order"))
|
placements = sorted(placements, key=attrgetter("order"))
|
||||||
@@ -219,6 +231,16 @@ class Compositor:
|
|||||||
sub_clip,
|
sub_clip,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for chrome_widget, chrome_region in widget.arrange_chrome(region.size):
|
||||||
|
render_region = RenderRegion(
|
||||||
|
chrome_region + region.origin + layout_offset,
|
||||||
|
order,
|
||||||
|
clip,
|
||||||
|
total_region.size,
|
||||||
|
)
|
||||||
|
log(render_region)
|
||||||
|
map[chrome_widget] = render_region
|
||||||
|
|
||||||
map[widget] = RenderRegion(
|
map[widget] = RenderRegion(
|
||||||
region + layout_offset, order, clip, total_region.size
|
region + layout_offset, order, clip, total_region.size
|
||||||
)
|
)
|
||||||
@@ -245,8 +267,9 @@ class Compositor:
|
|||||||
|
|
||||||
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
|
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
|
||||||
"""Get the widget under the given point or None."""
|
"""Get the widget under the given point or None."""
|
||||||
|
contains = Region.contains
|
||||||
for widget, cropped_region, region, _ in self:
|
for widget, cropped_region, region, _ in self:
|
||||||
if cropped_region.contains(x, y):
|
if contains(cropped_region, x, y):
|
||||||
return widget, region
|
return widget, region
|
||||||
raise errors.NoWidget(f"No widget under screen coordinate ({x}, {y})")
|
raise errors.NoWidget(f"No widget under screen coordinate ({x}, {y})")
|
||||||
|
|
||||||
@@ -345,7 +368,7 @@ class Compositor:
|
|||||||
[
|
[
|
||||||
(widget, region, order, clip)
|
(widget, region, order, clip)
|
||||||
for widget, (region, order, clip, _) in self.map.items()
|
for widget, (region, order, clip, _) in self.map.items()
|
||||||
if widget.visible
|
if widget.visible and not widget.is_transparent
|
||||||
],
|
],
|
||||||
key=itemgetter(2),
|
key=itemgetter(2),
|
||||||
reverse=True,
|
reverse=True,
|
||||||
@@ -355,14 +378,12 @@ class Compositor:
|
|||||||
|
|
||||||
divide = Segment.divide
|
divide = Segment.divide
|
||||||
intersection = Region.intersection
|
intersection = Region.intersection
|
||||||
|
overlaps = Region.overlaps
|
||||||
|
|
||||||
for widget, region, _order, clip in widget_regions:
|
for widget, region, _order, clip in widget_regions:
|
||||||
if widget.is_transparent:
|
|
||||||
continue
|
|
||||||
if region in clip:
|
if region in clip:
|
||||||
lines = widget._get_lines()
|
yield region, clip, widget._get_lines()
|
||||||
yield region, clip, lines
|
elif overlaps(clip, region):
|
||||||
elif clip.overlaps(region):
|
|
||||||
lines = widget._get_lines()
|
lines = widget._get_lines()
|
||||||
new_x, new_y, new_width, new_height = intersection(region, clip)
|
new_x, new_y, new_width, new_height = intersection(region, clip)
|
||||||
delta_x = new_x - region.x
|
delta_x = new_x - region.x
|
||||||
@@ -377,7 +398,7 @@ class Compositor:
|
|||||||
cls, chops: list[dict[int, list[Segment] | None]]
|
cls, chops: list[dict[int, list[Segment] | None]]
|
||||||
) -> list[list[Segment]]:
|
) -> list[list[Segment]]:
|
||||||
|
|
||||||
# Pretty sure we don't need to sort the buck items
|
# Pretty sure we don't need to sort the bucket items
|
||||||
segment_lines = [
|
segment_lines = [
|
||||||
sum(
|
sum(
|
||||||
[line for line in bucket.values() if line is not None],
|
[line for line in bucket.values() if line is not None],
|
||||||
@@ -432,6 +453,7 @@ class Compositor:
|
|||||||
first_cut, last_cut = render_region.x_extents
|
first_cut, last_cut = render_region.x_extents
|
||||||
final_cuts = [cut for cut in cuts[y] if (last_cut >= cut >= first_cut)]
|
final_cuts = [cut for cut in cuts[y] if (last_cut >= cut >= first_cut)]
|
||||||
|
|
||||||
|
# TODO: Suspect this may break for region not on cut boundaries
|
||||||
if len(final_cuts) == 2:
|
if len(final_cuts) == 2:
|
||||||
# Two cuts, which means the entire line
|
# Two cuts, which means the entire line
|
||||||
cut_segments = [line]
|
cut_segments = [line]
|
||||||
@@ -440,8 +462,8 @@ class Compositor:
|
|||||||
render_x = render_region.x
|
render_x = render_region.x
|
||||||
relative_cuts = [cut - render_x for cut in final_cuts]
|
relative_cuts = [cut - render_x for cut in final_cuts]
|
||||||
_, *cut_segments = divide(line, relative_cuts)
|
_, *cut_segments = divide(line, relative_cuts)
|
||||||
# Since we are painting front to back, the first segments for a cut "wins"
|
|
||||||
|
|
||||||
|
# Since we are painting front to back, the first segments for a cut "wins"
|
||||||
chops_line = chops[y]
|
chops_line = chops[y]
|
||||||
for cut, segments in zip(final_cuts, cut_segments):
|
for cut, segments in zip(final_cuts, cut_segments):
|
||||||
if chops_line[cut] is None:
|
if chops_line[cut] is None:
|
||||||
|
|||||||
@@ -488,6 +488,95 @@ class Region(NamedTuple):
|
|||||||
)
|
)
|
||||||
return union_region
|
return union_region
|
||||||
|
|
||||||
|
def split(self, cut_x: int, cut_y: int) -> tuple[Region, Region, Region, Region]:
|
||||||
|
"""Split a region in to 4 from given x and y offsets (cuts).
|
||||||
|
|
||||||
|
cut_x ↓
|
||||||
|
┌────────┐┌───┐
|
||||||
|
│ ││ │
|
||||||
|
│ ││ │
|
||||||
|
│ ││ │
|
||||||
|
cut_y → └────────┘└───┘
|
||||||
|
┌────────┐┌───┐
|
||||||
|
│ ││ │
|
||||||
|
└────────┘└───┘
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cut_x (int): Offset from self.x where the cut should be made. If negative, the cut
|
||||||
|
is taken from the right edge.
|
||||||
|
cut_y (int): Offset from self.y where the cut should be made. If negative, the cut
|
||||||
|
is taken from the lower edge.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[Region, Region, Region, Region]: Four new regions which add up to the original (self).
|
||||||
|
"""
|
||||||
|
|
||||||
|
x, y, width, height = self
|
||||||
|
if cut_x < 0:
|
||||||
|
cut_x = width + cut_x
|
||||||
|
if cut_y < 0:
|
||||||
|
cut_y = height + cut_y
|
||||||
|
|
||||||
|
_Region = Region
|
||||||
|
return (
|
||||||
|
_Region(x, y, cut_x, cut_y),
|
||||||
|
_Region(x + cut_x, y, width - cut_x, cut_y),
|
||||||
|
_Region(x, y + cut_y, cut_x, height - cut_y),
|
||||||
|
_Region(x + cut_x, y + cut_y, width - cut_x, height - cut_y),
|
||||||
|
)
|
||||||
|
|
||||||
|
def split_vertical(self, cut: int) -> tuple[Region, Region]:
|
||||||
|
"""Split a region in to two, from a given x offset.
|
||||||
|
|
||||||
|
cut ↓
|
||||||
|
┌────────┐┌───┐
|
||||||
|
│ ││ │
|
||||||
|
│ ││ │
|
||||||
|
└────────┘└───┘
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cut (int): An offset from self.x where the cut should be made. If cut is negative,
|
||||||
|
it is taken from the right edge.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[Region, Region]: Two regions, which add up to the original (self).
|
||||||
|
"""
|
||||||
|
|
||||||
|
x, y, width, height = self
|
||||||
|
if cut < 0:
|
||||||
|
cut = width + cut
|
||||||
|
|
||||||
|
return (
|
||||||
|
Region(x, y, cut, height),
|
||||||
|
Region(x + cut, y, width - cut, height),
|
||||||
|
)
|
||||||
|
|
||||||
|
def split_horizontal(self, cut: int) -> tuple[Region, Region]:
|
||||||
|
"""Split a region in to two, from a given x offset.
|
||||||
|
|
||||||
|
┌─────────┐
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
cut → └─────────┘
|
||||||
|
┌─────────┐
|
||||||
|
└─────────┘
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cut (int): An offset from self.x where the cut should be made. May be negative,
|
||||||
|
for the offset to start from the right edge.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[Region, Region]: Two regions, which add up to the original (self).
|
||||||
|
"""
|
||||||
|
x, y, width, height = self
|
||||||
|
if cut < 0:
|
||||||
|
cut = height + cut
|
||||||
|
|
||||||
|
return (
|
||||||
|
Region(x, y, width, cut),
|
||||||
|
Region(x, y + cut, width, height - cut),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Spacing(NamedTuple):
|
class Spacing(NamedTuple):
|
||||||
"""The spacing around a renderable."""
|
"""The spacing around a renderable."""
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class Layout(ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def arrange(
|
def arrange(
|
||||||
self, parent: Widget, size: Size, scroll: Offset
|
self, parent: Widget, size: Size, scroll: Offset
|
||||||
) -> tuple[Iterable[WidgetPlacement], set[Widget]]:
|
) -> tuple[list[WidgetPlacement], set[Widget]]:
|
||||||
"""Generate a layout map that defines where on the screen the widgets will be drawn.
|
"""Generate a layout map that defines where on the screen the widgets will be drawn.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class DockLayout(Layout):
|
|||||||
|
|
||||||
def arrange(
|
def arrange(
|
||||||
self, parent: Widget, size: Size, scroll: Offset
|
self, parent: Widget, size: Size, scroll: Offset
|
||||||
) -> tuple[Iterable[WidgetPlacement], set[Widget]]:
|
) -> tuple[list[WidgetPlacement], set[Widget]]:
|
||||||
|
|
||||||
width, height = size
|
width, height = size
|
||||||
layout_region = Region(0, 0, width, height)
|
layout_region = Region(0, 0, width, height)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Iterable
|
|
||||||
|
|
||||||
|
|
||||||
from textual.geometry import Size, Offset, Region
|
from textual.geometry import Size, Offset, Region
|
||||||
from textual.layout import Layout, WidgetPlacement
|
from textual.layout import Layout, WidgetPlacement
|
||||||
@@ -27,11 +25,9 @@ class HorizontalLayout(Layout):
|
|||||||
parent_size = parent.size
|
parent_size = parent.size
|
||||||
|
|
||||||
for widget in parent.children:
|
for widget in parent.children:
|
||||||
|
|
||||||
(content_width, content_height), margin = widget.styles.get_box_model(
|
(content_width, content_height), margin = widget.styles.get_box_model(
|
||||||
size, parent_size
|
size, parent_size
|
||||||
)
|
)
|
||||||
|
|
||||||
region = Region(margin.left + x, margin.top, content_width, content_height)
|
region = Region(margin.left + x, margin.top, content_width, content_height)
|
||||||
max_height = max(max_height, content_height + margin.height)
|
max_height = max(max_height, content_height + margin.height)
|
||||||
add_placement(WidgetPlacement(region, widget, 0))
|
add_placement(WidgetPlacement(region, widget, 0))
|
||||||
|
|||||||
@@ -27,11 +27,9 @@ class VerticalLayout(Layout):
|
|||||||
parent_size = parent.size
|
parent_size = parent.size
|
||||||
|
|
||||||
for widget in parent.children:
|
for widget in parent.children:
|
||||||
|
|
||||||
(content_width, content_height), margin = widget.styles.get_box_model(
|
(content_width, content_height), margin = widget.styles.get_box_model(
|
||||||
size, parent_size
|
size, parent_size
|
||||||
)
|
)
|
||||||
|
|
||||||
region = Region(margin.left, y + margin.top, content_width, content_height)
|
region = Region(margin.left, y + margin.top, content_width, content_height)
|
||||||
max_width = max(max_width, content_width + margin.width)
|
max_width = max(max_width, content_width + margin.width)
|
||||||
add_placement(WidgetPlacement(region, widget, 0))
|
add_placement(WidgetPlacement(region, widget, 0))
|
||||||
|
|||||||
@@ -95,10 +95,12 @@ class Screen(Widget):
|
|||||||
try:
|
try:
|
||||||
hidden, shown, resized = self._compositor.reflow(self, self.size)
|
hidden, shown, resized = self._compositor.reflow(self, self.size)
|
||||||
|
|
||||||
|
Hide = events.Hide
|
||||||
|
Show = events.Show
|
||||||
for widget in hidden:
|
for widget in hidden:
|
||||||
widget.post_message_no_wait(events.Hide(self))
|
widget.post_message_no_wait(Hide(self))
|
||||||
for widget in shown:
|
for widget in shown:
|
||||||
widget.post_message_no_wait(events.Show(self))
|
widget.post_message_no_wait(Show(self))
|
||||||
|
|
||||||
send_resize = shown | resized
|
send_resize = shown | resized
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
import rich.repr
|
import rich.repr
|
||||||
from rich.color import Color
|
from rich.color import Color
|
||||||
from rich.console import ConsoleOptions, RenderResult, RenderableType
|
from rich.console import ConsoleOptions, RenderResult, RenderableType
|
||||||
from rich.segment import Segment
|
from rich.segment import Segment, Segments
|
||||||
from rich.style import Style, StyleType
|
from rich.style import Style, StyleType
|
||||||
|
|
||||||
from textual.reactive import Reactive
|
from textual.reactive import Reactive
|
||||||
|
|||||||
@@ -36,8 +36,10 @@ from .layout import Layout
|
|||||||
from .reactive import Reactive, watch
|
from .reactive import Reactive, watch
|
||||||
from .renderables.opacity import Opacity
|
from .renderables.opacity import Opacity
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .screen import Screen
|
from .screen import Screen
|
||||||
|
from .scrollbar import ScrollBar
|
||||||
|
|
||||||
|
|
||||||
class RenderCache(NamedTuple):
|
class RenderCache(NamedTuple):
|
||||||
@@ -79,6 +81,9 @@ class Widget(DOMNode):
|
|||||||
self.render_cache: RenderCache | None = None
|
self.render_cache: RenderCache | None = None
|
||||||
self.highlight_style: Style | None = None
|
self.highlight_style: Style | None = None
|
||||||
|
|
||||||
|
self._vertical_scrollbar: ScrollBar | None = None
|
||||||
|
self._horizontal_scrollbar: ScrollBar | None = None
|
||||||
|
|
||||||
super().__init__(name=name, id=id, classes=classes)
|
super().__init__(name=name, id=id, classes=classes)
|
||||||
self.add_children(*children)
|
self.add_children(*children)
|
||||||
|
|
||||||
@@ -87,6 +92,54 @@ class Widget(DOMNode):
|
|||||||
scroll_x = Reactive(0)
|
scroll_x = Reactive(0)
|
||||||
scroll_y = Reactive(0)
|
scroll_y = Reactive(0)
|
||||||
virtual_size = Reactive(Size(0, 0))
|
virtual_size = Reactive(Size(0, 0))
|
||||||
|
show_vertical_scrollbar = Reactive(False)
|
||||||
|
show_horizontal_scrollbar = Reactive(False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vertical_scrollbar(self) -> ScrollBar:
|
||||||
|
"""Get a vertical scrollbar (create if necessary)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScrollBar: ScrollBar Widget.
|
||||||
|
"""
|
||||||
|
from .scrollbar import ScrollBar
|
||||||
|
|
||||||
|
if self._vertical_scrollbar is not None:
|
||||||
|
return self._vertical_scrollbar
|
||||||
|
self._vertical_scrollbar = scroll_bar = ScrollBar(
|
||||||
|
vertical=True, name="vertical"
|
||||||
|
)
|
||||||
|
self.app.register(self, scroll_bar)
|
||||||
|
return scroll_bar
|
||||||
|
|
||||||
|
@property
|
||||||
|
def horizontal_scrollbar(self) -> ScrollBar:
|
||||||
|
"""Get a vertical scrollbar (create if necessary)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScrollBar: ScrollBar Widget.
|
||||||
|
"""
|
||||||
|
from .scrollbar import ScrollBar
|
||||||
|
|
||||||
|
if self._horizontal_scrollbar is not None:
|
||||||
|
return self._horizontal_scrollbar
|
||||||
|
self._horizontal_scrollbar = scroll_bar = ScrollBar(
|
||||||
|
vertical=True, name="vertical"
|
||||||
|
)
|
||||||
|
self.app.register(self, scroll_bar)
|
||||||
|
return scroll_bar
|
||||||
|
|
||||||
|
@property
|
||||||
|
def scrollbars_enabled(self) -> tuple[bool, bool]:
|
||||||
|
"""A tuple of booleans that indicate if scrollbars are enabled.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[bool, bool]: A tuple of (<vertical scrollbar enabled>, <horizontal scrollbar enabled>)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.layout is None:
|
||||||
|
return False, False
|
||||||
|
return self.show_vertical_scrollbar, self.show_horizontal_scrollbar
|
||||||
|
|
||||||
def __init_subclass__(cls, can_focus: bool = True) -> None:
|
def __init_subclass__(cls, can_focus: bool = True) -> None:
|
||||||
super().__init_subclass__()
|
super().__init_subclass__()
|
||||||
@@ -102,6 +155,30 @@ class Widget(DOMNode):
|
|||||||
if pseudo_classes:
|
if pseudo_classes:
|
||||||
yield "pseudo_classes", set(pseudo_classes)
|
yield "pseudo_classes", set(pseudo_classes)
|
||||||
|
|
||||||
|
def arrange_chrome(self, size: Size) -> Iterable[tuple[Widget, Region]]:
|
||||||
|
region = size.region
|
||||||
|
show_vertical_scrollbar, show_horizontal_scrollbar = self.scrollbars_enabled
|
||||||
|
|
||||||
|
if show_horizontal_scrollbar and show_vertical_scrollbar:
|
||||||
|
(
|
||||||
|
region,
|
||||||
|
vertical_scrollbar_region,
|
||||||
|
horizontal_scrollbar_region,
|
||||||
|
_,
|
||||||
|
) = region.split(-1, -1)
|
||||||
|
if vertical_scrollbar_region:
|
||||||
|
yield self.vertical_scrollbar, vertical_scrollbar_region
|
||||||
|
if horizontal_scrollbar_region:
|
||||||
|
yield self.horizontal_scrollbar, horizontal_scrollbar_region
|
||||||
|
elif show_vertical_scrollbar:
|
||||||
|
region, scrollbar_region = region.split_vertical(-1)
|
||||||
|
if scrollbar_region:
|
||||||
|
yield self.vertical_scrollbar, scrollbar_region
|
||||||
|
elif show_horizontal_scrollbar:
|
||||||
|
region, scrollbar_region = region.split_horizontal(-1)
|
||||||
|
if scrollbar_region:
|
||||||
|
yield self.horizontal_scrollbar, scrollbar_region
|
||||||
|
|
||||||
def get_pseudo_classes(self) -> Iterable[str]:
|
def get_pseudo_classes(self) -> Iterable[str]:
|
||||||
"""Pseudo classes for a widget"""
|
"""Pseudo classes for a widget"""
|
||||||
if self._mouse_over:
|
if self._mouse_over:
|
||||||
|
|||||||
@@ -11,11 +11,14 @@ class Static(Widget):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
renderable: RenderableType,
|
renderable: RenderableType,
|
||||||
|
*,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: set[str] | None = None,
|
||||||
style: StyleType = "",
|
style: StyleType = "",
|
||||||
padding: PaddingDimensions = 0,
|
padding: PaddingDimensions = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(name)
|
super().__init__(name=name, id=id, classes=classes)
|
||||||
self.renderable = renderable
|
self.renderable = renderable
|
||||||
self.style = style
|
self.style = style
|
||||||
self.padding = padding
|
self.padding = padding
|
||||||
|
|||||||
@@ -307,3 +307,49 @@ def test_spacing_add():
|
|||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
Spacing(1, 2, 3, 4) + "foo"
|
Spacing(1, 2, 3, 4) + "foo"
|
||||||
|
|
||||||
|
|
||||||
|
def test_split():
|
||||||
|
assert Region(10, 5, 22, 15).split(10, 5) == (
|
||||||
|
Region(10, 5, 10, 5),
|
||||||
|
Region(20, 5, 12, 10),
|
||||||
|
Region(10, 10, 10, 10),
|
||||||
|
Region(20, 10, 10, 5),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_split_negative():
|
||||||
|
assert Region(10, 5, 22, 15).split(-1, -1) == (
|
||||||
|
Region(10, 5, 21, 14),
|
||||||
|
Region(31, 5, 1, 14),
|
||||||
|
Region(10, 19, 21, 1),
|
||||||
|
Region(31, 19, 1, 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_split_vertical():
|
||||||
|
assert Region(10, 5, 22, 15).split_vertical(10) == (
|
||||||
|
Region(10, 5, 10, 15),
|
||||||
|
Region(20, 5, 12, 15),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_split_vertical_negative():
|
||||||
|
assert Region(10, 5, 22, 15).split_vertical(-1) == (
|
||||||
|
Region(10, 5, 21, 15),
|
||||||
|
Region(31, 5, 1, 15),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_split_horizontal():
|
||||||
|
assert Region(10, 5, 22, 15).split_horizontal(5) == (
|
||||||
|
Region(10, 5, 22, 5),
|
||||||
|
Region(10, 10, 22, 10),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_split_horizontal_negative():
|
||||||
|
assert Region(10, 5, 22, 15).split_horizontal(-1) == (
|
||||||
|
Region(10, 5, 22, 14),
|
||||||
|
Region(10, 19, 22, 1),
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user