Skip to content

Commit 82d77cf

Browse files
committed
scroll widget under mouse
1 parent bfb9490 commit 82d77cf

File tree

6 files changed

+57
-35
lines changed

6 files changed

+57
-35
lines changed

poetry.lock

Lines changed: 14 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ python = "^3.7"
1010
rich = "^10.3.0"
1111

1212
[tool.poetry.dev-dependencies]
13-
#rich = {git = "[email protected]:willmcgugan/rich", rev = "tui"}
13+
rich = {git = "[email protected]:willmcgugan/rich", rev = "pretty-classes"}
1414

1515
[build-system]
1616
requires = ["poetry-core>=1.0.0"]

src/textual/app.py

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
import signal
77
from typing import Any, ClassVar, Type
8+
import warnings
89

910
from rich.control import Control
1011
from rich.repr import rich_repr, RichReprResult
@@ -23,6 +24,14 @@
2324
log = logging.getLogger("rich")
2425

2526

27+
# asyncio will warn against resources not being cleared
28+
warnings.simplefilter("always", ResourceWarning)
29+
# https://github.com/boto/boto3/issues/454
30+
warnings.filterwarnings(
31+
"ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>"
32+
)
33+
34+
2635
LayoutDefinition = "dict[str, Any]"
2736

2837
try:
@@ -76,28 +85,36 @@ def on_keyboard_interupt(self) -> None:
7685
asyncio.run_coroutine_threadsafe(self.post_message(event), loop=loop)
7786

7887
async def process_messages(self) -> None:
88+
try:
89+
await self._process_messages()
90+
except Exception:
91+
self.console.print_exception(show_locals=True)
92+
93+
async def _process_messages(self) -> None:
7994
log.debug("driver=%r", self.driver)
8095
loop = asyncio.get_event_loop()
8196

8297
loop.add_signal_handler(signal.SIGINT, self.on_keyboard_interupt)
8398
driver = self.driver(self.console, self)
84-
try:
85-
driver.start_application_mode()
86-
except Exception:
87-
log.exception("error starting application mode")
88-
raise
8999

90100
active_app.set(self)
91101

92102
await self.add(self.view)
93103

94104
await self.post_message(events.Startup(sender=self))
95-
self.refresh()
96105
try:
106+
driver.start_application_mode()
107+
except Exception:
108+
log.exception("error starting application mode")
109+
raise
110+
try:
111+
self.refresh()
97112
await super().process_messages()
98113
finally:
99-
loop.remove_signal_handler(signal.SIGINT)
100-
driver.stop_application_mode()
114+
try:
115+
driver.stop_application_mode()
116+
finally:
117+
loop.remove_signal_handler(signal.SIGINT)
101118

102119
await asyncio.gather(*(child.close_messages() for child in self.children))
103120
self.children.clear()
@@ -116,7 +133,6 @@ async def on_event(self, event: events.Event, priority: int) -> None:
116133
if isinstance(event, events.Key):
117134
key_action = self.KEYS.get(event.key, None)
118135
if key_action is not None:
119-
log.debug("action %r", key_action)
120136
await self.action(key_action)
121137
return
122138

@@ -149,18 +165,11 @@ async def dispatch_action(
149165
self, destination: str, action_name: str, params: Any
150166
) -> None:
151167
action_target = self._action_targets.get(destination, None)
152-
log.debug("ACTION TARGET %r", action_target)
153168
if action_target is not None:
154169
method_name = f"action_{action_name}"
155170
method = getattr(action_target, method_name, None)
156-
log.debug("ACTION METHOD %r", method)
157171
if method is not None:
158-
try:
159-
await method(*params)
160-
except Exception:
161-
log.exception(
162-
f"error in action {destination}.{action_name}{params!r}"
163-
)
172+
await method(*params)
164173

165174
async def on_shutdown_request(self, event: events.ShutdownRequest) -> None:
166175
log.debug("shutdown request")
@@ -187,6 +196,9 @@ async def on_mouse_scroll_down(self, event: events.MouseScrollUp) -> None:
187196
async def action_quit(self) -> None:
188197
await self.close_messages()
189198

199+
async def action_bang(self) -> None:
200+
1 / 0
201+
190202

191203
if __name__ == "__main__":
192204
import asyncio
@@ -212,7 +224,7 @@ async def action_quit(self) -> None:
212224

213225
class MyApp(App):
214226

215-
KEYS = {"q": "quit", "ctrl+c": "quit", "b": "view.toggle('left')"}
227+
KEYS = {"q": "quit", "x": "bang", "ctrl+c": "quit", "b": "view.toggle('left')"}
216228

217229
async def on_startup(self, event: events.Startup) -> None:
218230
await self.view.mount_all(

src/textual/message_pump.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,9 @@ async def process_messages(self) -> None:
144144
priority, message = await self.get_message()
145145
except MessagePumpClosed:
146146
break
147-
except Exception:
148-
log.exception("error getting message")
149-
break
147+
except Exception as error:
148+
raise error from None
149+
150150
log.debug("%r -> %r", message, self)
151151
# Combine any pending messages that may supersede this one
152152
while True:
@@ -157,8 +157,9 @@ async def process_messages(self) -> None:
157157

158158
try:
159159
await self.dispatch_message(message, priority)
160-
except Exception:
161-
log.exception("error dispatching %r", message)
160+
except Exception as error:
161+
raise
162+
162163
finally:
163164
if self._message_queue.empty():
164165
idle_handler = getattr(self, "on_idle", None)

src/textual/scrollbar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def render_bar(
7575
start_index, start_bar = divmod(start, 8)
7676
end_index, end_bar = divmod(end, 8)
7777

78-
if start_index == end_index:
78+
if window_size / step_size <= 1:
7979
if start_index < len(segments):
8080
segments[start_index] = _Segment(" ", _Style(bgcolor=bar))
8181
else:

src/textual/view.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ async def forward_input_event(self, event: events.Event) -> None:
218218
pass
219219
else:
220220
await widget.forward_input_event(event)
221+
elif isinstance(event, (events.MouseScrollDown, events.MouseScrollUp)):
222+
widget, _region = self.get_widget_at(event.x, event.y)
223+
scroll_widget = widget or self.focused
224+
if scroll_widget is not None:
225+
await scroll_widget.forward_input_event(event)
221226
else:
222227
if self.focused is not None:
223228
await self.focused.forward_input_event(event)

0 commit comments

Comments
 (0)