big table

This commit is contained in:
Will McGugan
2021-09-05 08:46:47 +01:00
parent 3dba37e550
commit 771167c136
8 changed files with 79 additions and 23 deletions

View File

@@ -12,6 +12,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Changed message handlers to use prefix handle\_
- Renamed messages to drop the Message suffix
### Added
- Added App.measure
- Added auto_width to Vertical Layout, WindowView, an ScrollView
- Added big_table.py example
## [0.1.10] - 2021-08-25
### Added

View File

@@ -1,9 +1,8 @@
from rich.table import Table
from rich.measure import Measurement
from textual import events
from textual.app import App
from textual.widgets import Header, Footer, ScrollView
from textual.widgets import ScrollView
class MyApp(App):
@@ -14,19 +13,17 @@ class MyApp(App):
async def on_mount(self, event: events.Mount) -> None:
self.body = body = ScrollView()
#body.virtual_size.width = 300
self.body = body = ScrollView(auto_width=True)
await self.view.dock(body)
async def add_content():
table = Table(title="Demo", width=1000)
table = Table(title="Demo")
for i in range(40):
table.add_column(f'Col {i + 1}', style='magenta')
table.add_column(f"Col {i + 1}", style="magenta")
for i in range(200):
table.add_row(*[f'cell {i},{j}' for j in range(40)])
table.add_row(*[f"cell {i},{j}" for j in range(40)])
await body.update(table)

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
version = "0.1.10"
version = "0.1.11"
homepage = "https://github.com/willmcgugan/textual"
description = "Text User Interface using Rich"
authors = ["Will McGugan <willmcgugan@gmail.com>"]

View File

@@ -10,6 +10,7 @@ from rich.control import Control
import rich.repr
from rich.screen import Screen
from rich.console import Console, RenderableType
from rich.measure import Measurement
from rich.traceback import Traceback
from . import events
@@ -347,6 +348,21 @@ class App(MessagePump):
except Exception:
self.panic()
def measure(self, renderable: RenderableType, max_width=100_000) -> int:
"""Get the optimal width for a widget or renderable.
Args:
renderable (RenderableType): A renderable (including Widget)
max_width ([type], optional): Maximum width. Defaults to 100_000.
Returns:
int: Number of cells required to render.
"""
measurement = Measurement.get(
self.console, self.console.options.update(max_width=max_width), renderable
)
return measurement.maximum
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
"""Get the widget under the given coordinates.
@@ -455,7 +471,7 @@ class App(MessagePump):
return True
async def on_key(self, event: events.Key) -> None:
self.log("App.on_key")
# self.log("App.on_key")
await self.press(event.key)
async def on_shutdown_request(self, event: events.ShutdownRequest) -> None:

View File

@@ -50,25 +50,32 @@ class ReflowResult(NamedTuple):
resized: set[Widget]
@rich.repr.auto
class LayoutUpdate:
def __init__(self, lines: Lines, x: int, y: int) -> None:
def __init__(self, lines: Lines, region: Region) -> None:
self.lines = lines
self.x = x
self.y = y
self.region = region
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
yield Control.home().segment
x = self.x
x = self.region.x
new_line = Segment.line()
move_to = Control.move_to
for last, (y, line) in loop_last(enumerate(self.lines, self.y)):
for last, (y, line) in loop_last(enumerate(self.lines, self.region.y)):
yield move_to(x, y).segment
yield from line
if not last:
yield new_line
def __rich_repr__(self) -> rich.repr.Result:
x, y, width, height = self.region
yield "x", x
yield "y", y
yield "width", width
yield "height", height
class Layout(ABC):
"""Responsible for arranging Widgets in a view and rendering them."""
@@ -378,6 +385,7 @@ class Layout(ABC):
update_region = region.intersection(clip)
update_lines = self.render(console, crop=update_region).lines
update = LayoutUpdate(update_lines, update_region.x, update_region.y)
update = LayoutUpdate(update_lines, update_region)
log(update)
return update

View File

@@ -13,17 +13,27 @@ from ..widget import Widget
class VerticalLayout(Layout):
def __init__(self, *, z: int = 0, gutter: tuple[int, int] | None = None):
def __init__(
self,
*,
auto_width: bool = False,
z: int = 0,
gutter: tuple[int, int] | None = None
):
self.auto_width = auto_width
self.z = z
self.gutter = gutter or (0, 0)
self._widgets: list[Widget] = []
self._max_widget_width = 0
super().__init__()
def add(self, widget: Widget) -> None:
self._widgets.append(widget)
self._max_widget_width = max(widget.app.measure(widget), self._max_widget_width)
def clear(self) -> None:
del self._widgets[:]
self._max_widget_width = 0
def get_widgets(self) -> Iterable[Widget]:
return self._widgets
@@ -34,7 +44,11 @@ class VerticalLayout(Layout):
index = 0
width, height = size
gutter_height, gutter_width = self.gutter
render_width = width - gutter_width * 2
render_width = (
max(width, self._max_widget_width) + gutter_width * 2
if self.auto_width
else width - gutter_width * 2
)
x = gutter_width
y = gutter_height
@@ -54,4 +68,9 @@ class VerticalLayout(Layout):
region = Region(x, y, render_width, render_height)
add_widget(widget, region - scroll, viewport)
x, y, width, height = map.contents_region
map.contents_region = Region(
x, y, width + self.gutter[0], height + self.gutter[1]
)
return map

View File

@@ -3,11 +3,11 @@ from __future__ import annotations
from rich.console import RenderableType
from .. import events
from ..geometry import Offset, Size
from ..geometry import Size
from ..layouts.vertical import VerticalLayout
from ..view import View
from ..message import Message
from ..messages import Update, Layout
from .. import messages
from ..widget import Widget
from ..widgets import Static
@@ -22,10 +22,11 @@ class WindowView(View, layout=VerticalLayout):
self,
widget: RenderableType | Widget,
*,
auto_width: bool = False,
gutter: tuple[int, int] = (0, 1),
name: str | None = None
) -> None:
layout = VerticalLayout(gutter=gutter)
layout = VerticalLayout(gutter=gutter, auto_width=auto_width)
self.widget = widget if isinstance(widget, Widget) else Static(widget)
layout.add(self.widget)
super().__init__(name=name, layout=layout)
@@ -40,10 +41,16 @@ class WindowView(View, layout=VerticalLayout):
self.refresh(layout=True)
await self.emit(WindowChange(self))
async def handle_update(self, message: Update) -> None:
async def handle_update(self, message: messages.Update) -> None:
message.prevent_default()
await self.emit(WindowChange(self))
async def handle_layout(self, message: messages.Layout) -> None:
self.log("TRANSLATING layout")
self.layout.require_update()
message.stop()
self.refresh()
async def watch_virtual_size(self, size: Size) -> None:
await self.emit(WindowChange(self))

View File

@@ -21,6 +21,7 @@ class ScrollView(View):
self,
contents: RenderableType | Widget | None = None,
*,
auto_width: bool = False,
name: str | None = None,
style: StyleType = "",
fluid: bool = True,
@@ -30,7 +31,9 @@ class ScrollView(View):
self.fluid = fluid
self.vscroll = ScrollBar(vertical=True)
self.hscroll = ScrollBar(vertical=False)
self.window = WindowView("" if contents is None else contents)
self.window = WindowView(
"" if contents is None else contents, auto_width=auto_width
)
layout = GridLayout()
layout.add_column("main")
layout.add_column("vscroll", size=1)