cursor and hover

This commit is contained in:
Will McGugan
2022-06-22 11:03:04 +01:00
parent b2ed540c50
commit 3451152993
2 changed files with 76 additions and 54 deletions

View File

@@ -29,7 +29,7 @@ from ._context import active_app
from ._types import Lines from ._types import Lines
from .dom import DOMNode from .dom import DOMNode
from ._layout import ArrangeResult 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 .layouts.vertical import VerticalLayout
from .message import Message from .message import Message
from . import messages from . import messages
@@ -624,12 +624,15 @@ class Widget(DOMNode):
return any(scrolls) 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. """Scrolls a given region in to view.
Args: Args:
region (Region): A region that should be visible. region (Region): A region that should be visible.
animate (bool, optional): Enable animation. Defaults to True. animate (bool, optional): Enable animation. Defaults to True.
spacing (Spacing): Space to subtract from the window region.
Returns: Returns:
bool: True if the window was scrolled. bool: True if the window was scrolled.
@@ -637,50 +640,41 @@ class Widget(DOMNode):
scroll_x, scroll_y = self.scroll_offset scroll_x, scroll_y = self.scroll_offset
width, height = self.region.size 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: if region in window:
# Widget is visible, nothing to do # Widget is entirely visible, nothing to do
return False return False
( window_left, window_top, window_right, window_bottom = window.corners
container_left, left, top, right, bottom = region.corners
container_top,
container_right,
container_bottom,
) = container_region.corners
(
child_left,
child_top,
child_right,
child_bottom,
) = region.corners
delta_x = 0 delta_x = delta_y = 0
delta_y = 0
if not ( if not (
(container_right >= child_left > container_left) (window_right > left >= window_left)
and (container_right >= child_right > container_left) and (window_right > right >= window_left)
): ):
delta_x = min( delta_x = min(
child_left - container_left, left - window_left,
child_left - (container_right - region.width), left - (window_right - region.width),
key=abs, key=abs,
) )
if not ( if not (
(container_bottom >= child_top > container_top) (window_bottom > top >= window_top)
and (container_bottom >= child_bottom > container_top) and (window_bottom > bottom >= window_top)
): ):
delta_y = min( delta_y = min(
child_top - container_top, top - window_top,
child_top - (container_bottom - region.height), top - (window_bottom - region.height),
key=abs, key=abs,
) )
scrolled = self.scroll_relative( 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 return scrolled

View File

@@ -16,7 +16,7 @@ from .. import events
from .._cache import LRUCache from .._cache import LRUCache
from .._segment_tools import line_crop from .._segment_tools import line_crop
from .._types import Lines from .._types import Lines
from ..geometry import clamp, Region, Size from ..geometry import clamp, Region, Size, Spacing
from ..reactive import Reactive from ..reactive import Reactive
from .._profile import timer from .._profile import timer
from ..scroll_view import ScrollView from ..scroll_view import ScrollView
@@ -103,8 +103,7 @@ class DataTable(ScrollView, Generic[CellType]):
} }
DataTable > .datatable--highlight { DataTable > .datatable--highlight {
background: $secondary; background: $primary 20%;
color: $text-secondary;
} }
""" """
@@ -135,7 +134,7 @@ class DataTable(ScrollView, Generic[CellType]):
self._row_render_cache = LRUCache(1000) self._row_render_cache = LRUCache(1000)
self._cell_render_cache: LRUCache[tuple[int, int, Style], Lines] 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[tuple[int, int, int, int], list[Segment]]
self._line_cache = LRUCache(1000) self._line_cache = LRUCache(1000)
@@ -144,13 +143,15 @@ class DataTable(ScrollView, Generic[CellType]):
show_header = Reactive(True) show_header = Reactive(True)
fixed_rows = Reactive(0) fixed_rows = Reactive(0)
fixed_columns = Reactive(0) fixed_columns = Reactive(1)
zebra_stripes = Reactive(False) zebra_stripes = Reactive(False)
header_height = Reactive(1) header_height = Reactive(1)
show_cursor = Reactive(True) show_cursor = Reactive(True)
cursor_type = Reactive(CELL) cursor_type = Reactive(CELL)
cursor_row = Reactive(0) 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: def _clear_caches(self) -> None:
self._row_render_cache.clear() 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), 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] row = self.rows[row_index]
x = sum(column.width for column in self.columns[:column_index]) x = sum(column.width for column in self.columns[:column_index])
width = self.columns[column_index].width width = self.columns[column_index].width
@@ -256,6 +257,7 @@ class DataTable(ScrollView, Generic[CellType]):
style: Style, style: Style,
width: int, width: int,
cursor: bool = False, cursor: bool = False,
hover: bool = False,
) -> Lines: ) -> Lines:
"""Render the given cell. """Render the given cell.
@@ -268,9 +270,11 @@ class DataTable(ScrollView, Generic[CellType]):
Returns: Returns:
Lines: A list of segments per line. Lines: A list of segments per line.
""" """
if hover:
style += self.component_styles["datatable--highlight"].node.rich_style
if cursor: if cursor:
style += self.component_styles["datatable--cursor"].node.rich_style 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: if cell_key not in self._cell_render_cache:
style += Style.from_meta({"row": row_index, "column": column_index}) style += Style.from_meta({"row": row_index, "column": column_index})
height = ( height = (
@@ -286,7 +290,12 @@ class DataTable(ScrollView, Generic[CellType]):
return self._cell_render_cache[cell_key] return self._cell_render_cache[cell_key]
def _render_row( 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]: ) -> tuple[Lines, Lines]:
"""Render a row in to lines for each cell. """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. 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: if cache_key in self._row_render_cache:
return self._row_render_cache[cache_key] return self._row_render_cache[cache_key]
@@ -333,7 +342,8 @@ class DataTable(ScrollView, Generic[CellType]):
column.index, column.index,
row_style, row_style,
column.width, column.width,
cursor=cursor == column.index, cursor=cursor_column == column.index,
hover=hover_column == column.index,
)[line_no] )[line_no]
for column in self.columns for column in self.columns
] ]
@@ -373,20 +383,24 @@ class DataTable(ScrollView, Generic[CellType]):
""" """
width = self.region.width 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: if cache_key in self._line_cache:
return self._line_cache[cache_key] return self._line_cache[cache_key]
row_index, line_no = self._get_offsets(y)
fixed, scrollable = self._render_row( fixed, scrollable = self._render_row(
row_index, row_index,
line_no, line_no,
base_style, base_style,
cursor=self.cursor_column cursor_column=cursor_column,
if (self.show_cursor and self.cursor_row == row_index) hover_column=hover_column,
else -1,
) )
fixed_width = sum(column.width for column in self.columns[: self.fixed_columns]) fixed_width = sum(column.width for column in self.columns[: self.fixed_columns])
@@ -440,41 +454,55 @@ class DataTable(ScrollView, Generic[CellType]):
return lines return lines
def on_mouse_move(self, event): def on_mouse_move(self, event: events.MouseMove):
print(self.get_style_at(event.x, event.y).meta) 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: async def on_key(self, event) -> None:
await self.dispatch_key(event) 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: def _scroll_cursor_in_to_view(self) -> None:
region = self._get_cursor_region(self.cursor_row, self.cursor_column) region = self._get_cell_region(self.cursor_row, self.cursor_column)
print("CURSOR", region) spacing = self._get_cell_border()
self.scroll_to_region(region) 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): def key_down(self, event: events.Key):
self.cursor_row += 1 self.cursor_row += 1
self._clear_caches()
event.stop() event.stop()
event.prevent_default() event.prevent_default()
self._scroll_cursor_in_to_view() self._scroll_cursor_in_to_view()
def key_up(self, event: events.Key): def key_up(self, event: events.Key):
self.cursor_row -= 1 self.cursor_row -= 1
self._clear_caches()
event.stop() event.stop()
event.prevent_default() event.prevent_default()
self._scroll_cursor_in_to_view() self._scroll_cursor_in_to_view()
def key_right(self, event: events.Key): def key_right(self, event: events.Key):
self.cursor_column += 1 self.cursor_column += 1
self._clear_caches()
event.stop() event.stop()
event.prevent_default() event.prevent_default()
self._scroll_cursor_in_to_view() self._scroll_cursor_in_to_view()
def key_left(self, event: events.Key): def key_left(self, event: events.Key):
self.cursor_column -= 1 self.cursor_column -= 1
self._clear_caches()
event.stop() event.stop()
event.prevent_default() event.prevent_default()
self._scroll_cursor_in_to_view() self._scroll_cursor_in_to_view()