mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
arrange refactor and scroll widget
This commit is contained in:
@@ -65,8 +65,12 @@ class SimpleAnimation(Animation):
|
||||
), "end_value must be animatable"
|
||||
value = self.start_value.blend(self.end_value, eased_factor)
|
||||
else:
|
||||
assert isinstance(self.start_value, float), "`start_value` must be float"
|
||||
assert isinstance(self.end_value, float), "`end_value` must be float"
|
||||
assert isinstance(
|
||||
self.start_value, (int, float)
|
||||
), f"`start_value` must be float, not {self.start_value!r}"
|
||||
assert isinstance(
|
||||
self.end_value, (int, float)
|
||||
), f"`end_value` must be float, not {self.end_value!r}"
|
||||
if self.end_value > self.start_value:
|
||||
eased_factor = self.easing(factor)
|
||||
value = (
|
||||
|
||||
@@ -208,14 +208,6 @@ class Compositor:
|
||||
total_region = region.size.region
|
||||
sub_clip = clip.intersection(region)
|
||||
|
||||
# for chrome_widget, chrome_region in widget.arrange_chrome(region.size):
|
||||
# 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)
|
||||
@@ -232,13 +224,14 @@ class Compositor:
|
||||
)
|
||||
|
||||
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(
|
||||
|
||||
@@ -485,6 +485,17 @@ class App(DOMNode):
|
||||
for _widget_id, widget in name_widgets:
|
||||
widget.post_message_no_wait(events.Mount(sender=parent))
|
||||
|
||||
def start_widget(self, parent: Widget, widget: Widget) -> None:
|
||||
"""Start a widget (run it's task) so that it can receive messages.
|
||||
|
||||
Args:
|
||||
parent (Widget): The parent of the Widget.
|
||||
widget (Widget): The Widget to start.
|
||||
"""
|
||||
widget.set_parent(parent)
|
||||
widget.start_messages()
|
||||
widget.post_message_no_wait(events.Mount(sender=parent))
|
||||
|
||||
def is_mounted(self, widget: Widget) -> bool:
|
||||
return widget in self.registry
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ class Screen(Widget):
|
||||
await self.refresh_layout()
|
||||
|
||||
async def on_resize(self, event: events.Resize) -> None:
|
||||
self._update_size(event.size, event.virtual_size)
|
||||
self.size_updated(event.size, event.virtual_size)
|
||||
await self.refresh_layout()
|
||||
event.stop()
|
||||
|
||||
|
||||
@@ -205,6 +205,9 @@ class ScrollBar(Widget):
|
||||
style=style,
|
||||
)
|
||||
|
||||
async def on_event(self, event) -> None:
|
||||
await super().on_event(event)
|
||||
|
||||
async def on_enter(self, event: events.Enter) -> None:
|
||||
self.mouse_over = True
|
||||
|
||||
@@ -235,7 +238,7 @@ class ScrollBar(Widget):
|
||||
self.grabbed = None
|
||||
|
||||
async def on_mouse_move(self, event: events.MouseMove) -> None:
|
||||
if self.grabbed:
|
||||
if self.grabbed and self.window_size:
|
||||
x: float | None = None
|
||||
y: float | None = None
|
||||
if self.vertical:
|
||||
|
||||
@@ -29,7 +29,7 @@ from ._callback import invoke
|
||||
from ._context import active_app
|
||||
from ._types import Lines
|
||||
from .dom import DOMNode
|
||||
from .geometry import Offset, Region, Size
|
||||
from .geometry import clamp, Offset, Region, Size
|
||||
from .message import Message
|
||||
from . import messages
|
||||
from .layout import Layout
|
||||
@@ -38,8 +38,7 @@ from .renderables.opacity import Opacity
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .screen import Screen
|
||||
from .scrollbar import ScrollBar
|
||||
from .scrollbar import ScrollBar, ScrollTo
|
||||
|
||||
|
||||
class RenderCache(NamedTuple):
|
||||
@@ -89,14 +88,42 @@ class Widget(DOMNode):
|
||||
|
||||
has_focus = Reactive(False)
|
||||
mouse_over = Reactive(False)
|
||||
scroll_x = Reactive(0)
|
||||
scroll_y = Reactive(0)
|
||||
scroll_x = Reactive(0.0)
|
||||
scroll_y = Reactive(0.0)
|
||||
scroll_target_x = Reactive(0.0)
|
||||
scroll_target_y = Reactive(0.0)
|
||||
virtual_size = Reactive(Size(0, 0))
|
||||
show_vertical_scrollbar = Reactive(False)
|
||||
show_horizontal_scrollbar = Reactive(False)
|
||||
|
||||
async def watch_scroll_x(self, new_value: float) -> None:
|
||||
self.hscroll.position = round(new_value)
|
||||
|
||||
async def watch_scroll_y(self, new_value: float) -> None:
|
||||
self.vscroll.position = round(new_value)
|
||||
|
||||
def validate_scroll_x(self, value: float) -> float:
|
||||
return clamp(value, 0, self.max_scroll_x)
|
||||
|
||||
def validate_scroll_target_x(self, value: float) -> float:
|
||||
return clamp(value, 0, self.max_scroll_x)
|
||||
|
||||
def validate_scroll_y(self, value: float) -> float:
|
||||
return clamp(value, 0, self.max_scroll_y)
|
||||
|
||||
def validate_scroll_target_y(self, value: float) -> float:
|
||||
return clamp(value, 0, self.max_scroll_y)
|
||||
|
||||
@property
|
||||
def vertical_scrollbar(self) -> ScrollBar:
|
||||
def max_scroll_y(self) -> float:
|
||||
return max(0, self.virtual_size.height - self.size.height)
|
||||
|
||||
@property
|
||||
def max_scroll_x(self) -> float:
|
||||
return max(0, self.virtual_size.width - self.size.width)
|
||||
|
||||
@property
|
||||
def vscroll(self) -> ScrollBar:
|
||||
"""Get a vertical scrollbar (create if necessary)
|
||||
|
||||
Returns:
|
||||
@@ -109,11 +136,11 @@ class Widget(DOMNode):
|
||||
self._vertical_scrollbar = scroll_bar = ScrollBar(
|
||||
vertical=True, name="vertical"
|
||||
)
|
||||
self.app.register(self, scroll_bar)
|
||||
self.app.start_widget(self, scroll_bar)
|
||||
return scroll_bar
|
||||
|
||||
@property
|
||||
def horizontal_scrollbar(self) -> ScrollBar:
|
||||
def hscroll(self) -> ScrollBar:
|
||||
"""Get a vertical scrollbar (create if necessary)
|
||||
|
||||
Returns:
|
||||
@@ -127,6 +154,7 @@ class Widget(DOMNode):
|
||||
vertical=True, name="vertical"
|
||||
)
|
||||
self.app.register(self, scroll_bar)
|
||||
self.app.start_widget(self, scroll_bar)
|
||||
return scroll_bar
|
||||
|
||||
@property
|
||||
@@ -167,17 +195,17 @@ class Widget(DOMNode):
|
||||
_,
|
||||
) = region.split(-1, -1)
|
||||
if vertical_scrollbar_region:
|
||||
yield self.vertical_scrollbar, vertical_scrollbar_region
|
||||
yield self.vscroll, vertical_scrollbar_region
|
||||
if horizontal_scrollbar_region:
|
||||
yield self.horizontal_scrollbar, horizontal_scrollbar_region
|
||||
yield self.hscroll, horizontal_scrollbar_region
|
||||
elif show_vertical_scrollbar:
|
||||
region, scrollbar_region = region.split_vertical(-1)
|
||||
if scrollbar_region:
|
||||
yield self.vertical_scrollbar, scrollbar_region
|
||||
yield self.vscroll, scrollbar_region
|
||||
elif show_horizontal_scrollbar:
|
||||
region, scrollbar_region = region.split_horizontal(-1)
|
||||
if scrollbar_region:
|
||||
yield self.horizontal_scrollbar, scrollbar_region
|
||||
yield self.hscroll, scrollbar_region
|
||||
|
||||
def get_pseudo_classes(self) -> Iterable[str]:
|
||||
"""Pseudo classes for a widget"""
|
||||
@@ -246,7 +274,7 @@ class Widget(DOMNode):
|
||||
|
||||
@property
|
||||
def scroll(self) -> Offset:
|
||||
return Offset(self.scroll_x, self.scroll_y)
|
||||
return Offset(int(self.scroll_x), int(self.scroll_y))
|
||||
|
||||
@property
|
||||
def is_transparent(self) -> bool:
|
||||
@@ -255,7 +283,7 @@ class Widget(DOMNode):
|
||||
Returns:
|
||||
bool: ``True`` if there is background color, otherwise ``False``.
|
||||
"""
|
||||
return self.layout is not None or self.styles.text.bgcolor is None
|
||||
return self.layout is not None and self.styles.text.bgcolor is None
|
||||
|
||||
@property
|
||||
def console(self) -> Console:
|
||||
@@ -284,9 +312,14 @@ class Widget(DOMNode):
|
||||
def on_style_change(self) -> None:
|
||||
self.clear_render_cache()
|
||||
|
||||
def _update_size(self, size: Size, virtual_size: Size) -> None:
|
||||
def size_updated(self, size: Size, virtual_size: Size) -> None:
|
||||
self._size = size
|
||||
self._virtual_size = virtual_size
|
||||
if self.show_vertical_scrollbar:
|
||||
self.vscroll.virtual_size = virtual_size.height
|
||||
self.vscroll.window_size = size.height
|
||||
self.vscroll.refresh()
|
||||
self.log(virtual_size.height, size.height)
|
||||
|
||||
def render_lines(self) -> None:
|
||||
width, height = self.size
|
||||
@@ -371,7 +404,7 @@ class Widget(DOMNode):
|
||||
return await super().post_message(message)
|
||||
|
||||
async def on_resize(self, event: events.Resize) -> None:
|
||||
self._update_size(event.size, event.virtual_size)
|
||||
self.size_updated(event.size, event.virtual_size)
|
||||
self.refresh()
|
||||
|
||||
async def on_idle(self, event: events.Idle) -> None:
|
||||
@@ -422,3 +455,13 @@ class Widget(DOMNode):
|
||||
async def on_key(self, event: events.Key) -> None:
|
||||
if await self.dispatch_key(event):
|
||||
event.prevent_default()
|
||||
|
||||
async def handle_scroll_to(self, message: ScrollTo) -> None:
|
||||
if message.x is not None:
|
||||
self.scroll_target_x = message.x
|
||||
if message.y is not None:
|
||||
self.scroll_target_y = message.y
|
||||
message.stop()
|
||||
# self.refresh(layout=True)
|
||||
self.animate("scroll_x", self.scroll_target_x, speed=150, easing="out_cubic")
|
||||
self.animate("scroll_y", self.scroll_target_y, speed=150, easing="out_cubic")
|
||||
|
||||
Reference in New Issue
Block a user