mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Scroll view now uses grid
This commit is contained in:
@@ -74,7 +74,7 @@ class CalculatorApp(App):
|
||||
|
||||
layout.place(
|
||||
*buttons.values(),
|
||||
numbers=Static(Padding(numbers, (0, 1)), style="white on rgb(51,51,51)"),
|
||||
numbers=Static(Padding(numbers, (0, 1), style="white on rgb(51,51,51)")),
|
||||
zero=make_button("0"),
|
||||
)
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ log = logging.getLogger("rich")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .widget import Widget, WidgetID
|
||||
from .view import View
|
||||
|
||||
|
||||
class NoWidget(Exception):
|
||||
@@ -137,6 +138,9 @@ class Layout(ABC):
|
||||
) -> dict[Widget, OrderedRegion]:
|
||||
...
|
||||
|
||||
async def mount_all(self, view: "View") -> None:
|
||||
await view.mount(*self.get_widgets())
|
||||
|
||||
@property
|
||||
def map(self) -> dict[Widget, OrderedRegion]:
|
||||
return self._layout_map
|
||||
|
||||
@@ -11,6 +11,7 @@ from .._layout_resolve import layout_resolve
|
||||
from .._loop import loop_last
|
||||
from ..geometry import Dimensions, Point, Region
|
||||
from ..layout import Layout, OrderedRegion
|
||||
from ..view import View
|
||||
from ..widget import Widget
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
@@ -49,14 +50,16 @@ class GridLayout(Layout):
|
||||
self.rows: list[GridOptions] = []
|
||||
self.areas: dict[str, GridArea] = {}
|
||||
self.widgets: dict[Widget, str | None] = {}
|
||||
self.column_gap = 1
|
||||
self.row_gap = 1
|
||||
self.column_gap = 0
|
||||
self.row_gap = 0
|
||||
self.column_repeat = False
|
||||
self.row_repeat = False
|
||||
self.column_align: GridAlign = "start"
|
||||
self.row_align: GridAlign = "start"
|
||||
self.column_gutter: int = 0
|
||||
self.row_gutter: int = 0
|
||||
self.hidden_columns: set[str] = set()
|
||||
self.hidden_rows: set[str] = set()
|
||||
|
||||
if gap is not None:
|
||||
if isinstance(gap, tuple):
|
||||
@@ -75,6 +78,18 @@ class GridLayout(Layout):
|
||||
|
||||
super().__init__()
|
||||
|
||||
def hide_row(self, row_name: str) -> None:
|
||||
self.hidden_rows.add(row_name)
|
||||
|
||||
def show_row(self, row_name: str) -> None:
|
||||
self.hidden_rows.discard(row_name)
|
||||
|
||||
def hide_column(self, column_name: str) -> None:
|
||||
self.hidden_rows.add(column_name)
|
||||
|
||||
def show_column(self, column_name: str) -> None:
|
||||
self.hidden_rows.discard(column_name)
|
||||
|
||||
def add_column(
|
||||
self,
|
||||
name: str,
|
||||
@@ -277,14 +292,33 @@ class GridLayout(Layout):
|
||||
|
||||
return names, tracks, len(spans), max_size
|
||||
|
||||
def add_widget(widget: Widget, region: Region, order: tuple[int, int]):
|
||||
region = region + offset + widget.layout_offset
|
||||
map[widget] = OrderedRegion(region, order)
|
||||
if isinstance(widget, View):
|
||||
sub_map = widget.layout.generate_map(
|
||||
region.width, region.height, offset=region.origin
|
||||
)
|
||||
map.update(sub_map)
|
||||
|
||||
container = Dimensions(
|
||||
width - self.column_gutter * 2, height - self.row_gutter * 2
|
||||
)
|
||||
column_names, column_tracks, column_count, column_size = resolve_tracks(
|
||||
self.columns, container.width, self.column_gap, self.column_repeat
|
||||
[
|
||||
options
|
||||
for options in self.columns
|
||||
if options.name not in self.hidden_columns
|
||||
],
|
||||
container.width,
|
||||
self.column_gap,
|
||||
self.column_repeat,
|
||||
)
|
||||
row_names, row_tracks, row_count, row_size = resolve_tracks(
|
||||
self.rows, container.height, self.row_gap, self.row_repeat
|
||||
[options for options in self.rows if options.name not in self.hidden_rows],
|
||||
container.height,
|
||||
self.row_gap,
|
||||
self.row_repeat,
|
||||
)
|
||||
grid_size = Dimensions(column_size, row_size)
|
||||
|
||||
@@ -323,7 +357,8 @@ class GridLayout(Layout):
|
||||
self.column_align,
|
||||
self.row_align,
|
||||
)
|
||||
map[widget] = OrderedRegion(region + gutter, (0, order))
|
||||
# map[widget] = OrderedRegion(region + gutter + offset, (0, order))
|
||||
add_widget(widget, region + gutter, (0, order))
|
||||
order += 1
|
||||
|
||||
# Widgets with no area assigned.
|
||||
@@ -355,7 +390,8 @@ class GridLayout(Layout):
|
||||
self.column_align,
|
||||
self.row_align,
|
||||
)
|
||||
map[widget] = OrderedRegion(region + gutter, (0, order))
|
||||
# map[widget] = OrderedRegion(region + gutter + offset, (0, order))
|
||||
add_widget(widget, region + gutter, (0, order))
|
||||
order += 1
|
||||
|
||||
return map
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||
from rich.padding import Padding, PaddingDimensions
|
||||
from rich.segment import Segment
|
||||
@@ -10,6 +12,8 @@ from .geometry import Dimensions, Point
|
||||
from .message import Message
|
||||
from .widget import Widget, Reactive
|
||||
|
||||
log = getLogger("rich")
|
||||
|
||||
|
||||
class PageUpdate(Message):
|
||||
def can_batch(self, message: "Message") -> bool:
|
||||
|
||||
@@ -135,6 +135,7 @@ class View(Widget):
|
||||
region = self.get_widget_region(widget)
|
||||
else:
|
||||
widget, region = self.get_widget_at(event.x, event.y)
|
||||
log.debug("WIDGET %r", widget)
|
||||
except NoWidget:
|
||||
await self.app.set_mouse_over(None)
|
||||
else:
|
||||
|
||||
58
src/textual/widgets/_button.py
Normal file
58
src/textual/widgets/_button.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.align import Align
|
||||
from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
|
||||
from rich.padding import Padding
|
||||
from rich.panel import Panel
|
||||
import rich.repr
|
||||
from rich.style import StyleType
|
||||
|
||||
from ..reactive import Reactive
|
||||
from ..widget import Widget
|
||||
|
||||
|
||||
class Expand:
|
||||
def __init__(self, renderable: RenderableType) -> None:
|
||||
self.renderable = renderable
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
width = options.max_width
|
||||
height = options.height or 1
|
||||
yield from console.render(
|
||||
self.renderable, options.update_dimensions(width, height)
|
||||
)
|
||||
|
||||
|
||||
class ButtonRenderable:
|
||||
def __init__(self, label: RenderableType, style: StyleType = "") -> None:
|
||||
self.label = label
|
||||
self.style = style
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
width = options.max_width
|
||||
height = options.height or 1
|
||||
|
||||
yield Align.center(
|
||||
self.label, vertical="middle", style=self.style, width=width, height=height
|
||||
)
|
||||
|
||||
|
||||
class Button(Widget):
|
||||
def __init__(
|
||||
self,
|
||||
label: RenderableType,
|
||||
name: str | None = None,
|
||||
style: StyleType = "white on dark_blue",
|
||||
):
|
||||
self.label = label
|
||||
self.name = name or str(label)
|
||||
self.style = style
|
||||
super().__init__()
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
return ButtonRenderable(self.label, style=self.style)
|
||||
return Align.center(self.label, vertical="middle", style=self.style)
|
||||
@@ -7,18 +7,19 @@ from rich.style import StyleType
|
||||
|
||||
|
||||
from .. import events
|
||||
from ..layouts.grid import GridLayout
|
||||
from ..message import Message
|
||||
from ..scrollbar import ScrollTo, ScrollBar
|
||||
from ..geometry import clamp
|
||||
from ..page import Page
|
||||
from ..views import DockView
|
||||
from ..view import View
|
||||
from ..reactive import Reactive
|
||||
|
||||
|
||||
log = logging.getLogger("rich")
|
||||
|
||||
|
||||
class ScrollView(DockView):
|
||||
class ScrollView(View):
|
||||
def __init__(
|
||||
self,
|
||||
renderable: RenderableType | None = None,
|
||||
@@ -29,12 +30,23 @@ class ScrollView(DockView):
|
||||
) -> None:
|
||||
self.fluid = fluid
|
||||
self._vertical_scrollbar = ScrollBar(vertical=True)
|
||||
self._horizontal_scrollbar = ScrollBar(vertical=False)
|
||||
self._page = Page(renderable or "", style=style)
|
||||
super().__init__(name=name)
|
||||
layout = GridLayout()
|
||||
layout.add_column("main")
|
||||
layout.add_column("vertical", size=1)
|
||||
layout.add_row("main")
|
||||
layout.add_row("horizontal", size=1)
|
||||
layout.add_areas(
|
||||
content="main,main", vertical="vertical,main", horizontal="main,horizontal"
|
||||
)
|
||||
layout.hide_row("horizontal")
|
||||
super().__init__(name=name, layout=layout)
|
||||
|
||||
x: Reactive[float] = Reactive(0)
|
||||
y: Reactive[float] = Reactive(0)
|
||||
|
||||
target_x: Reactive[float] = Reactive(0)
|
||||
target_y: Reactive[float] = Reactive(0)
|
||||
|
||||
def validate_y(self, value: float) -> float:
|
||||
@@ -52,8 +64,15 @@ class ScrollView(DockView):
|
||||
self.require_repaint()
|
||||
|
||||
async def on_mount(self, event: events.Mount) -> None:
|
||||
await self.dock(self._vertical_scrollbar, edge="right", size=1)
|
||||
await self.dock(self._page, edge="top")
|
||||
assert isinstance(self.layout, GridLayout)
|
||||
self.layout.place(
|
||||
content=self._page,
|
||||
vertical=self._vertical_scrollbar,
|
||||
horizontal=self._horizontal_scrollbar,
|
||||
)
|
||||
await self.layout.mount_all(self)
|
||||
# await self.dock(self._vertical_scrollbar, edge="right", size=1)
|
||||
# await self.dock(self._page, edge="top")
|
||||
|
||||
def scroll_up(self) -> None:
|
||||
self.target_y += 1.5
|
||||
|
||||
Reference in New Issue
Block a user