Skip to content

Commit fe288e8

Browse files
committed
repaired scroll view
1 parent 426fa61 commit fe288e8

File tree

16 files changed

+98
-106
lines changed

16 files changed

+98
-106
lines changed

examples/new.py

Lines changed: 0 additions & 25 deletions
This file was deleted.

examples/simple.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,37 @@
22

33
from textual import events
44
from textual.app import App
5-
from textual.widgets.header import Header
6-
from textual.widgets.placeholder import Placeholder
7-
from textual.widgets.scroll_view import ScrollView
8-
9-
with open("richreadme.md", "rt") as fh:
10-
readme = Markdown(fh.read(), hyperlinks=True, code_theme="fruity")
5+
from textual.view import DockView
6+
from textual.widgets import Header, Footer, Placeholder, ScrollView
117

128

139
class MyApp(App):
10+
"""An example of a very simple Textual App"""
1411

15-
KEYS = {"q": "quit", "ctrl+c": "quit", "b": "view.toggle('left')"}
12+
async def on_load(self, event: events.Load) -> None:
13+
await self.bind("q,ctrl+c", "quit")
14+
await self.bind("b", "view.toggle('sidebar')")
1615

1716
async def on_startup(self, event: events.Startup) -> None:
18-
await self.view.mount_all(
19-
header=Header(self.title), left=Placeholder(), body=ScrollView(readme)
20-
)
17+
view = await self.push_view(DockView())
18+
header = Header(self.title)
19+
footer = Footer()
20+
sidebar = Placeholder(name="sidebar")
21+
22+
with open("richreadme.md", "rt") as fh:
23+
readme = Markdown(fh.read(), hyperlinks=True)
24+
25+
body = ScrollView(readme)
26+
27+
footer.add_key("b", "Toggle sidebar")
28+
footer.add_key("q", "Quit")
29+
30+
await view.dock(header, edge="top")
31+
await view.dock(footer, edge="bottom")
32+
await view.dock(sidebar, edge="left", size=30)
33+
await view.dock(body, edge="right")
34+
self.require_layout()
2135

2236

23-
app = MyApp()
37+
app = MyApp(title="Simple App")
2438
app.run()

