mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
cursor and hover
This commit is contained in:
@@ -29,7 +29,7 @@ from ._context import active_app
|
||||
from ._types import Lines
|
||||
from .dom import DOMNode
|
||||
from ._layout import ArrangeResult
|
||||
from .geometry import clamp, Offset, Region, Size
|
||||
from .geometry import clamp, Offset, Region, Size, Spacing
|
||||
from .layouts.vertical import VerticalLayout
|
||||
from .message import Message
|
||||
from . import messages
|
||||
@@ -624,12 +624,15 @@ class Widget(DOMNode):
|
||||
|
||||
return any(scrolls)
|
||||
|
||||
def scroll_to_region(self, region: Region, *, animate: bool = True) -> bool:
|
||||
def scroll_to_region(
|
||||
self, region: Region, *, spacing: Spacing | None = None, animate: bool = True
|
||||
) -> bool:
|
||||
"""Scrolls a given region in to view.
|
||||
|
||||
Args:
|
||||
region (Region): A region that should be visible.
|
||||
animate (bool, optional): Enable animation. Defaults to True.
|
||||
spacing (Spacing): Space to subtract from the window region.
|
||||
|
||||
Returns:
|
||||
bool: True if the window was scrolled.
|
||||
@@ -637,50 +640,41 @@ class Widget(DOMNode):
|
||||
|
||||
scroll_x, scroll_y = self.scroll_offset
|
||||
width, height = self.region.size
|
||||
container_region = Region(scroll_x, scroll_y, width, height)
|
||||
window = Region(scroll_x, scroll_y, width, height)
|
||||
if spacing is not None:
|
||||
window = window.shrink(spacing)
|
||||
|
||||
if region in container_region:
|
||||
# Widget is visible, nothing to do
|
||||
if region in window:
|
||||
# Widget is entirely visible, nothing to do
|
||||
return False
|
||||
|
||||
(
|
||||
container_left,
|
||||
container_top,
|
||||
container_right,
|
||||
container_bottom,
|
||||
) = container_region.corners
|
||||
(
|
||||
child_left,
|
||||
child_top,
|
||||
child_right,
|
||||
child_bottom,
|
||||
) = region.corners
|
||||
window_left, window_top, window_right, window_bottom = window.corners
|
||||
left, top, right, bottom = region.corners
|
||||
|
||||
delta_x = 0
|
||||
delta_y = 0
|
||||
delta_x = delta_y = 0
|
||||
|
||||
if not (
|
||||
(container_right >= child_left > container_left)
|
||||
and (container_right >= child_right > container_left)
|
||||
(window_right > left >= window_left)
|
||||
and (window_right > right >= window_left)
|
||||
):
|
||||
delta_x = min(
|
||||
child_left - container_left,
|
||||
child_left - (container_right - region.width),
|
||||
left - window_left,
|
||||
left - (window_right - region.width),
|
||||
key=abs,
|
||||
)
|
||||
|
||||
if not (
|
||||
(container_bottom >= child_top > container_top)
|
||||
and (container_bottom >= child_bottom > container_top)
|
||||
(window_bottom > top >= window_top)
|
||||
and (window_bottom > bottom >= window_top)
|
||||
):
|
||||
delta_y = min(
|
||||
child_top - container_top,
|
||||
child_top - (container_bottom - region.height),
|
||||
top - window_top,
|
||||
top - (window_bottom - region.height),
|
||||
key=abs,
|
||||
)
|
||||
|
||||
scrolled = self.scroll_relative(
|
||||
delta_x or None, delta_y or None, animate=abs(delta_y) != 1, duration=0.2
|
||||
delta_x or None, delta_y or None, animate=animate, duration=0.2
|
||||
)
|
||||
|
||||
return scrolled
|
||||
|
||||
@@ -16,7 +16,7 @@ from .. import events
|
||||
from .._cache import LRUCache
|
||||
from .._segment_tools import line_crop
|
||||
from .._types import Lines
|
||||
from ..geometry import clamp, Region, Size
|
||||
from ..geometry import clamp, Region, Size, Spacing
|
||||
from ..reactive import Reactive
|
||||
from .._profile import timer
|
||||
from ..scroll_view import ScrollView
|
||||
@@ -103,8 +103,7 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
}
|
||||
|
||||
DataTable > .datatable--highlight {
|
||||
background: $secondary;
|
||||
color: $text-secondary;
|
||||
background: $primary 20%;
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -135,7 +134,7 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
self._row_render_cache = LRUCache(1000)
|
||||
|
||||
self._cell_render_cache: LRUCache[tuple[int, int, Style], Lines]
|
||||
self._cell_render_cache = LRUCache(10000)
|
||||
self._cell_render_cache = LRUCache(1000)
|
||||
|
||||
self._line_cache: LRUCache[tuple[int, int, int, int], list[Segment]]
|
||||
self._line_cache = LRUCache(1000)
|
||||
@@ -144,13 +143,15 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
|
||||
show_header = Reactive(True)
|
||||
fixed_rows = Reactive(0)
|
||||
fixed_columns = Reactive(0)
|
||||
fixed_columns = Reactive(1)
|
||||
zebra_stripes = Reactive(False)
|
||||
header_height = Reactive(1)
|
||||
show_cursor = Reactive(True)
|
||||
cursor_type = Reactive(CELL)
|
||||
cursor_row = Reactive(0)
|
||||
cursor_column = Reactive(0)
|
||||
cursor_column = Reactive(1)
|
||||
hover_row = Reactive(0)
|
||||
hover_column = Reactive(0)
|
||||
|
||||
def _clear_caches(self) -> None:
|
||||
self._row_render_cache.clear()
|
||||
@@ -188,7 +189,7 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
len(self._y_offsets) + (self.header_height if self.show_header else 0),
|
||||
)
|
||||
|
||||
def _get_cursor_region(self, row_index: int, column_index: int) -> Region:
|
||||
def _get_cell_region(self, row_index: int, column_index: int) -> Region:
|
||||
row = self.rows[row_index]
|
||||
x = sum(column.width for column in self.columns[:column_index])
|
||||
width = self.columns[column_index].width
|
||||
@@ -256,6 +257,7 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
style: Style,
|
||||
width: int,
|
||||
cursor: bool = False,
|
||||
hover: bool = False,
|
||||
) -> Lines:
|
||||
"""Render the given cell.
|
||||
|
||||
@@ -268,9 +270,11 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
Returns:
|
||||
Lines: A list of segments per line.
|
||||
"""
|
||||
if hover:
|
||||
style += self.component_styles["datatable--highlight"].node.rich_style
|
||||
if cursor:
|
||||
style += self.component_styles["datatable--cursor"].node.rich_style
|
||||
cell_key = (row_index, column_index, style)
|
||||
cell_key = (row_index, column_index, style, cursor, hover)
|
||||
if cell_key not in self._cell_render_cache:
|
||||
style += Style.from_meta({"row": row_index, "column": column_index})
|
||||
height = (
|
||||
@@ -286,7 +290,12 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
return self._cell_render_cache[cell_key]
|
||||
|
||||
def _render_row(
|
||||
self, row_index: int, line_no: int, base_style: Style, cursor: int = -1
|
||||
self,
|
||||
row_index: int,
|
||||
line_no: int,
|
||||
base_style: Style,
|
||||
cursor_column: int = -1,
|
||||
hover_column: int = -1,
|
||||
) -> tuple[Lines, Lines]:
|
||||
"""Render a row in to lines for each cell.
|
||||
|
||||
@@ -299,7 +308,7 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
tuple[Lines, Lines]: Lines for fixed cells, and Lines for scrollable cells.
|
||||
"""
|
||||
|
||||
cache_key = (row_index, line_no, base_style)
|
||||
cache_key = (row_index, line_no, base_style, cursor_column, hover_column)
|
||||
|
||||
if cache_key in self._row_render_cache:
|
||||
return self._row_render_cache[cache_key]
|
||||
@@ -333,7 +342,8 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
column.index,
|
||||
row_style,
|
||||
column.width,
|
||||
cursor=cursor == column.index,
|
||||
cursor=cursor_column == column.index,
|
||||
hover=hover_column == column.index,
|
||||
)[line_no]
|
||||
for column in self.columns
|
||||
]
|
||||
@@ -373,20 +383,24 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
"""
|
||||
|
||||
width = self.region.width
|
||||
row_index, line_no = self._get_offsets(y)
|
||||
cursor_column = (
|
||||
self.cursor_column
|
||||
if (self.show_cursor and self.cursor_row == row_index)
|
||||
else -1
|
||||
)
|
||||
hover_column = self.hover_column if (self.hover_row == row_index) else -1
|
||||
|
||||
cache_key = (y, x1, x2, width)
|
||||
cache_key = (y, x1, x2, width, cursor_column, hover_column)
|
||||
if cache_key in self._line_cache:
|
||||
return self._line_cache[cache_key]
|
||||
|
||||
row_index, line_no = self._get_offsets(y)
|
||||
|
||||
fixed, scrollable = self._render_row(
|
||||
row_index,
|
||||
line_no,
|
||||
base_style,
|
||||
cursor=self.cursor_column
|
||||
if (self.show_cursor and self.cursor_row == row_index)
|
||||
else -1,
|
||||
cursor_column=cursor_column,
|
||||
hover_column=hover_column,
|
||||
)
|
||||
fixed_width = sum(column.width for column in self.columns[: self.fixed_columns])
|
||||
|
||||
@@ -440,41 +454,55 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
|
||||
return lines
|
||||
|
||||
def on_mouse_move(self, event):
|
||||
print(self.get_style_at(event.x, event.y).meta)
|
||||
def on_mouse_move(self, event: events.MouseMove):
|
||||
meta = self.get_style_at(event.x, event.y).meta
|
||||
self.hover_row = meta.get("row")
|
||||
self.hover_column = meta.get("column")
|
||||
|
||||
async def on_key(self, event) -> None:
|
||||
await self.dispatch_key(event)
|
||||
|
||||
def _get_cell_border(self) -> Spacing:
|
||||
top = self.header_height if self.show_header else 0
|
||||
top += sum(
|
||||
self.rows[row_index].height
|
||||
for row_index in range(self.fixed_rows)
|
||||
if row_index in self.rows
|
||||
)
|
||||
left = sum(column.width for column in self.columns[: self.fixed_columns])
|
||||
return Spacing(top, 0, 0, left)
|
||||
|
||||
def _scroll_cursor_in_to_view(self) -> None:
|
||||
region = self._get_cursor_region(self.cursor_row, self.cursor_column)
|
||||
print("CURSOR", region)
|
||||
self.scroll_to_region(region)
|
||||
region = self._get_cell_region(self.cursor_row, self.cursor_column)
|
||||
spacing = self._get_cell_border()
|
||||
self.scroll_to_region(region, animate=False, spacing=spacing)
|
||||
|
||||
def on_click(self, event: events.Click) -> None:
|
||||
meta = self.get_style_at(event.x, event.y).meta
|
||||
self.cursor_row = meta.get("row")
|
||||
self.cursor_column = meta.get("column")
|
||||
self._scroll_cursor_in_to_view()
|
||||
|
||||
def key_down(self, event: events.Key):
|
||||
self.cursor_row += 1
|
||||
self._clear_caches()
|
||||
event.stop()
|
||||
event.prevent_default()
|
||||
self._scroll_cursor_in_to_view()
|
||||
|
||||
def key_up(self, event: events.Key):
|
||||
self.cursor_row -= 1
|
||||
self._clear_caches()
|
||||
event.stop()
|
||||
event.prevent_default()
|
||||
self._scroll_cursor_in_to_view()
|
||||
|
||||
def key_right(self, event: events.Key):
|
||||
self.cursor_column += 1
|
||||
self._clear_caches()
|
||||
event.stop()
|
||||
event.prevent_default()
|
||||
self._scroll_cursor_in_to_view()
|
||||
|
||||
def key_left(self, event: events.Key):
|
||||
self.cursor_column -= 1
|
||||
self._clear_caches()
|
||||
event.stop()
|
||||
event.prevent_default()
|
||||
self._scroll_cursor_in_to_view()
|
||||
|
||||
Reference in New Issue
Block a user