mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
big table
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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>"]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user