mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
renamed Region.origin to offset
This commit is contained in:
@@ -112,7 +112,6 @@ class BasicApp(App, css_path="basic.css"):
|
||||
Tweet(TweetBody()),
|
||||
Widget(
|
||||
Static(Syntax(CODE, "python"), classes="code"),
|
||||
self.scroll_to_target,
|
||||
classes="scrollable",
|
||||
),
|
||||
Error(),
|
||||
|
||||
231
sandbox/will/basic.css
Normal file
231
sandbox/will/basic.css
Normal file
@@ -0,0 +1,231 @@
|
||||
/* CSS file for basic.py */
|
||||
|
||||
|
||||
|
||||
* {
|
||||
transition: color 300ms linear, background 300ms linear;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-background: $panel-darken-2;
|
||||
scrollbar-background-hover: $panel-darken-3;
|
||||
scrollbar-color: $system;
|
||||
scrollbar-color-active: $accent-darken-1;
|
||||
scrollbar-size-horizontal: 1;
|
||||
scrollbar-size-vertical: 2;
|
||||
}
|
||||
|
||||
App > Screen {
|
||||
layout: dock;
|
||||
docks: side=left/1;
|
||||
background: $surface;
|
||||
color: $text-surface;
|
||||
}
|
||||
|
||||
|
||||
#sidebar {
|
||||
color: $text-primary;
|
||||
background: $primary-background;
|
||||
dock: side;
|
||||
width: 30;
|
||||
offset-x: -100%;
|
||||
layout: dock;
|
||||
transition: offset 500ms in_out_cubic;
|
||||
}
|
||||
|
||||
#sidebar.-active {
|
||||
offset-x: 0;
|
||||
}
|
||||
|
||||
#sidebar .title {
|
||||
height: 3;
|
||||
background: $primary-background-darken-2;
|
||||
color: $text-primary-darken-2 ;
|
||||
border-right: outer $primary-darken-3;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
#sidebar .user {
|
||||
height: 8;
|
||||
background: $primary-background-darken-1;
|
||||
color: $text-primary-darken-1;
|
||||
border-right: outer $primary-background-darken-3;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
#sidebar .content {
|
||||
background: $primary-background;
|
||||
color: $text-primary-background;
|
||||
border-right: outer $primary-background-darken-3;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
#header {
|
||||
color: $text-primary-darken-1;
|
||||
background: $primary-darken-1;
|
||||
height: 3;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
#content {
|
||||
color: $text-background;
|
||||
background: $background;
|
||||
layout: vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
|
||||
Tweet {
|
||||
height:12;
|
||||
width: 100%;
|
||||
|
||||
margin: 1 3;
|
||||
background: $panel;
|
||||
color: $text-panel;
|
||||
layout: vertical;
|
||||
/* border: outer $primary; */
|
||||
padding: 1;
|
||||
border: wide $panel-darken-2;
|
||||
overflow: auto;
|
||||
/* scrollbar-gutter: stable; */
|
||||
align-horizontal: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.scrollable {
|
||||
|
||||
overflow-y: scroll;
|
||||
margin: 1 2;
|
||||
height: 20;
|
||||
align-horizontal: center;
|
||||
layout: vertical;
|
||||
}
|
||||
|
||||
.code {
|
||||
|
||||
height: auto;
|
||||
|
||||
}
|
||||
|
||||
|
||||
TweetHeader {
|
||||
height:1;
|
||||
background: $accent;
|
||||
color: $text-accent
|
||||
}
|
||||
|
||||
TweetBody {
|
||||
width: 100%;
|
||||
background: $panel;
|
||||
color: $text-panel;
|
||||
height: auto;
|
||||
padding: 0 1 0 0;
|
||||
}
|
||||
|
||||
Tweet.scroll-horizontal TweetBody {
|
||||
width: 350;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: $accent;
|
||||
color: $text-accent;
|
||||
width:20;
|
||||
height: 3;
|
||||
/* border-top: hidden $accent-darken-3; */
|
||||
border: tall $accent-darken-2;
|
||||
/* border-left: tall $accent-darken-1; */
|
||||
|
||||
|
||||
/* padding: 1 0 0 0 ; */
|
||||
|
||||
transition: background 400ms in_out_cubic, color 400ms in_out_cubic;
|
||||
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: $accent-lighten-1;
|
||||
color: $text-accent-lighten-1;
|
||||
width: 20;
|
||||
height: 3;
|
||||
border: tall $accent-darken-1;
|
||||
/* border-left: tall $accent-darken-3; */
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#footer {
|
||||
color: $text-accent;
|
||||
background: $accent;
|
||||
height: 1;
|
||||
border-top: hkey $accent-darken-2;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
|
||||
#sidebar .content {
|
||||
layout: vertical
|
||||
}
|
||||
|
||||
OptionItem {
|
||||
height: 3;
|
||||
background: $primary-background;
|
||||
border-right: outer $primary-background-darken-2;
|
||||
border-left: blank;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
OptionItem:hover {
|
||||
height: 3;
|
||||
color: $accent;
|
||||
background: $primary-background-darken-1;
|
||||
/* border-top: hkey $accent2-darken-3;
|
||||
border-bottom: hkey $accent2-darken-3; */
|
||||
text-style: bold;
|
||||
border-left: outer $accent-darken-2;
|
||||
}
|
||||
|
||||
Error {
|
||||
width: 100%;
|
||||
height:3;
|
||||
background: $error;
|
||||
color: $text-error;
|
||||
border-top: hkey $error-darken-2;
|
||||
border-bottom: hkey $error-darken-2;
|
||||
margin: 1 3;
|
||||
|
||||
text-style: bold;
|
||||
align-horizontal: center;
|
||||
}
|
||||
|
||||
Warning {
|
||||
width: 100%;
|
||||
height:3;
|
||||
background: $warning;
|
||||
color: $text-warning-fade-1;
|
||||
border-top: hkey $warning-darken-2;
|
||||
border-bottom: hkey $warning-darken-2;
|
||||
margin: 1 2;
|
||||
text-style: bold;
|
||||
align-horizontal: center;
|
||||
}
|
||||
|
||||
Success {
|
||||
width: 100%;
|
||||
height:3;
|
||||
box-sizing: border-box;
|
||||
background: $success-lighten-3;
|
||||
color: $text-success-lighten-3-fade-1;
|
||||
border-top: hkey $success;
|
||||
border-bottom: hkey $success;
|
||||
margin: 1 2;
|
||||
text-style: bold;
|
||||
align-horizontal: center;
|
||||
}
|
||||
|
||||
|
||||
.horizontal {
|
||||
layout: horizontal
|
||||
}
|
||||
185
sandbox/will/basic.py
Normal file
185
sandbox/will/basic.py
Normal file
@@ -0,0 +1,185 @@
|
||||
from rich.console import RenderableType
|
||||
from rich.style import Style
|
||||
from rich.syntax import Syntax
|
||||
from rich.text import Text
|
||||
|
||||
from textual.app import App
|
||||
from textual.reactive import Reactive
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Static
|
||||
|
||||
CODE = '''
|
||||
class Offset(NamedTuple):
|
||||
"""A point defined by x and y coordinates."""
|
||||
|
||||
x: int = 0
|
||||
y: int = 0
|
||||
|
||||
@property
|
||||
def is_origin(self) -> bool:
|
||||
"""Check if the point is at the origin (0, 0)"""
|
||||
return self == (0, 0)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return self != (0, 0)
|
||||
|
||||
def __add__(self, other: object) -> Offset:
|
||||
if isinstance(other, tuple):
|
||||
_x, _y = self
|
||||
x, y = other
|
||||
return Offset(_x + x, _y + y)
|
||||
return NotImplemented
|
||||
|
||||
def __sub__(self, other: object) -> Offset:
|
||||
if isinstance(other, tuple):
|
||||
_x, _y = self
|
||||
x, y = other
|
||||
return Offset(_x - x, _y - y)
|
||||
return NotImplemented
|
||||
|
||||
def __mul__(self, other: object) -> Offset:
|
||||
if isinstance(other, (float, int)):
|
||||
x, y = self
|
||||
return Offset(int(x * other), int(y * other))
|
||||
return NotImplemented
|
||||
'''
|
||||
|
||||
|
||||
lorem_short = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. In velit liber a a a, volutpat nec hendrerit at, faucibus in odio. Aliquam hendrerit nibh sed quam volutpat maximus. Nullam suscipit convallis lorem quis sodales. In tristique lobortis ante et dictum. Ut at finibus ipsum."""
|
||||
lorem = (
|
||||
lorem_short
|
||||
+ """ In urna dolor, placerat et mi facilisis, congue sollicitudin massa. Phasellus felis turpis, cursus eu lectus et, porttitor malesuada augue. Sed feugiat volutpat velit, sollicitudin fringilla velit bibendum faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In velit libero, volutpat nec hendrerit at, faucibus in odio. Aliquam hendrerit nibh sed quam volutpat maximus. Nullam suscipit convallis lorem quis sodales. In tristique lobortis ante et dictum. Ut at finibus ipsum. In urna dolor, placerat et mi facilisis, congue sollicitudin massa. Phasellus felis turpis, cursus eu lectus et, porttitor malesuada augue. Sed feugiat volutpat velit, sollicitudin fringilla velit bibendum faucibus. """
|
||||
)
|
||||
|
||||
lorem_short_text = Text.from_markup(lorem_short)
|
||||
lorem_long_text = Text.from_markup(lorem * 2)
|
||||
|
||||
|
||||
class TweetHeader(Widget):
|
||||
def render(self) -> RenderableType:
|
||||
return Text("Lorem Impsum", justify="center")
|
||||
|
||||
|
||||
class TweetBody(Widget):
|
||||
short_lorem = Reactive(False)
|
||||
|
||||
def render(self) -> Text:
|
||||
return lorem_short_text if self.short_lorem else lorem_long_text
|
||||
|
||||
|
||||
class Tweet(Widget):
|
||||
pass
|
||||
|
||||
|
||||
class OptionItem(Widget):
|
||||
def render(self) -> Text:
|
||||
return Text("Option")
|
||||
|
||||
|
||||
class Error(Widget):
|
||||
def render(self) -> Text:
|
||||
return Text("This is an error message", justify="center")
|
||||
|
||||
|
||||
class Warning(Widget):
|
||||
def render(self) -> Text:
|
||||
return Text("This is a warning message", justify="center")
|
||||
|
||||
|
||||
class Success(Widget):
|
||||
def render(self) -> Text:
|
||||
return Text("This is a success message", justify="center")
|
||||
|
||||
|
||||
class BasicApp(App, css_path="basic.css"):
|
||||
"""A basic app demonstrating CSS"""
|
||||
|
||||
def on_load(self):
|
||||
"""Bind keys here."""
|
||||
self.bind("s", "toggle_class('#sidebar', '-active')")
|
||||
|
||||
def on_mount(self):
|
||||
"""Build layout here."""
|
||||
|
||||
self.scroll_to_target = Tweet(TweetBody())
|
||||
self.mount(
|
||||
header=Static(
|
||||
Text.from_markup(
|
||||
"[b]This is a [u]Textual[/u] app, running in the terminal"
|
||||
),
|
||||
),
|
||||
content=Widget(
|
||||
Tweet(TweetBody()),
|
||||
Widget(
|
||||
Static(Syntax(CODE, "python"), classes="code"),
|
||||
classes="scrollable",
|
||||
),
|
||||
Error(),
|
||||
Tweet(TweetBody(), classes="scrollbar-size-custom"),
|
||||
Warning(),
|
||||
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||
Success(),
|
||||
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||
Tweet(TweetBody(), classes="scroll-horizontal"),
|
||||
),
|
||||
footer=Widget(),
|
||||
sidebar=Widget(
|
||||
Widget(classes="title"),
|
||||
Widget(classes="user"),
|
||||
OptionItem(),
|
||||
OptionItem(),
|
||||
OptionItem(),
|
||||
Widget(classes="content"),
|
||||
),
|
||||
)
|
||||
|
||||
async def on_key(self, event) -> None:
|
||||
await self.dispatch_key(event)
|
||||
|
||||
def key_d(self):
|
||||
self.dark = not self.dark
|
||||
|
||||
async def key_q(self):
|
||||
await self.shutdown()
|
||||
|
||||
def key_x(self):
|
||||
self.panic(self.tree)
|
||||
|
||||
def key_escape(self):
|
||||
self.app.bell()
|
||||
|
||||
def key_t(self):
|
||||
# Pressing "t" toggles the content of the TweetBody widget, from a long "Lorem ipsum..." to a shorter one.
|
||||
tweet_body = self.query("TweetBody").first()
|
||||
tweet_body.short_lorem = not tweet_body.short_lorem
|
||||
|
||||
def key_v(self):
|
||||
self.get_child(id="content").scroll_to_widget(self.scroll_to_target)
|
||||
|
||||
def key_space(self):
|
||||
self.bell()
|
||||
|
||||
|
||||
app = BasicApp()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
|
||||
from textual.geometry import Region
|
||||
from textual.color import Color
|
||||
|
||||
print(Region.intersection.cache_info())
|
||||
print(Region.overlaps.cache_info())
|
||||
print(Region.union.cache_info())
|
||||
print(Region.split_vertical.cache_info())
|
||||
print(Region.__contains__.cache_info())
|
||||
from textual.css.scalar import Scalar
|
||||
|
||||
print(Scalar.resolve_dimension.cache_info())
|
||||
|
||||
from rich.style import Style
|
||||
|
||||
print(Style._add.cache_info())
|
||||
@@ -328,7 +328,7 @@ class Compositor:
|
||||
sub_clip = clip.intersection(child_region)
|
||||
|
||||
# The region covered by children relative to parent widget
|
||||
total_region = child_region.reset_origin
|
||||
total_region = child_region.reset_offset
|
||||
|
||||
if widget.is_container:
|
||||
# Arrange the layout
|
||||
@@ -338,7 +338,7 @@ class Compositor:
|
||||
|
||||
# An offset added to all placements
|
||||
placement_offset = (
|
||||
container_region.origin + layout_offset - widget.scroll_offset
|
||||
container_region.offset + layout_offset - widget.scroll_offset
|
||||
)
|
||||
|
||||
# Add all the widgets
|
||||
@@ -358,7 +358,7 @@ class Compositor:
|
||||
container_size
|
||||
):
|
||||
map[chrome_widget] = MapGeometry(
|
||||
chrome_region + container_region.origin + layout_offset,
|
||||
chrome_region + container_region.offset + layout_offset,
|
||||
order,
|
||||
clip,
|
||||
container_size,
|
||||
@@ -414,7 +414,7 @@ class Compositor:
|
||||
def get_offset(self, widget: Widget) -> Offset:
|
||||
"""Get the offset of a widget."""
|
||||
try:
|
||||
return self.map[widget].region.origin
|
||||
return self.map[widget].region.offset
|
||||
except KeyError:
|
||||
raise errors.NoWidget("Widget is not in layout")
|
||||
|
||||
|
||||
@@ -224,17 +224,17 @@ class Region(NamedTuple):
|
||||
return cls(x1, y1, x2 - x1, y2 - y1)
|
||||
|
||||
@classmethod
|
||||
def from_origin(cls, origin: tuple[int, int], size: tuple[int, int]) -> Region:
|
||||
"""Create a region from origin and size.
|
||||
def from_offset(cls, offset: tuple[int, int], size: tuple[int, int]) -> Region:
|
||||
"""Create a region from offset and size.
|
||||
|
||||
Args:
|
||||
origin (Point): Origin (top left point)
|
||||
offset (Point): Offset (top left point)
|
||||
size (tuple[int, int]): Dimensions of region.
|
||||
|
||||
Returns:
|
||||
Region: A region instance.
|
||||
"""
|
||||
x, y = origin
|
||||
x, y = offset
|
||||
width, height = size
|
||||
return cls(x, y, width, height)
|
||||
|
||||
@@ -280,7 +280,7 @@ class Region(NamedTuple):
|
||||
return self.width * self.height
|
||||
|
||||
@property
|
||||
def origin(self) -> Offset:
|
||||
def offset(self) -> Offset:
|
||||
"""Get the start point of the region."""
|
||||
return Offset(self.x, self.y)
|
||||
|
||||
@@ -328,7 +328,7 @@ class Region(NamedTuple):
|
||||
return range(self.y, self.y + self.height)
|
||||
|
||||
@property
|
||||
def reset_origin(self) -> Region:
|
||||
def reset_offset(self) -> Region:
|
||||
"""An region of the same size at (0, 0)."""
|
||||
_, _, width, height = self
|
||||
return Region(0, 0, width, height)
|
||||
@@ -347,6 +347,19 @@ class Region(NamedTuple):
|
||||
return Region(x - ox, y - oy, width, height)
|
||||
return NotImplemented
|
||||
|
||||
def at_offset(self, offset: tuple[int, int]) -> Region:
|
||||
"""Get a new Region with the same size at a given offset.
|
||||
|
||||
Args:
|
||||
offset (tuple[int, int]): An offset.
|
||||
|
||||
Returns:
|
||||
Region: New Region with adjusted offset.
|
||||
"""
|
||||
x, y = offset
|
||||
_x, _y, width, height = self
|
||||
return Region(x, y, width, height)
|
||||
|
||||
def expand(self, size: tuple[int, int]) -> Region:
|
||||
"""Increase the size of the region by adding a border.
|
||||
|
||||
@@ -430,7 +443,7 @@ class Region(NamedTuple):
|
||||
)
|
||||
|
||||
def translate(self, x: int = 0, y: int = 0) -> Region:
|
||||
"""Move the origin of the Region.
|
||||
"""Move the offset of the Region.
|
||||
|
||||
Args:
|
||||
translate_x (int): Value to add to x coordinate.
|
||||
|
||||
@@ -595,9 +595,9 @@ class Widget(DOMNode):
|
||||
|
||||
# We can either scroll so the widget is at the top of the container, or so that
|
||||
# it is at the bottom. We want to pick which has the shortest distance
|
||||
top_delta = widget_region.origin - container_region.origin
|
||||
top_delta = widget_region.offset - container_region.origin
|
||||
|
||||
bottom_delta = widget_region.origin - (
|
||||
bottom_delta = widget_region.offset - (
|
||||
container_region.origin
|
||||
+ Offset(0, container_region.height - widget_region.height)
|
||||
)
|
||||
@@ -627,7 +627,10 @@ class Widget(DOMNode):
|
||||
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, if required.
|
||||
|
||||
This method will scroll the least distance required to move `region` fully within
|
||||
the scrollable area.
|
||||
|
||||
Args:
|
||||
region (Region): A region that should be visible.
|
||||
@@ -638,9 +641,7 @@ class Widget(DOMNode):
|
||||
bool: True if the window was scrolled.
|
||||
"""
|
||||
|
||||
scroll_x, scroll_y = self.scroll_offset
|
||||
width, height = self.region.size
|
||||
window = Region(scroll_x, scroll_y, width, height)
|
||||
window = self.region.at_offset(self.scroll_offset)
|
||||
if spacing is not None:
|
||||
window = window.shrink(spacing)
|
||||
|
||||
@@ -650,13 +651,13 @@ class Widget(DOMNode):
|
||||
|
||||
window_left, window_top, window_right, window_bottom = window.corners
|
||||
left, top, right, bottom = region.corners
|
||||
|
||||
delta_x = delta_y = 0
|
||||
|
||||
if not (
|
||||
(window_right > left >= window_left)
|
||||
and (window_right > right >= window_left)
|
||||
):
|
||||
# The window needs to scroll on the X axis to bring region in to view
|
||||
delta_x = min(
|
||||
left - window_left,
|
||||
left - (window_right - region.width),
|
||||
@@ -667,6 +668,7 @@ class Widget(DOMNode):
|
||||
(window_bottom > top >= window_top)
|
||||
and (window_bottom > bottom >= window_top)
|
||||
):
|
||||
# The window needs to scroll on the Y axis to bring region in to view
|
||||
delta_y = min(
|
||||
top - window_top,
|
||||
top - (window_bottom - region.height),
|
||||
|
||||
@@ -130,13 +130,15 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
|
||||
self._y_offsets: list[tuple[int, int]] = []
|
||||
|
||||
self._row_render_cache: LRUCache[tuple[int, int, Style], tuple[Lines, Lines]]
|
||||
self._row_render_cache: LRUCache[
|
||||
tuple[int, int, Style, int, int], tuple[Lines, Lines]
|
||||
]
|
||||
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, bool, bool], Lines]
|
||||
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, int, int], list[Segment]]
|
||||
self._line_cache = LRUCache(1000)
|
||||
|
||||
self._line_no = 0
|
||||
@@ -148,10 +150,10 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
header_height = Reactive(1)
|
||||
show_cursor = Reactive(True)
|
||||
cursor_type = Reactive(CELL)
|
||||
cursor_row = Reactive(0)
|
||||
cursor_column = Reactive(1)
|
||||
hover_row = Reactive(0)
|
||||
hover_column = Reactive(0)
|
||||
cursor_row: Reactive[int] = Reactive(0)
|
||||
cursor_column: Reactive[int] = Reactive(1)
|
||||
hover_row: Reactive[int] = Reactive(0)
|
||||
hover_column: Reactive[int] = Reactive(0)
|
||||
|
||||
def _clear_caches(self) -> None:
|
||||
self._row_render_cache.clear()
|
||||
@@ -408,13 +410,7 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
scrollable_line: list[Segment] = list(chain.from_iterable(scrollable))
|
||||
|
||||
segments = fixed_line + line_crop(scrollable_line, x1 + fixed_width, x2, width)
|
||||
|
||||
# remaining_width = width - (fixed_width + min(width, (x2 - x1 + fixed_width)))
|
||||
# if remaining_width > 0:
|
||||
# segments.append(Segment(" " * remaining_width, base_style))
|
||||
# elif remaining_width < 0:
|
||||
segments = Segment.adjust_line_length(segments, width, style=base_style)
|
||||
|
||||
simplified_segments = list(Segment.simplify(segments))
|
||||
|
||||
self._line_cache[cache_key] = simplified_segments
|
||||
@@ -456,8 +452,9 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
|
||||
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")
|
||||
if meta:
|
||||
self.hover_row = meta["row"]
|
||||
self.hover_column = meta["column"]
|
||||
|
||||
async def on_key(self, event) -> None:
|
||||
await self.dispatch_key(event)
|
||||
@@ -479,9 +476,10 @@ class DataTable(ScrollView, Generic[CellType]):
|
||||
|
||||
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()
|
||||
if meta:
|
||||
self.cursor_row = meta["row"]
|
||||
self.cursor_column = meta["column"]
|
||||
self._scroll_cursor_in_to_view()
|
||||
|
||||
def key_down(self, event: events.Key):
|
||||
self.cursor_row += 1
|
||||
|
||||
@@ -126,7 +126,7 @@ def test_region_from_union():
|
||||
|
||||
|
||||
def test_region_from_origin():
|
||||
assert Region.from_origin(Offset(3, 4), (5, 6)) == Region(3, 4, 5, 6)
|
||||
assert Region.from_offset(Offset(3, 4), (5, 6)) == Region(3, 4, 5, 6)
|
||||
|
||||
|
||||
def test_region_area():
|
||||
@@ -140,7 +140,7 @@ def test_region_size():
|
||||
|
||||
|
||||
def test_region_origin():
|
||||
assert Region(1, 2, 3, 4).origin == Offset(1, 2)
|
||||
assert Region(1, 2, 3, 4).offset == Offset(1, 2)
|
||||
|
||||
|
||||
def test_region_bottom_left():
|
||||
@@ -275,7 +275,7 @@ def test_region_y_range():
|
||||
|
||||
|
||||
def test_region_reset_origin():
|
||||
assert Region(5, 10, 20, 30).reset_origin == Region(0, 0, 20, 30)
|
||||
assert Region(5, 10, 20, 30).reset_offset == Region(0, 0, 20, 30)
|
||||
|
||||
|
||||
def test_region_expand():
|
||||
|
||||
Reference in New Issue
Block a user