Scroll view now uses grid

This commit is contained in:
Will McGugan
2021-07-11 15:47:00 +01:00
parent 022dbeec6c
commit 9d998eb982
7 changed files with 134 additions and 12 deletions

View File

@@ -74,7 +74,7 @@ class CalculatorApp(App):
layout.place( layout.place(
*buttons.values(), *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"), zero=make_button("0"),
) )

View File

@@ -25,6 +25,7 @@ log = logging.getLogger("rich")
if TYPE_CHECKING: if TYPE_CHECKING:
from .widget import Widget, WidgetID from .widget import Widget, WidgetID
from .view import View
class NoWidget(Exception): class NoWidget(Exception):
@@ -137,6 +138,9 @@ class Layout(ABC):
) -> dict[Widget, OrderedRegion]: ) -> dict[Widget, OrderedRegion]:
... ...
async def mount_all(self, view: "View") -> None:
await view.mount(*self.get_widgets())
@property @property
def map(self) -> dict[Widget, OrderedRegion]: def map(self) -> dict[Widget, OrderedRegion]:
return self._layout_map return self._layout_map

View File

@@ -11,6 +11,7 @@ from .._layout_resolve import layout_resolve
from .._loop import loop_last from .._loop import loop_last
from ..geometry import Dimensions, Point, Region from ..geometry import Dimensions, Point, Region
from ..layout import Layout, OrderedRegion from ..layout import Layout, OrderedRegion
from ..view import View
from ..widget import Widget from ..widget import Widget
if sys.version_info >= (3, 8): if sys.version_info >= (3, 8):
@@ -49,14 +50,16 @@ class GridLayout(Layout):
self.rows: list[GridOptions] = [] self.rows: list[GridOptions] = []
self.areas: dict[str, GridArea] = {} self.areas: dict[str, GridArea] = {}
self.widgets: dict[Widget, str | None] = {} self.widgets: dict[Widget, str | None] = {}
self.column_gap = 1 self.column_gap = 0
self.row_gap = 1 self.row_gap = 0
self.column_repeat = False self.column_repeat = False
self.row_repeat = False self.row_repeat = False
self.column_align: GridAlign = "start" self.column_align: GridAlign = "start"
self.row_align: GridAlign = "start" self.row_align: GridAlign = "start"
self.column_gutter: int = 0 self.column_gutter: int = 0
self.row_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 gap is not None:
if isinstance(gap, tuple): if isinstance(gap, tuple):
@@ -75,6 +78,18 @@ class GridLayout(Layout):
super().__init__() 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( def add_column(
self, self,
name: str, name: str,
@@ -277,14 +292,33 @@ class GridLayout(Layout):
return names, tracks, len(spans), max_size 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( container = Dimensions(
width - self.column_gutter * 2, height - self.row_gutter * 2 width - self.column_gutter * 2, height - self.row_gutter * 2
) )
column_names, column_tracks, column_count, column_size = resolve_tracks( 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( 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) grid_size = Dimensions(column_size, row_size)
@@ -323,7 +357,8 @@ class GridLayout(Layout):
self.column_align, self.column_align,
self.row_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 order += 1
# Widgets with no area assigned. # Widgets with no area assigned.
@@ -355,7 +390,8 @@ class GridLayout(Layout):
self.column_align, self.column_align,
self.row_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 order += 1
return map return map

View File

@@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from logging import getLogger
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
from rich.padding import Padding, PaddingDimensions from rich.padding import Padding, PaddingDimensions
from rich.segment import Segment from rich.segment import Segment
@@ -10,6 +12,8 @@ from .geometry import Dimensions, Point
from .message import Message from .message import Message
from .widget import Widget, Reactive from .widget import Widget, Reactive
log = getLogger("rich")
class PageUpdate(Message): class PageUpdate(Message):
def can_batch(self, message: "Message") -> bool: def can_batch(self, message: "Message") -> bool:

View File

@@ -135,6 +135,7 @@ class View(Widget):
region = self.get_widget_region(widget) region = self.get_widget_region(widget)
else: else:
widget, region = self.get_widget_at(event.x, event.y) widget, region = self.get_widget_at(event.x, event.y)
log.debug("WIDGET %r", widget)
except NoWidget: except NoWidget:
await self.app.set_mouse_over(None) await self.app.set_mouse_over(None)
else: else:

View 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)

View File

@@ -7,18 +7,19 @@ from rich.style import StyleType
from .. import events from .. import events
from ..layouts.grid import GridLayout
from ..message import Message from ..message import Message
from ..scrollbar import ScrollTo, ScrollBar from ..scrollbar import ScrollTo, ScrollBar
from ..geometry import clamp from ..geometry import clamp
from ..page import Page from ..page import Page
from ..views import DockView from ..view import View
from ..reactive import Reactive from ..reactive import Reactive
log = logging.getLogger("rich") log = logging.getLogger("rich")
class ScrollView(DockView): class ScrollView(View):
def __init__( def __init__(
self, self,
renderable: RenderableType | None = None, renderable: RenderableType | None = None,
@@ -29,12 +30,23 @@ class ScrollView(DockView):
) -> None: ) -> None:
self.fluid = fluid self.fluid = fluid
self._vertical_scrollbar = ScrollBar(vertical=True) self._vertical_scrollbar = ScrollBar(vertical=True)
self._horizontal_scrollbar = ScrollBar(vertical=False)
self._page = Page(renderable or "", style=style) 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) x: Reactive[float] = Reactive(0)
y: Reactive[float] = Reactive(0) y: Reactive[float] = Reactive(0)
target_x: Reactive[float] = Reactive(0)
target_y: Reactive[float] = Reactive(0) target_y: Reactive[float] = Reactive(0)
def validate_y(self, value: float) -> float: def validate_y(self, value: float) -> float:
@@ -52,8 +64,15 @@ class ScrollView(DockView):
self.require_repaint() self.require_repaint()
async def on_mount(self, event: events.Mount) -> None: async def on_mount(self, event: events.Mount) -> None:
await self.dock(self._vertical_scrollbar, edge="right", size=1) assert isinstance(self.layout, GridLayout)
await self.dock(self._page, edge="top") 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: def scroll_up(self) -> None:
self.target_y += 1.5 self.target_y += 1.5