Skip to content

Commit

Permalink
Merge pull request Textualize#90 from willmcgugan/layout-plus
Browse files Browse the repository at this point in the history
Layout plus
  • Loading branch information
willmcgugan authored Sep 5, 2021
2 parents d2b4866 + 2ff2317 commit 6688d0b
Show file tree
Hide file tree
Showing 30 changed files with 353 additions and 123 deletions.
9 changes: 9 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[run]
omit =

[report]
exclude_lines =
pragma: no cover
if TYPE_CHECKING:
if __name__ == "__main__":
@overload
22 changes: 22 additions & 0 deletions .github/workflows/comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: issues
on:
issues:
types: [closed]
jobs:
add-comment:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Did I solve your problem?
uses: peter-evans/create-or-update-comment@a35cf36e5301d70b76f316e867e7788a55a31dae
with:
issue-number: ${{ github.event.issue.number }}
body: |
Did I solve your problem?
Consider [sponsoring my work](https://github.com/sponsors/willmcgugan) on Textual with a monthly donation.
Or buy me a [coffee](https://ko-fi.com/willmcgugan) to say thanks.
– [Will McGugan](https://twitter.com/willmcgugan)
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.1.11] - Unreleased

### Changed

- Changed message handlers to use prefix handle\_
- Renamed messages to drop the Message suffix
- Events now bubble by default

### 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
Expand Down
33 changes: 33 additions & 0 deletions examples/big_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from rich.table import Table

from textual import events
from textual.app import App
from textual.widgets import ScrollView


class MyApp(App):
"""An example of a very simple Textual App"""

async def on_load(self, event: events.Load) -> None:
await self.bind("q", "quit", "Quit")

async def on_mount(self, event: events.Mount) -> None:

self.body = body = ScrollView(auto_width=True)

await self.view.dock(body)

async def add_content():
table = Table(title="Demo")

for i in range(40):
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)])

await body.update(table)

await self.call_later(add_content)


MyApp.run(title="Simple App", log="textual.log")
4 changes: 2 additions & 2 deletions examples/calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def make_button(text: str, style: str) -> Button:
*self.buttons.values(), clear=self.ac, numbers=self.numbers, zero=self.zero
)

def message_button_pressed(self, message: ButtonPressed) -> None:
def handle_button_pressed(self, message: ButtonPressed) -> None:
"""A message sent by the button widget"""

assert isinstance(message.sender, Button)
Expand Down Expand Up @@ -208,7 +208,7 @@ def do_math() -> None:
class CalculatorApp(App):
"""The Calculator Application"""

async def on_mount(self, event: events.Mount) -> None:
async def on_mount(self) -> None:
"""Mount the calculator widget."""
await self.view.dock(Calculator())

Expand Down
2 changes: 1 addition & 1 deletion examples/code_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ async def on_mount(self) -> None:
)
await self.view.dock(self.body, edge="top")

async def message_file_click(self, message: FileClick) -> None:
async def handle_file_click(self, message: FileClick) -> None:
"""A message sent by the directory tree when a file is clicked."""

syntax: RenderableType
Expand Down
2 changes: 1 addition & 1 deletion examples/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class GridTest(App):
async def on_mount(self) -> None:
"""Make a simple grid arrangement."""

grid = await self.view.dock_grid(edge="left", size=70, name="left")
grid = await self.view.dock_grid(edge="left", name="left")

grid.add_column(fraction=1, name="left", min_size=20)
grid.add_column(size=30, name="center")
Expand Down
3 changes: 3 additions & 0 deletions notes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Developer notes

These are notes made by the developer, and _not_ to be considered documentation.
5 changes: 5 additions & 0 deletions notes/layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Layout

## rich.layout.Layout

The Layout class is responsible for arranging widget within a defined area. There are several concrete Layout objects with different strategies for positioning widgets.
11 changes: 11 additions & 0 deletions notes/refresh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Refresh system

This note describes how Textual updates widgets on-screen.

if widget has made some changes and wishes to update visuals it can call Widget.refresh. There are two flags on this method; `repaint` which will repaint just the widget, and `layout` which will re-layout the screen. A layout must be done if the widget has changed size / position / visibility. Otherwise repaint will refresh just the widget area.

A refresh won't happen immediately when `refresh()` is called, rather it sets internal flags. The `on_idle` method of Widget checks these flags. This is so that multiple changes made to the UI while processing events don't cause excessive repainting of the screen (which makes the UI slow and jumpy).

In the case of a repaint. The Widget.on_idle handler will emit (send to the parent) an UpdateMessage. This message will be handled by the parent view, which will update the widget (a particular part of the screen).

In the case of a layout. The Widget.on_idle handler will emit a LayoutMessage. This message will be handled by the parent view, which calls refresh_layout on the root view, which will layout and repaint the entire screen.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
Expand Down
10 changes: 6 additions & 4 deletions src/textual/_loop.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from typing import Iterable, Tuple, TypeVar
from __future__ import annotations

from typing import Iterable, TypeVar

T = TypeVar("T")


def loop_first(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
def loop_first(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
"""Iterate and generate a tuple with a flag for first value."""
iter_values = iter(values)
try:
Expand All @@ -15,7 +17,7 @@ def loop_first(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
yield False, value


def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
def loop_last(values: Iterable[T]) -> Iterable[tuple[bool, T]]:
"""Iterate and generate a tuple with a flag for last value."""
iter_values = iter(values)
try:
Expand All @@ -28,7 +30,7 @@ def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
yield True, previous_value


def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]:
def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]:
"""Iterate and generate a tuple with a flag for first and last value."""
iter_values = iter(values)
try:
Expand Down
24 changes: 20 additions & 4 deletions src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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
Expand Down Expand Up @@ -347,6 +348,21 @@ def display(self, renderable: RenderableType) -> None:
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.
Expand Down Expand Up @@ -396,9 +412,9 @@ async def on_event(self, event: events.Event) -> None:
else:
await super().on_event(event)

async def on_idle(self, event: events.Idle) -> None:
if self.view.check_layout():
await self.view.refresh_layout()
# async def on_idle(self, event: events.Idle) -> None:
# if self.view.check_layout():
# await self.view.refresh_layout()

async def action(
self,
Expand Down Expand Up @@ -455,7 +471,7 @@ async def broker_event(
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:
Expand Down
Loading

0 comments on commit 6688d0b

Please sign in to comment.