src/textual/_xterm_parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def parse_mouse_code(self, code: str, sender: MessageTarget) -> events.Event | N
4141
event: events.Event
4242
if buttons & 64:
4343
event = (
44-
events.MouseScrollUp if button == 1 else events.MouseScrollDown
44+
events.MouseScrollDown if button == 1 else events.MouseScrollUp
4545
)(sender, x, y)
4646
else:
4747
event = (

src/textual/app.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from __future__ import annotations
2-
from argparse import Action
2+
import os
33

44
import asyncio
55

@@ -18,7 +18,7 @@
1818
from . import events
1919
from . import actions
2020
from ._animator import Animator
21-
from .geometry import Point, Region
21+
from .geometry import Region
2222
from ._context import active_app
2323
from .keys import Binding
2424
from .driver import Driver
@@ -239,13 +239,16 @@ async def shutdown(self):
239239
await self.close_messages()
240240

241241
def refresh(self) -> None:
242+
sync_available = os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal"
242243
if not self._closed:
243244
console = self.console
244245
try:
245-
console.file.write("\x1bP=1s\x1b\\")
246+
if sync_available:
247+
console.file.write("\x1bP=1s\x1b\\")
246248
with console:
247249
console.print(Screen(Control.home(), self.view, Control.home()))
248-
console.file.write("\x1bP=2s\x1b\\")
250+
if sync_available:
251+
console.file.write("\x1bP=2s\x1b\\")
249252
except Exception:
250253
log.exception("refresh failed")
251254

@@ -345,10 +348,10 @@ async def action_bell(self) -> None:
345348

346349
from rich.panel import Panel
347350

348-
from .widgets.header import Header
349-
from .widgets.footer import Footer
351+
from .widgets import Header
352+
from .widgets import Footer
350353

351-
from .widgets.placeholder import Placeholder
354+
from .widgets import Placeholder
352355
from .scrollbar import ScrollBar
353356

354357
from rich.markdown import Markdown

src/textual/layout.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def get_offset(self, widget: Widget) -> Point:
123123
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
124124
"""Get the widget under the given point or None."""
125125
for widget, region in self:
126-
if region.contains(x, y):
126+
if widget.is_visual and region.contains(x, y):
127127
return widget, region
128128
raise NoWidget(f"No widget under screen coordinate ({x}, {y})")
129129

@@ -181,6 +181,9 @@ def render(widget: Widget, width: int, height: int) -> Lines:
181181
return lines
182182

183183
for widget, region, _order in widget_regions:
184+
185+
if not widget.is_visual:
186+
continue
184187
region_lines = self.renders.get(widget)
185188
if region_lines is not None:
186189
yield region_lines
@@ -279,7 +282,6 @@ def __rich_console__(
279282

280283
def update_widget(self, console: Console, widget: Widget) -> LayoutUpdate | None:
281284
if widget not in self.renders:
282-
log.debug("WIDGET %r", self.renders)
283285
return None
284286
region, lines = self.renders[widget]
285287
new_lines = console.render_lines(

src/textual/layouts/dock.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,13 @@ def generate_map(
6161
layers: dict[int, Region] = defaultdict(lambda: layout_region)
6262

6363
def add_widget(widget: Widget, region: Region, order: tuple[int, int]):
64-
region = region + offset + widget.layout_offset
64+
region = region + offset + widget.layout_offset
65+
map[widget] = MapRegion(region, order)
6566
if isinstance(widget, View):
6667
sub_map = widget.layout.generate_map(
6768
region.width, region.height, offset=region.origin
6869
)
6970
map.update(sub_map)
70-
else:
71-
map[widget] = MapRegion(region, order)
7271

7372
for index, dock in enumerate(self.docks):
7473
dock_options = [

src/textual/page.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ def watch_y(self, new: int) -> None:
9696
self._page.offset = Point(x, new)
9797

9898
def update(self, renderable: RenderableType | None = None) -> None:
99-
10099
if renderable:
101100
self._page.update(renderable)
102101
else:

src/textual/scrollbar.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,6 @@ def __rich_repr__(self) -> RichReprResult:
166166
yield "window_size", self.window_size
167167
yield "position", self.position
168168

169-
__rich_repr__.angular = True
170-
171169
def render(self) -> RenderableType:
172170
return ScrollBarRender(
173171
virtual_size=self.virtual_size,

src/textual/view.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,17 @@
77
from typing import cast, Iterable, Optional, Tuple, TYPE_CHECKING
88

99
from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
10-
from rich.region import Region as LayoutRegion
1110
import rich.repr
12-
from rich.segment import Segments
1311
from rich.style import Style
1412

1513
from . import events
16-
from ._context import active_app
1714
from .layout import Layout, NoWidget
1815
from .layouts.dock import DockEdge, DockLayout, Dock
1916
from .geometry import Dimensions, Point, Region
2017
from .messages import UpdateMessage, LayoutMessage
2118

22-
from .widget import StaticWidget, Widget, WidgetID, Widget
23-
from .widgets.header import Header
19+
from .widget import Widget, Widget
20+
2421

2522
if TYPE_CHECKING:
2623
from .app import App
@@ -48,6 +45,10 @@ def __rich_console__(
4845
def __rich_repr__(self) -> rich.repr.RichReprResult:
4946
yield "name", self.name
5047

48+
@property
49+
def is_visual(self) -> bool:
50+
return False
51+
5152
@property
5253
def is_root_view(self) -> bool:
5354
return self.parent is self.app
@@ -69,12 +70,8 @@ async def message_update(self, message: UpdateMessage) -> None:
6970
self.app.display(display_update)
7071

7172
async def message_layout(self, message: LayoutMessage) -> None:
72-
7373
await self.root_view.refresh_layout()
7474

75-
# async def on_create(self, event: events.Created) -> None:
76-
# await self.mount(Header(self.title))
77-
7875
async def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None:
7976

8077
name_widgets: Iterable[tuple[str | None, Widget]]
@@ -101,13 +98,13 @@ async def refresh_layout(self) -> None:
10198
self.layout.reflow(width, height)
10299
self.app.refresh()
103100

104-
# for widget, region in self.layout:
105-
# if isinstance(widget, Widget):
106-
# await widget.post_message(
107-
# events.Resize(self, region.width, region.height)
108-
# )
101+
for widget, region in self.layout:
102+
if isinstance(widget, Widget):
103+
await widget.post_message(
104+
events.Resize(self, region.width, region.height)
105+
)
109106

110-
async def on_resize(self, event: events.Resize) -> None:
107+
async def on_resize(self, event: events.Resize) -> None:
111108
self.size = Dimensions(event.width, event.height)
112109
await self.refresh_layout()
113110

@@ -171,10 +168,10 @@ async def forward_event(self, event: events.Event) -> None:
171168
await self.focused.forward_event(event)
172169

173170
async def action_toggle(self, name: str) -> None:
174-
log.debug("%r", self.named_widgets)
175171
widget = self.named_widgets[name]
176172
widget.visible = not widget.visible
177-
await self.refresh_layout()
173+
await self.post_message(LayoutMessage(self))
174+
# await self.refresh_layout()
178175

179176

180177
class DoNotSet:
@@ -194,6 +191,7 @@ async def dock(
194191
edge: DockEdge = "top",
195192
z: int = 0,
196193
size: int | None | DoNotSet = do_not_set,
194+
name: str | None = None
197195
) -> Widget | tuple[Widget, ...]:
198196

199197
dock = Dock(edge, widgets, z)

src/textual/widget.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ def __init__(self, name: str | None = None) -> None:
6767

6868
super().__init__()
6969

70-
visible: Reactive[bool] = Reactive(True)
70+
visible: Reactive[bool] = Reactive(True, layout=True)
7171
layout_size: Reactive[int | None] = Reactive(None)
7272
layout_fraction: Reactive[int] = Reactive(1)
7373
layout_minimim_size: Reactive[int] = Reactive(1)
7474
layout_offset_x: Reactive[float] = Reactive(0, layout=True)
75-
layout_offset_y: Reactive[float] = Reactive(0)
75+
layout_offset_y: Reactive[float] = Reactive(0, layout=True)
7676

7777
def __init_subclass__(cls, can_focus: bool = True) -> None:
7878
super().__init_subclass__()
@@ -84,6 +84,10 @@ def __rich_repr__(self) -> rich.repr.RichReprResult:
8484
def __rich__(self) -> RenderableType:
8585
return self.render()
8686

87+
@property
88+
def is_visual(self) -> bool:
89+
return True
90+
8791
@property
8892
def app(self) -> "App":
8993
"""Get the current app."""
@@ -200,17 +204,8 @@ async def on_mouse_move(self, event: events.MouseMove) -> None:
200204
style_under_cursor = self.get_style_at(event.x, event.y)
201205
log.debug("%r", style_under_cursor)
202206

203-
# async def on_mouse_up(self, event: events.MouseUp) -> None:
204-
# style = self.get_style_at(event.x, event.y)
205-
# if "@click" in style.meta:
206-
# log.debug(style._link_id)
207-
# await self.app.action(style.meta["@click"], default_namespace=self)
208-
209-
210-
class StaticWidget(Widget):
211-
def __init__(self, renderable: RenderableType, name: str | None = None) -> None:
212-
super().__init__(name)
213-
self.renderable = renderable
214-
215-
def render(self) -> RenderableType:
216-
return self.renderable
207+
async def on_mouse_up(self, event: events.MouseUp) -> None:
208+
style = self.get_style_at(event.x, event.y)
209+
if "@click" in style.meta:
210+
log.debug(style._link_id)
211+
await self.app.action(style.meta["@click"], default_namespace=self)

0 commit comments

Comments
 (0)